`
saluya
  • 浏览: 120683 次
  • 性别: Icon_minigender_2
  • 来自: 西安
社区版块
存档分类
最新评论

处理 Java 程序中的内存漏洞? 研究何时应该关注内存漏洞以及如何预防内存漏洞

 
阅读更多

 

研究何时应该关注内存漏洞以及如何预防内存漏洞

(作者:IBM DeveloperWorks Jim Patrick

 

  Java 程序中也有内存漏洞?当然有。与流行的观念相反,在 Java 编程中,内存管理仍然是需要考虑的问题。在本文中,您将了解到什么会导致内存漏洞以及何时应该关注这些漏洞。您还有机会实践一下在您自己的项目中解决漏洞问题。

 

Java 程序中的内存漏洞是如何显现出来的

  大多数程序员都知道,使用像 Java 这样的编程语言的一大好处就是,他们不必再担心内存的分配和释放问题。您只须创建对象,当应用程序不再需要这些对象时,Java 会通过一种称为“垃圾收集”的机制将这些对象删除。这种处理意味着 Java 已经解决了困扰其他编程语言的烦人问题 -- 可怕的内存漏洞。真的是这样的吗?

 

  在深入讨论之前,我们先回顾一下垃圾收集的工作方式。垃圾收集器的工作是发现应用程序不再需要的对象,并在这些对象不再被访问或引用时将它们删除。垃圾收集器从根节点(在 Java 应用程序的整个生存周期内始终存在的那些类)开始,遍历被引用的所有节点进行清除。在它遍历这些节点的同时,它跟踪哪些对象当前正被引用着。任何类只要不再被引用,它就符合垃圾收集的条件。当删除这些对象以后,就可将它们所占用的内存资源返回给 Java 虚拟机 (JVM)

 

  所以的确是这样,Java 代码不要求程序员负责内存的管理和清除,它会自动对无用的对象执行垃圾收集。但是,我们要紧记的一点是仅当一个对象不再被引用时才会被统计为无用。图 1 说明了这个概念。

http://www.ccidnet.com/tech/guide/2001/04/12//image/01.jpg

1. 无用但仍被引用的对象

 

  上面说明了在 Java 应用程序执行期间具有不同生存周期的两个类。类 A 首先被实例化,并会在很长一段时间或程序的整个生存期内存在。在某个时候,类 B 被创建,类 A 添加对这个新创建的类的一个引用。现在,我们假定类 B 是某个用户界面小部件,它由用户显示甚至解除。如果没有清除类 A B 的引用,则即便不再需要类 B,并且即便在执行下一个垃圾收集周期以后,类 B 仍将存在并占用内存空间。

 

何时应该关注内存漏洞

  如果您的程序在执行一段时间以后发出 java.lang.OutOfMemoryError 错误,则内存漏洞肯定是一个重大嫌疑。除了这种明显的情况之外,何时还应该关注内存漏洞呢?持完美主义观点的程序员肯定会回答,应该查找并纠正所有内存漏洞。

  完全有这样的可能,垃圾收集器在应用程序的生存期内可能始终不会运行。不能保证 JVM 何时以及是否会调用垃圾收集器 -- 即便程序显式地调用 System.gc() 也是如此。通常,在当前的可用内存能够满足程序的内存需求时,JVM 不会自动运行垃圾收集器。当可用内存不能满足需求时,JVM 将首先尝试通过调用垃圾收集来释放出更多的可用内存。如果这种尝试仍然不能释放足够的资源,JVM 将从操作系统获取更多的内存,直至达到允许的最大极限。

 

  例如,考虑一个小型 Java 应用程序,它显示一些用于修改配置的简单用户界面元素,并且它有一个内存漏洞。很可能到应用程序关闭时也不会调用垃圾收集器,因为 JVM 很可能有足够的内存来创建程序所需的全部对象,而此后可用内存则所剩无几。因此,在这种情况下,即使某些“死”对象在程序执行时占用着内存,它实际上并没有什么用途。

 

  如果正在开发的 Java 代码要全天 24 小时在服务器上运行,则内存漏洞在此处的影响就比在我们的配置实用程序中的影响要大得多。在要长时间运行的某些代码中,即使最小的漏洞也会导致 JVM 耗尽全部可用内存。

 

  在相反的情况下,即便程序的生存期较短,如果存在分配大量临时对象(或者若干吞噬大量内存的对象)的任何 Java 代码,而且当不再需要这些对象时也没有取消对它们的引用,则仍然可能达到内存极限。

 

  最后一种情况是内存漏洞无关紧要。我们不应该认为 Java 内存漏洞像其他语言(如 C++)中的漏洞那样危险,在那些语言中内存将丢失,且永远不会被返回给操作系统。在 Java 应用程序中,我们使不需要的对象依附于操作系统为 JVM 所提供的内存资源。所以从理论上讲,一旦关闭 Java 应用程序及其 JVM,所分配的全部内存将被返回给操作系统。

 

确定应用程序是否有内存漏洞

  为了查看在 Windows NT 平台上运行的某个 Java 应用程序是否有内存漏洞,您可能试图在应用程序运行时观察“任务管理器”中的内存设置。但是,在观察了运行中的几个 Java 应用程序以后,您会发现它们比本地应用程序占用的内存要多得多。我做过的一些 Java 项目要使用 10 20 MB 的系统内存才能启动。而操作系统自带的 Windows Explorer 程序只需 5 MB 左右的内存。

 

  在 Java 应用程序内存使用方面应注意的另一点是,这个典型程序在 IBM JDK 1.1.8 JVM 中运行时占用的系统内存越来越多。似乎直到为它分配非常多的物理内存以后它才开始向系统返回内存。这些情况是内存漏洞的征兆吗要理解其中的缘由,                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         我们必须熟悉 JVM 如何将系统内存用作它的堆。当运行 java.exe 时,您使用一定的选项来控制垃圾收集堆的起始大小和最大大小(分别用 -ms -mx 表示)。Sun JDK 1.1.8 的默认起始设置为 1 MB,默认最大设置为 16 MBIBM JDK 1.1.8 的默认最大设置为系统总物理内存大小的一半。这些内存设置对 JVM 在用尽内存时所执行的操作有直接影响。JVM 可能继续增大堆,而不等待一个垃圾收集周期的完成。

 

  这样,为了查找并最终消除内存漏洞,我们需要使用比任务监视实用程序更好的工具。当您试图调试内存漏洞时,内存调试程序(请参阅参考资源)可能派得上用场。这些程序通常会显示堆中的对象数、每个对象的实例数和这些对象所占用的内存等信息。此外,它们也可能提供有用的视图,这些视图可以显示每个对象的引用和引用者,以便您跟踪内存漏洞的来源。

 

  下面我将说明我是如何用 Sitraka Software JProbedebugger 检测和去除内存漏洞的,以使您对这些工具的部署方式以及成功去除漏洞所需的过程有所了解。

 

内存漏洞的一个示例

  本例集中讨论一个问题,我们部门当时正在开发一个商业发行版软件,这是一个 Java JDK 1.1.8 应用程序,一个测试人员花了几个小时研究这个程序才最终使这个问题显现出来。这个 Java 应用程序的基本代码和包是由几个不同的开发小组在不同的时间开发的。我猜想,该应用程序中意外出现的内存漏洞是由那些没有真正理解别人开发的代码的程序员造成的。

 

  我们正在讨论的 Java 代码允许用户为 Palm 个人数字助理创建应用程序,而不必编写任何 Palm OS 本地代码。通过使用图形用户界面,用户可以创建窗体,向窗体中添加控件,然后连接这些控件的事件来创建 Palm 应用程序。测试人员发现,随着不断创建和删除窗体和控件,这个 Java 应用程序最终会耗尽内存。开发人员没有检测到这个问题,因为他们的机器有更多的物理内存。

 

  为了研究这个问题,我用 JProbe 来确定什么地方出了差错。尽管用了 JProbe 所提供的强大工具和内存快照,研究仍然是一个冗长乏味、不断重复的过程,首先要确定出现内存漏洞的原因,然后修改代码,最后还得检验结果。

 

  JProbe 提供几个选项,用来控制调试期间实际记录哪些信息。经过几次试验以后,我断定获取所需信息的最有效方法是,关闭性能数据收集,而将注意力集中在所捕获的堆数据上。JProbe 提供了一个称为 Runtime Heap Summary 的视图,它显示 Java 应用程序运行时所占用的堆内存量随时间的变化。它还提供了一个工具栏按钮,必要时可以强制 JVM 执行垃圾收集。如果您试图弄清楚,当 Java 应用程序不再需要给定的类实例时,这个实例会不会被作为垃圾收集,这个功能将很有用。图 2 显示了使用中的堆存储量随时间的变化。

http://www.ccidnet.com/tech/guide/2001/04/12//image/02-s.jpg

  图 2. Runtime Heap Summary

 

  在 Heap Usage Chart 中,蓝色部分表明已分配的堆空间大小。在启动这个 Java 程序并达到稳定状态以后,我强制垃圾收集器运行,在图中的表现就是绿线(这条线表明插入了一个检查点)左侧的蓝线的骤降。随后,我添加了四个窗体,然后又将它们删除,并再次调用了垃圾收集器。当程序返回仅有一个可视窗体的初始状态时,检查点之后的蓝色区域高于检查点之前的蓝色区域这一情况表明可能存在内存漏洞。我通过查看 Instance Summary 证实确实有一个漏洞,因为 Instance Summary 表明 FormFrame 类(它是窗体的主用户界面类)的计数在检查点之后增加了 4

 

查找原因

  为了将测试人员报告的问题剔出,我采取的第一个步骤是找出几个简单的、可重复的测试案例。就本例而言,我发现只须添加一个窗体,将它删除,然后强制执行垃圾收集,结果就会导致与被删除窗体相关联的许多类实例仍然处于活动状态。这个问题在 JProbe Instance Summary 视图中很明显,这个视图统计每个 Java 类在堆中的实例数。

 

  为了查明使垃圾收集器无法正常完成其工作的那些引用,我使用 JProbe Reference Graph(如图 3 所示)来确定哪些类仍然引用着目前未被删除的 FormFrame 类。在调试这个问题时该过程是最复杂的过程之一,因为我发现许多不同的对象仍然引用着这个无用的对象。用来查明究竟是哪个引用者真正造成这个问题的试错过程相当耗时。

 

  在本例中,一个根类(左上角用红色标明的那个类)是问题的发源地。右侧用蓝色突出显示的类处在从最初的 FormFrame 类跟踪而来的路径上。

http://www.ccidnet.com//tech/guide/2001/04/12//image/03-s.jpg

 

  图 3. 在引用图中跟踪内存漏洞

 

  就本例而言,最后查明罪魁祸首是包含一个静态 hashtable 的字体管理器类。通过逆向追踪引用者列表,我发现根节点是用来存储每个窗体所用字体的一个静态 hashtable。各个窗体可被单独放大或缩小,所以这个 hashtable 包含一个具有某个给定窗体的全部字体的 vector。当窗体的大小改变时,就会提取这个字体 vector,并将适当的缩放因子应用于字体大小。

 

  这个字体管理器类的问题是,虽然程序在创建窗体时将字体 vector 存入这个 hashtable 中,但没有提供在删除窗体时删除 vector 的代码。因此,这个静态 hashtable(在应用程序的生存周期内直存在)永远不会删除引用每个窗体的那些键。结果,窗体及其所有关联的类都闲置在内存中。

 

修正

  本问题的一个简单解决方案是在字体管理器类中添加一个方法,以便在用户删除窗体时以适当的键作为参数调用 hashtable remove() 方法。removeKeyFromHashtables() 方法如下所示:


 

public void removeKeyFromHashtables(GraphCanvas graph) {
   if (graph != null) {
    viewFontTable.remove(graph);   // 删除 hashtable 中的键
                     // 以预防内存漏洞
   }
  }
 

 

 

  随后,我在 FormFrame 类中添加了一个对此方法的调用。FormFrame 实际上是使用 Swing 的内部框架来实现窗体用户界面的,所以我将对字体管理器的调用添加到当完全关闭内部框架时所调用的方法中,如下所示:

  

  /** 当去掉 (dispose) FormFrame 时调用。清除引用以预防内存漏洞。
  */
  public void internalFrameClosed(InternalFrameEvent e) {
   FontManager.get().removeKeyFromHashtables(canvas);
   canvas = null;
   setDesktopIcon(null);
  }
 

  当作了这些修改以后,我使用调试器证实:当执行相同的测试案例时,与被删除的窗体相关联的对象计数减小。

 

预防内存漏洞

  可以通过观察某些常见问题来预防内存漏洞。Collection 类(如 hashtable vector)常常是出现内存漏洞的地方。当这个类被用 static 关键字声明并且在应用程序的整个生存期中存在时尤其是这样。

 

  另一个常见的问题是,您将一个类注册为事件监听程序,而在不再需要这个类时没有撤销注册。此外,您常常需要在适当的时候将指向其他类的类成员变量设置为 null

 

小结

  查找内存漏洞的原因可能是一个乏味的过程,更不用说需要专用调试工具的情况了。但是,一旦您熟悉了这些工具以及在跟踪对象引用时进行搜索的模式,您就能够找到内存漏洞。此外,您还会摸索出一些有价值的技巧,这些技巧不仅有助于节约项目的成本,而且使您能够领悟到在以后的项目中应该避免哪些编码方式来预防内存漏洞。

 

参考资源

  在开始查找内存漏洞之前,请先熟悉下列一种调试器:

 

Sitraka Software JProbe Profiler withMemory Debugger

Intuitive System Optimizeit Java Performance Profiler

Paul Moeller Win32Java Heap Inspector

IBM alphaWorks 网站上的 Jinsight

Jinsight: A tool for visualizing the execution of Java programsdeveloperWorks1999 11 月)详细说明了这个实用程序是如何帮助您分析性能和调试代码的。

注:本文讨论的项目是用 JDK 1.1.8 完成的,但 JDK 1.2 引入了一个新包,java.lang.ref,这个包可与垃圾收集器交互。另外,JDK 1.2 还引入了一个 java.util.WeakHashMap 类,可用它来代替传统的 java.util.Hashtable 类。这个类不会阻止垃圾收集器回收键对象。JDK 1.3 SolarisLinux Microsoft Windows 版本引入了 Java HotSpot Client VM,该虚拟机带有一个新的、经过改进的垃圾收集器。

作者简介

Jim Patrick IBM Pervasive Computing Division 的一名顾问程序员。他从 1996 年开始用 Java 编写程序。请通过 patrickj@us.ibm.com Jim 联系。

 

分享到:
评论

相关推荐

    java开发常见漏洞及处理说明

    Java开发中的安全问题一直是一个重要的关注点,尤其是在Web应用程序中,因为它们经常处理用户输入的数据。本文主要讨论了三个常见的安全漏洞:SQL注入、XSS跨站脚本攻击以及文件上传漏洞,以及如何通过过滤和拦截...

    Java反序列化漏洞利用工具.zip

    Java反序列化漏洞是软件安全领域的一个重要话题,它涉及到Java应用程序在处理序列化和反序列化过程中的安全问题。序列化是将对象状态转换为可存储或传输的数据格式的过程,而反序列化则是将这些数据恢复为原来的对象...

    Java反序列化漏洞探析及其修复方法研究.pdf

    Java反序列化漏洞是一种在信息安全领域引起广泛关注的新型高危漏洞,其最早在2015年被曝出,存在于Apache Commons Collections等公共库中。Java反序列化漏洞主要影响基于Java编写的各类应用程序,由于其能够远程执行...

    Java语言的程序漏洞分析技术研究.zip

    Java语言的程序漏洞分析技术是信息安全领域中的一个重要研究方向,主要关注如何发现、理解和修复Java应用程序中的安全漏洞。本文将深入探讨Java程序漏洞的类型、分析方法以及防范策略。 Java程序漏洞通常分为以下几...

    JAVA性能瓶颈和漏洞检测工具

    在Java开发过程中,性能优化和安全漏洞检测是至关重要的环节。Java性能瓶颈可能导致系统响应变慢,用户体验下降,甚至可能导致服务器资源耗尽。而漏洞的存在则可能让系统面临被攻击的风险,因此,掌握有效的检测工具...

    Java反序列化漏洞检查工具V1.2_Weblogic XML反序列化漏洞检查工具CVE-2017-10271

    Java反序列化漏洞是软件安全领域的一个重要话题,特别是在企业级应用服务器如Weblogic中,这类漏洞可能导致远程代码执行、系统权限提升等严重后果。工具"Java反序列化漏洞检查工具V1.2_Weblogic XML反序列化漏洞检查...

    Java语言的程序漏洞检测与诊断技术应用研究.zip

    Java语言的程序漏洞检测与诊断技术是软件开发过程中的关键环节,它关乎程序的安全性和稳定性。随着Java在互联网、企业级应用以及大数据处理等领域的广泛应用,理解和掌握这些技术显得尤为重要。本文将深入探讨Java...

    java反序列化漏洞工具

    Java反序列化漏洞是一种安全问题,它...总的来说,Java反序列化漏洞是Java应用程序中的一个重要安全风险,需要开发者和管理员密切关注。通过使用特定的检测工具,我们可以有效地识别并缓解这类威胁,保护系统的安全。

    Java语言的程序漏洞分析技术研究.pdf

    Java语言的程序漏洞分析技术研究.pdf

    Java语言的程序漏洞检测与诊断技术

    以对象为中心的方法能够更好地处理Java特有的内存管理和并发特性,提高漏洞诊断的效率和准确性。 总的来说,Java语言的程序漏洞检测与诊断技术是保障软件质量和安全的关键。随着技术的发展,研究人员不断探索新的...

    基于Java的漏洞扫描系统.zip

    【描述】中的“基于Java的漏洞扫描系统”暗示了该系统可能采用了Java的特性,如跨平台性、丰富的类库以及强大的网络处理能力。Java的这些特点使其成为开发此类系统的一个理想选择。Java的面向对象特性使得代码组织...

    基于字节码搜索的Java反序列化漏洞调用链挖掘方法.pdf

    Java反序列化漏洞是一种在Java应用程序中常见的安全漏洞,它发生在应用程序反序列化不可信的数据时,而这些数据可能被篡改,导致恶意代码执行。由于Java类库功能的不断更新与扩展,反序列化漏洞的潜在范围越来越广。...

    Java语言的程序漏洞检测与诊断技术应用研究.pdf

    本文详细探讨了Java语言的程序漏洞检测与诊断技术的应用研究,针对Java语言在移动终端广泛应用的背景下,如何有效地发现并排除程序中潜在的漏洞以确保用户使用的安全性。以下是相关的知识点: 1. Java语言概述:...

    【Java毕业设计】毕业设计——基于Java的漏洞扫描系统.zip

    基于Java的漏洞扫描系统是网络安全领域的一个重要课题,旨在帮助企业和组织发现并修复其信息系统中的安全漏洞。这个毕业设计项目可能涵盖了多个关键知识点,包括但不限于: 1. **Java核心技术**:首先,项目的实现...

    Java反序列化漏洞利用工具V1.7.jar

    Java反序列化漏洞利用工具

    java反序列化漏洞-验证.rar

    Java反序列化漏洞是软件安全领域的一个重要话题,尤其对于使用Java进行开发的系统来说,理解和防范这种漏洞至关重要。在Java编程中,序列化和反序列化是常见的数据传输和持久化手段,允许对象的状态被转换为字节流,...

    Java反序列化漏洞利用集成工具

    Java反序列化漏洞是软件安全领域的一个重要话题,尤其对于Java开发者和安全研究人员而言。这个“Java反序列化漏洞利用集成工具”很可能是一个用于测试和分析Java应用中反序列化漏洞的工具集。下面,我们将深入探讨...

    Java语言的程序漏洞检测与诊断技术.pdf

    "Java语言的程序漏洞检测与诊断技术" Java语言是一种广泛应用...Java语言的程序漏洞检测和诊断技术是Java语言发展的重要方向,我们需要重视该技术的重要性,并继续研究和发展该技术,以确保Java语言的安全性和稳定性。

    6、java反序列化漏洞.pdf

    在 Java 中,如何防止反序列化漏洞?防止反序列化漏洞的方法有多种,例如: 1. 只允许来自可信的来源的输入数据 2. 使用安全的序列化库,例如,使用 Java 的序列化机制,或者使用其他安全的序列化库 3. 验证输入...

Global site tag (gtag.js) - Google Analytics