当前位置: 首页 > 图灵资讯 > 技术篇> Java的内存泄漏

Java的内存泄漏

来源:图灵教育
时间:2023-04-28 09:25:10

  Java的一个重要优点是通过垃圾收集器(Garbage Collection,GC)程序员不需要通过调用函数来自动管理内存的回收。因此,许多程序员认为Java没有内存泄漏,或者即使有内存泄漏,也不是程序的责任,而是GC或JVM。其实这个想法是不正确的,因为Java也有内存泄漏,但是它的表现和C++不一样。问题的提出

  Java的一个重要优点是通过垃圾收集器(Garbage Collection,GC)程序员不需要通过调用函数来自动管理内存的回收。因此,许多程序员认为Java没有内存泄漏,或者即使有内存泄漏,也不是程序的责任,而是GC或JVM。其实这个想法是不正确的,因为Java也有内存泄漏,但是它的表现和C++不一样。

  Java技术应用于越来越多的服务器程序,如JSP,Servlet, EJB等,服务器程序通常运行很长时间。此外,在许多嵌入式系统中,内存总量非常有限。内存泄漏问题变得非常关键,即使每次运行少量泄漏,系统也面临着长期运行后崩溃的风险。 Java是如何管理内存的

  要判断Java是否有内存泄漏,首先要了解Java是如何管理内存的。Java的内存管理是对象的分配和释放。在Java中,程序员需要通过关键字new为每个对象申请内存空间 (基本类型除外),所有对象都在堆叠 (Heap)中间分配空间。此外,对象的释放是由GC决定和执行的。在Java中,内存的分配是由程序完成的,而内存的释放是由GC完成的。这种收支方法确实简化了程序员的工作。但与此同时,它也加重了JVM的工作。这也是Java程序运行速度慢的原因之一。为了正确释放对象,GC必须监控每个对象的运行状态,包括对象的申请、引用、引用、赋值等。

  监控对象的状态是为了更准确、更及时地释放对象,释放对象的基本原则是对象不再被引用。

  为了更好地理解GC的工作原理,我们可以将对象考虑为有向图的顶点,引用关系考虑为图的有向边,有向边从引用者指向被引对象。此外,每个线程对象都可以作为图片的起始顶点。例如,大多数程序都是从main过程中执行的,因此图片是从main过程的顶点开始的一棵树。在这个有向图中,根顶点的对象是有效的对象,GC不会回收这些对象。假如某个对象 (连通子图)和这个根的顶点是无法达到的(注意,这个图是有向图),所以我们认为这个(这些)对象不再被引用,可以被GC回收。

  下面,让我们举一个例子来解释如何使用图表来表示内存管理。对于程序的每一刻,我们都有一个图表来表示JVM的内存分配。以下右图是左程序运行到第六行的示意图。

  Java采用向图管理内存,可以消除引用循环的问题。例如,如果有三个对象相互引用,GC也可以回收它们,只要它们无法达到根的过程。这种方法的优点是内存管理精度高,但效率低。另一种常用的内存管理技术是使用计数器。例如,COM模型使用计数器来管理组件。与有向图相比,它的精度较低(循环引用问题难以处理),但执行效率很高。 Java中的内存泄漏是什么?

  下面,我们可以描述什么是内存泄漏。在Java中,内存泄漏是指有一些被分配的对象,具有以下两个特征。首先,这些对象是可访问的,也就是说,在有向图中,有一个通道可以与之连接;其次,这些对象是无用的,也就是说,程序将来不会再使用这些对象。如果对象满足这两个条件,可以判断为Java中的内存泄漏,不会被GC回收,但会占用内存。

  在C++中,内存泄漏的范围更大。有些对象被分配到内存空间,但无法到达。由于C++中没有GC,这些内存将永远无法收回。在Java中,GC负责回收这些无法到达的对象,因此程序员不需要考虑这部分内存泄漏。

  通过分析,我们了解到程序员需要管理C++的边缘和顶点,而Java程序员只需要管理边缘(不需要管理顶点的释放)。这样,Java就提高了编程的效率。

  因此,通过以上分析,我们知道Java中也有内存泄漏,但范围小于C++。因为Java保证任何对象都可以在语言上到达,所有未到达的对象都由GC管理。

  GC对程序员来说基本上是透明的,看不见的。虽然只有几个函数可以访问GC,比如运行GC的System.gc(),但根据Java语言规范, 这个函数不能保证JVM的垃圾收集器会执行。因为不同的JVM实现者可以使用不同的算法来管理GC。通常,GC的线程优先级较低。JVM调用GC的策略有很多。有的在内存使用达到一定程度后才开始工作,有的定期执行,有的平时执行,有的中断执行。但一般来说,我们不需要关心这些。除非GC的执行在某些情况下影响应用程序的性能,如基于Web的实时系统,如在线游戏,用户不希望GC突然中断应用程序执行和垃圾回收,所以我们需要调整GC的参数,使GC能够以温和的方式释放内存,如将垃圾回收分解为一系列小步骤,Sun提供的HotSpot JVM支持这一特性。

  以下是内存泄漏的简单例子。在这个例子中,我们循环申请Object对象,并将申请对象放入Vector中。如果我们只释放引用本身,Vector仍然引用该对象,因此该对象对GC是不可回收的。因此,如果对象添加到Vector中,则必须从Vector中删除,最简单的方法是将Vector对象设置为null。

  Vector v=new Vector(10);for (int i=1;i<100; i++){Object o=new Object();v.add(o);o=null;}

  ///此时,由于变量v引用这些对象,所有Object对象都没有释放。 如何检测内存泄漏?

  最后一个重要的问题是如何检测Java的内存泄漏。目前,我们通常使用一些工具来检查Java程序的内存泄漏。市场上有几种专业检查Java内存泄漏的工具。它们的基本工作原理是相似的。它们是通过监控Java程序运行过程中所有对象的申请和释放来统计、分析和可视化内存管理的所有信息。开发人员将根据这些信息判断程序是否存在内存泄漏问题。这些工具包括Optimizeitie Profiler,JProbe Profiler,JinSight , Rational 公司Purify等。

  接下来,我们将简要介绍Optimizeit的基本功能和工作原理。

  Optimizeit 4.11版Profiler支持Application,Applet,Servlet和Romote Application四种应用程序,可以支持大多数类型的JVM,包括SUNN JDK系列,IBMJDK系列,JbuilderJVM等。此外,该软件由Java编写,因此它支持多种操作系统。Optimizeit系列包括Thread debuger和Code 两种工具:Coverage,分别用于监控运行时的线程状态和代码覆盖面。

  当设置所有参数时,我们可以在Optimizeit环境中运行测量程序。在程序运行过程中,Optimizeit可以监控内存的使用曲线(如下图所示),包括JVM应用程序的堆(heap)实际使用的内存尺寸和内存尺寸。另外,在运行过程中,我们可以随时暂停程序的运行,甚至强行调用GC,让GC回收内存。通过内存使用曲线,我们可以全面了解程序使用内存的情况。对于长期运行的应用程序来说,这种监控是非常必要的,而且很容易发现内存泄漏。

  Optimizeit还可以从不同的角度观察内存的使用情况,提供了四种方法: 堆视角。 这是一个全面的视角,我们可以了解堆中对象的所有信息(数量和类型),并进行统计、排序和过滤。了解相关对象的变化。 方法视角。从方法的角度来看,我们可以知道每种对象分布在哪些方法和数量上。 对象视角。给出一个对象,从对象的角度,我们可以显示它的所有引用和引用对象,我们可以理解这个对象的所有引用关系。 引用图。 给定一个根,通过引用图,我们可以从这个顶点显示所有的引用。

  在运行过程中,我们可以随时观察内存的使用情况。这样,我们就可以很快找到长时间不释放和不再使用的对象。通过检查这些对象的生存周期,我们确认它们是否是内存泄漏。在实践中,找到内存泄漏是一件非常麻烦的事情。它要求程序员清楚整个程序的代码,并有丰富的调试经验,但这个过程对许多关键的Java程序非常重要。

  综上所述,Java也存在内存泄漏问题,主要是因为有些对象不再被使用,但仍被引用。为了解决这些问题,我们可以通过软件工具检查内存泄漏。检查的主要原理是暴露堆中的所有对象,让程序员找到无用但仍被引用的对象。