JVM_垃圾回收机制

如何判断对象为垃圾:
1、引用计数法:
在 Java 中,引用和对象是有关联的。如果要操作对象则必须进行引用。当对象没有任何与之关联的引用(为null),即他们的引用计数都为 0时,则说明对象不太可能再被用到,那么这个对象就是可回收对象。但是当堆内的两个对象相互引用时,这时如果对象没有外部引用,但是引用计数不为0,仍是不可回收的。(现在的JVM一般不用)
2、可达性分析法:
该算法的基本思路就是通过一些被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到GC Roots没有任何引用链相连时(即从GC Roots节点到该节点不可达),则证明该对象是不可用的。

在Java中,可作为GC Root的对象包括以下几种:
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI(即一般说的Native方法)引用的对象
JAVA中的四种引用类型:
1、强引用:
在 Java 中常见的就是强引用,把一个对象赋给一个引用变量,类似Object obj = new Object()这个引用变量就是一个强引 用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即该对象以后永远都不会被用到JVM也不会回收。因此强引用是造成Java内存泄漏的主要原因之 一。
2、软引用:
软引用需要用 SoftReference 类来实现,对于只有软引用的对象来说,当系统内存足够时它 不会被回收,当系统内存空间不足时它会被回收。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。
import java.lang.ref.SoftReference;
public class Main {
public static void main(String[] args) {
SoftReference<String> sr = new SoftReference<String>(new String("hello"));
System.out.println(sr.get());
}
}
3、弱引用:
弱引用需要用WeakReference 类来实现,它比软引用的生存期更短,对于只有弱引用的对象 来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,总会回收该对象占用的内存。
import java.lang.ref.WeakReference;
public class Main {
public static void main(String[] args) {
WeakReference<String> sr = new WeakReference<String>(new String("hello"));
System.out.println(sr.get());
System.gc(); //通知JVM的gc进行垃圾回收
System.out.println(sr.get());
}
}
4、虚引用:
虚引用需要PhantomReference类来实现,它不能单独使用,必须和引用队列联合使用。虚 引用的主要作用是跟踪对象被垃圾回收的状态。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
public class Main {
public static void main(String[] args) {
ReferenceQueue<String> queue = new ReferenceQueue<String>();
PhantomReference<String> pr = new PhantomReference<String>(new String("hello"), queue);
System.out.println(pr.get());
}
}
垃圾回收算法:
1、标记-清除算法:
分为两个阶段,标注和清除。标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。如图

从图中我们就可以发现,该算法大的问题是内存碎片化严重,后续可能发生大对象不能找到可利用空间的问题。
2、复制算法(针对新生代):
为了解决Mark-Sweep算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为等大小 的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用 的内存清掉,如图:

这种算法虽然实现简单,内存效率高,不易产生碎片,但是大的问题是可用内存被压缩到了原 本的一半。且存活对象增多的话,Copying算法的效率会大大降低。
( 每次垃圾收集都能发现大批对象已死, 只有少量存活. 因此选用复制算法, 只需要付出少量 存活对象的复制成本就可以完成收集. )
3、标记-整理算法(针对老年代):
结合了以上两个算法,为了避免缺陷而提出。标记阶段和Mark-Sweep算法相同,标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。
(因为对象存活率高、没有额外空间对它进行分配担保, 就必须采用“标记—清理”或“标 记—整理”算法来进行回收, 不必进行内存复制, 且直接腾出空闲内存. )
4、分代收集算法:
分代收集法是目前大部分JVM所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不同的域。
垃圾收集器:

1、Serial垃圾收集器(单线程+复制算法)
Serial Old垃圾收集器(单线程+标记-整理算法)
Serial 是一个单线程的收集器,它不但只会使用一个 CPU 或一条线程去完成垃圾收集工 作,并且在进行垃圾收集的同时,必须暂停其他所有的工作线程,直到垃圾收集结束。它简单高效,对于限定单个 CPU 环境来说,没有线程交互的开销,可以获得高的单线程垃圾收集效率,因此 Serial 垃圾收集器依然是java虚拟机运行在Client模式下默认的新生代垃圾收集器 。

2、ParNew 垃圾收集器(Serial+多线程)
ParNew垃圾收集器其实是Serial收集器的多线程版本,也使用复制算法,除了使用多线程进行垃 圾收集之外,其余的行为和Serial收集器完全一样;
ParNew 收集器默认开启和 CPU 数目相同的线程数,可以通过-XX:ParallelGCThreads 参数来限制垃圾收集器的线程数。ParNew虽然是除了多线程外和Serial收集器几乎完全一样,但是ParNew垃圾收集器是很多java 虚拟机运行在Server模式下新生代的默认垃圾收集器。
3、Parallel Scavenge收集器(ParNew+吞吐量)
同ParNew一样,但它重点关注的是程序达到一个可控制的吞吐量(吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间));高吞吐量可以高效率地利用 CPU 时间,尽快地完成程序的运算任务。
4、 Parallel Old收集器(多线程+标记-整理算法)
Parallel Old收集器是Parallel Scavenge的年老代版本
5、CMS 收集器(多线程 + 标记-清除算法)
Concurrent mark sweep(CMS)收集器是一种年老代垃圾收集器,其主要目标是获取短垃圾回收停顿时间。
CMS 收集器工作过程:

所以总体上来看CMS收集器的内存回收和用户线程是一起并发地执行。
缺点:
1、会占用大量的CPU
2、无法处理浮动垃圾
3、基于标记清除算法,会产生大量内存碎片
6、G1 收集器
Garbage first 垃圾收集器是目前垃圾收集器理论发展的前沿成果,相比与CMS 收集器,G1收集器两个突出的改进是:
1. 基于标记-整理算法,不产生内存碎片。
2. 可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。
G1 收集器避免全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域,并且跟踪这些区域 的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间,优先回收垃圾多的区域。区域划分和优先级区域回收机制,确保 G1 收集器可以在有限时间获得高的垃圾收 集效率

浙公网安备 33010602011771号