对JVM中垃圾回收机制的个人理解--对象的生命周期

时间:2021-03-17 09:06:15

现在我来瞎BB一些我看完垃圾回收的个人理解啦:掌声鲜花不是我需要的,最需要的就是指正我的错误。3q!!!!!!!!!!!!!!!!

首先,按照《深入理解JVM虚拟机》中所说的,主流的商用程序语言的主流实现中判断对象是否存活是用的可达性算法。

那么我们就先来看看JAVA中对象的生命周期:

1.创建阶段:用最简单粗暴的解释就是new一个对象,当然还有什么软引用,弱引用,虚引用,这些本菜鸡还没用到,就先不说了。

2.应用阶段:当创建了一个对象,并对某些变量赋值后,对象进入了应用阶段,这时对象被至少一个强引用所持有。

3.不可见阶段:这里等一下跟不可达阶段一起对比。

4.不可达阶段

5.收集阶段:当对象到了不可达阶段时,并且JVM已经重新分配好了内存,那么就进入了收集阶段。这个时候重写finalize方法可以进行对象的自救。尽量不要重写这个方法,因为

会影响JVM的对象重新分配与回收速度。

6.终结阶段:结束finalize()方法后对象仍然处于不可达状态,那么就会进入终结状态,这时等待GC回收。

7.对象空间的重新分配:GC对对象进行回收,对象彻底消失。

不可见与不可达之间:

这两者有一点还是相同的:程序本身不在持有该对象的强引用。

不可见阶段则相比较不可达阶段而言对了一条:该对象仍然被JVM装载下的某些静态变量或线程或JNI等引用。

而一个对象进入了不可见阶段,会造成内存泄露。

        感觉还是比较不好懂,至少我第一眼看到时感觉好迷茫,所以就自己写了一个例子来表示这两种状态:

/**

*为了更好的看到垃圾回收后的结果,我们将运行时的参数设为:

* -verbose:gc -Xms10M -Xmx10M -Xmn5M -XX:+PrintGCDetails

*/

public class ObjectLife{

private static byte[] by_static = null ;  //声明一个静态变量

private final static int ONE_MB = 1024 * 1024 ; // 声明一个静态常量,规定其值为1MB

public void seeorget(){

if(by_static  ==  null ){
byte[] by_if = new byte[ONE_MB]; //创建一个对象by_if,占用1MB的内存

by_static = by_if; //  by_if 被静态变量 by_static引用,所以进入不可见阶段 ,如果注释掉这段代码那么by_if就会进入不可达阶段

}

System.gc(); //进行一次Full GC (System)

}

}

public class Test{
public static void main(String[] args){
ObjectLife obj = new ObjectLife();

obj.seeorget();

}

}

在不将by_static = by_if; 注释掉之前我们运行后的到的控制台信息时这样的:

[Full GC (System) [Tenured: 0K->1176K(5120K), 0.0050972 secs] 1326K->1176K(9728K), [Perm : 2096K->2096K(12288K)], 0.0051386 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap
 def new generation   total 4608K, used 82K [0x03bc0000, 0x040c0000, 0x040c0000)
  eden space 4096K,   2% used [0x03bc0000, 0x03bd4818, 0x03fc0000)
  from space 512K,   0% used [0x03fc0000, 0x03fc0000, 0x04040000)
  to   space 512K,   0% used [0x04040000, 0x04040000, 0x040c0000)
 tenured generation   total 5120K, used 1176K [0x040c0000, 0x045c0000, 0x045c0000)
   the space 5120K,  22% used [0x040c0000, 0x041e6300, 0x041e6400, 0x045c0000)
 compacting perm gen  total 12288K, used 2123K [0x045c0000, 0x051c0000, 0x085c0000)
   the space 12288K,  17% used [0x045c0000, 0x047d2c10, 0x047d2e00, 0x051c0000)
No shared spaces configured.

因为本宝宝抱着巩固知识的心态写的文章,所以开始会对这打印出来的gc日志进行解释,所以以下分割线之间部分为gc日志解释,不想看的可以直接跳过哦~

-------------------------------------------------------------------------------------------帅的不要不要的分隔线-------------------------------------------------------------------------------------------------------

首先Full GC(System) 表示我们是在程序中调用的System.gc()方法。同样的还有Minor GC 和Major GC 这个先不说。

接下来的[Tenured 表示GC发生的区域:其中Tenured表示这次GC发生在老年代。后面的时间是这次GC所占用的时间。

后面的1326K->1176K(9728K) 表示GC前堆已使用量->GC后堆的使用量(堆总大小)

再往后的[Perm] 类似于上面的,这里的Perm代表的是永久代

[Times: user=0.00 sys=0.00, real=0.01 secs] :这个就是更详细的时间数据,分别是用户消耗的cpu时间,内核消耗cpu时间,以及stop-the-world时间。

Heap 以下部分是堆中各个代各个区域的使用情况。

其中

def new generation   total 4608K, used 82K [0x03bc0000, 0x040c0000, 0x040c0000)
  eden space 4096K,   2% used [0x03bc0000, 0x03bd4818, 0x03fc0000)
  from space 512K,   0% used [0x03fc0000, 0x03fc0000, 0x04040000)
  to   space 512K,   0% used [0x04040000, 0x04040000, 0x040c0000)

这一部分是新生代中的使用情况。

tenured generation   total 5120K, used 1176K [0x040c0000, 0x045c0000, 0x045c0000)
   the space 5120K,  22% used [0x040c0000, 0x041e6300, 0x041e6400, 0x045c0000)

这是老年代的使用情况。

还有

 compacting perm gen  total 12288K, used 2123K [0x045c0000, 0x051c0000, 0x085c0000)
   the space 12288K,  17% used [0x045c0000, 0x047d2c10, 0x047d2e00, 0x051c0000)

这是永久代的使用情况。

至于什么对象,什么情况进入哪个年代,在其他文章中再讲: 假设这里有url..................................................

----------------------------------------------------------------------------------------依旧帅气的不要不要的分割线------------------------------------------------------------------------------------------------------

现在回归正题,我们看到在

public void seeorget(){

if(by_static  ==  null ){
byte[] by_if = new byte[ONE_MB]; //创建一个对象by_if,占用1MB的内存

by_static = by_if; //  by_if 被静态变量 by_static引用,所以进入不可见阶段 ,如果注释掉这段代码那么by_if就会进入不可达阶段

}

System.gc(); //进行一次Full GC (System)

}

中我们进行Full GC (System)时,已经离开了by_if 的域,但是静态变量by_static引用了by_if,进入了不可见阶段,我们进行了垃圾回收,可是我们看到在老年代中这个对象仍然存在( the space 5120K,  22% used)所以这个对象并没有被回收。这就造成了内存泄露,而频繁多次的内存泄露则会造成内存溢出。因为这些空间一直被霸占,不能重新分配。


当我们把by_static = by_if; 注释掉之后,我们得到的gc日志是这样的

[Full GC (System) [Tenured: 0K->152K(5120K), 0.0044745 secs] 1326K->152K(9728K), [Perm : 2096K->2096K(12288K)], 0.0045098 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 4608K, used 82K [0x03b10000, 0x04010000, 0x04010000)
  eden space 4096K,   2% used [0x03b10000, 0x03b24818, 0x03f10000)
  from space 512K,   0% used [0x03f10000, 0x03f10000, 0x03f90000)
  to   space 512K,   0% used [0x03f90000, 0x03f90000, 0x04010000)
 tenured generation   total 5120K, used 152K [0x04010000, 0x04510000, 0x04510000)
   the space 5120K,   2% used [0x04010000, 0x040362f0, 0x04036400, 0x04510000)
 compacting perm gen  total 12288K, used 2122K [0x04510000, 0x05110000, 0x08510000)
   the space 12288K,  17% used [0x04510000, 0x04722bf0, 0x04722c00, 0x05110000)
No shared spaces configured.

这时我们看到老年代中没有存在该对象(the space 5120K,   2%)该对象被回收。对象进入不可达阶段。