垃圾收集器与内存分配策略

2021/06/01 953点热度 0人点赞 0条评论

3.2对象已死?

3.2.1 引用计数法

在对象中加入一个引用计数器;很难解决循环引用

3.2.2 可达性分析算法

GC Roots包含以下:

  • 虚拟机栈中引用的对象

  • 方法区中类的静态属性引用的对象;

  • 方法区中常量引用的对象;

  • 本地方法栈中JNI引入的对象;

  • 虚拟机内部引用,基本数据类型对应的class对象、常驻的异常对象、系统的类加载器;

  • 同步锁持有的对象;

  • 虚拟机内部的回调、本地代码缓存;

3.2.3 再谈引用

如果reference 类型的数据中存储的数值代表的是另外一块内存的起始地址;就称改reference数据代表某块内存、某个对象的引用。

引用分为:

  • 强引用:传统引用的定义。

  • 软引用:一些还有用,但非必须的对象;(在溢出之前,会被优先列入回收范围,进行第二次回收)

  • 弱引用:和软引用类似,但是它只能活到下一次垃圾回收为止(不管内存是否够用);

  • 虚引用:最弱的引用,无法通过虚引用获取对象,存在的意义只是为了这个对象在被回收时收到一个系统通知;

3.2.4 生存还是死亡?

即使可达性分析判定为不可达的对象,至少要经历两次标记过程;

可达性分析以后无引用链,进行第一次标记,随后进行筛选筛选的条件是是否必要执行finalize()方法(没有重新finalize()或者finalize()已经被调用过会被判定为没必要执行)。

执行finalize()方法的步骤:

  1. 该对象会被放入一个F-Queue的队列之中;

  2. 由虚拟机创建、低调低优先的Finalizer线程执行他们的finalize()方法;

finalize()是对象逃脱死亡命运的最后一次机会(在finalize里重新建立起引用)

一个对象的finalize()方法只会执行一次;

3.2.5 回收方法区

方法区的垃圾只要包含两块:废弃的常量不再使用的类型;

回收废弃常量和回收堆对象非常类似;

判断一个类型不再使用比较苛刻,必须同时满足以下:

  • 该类所有的实例都已经被回收(包含派生子类);

  • 加载该类的加载器已被回收;

  • 该类对应的java.lang.Class对象没有在任何地方被引用;

可以使用-verbose:class以及-XX:+TraceClass-Loading、-XX:+TraceClassUnLoading查看类加载和卸载信息,其中-verbose:class和-XX:+TraceClassLoading可以在 Product版的虚拟机中使用,-XX:+TraceClassUnLoading参数需要FastDebug版

3.3 垃圾收集算法

收集算法可分为:引用计数垃圾收集(Refrence Counting GC)和 追踪式垃圾收集(Tracing GC),这两类也常被称为“直接垃圾收集” 和“间接垃圾收集”。

3.3.1 分代收集理论

分代是为了更好更快的收集垃圾

分代收集建立在两个假说上:

  • 弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的。

  • 强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡。

  • 跨代引用假说(Intergenerational Reference Hypothesis):跨代引用相对于同代引用来说仅占极少数

奠定了多款常用垃圾收集器的设计原则:

  • 收集器应该将java堆划分出不同的区域;

  • 将回收对象依据年龄分配到不同的区域存储;

定义:

  • 部分收集(partial GC):指目标不是完整收集整个java堆的垃圾收集

  • 新生代收集(Minor GC/Young GC):指目标只是新生代的垃圾收集

  • 老年代收集(Major GC/Old GC):指目标只是老年代的垃圾收集。(只有cms会有单独收集老年代的行为)

  • 混合收集(Mixed GC): 指目标是收集整个新生代和部分老年代,目前只有G1

  • 整堆收集(Full GC):收集整个java堆和方法区的垃圾收集

3.3.2 标记-清除算法

  1. 先标记,

  2. 统一回收(不移动对象);

缺点:执行效率不稳定(对象越多越),内存空间的碎片化问题;

3.3.3 标记-复制算法

将内存按容量分为两块,每次使用一块,满了,就将还活着的挪移到另一块

缺点:资源利用率低;

大部分虚拟机用标记-复制来回收新生代;

hotspot 中的 serial 、ParNew 采用8:1:1的布局方式(Eden:survivor:survivor)

“逃生门”安全设计:当survivor空间不足以容纳一次minorGC之后的存活对象时,就需要依赖老年代分担;

3.3.4 标记-整理算法(Parallel Scavenge)

  1. 先标记;

  2. 让所有的存活对象都向内存空间一端移动;

缺点:必须stw,

3.4 HotSpot 的算法细节实现

3.4.1 根节点枚举

固定作为GC Roots的节点主要包括:全局性的应用(常量、静态属性)和执行上下文(栈帧中的本地变量表)

枚举根节点必须暂停用户线程(枚举时必须在一个能保证一致性的快照中)

CMS、G1、ZGC枚举根节点也必须停顿,只不过停顿时间可控,或很短;

HotSpot用一组OopMap的数据结构来准确定位并快速完成枚举;

3.4.2 安全点

因为使用OopMap来快速枚举,可能导致关系变化;

在“特定位置”记录这些信息,这些位置被称为安全点(safepoint),决定了用户程序执行时只能在安全点的位置停下来进行垃圾收集(在安全点能暂停)

安全点位置的选取基本上是以“是否具有让程序长时间执行的特征”为标准进行选定的,例如:方法调用、循环跳转、异常跳转 只有这些功能才能产生安全点

抢先式中断(Preemptive Suspension):不需要线程的执行代码主动配合,在垃圾收集时先全中断,不在安全点就恢复线程,让它一会再中断;没有虚拟机采用这种方式

主动式中断(Voluntary Suspension):当垃圾收集需要中断线程时,不对线程操作,而是设置一个共享操作位,各线程执行过程中会主动轮训这个标志,一旦发现中断,在就近的安全点主动挂起。还会让创建对象等其他需要在堆上分配。

hotspot 使用内存保护陷阱的方式,把轮训操作精简到只有一条汇编指令,来提高效率;

3.4.3 安全区域

安全点保证了程序执行时,不执行的程序(sleep 或blocked)没法保证,引入安全区来解决

3.4.4 记忆集和卡表

记忆集(Remembered Set):是一种用于记录从非收集区域指向收集区域的指针合集的抽象数据结构;

新生代建立记忆集用以避免把整个老年代加进gc root的扫描范围。

卡表是记忆集的一种实现

HotSpot 使用字节数组作为卡表,大小为2的9次方  512字节

只要卡页中有一个或多个对象的字段存在跨代指针,就会加入GC Roots中一起扫描

3.4.5 写屏障

在HotSpot中是通过写屏障技术维护卡表的状态。

写屏障可以看作是虚拟机层面的Aop技术,在引用对象赋值时会产生一个环绕通知(所以会有前后)。G1使用的写屏障,G1之前都是写后屏障。

3.4.6 并发的可达性分析

可达性分析算法理论上要求全过程都基于一个能保障一致性的快照中。全过程要冻结用户线程(stw);

标记是所有追踪式垃圾收集算法的共同特性。

三色标记法:

    三色标记法作为工具来辅助推到,把遍历对象图标记为三种颜色。

  • 白色:表示对象未被访问过,即不可达,为垃圾;

  • 黑色:表示对象已经被访问过,表示可达,已扫描不会再次标记扫描;

  • 灰色:表示对象已经被访问过,但这个对象上至少存在一个引用未被扫描,会继续往下扫描;

图片

当且仅当满足以下两个条件,会产生对象消失:

  • 赋值器插入了一条或多条从黑色对象到白色对象的新引用;

  • 赋值器删除了全部从灰色对象到该白色对象的直接或间接引用;

解决对象扫描时对象消失的问题,只需破坏一个即可

增量更新(Incremental Update)

  1.   破坏第一个条件

  2.   将新插入的引用记录下,扫描结束,再以记录的黑色对象为根,重新扫描;

  3.  cms基于增量更新实现

      

原始快照(Snapshot At The Beginning, SATB)

  1. 破坏第二个条件

  2. 灰色对象要删除指向白色对象的引用关系时,就将这个引用关系记录下来;

  3. 并发结束以灰色对象为根,重新扫描一次;

  4. G1和Shenandoah用原始快照实现;


3.5经典垃圾收集器

图片

概念:

并行(Parallel):并行描述的是多条垃圾收集器线程之间的关系。

并发(Concurrent):并发描述的是垃圾收集器线程与用户线程之间的关系。

3.5.1 Serial收集器

是一个单线程工作的收集器,收集时,必须暂停其他所有工作线程。

收集范围:新生代

默认线程数:1(只有一个)

实现算法:标记-复制算法

工作过程:

图片

参数:

  • - XX:MaxTenuringThreshold =15 (默认) 晋升年龄 (有效)

  • -XX:PretenureSizeThreshold 大对象进入老年代的阈值(有效)

3.5.2 ParNew收集器

是serial收集器的多线程版本;

收集范围:新生代

默认线程数:与cpu核心数相同;

实现算法:标记-复制算法

目标:缩短收集时的停顿时间

工作过程:

图片

是cms的默认年轻代收集器(只要激活CMS收集器),也可以使用 -XX:+/-UseParNewGC 来强制激活;

JDK9开始 ParNew 合并入CMS,不再单独存在;

参数:

  • - XX:MaxTenuringThreshold =15 (默认) 晋升年龄 (有效)

  • -XX:PretenureSizeThreshold 大对象进入老年代的阈值(有效)

  • -XX:ConcGCThreads并发线程数 设置过cpu耗时较多,会影响正常线程的执行

3.5.3 Parallel Scavenge收集器

吞吐量优先收集器

收集范围:新生代

默认线程数:

实现算法:标记-复制算法

目标:可控制的吞吐量

工作过程:

图片

设置参数:

  • -XX:MaxGCPauseMillis 大于0的毫秒数,最大停顿时间,

  • -XX:GCTimeRatio   0~100的整数,吞吐量大小,垃圾收集时间占总时间的比率;

  • -XX:+UseAdaptiveSizePolicy  开关参数,开启后就不用人工指定新生代的大小;

  • - XX: MaxTenuringThreshold  晋升年龄 (无效,自动调整)

  • -XX: PretenureSizeThreshold 大对象进入老年代的阈值(无效,自动调整)

3.5.4 Serial Old 收集器

serial收集器的老年代版本

收集范围:老年代

默认线程:单线程

实现算法:标记-整理算法

目标:

工作过程:同Serial

3.5.5 Parallel Old 收集器

是Parallel Scavenge收集器的老年代版本

收集范围:老年代

默认线程:多线程

实现算法:标记-整理算法

目标:吞吐量优先

组合使用:parallel Scavenge + Parallel Old

工作过程:

图片

3.5.6 CMS收集器(Concurrent Mark Sweep)

以获取最短回收停顿时间为目标的收集器

收集范围:老年代

默认线程:(处理器核心数量+3)/4 , 小于4C都是cpu的数量,4C以上占用25%

实现算法:标记-清除算法

目标:最短回收停顿时间

配合使用:ParNew

工作过程:

图片

耗时最长的是并发标记和并发清除,由于可以和用户线程同时工作,可以忽略

缺点:

  • 对处理器资源非常敏感

  • 无法处理“浮动垃圾”,有可能出现“Concurrent Mode Failure”失败进而导致另一次完全“stw”的full Gc的产生;

  • 会产生大量的内存碎片

参数:

  • -XX:CMSInitiatingOccupancyFraction  控制cms触发的百分比,降低内存回收的频率,jdk6 默认为92%

  • -XX:+UseCMS-CompactAtFullCollection  进行fullgc时开启内存碎片的合并整理;(JDK9开始废弃)

  • -XX:+UseConcMarkSweepGC jdk9及以上需要配置该参数开启

四个步骤

  1. 初始标记(CMS initial mark)

  2. 并发标记(CMS concurrent mark)

  3. 重新标记(CMS remark)

  4. 并发清除(CMS concurrent sweep)

参数说明

-server 必须,服务模式-Xms -Xmx jvm初始和最大堆内存,一般设置为相同,防止内存动态调整带来的性能降低-Xmn  年轻代大小-Xss  栈大小-PermSize  jdk7以下,方法区大小-MetaspaceSize jdk8及以上方法区大小,不设置最大,则为本地内存-MaxPermSize  jdk7及以下,方法区最大-AggressiveOpts JVM都会使用最新加入的优化技术(如果有的话)-UseBiasedLocking  优化线程锁-DisableExplicitGC 不响应 System.gc() 代码-MaxTenuringThreshold  设置垃圾在年轻代的存活次数,默认15次,根据实际情况调整-UseConcMarkSweepGC 设置年老代为并发收集-ParallelGCThreads Parallel收集时的线程数,默认为cpu个数-ConcGCThreads 并行-UseParNewGC 设置年轻代为并发收集-CMSParallelRemarkEnabled 在使用UseParNewGC 的情况下, 尽量减少 mark 的时间-UseCMSCompactAtFullCollection  对live object 进行整理, 使 memory 碎片减少-UseFastAccessorMethods  get,set 方法转成本地代码-UseCMSInitiatingOccupancyOnly

3.5.7 Garbage First 收集器

  • 里程碑式的成果,开创了面向局部收集的设计思路和基于Region的内存布局模式,不再坚持固定大小和固定数量的分代区域划分;

  • jdk7 update 4 开始商用,jdk8 update40 提供并发的类卸载的支持

  • jdk9的默认收集器

  • Region 可以根据需要扮演新生代的Eden空间、Survivor空间或者老年代;

  • Region有一类特殊的Humongous 区域,用来存储大对象(大小超过Region容量一半的对象即为大对象),超大对象会被存放在N个连续的Humongous Region之中;

  • Region都维护有自己的记忆集,G1的记忆集本质上是一种哈希表

  • G1会去跟踪各个Region里面垃圾堆积的价值

收集范围:堆内存任何部分(被称为全功能的垃圾收集器),哪块内存存放的垃圾数量多,回收收益最大,就回收哪,这就是G1的Mixed GC 模式

默认线程:

实现算法:标记-整理

目标:在有限的时间内获取尽可能高的收集效率;

使用场景:面向服务端应用

特点:

  • 不分代,面向堆

  • Mixed Gc模式,优先回收收益大的内存空间

  • Region可以扮演不同的角色,大小可指定(1~32mb)

工作过程:

四个步骤:

  • 初始标记(Initial Marking): 仅仅标记一下GC Roots能直接关联到的对象,需要STW,耗时极短

  • 并发标记(Concurrent Marking):递归扫描整个堆里的对象图,找到要回收的对象,

  • 最终标记(Final Making):   处理并发阶段结束后仍遗留下来的最后那少量的SATB记录;

  • 筛选回收(Live Data Counting and Evacuation)需要STW

分区示意图:

图片

缺点:

  1. 内存占用相对CMS大:至少要耗费大约相当于java堆容量的10%至20%的额外内存来维持收集器工作;

  2. 额外执行负载比CMS高

  3. 小内存时CMS优于G1

参数:

-XX:G1HeapRegionSize  取值1MB~32MB,  设置Region区间的大小,应该为2的幂次方

-XX:MaxGCPauseMillis  默认200毫秒,收集停顿时间

SATB 快照

RSet 记忆集   RSet、Card 和Region的关系

图片

3.6 低延迟垃圾收集器

衡量垃圾收集器的三个指标:

  1. 内存占用(Footprint)

  2. 吞吐量(Throughput)

  3. 延迟(Latency)

一款优秀的收集器通畅最多可以同时达成其中的两项;

图片

3.6.1 Shenandoh 收集器

  • 由redhat(IBM) 研发,在OpenJDK12 中使用

  • 在在jdk15转正了,还是OpenJDK中

  • 是第一款使用读屏障的收集器

收集范围:全堆

默认线程:

实现算法:并发整理

目标:低延迟,在对吞吐量影响不大的情况下,实现在任何堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内

使用场景:低延迟场景,秒杀,风控等

特点:使用转发指针和读屏障来实现并发整理

工作过程:九个阶段

  1. 初始标记(Initial Marking):与G1一样,标记与GC Roots 直接关联的对象,需要STW;

  2. 并发标记(Concurrent Marking):遍历对象图,标记出全部可达的对象

  3. 最终标记(Final Marking):处理剩余的SATB扫描,并在这个阶段统计回收价值最高的Region,将这些Region构成一组回收集

  4. 并发清理(Concurrent Cleanup):用于清理整个区域内一个存活对象都没有找到的Region;

  5. 并发回收(Concurrent Evacuation):通过读屏障,将存活的对象复制一份到其他未被使用的Region

  6. 初始引用跟新(Initial Update Reference): 将堆中所有指向久对象的引用修正到复制后的地址;

  7. 并发引用更新(Concurrent Update Reference):真正开始进行引用更新操作,与用户线程一起

  8. 最终引用更新(Final Update Reference):解决引用更新后,还要修正存在与GC Roots的引用,最后一次STW

  9. 并发清理(Concurrent Cleanup):最后调用一次并发清理回收这些Region的可用空间

3.6.2 ZGC收集器

Z Garbage Collector

  • 在jdk15转正了

  • 是一款基于Region内存布局,不设分代,

  • 使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算法的

  • 以低延迟为首要目标的垃圾收集器;

收集范围:全堆

默认线程:

实现算法:并发整理

目标:低延迟,在对吞吐量影响不大的情况下,实现在任何堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内

使用场景:低延迟场景,秒杀,风控等

特点

  • ZGC的Region具有动态性,动态创建和小会,以及动态的区域容量大小;

Region的分类:

     小型Region(Small Region):容量固定2MB,用于存放<256kb的小对象

    中型Region(Medium Region):容量固定为32MB,用于存放>=256KB,但小于4MB的对象;

     大型Region(Large Region):容量不固定,可以动态变化,但必须是2MB的整数倍,用于放置>=4mb的大对象,每个大型Region只会存放一个大对象,可能低于4mb;

  • 采用染色指针技术实现并发整理算法:将标记信息记在引用对象的指针上;

可以说zgc的可达性分析是遍历对象的“引用图”

在linux下64位指针的高18位不能用来寻址,zgc的染色体指针技术盯上了剩余46位的指针宽度,将其高4位提取出来存储四个标志信息(同时也压缩了zgc能够管理内存不能超过4TB~2的42次方)

用来标志:对象的三色标记状态、是否进入了重分配集、是否执行过finalize()方法。

图片

染色指针的优势:

  • Region的存活对象被移走,立即能够被释放和重用掉;

  • 减少在垃圾收集过程中的内存屏障的使用次数;zgc未使用任何的写屏障,只用了读屏障

  • 可扩展的存储结构,方便日后进一步提升性能;

工作过程:

图片

图片

  1. 并发阶段(Concurrent Mark):并发标记对象图“引用图”做可达性分析,需要STW

  2. 并发预备重分配(Concurrent Prepare for Relocate): 计算出本次收集过程需要清理哪些Region,将这些Region组成重分配集(Relocation Set)

  3. 并发重分配(Concurrent Relocate):是ZGC执行过程中的核心阶段,并为重分配集中的每个Region维护一个转发表(Forward Table),如果用户线程此时并发访问了重分配集中的对象,这次的访问会被预置的内存屏障所截获,然后根据Region上的转发记录转发到新复制的对象上,并同时更新该引用值,这种行为称为指针的“自愈”

  4. 并发重映射(Concurrent Remap):修正整个堆中指向重分配集中旧对象的引用,zgc把这一阶段的工作合并到了下一次并发标记阶段,节省了一次对象图的遍历

区别:

G1通过写屏障来维护记忆集,才能处理跨代指针,实现Region的增量回收

zgc没有记忆集,连分代都没有,这也限制了zgc能承受的对象分配速率不会太高;也许后续zgc实现了分代收集后会有所改善;

ZGC的GC日志:

zgc的多种GC触发机制

  • 阻塞内存分配请求触发:当垃圾来不及回收,垃圾将堆占满时,日志关键字“Allocation Stall

  • 基于分配速率的自适应算法:主要的gc触发方式,zgc根据对象的分配速率和gc时间,计算出什么时候触发,流量平稳时自适应算法可能在堆使用率达到95%才触发GC,通过ZAllocationSpikeTolerance参数设置,默认为2,数值越大,越早触发GC,日志关键字“Allocation Rate

  • 基于固定时间间隔:通过ZCollectionInterval控制,适合应对突增流量场景。比如定时活动、秒杀,日志关键字“Timer

  • 主动触发规则:类似于固定间隔规则,但时间间隔不固定,是zgc自行算出来的时机,通过ZProactive参数关闭,之日关键字“Proactive

  • 预热规则:服务刚启动时出现,一般不需要关注,日志关键字“Warmup

  • 外部触发:代码中显式调用System.gc()触发,日志关键字“System.gc()

  • 元数据分配触发:元数据区不足时导致,一般不需要关注,日志关键字“Metadata GC Threshold

常用配置:

  • -Xms -Xmx: 堆的最大内存和最小内存

  • -XX:ReservedCodeCacheSize -XX:InitialCodeCacheSize: 设置CodeCache的大小, JIT编译的代码都放在CodeCache中,一般服务64m或128m就已经足够

  • -XX:+UnlockExperimentalVMOptions -XX:+UseZGC:启用ZGC的配置  由于在实验中,需要用UnlockExperimentalVMOptions

  • -XX:ConcGCThreads:并发回收垃圾的线程。默认是总核数的12.5%,8核CPU默认是1。调大后GC变快,但会占用程序运行时的CPU资源,吞吐会受到影响

  • -XX:ParallelGCThreads:STW阶段使用线程数,默认是总核数的60%。

  • -XX:ZCollectionInterval:ZGC发生的最小时间间隔,单位秒

  • -XX:ZAllocationSpikeTolerance:ZGC触发自适应算法的修正系数,默认2,数值越大,越早的触发ZGC

  • -XX:+UnlockDiagnosticVMOptions -XX:-ZProactive:是否启用主动回收,默认开启,这里的配置表示关闭

  • -Xlog:设置GC日志中的内容、格式、位置以及每个日志的大小

示例:

-Xms10G -Xmx10G -XX:ReservedCodeCacheSize=256m -XX:InitialCodeCacheSize=256m -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -XX:ConcGCThreads=2 -XX:ParallelGCThreads=6 -XX:ZCollectionInterval=120 -XX:ZAllocationSpikeTolerance=5 -XX:+UnlockDiagnosticVMOptions -XX:-ZProactive -Xlog:safepoint,classhisto*=trace,age*,gc*=info:file=/opt/logs/logs/gc-%t.log:time,tid,tags:filecount=5,filesize=50m

3.7选择合适的垃圾收集器

3.7.1 Epsilon 收集器

是一款以不能够进行垃圾收集为卖点的垃圾收集器。

垃圾收集器也叫 “自动内存管理子系统”

垃圾收集器需要负责堆内存的管理与布局、对象的分配、与解释器的协作、与编译器的协作、与监控子系统协作

适用于服务网格中那些只存在一次执行过程的服务;

3.7.2 收集器的权衡

主要受以下因素影响:

  • 应用程序的关注点:有的是吞吐量、有的是低延迟,有的是内存占用;

  • 运行应用的基础设施:适用的系统架构,也许不支持

  • 适用jdk的发行厂商和版本号:不同的厂商和版本实现不同;

有充足的预算,没有太多调优经验:适用商业版的软硬件,C4

能掌控软硬件型号,适用zgc

对实验中中的zgc顾虑,可以使用Shenandoah

<6gb以下的堆内存,使用CMS

>6gb的内存,使用G1

3.7.3 虚拟机及垃圾收集器日志

jdk9为分界线

jdk9之前,各个功能模块的日志开关分布在不同的参数上

jdk9 HotSpot所有的日志都归到了-Xlog 参数上

-Xlog[:[selector][:[output][:[decorators][:output-options]]]]

命令行中最关键的参数是选择器(selector),它由标签(tag)和日志级别(level)共同组成

日志级别分为:由低到高(越高输出的日志越少)

Trace<Debug<Info<Warning<Error<Off

支持附加到日志行上的信息包含:

  • time:当前日期和时间。

  • uptime:虚拟机启动到现在经过的时间,以秒为单位。

  • timemillis:当前时间的毫秒数,相当于System.currentTimeMillis()的输出。

  • uptimemillis:虚拟机启动到现在经过的毫秒数。

  • timenanos:当前时间的纳秒数,相当于System.nanoTime()的输出。

  • uptimenanos:虚拟机启动到现在经过的纳秒数。

  • pid:进程ID。

  • tid:线程ID。

  • level:日志级别。

  • tags:日志输出的标签集。

默认值是uptime、level、tags

jdk9之前和jdk9之后展示示例

1,查看gc基本信息

    jdk9 之前使用 -XX:+PrintGC

    jdk9 之后使用-Xlog:gc

2, 查看gc详细信息

    jdk9 之前使用 -XX:+PrintGCDetails

    jdk9 之后使用-Xlog:gc*

3,查看GC前后堆、方法区可用容量变化

    jdk9 之前使用 -XX:+PrintHeapAtGC

    jdk9 之后使用-Xlog:gc+heap = debug

4,查看GC过程中用户线程并发时间以及停顿时间

    jdk9 之前使用 -XX:+PrintGCApplicationConcurrentTime 以及 -XX:+PrintGCApplicationStoppedTime

    jdk9 之后使用-Xlog:safepoint

5,查看收集器Ergonomics机制

    jdk9 之前使用 -XX:+PrintAdaptiveSizePolicy

    jdk9 之后使用-Xlog:gc+ergo*=trace

6,查看熬过收集后剩余对象的年龄分布信息

    jdk9 之前使用 -XX:+PrintTenuringDistribution

    jdk9 之后使用-Xlog:gc+age=trace

jdk9前后日志参数变化

图片

图片

3.7.4 垃圾收集器参数总结

图片

图片

3.8 实战:内存分配与回收策略

3.8.1 对象优先在Eden分配

大多数情况,对象在新生代Eden区中分配,当Eden区没有足够空间进行分配时,虚拟机将发起一次MinorGC

3.8.2大对象直接进入老年代

大对象就是指需要大量连续内存空间的java对象,最典型的大对象就是很长的字符串,或者元素数量庞大的数组;

HotSpot虚拟机提供了 -XX:PretenureSizeThreshold(只对Seral和ParNew有效)参数,指定大于该设置值的对象直接进入老年代分配;

3.8.3 长期存活的对象将进入老年代

默认对象年龄经过15次晋升后进入老年代

通过-XX:MaxTenuringThreshold设置

3.8.4 动态对象年龄判断

如果Survivor空间中相同年龄所有对象的大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代

3.8.5 空间分配担保

在发生MinorGC之前,会检查年老代剩余的空间是否大于新生代的所有对象的总空间,确保minor Gc 安全,如果设置了-XX:HandlePromotionFailure 设置不允许冒险,将进行一次Full GC。

常见的jvm调优手段:

  •  xss 尽可能的小,在满足自我需求的情况下,尽可能的小,太小当前线程容易出现Stack size too small,或者创建线程太多,导致unable to create new native thread;太大容易造成空间浪费,无法创建较多的线程;

  • xmx 和 xms 大小设置一样,防止堆空间“震荡”引发的不必要的gc,-XX:MinHeapFreeRatio 默认是40,最小空闲比例,会根据实际情况震荡;

  • 如果对象大部分都能进入老年代之前回收,就将新生代适当的调大;

  • 尽可能不让垃圾进入年轻代,可以根据实际情况调整MaxTenuringThreshold 的值,默认是15;

  • 避免大对象,如果大对象生命周期极其短,会干minor gc和fullgc;

  • -XX:+ DisableExplicitGC 禁止通过System.gc()来触发full gc,如果是单独跑批程序,为了快速释放内存,可以;

  • gc的停顿时间尽可能短;

  • 并行gc的线程数量不能太多,太多容易造成并行gc时,cpu过高;

  • -XX:UseTLAB 独立空间,可以快速的创建对象,避免申请空间时的竞争,但是会挤压Eden区的大小,默认占1%,存在空间浪费的情况;

  • 指针压缩 UseCompressedOops 可以节省部分空间,节省有限;

最有效的还是根据jmap 和jstat -gcutil 观测并调整;

yxkong

这个人很懒,什么都没留下

文章评论