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必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。
监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。
为了更好理解GC的工作原理,我们可以将对象考虑为有向图的顶点,将引用关系考虑为图的有向边,有向边从引用者指向被引对象。另外,每个线程对象可以作为一个图的起始顶点,例如大多程序从main进程开始执行,那么该图就是以main进程顶点开始的一棵根树。在这个有向图中,根顶点可达的对象都是有效对象,GC将不回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被GC回收。
下面一个例子说明如何用有向图表示内存管理。对于程序的每一个时刻,都有一个有向图表示JVM的内存分配情况。以下右图,就是左边程序运行到第6行的示意图。
Java使用有向图的方式进行内存管理,可以消除引用循环的问题,例如有三个对象,相互引用,只要它们和根进程不可达的,那么GC也是可以回收它们的。这种方式的优点是管理内存的精度很高,但是效率较低。另外一种常用的内存管理技术是使用计数器,例如COM模型采用计数器方式管理构件,它与有向图相比,精度行低(很难处理循环引用的问题),但执行效率很高。
二、什么是内存泄漏。
在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才开始工作,也有定时执行的,有的是平缓执行GC,有的是中断式执行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;
}
|
//此时,所有的Object对象都没有被释放,因为变量v引用这些对象。
三、如何检测Java的内存泄漏。
目前,通常使用一些工具来检查Java程序的内存泄漏问题。市场上已有几种专业检查Java内存泄漏的工具,它们的基本工作原理大同小异,都是通过监测Java程序运行时,所有对象的申请、释放等动作,将内存管理的所有信息进行统计、分析、可视化。开发人员将根据这些信息判断程序是否有内存泄漏问题。这些工具包括Optimizeit Profiler,JProbe Profiler,JinSight , Rational 公司的Purify等。
下面,简单介绍Optimizeit的基本功能和工作原理。
Optimizeit Profiler版本4.11支持Application,Applet,Servlet和Romote Application四类应用,并且可以支持大多数类型的JVM,包括SUN JDK系列,IBM的JDK系列,和Jbuilder的JVM等。并且,该软件是由Java编写,因此它支持多种操作系统。Optimizeit系列还包括Thread Debugger和Code Coverage两个工具,分别用于监测运行时的线程状态和代码覆盖面。
当设置好所有的参数了,我们就可以在OptimizeIt环境下运行被测程序,在程序运行过程中,Optimizeit可以监视内存的使用曲线(如下图),包括JVM申请的堆(heap)的大小,和实际使用的内存大小。另外,在运行过程中,我们可以随时暂停程序的运行,甚至强行调用GC,让GC进行内存回收。通过内存使用曲线,我们可以整体了解程序使用内存的情况。这种监测对于长期运行的应用程序非常有必要,也很容易发现内存泄露。
在运行过程中,我们还可以从不同视角观查内存的使用情况,Optimizeit提供了四种方式:
- 堆视角。 这是一个全面的视角,我们可以了解堆中的所有的对象信息(数量和种类),并进行统计、排序,过滤。了解相关对象的变化情况。
- 方法视角。通过方法视角,我们可以得知每一种类的对象,都分配在哪些方法中,以及它们的数量。
- 对象视角。给定一个对象,通过对象视角,我们可以显示它的所有出引用和入引用对象,我们可以了解这个对象的所有引用关系。
- 引用图。 给定一个根,通过引用图,我们可以显示从该顶点出发的所有出引用。
在运行过程中,我们可以随时观察内存的使用情况,通过这种方式,我们可以很快找到那些长期不被释放,并且不再使用的对象。我们通过检查这些对象的生存周期,确认其是否为内存泄露。在实践当中,寻找内存泄露是一件非常麻烦的事情,它需要程序员对整个程序的代码比较清楚,并且需要丰富的调试经验,但是这个过程对于很多关键的Java程序都是十分重要的。
综上所述,Java也存在内存泄露问题,其原因主要是一些对象虽然不再被使用,但它们仍然被引用。为了解决这些问题,我们可以通过软件工具来检查内存泄露,检查的主要原理就是暴露出所有堆中的对象,让程序员寻找那些无用但仍被引用的对象。
分享到:
相关推荐
### Java内存泄漏详解 #### 一、问题的提出 Java作为一种高级编程语言,因其自动化的内存管理和垃圾收集机制而受到广泛欢迎。很多开发者基于这一点,认为Java不会出现内存泄漏问题,或者即便出现也是垃圾收集器...
### Java内存管理与垃圾回收机制详解 #### 一、引言 Java作为一种广泛使用的编程语言,其内存管理和垃圾回收机制是开发人员必须了解的核心概念之一。本文将详细探讨JVM的垃圾回收机制及其调优方法,深入分析Java...
然而,Java内存泄漏问题常常源于开发者对内存管理的理解不足,导致无用对象依然被引用,从而无法被GC回收。 1. Java内存管理机制 - 分配:对象的内存空间在堆(Heap)中分配,通过`new`关键字创建对象。 - 释放:...
"Java内存泄漏.doc"和"JAVA内存泄漏,走开.doc"会详细介绍内存泄漏的成因和识别方法。例如,长期持有大对象、静态集合类的不当使用、数据库连接未关闭等都可能导致内存泄漏。了解这些情况并采取预防措施,可以防止...
此外,Java的垃圾回收机制和内存管理也降低了内存泄漏和程序崩溃的风险,这对于长时间运行的游戏至关重要。 该教程会从基础开始,讲解Java编程语言的核心概念,包括变量、数据类型、控制结构(如if语句、for循环和...
通过学习《深入Java内存模型》,开发者不仅可以理解Java内存管理的底层机制,还能掌握优化程序性能、避免并发问题的关键技能,从而提升代码质量并降低系统维护成本。这本书是每个Java开发者必备的参考书目,无论你是...
#### 五、Java内存泄漏示例 假设有一个场景,程序中创建了一个列表,并向其中添加了一些对象。随后,该列表被一个局部变量引用,但在该变量的作用域结束后并未显式地设置为null。这意味着虽然列表已经不再需要,但...
5. 垃圾收集器:自动管理内存,回收不再使用的对象,防止内存泄漏。 接下来,我们将关注JVM参数调优。这通常涉及到以下几个方面: 1. 内存配置:通过-Xms和-Xmx设置堆内存的初始大小和最大大小,-XX:NewRatio控制...
5. **内存泄漏**:分析可能导致内存泄漏的原因,如静态集合类引用、未关闭的数据库连接等,并提供检测和解决的方法。 6. **性能监控工具**:介绍如何使用JVisualVM、JConsole、MAT(Memory Analyzer Tool)等工具来...
Java内存模型(JMM)规定了线程如何访问和修改共享变量,以保证多线程环境下的正确性。它涉及到 volatile、synchronized、final 关键字的语义,以及 Happens-Before 规则等概念。 七、JVM执行引擎 JVM的执行引擎是...
本篇文章将基于“经典之谈——Java内存分配”这一主题,结合“详谈Java的内存分配.pdf”文档,深入讲解Java内存的各个方面。 首先,Java内存分为几个主要区域,分别是:程序计数器(Program Counter Register)、...
Java游戏编程原理与实践教程是针对游戏开发爱好者和专业开发者设计的一套全面的学习资源,旨在帮助读者理解并掌握使用Java语言进行游戏开发的核心概念和技术。本教程通过丰富的PPT课件,深入浅出地讲解了从基础到...
本文将深入探讨Java内存分析的相关知识点,基于提供的博文链接进行讲解。 首先,我们要理解Java内存模型,它主要由三个部分组成:堆(Heap)、栈(Stack)和方法区(Method Area)。堆是所有对象实例的存储区域,而...
Java内存泄露是一个严重的问题,它可能导致系统性能下降,甚至服务中断。这份13页的PDF文档,"java内存泄露定位与分析",很显然是针对Java开发者深入理解内存管理的重要资源。我们将探讨其中可能涵盖的关键知识点。 ...
在深入学习这些工具的使用时,你应该理解Java内存模型,包括堆内存(新生代、老年代)、方法区、栈空间等各个部分的工作原理。同时,了解垃圾收集器的工作机制,如Serial、Parallel、CMS和G1,以及它们如何影响内存...
4. **内存管理**:Java通过垃圾收集器自动管理内存,但理解何时对象会被垃圾回收以及如何避免内存泄漏是提升程序性能的关键。"JAVA解惑"可能会涵盖这部分内容,解释引用类型、弱引用、软引用等。 5. **多线程编程**...
8. **Java虚拟机(JVM)**:深入理解JVM的工作原理,包括内存管理、垃圾回收、类加载机制,对于优化程序性能和解决内存泄漏等问题大有裨益。 9. **设计模式**:笔记可能会涵盖常见的设计模式,如单例、工厂、观察者...
"异常和垃圾收集.ppt"这部分内容会深入讨论Java的错误处理机制,异常如何被抛出、捕获和处理,以及垃圾收集(Garbage Collection)如何自动管理内存,这对于避免程序中的内存泄漏至关重要。 "代码规范和Eclipse的...
Java的自动内存管理使得开发者无需手动释放内存,但理解其工作原理有助于避免内存泄漏和性能问题。 最后,文档可能会涵盖一些实用的Java库,如IO流、NIO(非阻塞I/O)以及网络编程,这些都是开发实际应用时不可或缺...