2.2 JVM中对象的生命周期
在JVM运行空间中,对象的整个生命周期大致可以分为7个阶段:创建阶段(Creation)、应用阶段(Using)、不可视阶段(Invisible)、不可到达阶段(Unreachable)、可收集阶段(Collected)、终结阶段(Finalized)与释放阶段(Free)。上面的这7个阶段,构成了JVM中对象的完整的生命周期。下面分别介绍对象在处于这7个阶段时的不同情形。
2.2.1 创建阶段
在对象创建阶段,系统要通过下面的步骤,完成对象的创建过程:
(1)为对象分配存储空间。
(2)开始构造对象。
(3)递归调用其超类的构造方法。
(4)进行对象实例初始化与变量初始化。
(5)执行构造方法体。
上面的5个步骤中的第3步就是指递归地调用该类所扩展的所有父类的构造方法,一个Java类(除Object类外)至少有一个父类(Object),这个规则既是强制的,也是隐式的。你可能已经注意到在创建一个Java类的时候,并没有显式地声明扩展(extends)一个Object父类。实际上,在Java程序设计中,任何一个Java类都直接或间接的是Object类的子类。例如下面的代码:
public class A {
…
}
这个声明等同于下面的声明:
public class A extends java.lang.Object {
…
}
上面讲解了对象处于创建阶段时,系统所做的一些处理工作,其中有些过程与应用的性能密切相关,因此在创建对象时,我们应该遵循一些基本的规则,以提高应用的性能。
下面是在创建对象时的几个关键应用规则:
(1)避免在循环体中创建对象,即使该对象占用内存空间不大。
(2)尽量及时使对象符合垃圾回收标准。
(3)不要采用过深的继承层次。
(4)访问本地变量优于访问类中的变量。
关于规则(1)避免在循环体中创建对象,即使该对象占用内存空间不大,需要提示一下,这种情况在我们的实际应用中经常遇到,而且我们很容易犯类似的错误,例如下面的代码:
… …
for (int i = 0; i < 10000; ++i) {
Object obj = new Object();
System.out.println("obj= "+ obj);
}
… …
上面代码的书写方式相信对你来说不会陌生,也许在以前的应用开发中你也这样做过,尤其是在枚举一个Vector对象中的对象元素的操作中经常会这样书写,但这却违反了上述规则(1),因为这样会浪费较大的内存空间,正确的方法如下所示:
… …
Object obj = null;
for (int i = 0; i < 10000; ++i) {
obj = new Object();
System.out.println("obj= "+ obj);
}
… …
采用上面的第二种编写方式,仅在内存中保存一份对该对象的引用,而不像上面的第一种编写方式中代码会在内存中产生大量的对象应用,浪费大量的内存空间,而且增大了系统做垃圾回收的负荷。因此在循环体中声明创建对象的编写方式应该尽量避免。
另外,不要对一个对象进行多次初始化,这同样会带来较大的内存开销,降低系统性能,如:
public class A {
private Hashtable table = new Hashtable ();
public A() {
// 将Hashtable对象table初始化了两次
table = new Hashtable();
}
}
正确的方式为:
public class B {
private Hashtable table = new Hashtable ();
public B() {
}
}
不要小看这个差别,它却使应用软件的性能相差甚远,如图2-5所示。
图2-5 初始化对象多次所带来的性能差别
看来在程序设计中也应该遵从“勿以恶小而为之”的古训,否则我们开发出来的应用也是低效的应用,有时应用软件中的一个极小的失误,就会大幅度地降低整个系统的性能。因此,我们在日常的应用开发中,应该认真对待每一行代码,采用最优化的编写方式,不要忽视细节,不要忽视潜在的问题。
2.2.2 应用阶段
当对象的创建阶段结束之后,该对象通常就会进入对象的应用阶段。这个阶段是对象得以表现自身能力的阶段。也就是说对象的应用阶段是对象整个生命周期中证明自身“存在价值”的时期。在对象的应用阶段,对象具备下列特征:
— 系统至少维护着对象的一个强引用(Strong Reference);
— 所有对该对象的引用全部是强引用(除非我们显式地使用了:软引用(Soft Reference)、弱引用(Weak Reference)或虚引用(Phantom Reference))。
上面提到了几种不同的引用类型。可能一些读者对这几种引用的概念还不是很清楚,下面分别对之加以介绍。在讲解这几种不同类型的引用之前,我们必须先了解一下Java中对象引用的结构层次。
Java对象引用的结构层次示意如图2-6所示。
图2-6 对象引用的结构层次示意
由图2-6我们不难看出,上面所提到的几种引用的层次关系,其中强引用处于顶端,而虚引用则处于底端。下面分别予以介绍。
1.强引用
强引用(Strong Reference)是指JVM内存管理器从根引用集合(Root Set)出发遍寻堆中所有到达对象的路径。当到达某对象的任意路径都不含有引用对象时,对这个对象的引用就被称为强引用。
2.软引用
软引用(Soft Reference)的主要特点是具有较强的引用功能。只有当内存不够的时候,才回收这类内存,因此在内存足够的时候,它们通常不被回收。另外,这些引用对象还能保证在Java抛出OutOfMemory 异常之前,被设置为null。它可以用于实现一些常用资源的缓存,实现Cache的功能,保证最大限度的使用内存而不引起OutOfMemory。再者,软可到达对象的所有软引用都要保证在虚拟机抛出OutOfMemoryError
之前已经被清除。否则,清除软引用的时间或者清除不同对象的一组此类引用的顺序将不受任何约束。然而,虚拟机实现不鼓励清除最近访问或使用过的软引用。下面是软引用的实现代码:
… …
import java.lang.ref.SoftReference;
…
A a = new A();
…
// 使用 a
…
// 使用完了a,将它设置为soft 引用类型,并且释放强引用;
SoftReference sr = new SoftReference(a);
a = null;
…
// 下次使用时
if (sr!=null) {
a = sr.get();
}
else{
// GC由于内存资源不足,可能系统已回收了a的软引用,
// 因此需要重新装载。
a = new A();
sr=new SoftReference(a);
}
… …
软引用技术的引进,使Java应用可以更好地管理内存,稳定系统,防止系统内存溢出,避免系统崩溃(crash)。因此在处理一些占用内存较大而且声明周期较长,但使用并不频繁的对象时应尽量应用该技术。正像上面的代码一样,我们可以在对象被回收之后重新创建(这里是指那些没有保留运行过程中状态的对象),提高应用对内存的使用效率,提高系统稳定性。但事物总是带有两面性的,有利亦有弊。在某些时候对软引用的使用会降低应用的运行效率与性能,例如:应用软引用的对象的初始化过程较为耗时,或者对象的状态在程序的运行过程中发生了变化,都会给重新创建对象与初始化对象带来不同程度的麻烦,有些时候我们要权衡利弊择时应用。
3.弱引用
弱引用(Weak Reference)对象与Soft引用对象的最大不同就在于:GC在进行回收时,需要通过算法检查是否回收Soft引用对象,而对于Weak引用对象,GC总是进行回收。因此Weak引用对象会更容易、更快被GC回收。虽然,GC在运行时一定回收Weak引用对象,但是复杂关系的Weak对象群常常需要好几次GC的运行才能完成。Weak引用对象常常用于Map数据结构中,引用占用内存空间较大的对象,一旦该对象的强引用为null时,对这个对象引用就不存在了,GC能够快速地回收该对象空间。与软引用类似我们也可以给出相应的应用代码:
… …
import java.lang.ref.WeakReference;
…
A a = new A();
…
// 使用 a
…
// 使用完了a,将它设置为weak 引用类型,并且释放强引用;
WeakReference wr = new WeakReference (a);
a = null;
…
// 下次使用时
if (wr!=null) {
a = wr.get();
}
else{
a = new A();
wr = new WeakReference (a);
}
… …
弱引用技术主要适用于实现无法防止其键(或值)被回收的规范化映射。另外,弱引用分为“短弱引用(Short Week Reference)”和“长弱引用(Long Week Reference)”,其区别是长弱引用在对象的Finalize方法被GC调用后依然追踪对象。基于安全考虑,不推荐使用长弱引用。因此建议使用下面的方式创建对象的弱引用。
… …
WeakReference wr = new WeakReference(obj);
或
WeakReference wr = new WeakReference(obj, false);
… …
4.虚引用
虚引用(Phantom Reference)的用途较少,主要用于辅助finalize函数的使用。Phantom对象指一些执行完了finalize函数,并且为不可达对象,但是还没有被GC回收的对象。这种对象可以辅助finalize进行一些后期的回收工作,我们通过覆盖Reference的clear()方法,增强资源回收机制的灵活性。虚引用主要适用于以某种比 Java 终结机制更灵活的方式调度 pre-mortem 清除操作。
&注意 在实际程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。
2.2.3 不可视阶段
在一个对象经历了应用阶段之后,那么该对象便处于不可视阶段,说明我们在其他区域的代码中已经不可以再引用它,其强引用已经消失,例如,本地变量超出了其可视范围,如下所示。
… …
public void process () {
try {
Object obj = new Object();
obj.doSomething();
} catch (Exception e) {
e.printStackTrace();
}
while (isLoop) { // ... loops forever
// 这个区域对于obj对象来说已经是不可视的了
// 因此下面的代码在编译时会引发错误
obj.doSomething();
}
}
… …
如果一个对象已使用完,而且在其可视区域不再使用,此时应该主动将其设置为空(null)。可以在上面的代码行obj.doSomething();下添加代码行obj = null;,这样一行代码强制将obj对象置为空值。这样做的意义是,可以帮助JVM及时地发现这个垃圾对象,并且可以及时地回收该对象所占用的系统资源。
2.2.4 不可到达阶段
处于不可到达阶段的对象,在虚拟机所管理的对象引用根集合中再也找不到直接或间接的强引用,这些对象通常是指所有线程栈中的临时变量,所有已装载的类的静态变量或者对本地代码接口(JNI)的引用。这些对象都是要被垃圾回收器回收的预备对象,但此时该对象并不能被垃圾回收器直接回收。其实所有垃圾回收算法所面临的问题是相同的——找出由分配器分配的,但是用户程序不可到达的内存块。
2.2.5 可收集阶段、终结阶段与释放阶段
对象生命周期的最后一个阶段是可收集阶段、终结阶段与释放阶段。当对象处于这个阶段的时候,可能处于下面三种情况:
(1)垃圾回收器发现该对象已经不可到达。
(2)finalize方法已经被执行。
(3)对象空间已被重用。
当对象处于上面的三种情况时,该对象就处于可收集阶段、终结阶段与释放阶段了。虚拟机就可以直接将该对象回收了。
分享到:
相关推荐
每个线程在其生命周期内都会有一个对应的堆栈结构,其中包含了一个或多个帧(Frame)。每个帧对应一个方法调用,包含了该方法的局部变量表、操作数栈以及返回地址等信息。 - **局部变量表**:用于存储方法参数和...
理解这些引用类型可以帮助我们在需要精确控制对象生命周期的场景下编写更高效、更合理的代码。 ## 总结 在Java中,GC通过可达性分析算法判断对象是否死亡,避免了引用计数法的局限性。理解GC的工作原理和对象的...
类加载机制确保了类的正确加载,并管理类的生命周期。 2. **JVM内存结构**:主要包括堆、栈、方法区等,用于存储和管理数据。 3. **GC(Garbage Collection)算法**:自动内存管理机制,用于回收不再使用的对象所...
- **对象生命周期**: - **Eden区**: 许多对象分配后很快就变为非活动对象,具有“infant mortality”现象。 - **Survivor Spaces**: - 当GC发生时,Eden中的存活对象被移入Survivor空间之一。 - 对象在Survivor...
2.3 **SUN JVM内存管理(优化)**:在Sun JVM中,内存分为新生代、老年代和永久代,用于不同生命周期的对象存储。新生代采用复制算法,老年代则用标记-整理或标记-压缩算法,以优化内存回收。 2.4 **SUN JVM调优**...
年轻代中的对象生命周期较短,频繁进行垃圾回收,而老年代对象生命周期长,回收频率较低。 1. 垃圾回收器设计决策 1.1. 顺序执行 vs 并行执行:并行GC利用多核处理器提升效率,但可能导致更多的碎片。 1.2. 并发...
通过对JVM内存管理的理解,我们可以更好地控制对象的生命周期,减少不必要的内存消耗,并提高程序的性能。此外,了解不同的垃圾回收算法可以帮助我们选择合适的JVM参数,从而提高系统的整体性能。推荐阅读《深入理解...
- 减少对象创建:避免短生命周期对象过多,减少频繁的GC操作。 - 对象池技术:对于经常创建和销毁的类型,使用对象池减少GC压力。 - 使用弱引用、软引用、虚引用:这些引用不会阻止对象被GC回收。 - 大对象直接进入...
#### 一、JVM的生命周期 **1.1 JVM实例的诞生** JVM(Java Virtual Machine,Java虚拟机)实例的诞生始于一个Java程序的启动。当用户通过命令行输入`java ClassName`来启动一个Java应用程序时,会创建一个JVM实例...
同时,栈内存的分配和释放速度快,堆内存则更适合存储长生命周期的对象。 2.5.4 堆(Heap) 堆内存分为新生代和老年代,新生代用于存放新创建的对象,老年代存放生存时间较长的对象。通过分代垃圾收集,JVM可以更...
Java虚拟机栈与线程的生命周期一致。每当一个线程启动时,JVM都会为其创建一个新的栈。栈中的每个元素称为帧(Frame),代表了一个方法的调用。帧包含了局部变量表、操作栈、动态链接信息以及方法退出的信息。当方法...
- **优化对象生命周期管理**:合理设计对象的生命周期,减少无用对象的创建。 - **检查并修复内存泄漏**:使用工具(如 VisualVM)定位内存泄漏点,并进行修复。 #### 四、Tomcat JVM 内存调整步骤 在 Linux 和 ...
### Java+JVM+垃圾回收机制 #### 一、哪些垃圾是需要回收的? 在Java虚拟机中,垃圾回收机制负责自动管理内存空间,...这种分代的设计思想使得垃圾回收更为高效,同时也为不同生命周期的对象提供了合适的处理方式。
- **老年代(Old Generation)**:老年代主要存放生命周期较长的对象。老年代相对较为稳定,垃圾回收次数较少,但每次回收的时间可能较长。 - **永久代(Permanent Generation)**:永久代在JDK1.8之前用于存放类和元...
HotSpot JVM的内存管理及垃圾回收算法基于分代的堆内存划分原理,这一原则认为不同对象的生命周期不同。因此,对于不同生命周期的对象采用不同的收集方式可以提高回收效率。整个JVM内存被划分为三个主要部分: 1. *...
- **内存溢出**:通常由于对象生命周期管理不当导致。 - **性能问题**:如GC频繁、响应时间过长等问题。 - **线程死锁**:由于线程间的同步问题引起。 ### 总结 JVM是Java开发中一个非常重要的概念,理解其内部机制...
虽然Java的垃圾回收机制可以自动回收,但在特定情况下,如静态引用、长生命周期对象引用短生命周期对象等,可能导致内存泄漏。 - **性能优化**:理解JVM的内存模型和垃圾回收机制,可以帮助我们优化程序性能,例如...
- **年轻代GC**: 主要针对的是短生命周期的对象进行回收。 - **老年代GC**: 主要处理的是长生命周期的对象。 - **混合GC**: 同时涉及年轻代和部分老年代的回收。 **2.3 垃圾回收器** 不同的垃圾回收器具有不同的...
Java虚拟机在执行Java程序时会将其管理的内存划分为多个不同的区域,每个区域都有其特定的用途及生命周期: - **程序计数器(Program Counter Register)**:用于记录当前线程执行的字节码指令的位置。每当执行引擎...
### JAVA中销毁一个对象的方法详解 ...对于一些特殊需求,还可以通过`finalize()`方法和`System.gc()`来辅助管理对象的生命周期。然而,在实际应用中,应当尽量避免依赖这些辅助手段,以减少潜在的问题和性能开销。