每日一题之 JVM-03

点击上方 Java后端,选择 设为星标

优质文章,及时送达


JVM性能监控

  1. JDK的命令行工具

  • jps(虚拟机进程状况工具):jps可以列出正在运行的虚拟机进程,并显示虚拟机执行主类(Main Class,main()函数所在的类)名称 以及这些进程的本地虚拟机唯一ID(Local Virtual Machine Identifier,LVMID)。

  • jstat(虚拟机统计信息监视工具):jstat是用于监视虚拟机各种运行状态信息的命令行工 具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。

  • jinfo(Java配置信息工具):jinfo的作用是实时地查看和调整虚拟机各项参数。

  • jmap(Java内存映像工具):命令用于生成堆转储快照(一般称为heapdump或dump文 件)。如果不使用jmap命令,要想获取Java堆转储快照,还有一些比较“暴力”的手段:譬如 在第2章中用过的-XX:+HeapDumpOnOutOfMemoryError参数,可以让虚拟机在OOM异常出 现之后自动生成dump文件。jmap的作用并不仅仅是为了获取dump文件,它还可以查询finalize执行队列、Java堆和永 久代的详细信息,如空间使用率、当前用的是哪种收集器等。

  • jhat(虚拟机堆转储快照分析工具):jhat命令与jmap搭配使用,来分析jmap生成的堆 转储快照。jhat内置了一个微型的HTTP/HTML服务器,生成dump文件的分析结果后,可以在 浏览器中查看。

  • jstack(Java堆栈跟踪工具):jstack命令用于生成虚拟机当前时刻的线程快照。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈 的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循 环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的常见原因。线程出现停顿 的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做些 什么事情,或者等待着什么资源。

  • JDK的可视化工具

    • JConsole

    • VisualVM

    JVM常见参数

    1. -Xms20M:表示设置JVM启动内存的最小值为20M,必须以M为单位

    2. -Xmx20M:表示设置JVM启动内存的最大值为20M,必须以M为单位。将-Xmx和-Xms设置为一样可以避免JVM内存自动扩展。大的项目-Xmx和-Xms一般都要设置到10G、20G甚至还要高

    3. -verbose:gc:表示输出虚拟机中GC的详细情况

    4. -Xss128k:表示可以设置虚拟机栈的大小为128k

    5. -Xoss128k:表示设置本地方法栈的大小为128k。不过HotSpot并不区分虚拟机栈和本地方法栈,因此对于HotSpot来说这个参数是无效的

    6. -XX:PermSize=10M:表示JVM初始分配的永久代(方法区)的容量,必须以M为单位

    7. -XX:MaxPermSize=10M:表示JVM允许分配的永久代(方法区)的最大容量,必须以M为单位,大部分情况下这个参数默认为64M

    8. -Xnoclassgc:表示关闭JVM对类的垃圾回收

    9. -XX:+TraceClassLoading表示查看类的加载信息

    10. -XX:+TraceClassUnLoading:表示查看类的卸载信息

    11. -XX:NewRatio=4:表示设置年轻代(包括Eden和两个Survivor区)/老年代 的大小比值为1:4,这意味着年轻代占整个堆的1/5

    12. -XX:SurvivorRatio=8:表示设置2个Survivor区:1个Eden区的大小比值为2:8,这意味着Survivor区占整个年轻代的1/5,这个参数默认为8

    13. -Xmn20M:表示设置年轻代的大小为20M

    14. -XX:+HeapDumpOnOutOfMemoryError:表示可以让虚拟机在出现内存溢出异常时Dump出当前的堆内存转储快照

    15. -XX:+UseG1GC:表示让JVM使用G1垃圾收集器

    16. -XX:+PrintGCDetails:表示在控制台上打印出GC具体细节

    17. -XX:+PrintGC:表示在控制台上打印出GC信息

    18. -XX:PretenureSizeThreshold=3145728:表示对象大于3145728(3M)时直接进入老年代分配,这里只能以字节作为单位

    19. -XX:MaxTenuringThreshold=1:表示对象年龄大于1,自动进入老年代,如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代的存活时间,增加在年轻代被回收的概率。

    20. -XX:CompileThreshold=1000:表示一个方法被调用1000次之后,会被认为是热点代码,并触发即时编译

    21. -XX:+PrintHeapAtGC:表示可以看到每次GC前后堆内存布局

    22. -XX:+PrintTLAB:表示可以看到TLAB的使用情况

    23. -XX:+UseSpining:开启自旋锁

    24. -XX:PreBlockSpin:更改自旋锁的自旋次数,使用这个参数必须先开启自旋锁

    25. -XX:+UseSerialGC:表示使用jvm的串行垃圾回收机制,该机制适用于单核cpu的环境下

    26. -XX:+UseParallelGC:表示使用jvm的并行垃圾回收机制,该机制适合用于多cpu机制,同时对响应时间无强硬要求的环境下,使用-XX:ParallelGCThreads=设置并行垃圾回收的线程数,此值可以设置与机器处理器数量相等。

    27. -XX:+UseParallelOldGC:表示年老代使用并行的垃圾回收机制

    28. -XX:+UseConcMarkSweepGC:表示使用并发模式的垃圾回收机制,该模式适用于对响应时间要求高,具有多cpu的环境下

    29. -XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。

    30. -XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低响应时间或者收集频率等,此值建议使用并行收集器时,一直打开

    JVM调优目标-何时需要做jvm调优

    1. heap 内存(老年代)持续上涨达到设置的最大内存值;

    2. Full GC 次数频繁;

    3. GC 停顿时间过长(超过1秒);

    4. 应用出现OutOfMemory 等内存异常;

    5. 应用中有使用本地缓存且占用大量内存空间;

    6. 系统吞吐量与响应性能不高或下降。

    JVM调优实战

    1. Major GC和Minor GC频繁

      首先优化Minor GC频繁问题。通常情况下,由于新生代空间较小,Eden区很快被填满,就会导致频繁Minor GC,因此可以通过增大新生代空间来降低Minor GC的频率。例如在相同的内存分配率的前提下,新生代中的Eden区增加一倍,Minor GC的次数就会减少一半。

      扩容Eden区虽然可以减少Minor GC的次数,但会增加单次Minor GC时间么?扩容后,Minor GC时增加了T1(扫描时间),但省去T2(复制对象)的时间,更重要的是对于虚拟机来说,复制对象的成本要远高于扫描成本,所以,单次Minor GC时间更多取决于GC后存活对象的数量,而非Eden区的大小。因此如果堆中短期对象很多,那么扩容新生代,单次Minor GC时间不会显著增加。

    2. 请求高峰期发生GC,导致服务可用性下降

      由于跨代引用的存在,CMS在Remark阶段必须扫描整个堆,同时为了避免扫描时新生代有很多对象,增加了可中断的预清理阶段用来等待Minor GC的发生。只是该阶段有时间限制,如果超时等不到Minor GC,Remark时新生代仍然有很多对象,我们的调优策略是,通过参数强制Remark前进行一次Minor GC,从而降低Remark阶段的时间。另外,类似的JVM是如何避免Minor GC时扫描全堆的?经过统计信息显示,老年代持有新生代对象引用的情况不足1%,根据这一特性JVM引入了卡表(card table)来实现这一目的。卡表的具体策略是将老年代的空间分成大小为512B的若干张卡(card)。卡表本身是单字节数组,数组中的每个元素对应着一张卡,当发生老年代引用新生代时,虚拟机将该卡对应的卡表元素设置为适当的值。如上图所示,卡表3被标记为脏(卡表还有另外的作用,标识并发标记阶段哪些块被修改过),之后Minor GC时通过扫描卡表就可以很快的识别哪些卡中存在老年代指向新生代的引用。这样虚拟机通过空间换时间的方式,避免了全堆扫描。

    3. STW过长的GC

      对于性能要求很高的服务,建议将MaxPermSize和MinPermSize设置成一致(JDK8开始,Perm区完全消失,转而使用元空间。而元空间是直接存在内存中,不在JVM中),Xms和Xmx也设置为相同,这样可以减少内存自动扩容和收缩带来的性能损失。虚拟机启动的时候就会把参数中所设定的内存全部化为私有,即使扩容前有一部分内存不会被用户代码用到,这部分内存在虚拟机中被标识为虚拟内存,也不会交给其他进程使用。

    4. 外部命令导致系统缓慢

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

    5. 由Windows虚拟内存导致的长时间停顿

      一个带心跳检测功能的GUI桌面程序,每15秒会发送一次心跳检测信号,如果对方30秒以内都没有信号返回,那就认为和对方程序的连接已经断开。程序上线后发现心跳 检测有误报的概率,查询日志发现误报的原因是程序会偶尔出现间隔约一分钟左右的时间完 全无日志输出,处于停顿状态。

      因为是桌面程序,所需的内存并不大(-Xmx256m),所以开始并没有想到是GC导致的 程序停顿,但是加入参数-XX:+PrintGCApplicationStoppedTime-XX:+PrintGCDateStamps- Xloggc:gclog.log后,从GC日志文件中确认了停顿确实是由GC导致的,大部分GC时间都控 制在100毫秒以内,但偶尔就会出现一次接近1分钟的GC。

      从GC日志中找到长时间停顿的具体日志信息(添加了-XX:+PrintReferenceGC参数), 找到的日志片段如下所示。从日志中可以看出,真正执行GC动作的时间不是很长,但从准 备开始GC,到真正开始GC之间所消耗的时间却占了绝大部分。

      除GC日志之外,还观察到这个GUI程序内存变化的一个特点,当它最小化的时候,资源 管理中显示的占用内存大幅度减小,但是虚拟内存则没有变化,因此怀疑程序在最小化时它的工作内存被自动交换到磁盘的页面文件之中了,这样发生GC时就有可能因为恢复页面文件的操作而导致不正常的GC停顿。在Java的GUI程序中要避免这种现象,可以 加入参数“-Dsun.awt.keepWorkingSetOnMinimize=true”来解决。

    作者:yuanguangxin

    链接:https://github.com/yuanguangxin/LeetCode

    - END -

    最近整理一份面试资料《Java技术栈学习手册》,覆盖了Java技术、面试题精选、Spring全家桶、Nginx、SSM、微服务、数据库、数据结构、架构等等。
    获取方式:点“ 在看,关注公众号 Java后端 并回复 777 领取,更多内容陆续奉上。
    推荐阅读 
    1. 每日一题之 ZooKeeper
    2. 每日一题之 Redis
    3. 每日一题之 MySQL
    
    4. 每日一题之 JVM-01
    
    5. 每日一题之 JVM-02
    
    
    喜欢文章,点个在看 
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值