java里内存、GC、性能调优的常用方法
内存调优内存泄漏memory leak在Java中如果不再使用一个对象但是该对象依然在GC ROOT的引用链上这个对象就不会被垃圾回收器回收这种情况就称之为内存泄漏。常见的GC ROOT线程栈里的局部变量publicvoidfoo(){PersonpnewPerson();// p 在栈帧局部变量表 → 整棵 Person 对象都是 GC Root 可达bar();}静态字段类变量publicclassCacheHolder{publicstaticMapLong,OrderorderCachenewConcurrentHashMap();//静态字段引用的 ConcurrentHashMap 实例是 GC Root}正在加锁的对象synchronized(myLock){// myLock 对象被当前线程持有doSomething();}堆内存状况的对比使用VisualVM查看- 正常情况处理业务时会出现上下起伏业务对象频繁创建内存会升高触发MinorGC之后内存会降下来。手动执行FULL GC之后内存大小会骤降而且每次降完之后的大小是接近的。长时间观察内存曲线应该是在一个范围内。- 出现内存泄漏- 处于持续增长的情况即使Minor GC也不能把大部分对象回收- 手动FULL GC之后的内存量每一次都在增长- 长时间观察内存曲线持续增长生成内存快照的Java虚拟机参数在发生溢出的时候-XX:HeapDumpOnOutOfMemoryError发生OutOfMemoryError错误时自动生成hprof内存快照文件。-XX:HeapDumpPath指定hprof文件的输出路径。–Xmx堆的最大值和-Xms (堆的初始total)-Xmx256m -Xms256m -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPathD:\jvm\dump\test1.hprof手动生成快照这个过程中会影响用户的使用通过JDK自带的jmap命令导出格式为jmap -dump:live,formatb,file文件路径和文件名 进程ID通过arthas的heapdump命令导出格式为heapdump --live 文件路径和文件名导出后的hprof通过mat打开后可以通过TaskThread定位到当前正在执行的线程里正在执行的方法从支配树里找到此时线程正在执行的方法先找tomcat里的TaskThread然后找HandlerExecutionChain或者HandlerMethod鼠标右键后选择list objects然后选with outgoing references选项英文直译实际含义用途with outgoing references“出引用”这条对象内部都引用了谁想看“它把内存压给了哪些对象”往下追内存泄漏源头。with incoming references“入引用”都有哪些对象引用了它想看“是谁在抓着它不放”往上追为什么它没被 GC。通过 outgoing references就可以找到他调用的方法。通过VisualVM监听远程java进程启动命令java \-Dcom.sun.management.jmxremote \-Dcom.sun.management.jmxremote.port9010\-Dcom.sun.management.jmxremote.authenticatefalse\-Dcom.sun.management.jmxremote.sslfalse\-Djava.rmi.server.hostname192.168.42.168\//你启动jar包服务器的ip-jarCoreApplication.jar写ip鼠标右键完成远程监听在线定位内存问题1、使用jmap-histo:live 进程ID 文件名 命令将内存中存活对象以直方图的形式保存到文件中这个过程会影响用户的时间但是时间比较短暂。2、分析内存占用最多的对象一般这些对象就是造成内存泄漏的原因。3、使用arthas的stack命令追踪对象创建的方法被调用的调用路径找到对象创建的根源。也可以使用btrace工具编写脚本追踪方法执行的过程。jmap-histo:live 进程ID 文件名打开导出的文件发现UserEntity的数量太多了不太正常通过arthas里的stack方法跟踪这个对象发送请求后这里就找到了方法确认实体出现方式stack com.xxx.YourEntity -n 1栈顶是你自己包 → 业务代码 new 的 → 任务完成栈顶是 FastJSON / MyBatis / Jackson → 框架反序列化 → 继续 ②找“第一次用到实体”的框架入口常见入口就 3 个直接按场景选一条Redis 反序列化stack com.xxx.framework.redis.RedisCache getCacheObject -n 1追这个方法是因为getCacheObject 肯定是我写的代码里调用的而不是工具类调用的跟踪这个能找到实际的方法MyBatis 查询stack com.xxx.mapper.YourEntityMapper select* -n 1JSON 接口stack com.alibaba.fastjson2.JSON parseObject -n 1把打印出来的第一个自己包名的方法如 com.xxx.service.impl.YourServiceImpl.findUser复制下来。直捣黄龙——trace 真正的业务方法trace com.xxx.service.impl.YourServiceImpl findUser -n 5 --skipJDKMethod false控制台就会给出完整链路Controller → Service → 框架入口 → 反序列化 → 实体.()最顶层那个 Controller 方法就是你要找的“实体使用者”。或者使用btrace方法1、下载btrace工具 官方地址https://github.com/btraceio/btrace/releases/latest2、编写btrace脚本通常是一个java文件。优点3、将btrace工具和脚本上传到服务器在服务器上运行 btrace 进程ID 脚本文件名 。4、观察执行结果首先创建一个maven项目引入依赖dependenciesdependencygroupIdorg.openjdk.btrace/groupIdartifactIdbtrace-agent/artifactIdversion${btrace.version}/versionscopesystem/scopesystemPathF:\JVMTOOLS\btrace-v2.2.4-bin.tar\btrace-v2.2.4-bin\libs\btrace-agent.jar/systemPath/dependencydependencygroupIdorg.openjdk.btrace/groupIdartifactIdbtrace-boot/artifactIdversion${btrace.version}/versionscopesystem/scopesystemPathF:\JVMTOOLS\btrace-v2.2.4-bin.tar\btrace-v2.2.4-bin\libs\btrace-boot.jar/systemPath/dependencydependencygroupIdorg.openjdk.btrace/groupIdartifactIdbtrace-client/artifactIdversion${btrace.version}/versionscopesystem/scopesystemPathF:\JVMTOOLS\btrace-v2.2.4-bin.tar\btrace-v2.2.4-bin\libs\btrace-client.jar/systemPath/dependency/dependencies编写代码BTracepublicclassTracingUserEntity{OnMethod(clazzcom.itheima.jvmoptimize.entity.UserEntity,method/.*/)publicstaticvoidtraceExecute(){jstack();}}代码拖到服务器记得btrace也得在服务器进行监听GC调优看gc信息最简单的办法jstat -gc 进程ID每次统计的间隔毫秒统计次数列名全称含义单位KBS0CSurvivor 0 Capacity年轻代第 0 个 Survivor 区总大小S1CSurvivor 1 Capacity年轻代第 1 个 Survivor 区总大小S0USurvivor 0 UsedS0 已使用大小S1USurvivor 1 UsedS1 已使用大小ECEden CapacityEden 区总大小EUEden UsedEden 已使用大小OCOld Capacity老年代总大小OUOld Used老年代已使用大小MCMetaspace CapacityMetaspace 已提交大小JDK 8MUMetaspace UsedMetaspace 已使用大小CCSCCompressed Class Space Capacity压缩类空间总大小CCSUCompressed Class Space Used压缩类空间已使用大小YGCYoung GC 次数自启动以来 Young GC 总次数YGCTYoung GC TimeYoung GC 累计耗时秒FGCFull GC 次数Full GC 总次数FGCTFull GC TimeFull GC 累计耗时秒CGCConcurrent GC 次数G1/ZGC 等并发 GC 周期次数CGCTConcurrent GC Time并发 GC 累计耗时秒GCTTotal GC Time所有 GC 累计耗时秒 YGCTFGCTCGCT例子年轻代EU≈EC 且 YGC 猛涨 → 对象朝生夕灭正常EU 一直小于 EC 但 YGC 不涨 → 流量低没事EU 长期接近 EC 且 YGC 涨得飞快 → ** Eden 太小或对象太大**考虑调大 -Xmn 或检查一次性分配大数组的代码。老年代OU 匀速上涨 FGC 上涨也清不掉 → 内存泄漏/缓存堆积OU 上涨后 FGC 一次掉回 → 正常晋升只要 FGCT 不飙高就接受FGC 次数多但 OU 几乎没降 → 老年代不足或泄露需 dump 堆。MetaspaceMU 逼近 MC 且 MC 持续上涨 → 类泄漏反射、Groovy、Jackson 动态类调大 -XX:MaxMetaspaceSize 只是缓兵之计得找代码。耗时YGCT/FGCT 任意一项单次平均超过 200 ms用总耗时÷次数→ 用户线程被暂停需要换 GC 器或调大堆。使用visualVM安装插件看-verbose:gc加在虚拟机参数可以在运行中打印gc日志直接生成专门的gc日志JDK 8及以下-XX:PrintGCDetails -Xloggc:文件名JDK 9-Xlog:gc*:file文件名将刚刚生成的日志文件使用gcviewer看或者使用 GCeasy看https://gceasy.io/优化基础JVM参数-Xmx 和 –Xms-Xms用来设置初始堆大小建议将-Xms设置的和-Xmx一样大-XX:MaxMetaspaceSize值 参数指的是最大元空间大小默认值比较大如果出现元空间内存泄漏会让操作系统可用内存不可控建议根据测试情况设置最大值一般设置为256m。-XX:MetaspaceSize值 参数指的是到达这个值之后会触发FULLGC-Xss虚拟机栈大小如果我们不指定栈的大小JVM 将创建一个具有默认大小的栈。大小取决于操作系统和计算机的体系结构。比如Linux x86 64位 1MB如果不需要用到这么大的栈内存完全可以将此值调小节省内存空间合理值为256k – 1m之间。使用-Xss256k-XX:DisableExplicitGC禁止在代码中使用System.gc() System.gc()可能会引起FULLGC在代码中尽量不要使用。使用DisableExplicitGC参数可以禁止使用System.gc()方法调用。-XX:HeapDumpOnOutOfMemoryError发生OutOfMemoryError错误时自动生成hprof内存快照文件。-XX:HeapDumpPath指定hprof文件的输出路径。打印GC日志JDK8及之前 -XX:PrintGCDetails-XX:PrintGCDateStamps-Xloggc:文件路径JDK9及之后 -Xlog:gc*:file文件路径常见模板-Xms1g# 堆初始大小1GB启动即申请避免运行时动态扩容-Xmx1g# 堆最大1GB与-Xms相同可消除堆伸缩带来的GC抖动-Xss256k# 单个线程栈大小256KB栈越深可创线程越多默认1M时可降到256k-XX:MaxMetaspaceSize512m # 元空间上限512MB防止类加载泄漏把本机内存打爆-XX:DisableExplicitGC# 屏蔽System.gc()避免代码/库主动触发FullGC造成莫名停顿-XX:HeapDumpOnOutOfMemoryError# 首次OOM时自动 dump 堆快照留作事后分析-XX:HeapDumpPath/opt/logs/my-service.hprof # 指定 dump 文件路径默认当前目录可能权限不足-Xlog:gc*:file/opt/logs/gc.log # 把GC详细日志写到文件方便GCViewer/gceasy 分析如果要查出某个方法为什么慢一般都是三步走1、通过arthas的trace命令首先找到性能较差的具体方法如果访问量比较大建议设置最小的耗时精确的找到耗时比较高的调用。2、通过watch命令查看此调用的参数和返回值重点是参数这样就可以在开发环境或者测试环境模拟类似的现象通过debug找到具体的问题根源。3、使用stop命令将所有增强的对象恢复。一般来说watch只是用来看参数用的watch io.xx.service.query.ChooseQueryService getData ‘{params, returnObj}’ ‘#cost1’ -x 4 --exclude-class-pattern ‘EnhancerBySpringCGLIB’trace io.xx.controller.query.ChooseQueryController getData ‘params[0].queryCode“0011”’ ‘‘里写条件如果不清楚可以用watch先看看完后在加写多个条件就’params[0].queryCode’‘0011’’ params[0].size15‘trace --skipJDKMethod false io.dataease.service.query.ChooseQueryService getData ‘#cost1’ 只保留 耗时 1ms 的调用 同时连JDK 方法也一起追踪。使用arthas监控sql执行时长watch org.apache.ibatis.executor.statement.PreparedStatementHandler query‘{target.boundSql.sql, target.parameterHandler.parameterObject, #cost}’‘#cost1’ -x 2 -s动态trace增强方法首先第一个终端进行你要监控的方法trace io.dataease.controller.query.ChooseQueryController getData params[0].queryCode0011’在开一个终端监听子方法trace io.dataease.service.query.ChooseQueryService getData --listenerId 3这里的id要和上面的id一样就可以在上面的参数限制下继续监听当前条件
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2419692.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!