`

用弱引用堵住内存泄漏

阅读更多
虽然用 Java™ 语言编写的程序在理论上是不会出现“内存泄漏”的,但是有时对象在不再作为程序的逻辑状态的一部分之后仍然不被垃圾收集。本月,负责保障应用程序健康的工程师 Brian Goetz 探讨了无意识的对象保留的常见原因,并展示了如何用弱引用堵住泄漏。
  要让垃圾收集(GC)回收程序不再使用的对象,对象的逻辑 生命周期(应用程序使用它的时间)和对该对象拥有的引用的实际 生命周期必须是相同的。在大多数时候,好的软件工程技术保证这是自动实现的,不用我们对对象生命周期问题花费过多心思。但是偶尔我们会创建一个引用,它在内存中包含对象的时间比我们预期的要长得多,这种情况称为无意识的对象保留(unintentional object retention)。

  全局 Map 造成的内存泄漏

  无意识对象保留最常见的原因是使用 Map 将元数据与临时对象(transient object)相关联。假定一个对象具有中等生命周期,比分配它的那个方法调用的生命周期长,但是比应用程序的生命周期短,如客户机的套接字连接。需要将一些元数据与这个套接字关联,如生成连接的用户的标识。在创建 Socket 时是不知道这些信息的,并且不能将数据添加到 Socket 对象上,因为不能控制 Socket 类或者它的子类。这时,典型的方法就是在一个全局 Map 中存储这些信息,如清单 1 中的 SocketManager 类所示:

  清单 1. 使用一个全局 Map 将元数据关联到一个对象

public class SocketManager {
    private Map m = new HashMap();
   
    public void setUser(Socket s, User u) {
        m.put(s, u);
    }
    public User getUser(Socket s) {
        return m.get(s);
    }
    public void removeUser(Socket s) {
        m.remove(s);
    }
}

SocketManager socketManager;
...
socketManager.setUser(socket, user);

  这种方法的问题是元数据的生命周期需要与套接字的生命周期挂钩,但是除非准确地知道什么时候程序不再需要这个套接字,并记住从 Map 中删除相应的映射,否则,Socket 和 User 对象将会永远留在 Map 中,远远超过响应了请求和关闭套接字的时间。这会阻止 Socket 和 User 对象被垃圾收集,即使应用程序不会再使用它们。这些对象留下来不受控制,很容易造成程序在长时间运行后内存爆满。除了最简单的情况,在几乎所有情况下找出什么时候 Socket 不再被程序使用是一件很烦人和容易出错的任务,需要人工对内存进行管理。

  找出内存泄漏

  程序有内存泄漏的第一个迹象通常是它抛出一个 OutOfMemoryError,或者因为频繁的垃圾收集而表现出糟糕的性能。幸运的是,垃圾收集可以提供能够用来诊断内存泄漏的大量信息。如果以 -verbose:gc 或者 -Xloggc 选项调用 JVM,那么每次 GC 运行时在控制台上或者日志文件中会打印出一个诊断信息,包括它所花费的时间、当前堆使用情况以及恢复了多少内存。记录 GC 使用情况并不具有干扰性,因此如果需要分析内存问题或者调优垃圾收集器,在生产环境中默认启用 GC 日志是值得的。

  有工具可以利用 GC 日志输出并以图形方式将它显示出来,JTune 就是这样的一种工具(请参阅 参考资料)。观察 GC 之后堆大小的图,可以看到程序内存使用的趋势。对于大多数程序来说,可以将内存使用分为两部分:baseline 使用和 current load 使用。对于服务器应用程序,baseline 使用就是应用程序在没有任何负荷、但是已经准备好接受请求时的内存使用,current load 使用是在处理请求过程中使用的、但是在请求处理完成后会释放的内存。只要负荷大体上是恒定的,应用程序通常会很快达到一个稳定的内存使用水平。如果在应用程序已经完成了其初始化并且负荷没有增加的情况下,内存使用持续增加,那么程序就可能在处理前面的请求时保留了生成的对象。

  清单 2 展示了一个有内存泄漏的程序。MapLeaker 在线程池中处理任务,并在一个 Map 中记录每一项任务的状态。不幸的是,在任务完成后它不会删除那一项,因此状态项和任务对象(以及它们的内部状态)会不断地积累。

  清单 2. 具有基于 Map 的内存泄漏的程序

public class MapLeaker {
    public ExecutorService exec = Executors.newFixedThreadPool(5);
    public Map taskStatus
        = Collections.synchronizedMap(new HashMap());
    private Random random = new Random();

    private enum TaskStatus { NOT_STARTED, STARTED, FINISHED };

    private class Task implements Runnable {
        private int[] numbers = new int[random.nextInt(200)];

        public void run() {
            int[] temp = new int[random.nextInt(10000)];
            taskStatus.put(this, TaskStatus.STARTED);
            doSomeWork();
            taskStatus.put(this, TaskStatus.FINISHED);
        }
    }

    public Task newTask() {
        Task t = new Task();
        taskStatus.put(t, TaskStatus.NOT_STARTED);
        exec.execute(t);
        return t;
    }
}
                   
  确信有了内存泄漏后,下一步就是找出哪种对象造成了这个问题。所有内存分析器都可以生成按照对象类进行分解的堆快照。有一些很好的商业堆分析工具,但是找出内存泄漏不一定要花钱买这些工具 ?? 内置的 hprof 工具也可完成这项工作。要使用 hprof 并让它跟踪内存使用,需要以 -Xrunhprof:heap=sites 选项调用 JVM。

  清单 3 显示分解了应用程序内存使用的 hprof 输出的相关部分。(hprof 工具在应用程序退出时,或者用 kill -3 或在 Windows 中按 Ctrl+Break 时生成使用分解。)注意两次快照相比,Map.Entry、Task 和 int[] 对象有了显著增加。

  请参阅 清单 3。

  清单 4 展示了 hprof 输出的另一部分,给出了 Map.Entry 对象的分配点的调用堆栈信息。这个输出告诉我们哪些调用链生成了 Map.Entry 对象,并带有一些程序分析,找出内存泄漏来源一般来说是相当容易的。

  清单 4. HPROF 输出,显示 Map.Entry 对象的分配点

TRACE 300446:
java.util.HashMap$Entry.(:Unknown line)
java.util.HashMap.addEntry(:Unknown line)
java.util.HashMap.put(:Unknown line)
java.util.Collections$SynchronizedMap.put(:Unknown line)
com.quiotix.dummy.MapLeaker.newTask(MapLeaker.java:48)
com.quiotix.dummy.MapLeaker.main(MapLeaker.java:64)

分享到:
评论

相关推荐

    Java 理论与实践:用弱引用堵住内存泄漏

    本月,负责保障应用程序健康的工程师 Brian Goetz 探讨了无意识的对象保留的常见原因,并展示了如何用弱引用堵住泄漏。  要让垃圾收集(GC)回收程序不再使用的对象,对象的逻辑 生命周期(应用程序使用它的时间...

    Java理论与实践:用弱引用堵住内存泄漏

    【Java理论与实践:用弱引用堵住内存泄漏】这篇文章除了介绍弱引用的概念,还探讨了如何使用弱引用来防止内存泄漏的问题。在Java编程中,内存泄漏并非像C++那样由忘记释放内存引起,而是由于对象生命周期与引用生命...

    Java弱引用与WeakHashMap

     《Java 理论与实践: 用弱引用堵住内存泄漏》一文也指出了使用全局的Map作为缓存容器时发生的内存泄露问题,介绍了如何使用hprof工具来找出内存泄露,并分析了如何使用弱引用来防止内存泄露,还分析了

    堵住电流泄漏:摩尔定律在晶体管发展中继续有效

    总的来说,堵住电流泄漏和解决晶体管尺寸缩小带来的问题,是推动晶体管持续发展、维持摩尔定律生命力的关键。随着新材料、新技术的出现和应用,未来的信息技术将不断突破现有极限,实现更高的性能和更低的能耗。

    堵住泄漏:当心电容器漏电!

    "堵住泄漏:当心电容器漏电!"这个标题提醒我们,电容的选择不当可能会导致电路性能下降,甚至引发故障。 电容器的基本结构由两片电极板和中间的绝缘介质组成。电容值C由相对介电常数εr、电极面积A和间距d共同决定...

    检查并堵住网站的eWebEditor漏洞

    检查并堵住网站的eWebEditor漏洞

    堵住电流泄漏:摩尔定律在晶体管发展中继续有效

    为解决这一问题,工程师提出三维晶体管设计,如三栅极晶体管,以增加栅极对沟道的控制,降低泄漏电流,提高性能和能效。 【技术发展与产业动态】 英特尔作为业界巨头,已计划推出三栅极晶体管技术,预计能显著降低...

    ASTMD3078-02柔性包装气泡法泄漏试验方法参照.pdf

    浸液是使用的一种浸液,不会分解测试用的包装。表面张力低的液体一般敏感度更高。 在测试过程中,需要将样品浸入到真空室内的装有液体的容器中,盖上真空室盖子,关掉出气阀,打开真空,气压计缓慢增长(大约 1 寸 ...

    堵住十大安全漏洞 避免Web应用程序的安全风险

    ### 堵住十大安全漏洞避免Web应用程序的安全风险 随着信息技术的发展,Web应用程序因其灵活性和易用性成为了许多企业的首选。然而,随着Web应用程序数量的增长,相应的安全问题也日益凸显。为了帮助企业及其开发者...

    堵住研究生招考漏洞须从制度入手.pdf

    堵住研究生招考漏洞须从制度入手.pdf

    堵住Microsoft.NET本地权限提升的漏洞风险.docx

    堵住Microsoft.NET本地权限提升的漏洞风险.docx

    修改注册表堵住资源共享隐患 论文

    Windows操作系统,尤其是Windows 2003系统,由于其广泛的使用,成为了黑客和恶意攻击者的首要目标。他们通过寻找系统漏洞,尤其是资源共享方面的安全隐患,来窃取用户数据或实施攻击。本文主要探讨了通过修改注册表...

    堵住孩子身边的安全漏洞.docx

    【儿童安全】保护儿童免受意外伤害是每个家长和社区的重要责任。全球儿童安全组织的资深安全顾问张咏梅强调,儿童的意外伤害...只有这样,我们才能有效地堵住孩子身边的这些安全漏洞,为他们创造一个更安全的成长环境。

    自来水公司危险化学品泄漏应急预案精选.doc

    同时,现场人员需立即通知水质监测站和QES(质量、环境、安全)管理部门,以便快速堵住泄漏源。 对于大规模泄漏,如喷射、爆发性涌泄,预案可能涉及到更复杂的应急措施,包括疏散人员、启动应急设备、使用专门的堵...

    消除内部威胁 堵住数据丢失防护的缺口

    无论数据丢失是无意间发生的还是恶意行为所致,其对企业的影响都是极其严重的:泄漏交易秘密、影响与客户的友好关系,甚至招致法律严惩。要阻止这种对公司业绩、品牌形象和竞争优势影响巨大的破坏行为,企业必须采取...

    06790 主管培训--培训老总-堵住企业出血的伤口(DOC 10页).doc

    在企业管理中,"堵住企业出血的伤口"意味着要解决那些导致企业资源流失严重的问题。许多企业在产品、价格、渠道、销售和管理等多个方面都存在瓶颈,同时内部管理漏洞频现,如同一个病人身患多种疾病并流血不止,首要...

    码头泄漏火灾爆炸现场处置方案.docx

    - 控制泄漏源,如堵住溢油源,使用围油栏、吸油材料回收泄漏物料,同时排除可能的火源。 - 扑救火灾时,关闭相关阀门,启动泡沫炮等灭火设备,必要时指挥船舶撤离危险区域。 8. **大范围火灾应对**: - 当火势...

    码头(泄漏)火灾、爆炸现场处置方案.docx

    - **泄漏物未被引燃**:应停止相关作业,寻找并堵住泄漏源,使用围油栏、吸油材料等进行处理,并消除所有可能的火源。 - **泄漏物被引燃**:迅速关闭阀门,切断电源,使用泡沫炮和其他灭火器材灭火。必要时,指挥...

    清理垃圾堵住电梯门情况说明.docx

    电梯门的正常运行对于电梯的安全使用至关重要。当电梯门出现无法正常开关的情况时,可能存在以下两种主要问题: 1. 防夹人系统的故障:电梯的防夹人系统是为了确保乘客安全而设计的,通常包括平安触板和光幕两部分...

Global site tag (gtag.js) - Google Analytics