【JVM之二】垃圾回收

我感觉这句话特别好:java和c++之间有堵内存动态分配和垃圾收集技术构成的高墙,墙内的人想出去,墙外的人想进来。哈哈哈,有点围城的感觉。

一.JVM里回收哪里的垃圾?

内存区域一共有:程序计数器、虚拟机栈、本地方法栈、堆、方法区。前三个都是线程私有,随着线程的生而生,随着线程的灭亡而灭亡。所以GC(garbage collection)的内存区域就只有堆和方法区了。 我们都知道堆里面存放的是对象,而方法区里面存放的是类的信息、常量、静态变量、即时编译器编译后的代码等.....然而什么时候该回收他们呢?

二:什么时候回收垃圾

有一句话说的特别好,垃圾判定标准是什么?是否还存在指向这个对象的引用。

1、首先说一下堆里面的对象:怎么判定是否存在指向这个对象的引用呢?有两个方法:引用计数器算法和可达性分析算法。

(1)引用计数器算法:给对象添加一个引用计数器,每当一个地方引用他就加一,引用失效,减一。

缺点很致命:就是无法解决对象之间的循环引用问题。

(2)可达性分析算法:如下图(借鉴的我一个学长的几张图 很清晰易懂,向他学习!)

如果一个对象有GC Roots没有任何引用链相连,那么证明这个对象不可用。就是图中的红球。 

作为GC Roots中的对象有四种:

①虚拟机栈中(栈帧的局部变量表)引用的对象

②本地方法栈中引用的对象

③方法区中静态属性引用的对象

④方法区中常量引用的对象

【JVM之二】垃圾回收

2.再说下回收方法区里面的垃圾,方法区里面的垃圾有两种:废弃的常量和无用的类,然而怎么判定是不是废弃的常量和无用的类呢?

废弃的常量:没有任何地方引用这个常量,那么回收!、

无用的类:

(1)堆中不存在该类的实例

(2)加载该类的classloader已经被回收

(3)类对应的class对象没有在任何地方被引用,并且不能在任何地方通过反射的方法访问这个方法。

三、知道那些是垃圾,接下来就是清理工作了?简单总结几种回收算法:标记清除、标记整理、复制算法。

(1)先来最简单的标记清除算法:第一步工作就是标记出垃圾对象,第二部工作当然是清除这个垃圾对象了。

缺点很明显就是产生了很多内存碎片,还有就是这个算法的标记过程和清除过程效率都不高!

 

 

【JVM之二】垃圾回收

(2)我理解标记整理算法是标记清除算法的升级,首先第一步还是标记垃圾对象,第二步是把存活的对象向一端移动,第三部是把内存的端边界之外的内存进行清理。如下图:

【JVM之二】垃圾回收

(2)还有就是复制算法了:

JVM把新生代分为了三块:一个Eden和两个Survival,系统默认划分比例是8:1:1,复制算法是怎么一个过程呢?

首先在把新生成的对象放到eden里面去,第一次回收垃圾的时候,把存活的对象复制到survivor1(假设,两个servivor任意一个都可以)中去;

把新生成的对象在放到eden里面,进行第二次垃圾回收,把eden和survivor1中存活的对象复制到survivor2中去

新生成的对象在放到eden里面,进行第三次垃圾回收,把eden和survivor2中的存活的对象复制到survivor1中去

新生成的对象在放到eden里面,第四次垃圾回收,把eden和survivor1中的对象复制到survivor2中去.............一直循环(如下图)
 

【JVM之二】垃圾回收

但是!!

如果eden和一个survivor中存活的对象大于另一个survivor的空间大小了怎么办!那就用分配担保机制把对象放到老年代中去。

为什么说是分配担保机制呢?因为老年代也不能保证有足够的空间去存放这些存活的对象啊!

如下图是空间分配担保机制的详细步骤:

【JVM之二】垃圾回收

总结一下都有那些情况新生代中的对象晋升到老年代中:

(1)第一种就是上面的那一种情况,survovor放不下了,那就分配担保进入老年代

(2)大对象直接进入老年代(可设置大对象的大小)

(3)长期存活的对象进入老年代(年代计数器)

(4)动态年龄判断:如果survivor中同年龄所有对象的大小大于survivor存储空间的一半,那么大于等于这个年龄的晋升老年代