什么是垃圾
- 没有任何引用指向的一个或多个对象(循环引用)
如何定位垃圾
-
reference count(引用计数算法,Python的算法)
在对象上专门记录一个数字有多少引用指向了他,什么时候变成0了就是垃圾了
存在的问题:
不能解决循环引用:当几个对象循环引用时,但没有其他引用指向这里面的对象时,这时候叫做几个垃圾,这几个对象每个引用都是1,会发生内存泄漏 -
Root Searching(跟可达算法)
现在正跑着的线程栈里面的局部变量以及从class引进的静态变量、常量池和JNI指针引用的那些变量
根:线程栈变量、静态变量、常量池、JNI指针
调优
所谓调优就是追求吞吐量优先还是响应时间优先还是在满足一定的响应时间的情况下要求达到多大的吞吐量
吞吐量优先:PS+PO
响应时间优先:PN+CMS、G1
1.根据需求进行JVM规划和预调优
2.优化运行JVM运行环境(慢、卡顿)
3.解决JVM运行过程中出现的各种问题(OOM)
例如:
1.熟悉业务场景(没有最好的垃圾回收器,只有最合适的)
2.选择垃圾回收器组合
3.计算内存需求(小的话可进行多一些GC)
4.设定年代大小,升级年龄
5.选定CPU,越高越好
6.设置日志参数
7.观察日志情况
系统CPU经常100%如何调优:
1.找出那个进程CPU高(top)
2.该进程中的哪个线程CPU高(top -Hp 进程号)
3.到处该线程的堆栈(jstack -l 进程id)
4.查找哪个方法(栈桢)消耗时间最长
系统内存飙高,如何查找问题:
1.导出堆内存(jmap -histo 进程id)//配合管道+head使用显示占用内存最多的前多少个对象
2.分析
-
吞吐量
- 用户代码执行时间/(用户代码执行时间+垃圾回收时间)
-
响应时间
- STW越短,响应时间越好
常见的垃圾收集器
组合
Serial–Serial Old
ParNew–CMS–Serial Old
Parallel Scavenge–Parallel Old
Parallel Scavenge–Serial Old
-
young
-
Serial(copying)
串行回收,单线程(适合单核CPU使用)
当Y(Eden)区满了触发YGC,STW,垃圾回收线程上场,然后继续执行 -
ParNew(copying)
配合CMS使用,并行回收
-
Parallel Scavenge(copying) 1.8默认
并行回收,可设置线程数(最好和CPU数量一样)
当Y(Eden)区满了触发YGC,STW,垃圾并行回收线程上场,然后继续执行
-
-
old
-
CMS[Concurrent Mark Sweep]
并发的,垃圾回收和应用程序同时运行,降低STW时间(200ms以内)
-
四个阶段
-
1.CMS initial mark(初始标记)
先找跟对象(很少),会产生STW,但时间短
-
2.CMS concurrent mark(并发标记)
并发执行(耗时),顺着跟对象往下找,但可能出现在并发过程中原来那个是垃圾的对象有个引用不再是垃圾了,进入remark阶段
-
3.CMS remark(重新标记)
-
-
标记在上个阶段过程中中间改过的对象,也是STW,时间短
- 4.并发清理(可能会出现浮动垃圾) 会有些浮动垃圾,下次清理 - 会出现的问题 - 1.Memory Fragmentation(内存碎片化) 当年轻代中的对象扔到老年代时,因为碎片没有地方存放这些对象,会请Serial old(单线程) - 2.Floating Garbage(浮动垃圾) Concurrent Mode Failure: 解决:降低触发CMS的阈值 PromotionFailed: 解决:保持老年代有足够空间
- Serial Old(Mark-Compact)
- Parallel old(Mark-Compact) 1.8默认
-
非组合
-
G1(1.9默认)
Garbage First Garbage Collector[G1 GC]
并发收集(和CMS差不多),压缩空闲空间不会延长GC的暂停时间,更易预测的GC暂停时间分而治之,分成一个一个region,每个region在逻辑上分代物理不分(其他垃圾收集器都是逻辑物理都分)整个堆被划分成2048左右个Region。每个Region的大小在1-32MB之间,具体多大取决于堆的大小。
region空间:(不是固定的O或E,进行一次gc就可能会变)
old、survivor、eden、Humongous(超过单个region的50%,大对象)新老年代比例:5%-60%,一般不用手工指定,也不要手工指定,因为这是G1预测停顿时间的基准(如果某次YGC时400ms,大于200ms就会自动将Y区调小)
触发时机:
YGC(STW):Eden空间不足 (多线程并行执行)
FGC:old空间不足、程序调用了System.gc(); //JDK10之前是Serial序列化回收,效率非常低,尽量不要产生FGC
如果G1产生FGC应该:
1.扩内存 2.提高CPU性能 3.降低MixedGC(其实就是一个CMS,不管是哪个Region(O,E,S)满了都会回收)触发的阈值,让MaxedGC提高发生(默认45%)
MaxedGC过程:初始标记(STW)、并发标记、最终标记(重新标记,STW)、筛选回收(STW 并行)
用在多核大内存的机器上,他在大多数情况下可实现指定的STW时间,同时还能保持较高的吞吐量(据研究和PS相比,G1降低了10%-15%,停顿时间200ms,如果想让程序不管怎样200ms以内必须有相应可以用G1)-
CSet(collection set)
一组可被回收的分区的集合(记录可被回收的card)。在CSet中存活的数据会在GC过程中被移动到另一个可用分区,CSet中的分区可以来自Eden空间、Survivor空间或老年代。
CSet会占用不到整个堆空间的1%的大小 -
RSet(Remember set) G1高效回收的关键
记录了其他Region中的对象到本Region的引用,RSet的价值在于使得垃圾收集器不需要扫描整个堆找到谁引用了当前分区的对象,只需要扫描RSet(HashMap)即可
-
-
ZGC
-
Shenandoah
-
Epsilon(JDK做测试debug时用的)
内存模型
Run-time Data Areas
-
JVM Stacks
每个方法都会有一个栈桢frame,每个方法从调用直至执行结束就对应着一个栈桢从虚拟机栈中入栈到出栈的过程
-
local variables(局部变量表)
-
Operand stacks(操作数栈)
-
Dynamic Linking(动态链接)
-
Return address(方法出口)
返回调用这个栈桢的
如a() -> b(),方法a调用b,b方法的返回值放在什么地方
-
-
Heap
堆是线程共享的,因此堆上的对象对于各个线程都是共享和可见的,只要持有对象的引用,就可以访问堆中存储的对象数据。
-
Program Counter
-
Native method stacks
-
Direct Memory
-
Method Area(逻辑概念)
都是装class的
JDK1.7是永久代(Perm Space),必需指定大小限制,可能会动态的添加很多class文件、类,可能会发生永久代的内存溢出。JDK1.8元数据区(Meta Space),大小可以设置也可以不设置,不设置就无上限(受限于物理内存)
1.7字符串常量存永久代FGC不会清理
1.8在堆,会触发FGC清理
对象分配
默认都是开着的,如果关上了默认就会在Eden区正常分配,而且可能发生线程争用问题
如果开启栈上分配,JVM会先进行栈上分配,如果没有开启栈上分配或则不符合条件的则会进行TLAB分配,如果TLAB分配不成功,再尝试在eden区分配,如果对象满足了直接进入老年代的条件,那就直接分配在老年代。
一般认为new出来的对象都是被分配在堆上的,其实这个结论不完全正确,因为是大部分new出来的对象被分配在堆上,而不是全部。通过对Java对象分配的过程分析,可以知道有另外两个地方也是可以存放对象的。这两个地方分别栈 (涉及逃逸分析相关知识)和TLAB(Thread Local Allocation Buffer)
-
栈上分配
-
线程私有小对象
-
无逃逸(就在这段代码中使用)
如果确定一个对象的作用域不会逃逸出方法之外,那可以将这个对象分配在栈上,这样,对象所占用的内存空间就可以随栈帧出栈而销毁
-
支持标量替换(可以用属性代替对象,如对象中就有个int a)
允许将对象打散分配在栈上,比如若一个对象拥有两个字段,会将这两个字段视作局部变量进行分配。
-
无需调整
-
-
线程本地分配缓存区(TLAB)
Thread Local Allocation Buffer
在同一时间,可能会有多个线程在堆上申请空间。因此,每次对象分配都必须要进行同步(虚拟机采用CAS配上失败重试的方式保证更新操作的原子性),而在竞争激烈的场合分配的效率又会进一步下降。JVM使用TLAB来避免多线程冲突,在给对象分配内存时,每个线程使用自己的TLAB,这样可以避免线程同步,提高了对象分配的效率。- 占用Eden,默认1%
- 多线程时不用竞争Eden就可以申请空间,提高效率
- 小对象
- 无需调整
对象在内存中布局
当Java处在偏向锁,重量级锁时,hashcode存储在哪?
当一个对象已经计算过identityhashcode,他就无法进入偏向锁状态,Java中一个对象的hashcode实在调用object#hash或System.identityHashCode()方法时才生成的,如果无锁状态则存放在markword中,如果是重量级锁则存在对应的monitor中,而偏向锁时没有地方存放该信息的,所以必须升级锁
-
普通对象
-
1.对象头MarkWord(8字节)
-
2.classPoint指针
一个对象new出来有一个指针,指向哪个class的
java -XX:+UseCompressedOops 为4字节,不开启为8字节 -
3.实例数据
1.引用类型:java -XX:+UseCompressedOops 为4字节,不开启为8字节
Oops Ordinary Object Pointers -
4.Padding对齐
8的倍数
64位计算机读取是按块来读的,不是按字节,按8的倍数读取效率更高
-
-
数组对象
- 1.对象头MarkWord
- 2.classPoint指针
- 3.数组长度(4字节)
- 3.数组数据
- 4.Padding对齐
异常
-
Memory Leak(内存泄漏)
一个内存分配了空间再也分配不了别的对象,也不回收
-
OOM(out of memory 内存溢出)
不断产生对象,内存撑不住了
编码执行过程
xx.java -->javaC–>xx.class
xx.class、Java类库–>classLoader–>字节码解释器、JIT(即时编译器)
字节码解释器、JIT(即时编译器)–>执行引擎
I I
V
OS硬件
对象的创建过程:
1.class loading
2.class linking
3.class initializing
4.申请对象内存
5.成员变量赋默认值
6.调用构造方法 (1.成员变量顺序赋初始值2.执行构造方法语句)
类加载过程
-
1.loading
将class加载(load)到内存
load到内存后创建两快内容:
1.class扔到内存里
2.生成了class类的对象(在Method Area中),指向了class的内容 -
2.Linking
-
1.Verification
校验,看class符不符合class的标准(如前4个字节不是cafebaby),不符合就拒掉
-
2.Preparation
静态变量赋默认值
-
3.Resolution
常量池中用到的哪些符号引用转换成直接的内存地址(可访问到内容)
-
-
3.Initialzing
静态变量赋值为初始值,会执行静态代码块
-
4.GC
类加载器
JVM是按需加载采用双亲委派机制(为了安全,如果没有这个机制,自己写一个java.lang.String就会覆盖类库的类,打成一个类库发给客户就可能发生信息泄漏的安全隐患,客户Sting存储密码,类库可能会远程发送)
parent方向:(箭头方向是其父加载器,非继承关系)
Custom ClassLoader -> App -> Extension -> BootStrap
child方向:
BootStrap -> Extension -> App -> Custom ClassLoader
从parent方向检查某一个类是否已经加载了
从child方向进行实际查找和加载
将一个类加载到内存:
xxx.class.getClassLoader().LoadClass(“com.xxx.xxx”)返回Class对象
例如Spring管理某个class会生成一个动态代理(是一个新的class),Spring把新的class load到内存
一个class需要被load到内存执行的过程:(双亲委派机制)
加入自定义的,先去自定义的类加载器去找,他的内部维护着一个缓存(HashSet),看是否已经被加载,如果被加载过了就不会加载了,没有则去父加载其查找,知道有一个加载器加载过了返回,如果都没有加载过,则从Bootstrap往child方向去委托,每个加载器有自己对应的加载的内容,如果要加载的不是自己对应的则一次向下委托,知道加载成功,否则抛ClassNotFound Exception
-
BootStrap
加载lib/rt.jar、charset.jar等核心类,C++实现
System.out.print(String.class.getClassLoader());
输出:null
由BootStrap加载的,因为bootstrap是C++实现的所以为null -
Extension
加载扩展jar包,jre/lib/ext/*.jar,或由-DJava.ext.dirs指定
-
App
加载classpath指定内容
System.out.print(xxx.class.getClassLoader());
输出: …$AppClassLoader…
由App加载的System.out.print(xxx.class.getClassLoader().getParent());
输出:…$ExtenSion…
类加载器的parentSystem.out.print(xxx.class.getClassLoader().getClassLoader());
输出:null
获取类加载器App的类加载器,加载器的加载器不是他的parent -
Custom ClassLoader
自定义ClassLoader
JVM算法
常见的垃圾回收算法
由于做YGC时需要扫描整个old区效率非常低,所以jvm设计了CardTable(将old区的对象分组,里面有很多card)的数据结构来标记老年代的某一块内存区域中的对象是否持有新生代对象的引用,卡表的数量取决于老年代的大小和每张卡对应的内存大小,如果CardTable中有对象指向Y区,就将它设为Dirty,下次扫描时只需要扫描Dirty的Card,在结构上,CardTable用bitmap实现
-
Mark-Sweep(标记清除)
找到垃圾清除掉,并标记这块可以使用
缺点:位置不连续容易产生碎片
-
Copying(拷贝)
将内存分为两半,只用其中一半,当回收后将一半中的存活的对象复制(很快)到另一半(防止产生碎片),以后再分配对象往这一半里分配,若再次回收重复操作
缺点:浪费空间
-
Mark-Compact(标记压缩)
在做清除垃圾并标记此块为可用时时同时做一次压缩和整理,将有用的对象移到被标记的区域
缺点:效率偏低(较其他两个),如果是多线程则需要线程同步,但线程本来就低
-
三色标记算法(CMS、G1并发标记阶段)
白色:未被标记的对象(被灰色引用的)
灰色:自身被标记,成员变量(引用的)未被标记(被黑色引用的)
黑色:自身和成员变量均已被标记完成漏标问题:
1.在并发标记阶段灰色不指向白色了
2.在remark过程中,黑色指向了白色。
如果不对白色重新扫描会漏标(黑色已经标记完了不会重新标记),会把白色对象当做没有新引用指向从而回收掉打破上述两个条件之一即可
1.incremental update – 增量更新,关注引用的增加,把黑色重新标记为灰色,下次重新扫描属性 (CMS实现)
2.SATB(snapshot at the beginning)在开始时进行快照 – 关注引用的删除,当灰色删除引用的白色时,把这个引用push到GC的堆栈(GC中有个栈里面有一个个灰色指向白色的引用),保证白色还能被GC扫描到(由于RSet存在,不需要扫描整个堆) (G1实现)
SATB 扫描次数比第一种的少,不需要重新扫描灰色
分代算法(堆内存逻辑分区)
新生代和老年代比例1:2
-
new-young(新生代)
存活对象少,使用copying算法效率高
分为Eden和两个Survivor(copying算法)区,比例8:1:1
YGC(young GC)后大多数对象会被回收(如:通过for循环new出来对象在Eden区,之后再没引用就会被回收)
每YGC一次存活的对象年龄+1,到达年龄(看具体的垃圾收集器,一般是15岁)放入old区(老年代)
如果对象过大S区放不下则放入老年代 -
old(老年代)
垃圾少,一般使用mark compact算法,g1使用copy
如果对象过大老年代装不下则进行一次FGC(Full GC)
命令
JVM命令
-
top(系统cpu占用情况)
-
top -Hp PID(进程中每个线程的占用cpu情况)
-
printf “%x\n” PID(上个命令输出的PID,转换线程id,许转换为16进制)
-
jps(java的ps)
-
jstat -gc PID 每多久执行一次GC
S0C、S1C、S0U、S1U:Survivor 0/1区容量(Capacity)和使用量(Used)
EC、EU:Eden区容量和使用量
OC、OU:年老代容量和使用量
PC、PU:永久代容量和使用量
YGC、YGT:年轻代GC次数和GC耗时
FGC、FGCT:Full GC次数和Full GC耗时
GCT:GC总耗时
NGCMN:年轻代(young)中初始化(最小)的大小 (字节)
NGCMX:年轻代(young)的最大容量 (字节)
NGC:年轻代(young)中当前的容量 (字节)
OGCMN:old代中初始化(最小)的大小 (字节)
OGCMX:old代的最大容量 (字节)
OGC:old代当前新生成的容量 (字节)
PGCMN:perm代中初始化(最小)的大小 (字节)
PGCMX:perm代的最大容量 (字节)
PGC:perm代当前新生成的容量 (字节)
S0:年轻代中第一个survivor(幸存区)已使用的占当前容量百分比
S1:年轻代中第二个survivor(幸存区)已使用的占当前容量百分比
E:年轻代中Eden(伊甸园)已使用的占当前容量百分比
O:old代已使用的占当前容量百分比
P:perm代已使用的占当前容量百分比
S0CMX:年轻代中第一个survivor(幸存区)的最大容量 (字节)
S1CMX :年轻代中第二个survivor(幸存区)的最大容量 (字节)
ECMX:年轻代中Eden(伊甸园)的最大容量 (字节)
DSS:当前需要survivor(幸存区)的容量 (字节)(Eden区已满)
TT: 持有次数限制
MTT : 最大持有次数限制 -
jstack (-l) PID(可加管道和grep + 转换后的线程id 可定位当前线程情况(锁))
-
jmap -histo PID (对每个对象占用内存情况排序,配合管道和head使用)
-
jvisualvm、jconsul(java虚拟机监控程序生产一般不使用)
GC常用参数
-
通用参数
-Xmn -Xms -Xmx -Xss
年轻代 最小堆 最大堆 栈空间(最大最小堆一般设为一样)-XX:+UseTLAB
使用TLAB,默认打开-XX:+PrintTLAB
打印TLAB的使用情况-XX:TLABSize
设置TLAB大小-XX:+DisableExplictGC
System.gc()不管用 ,FGC-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintHeapAtGC
-XX:+PrintGCTimeStamps
-XX:+PrintGCApplicationConcurrentTime (低)
打印应用程序时间-XX:+PrintGCApplicationStoppedTime (低)
打印暂停时长-XX:+PrintReferenceGC (重要性低)
记录回收了多少种不同引用类型的引用-verbose:class
类加载详细过程-XX:+PrintVMOptions
-XX:+PrintFlagsFinal -XX:+PrintFlagsInitial
必须会用,配合grep-Xloggc:opt/log/gc.log
-XX:MaxTenuringThreshold
升代年龄,最大值15锁自旋次数 -XX:PreBlockSpin 热点代码检测参数-XX:CompileThreshold 逃逸分析 标量替换 …
这些不建议设置 -
Parallel常用参数
-XX:SurvivorRatio
Eden和Survivor比例,默认8:1:1
-XX:PreTenureSizeThreshold
大对象到底多大-XX:MaxTenuringThreshold
-XX:+ParallelGCThreads
并行收集器的线程数,同样适用于CMS,一般设为和CPU核数相同-XX:+UseAdaptiveSizePolicy
自动选择各区大小比例 -
CMS常用参数
-XX:+UseConcMarkSweepGC
-XX:ParallelCMSThreads
CMS线程数量,默认为CPU数量一半-XX:CMSInitiatingOccupancyFraction
使用多少比例的老年代后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小,(频繁CMS回收)-XX:+UseCMSCompactAtFullCollection
在FGC时进行压缩-XX:CMSFullGCsBeforeCompaction
因为CMS有碎片,多少次FGC之后进行压缩-XX:+CMSClassUnloadingEnabled
回收Method Area
-XX:CMSInitiatingPermOccupancyFraction
达到什么比例时进行Perm回收GCTimeRatio
设置GC时间占用程序运行时间的百分比-XX:MaxGCPauseMillis
停顿时间,是一个建议时间,GC会尝试用各种手段达到这个时间,比如减小年轻代 -
G1常用参数
-XX:+UseG1GC
-XX:MaxGCPauseMillis
建议值,G1会尝试调整Young区的块数来达到这个值-XX:GCPauseIntervalMillis
GC的间隔时间-XX:+G1HeapRegionSize
分区大小,建议逐渐增大该值,1 2 4 8 16 32。
随着size增加,垃圾的存活时间更长,GC间隔更长,但每次GC的时间也会更长
ZGC做了改进(动态区块大小)G1NewSizePercent
新生代最小比例,默认为5%G1MaxNewSizePercent
新生代最大比例,默认为60%GCTimeRatio
GC时间建议比例,G1会根据这个值调整堆空间ConcGCThreads
线程数量InitiatingHeapOccupancyPercent
启动G1的堆空间占用比例
日志参数
-Xloggc:/opt/xxx/logs/xxx-xx-gc-%t.log 日志存放路径
-XX:+UseGCLogFileRetation 循环使用
-XX:NumberOfGCLogFiles=5 日志文件5个
-XX:GCLogFileSize=20M 一个日志文件大小
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCCause
===五个日志文件循环产生,如果都满了则覆盖,整体大小就100M
GC日志详解
PS日志
G1日志
[GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0015790 secs]
//young -> 年轻代 Evacuation-> 复制存活对象
//initial-mark 混合回收的阶段,这里是YGC混合老年代回收
[Parallel Time: 1.5 ms, GC Workers: 1] //一个GC线程
[GC Worker Start (ms): 92635.7]
[Ext Root Scanning (ms): 1.1]
[Update RS (ms): 0.0]
[Processed Buffers: 1]
[Scan RS (ms): 0.0]
[Code Root Scanning (ms): 0.0]
[Object Copy (ms): 0.1]
[Termination (ms): 0.0]
[Termination Attempts: 1]
[GC Worker Other (ms): 0.0]
[GC Worker Total (ms): 1.2]
[GC Worker End (ms): 92636.9]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.0 ms]
[Other: 0.1 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 0.0 ms]
[Ref Enq: 0.0 ms]
[Redirty Cards: 0.0 ms]
[Humongous Register: 0.0 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.0 ms]
[Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)]
[Times: user=0.00 sys=0.00, real=0.00 secs]
//以下是混合回收其他阶段
[GC concurrent-root-region-scan-start]
[GC concurrent-root-region-scan-end, 0.0000078 secs]
[GC concurrent-mark-start]
//无法evacuation,进行FGC
[Full GC (Allocation Failure) 18M->18M(20M), 0.0719656 secs]
[Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)], [Metaspace: 38
76K->3876K(1056768K)] [Times: user=0.07 sys=0.00, real=0.07 secs]
CMS日志
[GC (CMS Initial Mark) [1 CMS-initial-mark: 8511K(13696K)] 9866K(19840K), 0.0040321 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
//8511 (13696) : 老年代使用(最大)
//9866 (19840) : 整个堆使用(最大)
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.018/0.018 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
//这里的时间意义不大,因为是并发执行
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
//标记Card为Dirty,也称为Card Marking
[GC (CMS Final Remark) [YG occupancy: 1597 K (6144 K)][Rescan (parallel) , 0.0008396 secs][weak refs processing, 0.0000138 secs][class unloading, 0.0005404 secs][scrub symbol table, 0.0006169 secs][scrub string table, 0.0004903 secs][1 CMS-remark: 8511K(13696K)] 10108K(19840K), 0.0039567 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
//STW阶段,YG occupancy:年轻代占用及容量
//[Rescan (parallel):STW下的存活对象标记
//weak refs processing: 弱引用处理
//class unloading: 卸载用不到的class
//scrub symbol(string) table:
//cleaning up symbol and string tables which hold class-level metadata and
//internalized string respectively
//CMS-remark: 8511K(13696K): 阶段过后的老年代占用及容量
//10108K(19840K): 阶段过后的堆占用及容量
[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.005/0.005 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
//标记已经完成,进行并发清理
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
//重置内部结构,为下次GC做准备
参考资料
https://blogs.oracle.com/jonthecollector/our-collectors
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp
JVM调优参考文档:https://docs.oracle.com/en/java/javase/13/gctuning/introduction-garbage-collection-tuning.html#GUID-8A443184-7E07-4B71-9777-4F12947C8184
https://www.cnblogs.com/nxlhero/p/11660854.html 在线排查工具
https://www.jianshu.com/p/507f7e0cc3a3 arthas常用命令
Arthas手册:
启动arthas java -jar arthas-boot.jar
绑定java进程
dashboard命令观察系统整体情况
help 查看帮助
help xx 查看具体命令帮助
jmap命令参考: https://www.jianshu.com/p/507f7e0cc3a3
jmap -heap pi
jmap -histo pid
jmap -clstats pid