JavaGuide知识点整理——JVM垃圾回收

开发者 2024-9-15 09:27:57 69 0 来自 中国
本篇文章的根本脉络


当须要排查各种内存溢出题目,当垃圾收集称为系统到达更高并发的瓶颈时,我们就须要对这些主动化技能实行须要的监控和调治。
揭开JVM内存分配与回收的秘密面纱

java的主动内存管理告急是针对对象内存的回收和对象内存的分配。同时java主动内存管理最核心的功能是堆内存中对象的分配与回收。
java堆是垃圾收集器管理的告急地区,因此也被称为GC堆。从垃圾回收的角度,由于现在收集器根本都接纳分代垃圾收集算法,以是java堆还可以细分为新生代,老年代。再过细一点有:Eden空间,From Survivor,To Survivor空间等。进一步分别的目的是更好的回收内存,大概更快地分配内存。
堆空间的根本布局如下:

2.png
上图所示的Eden区,s0区,s1区都属于新生代,Old Memory属于老年代。
大部分环境下,对象都会起首在Eden区分配,在一次新生代垃圾回收后,假如对象还存活,则会进入s0大概s1,而且对象年事加1.当它的年事增长到肯定程度(默认15)就会提升到老年代中,对象提升到老年代的年事阈值可以通过参数-XX:MaxTenuringThreshold来设置。这个值会在假造机运行过程中进行调解。但是HotSpot有个机制:遍历全部对象时,按照年事从小打大对其占用巨细累积,当累积的某个年事巨细凌驾了s区的一半时,去这个年事和设置的默认年事更小的谁人值作为新的提升年事阈值。
好比说 设置年事10,   S区内存空间共10, 1岁的2,2岁的1,3岁的4,4岁的0,5岁的2...这个时间HotSpot遍历的时间  1岁的2  + 2岁的1 +3岁的4.发现到3岁凌驾了s区的一半,那么会把3和10去对比,发现3更小,会把提升年事阈值设置为3、
动态年事盘算的代码如下:
uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) {//survivor_capacity是survivor空间的巨细size_t desired_survivor_size = (size_t)((((double)survivor_capacity)*TargetSurvivorRatio)/100);size_t total = 0;uint age = 1;while (age < table_size) {  //sizes数组是每个年事段对象巨细  total += sizes[age];  if (total > desired_survivor_size) {      break;  }  age++;}uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;...}颠末GC后,Eden区和From区应该呗清空。这个时间From和To会互换以为。也就是新的To酿成了前次GC前的From,新的From就是前次GC前的To。不管怎么样都会包管名为To的Survivor地区是空的、MinorGC会不停重复如许的过程。在这个过程,有大概当Minor GC后,Survivor的From地区空间不敷,有一些还不到达进入老年代条件的实例放不下,则放不下的部分会提进步入老年代。下面我们用代码测试一下:
参数设置如下
-verbose:gc-Xmx200M-Xms200M-Xmn50M-XX:+PrintGCDetails-XX:TargetSurvivorRatio=60-XX:+PrintTenuringDistribution-XX:+PrintGCDateStamps-XX:MaxTenuringThreshold=3-XX:+UseConcMarkSweepGC-XX:+UseParNewGC示例代码如下:
/** 本实例用于java GC以后,新生代survivor地区的变革,以及提升到老年代的时间和方式的测试代码。须要自行分步解释不须要的代码进行反复测试对比** 由于java的main函数以及其他底子服务也会占用一些eden空间,以是要提前空跑一次main函数,来看看这部分占用。** 自界说的代码中,我们使用堆内分配数组和栈内分配数组的方式来分别模仿不可被GC的和可被GC的资源。*** */public class JavaGcTest {    public static void main(String[] args) throws InterruptedException {        //空跑一次main函数来查看java服务本身占用的空间巨细,我这里是占用了3M。以是40-3=37,下面分配三个1M的数组和一个34M的垃圾数组。        // 为了到达TargetSurvivorRatio(渴望占用的Survivor地区的巨细)这个比例指定的值, 即5M*60%=3M(Desired survivor size),        // 这里用1M的数组的分配来到达Desired survivor size        //阐明: 5M为S区的From或To的巨细,60%为TargetSurvivorRatio参数指定,可以更改参数获取差别的结果。        byte[] byte1m_1 = new byte[1 * 1024 * 1024];        byte[] byte1m_2 = new byte[1 * 1024 * 1024];        byte[] byte1m_3 = new byte[1 * 1024 * 1024];        //使用函数方式来申请空间,函数运行完毕以后,就会酿成垃圾等待回收。此时应包管eden的地区占用到达100%。可以通过调解传入值来到达结果。        makeGarbage(34);        //再次申请一个数组,由于eden已经满了,以是这里会触发Minor GC        byte[] byteArr = new byte[10*1024*1024];        // 这次Minor Gc时, 三个1M的数组由于尚有引用,以是进入From地区(由于是第一次GC)age为1        // 且由于From区已经占用到达了60%(-XX:TargetSurvivorRatio=60), 以是会重新盘算对象提升的age。        // 盘算方法见上文,盘算出age:min(age, MaxTenuringThreshold) = 1,输出中会有Desired survivor size 3145728 bytes, new threshold 1 (max 3)字样        //新的数组byteArr进入eden地区。        //再次触发垃圾回收,证明三个1M的数组会由于其第二次回收后age为2,大于上一次盘算出的new threshold 1,以是进入老年代。        //而byteArr由于凌驾survivor的单个地区,直接进入了老年代。        makeGarbage(34);    }    private static void makeGarbage(int size){        byte[] byteArrTemp = new byte[size * 1024 * 1024];    }}注意如下输出结果汇总老年代的信息为concurrent mark-sweep generation、别的还列出了某次GC后是否重新天生了threshold。以及各个年事占用空间巨细。
2021-07-01T10:41:32.257+0800: [GC (Allocation Failure) 2021-07-01T10:41:32.257+0800: [ParNewDesired survivor size 3145728 bytes, new threshold 1 (max 3)- age   1:    3739264 bytes,    3739264 total: 40345K->3674K(46080K), 0.0014584 secs] 40345K->3674K(199680K), 0.0015063 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]2021-07-01T10:41:32.259+0800: [GC (Allocation Failure) 2021-07-01T10:41:32.259+0800: [ParNewDesired survivor size 3145728 bytes, new threshold 3 (max 3): 13914K->0K(46080K), 0.0046596 secs] 13914K->13895K(199680K), 0.0046873 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]Heap par new generation   total 46080K, used 35225K [0x05000000, 0x08200000, 0x08200000)  eden space 40960K,  86% used [0x05000000, 0x072667f0, 0x07800000)  from space 5120K,   0% used [0x07800000, 0x07800000, 0x07d00000)  to   space 5120K,   0% used [0x07d00000, 0x07d00000, 0x08200000) concurrent mark-sweep generation total 153600K, used 13895K [0x08200000, 0x11800000, 0x11800000) Metaspace       used 153K, capacity 2280K, committed 2368K, reserved 4480K对象优先在Eden区分配

现在主流的垃圾收集器都会接纳分代回收算法, 因此须要将堆内存分为新生代和老年代。如许我们就可以根据各个年代的特点选择符合的垃圾收集算法。
大多数环境下,对象在新生代中Eden区分配。当Eden区没有足够空间进行分配时,假造机将发起一次Minor GC,下面我们实际测试一下:
jvm打印GC日志的下令如下-XX:+PrintGCDetails
代码如下:
    public static void main(String[] args) throws Exception {        byte[] allocation1, allocation2;        allocation1 = new byte[20000*1024];        // allocation2 = new byte[20000*1024];    }实例化一个后运行结果如下:

从图中可以看出Eden区占用百分之九十多了,肯定装不下实例化后的allocation2了,现在我们打开解释的代码再次运行:

简单表明一下为什么会出现这种环境:由于给allocation2分配内存的时间eden区险些被分配完了,我们刚刚讲了当Eden区没有足够空间进行分配时,假造机将发起一次Minor GC, GC期间假造机又发现allocation1无法存入Survivor空间,以是只好通过分配包管机制把新生代的对象提前转移到老年代中去,老年代上的空间足够存放allocation1,以是不会出现full GC。实行Minor GC后,反面分配的对象假如能存进Eden区照旧会在eden区分配内存。可以实行下面的代码验证:
6.png
7.png
由此阐明新对象分配在eden区。
大对象直接进入老年代

大对象就是须要大量连续内存空间的对象(好比字符串,数组)
为什么这么做呢?
为了克制为大对象分配内存时由于分配包管机制带来的复制而低沉效率。
恒久存活的对象将进入老年代

既然假造机接纳了分代收集的头脑来管理内存,那么内存回收时就必须能辨认哪些对象应该放在新生代,哪些对象应该放在老年代。为了做到这一点,假造机给每个对象一个对象年事计数器。
假如对象在Eden出生并颠末一次Minor GC后仍旧可以大概存活,而且能被Survivor容纳的话,将被移动到Survivor空间中,并将对象年事设置为1,对象在Survivor中每熬过一次Minor GC,年事就增长一岁。当它的年事增长到肯定程度(默认15岁)就会提升到老年代中,对象提升到老年代的年事阈值,可以通过参数-XX:MaxTenuringThreshold来设置。
动态对象年事判定

大部分环境对象都会起首在Eden地区分配,在一次新生代垃圾回收之后,假如对象还存活,则会进入S0大概S1。而且对象的年事还会加1.当年事到大设定的阈值会提升到老年代。
但是HotSpot另有一个机制:HotSpot遍历全部对象时,按照年事从小到大对其巨细进行累积。当累积的某个年事巨细凌驾Survivor区的一半时,取这个年事和默认阈值较小的谁人作为提升年事的阈值。代码如下:
uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) {//survivor_capacity是survivor空间的巨细size_t desired_survivor_size = (size_t)((((double)survivor_capacity)*TargetSurvivorRatio)/100);size_t total = 0;uint age = 1;while (age < table_size) {//sizes数组是每个年事段对象巨细total += sizes[age];if (total > desired_survivor_size) {   break;}age++;}uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;...}另有一点:默认提升年事并不都是15,区分垃圾收集器的。CMS默认的就是6.
告急进行GC的地区

针对HotSpot VM的实现,它内里的GC实在准确的分类只有两种:

  • 部分收集(Partial GC):

    • 新生代收集(Minor GC/Young GC):只针对新生代进行垃圾收集。
    • 老年代收集(Major GC/Old GC):只对老年代进行垃圾收集。须要注意的是Major GC在有的语境中也用于指代 整堆收集
    • 混淆收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集。

  • 整堆收集(Full GC):收集整个java堆和方法区。
空间分配包管

空间分配包管是为了确保在Minor GC之前老年代本身另有容纳新生代全部对象的剩余空间。
JDK6.24之前,在发生Minor GC之前,假造机必须先检查老年代最大可用的连续空间是否大于新生代全部对象总空间。假如这个条件创建,那这一次Minor GC可以确保是安全的。假如不创建,则假造时机先查看-XX:HandlePromotionFailure 参数的设置值是否允许包管失败。假如允许,那么会继续检查老年代最大可用的连续空间是否大于历次提升到老年代对象的均匀巨细,假如大于,将会实行一次Minor GC。只管这次Minor GC是有风险的。假如小于大概XX:HandlePromotionFailure设置不允许冒险,那这时就要改为进行一次Full GC。
JDK6.24以后的会泽变为只要老年代的连续空间大于新生代对象总巨细大概历次提升的均匀巨细,就会进行MinorGC,否则将进行Full GC。
对象已经殒命

堆中险些放着全部的对象实例,对堆垃圾回收前的第一步就是要判定哪些对象已经殒命(即不能再被任何途径使用的对象)。
引用计数法

给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加1.当引用失效,计数器就减1.任何时间计数器为0的对象就是不大概再被使用的。
这个方法实现简单,效率高,但是现在主流的假造机中并没有选择这个算法来管理内存,其告急原因就是它很难结果循环引用的题目。所谓对象的相互引用题目,就是两个对象相互引用,除此之外再无其他引用。由于相互引用导致计数器不为0.于是引用计数法无法关照GC回收他们。代码如下:
public class ReferenceCountingGc {    Object instance = null;    public static void main(String[] args) {        ReferenceCountingGc objA = new ReferenceCountingGc();        ReferenceCountingGc objB = new ReferenceCountingGc();        objA.instance = objB;        objB.instance = objA;        objA = null;        objB = null;    }}可达性分析算法

这个算法的根本头脑就是通过一系列的称为“GC Roots”的对象作为出发点,从这些节点开始向下搜刮,节点所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连的话,证明此对象是不可用的,须要被回收。
下图中的Object6-Object10固然有引用关系,但是他们到GC Roots不可达,由于是须要被回收的对象。

8.png 哪些对象可以作为GC Roots呢?

  • 假造机栈(栈帧中的本地变量表)中引用的对象
  • 本地方法栈(Native方法)中引用的对象
  • 方法区中类静态属性引用的变量
  • 方法区中常量引用的对象
  • 全部被同步锁持有的对象
对象可以被回收,就代表肯定会被回收么?
纵然在可达性分析法中不可达的对象,也并非是"非死不可"的,这时间它们临时处于"缓刑阶段"。要真正宣告一个对象殒命。至少要履历两次标志过程:可达性分析法中不可达的对象被第一次标志而且进行一次筛选。筛选的条件是此对象是否有须要实行finalize方法,当对象没有覆盖finalize方法大概finalize方法已经被假造机调用过期,假造机将这两种环境视为没有须要实行。
被判定为须要实行的对象将会被放在一个队列中进行二次标志,除非这个对象与引用链上的任何一个对象创建关联,否则就会被真的回收。
再谈引用

无论是否通过引用计数法判定对象引用数目,照旧通过可达性分析法判定对象的引用链是否可达,判定对象的存活都与"引用"有关。
JDK1.2之前,java中引用的界说很传统:假如reference范例的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用。
JDK1.2之后,java对引用的概念进行了扩充,将引用分为强引用,软引用,弱引用,虚引用四种(引用强度渐渐削弱)
强引用
从前我们使用的大部分引用实际上都是强引用。这是使用最广泛的引用。假如一个对象具有强引用,那么类似于必不可少的生存用品。垃圾回收器绝对不会回收它。当内存空间不敷的时间,java假造机甘心抛出OOM错误使步伐非常停止,也不会靠随意回收具有强引用的对象来办理内存不敷的题目。
软引用
假如一个对象只具有软引用,那就类似于可有可无的生存用品。假如内存空间足够的话,垃圾回收器就不会回收它,假如内存空间不敷了,就会回收这些对象的内存,只要垃圾回收器没有回收它,该对象就可以被步伐使用。软引用可以用来实现内存敏感的高速缓存。
软引用可以和一个引用队列联合使用。假如软引用所引用的对象被垃圾回收,java假造机就会把这个软引用加入到与之关联的引用队列中。
弱引用
假如一个对象只具有弱引用,也类似于可有可无的生存用品。弱引用与软引用的区别在于:只有弱引用的对象拥有更短暂的生命周期。在垃圾回收器扫描他所管辖的内存地区时,一旦发现了只有弱引用的对象,不管当前内存是否足够,都会回收它,不过由于垃圾回收器是一个优先级很低的线程,因此不肯定会很快发现那些只有弱引用的对象。
虚引用
虚引用顾名思义形同虚设,并不会决定对象的声明周期,假如一个对象只有虚引用,那么就和没有引用一样。任何时间都大概被垃圾回收。
虚引用告急用来跟踪对象被垃圾回收的活动。
虚引用和软引用和弱引用的区别:虚引用必须和引用队列联合使用。当垃圾回收器预备回收一个对象的时间,假如发现它另有虚引用,就会在回收对象之前把这个虚引用加入到与之关联的引用队列中。步伐可以判定引用队列中是否已经加入了虚引用。来相识被引用的对象是否将要被垃圾回收。步伐假如发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前接纳须要的办法。
特别注意的是,在步伐计划中一样平常很少使用弱引用和虚引用,使用软引用的环境比力多,这是由于软引用可以加快JVM对垃圾内存的回收速率,可以维护系统的运行安全,防止内存溢出等题目的产生。
怎样判定一个常量是废弃常量?

运行时常量池告急回收的是废弃的常量,那么我们怎样判定一个常量是废弃常量呢?
JDK1.7之前运行时常量池逻辑包含字符串常量池存在方法区,此时HotSpot假造机对方法区的实现为永世代
JDK1.7字符串常量池被从方法区拿到了堆中,这里没有提到运行时常量池。也就是说字符串常量池被单独拿到堆中,运行时常量池剩下的东西照旧在方法区中,也就是永世代。
JDK1.8HotSpot移除了永世代,用元空间取而代之。这时间字符串常量池照旧在堆中,运行时常量池照旧在方法区。只不过是从永世代酿成了元空间。
假如在字符串常量池中存在字符串"abc",假如当前没有任何String对象引用该字符串常量的话,阐明常量"abc"是废弃常量,假如这时间发生内存回收且有须要的话,"abc"就会被系统清算出常量池了。
怎样判定一个类是无用的类

方法区告急回收的是无用的类,那么怎样判定一个类是无用的类呢?
判定一个常量是否废弃比力简单,而判定一个类是否是无用的类的条件则相对苛刻很多,类须要同时满意下面三个条件才算是无用的类:

  • 该类全部的实例都已经被回收,也就是java堆中不存在该类的任何实例。
  • 加载该类的ClassLoader已经被回收
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
假造机对可以满意上述三个条件的无用类进行回收,这里说的仅仅是可以,而不是和对象一样不使用了就肯定被回收。
垃圾收集算法

标志-清除算法

该算法分为标志和清除两个阶段:起首标志出全部不须要回收的对象,在标志完成后同一回收掉全部没有被标志的对象。它是最底子的收集算法,后续的算法都是对其不敷进行改进得到的。这种垃圾收集算法会带来两个显着的题目:

  • 效率题目
  • 空间题目(标志清除悔恨产生大量不连续的碎片)

标志-复制算法

为了办理效率题目,标志-复制算法出现了,它可以将内存分为巨细类似的两块,每次使用此中一块,当这一块内存用完了以后,就将还存活的对象复制到另一块去,然后再把使用的空间一次性清算掉。如许每次的内存回收都是对内存区间的一半进行回收。

标志-整理算法

根据老年代的特点提出的一种标志算法,标志过程和标志-清除算法一样。但是后续步调不是直接对可回收对象回收。而是让全部存活的对象向一端移动,然后直接清算掉端边界以外的内存。

分代收集算法

当前假造机的垃圾收集都接纳分代收集算法。这种算法没有什么新的头脑,只是根据对象存活周期的差别将内存分为几个块。一样平常java将堆分为新生代和老年代。如许我们就可以根据各个年代的特点选择符合的垃圾收集算法。
好比在新生代中,每次收集都会有大量对象死去,以是可以接纳标志-复制算法。只须要付出少了对象的复制资本就可以完成每次的垃圾收集。而老年代的对象存活几率是比力高的,而且没有额外的空间对他们进行分配包管,以是我们必须选择标志-清除大概标志-整理算法进行垃圾回收。
垃圾收集器

假如说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的详细实现。
固然我们对各个收集器进行比力,但是并不是要挑选出一个最好的收集器。由于直到现在为止还没有最好的垃圾收集器出现,更没有全能的垃圾收集器。我们能做到的就是根据详细的应用场景选择符合本身的垃圾收集器。试想一下:假如有一种任何场景下都实用的完善收集器存在,那么HotSpot假造机就不会实现那么多差别的垃圾收集器了。
Serial收集器

Serial串行收集器是最根本,纵然最久长的垃圾收集器了。这是一个单线程的收集器。它的“单线程”的意义不光仅意味着它只会实用一条垃圾收集线程去完成垃圾收集工作,更告急的是它在进行垃圾收集工作的时间必须停息别的全部线程(Stop The World),直到它收集竣事。
Serial收集器新生代接纳标志-复制算法,老年代接纳标志-整理算法。


假造机的计划者们固然知道STW会带来不良的用户体验。以是在后续的垃圾收集器计划中停顿时间在不断紧缩(仍旧另有停顿。现在是没有不会停顿的)。
但是Serial收集器有一个优于其他收集器的地方:它简单而高效(与其他收集器的单线程相比)。Serial收集器由于没有线程交互的开销,自然可以得到很高的单线程收集效率。Serial收集器对于运行在Client模式下的假造机来说是个不错的选择。
ParNew收集器

ParNew收集器实在就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集之外,别的举动(控制参数,收集算法,回收策略等)都是Serial收集器完全一样。


他是很多运行在Server模式下的假造机告急选择,除了Serial手机七万,只有它能和CMS收集器(真正意义上的并发收集器)共同工作。
并行和并发概念增补:

  • 并行:指多条垃圾收集线程并行工作,但是此用户仍旧处于等待状态。
  • 并发:指用户线程与垃圾收集线程同时实行(不肯定是并行,大概是瓜代实行),用户步伐在继续运行,而垃圾收集器运行在另一个CPU上。
Parallel Scavenge收集器

Parallel Scavenge收集器也是标志-复制算法的多线程收集器,看上去和ParNew一样,但是他有个特别的地方:
Parallel Scavenge收集器关注点是吞吐量(高效率的使用CPU)。CMS等垃圾收集器的关注点更多是用户线程的停顿时间(进步用户体验)。所谓吞吐量就是CPU中用于运行用户代码的时间和CPU总斲丧时间的比值。Parallel Scavenge收集器提供了很多参数供用户找到最符合的停顿时间大概最大吞吐量。假如对于收集器不相识的手动优化存在困难的时间,用Parallel Scavenge收集器共同自顺应调治策略,把内存管理优化交给假造机完成也是一个不错的选择。
Parallel Scavenge新生代接纳标志-复制算法,老年代接纳标志-整理算法
这是JDK8默认的收集器。我们可以用指令查看:
java -XX:+PrintCommandLineFlags -version
Serial Old 收集器

Serial 收集器的老年代版本,它同样是一个单线程收集器。告急有两大用途:一种用途是JDK1.5及其从前的版本中和Parllel Scavenge搭配使用。另一种是作为CMS的后备方案。
Parallel Old收集器

Parallel Scavenge收集器的老年代版本。使用多线程和标志-整理算法。在注意吞吐量和CPU资源的场所,都可以有限思量Parallel Scavenge收集器 和 Parallel Old收集器.
CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目的的收集器。它非常符合在注意用户体验的应用上使用。
CMS收集器是HotSpot假造机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程和用户线程(根本上)同时工作。
从名字上Mark Sweep这两个此可以看出CMS收集器是一种标志-清除算法实现的。它的运作过程比前几种垃圾收集器更复杂一点。整个过程分为四种:

  • 初始标志:停息全部其他线程,记载下直接与root相连的对象,速率很快。
  • 并发标志:同时开启GC和用户线程,用一个闭包布局去记载可达对象。但是在这个阶段竣事,这个闭包布局并不能包管包含当前全部的可达对象。由于用户线程大概会不断的更新引用域。以是GC线程无法包管可达性分析的实时性。以是这个算法里会跟踪记载这些发生引用更新的地方。
  • 重新标志:重新标志阶段就是为了修正并发标志期间由于用户步伐运行而导致标志产生变动的那一部分对象的标志记载。这个阶段的停顿比初始标志长,远远比并发标志短。
  • 并发清晰:开启用户线程,关照GC线程开始对未标志的地区做排除。

    CMS是告急长处:并发收集,低停顿。
    但是也有下面三个显着的缺点:
  • 对CPU资源敏感
  • 无法处置处罚浮动垃圾
  • 它使用的回收算法标志-清除会导致收集竣事时有大量的空间碎片产生
G1收集器

G1是一款面向服务器的垃圾收集器。告急针对配备多颗处置处罚器以及大容量内存的呆板。以极高概率满意GC停顿时间要求的同时,还具备高吞吐量性能特性。
被视为JDK1.7中HotSpot假造机的一个告急进化特性,它具备以下特点:

  • 并行与并发:G1能充实使用CPU,多核环境下的硬件上风,使用多个CPU(CPU大概CPU核心)来紧缩STW停顿时间。部分其他收集器本来须要停顿java线程实行GC操作,G1收集器仍旧可以通过并发的方式让java线程继续实行。
  • 分代收集:固然G1可以不须要其他收集器共同就能独立管理整个GC堆,但是照旧保存了分代的概念。
  • 空间整合:与CMS的标志-清算差别,C1从团体上看是基于标志-整理算法实现的收集器,从局部上看是基于标志-复制算法实现的。
  • 可猜测的停顿:这个G1相比于CMS的另一个大上风,低沉停顿时间是G1和CMS的共同关注点,但是G1除了寻求低停顿之外,还能创建可猜测的停顿时间模子,能让使用者明白指定在一个长度M毫秒的时间段内。
G1收集器运作大抵分为下面几个步调:

  • 初始标志
  • 并发标志
  • 终极标志
  • 筛选回收
G1收集器在后台维护了一个优先列表,每次根据允许的时间优先回收代价最大的Region,这种使用Region分别内存空间以及优先级的地区回收方式,包管了G1收集器在有限的时间内尽大概高的收集率(把内存化整为零)。
好比一个Region预计100ms回收20M垃圾,另一个Region预计10ms回收100M垃圾,那么10ms100M的这个就会优先回收。
ZGC收集器

ZGC(The Z Garbage Collector)是JDK11推出的一款实行性的低延迟垃圾回收器。计划目的如下:

  • 停顿时间不凌驾10ms
  • 停顿时间不会随着堆巨细大概活泼对象的巨细而增长
  • 支持8MB-4TB级别的堆
从计划目的上看ZGC实用于大内存低延服务的内存管理。其着实非常寻求用户体验的环境下,不管是CMS照旧G1都会有所不敷。感爱好的可以去看下美团团队分享的关于ZGC的先容,内容比力多,我就不搬运了~附上链接:新一代垃圾回收器ZGC的探索与实践
本篇条记就记到这里,假如轻微帮到你了记得点个喜好点个关注,也祝各人工作顺顺遂利,每天进步一点点~
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-19 00:23, Processed in 0.168967 second(s), 35 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表