在确定如何判断对象已经死亡之后,就可以进入GC的回收阶段了,而回收阶段比较重要的知识就是垃圾回收算法了。
常见的垃圾回收算法主要分为四种:标记清除(Mark-Sweep),复制(Copying),标记整理(Mark-Compact),分代收集(Generational Collection)。
1. 标记清除
标记清除是一种最基本的垃圾回收算法,首先标记出需要回收的对象,在标记完成之后统一回收被标记的对象。
https://www.cnblogs.com/dolphin0520/p/3783345.html
这个方法很好理解,就是执行GC的两次标记过程,被标记的对象,将会被清除。但是随之而来又有相应的问题,算法执行的效率不高,而且重点是清除之后会产生内部碎片,如上图所示,黑色部分被回收之后,产生了一些碎片,这样在分配内存给大对象的时候,无法找到足够的内存,而又要触发收集动作。
2. 复制
为了解决标记清除算法问题,产生了复制算法。
首先在使用时候,将内存分为两块,比如下图中是上下两块,回收后将上部分所有的存活对象移动到下面,将上面空闲出来。
https://www.cnblogs.com/dolphin0520/p/3783345.html
很显然,这种操作解决了碎片的问题,但也暴露出相应的弊端,比如说空间的浪费,在使用的时候将储存空间一分为二,内存优势大打折扣,一次GC触发之前只能用一半内存来工作。
另一个显而易见的问题就是,当对象很多的时候,挨个复制去调整位置,也是个大问题。
3. 标记整理算法
前面说过的复制算法在对象很多,或者是存活率高的情况下,就要进行不断的Copy动作,效率很差,所以根据老年代的特点,产生了标记整理算法。
https://www.cnblogs.com/dolphin0520/p/3783345.html将对象标记之后,全部存活的对象向一端移动,然后清理掉存活对象边界以外的内存。
4. 分代收集
目前大多数Jvm都采用的一种方法,这种方法的重点不在于怎么清理,而是在于将内存空间分为了 老年代和新生代,老年代的特点就是其中的对象不怎么被回收,而新生代的特点就是其中的对象要经常被回收
在老年代,由于对象回收量较小,所以一般采用标记整理算法。
在新生代,由于对象回收量较大,所以一般采用复制算法。
但是也不是说一定要付出复制算法的超高代价,因为实际应用中,一般将新生代划分为一块大的Eden和两块小的Survivor空间,每次使用一块Eden和一块Survivor,当回收时,把所有的存活对象Copy到另一块Survivor上,其余的空间全清理,然后继续使用这块Survivor和Eden来进行操作。
一般Eden : Survivor = 8 : 1 (也就是总共分了10份,Eden占8份)
其他
需要注意的是,很多人认为方法区是不会被GC的,刚才我们说过,有好多存在于方法区的静态属性对象和常量对象就不会被回收,还可以当作GC Roots。
但是其实不然,方法区,或者叫做永久代的回收性价比不高,但是Jvm没有规定说一定不进行GC,永久代的垃圾回收主要分为两个部分 废弃常量和无用的类
判断常量是否废弃很简单,类似于判定对象是否被引用,而判断类是否无用,要看下面三个方面:
- 是否有对应类的实例还存在
- 是否有经过反射或者Class等方式访问到
- 类加载器是否被回收。
具体的回收,暂时不做深究。
参考 : 周志明《深入理解Java虚拟机》
https://www.cnblogs.com/dolphin0520/p/3783345.html
网友评论