JVM 是面试的重灾区,同样垃圾回收期也是重中之重。不过我们学习不是为了应付面试,而是处在这个行业中,应该去了解一下。
如果是做JVM调优的,必须要了解一下各种垃圾回收器的特点。进行非常多的参数调优不起作用,有时候简单的通过修改默认的垃圾回收器,就能起到很好的作用。
下边的文章是我自己写的,在写之前可看了别人挺多文章,关于垃圾回收器这篇文章很推荐:https://www.cnblogs.com/yanl55555/p/13365572.html
来先看看都有哪些垃圾回收器
垃圾回收器,可以进行分类,分为多线程的和单线程的。
垃圾回收器有以下几个。
1.新生代
- Serial (第一代)
- PraNew (第二代)
- Parallel Scavenge (第三代)
- G1收集器(第四代)
2.老年代
- Serial Old (第一代)
- Parallel Old (第二代)
- CMS (第三代)
- G1收集器 (第四代)
JVM垃圾收集器发展历程
第一阶段,Serial(串行)收集器
在jdk1.3.1之前,java虚拟机仅仅能使用Serial收集器。 Serial收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅是说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。
第二阶段,Parallel(并行)收集器
Parallel收集器也称吞吐量收集器,相比Serial收集器,Parallel最主要的优势在于使用多线程去完成垃圾清理工作,这样可以充分利用多核的特性,大幅降低gc时间。
第三阶段,CMS(并发)收集器
CMS收集器在Minor GC时会暂停所有的应用线程,并以多线程的方式进行垃圾回收。在Full GC时不再暂停应用线程,而是使用若干个后台线程定期的对老年代空间进行扫描,及时回收其中不再使用的对象。
第四阶段,G1(并发)收集器
G1收集器(或者垃圾优先收集器)的设计初衷是为了尽量缩短处理超大堆(大于4GB)时产生的停顿。相对于CMS的优势而言是内存碎片的产生率大大降低。
垃圾回收期的发展背景
可能记起来有点头疼,不妨从它的发展历程这个点出发,来记忆。其实问题就会变得简单,这是一个为什么有了自行车,还要发明汽车,汽车明明很快了,为什么还要有动车的问题 。
JVM的本质就是介于我们写的程序与操作系统资源的中间层,系统资源又特指 内存资源。
而垃圾回收器的发展,也是基于可使用的堆内存的扩大。回退十年,由于计算机本身发展的滞后,可用的机器资源,比如内存没有多少,可分配给堆内存的更是没有多少,当然当时的业务场景,要用到的资源同样没有多少。
我们先对垃圾回收器进行一个分类,分别是多线程的和单线程的。其中serial 就是单线程的,它就适合使用在内存非常小的场景下。因为本身堆内存没有多大,即使使用单线程,处理的时间也没有那么长。这也是最开始使用serial垃圾回收器的原因。
随着计算机的发展,和业务的增长,高并发的出现,我们需要对堆内存进行扩大,而之前的serial 也就不适用了。如果在超大堆内存下一定要使用它,只能表现出来的是系统莫名其妙的卡顿,毋庸置疑,很大一部分时间,就是因为正在垃圾回收,又因为STW的机制(stop the world 垃圾回收的时候,除了垃圾回收以外的工作必须停下来)。因此诞生了 parallel scavenge 垃圾回收器,从名字上就很容易知道,他是多线程的垃圾回收器。多个线程同时工作,从一定程度上缩短 STW的时间。但是这依然不够,因此诞生了CMS垃圾回收器,由于CMS垃圾回收器,有比较大的bug,它并不是默认的垃圾回收器。JDK 1.8 默认的垃圾回收器还是 Parallel Scavenge。
查看默认垃圾回收器
windows下,使用CMD 命令行工具,使用 java -XX:+PrintCommandLineFlags -version 这条命令来查看默认的垃圾回收器。
java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=266390080 -XX:MaxHeapSize=4262241280 -XX:+PrintCommandLineFlags
-XX:+UseCompressedClassPointers -XX:+UseCompressedOops
-XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
java version "1.8.0_191"
Java(TM) SE Runtime Environment (build 1.8.0_191-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)
UseParallelGC 即 Parallel Scavenge + Parallel Old,再查看详细信息
重点了解一下G1这个垃圾回收器
垃圾回收器进步的目标
垃圾回收器第四代产物。推动垃圾回收器发展的出发点只有一个,那就是尽可能的减少STW的时间,系统才能更快更多的对外提供服务。
G1 垃圾回收器的应用背景
JVM需要管理超大的堆内存
计算器的进步,多核CPU广泛的应用。
G1垃圾回收器的重要进步思想
G1最大的特点是引入分区的思路,弱化了分代的概念。这是一种分治的思想。同时这也是在特定背景下,应运而生的垃圾回收器。
G1与CMS的比较
- 算法: G1基于标记-整理算法, 不会产生空间碎片,分配大对象时不会无法得到连续的空间而提前触发一次FULL GC。
- 停顿时间可控: G1可以通过设置预期停顿时间(Pause Time)来控制垃圾收集时间避免应用雪崩现象。
- 并行与并发:G1能更充分的利用CPU,多核环境下的硬件优势来缩短stop the world的停顿时间。
- CMS中,堆被分为PermGen,YoungGen,OldGen;而YoungGen又分了两个survivo区域。在G1中,堆被平均分成几个区域(region),在每个区域中,虽然也保留了新老代的概念,但是收集器是以整个区域为单位收集的。
- G1在回收内存后会马上同时做合并空闲内存的工作、而CMS默认是在STW(stop the world)的时候做。
- G1会在Young GC中使用、而CMS只能在O区使用。
G1的堆内存算法
- G1之前的JVM内存模型
- 新生代:伊甸园区(eden space) + 2个幸存区
- 老年代
- 持久代(perm space):JDK1.8之前
- 元空间(metaspace):JDK1.8之后取代持久代
2. G1收集器的内存模型
1) G1堆内存结构
堆内存会被切分成为很多个固定大小区域(Region),每个是连续范围的虚拟内存。
堆内存中一个区域(Region)的大小可以通过-XX:G1HeapRegionSize参数指定,大小区间最小1M、最大32M,总之是2的幂次方。
默认把堆内存按照2048份均分。
2) G1堆内存分配
每个Region被标记了E、S、O和H,这些区域在逻辑上被映射为Eden,Survivor和老年代。
存活的对象从一个区域转移(即复制或移动)到另一个区域。区域被设计为并行收集垃圾,可能会暂停所有应用线程。
如上图所示,区域可以分配到Eden,survivor和老年代。此外,还有第四种类型,被称为巨型区域(Humongous Region)。Humongous区域是为了那些存储超过50%标准region大小的对象而设计的,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。
G1回收流程
在执行垃圾收集时,G1以类似于CMS收集器的方式运行。
- G1收集器的阶段分以下几个步骤:
1)G1执行的第一阶段:初始标记(Initial Marking )
这个阶段是STW(Stop the World )的,所有应用线程会被暂停,标记出从GC Root开始直接可达的对象。
2)G1执行的第二阶段:并发标记
从GC Roots开始对堆中对象进行可达性分析,找出存活对象,耗时较长。当并发标记完成后,开始最终标记(Final Marking )阶段
3)最终标记
标记那些在并发标记阶段发生变化的对象,将被回收。
4)筛选回收
首先对各个Regin的回收价值和成本进行排序,根据用户所期待的GC停顿时间指定回收计划,回收一部分Region。
最后,G1中提供了两种模式垃圾回收模式,Young GC和Mixed GC,两种都是Stop The World(STW)的。
G1的GC模式
- YoungGC年轻代收集
在分配一般对象(非巨型对象)时,当所有eden region使用达到最大阀值并且无法申请足够内存时,会触发一次YoungGC。每次younggc会回收所有Eden以及Survivor区,并且将存活对象复制到Old区以及另一部分的Survivor区。
YoungGC的回收过程如下:
- 根扫描,跟CMS类似,Stop the world,扫描GC Roots对象。
- 处理Dirty card,更新RSet.
- 扫描RSet,扫描RSet中所有old区对扫描到的young区或者survivor去的引用。
- 拷贝扫描出的存活的对象到survivor2/old区
- 处理引用队列,软引用,弱引用,虚引用
2. mixed gc
当越来越多的对象晋升到老年代old region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即mixed gc,该算法并不是一个old gc,除了回收整个young region,还会回收一部分的old region,这里需要注意:是一部分老年代,而不是全部老年代,可以选择哪些old region进行收集,从而可以对垃圾回收的耗时时间进行控制。
G1没有fullGC概念,需要fullGC时,调用serialOldGC进行全堆扫描(包括eden、survivor、o、perm)。
何时使用G1
G1的第一个重要特点是为用户的应用程序的提供一个低GC延时和大内存GC的解决方案。这意味着堆大小6GB或更大,稳定和可预测的暂停时间将低于0.5秒。
如果应用程序使用CMS或ParallelOld垃圾回收器具有一个或多个以下特征,将有利于切换到G1:
- Full GC持续时间太长或太频繁
- 对象分配率或年轻代升级老年代很频繁
- 不期望的很长的垃圾收集时间或压缩暂停(超过0.5至1秒)
注意:如果你正在使用CMS或ParallelOld收集器,并且你的应用程序没有遇到长时间的垃圾收集暂停,则保持与您的当前收集器是很好的,升级JDK并不必要更新收集器为G1。
为什么G1 GC从JDK 9之后成为默认的垃圾回收器
这篇文章整理的比较清晰。