JVM学习(虚拟机性能监控工具以及基础调优)

虚拟机性能监控工具以及基础调优

经过了前面给大家有关于JVM垃圾回收、内存分配等各种方面的知识,今天我们来初步接触JVM的调优,当然也只是基本的一些调优的知识以及一些案例,仅供大家参考学习,加深一点印象。在实际环境中的调优还是要看当时的情况下来进行调优,希望大家看完之后能够有所收获,有所进步。

1、监控工具

众所周知,Java的JDK的bin目录下有“java.exe”、“javac.exe”,这两个命令行工具,但我在没仔细接触之前也不知道,它的bin目录下还有这么多能够配合JDK使用的小工具,下面给大家介绍一些好用的易用的。

1.1、JPS

JPS(JVM Process Status Tool)是使用频率最高的JDK命令行工具,jps可以查看当前正在进行的虚拟机进程,并显示函数所在的类。如下图所示。在我们想查看本机所有运行的java进程时,非常方便先打JPS便能看到进程ID。然后根据拿到的对应PID做自己想做的事情。
jps命令格式
jps [ options ] [ hostid ]
在这里插入图片描述

1.2、jstat

jstat(JVM Statistics Monitoring Tool)是用来监控虚拟机各种运行状态信息的命令行工具。
它能够显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。没有提供GUI图形界面,只提供了纯文本控制台环境的服务器。
在这里博主写了一个多线程的抢票系统来持续运行,主要是为了给jstat的应用空间,
在这里我们jps搭配jstat -gc就可以一起使用来看实时的5272 Sell主类的端口的JVM垃圾收集
以下是介绍-gc查看垃圾收集的参数的介绍
S0C:年轻代中第一个survivor(幸存区)的容量 (字节)
S1C:年轻代中第二个survivor(幸存区)的容量 (字节)
S0U:年轻代中第一个survivor(幸存区)目前已使用空间 (字节)
S1U:年轻代中第二个survivor(幸存区)目前已使用空间 (字节)
EC:年轻代中Eden(新生代)的容量 (字节)
EU:年轻代中Eden(新生代)目前已使用空间 (字节)
OC:Old代的容量 (字节)
OU:Old代目前已使用空间 (字节)
PC:Perm(持久代)的容量 (字节)
PU:Perm(持久代)目前已使用空间 (字节)
YGC:从应用程序启动到采样时年轻代中gc次数
YGCT:从应用程序启动到采样时年轻代中gc所用时间(s)
FGC:从应用程序启动到采样时old代(全gc)gc次数
FGCT:从应用程序启动到采样时old代(全gc)gc所用时间(s)
GCT:从应用程序启动到采样时gc用的总时间(s)
在这里插入图片描述

1.3、jmap

jmap(Memory Map for Java)命令用于生成堆转储快照(一般称为headdump或dump文件)。如果想获取Java堆转储快照就可以使用jmap方法,另外还有一个在虚拟机中添加VM参数-XX:+HeapDumpOnOutOfMemoryError参数,可以让虚拟机在OOM(内存溢出)异常出现之后自动生成dump文件,通过-XX:+HeapDumpOnCtrlBreak参数则可以使用[Ctrl]+[Break]键让虚拟机生成dump文件,又或者在Linux系统下通过kill -x 命令发送进程退出信号吓唬虚拟机,也能拿到dump文件
jmap 命令格式:
jmap [ option ] vmid

1.4、可视化工具VIsualVM

打开控制台 在控制台输入jvisualvm按回车出现下图命令我们就可以进入到VisualVM的界面
在这里插入图片描述
VisualVM是极其强大的监控工具,可以看到多方面Java的信息以及CPU、内存等方面。
更全面更详细的使用就交给大家自己去探索了。还是比较简单入手
在这里插入图片描述

2、基础调优

下面给大家说一些简单的调优,以及出现的一些例子调优该怎么面对。

2.1、如何看懂GC垃圾日志

如下图所示,我们看到的分为GC和Full GC,GC就是普通的在新生代的垃圾回收动作,FullGC就是对全局包括老年代的全局垃圾回收。
先从上面说起,PSYoungGen,回收前是975K的占用内存,经过回收后占用的为496K(总共的新生代大小是4600K),后面的975也是表示先前全部JVM可用内存中占了975K,垃圾回收后还有656K(总共的堆内存区域为9728K),后来是这次新生代的垃圾回收的时间。哲理的PS对应的垃圾收集器是Parallel Scavenge。
再看下面的Full GC(也就是System.gc()我主动发起的STW)可见老年代回收机是ParOld,后面参数跟上面介绍一样。
Metaspace是元空间数据,括号内也与上面机制一样。
Times括号中的是:
user=0.02代表是进程执行用户态代码消耗的时间,
sys=0.00代表是进程在内核态消耗的 CPU 时间,
real=0.02代表的是总的CPU 的时间为0.01秒。
那么为什么总时间小于user+sys呢,这主要是因为日志时间是从 JVM 中获得的,而这个 JVM 在多核的处理器上被配置了多个 GC 线程,由于多个线程并行地执行 GC,因此整个 GC 工作被这些线程共享。
在这里插入图片描述

2.2、堆外内存导致的溢出错误

例如,一个学校的小型项目,基于B/S的电子考试系统。硬件为一台普通PC机,Core i5 CPU ,4GB内存,运行32位Windows操作系统。
测试期间发现服务端不定时抛出内存溢出异常,可是服务器不一定每次都会出现异常,但假如在考试期间崩溃一次,那估计正常电子考试都会乱套,网站管理员尝试把堆开到最大,而32位系统最多到1,6GB就无法再大了,并且大了也改变不了本质,抛出内存溢出的更加频繁了。这时加入-XX:+HeapDumpOnOutOfMemoryError,却还是没有反应,这时看到了异常堆栈抛出内存异常的错误。这时就知道了问题所在,错误在DirectMemory内存上,因为服务器使用32位Windows平台的限制是2GB,其中划了1.6GB给Java堆,那么DirectMemory最大最大也只能有将近0.4GB中一部分,那么垃圾回收进行的时候,虽然虚拟机会对DirectMemory进行回收,但是却不能像新生代老年代那么满了就回收,因为没有专门盯着它的垃圾收集器,只能等待老年代满了之后蹭一蹭Full GC,所以这才只能一直在catch里抛出异常,并且这个系统使用了大量的NIO操作,很费DirectMemory内存,那么这时候我们就需要学会在每次抛出错误异常的时候,我们在代码中添加一次Full GC来主动帮它解决方法区内存溢出的问题。

2.3、外部命令导致系统缓慢

当有系统每个用户请求的处理都需要执行一个外部shell脚本来获得系统的一些信息的时候,执行脚本是通过Java的Runtime.getRuntime().exec()方法来调用的。这种调用方式确实可以达到目的,但是它在虚拟机中是非常消耗资源的操作,即使外部命令本身很快就能执行完毕,但频繁调用和创建进程的开销是非常可观。Java虚拟机执行这个命令会是这样:克隆一个和当前虚拟机拥有一样的环境变量的进程,再用这个新的进程去执行外部命令,最后再退出这个进程。如果频繁执行这个操作,系统的消耗会很大,不仅是CPU,内存负担也很重。
用户根据建议去掉了Shell脚本执行的语句,改为使用Java的API去获取这些信息后,很快恢复了正常

2.4、服务器JVM进程崩溃

例如,一个基于B/S的MIS系统,硬件为两台2个CPU、8GX内存的HP系统,服务器是WebLogic9.2。正常运行一段时间后,最近发现在运行期间频繁出现集群节点的虚拟机进程自动关闭的现象。留下了一个hs_err_pid###.log文件后,进程就消失了,两台虚拟机里的每个节点都出现过进程崩溃的现象。
由于MIS系统的用户多,待办事项变化很快,为了不被OA系统速度拖累,使用了异步的方式调用Web服务,但由于两边服务速度的完全不对等,时间越长积累了越多Web服务没有调用完成,导致等待的线程和Socket连接越来越多,虚拟机最终崩溃。通知OA门户方修复无法使用的集成接口,并将异步调用改为生产者/消费者模式的消息队列实现后,系统恢复。

2.5、不恰当数据结构内存占用过大

有一个后台RPC服务器,使用64位虚拟机,内存配置为-Xms4g,-Xmx8g,-Xmn1g,使用ParNew+CMS的收集器组合。平时对外服务的MinorGC时间约在30ms以内完全可以接收,但业务上每次需要加载一个约80MB的数据文件到内存分析,这些数据会在内存中形成超过100万个HashMap<Long,Long>Entry,这段时间内MinorGC会造成超过500ms的停顿这就很难接受了。
在分析数据文件期间,800MB的Eden空间很快被填满从而引发GC,但Minor GC之后,新生代中绝大部分是存活的,我们知道ParNew使用的是赋值算法,这个算法最好基于的是对象都是朝生夕灭的特性,如果存活对象国对,这样就是一个沉重的负担,导致GC暂停时间明显边长,所以仅从GC调优的角度去解决这个问题,我们可以考虑将新生代中存活的对象在第一次MinorGC后立即进入老年代,等到FullGC再清除他们,这种措施只能治标不能治本,因为HashMap<Long,Long>这种结构存储数据文件空间效率太低。要想治本,必须从源代码进行修改才行。换一种方式存储

3、运行速度调优

3.1、JDK带来的免费提升

调优的第一步,应该在可控的范围内对自己的JDK进行版本提升,因为开发商总是会在发布新的JDK的时候宣布JVM的运行速度比上一版本的JVM的运行速度有了提升。实际上也确实有一定的提升,所以这也是一种免费的提升。

3.2、编译时间和类加载时间的优化

Jav中有个字节码验证,在加载的时候也会很导致我们的加载很慢,我们可以通过-Xverify:none将字节码验证过程取消掉,在加载速度上有短暂的提升。当然Java中也有JIT(即时热点代码编译器)来从一定程度上优化代码加载。

3.3、根据情况调整内存控制GC频率

三大非用户时间(编译时间、类加载时间、GC时间)内还有GC时间没有进行调整,我们在设置内存时候可以设置最小堆内存最大堆内存等内存分配空间,在分配新生代以及老生代的时代我们也能自己根据实际调优情况来进行调整,不能过于死板的使用默认的虚拟机参数,就比如有大对象经常存储的话,你的堆内Suvivor区内存不够,每次都让大对象直接进入了老年代,这样老年代很容易就满了,就会发生FullGC,对整个项目来说这个都是不好的消息,所以这不妨来提升一下Survivor区的内存,使这种朝生夕灭的大对象进入新生代并且在垃圾收集后消失。

3.4、灵活选择垃圾收集器

比如用到CPU计算程序比较多的时候,CPU占用情况很高的时候,我们就可以考虑使用吞吐量优先的垃圾收集器,Parallel Scavenge收集器就是专门针对这一吞吐量来用的,假如用户交互量较大,需要很多的交互行为,这时候我们就应该用CMS垃圾收集器,针对不同情况用不同的垃圾收集器,我们要学会判断我们即将运行的环境属于什么样,用什么样的垃圾收集器。

4、调优参数

-Xverify:none 取消字节验证
-Xms512m 堆大小最小512M
-Xmx512m 堆大小最大512M
-Xmn128m 新生代大小128M(默认eden/to/from=8:1:1)
-XX:PermSize=96m 方法区96M
-XX:MaxPermSize=96m 方法区最大96M
-XX:+UserParNewGC 新生代用ParNew垃圾收集器
…………还有很多可用的虚拟机参数,比如eden/to这些也可以设置,大家有兴趣或者要用到的时候再去找就行!

总结

今天给大家介绍的就是这些监控工具以及基础调优的一些知识,希望大家能够从中有自己的收获,能够将其运用到自己的实际项目开发中去。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值