`
yesjavame
  • 浏览: 687481 次
  • 性别: Icon_minigender_2
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

处理Java程序中的内存漏洞

阅读更多

最近碰到一个棘手的问题,在已经展开的稳定性测试中。频繁出现Was宕机等问题,于是在征询了研发组意见后。决定对Was发生宕机前后,进行内存快照。最初的方案是在,Was启动后和发生死机时,使用HeapDump来分析具体程序调用的Java对象。但时间的快照文件却非常难以分析发生宕机时候内存堆内具体的变化情况。由于,需要准确定位到java虚拟机中堆栈的使用情况。由此,我们引用了一个新的测试分析工具jProbe对Was启动时,场景运行的开始点、Action1的结束点以及场景运行的结束点进行内存分析。jProbe的核心思想是通过对比多次快照的内存调用情况,分析出前后两次不同的内存启动后占用比率,从而引导研发推断出程序可能发生问题的对象。

Java 程序中也有内存漏洞?当然有。与流行的观念相反,在 Java 编程中,内存管理仍然是需要考虑的问题。在本文中,您将了解到什么会导致内存漏洞以及何时应该关注这些漏洞。您还有机会实践一下在您自己的项目中解决漏洞问题。
  
  Java 程序中的内存漏洞是如何显现出来的
  大多数程序员都知道,使用像 Java 这样的编程语言的一大好处就是,他们不必再担心内存的分配和释放问题。您只须创建对象,当应用程序不再需要这些对象时,Java 会通过一种称为“垃圾收集”的机制将这些对象删除。这种处理意味着 Java 已经解决了困扰其他编程语言的烦人问题 -- 可怕的内存漏洞。是这样的吗?
  
  在深入讨论之前,我们先回顾一下垃圾收集的工作方式。垃圾收集器的工作是发现应用程序不再需要的对象,并在这些对象不再被访问或引用时将它们删除。垃圾收集器从根节点(在 Java 应用程序的整个生存周期内始终存在的那些类)开始,遍历被引用的所有节点进行清除。在它遍历这些节点的同时,它跟踪哪些对象当前正被引用着。任何类只要不再被引用,它就符合垃圾收集的条件。当删除这些对象以后,就可将它们所占用的内存资源返回给 Java 虚拟机 (JVM)。
  
  所以的确是这样,Java 代码不要求程序员负责内存的管理和清除,它会自动对无用的对象执行垃圾收集。但是,要紧记的一点是 仅当一个对象不再被引用时才会被统计为无用的。图 1 说明了这个概念。
  

 
   图 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 MB。IBM 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 显示了使用中的堆存储量随时间的变化。
    
   图 2. Runtime Heap Summary
  
  在 Heap Usage Chart 中,蓝色部分表明已分配的堆空间大小。在启动这个 Java 程序并达到稳定状态以后,我强制垃圾收集器运行,在图中的表现就是绿线(这条线表明插入了一个检查点)左侧的蓝线的骤降。随后,我添加了四个窗体,然后又将它们删除,并再次调用了垃圾收集器。当程序返回仅有一个可视窗体的初始状态时,检查点之后的蓝色区域高于检查点之前的蓝色区域这一情况表明可能存在内存漏洞。我通过查看 Instance Summary 证实确实有一个漏洞,因为 Instance Summary 表明 FormFrame 类(它是窗体的主用户界面类)的计数在检查点之后增加了 4。
  
  查找原因
  为了将测试人员报告的问题剔出,我采取的第一个步骤是找出几个简单的、可重复的测试案例。就本例而言,我发现只须添加一个窗体,将它删除,然后强制执行垃圾收集,结果就会导致与被删除窗体相关联的许多类实例仍然处于活动状态。这个问题在 JProbe 的 Instance Summary 视图中很明显,这个视图统计每个 Java 类在堆中的实例数。
  
  为了查明使垃圾收集器无法正常完成其工作的那些引用,我使用 JProbe 的 Reference Graph(如图 3 所示)来确定哪些类仍然引用着目前未被删除的 FormFrame 类。在调试这个问题时该过程是最复杂的过程之一,因为我发现许多不同的对象仍然引用着这个无用的对象。用来查明究竟是哪个引用者真正造成这个问题的试错过程相当耗时。
  
  在本例中,一个根类(左上角用红色标明的那个类)是问题的发源地。右侧用蓝色突出显示的类处在从最初的 FormFrame 类跟踪而来的路径上。
  
   图 3. 在引
分享到:
评论

相关推荐

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

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

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

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

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

    首先,Java程序漏洞主要分为四大类:安全性漏洞、内存管理漏洞、并发编程错误和设计缺陷。安全性漏洞可能涉及代码注入、权限提升或数据泄露等问题;内存管理漏洞通常表现为内存泄漏或空指针异常;并发编程错误如死锁...

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

    本文将深入探讨Java程序漏洞的类型、分析方法以及防范策略。 Java程序漏洞通常分为以下几类: 1. 输入验证不足:这是最常见的漏洞类型,如SQL注入、跨站脚本(XSS)等,由于对用户输入的数据处理不当,攻击者可以...

    Java反序列化安全漏洞防控

    Java反序列化安全漏洞是一种严重的安全威胁,它主要发生在Java程序处理序列化数据时。序列化是Java中的一种机制,可以将对象状态转换为字节流,以便存储或传输。反序列化则是将字节流恢复为Java对象的过程。 在Java...

    基于YARA的Java内存马检测方案设计.pdf

    基于YARA的Java内存马检测方案设计通过向JVM中注入Agent将高风险类导出并通过YARA实现对Java内存中的恶意代码的检测和定位。该方案可以有效地检测Java内存马,具有较高的检测准确率和较低的误报率。 知识点六:Java...

    java程序中加载动态链接库文件

    ### Java程序中加载动态链接库文件 #### 一、引言 在开发Java应用程序时,有时需要调用一些底层硬件接口或执行特定平台的操作,这些功能通常无法仅通过纯Java代码实现。这时,就需要借助于Java Native Interface ...

    Java应用程序的安全性研究.pdf

    本文主要研究了Java应用程序的安全性问题,提出了相应的解决方案,并着重讨论了与设计及实现相关的安全问题、Java应用程序本身的安全问题以及Java程序运行时的安全问题。 Java应用程序的安全性问题是 Java 开发语言...

    java内存对象搜索辅助工具,配合IDEA在Java应用运行时,对内存中的对象进行搜索

    4. **安全测试**:在描述中提到的“辅助构造java内存webshell”场景,指的是在Web应用程序安全测试中,通过查找并操纵内存中的request对象,可能可以模拟恶意攻击,发现潜在的安全漏洞。 5. **代码分析与优化**:...

    第57天:代码审计-JAVA项目框架类漏洞分析报告1

    在本文中,我们将深入探讨Java项目框架类中的漏洞分析,特别是关注OGNL(对象导航图语言)和Spring Expression Language(SpEL)。这两种表达式语言在Java应用开发中扮演着核心角色,尤其是在Struts2和Spring框架中...

    基于java的开发源码-漏洞检测程序 Yasca.zip

    【标题】"基于Java的开发源码-漏洞检测程序Yasca.zip" 提供的是一个用于安全审计和漏洞检测的开源工具。Yasca是基于Java语言编写的,这使得它具有跨平台的特性,可以在多种操作系统上运行。Java的使用使得开发者能够...

    Java高级知识点详解系列

    了解对象生命周期和内存泄漏的概念对于优化程序性能至关重要。 2. **泛型**:泛型是Java 5引入的一项特性,增强了代码的类型安全性和效率。泛型允许在类、接口和方法定义时指定参数类型,从而在编译阶段就能发现...

    mysql-connector-java-5.1.49.jar

    MySQL Connector/J是MySQL数据库系统与Java应用程序之间的重要桥梁,它是一个实现了JDBC(Java Database Connectivity)规范的驱动程序,使得Java开发者能够通过编写Java代码来访问和操作MySQL数据库。"mysql-...

    Java理论与实践:修复Java内存模型2

    Java内存模型(Java Memory Model, JMM)是Java平台中...理解并应用这些内存模型的改变对于编写高效、可靠的并发Java程序至关重要。通过JSR 133的实施,Java平台向着提供更加一致和强大的并发支持迈出了坚实的一步。

    Format String漏洞介绍

    当程序员没有正确提供格式字符串(format string)或允许用户控制格式字符串时,攻击者可以利用此漏洞来改变程序执行流程,获取敏感信息,甚至执行任意代码。 漏洞产生的原因是,`printf`系列函数会解析格式字符串...

    java插件jdk1.8.0版本

    Java JDK 1.8.0是Java开发工具集的一个重要版本,主要用于支持Java程序的开发、编译和运行。此版本特别为64位Windows操作系统设计,确保在高性能的计算机上提供最佳的效能。Java SE(Java Standard Edition)是Java...

    《Java语言程序设计(一)》课后习题答案(课程编号04747)

    - **应用程序**:通常指的是独立运行的Java程序,可以直接在命令行中运行,例如桌面应用软件。 - **小应用程序(Applet)**:一种特殊的应用程序,用于在Web浏览器中运行。随着HTML5的普及,Applet的使用逐渐减少。 ...

    JAVA程序设计与问题

    根据给定的信息,“JAVA程序设计与问题”这一标题与描述,以及“JAVA”这个标签,我们可以推断出这部分内容主要围绕Java编程语言的基础知识、高级特性及其在解决实际问题中的应用进行讨论。虽然提供的具体内容部分并...

Global site tag (gtag.js) - Google Analytics