JVM调优工具锦囊JDK自带工具与Arthas线上分析工具对
Arthas线上分析诊断调优工具
以前我们要排查线上问题,通常使用的是jdk自带的调优工具和命令。最常见的就是dump线上日志,然后下载到本地,导入到jvisualvm工具中。这样操作有诸多不变,现在阿里团队开发的Arhtas工具,拥有非常强大的功能,并且都是线上的刚需,尤其是情况紧急,不方便立刻发版,适合临时处理危急情况使用。下面分两部分来研究JVM性能调优工具:
1。JDK自带的性能调优工具
虽然有了Arthas,但也不要忘记JDK自带的性能调优工具,在某些场景下,他还是有很大作用的。而且Arthas里面很多功能其根本就是封装了JDK自带的这些调优命令。
2。Arthas线上分析工具的使用
这一部分,主要介绍几个排查线上问题常用的方法。功能真的很强大,刚兴趣的猿媛可以研究其基本原理。之前跟我同事讨论,感觉这就像病毒一样,可以修改内存里的东西,真的还是挺强大的。
以上两种方式排查线上问题,没有优劣之分,如果线上不能安装Arthas就是jdk自带命令,如果jdk自带命令不能满足部分要求,又可以安装Arthas,那就使用Arthas。他们只是排查问题的工具,重要的是排查问题的思路。不管黑猫、白猫,能抓住耗子就是好猫。一、JDK自带的调优工具
这里不是流水一样的介绍功能怎么用,就说说线上遇到的问题,我们通常怎么排查,排查的几种情况。内存溢出,出现OutOfMemoryError,这个问题如何排查CPU使用猛增,这个问题如何排查?进程有死锁,这个问题如何排查?JVM参数调优
下面来一个一个解决1、处理内存溢出,报OutOfMemoryError错误第一步:通过jmaphisto命令查看系统内存使用情况
使用的命令:jmaphisto进程号
运行结果:numinstancesbytesclassname1:1101980372161752〔B2:551394186807240〔Ljava。lang。Object;3:1235341181685128〔C4:76692170306096〔I5:45916814693376java。util。concurrent。locks。AbstractQueuedSynchronizerNode6:54369913048776java。lang。String7:49763611943264java。util。ArrayList8:12427110935848java。lang。reflect。Method9:3485827057632〔Ljava。lang。Class;10:1862445959808java。util。concurrent。ConcurrentHashMapNode8671:116zipkin2。reporter。Reporter18672:116zipkin2。reporter。Reporter2Total8601492923719424num:序号instances:实例数量bytes:占用空间大小classname:类名称,〔Cisachar〔〕,〔Sisashort〔〕,〔Iisaint〔〕,〔Bisabyte〔〕,〔〔Iisaint〔〕〔〕
通过这个命令,我们可以看出当前哪个对象最消耗内存。
上面这个运行结果是我启动了本地的一个项目,然后运行【jmaphistro进程号】运行出来的结果,直接去了其中的一部分。通过这里我们可以看看大的实例对象中,有没有我们自定义的实例对象。通过这个可以排查出哪个实例对象引起的内存溢出。
除此之外,Total汇总数据可以看出当前一共有多少个对象,暂用了多大内存空间。这里是有约860w个对象,占用约923M的空间。第二步:分析内存溢出,查看堆空间占用情况
使用命令jhsdbjmapheappid进程号
比如,我本地启动了一个项目,想要查看这个项目的内存占用情况:〔rootiZ2pl8Z〕jhsdbjmapheappid28692AttachingtoprocessID28692,pleasewait。。。Debuggerattachedsuccessfully。Servercompilerdetected。JVMversionis11。0。1310LTS370usingthreadlocalobjectallocation。GarbageFirst(G1)GCwith4thread(s)HeapConfiguration:MinHeapFreeRatio40MaxHeapFreeRatio70MaxHeapSize2065694720(1970。0MB)NewSize1363144(1。2999954223632812MB)MaxNewSize1239416832(1182。0MB)OldSize5452592(5。1999969482421875MB)NewRatio2SurvivorRatio8MetaspaceSize21807104(20。796875MB)CompressedClassSpaceSize1073741824(1024。0MB)MaxMetaspaceSize17592186044415MBG1HeapRegionSize1048576(1。0MB)HeapUsage:G1Heap:regions1970capacity2065694720(1970。0MB)used467303384(445。65523529052734MB)free1598391336(1524。3447647094727MB)22。622093161955704usedG1YoungGeneration:EdenSpace:regions263capacity464519168(443。0MB)used275775488(263。0MB)free188743680(180。0MB)59。36794582392776usedSurvivorSpace:regions6capacity6291456(6。0MB)used6291456(6。0MB)free0(0。0MB)100。0usedG1OldGeneration:regions179capacity275775488(263。0MB)used186285016(177。65523529052734MB)free89490472(85。34476470947266MB)67。54951912187352used
下面来看看参数的含义
堆空间配置信息HeapConfiguration:空闲堆空间的最小百分比,计算公式为:HeapFreeRatio(CurrentFreeHeapSizeCurrentTotalHeapSize)100,值的区间为0到100,默认值为40。如果HeapFreeRatioMinHeapFreeRatio,则需要进行堆扩容,扩容的时机应该在每次垃圾回收之后。MinHeapFreeRatio40空闲堆空间的最大百分比,计算公式为:HeapFreeRatio(CurrentFreeHeapSizeCurrentTotalHeapSize)100,值的区间为0到100,默认值为70。如果HeapFreeRatioMaxHeapFreeRatio,则需要进行堆缩容,缩容的时机应该在每次垃圾回收之后MaxHeapFreeRatio70JVM堆空间允许的最大值MaxHeapSize2065694720(1970。0MB)JVM新生代堆空间的默认值NewSize1363144(1。2999954223632812MB)JVM新生代堆空间允许的最大值MaxNewSize1239416832(1182。0MB)JVM老年代堆空间的默认值OldSize5452592(5。1999969482421875MB)新生代(2个Survivor区和Eden区)与老年代(不包括永久区)的堆空间比值,表示新生代:老年代1:2NewRatio2两个Survivor区和Eden区的堆空间比值为8,表示S0:S1:Eden1:1:8SurvivorRatio8JVM元空间的默认值MetaspaceSize21807104(20。796875MB)CompressedClassSpaceSize1073741824(1024。0MB)JVM元空间允许的最大值MaxMetaspaceSize17592186044415MB在使用G1垃圾回收算法时,JVM会将Heap空间分隔为若干个Region,该参数用来指定每个Region空间的大小G1HeapRegionSize1048576(1。0MB)
G1堆使用情况HeapUsage:G1Heap:regions1970capacity2065694720(1970。0MB)used467303384(445。65523529052734MB)free1598391336(1524。3447647094727MB)22。622093161955704usedG1的Heap使用情况,该Heap包含1970个Region,结合上文每个RegionSize1M,因此CapacityRegionsRegionSize19701M1970M,已使用空间为445。65M,空闲空间为1524。34M,使用率为22。62。
G1年轻代Eden区使用情况G1YoungGeneration:EdenSpace:regions263capacity464519168(443。0MB)used275775488(263。0MB)free188743680(180。0MB)59。36794582392776usedG1的Eden区的使用情况,总共使用了263个Region,结合上文每个RegionSize1M,因此UsedRegionsRegionSize2631M263M,Capacity443M表明当前Eden空间分配了443个Region,使用率为59。37。
G1年轻代Survivor区使用情况和G1老年代使用情况:和Eden区类似SurvivorSpace:regions6capacity6291456(6。0MB)used6291456(6。0MB)free0(0。0MB)100。0usedG1OldGeneration:regions179capacity275775488(263。0MB)used186285016(177。65523529052734MB)free89490472(85。34476470947266MB)67。54951912187352usedSurvivor区使用情况和Eden区类似。老年代参数含义和Eden区类似。
通过上面的命令,我们就能知道当前系统对空间的使用情况了,到底是老年代有问题还是新生代有问题。第三步:导出dump内存溢出的文件,导入到jvisualvm查看
如果前两种方式还是没有排查出问题,我们可以导出内存溢出的日志,在导入客户端进行分析
使用的命令是:jmapdump:filea。dump进程号
或者是直接设置JVM参数XX:HeapDumpOnOutOfMemoryErrorXX:HeapDumpPath。(路径)
然后导入到jvisualvm中进行分析,方法是:点击文件装入,导入文件,查看系统的运行情况了。
通过分析实例数,看看哪个对象实例占比最高,这里重点看我们自定义的类,然后分析这个对象里面有没有大对象,从而找出引起内存溢出的根本原因。2、CPU使用猛增,这个问题如何排查?
我们可以通过Jstack找出占用cpu最高的线程的堆栈信息,下面来一步一步分析。
假设我们有一段死循环,不断执行方法调用,线程始终运行不释放就会导致CPU飙高,示例代码如下:packagecom。lxl。jvm;publicclassMath{publicstaticintinitData666;publicstaticUserusernewUser();publicUseruser1;publicintcompute(){inta1;intb2;intc(ab)10;returnc;}publicstaticvoidmain(String〔〕args){MathmathnewMath();while(true){math。compute();}}}第一步:运行代码,使用top命令查看cpu占用情况
如上,现在有一个java进程,cpu严重飙高了,接下来如何处理呢?第二步:使用topp命令查看飙高进程topp46518
我们看到了单独的46518这个线程的详细信息第三步:按H,获取每个线程的内存情况
需要注意的是,这里的H是大写的H。
我们可以看出线程0和线程1线程号飙高。第四步:找到内存和cpu占用最高的线程tid
通过上图我们看到占用cpu资源最高的线程有两个,线程号分别是4018362,4018363。我们一第一个为例说明,如何查询这个线程是哪个线程,以及这个线程的什么地方出现问题,导致cpu飙高。第五步:将线程tid转化为十六进制
67187778是线程号为4013442的十六进制数。具体转换可以网上查询工具。第六步:执行〔jstack4018360grepA1067187778〕查询飙高线程的堆栈信息
接下来查询飙高线程的堆栈信息jstack4013440grepA10671908824013440:表示的是进程号67187778:表示的是线程号对应的十六进制数
通过这个方式可以查询到这个线程对应的堆栈信息
从这里我们可以看出有问题的线程id是0x4cd0,哪一句代码有问题呢,Math类的22行。第七步:查看对应的堆栈信息找出可能存在问题的代码
上述方法定位问题已经很精确了,接下来就是区代码里排查为什么会有问题了。
备注:上面的进程id可能没有对应上,在测试的时候,需要写对进程id和线程id3、进程有死锁,这个问题如何排查?
Jstack可以用来查看堆栈使用情况,以及进程死锁情况。下面就来看看如何排查进程死锁
还是通过案例来分析packagecom。lxl。jvm;publicclassDeadLockTest{privatestaticObjectlock1newObject();privatestaticObjectlock2newObject();publicstaticvoidmain(String〔〕args){newThread((){synchronized(lock1){try{System。out。println(thread1begin);Thread。sleep(5000);}catch(InterruptedExceptione){}synchronized(lock2){System。out。println(thread1end);}}})。start();newThread((){synchronized(lock2){try{System。out。println(thread2begin);Thread。sleep(5000);}catch(InterruptedExceptione){}synchronized(lock1){System。out。println(thread2end);}}})。start();}}
上面是两把锁,互相调用。定义了两个成员变量lock1,lock2main方法中定义了两个线程。线程1内部使用的是同步执行上锁,锁是lock1。休眠5秒钟之后,他要获取第二把锁,执行第二段代码。线程2和线程1类似,锁相反。问题:一开始,像个线程并行执行,线程一获取lock1,线程2获取lock2。然后线程1继续执行,当休眠5s后获取开启第二个同步执行,锁是lock2,但这时候很可能线程2还没有执行完,所以还没有释放lock2,于是等待。线程2刚开始获取了lock2锁,休眠五秒后要去获取lock1锁,这时lock1锁还没释放,于是等待。两个线程就处于相互等待中,造成死锁。第一步:通过Jstack命令来看看是否能检测到当前有死锁。jstack51789
从这里面个异常可以看出,prio:当前线程的优先级cpu:cpu耗时osprio:操作系统级别的优先级tid:线程idnid:系统内核的idstate:当前的状态,BLOCKED,表示阻塞。通常正常的状态是Running我们看到Thread0和Thread1线程的状态都是BLOCKED。
通过上面的信息,我们判断出两个线程的状态都是BLOCKED,可能有点问题,然后继续往下看。
我们从最后的一段可以看到这句话:FoundoneJavaleveldeadlock;意思是找到一个死锁。死锁的线程号是Thread0,Thread1。
Thread0:正在等待0x000000070e706ef8对象的锁,这个对象现在被Thread1持有。
Thread1:正在等待0x000000070e705c98对象的锁,这个对象现在正在被Thread0持有。
最下面展示的是死锁的堆栈信息。死锁可能发生在DeadLockTest的第17行和第31行。通过这个提示,我们就可以找出死锁在哪里了。第二步:使用jvisualvm查看死锁
如果使用jstack感觉不太方便,还可以使用jvisualvm,通过界面来查看,更加直观。
在程序代码启动的过程中,打开jvisualvm工具。
找到当前运行的类,查看线程,就会看到最头上的一排红字:检测到死锁。然后点击线程Dump按钮,查看相信的线程死锁的信息。
这里可以找到线程私锁的详细信息,具体内容和上面使用Jstack命令查询的结果一样,这里实用工具更加方便。4、JVM参数调优
jvm调优通常使用的是Jstat命令。1。垃圾回收统计jstatgcjstatgc进程id
这个命令非常常用,在线上有问题的时候,可以通过这个命令来分析问题。
下面我们来测试一下,启动一个项目,然后在终端驶入jstatgc进程id,得到如下结果:
上面的参数分别是什么意思呢?先识别参数的含义,然后根据参数进行分析S0C:第一个Survivor区的容量S1C:第二个Survivor区的容量S0U:第一个Survivor区已经使用的容量S1U:第二个Survivor区已经使用的容量EC:新生代Eden区的容量EU:新生代Eden区已经使用的容量OC:老年代容量OU:老年代已经使用的容量MC:方法区大小(元空间)MU:方法区已经使用的大小CCSC:压缩指针占用空间CCSU:压缩指针已经使用的空间YGC:YoungGC已经发生的次数YGCT:这一次YoungGC耗时FGC:FullGC发生的次数FGCT:FullGC耗时GCT:总的GC耗时,等于YGCTFGCT
连续观察GC变化的命令jstatgc进程ID间隔时间打印次数
举个例子:我要打印10次gc信息,每次间隔1秒
jstatgc进程ID100010
这样就连续打印了10次gc的变化,每次隔一秒。
这个命令是对整体垃圾回收情况的统计,下面将会差分处理。2。堆内存统计
这个命令是打印堆内存的使用情况。jstatgccapacity进程ID
NGCMN:新生代最小容量NGCMX:新生代最大容量NGC:当前新生代容量S0C:第一个Survivor区大小S1C:第二个Survivor区大小EC:Eden区的大小OGCMN:老年代最小容量OGCMX:老年代最大容量OGC:当前老年代大小OC:当前老年代大小MCMN:最小元数据容量MCMX:最大元数据容量MC:当前元数据空间大小CCSMN:最小压缩类空间大小CCSMX:最大压缩类空间大小CCSC:当前压缩类空间大小YGC:年轻代gc次数FGC:老年代GC次数3。新生代垃圾回收统计
命令:jstatgcnew进程ID〔间隔时间打印次数〕
这个指的是当前某一次GC的内存情况
S0C:第一个Survivor的大小S1C:第二个Survivor的大小S0U:第一个Survivor已使用大小S1U:第二个Survivor已使用大小TT:对象在新生代存活的次数MTT:对象在新生代存活的最大次数DSS:期望的Survivor大小EC:Eden区的大小EU:Eden区的使用大小YGC:年轻代垃圾回收次数YGCT:年轻代垃圾回收消耗时间4。新生代内存统计jstatgcnewcapacity进程ID
参数含义:NGCMN:新生代最小容量NGCMX:新生代最大容量NGC:当前新生代容量S0CMX:Survivor1区最大大小S0C:当前Survivor1区大小S1CMX:Survivor2区最大大小S1C:当前Survivor2区大小ECMX:最大Eden区大小EC:当前Eden区大小YGC:年轻代垃圾回收次数FGC:老年代回收次数5。老年代垃圾回收统计
命令:jstatgcold进程ID
参数含义:MC:方法区大小MU:方法区已使用大小CCSC:压缩指针类空间大小CCSU:压缩类空间已使用大小OC:老年代大小OU:老年代已使用大小YGC:年轻代垃圾回收次数FGC:老年代垃圾回收次数FGCT:老年代垃圾回收消耗时间GCT:垃圾回收消耗总时间,新生代老年代6。老年代内存统计
命令:jstatgcoldcapacity进程ID
参数含义:OGCMN:老年代最小容量OGCMX:老年代最大容量OGC:当前老年代大小OC:老年代大小YGC:年轻代垃圾回收次数FGC:老年代垃圾回收次数FGCT:老年代垃圾回收消耗时间GCT:垃圾回收消耗总时间7。元数据空间统计
命令jstatgcmetacapacity进程ID
MCMN:最小元数据容量MCMX:最大元数据容量MC:当前元数据空间大小CCSMN:最小指针压缩类空间大小CCSMX:最大指针压缩类空间大小CCSC:当前指针压缩类空间大小YGC:年轻代垃圾回收次数FGC:老年代垃圾回收次数FGCT:老年代垃圾回收消耗时间GCT:垃圾回收消耗总时间8。整体运行情况
命令:jstatgcutil进程ID
S0:Survivor1区当前使用比例S1:Survivor2区当前使用比例E:Eden区使用比例O:老年代使用比例M:元数据区使用比例CCS:指针压缩使用比例YGC:年轻代垃圾回收次数YGCT:年轻代垃圾回收消耗时间FGC:老年代垃圾回收次数FGCT:老年代垃圾回收消耗时间GCT:垃圾回收消耗总时间
通过查询上面的参数来分析整个堆空间。二、Arthas线上分析工具的使用
Arthas的功能非常强大,现附上官方文档:https:arthas。aliyun。comdoc
其实想要了解Arthas,看官方文档就可以了,功能全而详细。那为什么还要整理一下呢?我们这里整理的是一些常用功能,以及在紧急情况下可以给我们帮大忙的功能。
Arthas分为几个部分来研究,先来看看我们的研究思路哈
1。安装及启动这一块简单看,对于程序员来说,soeasy
2。dashboard仪表盘功能类似于JDK的jstat命令,
3。thread命令查询进行信息类似于jmap命令
4。反编译线上代码这个功能很牛,改完发版了,怎么没生效,反编译看看。
5。查询某一个函数的返回值
6。查询jvm信息,并修改当发生内存溢出是,可以手动设置打印堆日志到文件
7。profiler火焰图
下面就来看看Arthas的常用功能的用法吧1、Arthas的安装及启动
其实说到这快,不得不提的是,之前我一直因为arthas是一个软件,要启动,界面操作。当时我就想,要是这样,在线上安装一个单独的应用,公司肯定不同意啊,研究完才发现,原来Arthas就是一个jar包。运行起来就是用javajar就可以。1)安装
可以直接在Linux上通过命令下载:wgethttps:alibaba。github。ioarthasarthasboot。jar
也可以在浏览器直接访问https:alibaba。github。ioarthasarthasboot。jar,等待下载成功后,上传到Linux服务器上。2)启动
执行命令就可以启动了javajararthasboot。jar
启动成功可以看到如下界面:
然后找到你想监控的进程,输入前面对应的编号,就可以开启进行监控模式了。比如我要看4
看到这个就表示,进入应用监听成功2、dashboard仪表盘查询整体项目运行情况
执行命令dashboard
这里面一共有三块1)线程信息
我们可以看到当前进程下所有的线程信息。其中第13,14号线程当前处于BLOCKED阻塞状态,阻塞时间也可以看到。通过这个一目了然,当前有两个线程是有问题的,处于阻塞状态GC线程有6个。2)内存信息
内存信息包含三个部分:堆空间信息、非堆空间信息和GC垃圾收集信息堆空间信息g1edenspace:Eden区空间使用情况g1survivorspace:Survivor区空间使用情况g1oldgen:Old老年代空间使用情况非堆空间信息codeheapnonnmethods:非方法代码堆大小metaspace:元数据空间使用情况codeheapprofilednmethods:compressedclassspace:压缩类空间使用情况GC垃圾收集信息gc。g1younggeneration。count:新生代gc的数量gc。g1younggeneration。time(ms)新生代gc的耗时gc。g1oldgeneration。count:老年代gc的数量gc。g1oldgeneration。time(ms):老年代gc的耗时3)运行时信息os。name:当前使用的操作系统MacOSXos。version:操作系统的版本号10。16java。version:java版本号11。0。2java。home:java根目录LibraryJavaJavaVirtualMachinesjdk11。0。2。jdkContentsHomesystemload。average:系统cpu负载平均值4。43
loadaverage值的含义
单核处理器
假设我们的系统是单CPU单内核的,把它比喻成是一条单向马路,把CPU任务比作汽车。当车不多的时候,load1;当车占满整个马路的时候load1;当马路都站满了,而且马路外还堆满了汽车的时候,load1
Load1
Load1
Load1
多核处理器
我们经常会发现服务器Load1但是运行仍然不错,那是因为服务器是多核处理器(Multicore)。
假设我们服务器CPU是2核,那么将意味我们拥有2条马路,我们的Load2时,所有马路都跑满车辆。
Load2时马路都跑满了processors:处理器个数8timestampuptime:采集的时间戳FriJan0711:36:12CST20222349s
通过仪表盘,我们能从整体了解当前线程的运行健康状况3。thread命令查询CPU使用率最高的线程及问题原因
通过dashboard我们可以看到当前进程下运行的所有的线程。那么如果想要具体查看某一个线程的运行情况,可以使用thread命令1。统计cpu使用率最高的n个线程
先来看看常用的参数。参数说明
参数名称
参数说明
id
线程id
〔n:〕
指定最忙的前N个线程并打印堆栈
〔b〕
找出当前阻塞其他线程的线程
〔i〕
指定cpu使用率统计的采样间隔,单位为毫秒,默认值为200
〔all〕
显示所有匹配的线程
我们的目标是想要找出CPU使用率最高的n个线程。那么需要先明确,如何计算出CPU使用率,然后才能找到最高的。计算规则如下:首先、第一次采样,获取所有线程的CPU时间(调用的是java。lang。management。ThreadMXBeangetThreadCpuTime()及sun。management。HotspotThreadMBean。getInternalThreadCpuTimes()接口)然后、睡眠等待一个间隔时间(默认为200ms,可以通过i指定间隔时间)再次、第二次采样,获取所有线程的CPU时间,对比两次采样数据,计算出每个线程的增量CPU时间线程CPU使用率线程增量CPU时间采样间隔时间100注意:这个统计也会产生一定的开销(JDK这个接口本身开销比较大),因此会看到as的线程占用一定的百分比,为了降低统计自身的开销带来的影响,可以把采样间隔拉长一些,比如5000毫秒。
统计1秒内cpu使用率最高的n个线程:threadn3i1000
从线程的详情可以分析出,目前第一个线程的使用率是最高的,cpu占用了达到99。38。第二行告诉我们,是Arthas。java这个类的第38行导致的。
由此,我们可以一眼看出问题,然后定位问题代码的位置,接下来就是人工排查问题了。2、查询出当前被阻塞的线程
命令:threadb
可以看到内容提示,线程Thread1被线程Thread0阻塞。对应的代码行数是DeadLockTest。java类的第31行。根据这个提示去查找代码问题。3、指定采样的时间间隔
命令threadi1000
这个的含义是个1s统计一次采样4。反编译线上代码这个功能很牛,改完发版了,怎么没生效,反编译看看。
说道Arthas,不得不提的一个功能就是线上反编译代码的功能。经常会发生的一种状况是,线上有问题,定位问题后立刻改代码,可是发版后发现没生效,不可能啊刚刚提交成功了呀。于是重新发版,只能靠运气,不知道为啥没生效。
反编译线上代码可以让我们一目了然知道代码带动部分是否生效。反编译代码使用Arthas的jad命令jad命令将JVM中实际运行的class的bytecode反编译成java代码
用法:jadcom。lxl。jvm。DeadLockTest
运行结果:
运行结果分析:这里包含3个部分ClassLoader:类加载器就是加载当前类的是哪一个类加载器Location:类在本地保存的位置源码:类反编译字节码后的源码
如果不想想是类加载信息和本地位置,只想要查看类源码信息,可以增加sourceonly参数jadsourceonly类全名6。ognl动态执行线上的代码
能够调用线上的代码,是不是很神奇了。感觉哪段代码执行有问题,但是又没有日志,就可以使用这个方法动态调用目标方法了。
我们下面的案例都是基于这段代码执行,User类:publicclassUser{privateintid;privateStringname;publicUser(){}publicUser(intid,Stringname){this。idid;this。namename;}publicintgetId(){returnid;}publicvoidsetId(intid){this。idid;}publicStringgetName(){returnname;}publicvoidsetName(Stringname){this。namename;}}
DeadLockTest类:publicclassDeadLockTest{privatestaticObjectlock1newObject();privatestaticObjectlock2newObject();privatestaticListStringnamesnewArrayList();privateListStringcitysnewArrayList();publicstaticStringadd(){names。add(zhangsan);names。add(lisi);names。add(wangwu);names。add(zhaoliu);return123456;}publicListStringgetCitys(){DeadLockTestdeadLockTestnewDeadLockTest();deadLockTest。citys。add(北京);returndeadLockTest。citys;}publicstaticListUseraddUsers(Integerid,Stringname){ListUserusersnewArrayList();UserusernewUser(id,name);users。add(user);returnusers;}publicstaticvoidmain(String〔〕args){newThread((){synchronized(lock1){try{System。out。println(thread1begin);Thread。sleep(5000);}catch(InterruptedExceptione){}synchronized(lock2){System。out。println(thread1end);}}})。start();newThread((){synchronized(lock2){try{System。out。println(thread2begin);Thread。sleep(5000);}catch(InterruptedExceptione){}synchronized(lock1){System。out。println(thread2end);}}})。start();}}1)获取静态函数返回值是字符串ognl全路径类名静态方法名(参数)
示例1:在DeadLockTest类中有一个add静态方法,我们来看看通过ognl怎么执行这个静态方法。执行命令ognlcom。lxl。jvm。DeadLockTestadd()其中,第一个后面跟的是类的全名称;第二个跟的是属性或者方法名,如果属性是一个对象,想要获取属性里面的属性或者方法,直接打。属性名方法名即可。
运行效果:
我们看到了这个对象的返回值是123456返回值是对象ognl全路径类名静态方法名(参数)x2
这里我们可以尝试一下替换x2为x1;x3;案例1:返回对象的地址。不加x或者是x1ognlcom。lxl。jvm。DeadLockTestaddUsers(1,zhangsan)或ognlcom。lxl。jvm。DeadLockTestaddUsers(1,zhangsan)x1
返回值
案例2:返回对象中具体参数的值。加x2ognlcom。lxl。jvm。DeadLockTestaddUsers(1,zhangsan)x2
返回值
案例3:返回对象中有其他对象命令:ognlcom。lxl。jvm。DeadLockTestaddUsers(1,zhangsan)x2
执行结果:
x2获取的是对象的值,List返回的是数组信息,数组长度。命令:ognlcom。lxl。jvm。DeadLockTestaddUsers(1,zhangsan)x3
执行结果:
x3打印出对象的值,对象中List列表中的值。案例4:方法A的返回值当做方法B的入参ognlvalue1com。lxl。jvm。DeadLockTestgetCitys(),value2com。lxl。jvm。DeadLockTestgeneratorUser(1,lisi,value1),{value1,value2}x2
方法入参是简单类型的列表ognlcom。lxl。jvm。DeadLockTestreturnCitys({beijing,shanghai,guangdong})
方法入参是一个复杂对象ognlvalue1newcom。lxl。jvm。User(1,zhangsan),value1。setName(aaa),value1。setCitys({bj,sh}),value2com。lxl。jvm。DeadLockTestaddUsers(value1),value2x3
方法入参是一个map对象ognlvalue1newcom。lxl。jvm。User(1,zhangsan),value1。setCitys({bj,sh}),value2{mum:zhangnvshi,dad:wangxiansheng},value1。setFamily(value2),value1x2
2)获取静态字段ognl全路径类名静态属性名
示例:在DeadLockTest类中有一个names静态属性,下面来看看如何获取这个静态属性。执行命令:ognlcom。lxl。jvm。DeadLockTestnames其中,第一个后面跟的是类的全名称;第二个跟的是属性或者方法名,如果属性是一个对象,想要获取属性里面的属性或者方法,直接打。属性名方法名即可。
运行效果:
第一次执行获取属性命令,返回的属性是一个空集合;然后执行add方法,往names集合中添加了属性;再次请求names集合,发现有4个属性返回。3)获取实例对象ognlvalue1newcom。lxl。jvm。User(1,zhangsan),value1。setName(aaa),value1。setCitys({bj,sh}),{value1}x2
获取实例对象,使用new关键字,执行结果:
7。线上代码修改
生产环境有时会遇到非常紧急的问题,或突然发现一个bug,这时候不方便重新发版,或者发版未生效,可以使用Arthas临时修改线上代码。通过Arthas修改的步骤如下:1。从读取。class文件2。编译成。java文件3。修改。java文件4。将修改后的。java文件编译成新的。class文件5。将新的。class文件通过classloader加载进JVM内第一步:读取。class文件scdDeadLockTest
使用sc命令查看JVM已加载的类信息。关于sc命令,查看官方文档:https:arthas。aliyun。comdocsc。htmld:表示打印类的详细信息
最后一个参数classLoaderHash,表示在jvm中类加载的hash值,我们要获得的就是这个值。第二步:使用jad命令将。class文件反编译为。java文件才行jadc7c53a9ebsourceonlycom。lxl。jvm。DeadLockTestUserslxlDownloadsDeadLockTest。javajad命令是反编译指定已加载类的源码c:类所属ClassLoader的hashcodesourceonly:默认情况下,反编译结果里会带有ClassLoader信息,通过sourceonly选项,可以只打印源代码。com。lxl。jvm。DeadLockTest:目标类的全路径UserslxlDownloadsDeadLockTest。java:反编译文件的保存路径DecompiledwithCFR。Couldnotloadthefollowingclasses:com。lxl。jvm。Userpackagecom。lxl。jvm;importcom。lxl。jvm。User;importjava。util。ArrayList;importjava。util。List;publicclassDeadLockTest{privatestaticObjectlock1newObject();privatestaticObjectlock2newObject();privatestaticListStringnamesnewArrayListString();privateListStringcitysnewArrayListString();publicstaticListStringgetCitys(){DeadLockTestdeadLockTestnewDeadLockTest();25deadLockTest。citys。add(北京);27returndeadLockTest。citys;}。。。。。。publicstaticvoidmain(String〔〕args){。。。。。。}}
这里截取了部分代码。第三步:修改java文件publicstaticListStringgetCitys(){System。out。println(这里增加了一句日志打印);DeadLockTestdeadLockTestnewDeadLockTest();25deadLockTest。citys。add(北京);27returndeadLockTest。citys;}第四步:使用mc命令将。java文件编译成。class文件mcc512ddf17dUsersluoxiaoliDownloadsUsersluoxiaoliDownloadsDeadLockTest。javamc:编译。java文件生。class文件,详细使用方法参考官方文档https:arthas。aliyun。comdocmc。htmlc:指定classloader的hash值d:指定输出目录最后一个参数是java文件路径
这是反编译后的class字节码文件
第五步:使用redefine命令,将。class文件重新加载进JVMredefinecUsersDownloadscomlxljvmDeadLockTest。class
最后看到redefinesuccess,表示重新加载。class文件进JVM成功了。注意事项
redefine命令使用之后,再使用jad命令会使字节码重置,恢复为未修改之前的样子。官方关于redefine命令的说明第六步:检验效果
这里检测效果,调用接口,执行日志即可。8、实时修改生产环境的日志级别
这个功能也很好用,通常,我们在日志中打印的日志级别一般是infor、warn、error级别的,debug日志一般看不到。那么出问题的时候,一些日志,在写代码的时候会被记录在debug日志中,而此时日志级别又很高。那么迫切需要调整日志级别。
这个功能很好用啊,我们可以将平时不经常打印出来的日志设置为debug级别。设置线上日志打印级别为info。当线上有问题的时候,可以将日志级别动态调整为debug。异常排查完,在修改回info。这对访问量特别大日志内容很多的项目比较有效,可以有效节省日志输出带来的开销。第一步:使用logger命令查看日志级别
当前应用的日志级别是info类加载的hash值是18b4aac2
我们定义一个接口,其源代码内容如下:PostMapping(valuetest)publicStringtest(){log。debug(这是一条debug级别的日志);log。info(这是一条info级别的日志);log。error(这是一条error级别的日志);log。warn(这是一条warn级别的日志);return完成;}
可以调用接口,查看日志输出代码。
我们看到,日志输出的是info及以下的级别。第二步:修改logger日志的级别loggerc18b4aac2nameROOTleveldebug
修改完日志级别以后,输出日志为debug级别。
8。查询jvm信息,并修改当发生内存溢出时,可以手动设置打印堆日志到文件
通常查询jvm参数,使用的是Java自带的工具〔jinfo进程号〕。arthas中通过vmoption获取jvm参数:
假设,我们要设置JVM出现OutOfMemoryError的时候,自动dump堆快照vmoptionHeapDumpOnOutOfMemoryErrortrue
这时,如果发生堆内存溢出,会打印日志到文件9。监控函数耗时trace待监控方法的全类名待监控的方法名tracecom。lxl。jvm。DeadLockTestgeneratorUser
通过圈起来的部分可以看到,接口的入口函数time总耗时371ms其中getDataFromDb函数耗时200msgetDataFromRedis函数耗时100msgetDataFromOuter函数耗时50msprocess函数耗时20ms
很明显,最慢的函数已经找到了,接下里就要去对代码进行进一步分析,然后再进行优化
以上就是JVM调优常用的工具了,如果觉得本文对你有帮助,可以转发关注支持一下
原文链接:https:www。cnblogs。comITPowerp15785439。html
作者:盛开的太阳
实体店位置很好却客流惨淡?教你5种小程序引流技巧实体店生意好不好,最直观的感受就是客流量,有些商家该说了,我店的商品也不差啊,为什么就是没有客流呢?没有客流,就没有转化,这时候,商家应该怎样做呢?一、实体店客流惨淡的原……
关于桥梁检测的几种不同方法桥,是人类智慧的结晶,它给人们带来交通的便利,并给人以美感。自古以来,人们对于桥的赞美数不胜数:骆宾王的云气横开八阵形,桥形遥分七星势;苏轼的弯弯飞桥出,敛敛半月彀,毛泽东挥笔……
2017年移动通信业务收入9071亿元纽约时报2017年总营一、工信部:2017年移动通信业务收入达9071亿元事件:工信部日前发布《2017年通信业统计公报》,初步核算,2017年电信业务总量达到27557亿元,同比增长76。4……
大屏电视的痛点在哪里?画质智能才是根本现在大家的生活质量都提高了,对于家电一类的东西早就不是停留在够用就好的层次上,而是更加追求产品的品质与性能,这应该就是品质生活的一个反映了。特别是在电视这里,电视作为家庭娱乐活……
网传涉及A4L等车型奥迪因芯片短缺导致停产系假新闻刚刚,网传一张涉及A4L等车型奥迪因芯片短缺导致停产的新闻消息,针对此事,我们向奥迪内部人士进行了确认,经过确认后了解到,该网传消息为假新闻,请大家勿听信谣传,一切以官方发布的……
当抽象艺术碰上CharioGhibli会产生什么样的火花?视频加载中。。。绘画和音乐都是反映人类现实生活情感的艺术。可以说,抽象艺术是无声的乐,面对一件抽象绘画,看到线条、色块的交融与律动,似乎能感觉到有音乐在心底响起。而且在昌……
真我GT大师探索版和小米10s之间,该如何选择?根据买新不买旧的原则,小芳我个人是更加倾向于推荐大家购买真我GT大师探索版这款手机的。不过话又说回来,每个人在购买手机的过程当中,因为关注的东西是不一样的,那么做出的最终购机选……
元宇宙迭代互联网码世界颠覆互联网近日,新华网《巨头纷纷入局!元宇宙究竟是什么?》、人民网《引爆科技圈的元宇宙是什么?》、央视网《一文读(不)懂元宇宙!》、市界《元宇宙,真能重启世界?》、国际金融报《风口还是炒……
颠覆认知的商业思维原来这样才能挣大钱绝不认怂,没有平台,就创造平台!卖产品只能微利,做平台才能暴力!链家与贝壳,没有一套房,却是地产的首富。时代变了,你越想挣什么产品的钱,就不要去卖什么!美团与饿了么从来没……
FFmpeg实战002ffplay介绍及入门使用什么是ffplayffplay是FFmpeg提供的一个极为简单的音视频媒体播放器(由ffmpeg库和SDL库开发),可以用于音视频播放、可视化分析,提供音视频显示和播放相……
马桶旁砌堵墙,收纳功能立马变强对于小户型的居室来说,家里的每一平米都不能浪费。就拿卫生间来说,如果里面收纳做得不好,物品没有一个合适的地方来摆放,那么就会出现脏乱差的局面,严重影响了卫生间的使用和美观度。……
智能升级潮流出街,哪吒VPro上市7。69万元起以智取胜,向新而升。11月3日,7万级智能科技舱哪吒VPro精智上市,新车包括长续航智趣版、长续航智享版两款车型,补贴后官方售价分别是7。69万元、8。09万元。基于年轻消费者……
诚邀您莅临,2018马丁吉他夏日讲座在传统家族式的管理模式下,Martin马丁吉他已经走过了180多年,传承到家族的第6代掌门人ChrisMartin。Martin马丁吉他的品牌故事、Customshop系列、主……
服务机器人需求剧增,盘点这条赛道上的赢家文杨剑勇近年来,随着人工智能技术发展,机器人迎来最好的发展时期。在世界工厂有超过270万台工业机器人在运行,该数据来自国际机器人联合会IFR。主要得益于智能生产与自动化推……
冷库中标后技术要求及售后服务1。冷库技术培训中标供应商应结合设备安装、调试等阶段,同步地免费对采购人的技术人员就有关系统安装、维护、操作使用等方面进行现场技术培训,使受训人员能熟练掌握所有的安装测试……
生活小记1背景:景区内的三乙医院,因此医院的建筑限高,最高建筑是7楼~也因此医院有些楼房较老较破~本人医院小螺丝钉一枚,科室存在于老破房内,但大家生活和谐,相处愉快。据说事情发生在……
属于年轻人的OPPOEncoAir,均衡无短板的入门级TWS真无线这个品类的耳机,它是属于年轻人的。我问过一些朋友,他们给长辈推荐真无线的努力,很多都是以失败而告终的老人家的思维很淳朴,他们大多觉得这东西又分三个零件看着就会容易丢……
从这几个方面入手,教你如何应对以后的就业形势学习是我们的头等大事,其实我们想要学习好,归根结底都是想要选择一个好的工作,能够站在更好的平台以更高的眼光来看待生活。其实良好的生活习惯、正确的三观、积极乐观的性格,也都会使我……
值得被吹爆的大兴机场,正引领全新DIY值机方式普及要说去年的热点话题新闻,大兴机场必定位列其中。自去年于正式通航后,集结众多先进黑科技的北京大兴国际机场,成为北京的新晋地标建筑,亦晋升为国际重要机场枢纽代表之一。与之而来的是,……
顺应可持续发展战略,是时候关注工业用电节能问题了电能作为生活、经济发展的重要能源,是人类活动持续、有效进行的基础保障。按照国家电网公司根据用户用电性质的分类,用电可以分为:居民生活用电、一般工商业用电、大工业用电、农业生产用……
从二类医疗器械开始,触控一体机这个检测要求高了起来智慧医疗的蓬勃发展,让医疗智能化成为了当前行业的主要发展方向,这也使得众多与医疗智能化相关联的产业得到全新的发展机遇,智能硬件产业便是其一。而在许多医疗器械设备中,都需要触控显……
鼠标怎么选?适合自己的最重要二十世纪以来,无论是办公还是娱乐,人们都越来越离不开电脑了,自然而然的,也越来越离不开鼠标。生活中,许多人对外设产品不了解,所以总是买不到适合自己的产品,使用之后不是握着不舒服……
助力智能螺丝机机器人应用,工业一体机加速柔性智能制造推行随着我国紧固件市场规模将继续保持上升,据公开数据显示,2019年中国紧固件的总体市场规模将达到1380亿元,并持续呈增长趋势。加上柔性智能制造的需求,国内紧固件智能制造发展,正……
工业平板电脑在制造业数字化改造中的应用体现工业平板电脑在制造业领域应用广泛,随着制造业数字化和自动化浪潮的到来,工业平板电脑也迎来了新的发展机遇。一个常规化的制造业工厂改造成数字化、自动化模式,这其中需要几步?抛……