我看 JAVA 之 垃圾回收 GC
我看 JAVA 之 垃圾回收 GC
概述
随着程序的运行,内存中对象、变量等占据的内存越来越多,如果不及时进行垃圾回收,必然会带来程序性能的下降,甚至造成一些不必要的系统异常(比如 OOM)。
通过引用计数法和可达性分析来判断是否有必要进行垃圾回收。
常见的回收算法:标记清除、复制算法、标记-整理算法、分代回收算法。
HotSpot 虚拟机中的主要包含如下几种垃圾收集器:Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1、ZGC。
在 《我看 JAVA 之 JVM》章节,我们已经介绍了 JVM 的几个主要内存区域(见下图)。JVM 中本地方法栈、程序计数器、虚拟机栈随着线程存亡而存亡,这几个区域的内存分配和回收具有确定性,随着方法结束或线程消亡,内存会跟着回收。JVM 的 Heap 堆不一样,这部分的内存分配和回收是动态的,通常我们关注的垃圾回收也主要是这部分内存。
如何判断哪些对象需要被回收?
引用计数法
为 java 对象添加一个引用计数器 counter,对象被引用一次则 counter++,引用时效则 counter--,当 counter == 0 时,对象未被使用可以回收,但是引用计数法无法解决循环引用的问题。
可达性分析法
通过一系列的称为 "GC Roots" 的对象作为起点,开始向下搜索,搜索所经过的路径称为引用链(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时,则证明该对象是不可用的。
如果,相对 GC Roots 来说,obj1~obj4 是可达的,而 obj5,obj6 不可达,说明 obj5 和 obj6 是可以被回收的。
在 Java 中,可作为 GC Root 的对象包括以下几种:
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中 JNI(即一般说的 Native 方法)引用的对象
垃圾收集算法
常见的垃圾回收算法主要包括 5 种,分别为标记清除算法、复制算法、标记-整理算法、分代回收算法、分区回收算法。
标记清除算法(Mark-Sweep)
分为“标记”和“清除”两个阶段:首先标记出需要回收的对象,标记完成之后统一清除对象。
缺点:
标记和清除效率不高。
产生大量不连续的内存碎片,导致有大量内存剩余的情况下,由于,没有连续的空间来存放较大的对象,从而触发了另一次垃圾收集动作。
复制算法(Copying)
为了解决 Mark-Sweep 算法的缺陷,Copying 算法就被提了出来。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。
缺点:
内存缩小为原来的一半,太浪费内存
标记整理算法(Mark-Compact)
为了解决 Copying 算法的缺陷,充分利用内存空间,提出了 Mark-Compact 算法。该算法标记阶段和 Mark-Sweep 一样,但是在整理阶段,不是直接对标记对象进行清理,而是让所有存活的对象都移动到一端,然后,直接把边界以外的内存清空。解决了 Mark-Sweep 算法会造成大量不连续内存碎片的问题。
缺点:
标记和整理效率不高
分代回收算法
目前大多 JVM 都采用分代收集算法,根据对象的存活的时间的长短,将内存分为了新生代、老年代,永久代等,这样就可以针对不同的区域,采取对应的算法。
垃圾收集器
Serial
stop-the-world
复制算法
单线程
client 模式
ParNew
stop-the-world
复制算法
多线程,在单核环境下性能相对 Serial 不一定占优势
效率优先
ParNew 相对 Parallel Scavenge 来说,可以与 CMS 搭配使用
Parallel Scavenge
stop-the-world
复制算法
多线程
吞吐量优先
Serial Old
stop-the-world
标记-清除-整理算法
单线程
Parallel Old
复制算法
多线程
CMS
并发收集器
低停顿
G1
后续单独讲解
ZGC
后续单独讲解
评论