JVM学习笔记(四)
文章目录
笔记链接
JVM学习笔记(一)
JVM学习笔记(二)
JVM学习笔记(三)
JVM学习笔记(四)
(待更新…)
1.GC算法
1.1GC-判断对象是否可回收
1.1.1 引用计数法
在 Java 中,引用和对象是有关联的。显然引用计数法,即一个对象如果没有任何与之关联的引用,即他们的引用计数为 0,则说明对象不太可能再被用到,那么这个对象就是可回收对象。
方案: 给对象维护一个引用计数器,当有一个地方引用到它,则计数器加1,当有引用失效,则计数器减1,当为0时,说明没有地方引用到这个对象。
- 优点: 实现简单、效率高
- 缺点: 无法解决循环引用。(致命缺点)
循环引用问题: 假设栈中有两引用A,B,对应堆中的对象分别为a,b,而对象a,b中有相互指向的引用C和D,现在A=null,B=null,栈中已经没有引用指向对象a和b,但对象中C和D引用相互指向,所以a,b无法释放。
如图效果:
1.1.1 可达性分析
为了解决引用计数法的循环引用问题,Java 使用了可达性分析的方法。
通过一系列的“GC roots”对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是不可达对象,则将面临回收。
图片:
图中的a,b对象将会被回收。
1.2GC-回收算法
标记清除法(Mark-Sweep)
法如其名,分为两个阶段,标注和清除。标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。
- 缺点: 内存碎片化。
如图:
标记整理法(Mark-Compact)
标记阶段和 Mark-Sweep 算法相同,标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。
如图:
复制算法(copy)
按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉。回顾:GC中minor GC用的算法。
-
优点: 内存效率高,不易产生碎片
-
缺点: 浪费一定量的空间(要找地方放复制的对象)
图片:
1.2.1 分代收集算法(重点)
现在绝大部分的JVM在使用的算法,其实就是根据堆的老年代和新生代而划分使用上面三种GC中的某几种。老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。
1. 新生代和复制算法
新生代通常使用复制算法,From和To区域就是这样来的,以为复制算法需要交换区来保存存活对象。为新生代中每次垃圾回收都要回收大部分对象,即要复制的操作比较少,但通常并不是按照 1:1 来划分新生代。而是按照8:1:1来划分(这个是HotSpot虚拟机的默认比例)。o( ̄▽ ̄)ブ,来回顾一下前面的笔记:
由于频繁创建对象,所以新生代会频繁触发MinorGC 进行垃圾回收。当 Eden 区内存不够的时候就会触发 MinorGC,对新生代区进行一次垃圾回收。MinorGC使用的是复制算法,过程如下:
- MinorGC会把Eden Space+From Space扫描一边
- 扫描过后将存活的对象复制到 To Space 区域,并对对象的年龄进行+1
- 然后清空Eden Space+From Space的对象
- 最后,将To Space和From Space标志互换(不移动对象,区域的标志更换,就是To变成From),To Space等待下次MinorGC的回收。
2. 老年代与标记整理算法
老年代因为每次只回收少量对象,因而一般采用 Mark-Compact 算法。老样子回顾一下,这次整合一下:
- MinorGC会把Eden Space+From Space扫描一边
- 扫描过后将存活的对象复制到 To Space 区域,并对对象的年龄进行+1
- 然后清空Eden Space+From Space的对象
- 最后,将To Space和From Space标志互换(不移动对象,区域的标志更换,就是To变成From),To Space等待下次MinorGC的回收。
- 当老年区满了以后(一般以为MinorGC后存活的对象无法放到老年区),垃圾回收器会对老年区进行清理。
- 标记老年区中的存活对象后,将存活对象移向内存的一端。
- 然后清除端边界外的对象。
分区收集算法
分区算法则将整个堆空间划分为连续的不同小区间, 每个小区间独立使用, 独立回收.。
2.GC垃圾收集器
堆内存被划分为新生代和年老代两部分,新生代主要使用复制和标记-清除垃圾回收算法;年老代主要使用标记-整理垃圾回收算法,因此 java 虚拟中针对新生代和年老代分别提供了多种不同的垃圾收集器。垃圾回收器是GC的执行者,所以前面的笔记并没有特别说明Full GC和Major GC是指定用那种算法进行回收,其实不同垃圾回收器对不同的区域进行垃圾回收,而且不同垃圾回收器使用的算法并不完全一致。下面我就要介绍垃圾回收器的种类。o( ̄▽ ̄)ブ
2.1 垃圾收集器的种类
垃圾回收器主要分三大类:
- 新生代收集器:Serial、ParNew、Parallel Scavenge
- 老年代收集器:CMS、Serial Old、Parallel Old
- 整堆收集器: G1
HotSpot JVM (JDK1.6)的垃圾回收器情况图
2.2 Serial垃圾收集器
Serial是最基本垃圾收集器,Serial使用的算法和线程类型如下:
- 算法: 复制算法
- 线程: 单线程
- 区域: 新生代
Serial 是一个单线程的收集器,**在进行垃圾收集的同时,必须暂停其他所有的工作线程,直到垃圾收集结束。(多CPU下也如此)**Serial 垃圾收集器虽然在收集垃圾过程中需要暂停所有其他的工作线程,但是它简单高效,对于限定单个 CPU 环境来说,没有线程切换的开销。单核单CPU的情况下可以获得最高的单线程垃圾收集效率。
Serial和Serial Old执行过程如下图:
2.3 ParNew垃圾收集器
ParNew 垃圾收集器其实是 Serial 收集器的多线程版本,也使用复制算法,除了使用多线程进行垃圾收集之外,其余的行为和 Serial 收集器完全一样。
- 算法: 复制算法
- 线程: 多线程
- 区域: 新生代
ParNew 收集器默认开启和 CPU 数目相同的线程数,可以通过下面JVM参数调节
-XX:ParallelGCThreads
ParNew垃圾收集器是很多 java虚拟机运行在 Server 模式下新生代的默认垃圾收集器。
2.4 Parallel Scavenge 收集器
Parallel Scavenge 收集器也是一个新生代垃圾收集器,在垃圾收集过程中也是需要暂停所有的工作线程有以下特点:
- 算法: 复制算法
- 线程: 多线程
- 调节策略: 自适应调节策略(根据吞吐量控制)
- 区域: 新生代
Parallel Scavenge是一种非常高效的垃圾收集器,它使用吞吐量来控制垃圾回收的时间,吞吐量公式如下:
吞 吐 量 = 运 行 用 户 代 码 s h i 间 运 行 用 户 代 码 时 间 + 垃 圾 收 集 时 间 吞吐量=\frac{运行用户代码shi间}{运行用户代码时间+垃圾收集时间} 吞吐量=运行用户代码时间+垃圾收集时间运行用户代码shi间
高吞吐量可以最高效率地利用 CPU 时间,尽快地完成程序的运算任务,主要适用于在后台运算而不需要太多交互的任务。Parallel Scavenge追求的是高吞吐量。自适应调节策略也是 ParallelScavenge 收集器与 ParNew 收集器的一个重要区别。
2.5 Serial Old收集器
Serial Old 是 Serial 垃圾收集器年老代版本,老年代一般使用标志整理算法(Mark-compact),跟Serial 垃圾收集器一样,是单线程,也一样要停掉其他线程的运行。运行在 Serial Old是Client 默认的 java 虚拟机默认的老年代垃圾收集器,跟Serial不同,Serial是Client 默认的 java 虚拟机默认的新生代垃圾收集器。
- 算法: 标志整理算法
- 线程: 单线程
- 区域: 老年代
Serial和Serial Old执行过程如下图:
2.6 Parallel Old收集器
从名字上就知道,Parallel Old 收集器是Parallel Scavenge的年老代版本,行为上跟Parallel Scavenge基本一样,而算法不同。Parallel Old 正是为了在年老代同样提供吞吐量优先的垃圾收集器。
- 算法: 标志整理算法
- 线程: 多线程
- 区域: 老年代
- 提供时间: JDK1.6
Parallel Scavenge 和Parallel Old 搭配运行过程图:
2.7 CMS 收集器
Concurrent mark sweep(CMS)收集器是老年代垃圾收集器,主要目标是获取最短垃圾回收停顿时间,使用的算法跟其他老年代的垃圾收集器有所不同,使用的是标记清除算法。一般用在一些交互比较高的程序,最短时间的停顿,能有效确保交互程序和用户交互的体验。
- 算法: 标志清除算法
- 线程: 多线程
- 区域: 老年代
CMS工作机制:
- 初始标记:只是标记一下 GC Roots 能直接关联的对象,速度很快,但暂停所有的工作线程。
- 并发标记:进行 GC Roots 跟踪的过程,和用户线程一起工作。
- 重新标记:为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。
- 并发清除:清除 GC Roots 不可达对象,和用户线程一起工作,不需要暂停工作线程。
各阶段的耗时:
- 初始标记:耗时短(简易标志,速度快)
- 并发标记:耗时较长(跟踪)
- 重新标记:耗时短(补全并发标记时,程序变化的那部分,标记量小)
- 并发清除:耗时较长(清除)
结论:总体上来看CMS 收集器的内存回收和用户线程是一起并发地执行。
工作过程图:
2.8 G1收集器
Garbage First(G1)是垃圾收集领域的最新成果,同时也是HotSpot在JVM上力推的垃圾收集器,并赋予取代CMS的使命。
找到两篇很好的博客
https://blog.csdn.net/renfufei/article/details/41897113.
https://blog.csdn.net/coderlius/article/details/79272773.
- G1的设计原则是"首先收集尽可能多的垃圾(Garbage First)"。因此,G1并不会等内存耗尽(串行、并行)或者快耗尽(CMS)的时候开始垃圾收集,而是在内部采用了启发式算法,在老年代找出具有高收集收益的分区进行收集。同时G1可以根据用户设置的暂停时间目标自动调整年轻代和总堆大小,暂停目标越短年轻代空间越小、总空间就越大;
- G1采用内存分区(Region)的思路,将内存划分为一个个相等大小的内存分区,回收时则以分区为单位进行回收,存活的对象复制到另一个空闲分区中。由于都是以相等大小的分区为单位进行操作,因此G1天然就是一种压缩方案(局部压缩);
- G1虽然也是分代收集器,但整个内存分区不存在物理上的年轻代与老年代的区别,也不需要完全独立的survivor(to space)堆做复制准备。G1只有逻辑上的分代概念,或者说每个分区都可能随G1的运行在不同代之间前后切换;
- G1的收集都是STW的,但年轻代和老年代的收集界限比较模糊,采用了混合(mixed)收集的方式。即每次收集既可能只收集年轻代分区(年轻代收集),也可能在收集年轻代的同时,包含部分老年代分区(混合收集),这样即使堆内存很大时,也可以限制收集范围,从而降低停顿。
. G1虽然也是分代收集器,但整个内存分区不存在物理上的年轻代与老年代的区别,也不需要完全独立的survivor(to space)堆做复制准备。G1只有逻辑上的分代概念,或者说每个分区都可能随G1的运行在不同代之间前后切换;- G1的收集都是STW的,但年轻代和老年代的收集界限比较模糊,采用了混合(mixed)收集的方式。即每次收集既可能只收集年轻代分区(年轻代收集),也可能在收集年轻代的同时,包含部分老年代分区(混合收集),这样即使堆内存很大时,也可以限制收集范围,从而降低停顿。