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中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为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内存泄漏详解 #### JVM内存管理概览 在探讨Java内存泄漏之前,我们先简要回顾一下JVM(Java虚拟机)的基本架构及其内存管理机制。这有助于更好地理解内存泄漏的发生原因及其解决方法。 ##### 类装载子...
Java语言的设计考虑到了现代多处理器和多线程环境的特点,因此在设计之初就将内存模型纳入了语言规范之中。Java平台自动集成了线程及多处理器技术的支持,这是许多其他编程语言所不具备的优势之一。然而,随着技术的...
防止内存泄漏是JMM中的一个重要概念,它是指在Java虚拟机中避免内存泄漏的方法。防止内存泄漏是JMM中的一个重要组成部分,它们之间的关系非常重要。 JMM是Java语言规范中的一部分,它描述了程序中各个变量之间的...
与C/C++等语言不同,Java自动管理内存,通过垃圾回收机制自动回收不再使用的对象,避免了手动管理内存带来的内存泄漏风险。然而,即使有了垃圾回收,Java依然可能出现内存泄露,这主要是由于程序设计上的不当导致的...
### JVM(Java虚拟机)详解:Java语言中所用的常用术语解释 #### 一、Java虚拟机(JVM)概述 Java虚拟机(JVM)是Java语言的核心组件之一,它为Java程序提供了一个运行时环境。JVM的概念是Java语言跨平台能力的关键,...
在Java编程语言中,了解程序内存的管理对于优化性能和避免内存泄漏至关重要。本文将深入探讨Java程序在运行时如何使用内存,特别是JVM(Java虚拟机)如何处理栈和堆内存。 首先,栈内存主要负责存储基本数据类型...
### Java内存分配机制详解 #### 一、引言 Java作为一种广泛应用的编程语言,其内存...了解这些基础知识对于提高Java程序的性能和避免内存泄漏等问题至关重要。希望本文能够帮助读者更深入地理解Java内存分配机制。
### Java垃圾回收及内存泄漏知识点详解 #### 一、Java内存管理 1. **运行时数据区**:Java虚拟机管理的内存主要分为以下几个部分: - **方法区(Method Area)**:存储类的信息(如类名、字段、方法等)、常量、...
其中,垃圾回收机制(Garbage Collection,简称GC)是Java语言设计的一大亮点,它显著降低了应用程序中内存泄露的风险。然而,即便有了GC的存在,Java程序仍可能遭遇内存泄露的问题。本文旨在深入探讨Java中的内存...
理解这两种内存类型的工作原理及其区别对于优化程序性能、避免内存泄漏等问题至关重要。本文将深入探讨Java中堆内存与栈内存的分配机制,并通过对比分析它们之间的差异,帮助读者更好地掌握Java内存管理的核心概念。...
1. **常发性内存泄露**:这种类型的内存泄露会在每次执行相关代码时发生,每次都会导致内存泄漏。 2. **偶发性内存泄露**:只在特定环境或操作序列下才会出现的内存泄露。 3. **一次性内存泄露**:只发生一次的...
9. **jmap和jhat**: jmap用于获取堆内存快照,jhat则用于分析这些快照,帮助诊断内存泄漏问题。 10. **jinfo**: 提供配置信息,如虚拟机参数,类路径等。 11. **jstat**: 监控JVM统计信息,如垃圾收集、内存使用等...
《JAVA2 精要 语言详解与编程指南》是一本深入探讨JAVA编程技术的书籍,旨在帮助读者全面理解和熟练掌握JAVA语言的核心概念与实践技巧。这本书对于任何想要提升JAVA编程技能的人来说,都是一个宝贵的资源。 JAVA...
了解对象生命周期和内存泄漏的概念对于优化程序性能至关重要。 2. **泛型**:泛型是Java 5引入的一项特性,增强了代码的类型安全性和效率。泛型允许在类、接口和方法定义时指定参数类型,从而在编译阶段就能发现...
Java语言中的内存泄露是一个复杂而重要的主题,尤其是在大型系统中,有效的内存管理直接影响到程序的性能和稳定性。Java与C++等语言相比,其内存管理的主要区别在于它使用垃圾收集器(GC)自动回收不再使用的对象所...
### Java内存泄漏详解 #### 一、问题的提出 Java作为一种高级编程语言,因其自动化的内存管理和垃圾收集机制而受到广泛欢迎。很多开发者基于这一点,认为Java不会出现内存泄漏问题,或者即便出现也是垃圾收集器...
- **Native Method Stack**:本地方法栈与Java Virtual Machine Stack类似,但用于存储本地方法(非Java语言编写的方法)的执行栈帧。 通过理解这些内存区域的功能和特点,结合使用JVisualVM这样的工具,开发者...
Java2语言命令详解 在Java编程领域,理解并掌握各种命令行工具对于开发、调试和部署应用程序至关重要。本文将深入探讨Java2(也称为J2SE,Java 2 Standard Edition)中的关键命令,帮助开发者更好地驾驭Java环境。 ...