怎样处理Java程序中的内存漏洞

技术怎样处理Java程序中的内存漏洞本篇文章为大家展示了怎样处理Java程序中的内存漏洞,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。 Java 程序中也有内存漏洞?当然有。与流

本文向您展示了如何处理Java程序中的内存漏洞。内容简洁易懂,一定会让你眼前一亮。希望通过这篇文章的详细介绍,你能有所收获。

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资源管理器程序只需要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 显示了使用中的堆存储量随时间的变化。

怎样处理Java程序中的内存漏洞

  图 2. Runtime Heap Summary

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

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

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

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

怎样处理Java程序中的内存漏洞

  图 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。

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

上述内容就是怎样处理Java程序中的内存漏洞,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注行业资讯频道。

内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/131704.html

(0)

相关推荐

  • mysql中pt-online-schema-change怎么用

    技术mysql中pt-online-schema-change怎么用这篇文章主要介绍了mysql中pt-online-schema-change怎么用,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后

    攻略 2021年11月2日
  • 基于 Redis 存储 Session

    技术基于 Redis 存储 Session 基于 Redis 存储 Session基于 Redis 存储 Session
    如果我们想将 session 数据保存到 redis 中,只要将 session

    礼包 2021年10月26日
  • 三国时期是公元多少年,三国时期到底有多少人口

    技术三国时期是公元多少年,三国时期到底有多少人口有人说三国人口只有500万三国时期是公元多少年,也有人说是1000万,甚至不少人说2000万~3000万之间。那么到底三国时代有多少人口?东汉末年和三国时期的户门数字并无正

    生活 2021年10月29日
  • swing五种常见的布局是什么(swing中的常用布局方式)

    技术怎样进行Swing Set示例的分析怎样进行Swing Set示例的分析,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。Swing 工具包提供各种用于

    攻略 2021年12月19日
  • java线程思维导图是怎么样的

    技术java线程思维导图是怎么样的这篇文章将为大家详细讲解有关java线程思维导图是怎么样的,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。晚上在家利用二个小时时间整理了J

    攻略 2021年10月23日
  • 使用Redis之前5个必须了解的事情有哪些

    技术使用Redis之前5个必须了解的事情有哪些这篇文章给大家介绍使用Redis之前5个必须了解的事情有哪些,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。使用Redis开发应用程序是一个很愉快的过程,

    攻略 2021年11月10日