在Java程序设计中,内存的申请、分配、释放都由JVM负责完成,因此开发人员就省去了这部分工作,不过这也意味着所开发的应用不是最优的。开发人员有必要对Java应用开发中与内存管理相关的技术做一定的了解。
垃圾回收(Garbage Collection)是Java程序设计中内存管理的核心概念,那么在JVM运行环境中什么对象是垃圾呢?
定义如下:一个对象创建后被放置在JVM的对内存(heap)中,当永远不再引用这个对象时,它将被JVM在对内存中回收。被创建的对象不能再生,同时也没有办法通过程序语句释放它们。
我们也可以这样定义:当对象在JVM运行空间中无法通过根集合(Root set)到达时,这个对象就被称为垃圾对象。根集合是由类中的静态引用域与本地引用域组成的。
JVM管理的两种类型的内存:对内存(heap)和栈内存(stack)。堆内存主要用来存储程序在运行时创建或实例化的对象与变量,而栈内存则是用来存储程序代码中声明为静态(static)或(非静态)的方法。
堆内存(heap)通常被分成两个区域:新对象(new object)与老对象(old object)区域。
新对象区域又可以细分为三个小区域:伊甸园(Eden)区域、From区域与To区域。伊甸园区域用来保存新创建的对象,就像堆栈一样,当区域中的对象满了之后,JVM系统将做可达性测试,检测哪些对象由根集合出发是不可达的,这些对象就可被JVM回收,并且将其从伊甸园区域拷贝到To区域,此时一些对象将发生状态交换,有的对象就从To区域被转移到From区域,此时From区域就有了对象。
在老对象区域中的对象仍然会有一个较长的生命周期,但经过一段时间后,这些对象就会变成短命对象,也就是垃圾对象,从而被JVM回收,建议不要频繁地强制系统做垃圾回收,这样会影响系统的整体性能。
JVM中对象的生命周期大致可分为7个阶段:创建阶段(Creation)、应用阶段(Using)、不可视阶段(Invisible)、不可到达阶段(Unreachable)、可收集阶段(Collected)、终结阶段(Finalized)、释放阶段(Free)。
※ 创建阶段
系统通过下面的步骤,完成创建对象的过程:
(1) 为对象分配存储空间。
(2) 开始构造对象。
(3) 递归调用其超类的构造方法。
(4) 进行对象实例初始化与变量初始化。
(5) 执行构造方法体。
创建对象时几个关键应用规则:
(1) 避免在循环体中创建对象,即使对象占用的内存空间不大。
(2) 尽量及时使对象符合垃圾回收标准。
(3) 不要采用过深的继承层次。
(4) 访问本地变量优于访问类中的变量。
※ 应用阶段
在应用阶段,对象具备下列特征:
(1) 系统至少维护对象的一个强引用(Strong Reference);
(2) 所有对该对象的引用全部是强引用,除非我们显示使用了软引用(Soft Reference)、弱引用(Weak Reference)或虚引用(Phantom Reference)。
强引用是指JVM内存管理器从根引用集合(Root set)出发遍寻堆中所有到达对象的路径。当到达对象的任意路径都不含有引用对象时,这个对象的引用就被称为强引用。
软引用具有较强的引用功能,只有当内存不够的时候,才回收这类内存。另外这些引用对象还能保证在Java抛出OutOfMemory异常之前,被设置为NULL。它可以用于实现一些常用资源的缓存,实现Cache的功能,保证最大限度的试用内存而不引起OutOfMemory。但在某些时候对软引用的试用会降低应用程序的运行效率与性能。
弱引用对象与Soft引用对象的最大不同就在于:GC在进行回收时,需要通过算法检查是否回收Soft引用对象,而对于Weak引用对象,GC总是进行回收。Weak引用对象常用于Map结构中,引用占用内存空间比较大的对象,一旦对象的强引用为null时,这个对象的引用就不存在了,GC能够快速回收该对象。
虚引用的用途较少,主要用于辅助finalize函数的使用。
※ 不可视阶段
当一个对象处在不可视阶段,说明我们不能在其它区域的代码中引用它,此时应该主动将其设置为空,帮助JVM及时地发现这个垃圾对象,并且回收系统资源。
※ 可收集阶段、终结阶段与释放阶段
对象可能处于三种情况:
(1) 回收器发现该对象已经不可到达。
(2) Finalize方法已经被执行。
(3) 对象空间已被重用。
Java程序设计中有关内存管理的其它经验:
(1) 最基本的建议就是尽早释放无用对象的引用。
(2) 尽量少用finalize函数。
(3) 如果需要使用经常用大的图片,可以使用soft应用类型。
(4) 注意集合数据类型,包括数组、树、图、链表等数据结构,这些数据结构对GC来说,回收更为复杂。另外注意一些全局变量,静态变量,这些变量往往容易造成不必要的内存资源浪费。
(5) 尽量避免在类的默认构造器中创建、初始化大量对象,防止在调用其自类的构造器时造成不必要的内存浪费。
(6) 尽量避免强制系统做垃圾回收,增长系统做垃圾回收的最终时间,降低系统性能。
(7) 尽量避免显示申请数组空间,当不得不显示地申请数组空间时尽量准确的估计出其合理值。
(8) 尽量在远程方法调用(RMI)类应用开发时使用瞬间值(transient)变量,除非远程调用端需要获取该瞬间值变量的值。
(9) 尽量在合适的场景下使用对象池技术以提高系统性能,缩减系统开销,但是要注意对象池的尺寸不宜过大,及时清除无效对象释放内存资源,综合考虑应用运行环境的内存资源限制,避免过高估计运行环境所提供内存资源的数量。