本文主要为《深刻理解Java虚拟机》第三章的读书记录笔记,同时伴有一些网络上资料的总结。html
Java堆是JVM主要的内存管理区域,里面存放着大量的对象实例和数组。在垃圾回收算法和垃圾收集器以前,首先要作的就是判断哪些对象已经“死去”,须要进行回收即不可能再被任何途径使用的对象。java
引用计数法是这样:给对象中添加一个引用计数器,每当有一个地方使用它时,计数器值就加1。当引用失效时,计数器就减1。任什么时候刻计数器为0的对象就是不可能再被使用的。算法
如今主流的Java虚拟机都没有使用引用计数法,最主要的缘由就是它很难解决对象之间互相循环引用的问题。数组
可达性分析的基本思路:经过一系列称为"GC Roots"的对象做为起点,从这些节点开始向下搜索,若是从GC Roots到一个对象不可达,则证实此对象是不可用的,以下图所示。浏览器
Java语言中,可做为GC Roots的对象包括下面几种:缓存
对于Java程序而言,对象基本都位于堆内存中,简单来讲GC Roots就是有被堆外区域引用的对象。bash
在JDK 1.2
之前的版本中,若一个对象不被任何变量引用,那么程序就没法再使用这个对象。也就是说,只有对象处于(reachable
)可达状态,程序才能使用它。网络
从JDK 1.2
版本开始,对象的引用被划分为4
种级别,从而使程序能更加灵活地控制对象的生命周期。这4
种级别由高到低依次为:强引用、软引用、弱引用和虚引用。多线程
强引用是使用最广泛的引用,以下的方式就是强引用:并发
Object strongReference = new Object();
复制代码
举例来讲,
public void test() {
Object strongReference = new Object();
// 省略其余操做
}
复制代码
strongReference = null
后。这个对象再也不被GC Roots可达,那么这个对象在下次GC时就会被回收。class Obj {
pulic static Object strongReference = new Object();
}
复制代码
若是对象只具备软引用,则
// 强引用
String strongReference = new String("abc");
String str = new String("abc");
// 软引用
SoftReference<String> softReference = new SoftReference<String>(str);
复制代码
软引用能够和一个引用队列(ReferenceQueue)联合使用。若是软引用的对象被垃圾回收,JVM就会把这个软引用加入到与之关联的引用队列中。
ReferenceQueue<String> referenceQueue = new ReferenceQueue<>();
// 强引用
String str = new String("abc");
SoftReference<String> softReference = new SoftReference<>(str, referenceQueue);
// 消除强引用
str = null;
// Notify GC
System.gc();
System.out.println(softReference.get()); // abc
Reference<? extends String> reference = referenceQueue.poll();
System.out.println(reference); //null
复制代码
注意:
也就是说,垃圾收集线程会在虚拟机抛出OutOfMemoryError以前回收软引用对象,并且虚拟机会尽量优先回收长时间闲置不用的软引用对象。对那些刚构建的或刚使用过的较新的软对象会被虚拟机尽量保留。
应用场景:
浏览器的后退按钮。按后退时,这个后退时显示的网页内容是从新进行请求仍是从缓存中取出呢?这就要看具体的实现策略了。
这时候就可使用软引用,很好的解决了实际的问题:
// 获取浏览器对象进行浏览
Browser browser = new Browser();
// 从后台程序加载浏览页面
BrowserPage page = browser.getPage();
// 将浏览完毕的页面置为软引用
SoftReference softReference = new SoftReference(page);
// 消除强引用
page = null;
// 回退或者再次浏览此页面时
if(softReference.get() != null) {
// 内存充足,尚未被回收器回收,直接获取缓存
page = softReference.get();
} else {
// 内存不足,软引用的对象已经回收
page = browser.getPage();
// 从新构建软引用
softReference = new SoftReference(page);
}
复制代码
相比较软引用,只具备弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它锁管辖的内存区域的过程当中,一旦发现了只具备弱引用的对象,无论当前内存空间足够与否,都会回收它的内存。不过,因为垃圾回收器是一个优先级很低的线程,所以不必定会很快发现那些只具备弱引用的对象。
String str = new String("abc");
WeakReference<String> weakReference = new WeakReference<>(str);
// 消除强引用
str = null;
复制代码
一样,弱引用能够和一个引用队列(ReferenceQueue)联合使用,若是弱引用的对象被垃圾回收,JVM就会把这个弱引用加入到与之关联的引用队列中。
ReferenceQueue<String> queue = new ReferenceQueue<>();
String str = new String("abc");
WeakReference<String> weakReference = new WeakReference<>(str, queue);
str = null;
System.gc();
try {
// 休息几分钟,等待上面的垃圾回收线程运行完成
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(weakReference.get()); // null
System.out.println(queue.poll()); // java.lang.ref.WeakReference@22a71081
复制代码
虚引用顾名思义,就是形同虚设。与其余几种引用都不一样,虚引用并不会决定对象的生命周期。若是一个对象仅持有虚引用,那么它就和没有任何引用同样,在任什么时候候均可能被垃圾回收器回收。
应用场景:
虚引用主要用来跟踪对象被垃圾回收器回收的活动。 虚引用与软引用和弱引用的一个区别在于:
虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,若是发现它还有虚引用,就会在回收对象的内存以前,把这个虚引用加入到与之关联的引用队列中。
String str = new String("abc");
ReferenceQueue queue = new ReferenceQueue();
// 建立虚引用,要求必须与一个引用队列关联
PhantomReference pr = new PhantomReference(str, queue);
复制代码
程序能够经过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要进行垃圾回收。若是程序发现某个虚引用已经被加入到引用队列,那么就能够在所引用的对象的内存被回收以前采起必要的行动。
标记-清除算法分为“标记”和“清除”两个阶段,执行过程以下图所示:
标记-清除算法主要有两个不足:
复制算法的大体思路以下,其执行过程以下图所示:
复制算法的代价就是将内存缩小为原来的一半。
如今的商业虚拟机都是采用复制算法来回收新生代。
标记-整理算法分为“标记”和“整理”两个阶段,执行过程以下图所示:
分代收集算法就是降Java堆分为新生代和老年代,根据其各自的特色采用最适当的收集算法。
JVM垃圾收集器发展历程大体能够分为如下四个阶段: Serial(串行)收集器 -> Parallel(并行)收集器 -> CMS(并发)收集器 -> G1(并发)收集器
下图展现了7种做用域不一样分代的收集器,若是两个收集器之间存在连续,就说明它们能够搭配使用。下面逐一介绍这些收集器的特性、基本原理和使用场景。
Serial类收集器是一个单线程的收集器:
它只会用单个收集线程去进行垃圾回收的工做
它在进行垃圾收集的时候会“Stop The World”暂停其余全部的工做表线程,直到它收集结束
Serial收集器采起复制算法在新生代进行单线程的回收工做
Serial Old收集器采起标记-整理算法在老年代进行单线程的回收工做
Parallel类收集器就是Serial收集器的多线程版本:
Parallel Scavenge收集器还有一个开关参数-XX: UseAdaptiveSizePolicy,打开这个开关后就不用手动指定新生代的大小(-Xmn),Eden与Survivor区的比例(-XX:SurvivorRatio)等细节参数了,JVM会动态调整这些参数已提供最合适的停顿时间或者最大吞吐量。
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它是一个基于标记-清除算法实现的,运做过程分为4个步骤:
初始标记(CMS initial mark): 须要“Stop The World”,仅仅只是标记下GC Roots能直接关联到的对象,速度很快
并发标记(CMS concurrent mark): CMS线程与应用线程一块儿并发执行,从GC Roots开始对堆中对象进行可达性分析,找出存活对象,耗时较长
从新标记(CMS remark):从新标记就是为了修正并发标记期间因用户线程继续运做而致使标记产生变更的那一部分对象的标记记录,能够多线程并行
并发清除(CMS concurrent sweep):CMS线程与应用线程一块儿并发执行,进行垃圾清除
CMS收集器优势:并发收集、低停顿
CMS的三个明显的缺点:
同优秀的CMS同样,G1也是关注最小停顿时间的垃圾回收器,也一样适合大尺寸堆内存,官方也推荐用G1来代替选择CMS。
G1以前的JVM堆内存模型,堆被分为新生代,老年代,永久代(1.8以前,1.8以后是元空间),新生代中又分为Eden和两个Survivor区。
G1收集器的堆内存模型,堆被分为不少个大小连续的区域(Region),Region的大小能够经过-XX: G1HeapRegionSize参数指定,大小区间为[1M,32M]。
每一个Region被标记了E、S、O和H,这些区域在逻辑上被映射为Eden,Survivor,老年代和巨型区(Humongous Region)。巨型区域是为了存储超过50%标准region大小的巨型对象。
G1能够在新生代和老年代使用,而CMS只能在老年代使用。
G1是复制+标记-整理算法,CMS是标记清除算法。
G1收集器的工做流程大体分为以下几个步骤:
G1提供了两种GC模式,Young GC和Mixed GC,两种都是彻底Stop The World的
当愈来愈多的对象晋升到老年代old region时,为了不堆内存被耗尽,虚拟机会触发一次mixed gc,该算法并非一个old gc,除了回收整个young region,还会回收一部分的old region。这里须要注意:是一部分老年代,而不是所有老年代,能够选择哪些old region进行收集,从而能够对垃圾回收的耗时时间进行控制
G1没有fullGC概念,须要fullGC时,调用serialOldGC进行全堆扫描(包括eden、survivor、o、perm)。