垃圾回收基本概念和垃圾回收算法
一、为什么需要了解垃圾回收机制?
Java与C、C++很重要的一个区别点就在于java上层开发者不再需要手动申请和释放内存(当然,java也提供了这种方式);这种不需要关注内存申请和释放使上层开发人员能够更加简便编写出安全可靠的代码,更加关注业务本身,但与之而来的问题是,java的垃圾回收并不是万能的,当出现诸如OOM或生产环境卡顿时间长,响应慢等问题的时候,如果不了解java垃圾回收机制,则可能会无处下手。所以,熟悉垃圾回收原理能让我们更加从容的面对上次产品出现的问题,也是JVM调优的基础。
二、什么是垃圾?JVM怎么确定垃圾?
垃圾就是没有任何引用指向的对象,包括形成孤岛互相引用的对象(循环引用)。比如:new Student(),分配在堆中,作用域结束后就变成了无引用的一个堆内存块,也就是垃圾。
JVM通过GCROOT可达性分析确定堆中的垃圾对象,JVM把线程栈中的变量,静态变量,常量池以及JNI指针作为GC ROOT,并从各个GCROOT进行递归遍历(类似于顺藤摸瓜),凡是某个对象到任一GCROOT有一条引用链,则JVM认为其不为垃圾,所有其他没有形成到GCROOT引用链(包括只是互相引用)的对象,JVM都认为它们是垃圾,应该被垃圾回收器回收。
三、垃圾回收算法
1.标记清除算法(Mark-Sweep)
JVM从每一个GCROOT开始深度遍历,标记所有存活的对象,所有的标记完成之后,垃圾回收器会再把整个堆遍历一次,直接回收那些没有被标记的对象,也就是垃圾对象。
标记清除算法标记和清除阶段状态图如下:

可以明显的看出垃圾回收器在标记清除算法做的事情,标记阶段是把所有存活的对象进行标记,清除阶段是把所有垃圾对象直接清除。
优点:
算法相对比较简单(两次循环),存活对象较多的情况下效率比较高,一般会作为分代垃圾回收的老年代(大对象、长生命周期对象)回收算法。
缺点:
需要两次遍历,效率比较低,并且从上图中可以看出回收之后的未使用空间并不连续,容易产生空间碎片。
2.复制算法(Copying)
分配两块同样大小的内存空间from和to(对应年轻代中的s0和s1区域),每次都只使用其中一块from区域,当from空间中对象放满后,垃圾回收器会进行一次次回收,然后把存活的对象复制到to中,清空from区域,接着交换两块内存区域,此时原先的to区域变为了from,而from变为了to(未使用的状态),重复这个过程直到到达阈值,from和to区域一起回收。这种交替重复使用两块同样大小内存区域的算法就是复制算法。
缺点:
对比标记清除算法,复制算法需要把存活对象从from拷贝到to区域,有一块内存区域始终是空闲着的,造成空间的浪费;
优点:
只需要扫描一次,效率更高,并且所有对象都是集中在两块内存区域中的,回收之后不会产生碎片问题。
一般作为分代垃圾回收器中的年轻代回收算法,对象存活时间短,垃圾对象产生的频率高,存活对象少,需要快速回收。
3.标记整理(压缩)算法(Mark-Compact)
这种算法和标记清除算法类似,只不过为了解决回收后的碎片化问题,标记整理算法会在标记阶段完成后,会把所有垃圾对象朝某一个方向移动,最后集中进行清理。

可以从上图看出清理之后,存活对象占用的内存区域和未使用的区域都是连续的,也就不存在碎片化的问题。
缺点:
需要扫描两次并且需要移动对象到指定的位置,效率低;
优点:
不会产生碎片,方便对象分配,不会浪费空间。
四、堆内存逻辑分区

逻辑分代模型,根据不同分代存放不同特点的存活对象,java中基本所有的对象都在堆中被创建,也就是在Eden区中;Eden区中的大多数对象都会在第一次垃圾回收时被回收,存活下来的对象会转移到s0区域中,经过次回收和复制算法,当存活对象在s0和s1之间的复制年龄达到设定的阈值(默认为15->和对象头中的垃圾标识位有关,4位,最大值就为15,CMS默认为6)时,对象会进入到老年代。
注:对象年龄可以通过参数-XX:MaxTenuingThreshold参数设置。

浙公网安备 33010602011771号