jvm相关,垃圾收集算法,垃圾收集器,jvm调优--学习笔记

          对内存合理分配,优化jvm 参数,就是为了尽可能减少新生代(Minor GC),或者是整个老年代(Major GC) ,或者是整个 Java 堆 (Full GC) ,尽量减少 GC 带来的系统停顿,避免影响系统处理请求 ,其中Full GC占用性能最多,耗时最久

JVM 什么情况下会发生堆内存溢出

Java 堆中用于储存对象,只要不断地创建对象,并目保持 GC Roots 到对象之间有可达路径来避免垃圾回收机制清理这些对象,那么随着对象数量的增加,总容量达到最大堆的容量限制后就会产生内存溢出:
常用的jvm调优参数
jvm调优基本都是为了减少触发full GC带来的性能影响

-Xms3072M    //设置java堆内存大小
-Xmx3072M    //堆内存的最大大小
-Xmn1536M    //堆内存新生代的大小,扣除新生代,其余为老年代,不设置默认大概占总堆1/3
-Xss1M       //每个线程的线程栈大小,不设置默认1M
-XX:SurvivorRatio=8     //设置eden区和survivor区的大小比例,默认8:1:1
-XX:-UseCompressedClassPointers
-XX:MetaspaceSize=256M         //初始元空间大小
-XX:MaxMetaspaceSize=256M      //最大元空间大小
-XX:MaxTenuringThreshold=5     //设置分代年龄,默认15,CMS为6
-XX:TargetSurvivorRatio=xxxxM   //设置动态年龄判断触发GC时占用新生代的空间值
-XX:+UseG1GC                   //指定使用 G1 垃圾回收器,JDK9后默认使用G1收集器
-XX:PretenureSizeThreshold=1M  //设置大对象阈值,设置只对 Serial 和 ParNew 两款新生代收集器有效,其他新生代垃圾收集器不支持该参数 
-XX:+UseConcMarkSweepGC               //指定老年代 CMS 垃圾收集器,新生代默认开启使用ParNew收集器
-XX:CMSInitiatingOccupancyFraction   //设置触发CMS回收时的老年代被占用率,默认获取-XX:CMSTriggerRatio的值,默认值是80%
-XX:+UseCMSCompactAtFullCollection    //由于内存碎片过多,大对象放不下触发Full GC时是否启用内存整理,默认开启
-XX:CMSFullGCsBeforeCompaction       //设置几次Full GC后触发一次内存整理,默认为0次,每次触发Full GC后都进行内存整理
-XX:+PrintGCDetails         //打印GC详情
-XX:+PrintGCDatestamps      //允许每次GC时打印时间戳,默认关闭
-Xloggc;d:/dev/gc.log       //指定GC日志文件输出路径
-XX:+HeapDumpOnOutofMemoryError           //设置发生堆内存溢出时输出镜像文件
-XX:HeapDumpPath=d:/dev theapdump.hprof   //设置堆内存溢出时镜像文件路径
java -XX:+PrintFlagsFinal -version        //打印虚拟机所有参数
java -XX:+PrintCommandLineFlags -version  //打印已设置的虚拟机参数,堆大小,使用的垃圾收集器等-version可以不加,但是会输出一堆提示信息

-XX:MaxDirectMemorySize=1M       //设置最大允许使用本地直接内存(堆外内存)的大小,以字节为单位,NIO一般会用到,比如Netty

JVM 如何判断对象可以被回收

在JVM 堆里面存放着所有的Java 对象,垃圾收集器在对堆进行回收前,首先要确定这些对象之中哪些还“存活”着,哪些已经“死去”Java 通过 可达性分析 (Reachability Analysis) 算法 来判定对象是否存活的
该算法的基本思路: 通过一系列称为“GC Roots”的根对象作为起始节点,从这些节点开始根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain) ,如果某个对象到 GC Roots 间没有任何引用链相连(也称为不可达),则证明此对象是不可能再被就可以被垃圾回收器回收。
在这里插入图片描述

哪些对象可以作为 GC Roots

1、在虚拟机栈(栈顿中的本地变量表)中引用的对象,警如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等所引用的对象;
2、方法区/元空间中的类静态属性引用的对象;
3、方法区/元空间中的常量引用的对象;
4、在本地方法栈中JNI(即通常所说的 Native 方法) 引用的对象
5、Java 虚拟机内部的引用,如基本数据类型对应的 Class 对象,一些常驻的异常对象 (比如NullPointExcepiton、OutOfMemoryError) 等,还有系统类加载器
6、所有被同步锁 (synchronized 关键字) 持有的对象;
7、反映Java 虚拟机内部情况的JMXBean、JVMTI 中注册的回调、本地代码缓存等
8、其他可能临时性加入的对象

Java 中不同的引用类型

Java 里有不同的引用类型,分别是强引用、软引用、弱引用 和 虚引用
强引用: Object object = new Object()
软引用: SoftReference 内存充足时不回收,内存不足时则回收;
弱引用: WeakReference 不管内存是否充足,只要 GC 一运行就会回收该引用对象
虚引用: PhantomReference 这个其实暂时忽略也行,因为很少用,它形同虚设,就像没有引用一样,其作用就是该引用对象被 GC 回收时候触发一个系统通知,或者触发进一步的处理

堆内存模型

JVM 堆内存的分代模型:年轻代、老年代
在这里插入图片描述

新生代的垃圾回收(Minor GC)

所有类都在eden区创建,eden区占后触发新生代垃圾收集,将有引用的对象复制放入S0区,然后清空eden区,eden区再次占满时,再触发新生代垃圾回收,将S0区和eden区有引用的对象复制放入s1区,同时清空Eden区和S0区,eden区再次占满时,将Eden区和S1区对象复制放入S0区,清空S1区和Eden区,对象每发生一次分区复制,对象分代年龄加一,周而复始,当分代年龄到达时对象则放入老年代
代码里创建出来的对象,一般就是两种
1、一种是短期存活的,分配在 Java 堆内存之后,迅速使用完就会被垃圾回收:
2、一种是长期存活的,需要一直生存在 Java 堆内存里,让程序后续不停的去使用
第一种短期存活的对象,是在Java 堆内存的新生代里分配
第二种长期存活的对象,通过在新生代 SO 区和S1 区来回被垃圾回收 15 次后,进入 Java 堆内存的老年代中,这里的 15 次,我们也称为对象的年龄,即对象的年龄为 15 岁
垃圾收集器的默认分代年龄是15,CMS垃圾收集器的分代年龄是6,可通过参数调整

JVM 对象动态年龄判断

虚拟机并不是永远地要求对象的年龄必须达到了 MaxTenuringThreshold 才能晋升老年代结论–>动态年龄判断: Survivor 区的对象年龄从小到大进行累加,当累加到X 年龄时的总和大于 50%(可以使用-XX:TargetSurvivorRatio=? 来设置保留多少空闲空间,默认值是 50)那么比 X 大的都会晋升到老年代
理解:
当经过几次垃圾收集后,从最小年龄的不能释放的对象占用内存量开始往大年龄的对象开始累加,如果累加后占用的内存已经达到新生代内存的50%或者预设值,那么不管大年龄的对象是否达到分代年龄,都一律放入老年代

老年代空间分配担保机制

在JDK6之前的时候有一个参数-XX:+HandlePromotionFailure 用于开启是否要进行空间担保,如果不开启则每次都会full GC
在每次Minor GC之前会检查老年代剩余空间是否大于新生代所有对象的总大小

在这里插入图片描述
新生代 Minor GC 后剩余存活对象太多,无法放入 Survivor 区中,此时就必须将这些存活对象直接转移到老年代去,如果此时老年代空间也不够怎么办?
1、执行任何一次 Minor GC 之前,JVM 会先检查一下老年代可用内存空间,是否大于新生代所有对象的总大小,因为在极端情况下,可能新生代 Minor GC 之后,新生代所有对象都需要存活,那就会造成新生代所有对象全部要进入老年代:
2、如果老年代的可用内存大于新生代所有对象总大小,此时就可以放心大胆的对新生代发起-次 Minor GC,因为 MinorGC 之后即使所有对象都存活,Survivor 区放不下了,也可以转移到老年代去,
3、如果执行 Minor GC 之前,检测发现老年代的可用空间已经小于新生代的全部对象总大小那么就会进行下一个判断,判断老年代的可用空间大小,是否大于之前每一次 Minor GC 后进入老年代的对象的平均大小,如果判断发现老年代的内存大小,大于之前每一次 Minor GC后进入老年代的对象的平均大小,那么就是说可以冒险尝试一下 Minor GC,但是此时真的可能有风险,那就是 Minor GC 过后,剩余的存活对象的大小,大于 Survivor 空间的大小,也大于老年代可用空间的大小,老年代都放不下这些存活对象了,此时就会触发一次“Full GC”所以老年代空间分配担保机制的目的? 也是为了避免频繁进行 Full GC
4、如果 Full GC之后,老年代还是没有足够的空间存放 Minor GC 过后的剩余存活对象,那么此时就会导致“OOM”内存溢出

什么情况下对象会进入老年代

1、躲过 15 次 GC之后进入老年代,可通过JVM 参数“-XX:MaxTenuringThreshold”来设置年龄,默认为 15岁;
2、动态对象年龄判断
3、老年代空间担保机制
4、大对象直接进入老年代:
大对象是指需要大量连续内存空间的 Java 对象,比如很长的字符串或者是很大的数组或者List 集合大对象又会引起高额的内存复制开销,为了避免新生代里出现那些大对象,然后屡次躲过 GC 而进行来回复制,此时JVM 就直接把该大
对象放入老年代,而不会经过新生代;
可以通过JVM 参数“-XX:PretenureSizeThreshold”设置多大的对象直接进入老年代,该值为字节数
-XX:PretenureSizeThreshold 参数只对 Serial 和 ParNew 两款新生代收集器有效,其他新生代垃圾收集器不支持该参数,如果必须使用此参数进行调优,可考虑 ParNew+CMS 的收集器组包

垃圾收集器的分类及特点

Minor GC/Young GC : 新生代收集
Major GC/Old GC: 老年代收集
Full GC: 整堆收集,收集整个 Java 堆和元空间/方法区的垃圾收集
Mixed GC: 混合收集,收集整个新生代以及部分老年代的垃圾收集,目前只有 G1 收集器会有这种行为;

三种垃圾回收算法

  • 标记-清除算法
    标记-清除算法是最基础的收集算法,后续的很多垃圾回收算法是基于该算法而发展出来的它分为 ,标记,和,清除,两个阶段
    基于可达性分析算法进行标记需要清理,或者标记不需要清理的对象,然后清理
    缺点:1.执行效率低,2会产生内存碎片,内存不连续,有大对象时则会又触发垃圾回收
    标记清除算法中有一个三色标记清除法 被CMS和G1收集器在并发标记阶段使用
  • 标记-复制算法
    将内存分为两块,标记存活对象复制到另一个区域,并清空原先的区域
    优点:效率高,解决了清除算法的内存碎片问题,
    缺点:1.内存缩小一半,空间浪费多2。对象存活率高时复制对象过多效率变低,所以多用作新生代
  • 标记-整理算法
    根据老年代特点产生
    将需要存活的对象标记,并向内存一端移动,然后清除其他区域
    但是老年代的存在的都是大量活动对象,移动对象比较耗时,而且移动对象需要更新所有对象的引用,而且这种对象移动操作必须全程暂停用户应用程序才能进行,像这样的停顿我们也称为“Stop The World”即 STW:
  • 分代收集算法
    分代收集算法就是新生代和来年代分开收集思想

常用垃圾收集器的特点及收集过程

大体分为:
新生代收集器: Serial、ParNew、 Parallel Scavenge
老年代收集器: CMS、Serial Old、Parallel Old
整堆收集器:G1、ZGC 和 Shenandoah

Serial收集器
  • 特点:新生代收集器,最早的收集器,单线程(串行)的收集时需暂停用户线程的工作(STW),所以有卡顿现象但 Serial 收集器简单,不会有线程切换的开销,是 Client 模式下默认的垃圾收集器,-client,-server切换JDK模式,现在的JDK默认都是server模式,-XX:+UseSerialGC
  • 使用标记-复制算法
ParNew收集器
  • 特点:新生代收集器, Serial 收集器的多线程版本,大部分基本一样,单 CPU 下,ParNew
    还需要切换线程,可能还不如 Serial;Serial 和 ParNew 收集器可以配合 CMS 收集器,前者收集新生代,后者 CMS 收集老年代"-XX:+UseConcMarkSweepGC": 指定使用 CMS 后,会默认使用 ParNew 作为新生代垃圾收集器;
    -XX:+UseParNewGC": 强制指定使用 ParNew;-XX:ParallelGCThreads=2”: 指定垃圾收集的线程数量,ParNew 默认开启的收集线程与CPU 的数量相同;
  • 使用标记-复制算法
parallel 收集器(parallel Scavenge)JDK1.8的默认垃圾收集器
  • 特点:新生代收集器,并行的多线程收集器 (与 ParNew 收集器类似),侧重于达到一个可控的吞吐量,虚拟机运行 100 分钟,垃圾收集花 1 分钟,则吞吐量为 99%,有时候我们也把该垃圾收集器叫吞吐量垃圾收集器或者是吞吐量优先的垃圾收集器
    -XX: MaxGCPauseMillis 该参数设置大于0 的毫秒数,每次 GC 的时间将尽量保持不超过设置的值,但是这个值也不是设置得越小就越好,GC 暂停时间越短,那么 GC 的次数会变得更频繁;
    -XX:+UseAdaptiveSizePolicy 自适应新生代大小策略,默认这个参数是开启的,当这个参数被开启之后,就不需要人工指定新生代的大小 (-Xmn)、Eden 与 Survivor 区的比例 (-XXSurvivorRatio) 、晋升老年代对象大小 (-XX: PretenureSizeThreshold) 等细节参数了虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间获得最大的吞吐量,这种调节方式称为垃圾收集的自适应的调节策略(GC Ergonomics)如果我们不知道怎么对jvm 调优,我们可以使用 Parallel Scavenge 收集器配合自适应调节策略,把内存管理的调优任务交给虚拟机去完成也许是一个很不错的选择,只需要把基本的内存数据设置好(如-Xmx 设置最大堆),然后使用-XX:MaxGCPauseMillis 参数(最大停顿时间)给虚拟机设立一个优化目标,那具体细节参数的调节工作就由虚拟机完成,自适应调节策略也是 Parallel Scavenge 收集器区别于 ParNew 收集器的一个重要特性;
    参数: -XX:+UseParallelGC 指定使用 Parallel Scavenge 垃圾收集器
  • 使用标记-复制算法
Serial OldS收集器

特点:Serial收集器的老年代版本,串行单线程,可在 Client 模式下使用,也可在Server 模式下使用,采用标记-整理算法,Serial Old 收集器也可以作为 CMS 收集器发生失败时的后备预案,在并发收集发生 Concurrent Mode Failure (并发失败时)时使用;

  • 使用标记-整理算法
Parallel Old收集器

特点:Parallel Old 是 Parallel Scavenge 的老年代版本,多线程,标记整理算法,它是在 jdk1.6 开
始才提供;在注重吞吐量和 CPU 资源的情况下,Parallel Scavenge 新生代+ Parallel Old
参数: -XX:+UseParallelOldGC 指定使用 Parallel Old 收集器
新生代使用Parallel时,老年代默认使用Parallel Old

  • 使用-标记整理算法
CMS收集器(Concurrent Mark Sweep)

特点:CMS 是一款老年代的垃圾收集器,它是追求最短回收停顿时间为目标的收集器,互联网 B/S 结构的服务器端特别适合此收集器
我们知道垃圾回收会带来 Stop the World (stw) 的问题,会导致系统卡死时间过长,很多响应无法处理,所以CMS 垃圾回收器采取的是垃圾回收线程和系统工作线程尽量同时执行的模式来处理的,基于标记-清除算法
-XX:+UseConcMarkSweepGC 指定使用 CMS 垃圾收集器
老年代使用CMS时,新生代默认自动开启使用ParNew收集器;
CMS收集器运作分为4个阶段

  1. 初始标记 (STW,标记一下 根据GC Roots 标记需要存活的对象,速度很快;这个阶段只标记出被GC roots直接引用的对象,不会再标记下一层被引用的对象) ;
  2. 并发标记 (不会 stw;跟踪 GC Roots 的整个链路,从GC Roots 的直接关联对象开始遍历整个对象引用链路,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行)
  3. 重新标记 (stw,修正并发标记期间,因用户程序继续运行而导致标记产生变化的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短,它其实就是对在第二阶段中被系统程序运行变动过的少数对象进行标记,所以运行速度很快的)
  4. 并发清除 (不会 stw,清理删除掉标记阶段判断的已经死亡的对象,这个阶段其实是很耗时的,但由于不需要移动存活对象,并且这个阶段也是与用户线程同时并发执行的)

CMS垃圾收集器的缺点:
1、并发收集会占用 CPU 资源,特别是 cpu 数量小的服务器下,会占用用户线程,导致性能下降,CMS 默认启动的回收线程数是 (处理器核心数量 + 3) /4;
2、会产生浮动垃圾,因为并发清除的时候用户线程可能还在产生垃圾,这些垃圾没有清除而且不能让老年代填满了再清除,要给用户线程留一定空间,所以idk1.5 默认是老年代68%了就触发CMS回收,idk1.6 则提升到 92%;
通过-XX:CMSInitiatingOccupancyFraction 参数设置老年代占用率触发CMS回收
默认现在是取 -XX:CMSTriggerRatio 的值,默认时80%;
-XX:CMSInitiatingOccupancyFraction设置得更高,可以降低内存回收频率,获取更好的性能但这又会更容易面临另一种风险: 要是 CMS 运行期间预留的内存无法满足程序分配新对象的需要,就会出现一次“并发失败” (Concurrent Mode Failure),如果预留老年代不够用户线程使用,则启用 Serial old 收集,这就会暂停用户线程(STW),导致性能下降
3、CMS 基于标记-清除算法,清理后会产生碎片空间,空间碎片过多时,将会导致大对象无法分配,往往会出现老年代还有很多剩余空间,但没有足够大的连续空间来分配当前对象,而不得不提前触发一次 Full GC,CMS 垃圾收集器有一个
-XX:+UseCMSCompactAtFullCollection 开关参数 (默认是开启的) ,用于在 CMS 收集器进行 Full GC 时开启内存碎片的整理过程,由于这个内存整理必须移动存活对象,整理过程是无法并发的,就导致停顿时间变长!因此虚拟机设计者们还提供了另外一个参数
-XX:CMSFullGCsBeforeCompaction,即 CMS 收集器在执行过若干次 (数量由参数值决定不整理空间的 Full GC 之后,下一次进入 Full GC 前会先进行碎片整理(默认值为 0,表示每次进入 Full GC 时都进行碎片整理)

  • 使用标记-清除算法,但也有内存整理过程
G1(Garbage First)收集器

特点:整堆垃圾收集器,JDK9默认使用G1作为垃圾收集器,JDK9开始如果指定使用CMS垃圾收集器会有(Deprecate)的过时警告;
G1 是一款可以让我们设置垃圾回收的预期停顿时间的垃圾收集器,设置参数是-XX:MaxGCPauseMillis,默认值是 200ms;G1 可以指定垃圾回收导致的系统停顿时间不能超过多久,不管垃圾的多与少,垃圾回收的时间都不要超过我们设置的值,G1 全权给你负责,保证达到这个目标,这相当于我们就可以直接控制垃圾回收对系统性能的影响了;所以G1 垃圾收集器是尽量把垃圾回收对系统造成的影响控制在你指定的时间范围内,同时在有限的时间内尽量回收尽可能多的垃圾对象,这就是 G1 垃圾收集器的核心原理;

G1 垃圾收集器如何做到可预测的停顿时间
  1. 这与 G1 垃圾收集器独特的设计有关,它最大的特点就是把 Java 整个堆内存拆分为多个大小相等的 Region(默认每个Region是1M,最大32M,最大支持2048个Region)参数-XX:G1HeapRegionSize 设定每个Region的大小值为1MB-32MB在这里插入图片描述
    也就将整个堆平均分后标记每个小区域是作为做为Eden或者S0或老年代的,还有一个特有的大对象区Humongous----------------G1 认为只要大小超过了一个 Region 容量一半的对象即可判定为大对象而对于那些超过了整个 Region 容量的超级大对象,将会被存放在 N 个连续的 Humongous Region 之中G1 的大多数行为都把 Humongous Region 作为老年代的一部分来进行看待
  2. 、G1 它会追踪每个 Region 的回收价值,即它会计算每个 Region 里的对象有多少是垃圾如果对这个 Region 进行垃圾回收,需要耗费多长时间,可以回收掉多少垃圾
  3. G1 收集器之所以能建立可预测的停顿时间模型,是因为它将 Region 作为垃圾回收的最小单元,即每次可以选择一部分 Region 进行收集,避免在整个 Java 堆中进行全区域的垃圾收集,让 G1 收集器去跟踪各个 Region 里面的垃圾的“回收价值”,然后根据用户设定的收集停顿时间 (使用参数-XX:MaxGCPauseMilis 指定,默认值是 200 毫秒),然后在后台维护个优先级列表,优先处理回收价值大的那些 Region,这也是“Garbage First”名字的由来这种使用 Region 划分堆内存空间,基于回收价值的回收方式,保证了 G1 收集器在有限的时间内尽可能收集更多的垃圾;
    比如: G1 通过追踪发现,有1个 Region 中的垃圾对象有 10MB,回收它需要耗费 500 毫秒另一个 Region 中的垃圾对象有 20MB,回收它需要耗费 100 毫秒,那么 G1 垃圾收集器基于回收价值计算会选择回收 20MB 只需 100 毫秒的 Region;
    估计每个Region中的垃圾比例,优先回收有价值的Region,这就是为什么称为Garbage First(垃圾优先)
    无需回收整个堆,而是选择一个Collection Set (CS),一个需要回收的Region的集合

G1内部有两种GC:
1、 Fully young GC(全年轻代GC)
2、 Mixed GC(混合GC,收集全年轻代和部分老年代)

G1的新生代回收过程;

G1 的新生代也有 Eden 和 Survivor,其触发垃圾回收的机制也是类似的,随着不停在新生代Eden 对应的 Region 中放对象,JVM 就会不停的给新生代加入更多的 Region,直到新生代占据堆大小的最大比例 60%此时触发新生代的 GC,G1 就会依然用复制算法来进行垃圾回收,进入一个Stop the World”状态,然后把 Eden 对应的 Region中的存活对象复制到SO对应的 Region中,接着回收掉 Eden 对应的 Region 中的垃圾对象,
但这个过程与之前是有区别的,因为 G1 是可以设定目标 GC 停顿时间的,也就是 G1 执行 GC的时候最多可以让系统停顿多长时间,可以通过“-XX:MaxGCPauseMills”参数来设定, G1 就会通过对每个 Region 追踪回收它需要多少时间,可以回收多少对象来选择回收一部分 Region,保证 GC 停顿时间控制在指定范围内,尽可能多地回收对象,所以并不是一次将新生代的所有垃圾都回收

G1的老年代回收过程;

与CMS回收过程很相近

  1. 初始标记需要 Stop the World这个阶段只标记出被GC roots直接引用的对象,不会再标记下一层被引用的对象) ,这个过程速度很快,而目是借用进行 Minor GC 的时候同步完成的,所以 G1 收集器在这个阶段实际并没有额外的停顿;(也就是在新生代垃圾回收的时候就已经标记了)
  2. 并发标记,不需要 Stop the World,这个阶段会从 GC Roots 开始踪所有的存活对象初始标记阶段仅仅只是标记 GC Roots 直接关联的对象,而在并发标记阶段,就会进行 GCRoots 追踪,从这个 GC Root 对象直接关联的对象开始往下追踪,追踪全部的存活对象,这个阶段是很耗时的,但可以和系统程序并发运行,所以对系统程序的影响不大;
  3. 重新标记 (最终标记) ,需要 Stop the World,用户程序停止运行,最终标记一下有哪些存活对象,有哪些是垃圾对象;
  4. 筛选回收,需要 Stop the World,对各个 Region 的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个 Region 构成回收集,然后把决定回收的那一部分 Region 的存活对象复制到空的 Region 中,再清理掉整个旧 Region 的全部空间,这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的;
G1垃圾收集器的内存大小设置

默认新生代对堆内存的占比是 5%,可以通过“-XX:G1NewSizePercent”来设置新生代初始占比,一般默认值即可,因为在系统运行中,JVM 会不停的给新生代增加更多的 Region但 是 最多新生代的比不 会 超 过 60%,可以通过"-XX:G1MaxNewSizePercent”设置,并且一旦 Region 进行了垃圾回收,此时新生代的Region 数量就会减少,这些都是动态的;剩下的区域就是老年代空间;所以默认的最大占比是新生代 : 老年代 = 60% : 40%
G1垃圾收集器新生代默认的Region占比沿用CMS的8:1:1的比例划分Eden区和Survivor区的数量的;只不过随着对象不停的在新生代里分配,属于新生代的 Region 会不断增加,Eden 和 Survivol对应的 Region 也会不断增加;

  • G1垃圾收集器使用标记-复制算法来完成的垃圾收集,除了并发标记外,其余阶段也是要完全暂停用户线程的, 所以它并不是屯粹地追求低延迟,而是给它设定暂停目标,使其在延迟可控的情况下获得尽可能高的吞吐量

三色标记清除法

扫描范围:

三色标记算法的运用是在CMS和G1垃圾收集器的并发标记阶段,而且扫描的对象都是来自第一步 初始标记阶段标记出的被GC Roots直接引用的对象,从这些对象再向下扫描

原理:

三色标记算法把Gc roots可达性分析遍历对象过程中遇到的对象,按照“是否访问过”这个条件标记成以下三种颜色
-1. 黑色: 表示对象已经被垃圾收集器访问过,且当前对象直接引用的其他对象也都被访问了。(当前对象及其所有直接可达对象都被访问)
-2. 灰色: 表示当前对象已经被垃圾收集器访问过,但是当前对象直接引用的对象尚未完全访问到,全部访问后变为黑色。(当前对象直接可达对象没有全部被访问)
-3. 白色: 表示对象尚未被垃圾收集器访问过。 显然在可达性分析刚刚开始的阶段,所有的对象都是白色的,若在分析结束的阶段,仍然是白色的对象,即代表不可达。
一次完整的三色标记:

  1. 初始 所有对象都是白色
  2. 将GC Roots的直接引用对象移到【灰色集合】中
  3. 从【灰色集合】中获取对象,将当前对象引用到的对象移动到【灰色集合】中,将当前对象移动到【黑色集合】中
  4. 重复步骤3,直到【灰色集合】为空
问题:

由于并发标记阶段用户线程和垃圾收集线程都是并发执行的,在垃圾收集线程在执行标记时,用户线程又会产生新的垃圾对象也会产生新的引用,所以就出现了多标或漏标问题;
多标或漏标问题的解决方案:
漏标会导致被引用的对象被当成垃圾误删除,这是严重bug,必须解决,有两种解决方案:
量更新
(Incremental Update) 和原始快照(Snapshot At The Beginning,SATB)

  1. 增量更新:就是当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次这可以简化理解为,黑色对象一旦新插入了指向白色对象的引用之后,它就变回灰色对象了
  2. 原始快照:就是当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次,这样就能扫描到白色的对象,将白色对象直接标记为黑色(目的就是让这种对象在本轮gc清理中能存活下来,待下一轮gc的时候重新扫描,这个对象也有可能是浮动垃圾)
  • CMS: 写屏障+ 增量更新
  • G1,Shenandoah:写屏障+ SATB
  • ZGC:读屏障
    写屏障:所谓写屏障,就是在对象的引用发生改变的时候,都进行引用改变的记录,类似于一个AOP(切面)
    所以,在CMS和G1垃圾收集器都会有一个重新标记阶段并且会STW,扫描原始快照或者是增量更新保存的引用数据,将最终存活的对象标记出来
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值