近期发现测试的项目中有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. saltstack通过salt.client执行命令(转)

    利用saltstack的salt.client模块可以在python的命令行下或者python脚本里执行相应的salt命令 master端想要执行类似 salt '*' cmd.run 'uptime ...

  2. 观察者模式--java jdk中提供的支持

    一.简介 观察者设计模式有如下四个角色 抽象主题角色:把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者.抽象主题提供一个接口,可以增加和删除观察者角色.一般用一个抽象 ...

  3. JavaScript中点号“.”的多义性

    点号「.」在JavaScript中有两种语义 语义1.表示算术中的小数点(浮点数),如 2.5 语义2.取对象属性.方法,如 [].push(2) 这几乎没有任何难理解的地方,但下面这个问题则很有趣. ...

  4. XNote Ver:0.79

    隐藏主窗后,双击小图标显示主窗. 支持拖拉网页文字到小图标上,直接在当前项目上创建下级资料项目. 项目分类限50个汉字.

  5. 基于jython操作hbase

    一.前言 关于jython介绍,直接上官网www.jython.org,可以得到详细资料,这里只介绍一下jython操作hbase的一些方法,本质上和用java操作hbase差不多,只不过语法换成了p ...

  6. 字符串长度函数strlen()

    如下是我的测试文件: #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { ...

  7. [转]Flash ActionScript2.0面向对象游戏开发-推箱子

    本文转自:http://www.alixixi.com/Dev/W3C/Flash/2007/2007070868666.html 概述: Flash ActionScript2.0是一种面向对向的编 ...

  8. HDU 4409 Family Name List --乱搞、LCA

    题意: 给出一些名字,名字间有父子关系,有三种操作: 1.按祖先到后代,兄弟间按字典序由小到大排序,然后输出 2.求某个节点的兄弟节点有多少个,包括自己(注意,根节点的兄弟节点是1) 3.求节点a和b ...

  9. bootstrap学习总结-css样式设计(一)

    由于项目需要,所以打算好好学习下bootstrap框架,之前了解一点,框架总体不难,但涉及到的东西还是很多,想要熟练掌握它,还是要多练练. 一:bootstrap是什么? bs是什么?  即前端页面搭 ...

  10. MySQL数据库学习笔记(三)----基本的SQL语句

    ​[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/ ...