JVM调优
2024-01-04 16:12:59 22 举报
AI智能生成
JVM调优是指对Java虚拟机进行性能优化,以提高应用程序的运行效率和响应速度。JVM调优主要包括内存管理、垃圾回收、线程管理等方面。常用的JVM调优工具有VisualVM、JConsole、JProfiler等。在进行JVM调优时,需要根据应用程序的特点和需求,选择合适的调优策略和方法。例如,可以通过调整堆内存大小、新生代与老年代比例、垃圾回收器参数等来优化内存管理;通过调整线程池大小、线程栈大小等来优化线程管理。总之,JVM调优是一项复杂而重要的工作,需要开发人员具备一定的专业知识和实践经验。
作者其他创作
大纲/内容
JVM类加载机制
类加载的过程
加载
验证
准备
解析
初始化
使用
卸载
双亲委派模型
定义
类加载器
引导类加载器:BootStrapClassLoader
扩展类加载器:ExtClassLoader
应用程序加载器:AppClassLoader
自定义类加载器:MyClassLoader
实现原理
如此设计的好处
沙箱安全机制
避免类重复加载
打破双亲委派模型
自定义类加载器重写loadClass与findClass方法
Java的SPI机制
JVM内存结构
JVM内存结构模型图
程序计数器
作用: 记住下一条JVM指令的执行地址
特点
1.线程私有
2.唯一一个无内存溢出的区
3.内存地址不断动态变化:程序每运行一步字节码执行引擎都会修改
4.多线程也记住上次中断或挂起执行的位置
2.唯一一个无内存溢出的区
3.内存地址不断动态变化:程序每运行一步字节码执行引擎都会修改
4.多线程也记住上次中断或挂起执行的位置
本地方法栈
作用
特点
一些本地方法
虚拟机栈
线程运行所需的内存空间, 先进后出的内存结构
组成
多个栈帧
栈帧
栈帧:一个方法对应一个栈帧,读取方法时,压入栈帧底部
出入栈规则
存储的内容
局部变量表
操作数栈
保存计算过程的中间结果,存储计算过程中的临时变量
动态链接
栈帧内部包含一个指向运行时常量池中该栈帧所属方法的引用(地址:直接引用 程序运行过程中,将符号引用转换为内存中的对应地址)
符号: 方法名,类名
ex:public int computer(){}在方法区中有个地址,xxx.computer() => 通过动态链接将符号转为地址
符号: 方法名,类名
ex:public int computer(){}在方法区中有个地址,xxx.computer() => 通过动态链接将符号转为地址
方法出口信息
在方法退出之前,都需要返回到方法被调用的位置
特点
为什么有多个栈帧?
几个问题
是否涉及垃圾回收?
是否存在线程安全问题
栈内存溢出原因
方法区
定义
所有JVM线程共享的区,存储类相关信息(静态变量,成员变量,类变量,类信息)
运行时常量池,方法数据,成员方法及构造器的代码部分等
运行时常量池,方法数据,成员方法及构造器的代码部分等
特点
永久代(JDK < 1.8)
永久代有一个 JVM 本身设置的固定大小上限,无法进行调整
元空间(JDK >= 1.8)
-XX:MetaspaceSize=N //设置 Metaspace 的初始(和最小大小)
-XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小
-XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小
元空间使用的是直接内存,受本机可用内存的限制
常量池
Class常量池
常量池
字面量
符号引用
运行时常量池
字符串常量池(串池)
JDK 1.6在永久代 JDK 1.8在元空间
串池机制,可避免重复创建对象
字符串拼接原理
三种字符串操作
调优
类似HashTable,是hash表,性能与buckets桶个数密切相关,桶越多元素越分散,链表越短,哈希碰撞越少,查找速度越快
Java运行时类名,方法名,常量等也是以字符串形式存储在串池中
调优
调整HashTable桶个数
如果程序中含有大量字符串,使用intern()进行字符串入池 ,减少字符串个数,节约堆内存的使用
扩展延伸
八种包装类型的包装类和对象池
堆
new 关键创建的对象
垃圾回收
线程共享,考虑安全问题
对象的创建与内存分配
对象创建的流程
1.类加载检查
2.内存分配
分类
指针碰撞
空闲列表
并发问题
CAS
TLAB
3.初始化
4.设置对象头
对象在存储中的布局
对象头
什么是java对象的指针压缩?
为什么要进行指针压缩?
对齐填充
实例数据
5.执行init方法
对象内存分配
对象在栈上的分配
标量与聚合量
标量替换
对象在新生代的分配
大对象的分配
长期存活的对象将进入老年代
对象动态年龄判断
老年代空间分配担保机制
垃圾回收机制
回收机制
1.引用计数法
一个对象如果没有任何与之关联的引用,即他们的引用计数都为 0
可能产生循环引用问题,A->B->C->A 计数都为1,不能被回收
2.可达性分析
“GC roots”根对象作为起点搜索
通过根到该对象找不到可达路径则为垃圾对象
四种引用
强引用
软引用
弱引用
虚引用
补充:终结器引用
垃圾回收算法
标记清除法(Mark-Sweep)
回收方式
特点
适合老年代
复制算法(Copying)
回收方式
特点
适合回收新生代: 新生代存活率低
标记整理算法(Mark-Compact)
回收方式
根据老年代的特点特出的一种标记算法,结合了复制算法与标记清除法。
标记阶段和标记清除算法相同,标记后不是清理对象,而是将存活对象移向内存的一端。
然后清除端边界外的对象。
标记阶段和标记清除算法相同,标记后不是清理对象,而是将存活对象移向内存的一端。
然后清除端边界外的对象。
特点
适合老年代
JVM内存分代模型(用于分代垃圾回收算法)
核心思想
收集步骤
算法选择
新生代
伊甸园Eden
幸存区Survivor
S1(From)
S2(To)
老年代
相关概念
GC分类
Minor GC
当Eden区满时,触发Minor GC
Full GC
并行与并发
并行: 指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态
并发:并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个 CPU 上。
调优策略选择
吞吐量
吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
PS + PO
响应时间
STW越短,响应越快
垃圾回收器
Serial / Serial Old收集器 (串行)
基本参数
特点
单线程
必须暂停其他所有的工作线程STW( “Stop The World” ),直到收集结束
简单而高效(没有多线程交互的开销)
内存较小,适合个人电脑
应用场景
回收过程
Parallel Scavenge PS(吞吐量控制) / Pareallel Old
基本参数
特点
应用场景
回收过程
ParNew收集器(吞吐量优先)
基本参数
特点
回收过程
应用场景
CMS收集器 Concurrent Mark Sweep(响应时间优先)
基本参数
-XX: +UseConcMarkSweepGC
-XX: G1HeapRegionSize: 指定分区大小
-XX: ParallelCMSThreads: CMS线程数量
-XX: MaxGCPauseMillis: 期望停顿时间
-XX: CMSInitiatingOccupancyFraction: 老年代使用比例启动CMS
-XX:+UseCMSCompactAtFullCollection: 在FGC时进行压缩
-XX: CMSFullGCsBeforeCompaction=5: 5次CMS进行压缩
特点
回收过程
1.初始标记:仅标记GC Roots的直接关联对象,并且暂停其他所有线程(STW)
2.并发标记 Concurrent Mark:并发标记阶段就是从GC Roots 的直接关联对象开始遍历进行 GC RootsTracing, 可达性分析
3. 重新标记:修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录 (STW)
4.并发清理 Concurrent Sweep: 开启用户线程,同时GC线程开始对未标记的区域做清扫。这个阶段如果有新增对象会被标记为黑色不做任何处理。
5.并发重置:重置本次GC过程中的标记数据。
问题
对CPU资源敏感(会和服务抢资源)
使用“标记-清除”算法会导致大量空间碎片产生(配置压缩整理参数会进行内存整理)
-XX:+UseCMSCompactAtFullCollection
XX:CMSFullGCsBeforeCompaction=0
产生浮动垃圾,老年区太多内存碎片
-XX: CMSInitiatingOccupancyFraction: 默认92% -> 下调触发老年代Full GC的阈值
三色标记
垃圾回收器中的应用
黑色
灰色
白色
多标-浮动垃圾
漏标-读写屏障
G1 收集器(同时注重响应时间与吞吐量)
基本参数
-XX:+UseG1GC
-XX: MaxGCPauseMillis
-XX: +G1HeapRegionSize: 分区块大小: 1,2,4,8,16: SIZE越大,GC间隔长,每次GC时间更长
G1NewSizePercent/G1MaxNewSizePercent:
并行与并发: G1 能充分利用多 CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短 Stop-The-World 停顿的时间
将整个 Java 堆划分为多个大小相等的独立区域(Region)
空间整合: G1 从整体来看是基于“标记—整理”, 从局部(两个 Region 之间)上来看是基于“复制”
可预测的停顿
回收过程
应用场景
垃圾收集分类
YoungGC
MixedGC
Full GC
ZGC收集器收集器
主要目标
布局
Region设计
记忆集与卡表
如何选择垃圾收集器
垃圾回收器组合参数
-XX: +UseSerialGC = Serial New + Serial Old
-XX: UseConcMarkSweepGC = ParNew + CMS + Serial Old
-XX: UseParallelGC = Parallel Scavemage + Parallel Old(1.8默认)
-XX: UseG1GC
安全点与安全区域
安全点
安全区域
JVM调优工具及调优方法
常用命令
java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGCDetails TestPool
调优工具
jps
jmap
查看内存信息,实例个数以及占用内存大小
查看堆内存占用情况
堆dump
jstack
查找死锁
找出占用cpu高的线程堆栈信息
jinfo
查看正在运行的Java应用程序的扩展参数
查看系统属性
jstat
查看堆内存各部分的使用量,以及加载类的数量
垃圾回收整体统计
堆内存统计
新生代垃圾回收统计
新生代内存统计
老年代垃圾回收统计
老年代内存统计
元空间统计
垃圾回收信息统计(比例)
JVM 最近编译情况
应用场景:JVM运行情况分析
年轻代对象增长的速率
Young GC 的触发频率和每次耗时
每次 Young GC 后有多少对象存活和进入老年代
Full GC的触发频率和每次耗时
优化思路
调优思路分析
思路
尽量将用过即死的对象留在年轻代
调整比例,在年轻代被回收,避免放入老年代
每次YGC后存货对象小于Survior的50%
避免FullGC, FullGC会触发STW机制
调整参数
确认活跃数据大小
应用程序稳定运行时长期存活对象在堆中占用的空间大小,也就是Full GC后堆中老年代占用空间的大小
总堆:1200MB = 300MB × 4* 新生代:450MB = 300MB × 1.5* 老年代: 750MB = 1200MB - 450MB*
堆内存分配
-Xmn512M:新生代大小
-Xms: 堆初始阈值
-Xmx: 最大堆大小
-XX:MaxMetaspaceSize=256m:元数据区最大值
-XX:NewRatio=2: 老年代和新生代的比值。新生代占1/3,老年代2/3
-Xss256K虚拟机栈大小(默认1M,一般不需要)
-XX:SurvivorRatio=6: 新生代中eden区和survivor区的比值,每个survivor区占新生代的八分之一
-XX:PretenureSizeThreshold=1000000: 大于这个值的对象直接在老年代分配,避免在Eden区和Survivor区发生大量的内存复制
,该参数只对Serial和ParNew收集器有效
-Xms: 堆初始阈值
-Xmx: 最大堆大小
-XX:MaxMetaspaceSize=256m:元数据区最大值
-XX:NewRatio=2: 老年代和新生代的比值。新生代占1/3,老年代2/3
-Xss256K虚拟机栈大小(默认1M,一般不需要)
-XX:SurvivorRatio=6: 新生代中eden区和survivor区的比值,每个survivor区占新生代的八分之一
-XX:PretenureSizeThreshold=1000000: 大于这个值的对象直接在老年代分配,避免在Eden区和Survivor区发生大量的内存复制
,该参数只对Serial和ParNew收集器有效
Parallel Scavenge
-XX:MaxGCPauseMills:期望收集时间上限,用来控制收集对应用程序停顿的影响
-XX:GCTimeRatio:期望的 GC 时间占总时间的比例,用来控制吞吐量
-XX:UseAdaptiveSizePolicy:自动分代大小调节策略
-XX:GCTimeRatio:期望的 GC 时间占总时间的比例,用来控制吞吐量
-XX:UseAdaptiveSizePolicy:自动分代大小调节策略
CMS参数
-XX:CMSInitiatingOccupancyFraction=75 触发执行CMS回收的当前年代区内存占用的百分比
需打开-XX:+UseCMSInitiatingOccupancyOnly
-XX:+UseCMSCompactAtFullCollection:使用CMS执行Full GC时对内存进行压缩
-XX:CMSFullGCsBeforeCompaction=1: 多少次FGC后进行内存压缩
-XX:MaxGCPauseMillis 建议停顿时间
需打开-XX:+UseCMSInitiatingOccupancyOnly
-XX:+UseCMSCompactAtFullCollection:使用CMS执行Full GC时对内存进行压缩
-XX:CMSFullGCsBeforeCompaction=1: 多少次FGC后进行内存压缩
-XX:MaxGCPauseMillis 建议停顿时间
常见问题
Full GC 和 Young GC频繁
Full GC 频繁,甚至多于 Young GC
请求高峰期发生Full GC,单次暂停时间特别长
老年代充裕的情况下,发生Full GC(CMS JDK1.7)
案例
增大内存后反而卡顿严重
内存大->FGC时间越长
解决:PS -> PN + CMS / G1
解决:PS -> PN + CMS / G1
系统CPU经常100%
CPU 100% -> 有线程占用系统资源
- 找到进程中哪个线程cpu高(top)
- 导出该线程堆栈(jstack)
- 定位方法(栈帧)消耗时间(js)
内存标高
导出堆内存(jmap)
分析(jhat jvisualvm mat...)
分析(jhat jvisualvm mat...)
Major GC和Minor GC频繁
Minor GC每分钟100次 ,Major GC每4分钟一次,单次Minor GC耗时25ms,单次Major GC耗时200ms,接口响应时间50ms。
MinorGC频繁
由于新生代空间较小,Eden区很快被填满,就会导致频繁Minor GC,因此可以通过增大新生代空间来降低Minor GC的频率
新生代中的Eden区增加一倍,Minor GC的次数就会减少一半
Minor GC时间
单次Minor GC时间=T1(扫描新生代)和 T2(复制存活对象到Survivor区,更耗时间)
Minor GC时间更多取决于GC后存活对象的数量,而非Eden区的大小
堆中短期对象很多,那么扩容新生代,单次Minor GC时间不会显著增加
选择各分区大小
如果应用存在大量的短期对象,应该选择较大的年轻代;如果存在相对较多的持久对象,老年代应该适当增大
MaxTenuringThreshold设定的阈值
a)MaxTenuringThreshold设置的过大,原本应该晋升的对象一直停留在Survivor区,直到Survivor区溢出,一旦溢出发生,Eden+Svuvivor中对象将不再依据年龄全部提升到老年代,这样对象老化的机制就失效了
动态年龄计算:Hotspot遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积
当累积的某个年龄大小超过了survivor区的一半时,取这个年龄和MaxTenuringThreshold中更小的一个值,作为新的晋升年龄阈值
当累积的某个年龄大小超过了survivor区的一半时,取这个年龄和MaxTenuringThreshold中更小的一个值,作为新的晋升年龄阈值
MaxTenuringThreshold设置的过小,“过早晋升”即对象不能在新生代充分被回收,大量短期对象被晋升到老年代,老年代空间迅速增长,引起频繁的Major GC。分代回收失去了意义,严重影响GC性能。
高峰期GC导致性能下降
高峰期CMS在重标记(Remark)阶段耗时1.39s。Remark阶段是Stop-The-World
remark阶段主要是通过扫描堆来判断对象是否存活。CMS对老年代做回收,Remark阶段仅扫描老年代是否可行?结论是不可行
跨代引用的存在,CMS在Remark阶段必须扫描整个堆
Remark前增加了一个可中断的并发预清理(CMS-concurrent-abortable-preclean),该阶段主要工作仍然是并发标记对象是否存活
参数CMSMaxAbortablePrecleanTime ,默认为5s,如果可中断的预清理执行超过5s,不管发没发生Minor GC,都会中止此阶段,进入Remark
解决:提供CMSScavengeBeforeRemark参数,用来保证Remark前强制进行一次Minor GC
Full GC
4核16g, -Xms10G -Xmx10G -Xmn3g, 1.8默认GC
压测1小时,老年代7G,会发生多次FullGC
压测1小时,老年代7G,会发生多次FullGC
问题分析
Perm永久代/元空间不足,自动扩容导致STW
把-XX:PermSize参数和-XX:MaxPermSize设置成一样,强制虚拟机在启动的时候就把永久代的容量固定下来,避免运行时自动扩容
大量代理类产生,通过参数CMSPermGenSweepingEnabled ,可以让CMS在Perm区容量不足时对其回收
JVM设置了参数(-XX:PretenureSizeThreshold=1000000),大对象直接进入老年代。
Minor GC时,Survivor区域太小
UseAdaptiveSizePolicy参数导致Survivor动态调整到过小,对象直接到老年代
老年代内存本身比较小,Minor GC时,有概率触发Full GC
CMS GC时出现promotion failed和concurrent mode failure
老年代空间不足,需要尽快回收老年代里面的不再被使用的对象,终止CMS,直接进行Serial Old GC
GC日志
参数
-Xmn 年轻代 -Xms 最小堆 -Xmx 最大堆 -Xss 栈空间
生产环境:5个日志文件,循环写入
-Xloggc:/xxx/logs/xxx-gc-%t.log
-XX:MaxTenuringThreshold: 生代年龄,最大值15
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=5
-XX:GCLogFileSize=20M
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCCause
-XX:+PrintFlagsFinal 打印回收期可用参数
java -XX:+PrintFlagsFinal -version | grep G1(or CMS, PS etc)
生产环境:5个日志文件,循环写入
-Xloggc:/xxx/logs/xxx-gc-%t.log
-XX:MaxTenuringThreshold: 生代年龄,最大值15
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=5
-XX:GCLogFileSize=20M
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCCause
-XX:+PrintFlagsFinal 打印回收期可用参数
java -XX:+PrintFlagsFinal -version | grep G1(or CMS, PS etc)
内存泄漏与内存溢出
内存泄漏
内存溢出
阿里巴巴Arthas
0 条评论
下一页