为什么要进行jvm调优,jvm性能优化
如何解决写爬虫IP受阻的问题?立即使用。
JVM调优目标:使用较少的内存来实现更高的吞吐量或更低的延迟。
在程序上线前的测试或运行过程中,有时会出现一些JVM的问题,比如cpu负载高、请求延迟、tps降低等。甚至内存泄漏(垃圾收集时间越来越长,垃圾收集频率越来越高,每次垃圾收集清理的垃圾数据越来越少),导致系统崩溃。所以需要对JVM进行调优,让程序在正常运行的前提下获得更高的用户体验和运行效率。
以下是一些更重要的指标:
内存占用:程序正常运行所需的内存量。
延迟:垃圾收集导致的程序暂停时间。
吞吐量:用户程序运行时间与用户程序和垃圾收集占用的总时间的比率。
当然,像CAP原理一样,不可能同时满足一个程序的小内存占用、低延迟和高吞吐量的要求。不同的节目有不同的目标,调优时要考虑不同的方向。调优前一定要结合实际场景,有明确的优化目标,找到性能瓶颈,针对性的优化瓶颈,最后进行测试,通过各种监控工具确认调优结果是否达到目标。
JVM调优工具
(1)调优可以依赖和参考的数据包括系统运行日志、堆栈错误信息、gc日志、线程快照、堆转储快照等。
系统运行日志:系统运行日志是打印在程序代码中的日志,描述系统运行轨迹(执行方法、参数、返回值等。)在代码层面。通常,出现问题时,系统运行日志是第一个要查看的日志。
堆栈错误信息:当系统出现异常时,可以根据堆栈信息初步定位问题。比如可以根据“Java . lang . out memory error:Java heap space”判断堆内存溢出;根据“java.lang.StackOverflowError”可以判断是堆栈溢出;根据“Java . lang . out memory error:perm gen space”可以判断是方法区溢出等。
gc log:在程序启动时使用-XX: PrintgcDetails和-Xloggc:/data/jvm/gc.log记录程序运行时gc的详细过程,或者直接配置“-verbose:gc”参数将gc日志打印到控制台。通过记录的GC日志,可以分析每个内存区域GC的频率和时间,从而发现问题,有针对性地进行优化。
例如,以下GC日志:
2018-08-02t 14:39:11.560-0800:10.171:[GC[PSYoungGen:30128k-4091k(30208k)]51092k-50790k(98816k),0.0140970秒] [Times: user=0.02 sys=0.03,real=0.01秒]
2018-08-02t 14:39:11.574-0800:10.185:[全GC[PSYoungGen:4091k-0K(30208k)][ParOldGen:46698k-50669k(68608k)]50790k-50669k(98816k)[PSPermGen:2635k-2634k(21500
2018-08-02t 14:39:14.045-0800:12.656:[GC[PSYoungGen:14097k-4064k(30208k)]64766k-64536k(98816k),0.0117690秒] [Times: user=0.02 sys=0.01,real=0.01秒]
2018-08-02t 14:39:14.057-0800:12.668:[全GC[PSYoungGen:4064k-0K(30208k)][ParOldGen:60471k-401k(68608k)]64536k-401k(98816k)[pspermgen:2634k-2634k(21504k)]看第一个日志。“2018-08-02T14:39:11.560-0800”是一种UTC通用标准时间格式,精度为毫秒。参数“-XX: PrintgcDateStamps”被配置为将这个时间戳与gc日志一起打印出来,“10.171”是从JVM启动到GC发生的秒数。日志第一行开头的“[GC]表示这个GC中没有Stop-The-World,日志第二行开头的“[Full GC]表示这个GC中有Stop-The-World。所以,[GC和[Full GC]与新生代和老年无关,而与垃圾收集器的类型有关。如果直接调用System.gc(),会显示下一个“[PSYoungGen]”和“[ParOldGen”表示gc发生的区域,显示的具体名称也和垃圾收集器有关。例如,这里的[PSYoungGen]表示并行清除收集器,[ParOldGen]表示串行旧收集器。此外,串行收集器显示“[DefNew”,ParNew收集器显示“[par new]”下面的“30128K-4091K(30208K)”表示经过这次gc后,该区域的内存使用空间从30128K减少到4091K,总内存大小为30208K。每个区域gc描述后面的“51092K-50790K (98816K),0.0140970Secs”垃圾回收后,整个堆内存的内存使用空间从51092K减少到50790K,整个堆的总内存空间为98816K,gc用时0.0140970秒。
线程快照:顾名思义,根据线程快照可以看到线程在某一时刻的状态。可能存在请求超时、无限循环、死锁等情况。在系统中,可以根据线程快照进一步确定问题。通过执行虚拟机附带的“jstack pid”命令,可以转储当前进程中线程的快照信息。网上有很多更详细的使用和分析的例子。这篇文章已经写到这里很久了,就不多描述了。贴个博客参考:http://www.cnblogs.com/kongzhongqijing/articles/3630264.html.
转储快照:可以使用“-xx: heapdumpoutofmemory”和“-xx:heapdumppath=/data/JVM/Dump file . hprof”在程序内存耗尽时,以文件的形式转储当前的内存快照(也可以在程序运行的任意时刻用jmap命令直接转储内存快照),然后检查当时的内存使用情况。
(2)JVM调优工具
jps(JVM进程状态)可用于查看虚拟机启动的所有进程、执行主类的全名以及JVM启动参数。比如执行JPSTest类中的main方法时(main方法是连续执行的),执行jps -l时下面JPSTest类的pid是31354,加上-v参数也可以看到JVM启动参数。
3265
32914 sun.tools.jps
31353 org . jetbrains . jps . cmdline . launcher
31354 com . Danny . test . code . JVM . jpstest
80 使用JSTAT (JVM统计信息监控工具)监控虚拟机信息
Jstat -gc pid 500 10:每500毫秒打印一次Java堆状态(每个区域的容量、使用容量、gc时间等信息),打印10次。
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
11264.0 11264.0 11202.7 0.0 11776.0 1154.3 68608.0 36238.7 - - - - 14 0.077 7 0.049 0.126
11264.0 11264.0 11202.7 0.0 11776.0 4037.0 68608.0 36238.7 - - - - 14 0.077 7 0.049 0.126
11264.0 11264.0 11202.7 0.0 11776.0 6604.5 68608.0 36238.7 - - - - 14 0.077 7 0.049 0.126
11264.0 11264.0 11202.7 0.0 11776.0 9487.2 68608.0 36238.7 - - - - 14 0.077 7 0.049 0.126
11264.0 11264.0 0.0 0.0 11776.0 258.1 68608.0 58983.4 - - - - 15 0.082 8 0.059 0.141
11264.0 11264.0 0.0 0.0 11776.0 3076.8 68608.0 58983.4 - - - - 15 0.082 8 0.059 0.141
11264.0 11264.0 0.0 0.0 11776.0 0.0 68608.0 390.0 - - - - 16 0.084 9 0.066 0.149
11264.0 11264.0 0.0 0.0 11776.0 0.0 68608.0 390.0 - - - - 16 0.084 9 0.066 0.149
11264.0 11264.0 0.0 0.0 11776.0 258.1 68608.0 390.0 - - - - 16 0.084 9 0.066 0.149
1264.0 11264.0 0.0 0.0 11776.0 3012.8 68608.0 390.0-16 0.084 9 0.066 0.149 JSTAT还可以从其他角度监控各个区域的内存大小,监控类加载信息等。具体可参考google jstat的详细用法。
使用jmap(Java内存映射)检查堆内存信息
执行jmap -histo pid可以打印出当前堆中所有类的实例数和内存占用情况,如下:class name是每个类的类名([B是byte类型,[C是char类型,[I是int类型]),bytes是该类所有实例的内存占用情况,instances是该类的实例数:
数量#实例#字节类名
-
29274080 [B
2: 15252 1961040方法类别
3: 15252 1871400 constMethodKlass
4:18038 721520 Java . util . treemap $ Entry
5: 6182 530088
6: 11391 273384 java.lang.Long
7: 5576 267648 java.util.TreeMap
8时50分
9: 6124 146976
10:3330 133200 Java . util . linked hashmap $ Entry
1:5544 133056 javax . management . open mbean . compositedatasupport执行jmap -dump将堆内存快照转储到指定文件,例如执行jmap -dump:format=b,File=/data/JVM/dumpfile_jmap.hprof 3361可以将当前堆内存的快照转储到dump File _ jmap . hprof文件中,然后分析内存快照。
使用jconsole和JVM分析内存信息(伊甸园、幸存者、旧等各区域内存变化。).如果您正在查看远程服务器的JVM,需要添加以下参数来启动程序:
-DCOM . sun . management . JMX remote=true
-DJ ava . RMI . server . hostname=12 . 34 . 56 . 78
-DCOM . sun . management . JMX remote . port=18181
-DCOM . sun . management . JMX remote . authenticate=false
-DCOM;孙;管理;jmxremote SSL=false 下图显示了jconsole接口。overview选项可以观察堆内存使用情况、线程数量、类加载数量和CPU利用率;内存选项可以查看堆中每个区域的内存使用情况以及左下角的详细描述(内存大小、GC情况等。);Thread选项可以查看当前JVM加载的线程,查看每个线程的堆栈信息,检测死锁;VM概述了虚拟机的各种详细参数。(jconsole函数演示)
下图是jvisualvm的界面,比jconsole稍微丰富一点,但是大部分功能都需要插件。Overview类似于jconsole的VM overview,描述了jvm的详细参数和程序启动参数;显示的界面类似于jconsole的概览界面(CPU、堆/方法区、类加载、线程);Thread类似于jconsole的线程接口;采样器可以显示当前占用内存的类的排序列表及其实例的数量;Visual GC可以更丰富的显示每个区域的当前内存占用大小和历史信息(下图)。(jvisualvm函数演示)
分析反应器的转储快照
如上所述,“-xx: heapdumpoutofmemory”参数的配置可以在程序溢出时转储当前内存快照,也可以使用jmap命令随时转储当前内存状态的快照信息。转储的内存快照通常是一个带有。hprof作为后缀。
可以直接使用jhat(JVM堆分析工具)命令来分析内存快照。它的本质其实是嵌入了一个微型服务器,你可以通过浏览器分析对应的内存快照。例如,执行jhat-port 9810-j-xmx4g/data/JVM/dump file _ jmap . hprof意味着使用端口9810启动jhat中嵌入的服务器:
正在读取/Users/Danny hoo/data/JVM/dump file _ jmap . hprof.
转储文件已创建Fri时间2018年8月3日15:48:27
快照读取,解析.
解析276472个对象.
追参考文献,预计55点.......
消除重复引用.......
快照已解决。
在端口9810上启动HTTP服务器
服务器就绪。在控制台中,您可以看到服务器已经启动。当你访问http://127.0.0.1:9810/,可以在快照中看到分析各个类的结果(界面略低)。下图显示了我随机选择的一个类的信息,包括它的父类、加载这个类的类加载器以及占用的空间量。下面还有这个类的每个实例(引用)及其内存。
JVM也可以分析内存快照。在jvisualvm菜单中,选择堆内存快照,快照中的信息将显示在图形界面中。如下,主要可以查看每个类占用的空间,实例的数量,实例的细节等。
有很多分析内存快照的第三方工具,比如eclipse mat,比JVM更专业。它可以检查每个类和它对应的实例所占用的空间和数量,查询对象之间的调用链,检查一个实例和GC根之间的链等等。可以在eclipse中安装mat插件,也可以下载独立版(http://www . eclipse . org/mat/downloads . PHP)。我在mac上装了之后就卡了~下面是windows上的截图(MAT功能演示):
(3)JVM调优经验
JVM配置方面,一般可以使用默认配置(一些基本的初始参数可以保证一般应用稳定运行)。在测试中,可以根据系统运行状态(会话并发、会话时间等)进行合理的调整。),结合gc日志、内存监控、使用的垃圾收集器等。老年时内存太小,可能会造成频繁的全GC,内存太大,全GC时间会特别长。
那么JVM最合适的配置是多大,比如新一代和老一代?答案是不一定。调音就是寻找答案的过程。在物理内存固定的情况下,新生代的设置越大,老龄越小,全GC频率越高,但全GC时间越短。相反,新生代的设定越小,老龄越大,全GC的频率越低,但每次全GC花费的时间越多。建议如下:
-Xms和-Xmx的值设置为相等,堆大小默认为-Xms指定的大小。当默认空闲堆内存小于40%时,JVM会将堆扩展到-Xmx指定的大小;当空闲堆内存大于70%时,JVM会将堆减小到-Xms指定的大小。如果在完全GC后内存需求不能满足,它将被动态调整。这个阶段消耗更多的资源。
在新一代中尽量把它设置的大一些,这样对象在新一代中可以活的更久。每次Minor GC都收集尽可能多的垃圾对象,以防止或延迟对象进入老年的机会,从而降低应用程序中完全GC的频率。
如果在旧时代使用CMS收集器,新一代不用太大,因为CMS的并行收集速度也很快,收集过程耗时的并发标记和并发清理阶段可以和用户线程并发执行。
方法大小的设置,1.6之前需要考虑系统运行时动态添加的常量和静态变量,1.7只需要能够容纳启动和后期动态加载的类信息即可。
代码实现方面,存在程序等待、内存泄漏等性能问题。除了JVM配置中可能出现的问题,代码实现也有很大的关系:
避免创建过大的对象和数组:过大的对象或数组会在新一代空间不足时直接进入旧时代。如果是短命的大对象,会提前启动全GC。
避免同时加载大量数据,比如一次性从数据库中取出大量数据,或者一次性从Excel中读取大量记录。可以批量阅读,使用后尽快清除参考文献。
当集合中存在对对象的引用时,在这些对象用完之后,应尽快清空集合中的引用,并尽快回收这些无用的对象,避免进入老年期。
可以在适当的场景下(比如缓存实现)使用软引用和弱引用,比如软引用给ObjectA分配实例:soft reference ObjectA=newsoftreference();在内存溢出发生之前,objectA将被包含在二次恢复的恢复范围内。如果这次恢复没有足够的内存,将引发内存溢出异常。
避免无限循环。无限循环生成后,循环中可能会重复生成大量实例,导致内存空间很快被填满。
尽量避免等待外部资源(数据库、网络、设备资源等。)长久,缩短对象的生命周期,避免步入老年。如果不能及时返回结果,可以适当采用异步处理。
(4)记录4)JVM故障排除的案例
JVM服务问题疑难解答https://blog.csdn.net/jacin1/article/details/44837595
难忘的故障排除,频繁的全气相色谱过程,http://caogen81.iteye.com/blog/1513345
在线FullGC经常检查https://blog.csdn.net/wilsonpeng3/article/details/70064336/.
[JVM]https://www.cnblogs.com/Dhouse/p/7839810.html在线应用程序故障排除
JVM中一个全GC故障排除过程的http://iamzhongyong.iteye.com/blog/1830265
JVM内存溢出https://blog.csdn.net/nielinqi520/article/details/78455614导致CPU占用率高的故障排除案例
检查java内存泄漏的一个案例,https://blog.csdn.net/aasgis6u/article/details/54928744
(5)常用JVM参数参考:
本文来自美国,
参数 | 说明 | 实例 |
---|---|---|
-Xms | 初始堆大小,默认物理内存的1/64 | -Xms512M |
-Xmx | 最大堆大小,默认物理内存的1/4 | -Xms2G |
-Xmn | 新生代内存大小,官方推荐为整个堆的3/8 | -Xmn512M |
-Xss | 线程堆栈大小,jdk1.5及之后默认1M,之前默认256k | -Xss512k |
-XX:NewRatio=n | 设置新生代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4 | -XX:NewRatio=3 |
-XX:SurvivorRatio=n | 年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:8,表示Eden:Survivor=8:1:1,一个Survivor区占整个年轻代的1/8 | -XX:SurvivorRatio=8 |
-XX:PermSize=n | 永久代初始值,默认为物理内存的1/64 | -XX:PermSize=128M |
-XX:MaxPermSize=n | 永久代最大值,默认为物理内存的1/4 | -XX:MaxPermSize=256M |
-verbose:class | 在控制台打印类加载信息 | |
-verbose:gc | 在控制台打印垃圾回收日志 | |
-XX:+PrintGC | 打印GC日志,内容简单 | |
-XX:+PrintGCDetails | 打印GC日志,内容详细 | |
-XX:+PrintGCDateStamps | 在GC日志中添加时间戳 | |
-Xloggc:filename | 指定gc日志路径 | -Xloggc:/data/jvm/gc.log |
-XX:+UseSerialGC | 年轻代设置串行收集器Serial | |
-XX:+UseParallelGC | 年轻代设置并行收集器Parallel Scavenge | |
-XX:ParallelGCThreads=n | 设置Parallel Scavenge收集时使用的CPU数。并行收集线程数。 | -XX:ParallelGCThreads=4 |
-XX:MaxGCPauseMillis=n | 设置Parallel Scavenge回收的最大时间(毫秒) | -XX:MaxGCPauseMillis=100 |
-XX:GCTimeRatio=n | 设置Parallel Scavenge垃圾回收时间占程序运行时间的百分比。公式为1/(1+n) | -XX:GCTimeRatio=19 |
-XX:+UseParallelOldGC | 设置老年代为并行收集器ParallelOld收集器 | |
-XX:+UseConcMarkSweepGC | 设置老年代并发收集器CMS | |
-XX:+CMSIncrementalMode | 设置CMS收集器为增量模式,适用于单CPU情况。 |
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。