近期发现测试的项目中有JAVA内存泄露的现象。虽然JAVA有垃圾回收的机制,但是如果不及时释放引用就会发生内存泄露现象。在实际工作中我们使用Jprofiler调用java自带的 jmap来做检测还是很快能够定位到错误。不过亡羊补牢不如先把羊圈修补得好一些。下面这篇文章给出了几种常见的内存泄露类型。大家coding的时候注意一下。

btw,一些静态代码扫描工具也能检测出不好的编程习惯带来潜在的内存泄露的风险。

----------------------------------------------------------------------------------------------------------------------------------------------------------------------

ava平台的一个突出的特性是自动内存管理。很多人把这种特性误读为Java没有内存泄露。然而,在我印象中,现代Java框架以及基于Java的平台并非如此。特别是Android平台,能举出很多反例。为了让大家对Java平台的内存泄露有一个初步的认识,我们先来看一个Java实现的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class SimpleStack {
 
    private final Object[] objectPool = new Object[10];
    private int pointer = -1;
 
    public Object pop() {
        if(pointer < 0) {
            throw new IllegalStateException("no elements on stack");
        }
        return objectPool[pointer--];
    }
 
    public Object peek() {
        if(pointer < 0) {
            throw new IllegalStateException("no elements on stack");
        }
        return objectPool[pointer];
 
    }
 
    public void push(Object object) {
        if(pointer > 8) {
            throw new IllegalStateException("stack overflow");
        }
        objectPool[++pointer] = object;
    }
}

这个栈的实现基于一个对象数组,并维护了一个用于指向栈内当前可用单元的整型指针。上面的实现中,每次从栈顶弹出元素都会产生内存泄露。确切的说,即使不再使用栈顶元素,对象数组会继续持有栈顶元素的引用(除非栈顶元素再次入栈,栈顶元素的引用会被完全相同的引用覆盖)。因此,即便这个对象的其他引用都被释放,Java虚拟机也不能回收这个对象。由于这种栈实现并不允许外界直接访问其底层的对象池,因此除非有新元素入栈并被放置在栈内的同一个位置上,否则这个无法访问的引用将阻止垃圾回收器回收该对象。

幸运的是,这个内存泄露很容易修复:

1
2
3
4
5
6
7
8
9
10
public Object pop() {
    if (pointer < 1) {
        throw new IllegalStateException("no elements on stack");
    }
    try {
        return objectPool[pointer];
    } finally {
        objectPool[pointer--] = null;
    }
}

当然,在日常的Java开发中一般不会去实现一个内存数据结构。因此,让我们来看一个更常见的Java内存泄漏的例子。在Java开发中经常用到的观察者模式就会引起内存泄露:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Observed {
 
    public interface Observer {
        void update();
    }
 
    private Collection<Observer> observers = new HashSet<Observer>();
 
    void addListener(Observer observer) {
        observers.add(observer);
    }
 
    void removeListener(Observer observer) {
        observers.remove(observer);
    }
 
}

这次提供了一个直接删除底层对象池引用的方法。基于这种实现,任何已注册的Observer在使用后只要被正确注销,就不会存在内存泄漏的风险。然而,假设这样一个场景,框架的使用者在使用完Observer之后并没有及时注销。同理Observer将永远不会被回收,因为Observed一直保留着它的引用。更糟的是,没有Observer引用,是无法从Observed对象池外部删除Observer的,即无法回收未被及时注销的Observer

不过,有一种简单的方法能够修复这种潜在的内存泄露——弱引用。我个人认为这是Java程序员都应该知道的特性。简单地说,弱引用在功能上和普通的引用一样,但它不会妨碍垃圾回收。因此JVM执行垃圾回收时,如果没有发现强引用,那么你就会发现弱引用会被置为null。要使用弱引用,我们可以将上面的代码改为:

1
2
private Collection<Observer> observers = Collections.newSetFromMap(
        new WeakHashMap<Observer, Boolean>());

WeakHashMap是一个现成的弱引用Map,Map的键都是弱引用对象。使用WeakHashMap后,被观察者将不会阻止JVM对Observer进行垃圾回收。然而,你必须在代码注释中强调这一点。因为这个特性可能引起一些问题,比如使用者想要注册一个常驻内存的Observer(例如日志库),但他们并没有打算维持一个Observer引用。例如,Android平台上的OnSharedPreferencesChangeListener使用了弱引用,但文档中并没有声明这一特性。这给开发者带来了很多麻烦。

在本文的开头我提到了,现在的很多框架都需要使用者谨慎地管理内存。我想至少有两个例子可以印证这个观点。

Android平台

Android应用程序的核心类采用了基于生命周期的编程模型。这意味着你不能自行创建和管理这些类的实例,这些实例将由Android操作系统在需要的时候替你创建(比如应用程序需要显示某个特定的画面)。同理,Android操作系统将会决定应用何时不再需要某个特定实例(比如用户关闭了应用界面),并通过调用该实例特定的生命周期方法来通知该实例即将被删除。但是,如果你将这个实例的引用泄露到某个全局上下文,Android JVM将不能对这个实例进行回收。这与Android本身的设计理念相违背。由于Android手机通常没有限制应用程序的内存,即使在非常简单的应用中,也会频繁创建和销毁对象,所以在清理引用时必须格外小心。

不幸的是,应用程序核心类引用很容易被泄露到外部。你能看出下面的例子是如何泄露引用的吗?

1
2
3
4
5
6
7
8
9
10
11
12
class ExampleActivity extends Activity {
 
    @Override
    public void onCreate(Bundle bundle) {
        startService(new Intent(this, ExampleService.class).putExtra("mykey",
                new Serializable() {
                    public String getInfo() {
                        return "myinfo";
                    }
                }));
    }
}

如果你认为是传入Intent构造函数的this指针泄露了当前实例的引用,你就错了。这个Intent对象仅用于启动ExampleService,它会在ExampleService启动之后被销毁。然而,那个实现了Serializable接口的匿名内部类会持有闭包类ExampleActivity的引用。如果ExampleService一直维持着这个匿名类实例引用,那么也会持有这个ExampleActivity实例的引用。

出于这个原因,我建议Android开发者避免使用匿名类。

Web应用框架(特别是Wicket

Web应用框架通常将半永久性的用户数据存放在Session中。你在Session中写入的任何数据都会在内存中滞留,而且滞留的时间无法确定。如果有一定数量的访问者在你的Session中“乱扔垃圾”,运行Servlet容器的JVM早晚会挂掉。因此,你谨慎管理引用的另一个极端案例就是Wicket框架:Wicket框架会将用户的所有访问序列化成历史版本。这种过分简单的设计意味着,如果某个访问者点击十次欢迎页面,Wicket框架会在硬盘默认路径下序列化十个对象。Wicket页面对象持有的所有对象引用都会和页面对象一起被序列化到硬盘上,所以在管理引用时必须格外小心。

让我们来看一个错误使用Wicket框架的示例:

1
2
3
4
5
6
7
8
class ExampleWelcomePage extends WebPage {
 
    private final List<People> peopleList;
 
    public ExampleWelcomePage (PageParameters pageParameters) {
        peopleList = new Service().getWorldPhonebook();
    }
}

用户点击十次欢迎页面,就会在服务器硬盘上存储十份WorldPhoneBook拷贝。因此,在你使用Wicket开发应用时,务必要使用LoadableDetachableModels管理引用。

在Java程序中追踪内存泄漏是一件非常麻烦的事情,因此我想推荐一款非常好用的(但很可惜不是免费的)调式工具:JProfiler。它能够提供Java程序运行时的堆快照(heap dumps),帮助你了解程序运行时内部的具体情况。如果你的程序存在内存泄露的问题,我推荐你试一试JProfiler。JProfiler提供免费试用许可证。

更多阅读:如果你想要了解由自定义类加载器所引起的另一种内存泄露,请参阅Zeroturnaround博客

原文链接: javacodegeeks 翻译: ImportNew.com夏千林
译文链接: http://www.importnew.com/8935.html

[转载]Java应用程序中的内存泄漏及内存管理的更多相关文章

  1. java 内存泄漏和内存溢出

    参考:https://blog.csdn.net/eff666/article/details/52784724 1.内存溢出  内存溢出:OOM(OutOfMemoryError)异常,即程序需要内 ...

  2. 在Java Web程序中使用监听器可以通过以下两种方法

    之前学习了很多涉及servlet的内容,本小结我们说一下监听器,说起监听器,编过桌面程序和手机App的都不陌生,常见的套路都是拖一个控件,然后给它绑定一个监听器,即可以对该对象的事件进行监听以便发生响 ...

  3. 在并发Java应用程序中检测可见性错误

    了解什么是可见性错误,为什么会发生,以及如何在并发Java应用程序中查找难以捉摸的可见性错误.这些问题你可能也遇到过,当在优锐课学习了一段时间后,我对这些问题有了一定见解,写下这篇文章和大家分享. 检 ...

  4. 在 Java 应用程序中使用 Elasticsearch

    如果您使用过 Apache Lucene 或 Apache Solr,就会知道它们的使用体验非常有趣.尤其在您需要扩展基于 Lucene 或 Solr 的解决方案时,您就会了解 Elasticsear ...

  5. 在Java Web程序中使用Hibernate

    在Java Web程序中使用Hibernate与普通Java程序一样.本文中将使用Servlet和JSP结合Hibernate实现数据库表的增删改查操作. Web程序中,hibernate.cfg.x ...

  6. 在 Java 应用程序中绑定 Bean 和数据

    本指南介绍了 NetBeans IDE 对 Java 应用程序中 Bean 绑定和数据绑定的支持. 要学完本教程,您需要具备以下软件和资源. 软件或资源 要求的版本 NetBeans IDE 版本 7 ...

  7. Java应用程序中的声音播放

    声音可以创造意境,触发遐想,当与虚拟图像相结合时,更加可以让整个世界充满幻觉,声音是多媒体技术的基础. 播放声音是Java对多媒体的支持一个重要部分,它支持的声音文件类型主要有: AU - (扩展名为 ...

  8. 在 Java 应用程序中加一些 Groovy 进来

    如果您一直在阅读这个系列,那么您应该已经看到有各种各样使用 Groovy 的有趣方式,Groovy 的主要优势之一就是它的生产力.Groovy 代码通常要比 Java 代码更容易编写,而且编写起来也更 ...

  9. (转)从内存管 理、内存泄漏、内存回收探讨C++内存管理

    http://www.cr173.com/html/18898_all.html 内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟 ...

随机推荐

  1. 定制Asp.NET 5 MVC内建身份验证机制 - 基于自建SQL Server用户/角色数据表的表单身份验证

    背景 在需要进行表单认证的Asp.NET 5 MVC项目被创建后,往往需要根据项目的实际需求做一系列的工作对MVC 5内建的身份验证机制(Asp.NET Identity)进行扩展和定制: Asp.N ...

  2. Effective Java 44 Write doc comments for all exposed API elements

    Principle You must precede every exported class, interface, constructor, method, and field declarati ...

  3. SqlBulkCopy块拷贝数据时,不履行触发器和束缚 解决办法

    在new SqlBulkCopy时,设置SqlBulkCopyOptions属性即可 SqlBulkCopy bulkCopy = new SqlBulkCopy(ConStr,SqlBulkCopy ...

  4. 烂泥:NFS存储与VSphere配合使用

    本文首发于烂泥行天下. 公司服务器的虚拟化使用的是VM ESXi 5.0,为了更有效的利用服务器的硬盘空间.就把所有的镜像文件存放到另外一台linux服务器上,这样在使用vsphere安装虚拟机时可以 ...

  5. Centos Ping不通外网

    安装完成Vm,Centos6.5,设置了网络: 1.VM虚拟网络,采用桥接模式. 2.Centos里各种 设置ifcfg-eth0中的GETWAY,ADDIP等等 vim /etc/sysconfig ...

  6. Linux基础问答

    1.简述TCP三次握手四次挥手过程及各过程中客户端和服务器端的状态. 1 2 3 4 5 6 7 8 9 10 11 12 13 #三次握手 客户端向服务器端发送SYN包,客户端进入SYN_SEND状 ...

  7. IE6/7/8不支持jQuery创建非闭合格式的链接A

    代码如下 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <scri ...

  8. web.xml文件报错:cvc-complex-type.2.4.a: Invalid content was found starting with element 'init-param'.

    <?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" ...

  9. selenium如何解决IE自动填充表单问题

    有时候用selenium会碰到自动填充表单的问题,如输入用户名后,密码自动填充,此时再填充密码会导致登录失败,解决办法:每个输入框都调用clear()方法

  10. 【ASP.NET 进阶】根据IP地址返回对应位置信息

    其实就是使用了百度的IP库的功能接口,然后处理下就行了,效果图如下: 准备工作: 1.注册成为开度开发者,创建应用获得百度API调用的AK秘钥,百度开发中心地址:http://developer.ba ...