垃圾回收算法理论
复制算法
复制算法将可用的内存容量划分成大小相等的两块,每次只使用其中的一块;当这一块内存用完,就会将还存活的对象放在另一块区域上,然后再把已使用的内存空间一次清理掉,这样每次清理垃圾的时候都是对整个半区进行垃圾回收,内存分配的时候也不用考虑内存碎片的问题了,这样对于内存的回收就更加简单高效。
1540372816097.png但是这种算法也有缺点:
-
1)需要提前预留一半的内存区域用来存放存活的对象(经过垃圾收集后还存活的对象),这样导致可用的对象区域减小一半,总体的GC更加频繁了
-
2)如果出现存活对象数量比较多的时候,需要复制较多的对象,成本上升,效率降低
-
3)如果99%的对象都是存活的(老年代),那么老年代是无法使用这种算法的。
标记-清除算法
标记-清除算法是最基础的垃圾回收算法;
算法的分为两个阶段:
1:标记阶段
2:清除阶段
首先标记所有需要回收的对象,在标记完成之后统一回收所有被标记的对象;
1540372926729.png
标记-清除算法有两个不足之处:
1:一个是效率问题,标记和清除两个过程的效率都不高
2:空间问题:标记清除后会产生大量不连续的内存碎片(空间碎片太多可能会导致后续的程序运行过程中需要分配较大的对象时,无法找到足够的连续内存,这样就导致不得不提前出发垃圾收集动作);
标记-整理算法
标记-整理算法和标记-清除算法很相似,但是标记整理算法并不是直接对可回收对象进行清理,而是让所有存活的对象都像一端移动,然后直接清理掉端边界以外的内存;
1540372983639.pngJDK1.7 - 垃圾回收
1540373494951.png内存阶段划分
年轻代
年轻代是由Eden space和两个suvisor组成的,在初始阶段,新创建的对象会分配给Eden区(如果创建的对象非常大,那么对象会直接进去老年代),两个Suvisor区是空的。
- Eden:存放新生成的对象
- Suvisor:“两个幸存区。用来存放每次垃圾回收后存活的对象”
随着对象往Eden区进行填充,Eden区满了的时候,就会触young GC------ Minor GC
在这阶段会使用垃圾回收的算法---复制算法(复制算法会将存活的对象复制到from suvisor区域,然后已经无用的对象被回收)
1540373632804.png随着minorGC的不断进行,会反复重复上面的过程,只要经过复制过程,年龄就会加1;
当对象的年龄不断的增长,达到一个默认值“15”( -XX:MaxTenuringThreshold)的时候,对象就会进入老年代了。(在发生minor GC的时候。JVM都会去检查每次晋升到老年代的对象大小是否已经大于老年代剩余的空间,如果大于那么就会出现FULL GC)。
年老代
随着年轻代的GC重复操作,只要年龄达到了哪个触发点,就会把年轻代的对象复制到老年代里面;那么随着时间的推移,老年代的对象会越来越多,最终老年代的空间区域也会不够,就会出现老年的GC-------Major GC(标记清除或者标记-整理)
(年老带对象存满时,会触发一次FullGC,对堆内存空间整体清除)
持久代(方法区)
持久代=方法区
主要存放Class和Meta的信息,Class在被加载的时候被放入永久代。 它和存放对象的堆区域不同,GC(Garbage Collection)不会在主程序运行期对永久代进行清理,所以如果你的应用程序会加载很多Class的话,就很可能出现PermGen space错误。
方法区物理上存在于堆里,而且是在堆的持久代里面;但在逻辑上,方法区和堆是独立的。
一般说堆的持久代就是说方法区,因为一旦JVM把方法区(类信息,常量池,静态字段,方法)加载进内存以后,这些内存一般是不会被回收的了。
JDK1.8 +垃圾回收
JDK8 自带先进的使用G1回收器。
Oracle官方已经在JDK9中将G1变成默认的垃圾收集器
G1是将整个堆空间划分成大小想等的小块(每一块成为region),每一块的内存是连续的。和分代收集算法一样,G1中每个快也会充当Eden、Suvisor、Old三种角色,但是他们不是固定的,这样使得内存的使用更加灵活;
1540374259289.pngJVM垃圾回收器
Serial收集器
单线程收集器,“单线程”的意义不仅仅说明它只会使用一个CPU或一个收集线程去完成垃圾收集工作;
更重要的是它在垃圾收集的时候,必须暂停其他工作线程,直到垃圾收集完毕;-----Stop-the-world
1540374522368.pngSerial收集器也并不是只有缺点;Serial收集器由于简单并且高效;
对于单CPU环境来说,由于Serial收集器没有线程间的交互,专心做垃圾收集自然可以做获得最高的垃圾收集效率
使用方式:-XX:+UseSerialGC
ParNew 收集器
ParNew收集器是Serial收集器的多线程版本;
除了使用多线程收集以外,其余行为和Serial收集器是一样的(收集算法、stopTheWorld)
1540374624282.png
ParNew收集器在单CPU服务器上的垃圾收集效率绝对不会比Serial收集器高;
但是在多CPU服务器上,效果会明显比Serial好
使用方式:-XX:+UseParNewGC
Parallel Scavenge收集器
Parallel Scavenge收集器是一个新生代的收集器,并且使用复制算法,而且是一个并行的多线程收集器
Parallel Scavenge收集器的关注的点和其他收集器是不一样的;
-
其他收集器是尽量缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标是达到一个可控制的吞吐量(Throughput);
-
吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
(虚拟机总共运行100分钟,垃圾收集时间为1分钟,那么吞吐量就是99%)
Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,
- 分别是控制:最大垃圾收集停顿时间-XX:MaxGCPauseMillis
- 吞吐量大小-XX:GCTimeRatio
- 与Parallel Scavenge收集器有关的还有一个参数:-XX:+UseAdaptiveSizePolicy(有了这个参数之后,就不要手工指定年轻代、Eden、Suvisor区的比例,晋升老年代的对象年龄等,因为虚拟机会根据系统运行情况进行自适应调节)
使用方式:-XX:+UseParallelGC
Serial Old收集器
Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记-整理”算法。这个收集器的主要意义也是被Client模式下的虚拟机使用。
1540375056674.png
使用方式:-XX:+UseSerialGC
Parallel Old收集器
Parallel Old是parallel Scavenge的多线程版本,使用的是标记-整理算法;
Parallel Old收集器的工作过程:
1540375205440.png
使用方式: -XX:+UseParallelOldGC
CMS 收集器
CMS(concurrent mark sweep)是以获取最短垃圾收集停顿时间为目标的收集器;
目前很大一部分的java应用几种在互联网的B/S系统服务器上,这类应用尤其注重服务器的响应速度,希望系统停顿时间最短,给用户带来良好的体验;
CMS收集器使用的算法是标记-清除算法实现的;
整个过程分4个步骤:
1、 初始标记
2、 并发标记
3、 重新标记
4、 并发清除
其中初始标记和重新标记都需要stopTheWorld
- 初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快;
- 并发标记阶段就是进行GC Roots Tracing(根搜索算法)的过程;
- 重新标记阶段是为了修改并发标记期间因用户程序继续运行而导致产生变动的那一部分对象的标记记录,这个阶段停顿的时长要稍微比初始标记停顿的时间稍微长一点,但是远比并发标记的时间小很多;
- 整个过程耗时最长的是并发标记和并发清除,但是他们都是可以与用户线程一起工作的,所以CMS收集器是停顿时间最短的垃圾收集器
CMS垃圾收集器缺点:
1:CMS收集器对CPU资源特别的敏感;CMS在并发阶段,虽然不会导致用户线程停顿,但是会因为占用一部分线程而导致应用程序变慢,总吞吐量变低;
2:使用标记-清除算法,会产生内存碎片(配合-XX:+UseCMSCompactAtFullCollection使用)
使用方式:-XX:+UseConcMarkSweepGC
6.7 G1收集器
1540375564616.pngG1收集器是最新的垃圾收集器,能效最好的收集器;
考虑到之前的垃圾收集器的优缺点,希望能够有这样一款收集器能够做到:
- 可以像CMS收集器一样,GC操作与应用的线程一起并发执行。
- 紧凑的空闲内存区间且没有很长的GC停顿时间。
- 需要可预测的GC暂停耗时。
- 不想牺牲太多吞吐量性能。
- 启动后不需要请求更大的Java堆。
内存划分
G1是将整个堆空间划分成大小想等的小块(每一块成为region),每一块的内存是连续的。和分代收集算法一样,G1中每个快也会充当Eden、Suvisor、Old三种角色,但是他们不是固定的,这样使得内存的使用更加灵活;
原来分代存储每个代的空间都是固定的比例,现在很灵活,不需要固定下。
1540375594708.png在G1中有个特殊的区域叫做:Humongous区域。如果一个对象的空间超过了分区容量的50%以上,G1就认为这是一个巨型对象;默认巨型对象是需要存储在老年代中的,但是如果这个巨型对象只是短期存在,那么会对垃圾收集器造成负面影响;为了解决这个问题,G1专门划分了一个区域(Humongous)用来存储大对象;
注意:如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。
注意:一个Region的大小可以通过参数-XX:G1HeapRegionSize设定,取值范围从1M到32M,且是2的指数。如果不设定,那么G1会根据Heap大小自动决定。
GC模式
G1提供了两种GC模式,Young GC和Mixed GC,两种都是Stop The World(STW)的
-
G1中的young GC
Young GC主要是对Eden区进行GC,它在Eden空间耗尽时会被触发。在这种情况下,Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到年老代空间。Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中。最终Eden空间的数据为空,GC停止工作,应用线程继续执行。
发生在年轻代的GC算法,一般对象(除了巨型对象)都是在eden region中分配内存,当所有eden region被耗尽无法申请内存时,就会触发一次young gc,这种触发机制和之前的young gc差不多,执行完一次young gc,活跃对象会被拷贝到survivor region或者晋升到old region中,空闲的region会被放入空闲列表中,等待下次被使用。
-
G1中的Mixed GC
当越来越多的对象晋升到老年代old region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即mixed gc,该算法并不是一个old gc,除了回收整个young region,还会回收一部分的old region,这里需要注意:是一部分老年代,而不是全部老年代。
Full GC
如果对象内存分配速度过快,mixed gc来不及回收,导致老年代被填满,就会触发一次full gc,G1的full gc算法就是单线程执行的serial old gc,会导致异常长时间的暂停时间,需要进行不断的调优,尽可能的避免full gc.
部分参数
-XX:+UseG1GC | 使用 G1 (Garbage First) 垃圾收集器 |
---|---|
XX:MaxGCPauseMillis=n | 设置最大GC停顿时间(GC pause time)指标(target). 这是一个软性指标(soft goal), JVM 会尽量去达成这个目标. |
XX:InitiatingHeapOccupancyPercent=n | 启动并发GC周期时的堆内存占用百分比. G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比. 值为 0 则表示"一直执行GC循环". 默认值为 45. |
-XX:NewRatio=n | 新生代与老生代(new/old generation)的大小比例(Ratio). 默认值为 2. |
-XX:SurvivorRatio=n | eden/survivor 空间大小的比例(Ratio). 默认值为 8 |
-XX:MaxTenuringThreshold=n | 提升年老代的最大临界值(tenuring threshold). 默认值为 15. |
-XX:ParallelGCThreads=n | 设置垃圾收集器在并行阶段使用的线程数 |
-XX:ConcGCThreads=n | 并发垃圾收集器使用的线程数量 |
-XX:G1HeapRegionSize=n | 使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指定每个heap区的大小. 默认值将根据 heap size 算出最优解. 最小值为 1Mb, 最大值为 32Mb. |
持久代不见了
随着JDK8的到来,JVM不再有PermGen。但类的元数据信息(metadata)还在,只不过不再是存储在连续的堆空间上,而是移动到叫做“Metaspace”的本地内存(Native memory)中。
类的元数据信息转移到Metaspace的原因是PermGen很难调整。PermGen中类的元数据信息在每次FullGC的时候可能会被收集,但成绩很难令人满意。而且应该为PermGen分配多大的空间很难确定,因为PermSize的大小依赖于很多因素,比如JVM加载的class的总数,常量池的大小,方法的大小等。
此外,在HotSpot中的每个垃圾收集器需要专门的代码来处理存储在PermGen中的类的元数据信息。从PermGen分离类的元数据信息到Metaspace,由于Metaspace的分配具有和Java Heap相同的地址空间,因此Metaspace和Java Heap可以无缝的管理,而且简化了FullGC的过程,以至将来可以并行的对元数据信息进行垃圾收集,而没有GC暂停。
补充一些Jvm参数
- -Xms:JVM初始最小堆内存
- -Xmx:JVM允许最大堆内存
- -XX:PermSize JVM初始非堆内存
- -XX:MaxPermSize JVM允许最大的非堆内存
- -XX:+UseConcMarkSweepGC:年老代激活CMS收集器(标记算法),可以尽量减少fullGC
- -XX:+UseParNewGC :设置年轻代为多线程并行收集
- -XX:+UseCMSCompactAtFullCollection:在FULL GC的时候,对年老代的压缩(CMS的时候,会导致内存碎片,使内存空间不连续,可能会影响性能,但是可以消除碎片)
- -XX:CMSInitiatingOccupancyFraction=85:当年老代空间被占用85%的时候触发CMS垃圾收集
网友评论