JVM的对象的生命周期(六)
一、引用
Java 中的引用的定义很传统:如果 reference (引用)类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。 这种定义很纯粹,但是太过狭隘,一个对象在这种定义下只有被引用或者没有被引用两种状态,对于如何描述一些处于判刑中又或者我们想扔又舍不得的对象就显得无能为力。
我们希望能描述这样一类对象:当内存空间还足够时,则能保留在内存之中;如果内存空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象。 很多系统的缓存功能都符合这样的应用场景。
那么其实我们可以想到,我们的对象的生命周期中一定会有很多种的状态,然后我们会在各个状态都给出一个相对应的描述。
1.1、强引用
在 Java 中最常见的就是强引用,也是我们在开发过程中经常会使用到的引用.把一个对象赋给一个引用变量,这个引用变量就是一个强引 用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即 使该对象以后永远都不会被用到 JVM 也不会回收。因此强引用是造成 Java内存泄漏的主要原因之 一。
1.2、软引用
软引用需要用 SoftReference 类来实现,对于只有软引用的对象来说,当系统内存足够时它 不会被回收,当系统内存空间不足时它会被回收。软引用通常用在对内存敏感的程序中。
public class SoftReferenceDemo { public static void main(String[] args) { Demo demo=new Demo (); //业务代码使用Demo实例 // 使用完demo后,将它设置为soft引用类型,并且释放强引用 SoftReference softReference=new SoftReference ( demo ); demo=null; //下次使用时 if (softReference!=null){ demo=(Demo)softReference.get (); }else { // GC由于内存资源不足,可能系统已回收了a的软引用, // 因此需要重新装载。 demo=new Demo (); softReference=new SoftReference ( demo); } } }
1.3、弱引用
弱引用需要用 WeakReference 类来实现,它比软引用的生存期更短,对于只有弱引用的对象 来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,总会回收该对象占用的内存。
public class WeakReferenceDemo { public static void main(String[] args) throws InterruptedException { //100M的缓存数据 byte[] cacheData = new byte[100 * 1024 * 1024]; //将缓存数据用软引用持有 WeakReference<byte[]> cacheRef = new WeakReference<> (cacheData); System.out.println("第一次GC前" + cacheData); System.out.println("第一次GC前" + cacheRef.get()); //进行一次GC后查看对象的回收情况 System.gc(); //等待GC Thread.sleep(500); System.out.println("第一次GC后" + cacheData); System.out.println("第一次GC后" + cacheRef.get()); //将缓存数据的强引用去除 cacheData = null; System.gc(); //等待GC Thread.sleep(500); System.out.println("第二次GC后" + cacheData); System.out.println("第二次GC后" + cacheRef.get()); } }
1.4、虚引用
虚引用需要 PhantomReference 类来实现,它不能单独使用,必须和引用队列联合使用。虚引用的主要作用是跟踪对象被垃圾回收的状态。
二、对象的生命周期
在Java中,对象的生命周期包括以下几个阶段:
- 创建阶段( Created )
- 应用阶段( In Use )
- 不可见阶段( Invisible )
- 不可达阶段( Unreachable )
- 收集阶段( Collected )
- 终结阶段( Finalized )
- 对象空间重分配阶段( De-allocated )
创建阶段
其实我们在探讨类加载的时候就已经探讨了一部分对象创建的情况;在这阶段主要做下面几件事:
- 为对象分配存储空间
- 开始构造对象
- 从超类到子类对static成员进行初始化
- 超类成员变量按顺序初始化,递归调用超类的构造方法
- 子类成员变量按顺序初始化,子类构造方法调用
- 一旦对象被创建,并被分派给某些变量赋值,这个对象的状态就切换到了应用阶段
2.2、应用阶段( In Use )
- 对象至少被一个强引用持有着。
- 所有对该对象的引用全是强引用,(除非我们显示使用了其他类型的)
2.3、不可见阶段( Invisible )
当一个对象处于不可见阶段时,说明程序本身不再持有该对象的任何强引用,虽然该这些引用仍然是存在着的。简单说就是程序的执行已经超出了该对象的作用域了。
2.4、不可达阶段( Unreachable )
对象处于不可达阶段是指该对象不再被任何强引用所持有。与“不可见阶段”相比,“不可见阶段”是指程序不再持有该对象的任何强引用,这种情况下,该对象仍可能被 JVM 等系统下的某些已装载的静态变量或线程或 JNI 等强引用持有着,这些特殊的强引用被称为”GC root ”。存在着这些 GC root 会导致对象的内存泄露情况,无法被回收。
2.5、收集阶段( Collected )
当垃圾回收器发现该对象已经处于“不可达阶段”并且垃圾回收器已经对该对象的内存空间重新分配做好准备时,则对象进入了“收集阶段”。如果该对象已经重写了 finalize() 方法,则会去执行该方法的终端操作。这里要特别说明一下:不要重载fifinazlie()方法!原因有两点:
- 会影响JVM的对象分配与回收速度,在分配该对象时,JVM需要在垃圾回收器上注册该对象,以便在回收时能够执行该重载方法;在该方法的执行时需要消耗CPU时间且在执行完该方法后才会重新执行回收操作,即至少需要垃圾回收器对该对象执行两次GC。
- 可能造成该对象的再次“复活”,在fifinalize()方法中,如果有其它的强引用再次持有该对象,则会导致对象的状态由“收集阶段”又重新变为“应用阶段”。这个已经破坏了Java对象的生命周期进程,且“复活”的对象不利用后续的代码管理。
2.6、终结阶段
当对象执行完fifinalize()方法后仍然处于不可达状态时,则该对象进入终结阶段。在该阶段是等待垃圾回收器对该对象空间进行回收。
2.7、对象空间重新分配阶段
垃圾回收器对该对象的所占用的内存空间进行回收或者再分配了,则该对象彻底消失了,称之为“对象空间重新分配阶段”。
三、什么时候会垃圾回收
GC是由JVM自动完成的,根据JVM系统环境而定,所以时机是不确定的。当然,我们可以手动进行垃圾回收,比如调用System.gc()方法通知JVM进行一次垃圾回收,但是具体什么时刻运行也无法控制。也就是说System.gc()只是通知要回收,什么时候回收由JVM决定。但是不建议手动调用该方法,因为GC消耗的资源比较大。
- 当Eden区或者S区不够用了
- 老年代空间不够用了
- 方法区空间不够用了
- System.gc()
这短短的一生我们最终都会失去,不妨大胆一点,爱一个人,攀一座山,追一个梦