JVM 下篇:性能监控与调优

文章目录

  • 性能优化三部曲
    • 第1步(发现问题):性能监控
      • 何时需要性能监控
    • 第2步(排查问题):性能分析
    • 第3步(解决问题):性能调优
    • 性能评价/测试指标
  • JVM监控及诊断工具命令行
    • jps
    • jstat
    • jinfo
    • jmap
      • 导出内存镜像文件
      • 显示堆内存相关信息
      • 其它作用
    • jhat
    • jstack
    • jcmd
    • jstatd
  • JVM监控及诊断工具GUI
    • Jconsole
    • Visual VM
    • eclipse MAT
      • 获取dump文件
      • 分析堆dump文件
      • 浅堆与深堆
      • 对象实际大小
    • 内存泄漏
      • 内存泄漏与内存溢出的关系
      • 泄漏的分类
      • Java中内存泄漏的8种情况
        • 静态集合类
        • 单例模式
        • 内部类持有外部类
        • 各种连接,如数据库连接、网络连接和IO连接等
        • 变量不合理的作用
        • 改变哈希值
        • 缓存泄漏
        • 监听器和回调
        • 案例
    • Jprofiler
      • 概述
      • 数据采集
      • 内存视图
      • Heap Walker
      • 线程视图
      • 监视器&锁 Monitors& locks
    • Arthas
      • 启动
      • 退出
      • 相关诊断指令
        • 基础指令
        • JVM相关
        • class/ classloader相关
        • monitor/ watch/ trace相关
        • 其它
    • JMC
      • Java Flight Recorder
        • 事件类型
        • 启动方式
    • Flame Graphs(火焰图)
  • JVM运行时参数
    • JVM参数选项类型
      • 标准参数选项
      • -X 参数选项
      • -XX参数选项(重要)
    • 添加JVM参数选项
    • 常用的JVM参数选项
      • 打印设置的XX选项及值
      • 堆、栈、方法区等内存大小设置
        • 堆内存
        • 方法区
        • 直接内存
      • OutofMemory相关的选项
      • 垃圾收集器相关选项
        • 查看默认垃圾收集器
        • Serial回收器
        • ParNew回收器
        • Parallel回收器
        • CMS回收器
        • G1回收器
        • 怎么选择垃圾回收器
        • GC日志相关选项
          • 常用参数
          • 其他参数
      • 其他参数
    • 通过Java代码获取JVM参数
  • 分析 GC 日志
    • GC日志参数
    • GC日志格式
      • GC分类
      • GC 日志分类
        • Minor GC 日志
        • FulI GC 日志
      • GC 日志结构分析
        • 垃圾收集器
        • GC前后情况
        • GC时间
      • 日志分析
        • Minor GC 日志解析
        • Full GC 日志解析
    • GC日志分析工具
      • GCeasy
      • GCViewer

性能优化三部曲

第1步(发现问题):性能监控

一种以非强行或者入侵方式收集或査看应用运营性能数据的活动监控通常是指一种在生产、质量评估或者开发环境下实施的带有预防或主动性的活动

当应用相关干系人提出性能问题却没有提供足够多的线索时,首先我们需要进行性能监控,随后是性能分析。

何时需要性能监控

  • GC频繁
  • CPU Load过高
  • OOM
  • 内存泄漏
  • 死锁
  • 程序响应时间较长

第2步(排查问题):性能分析

一种以侵入方式收集运行性能数据的活动,它会影响应用的吞吐量或响应性

性能分析是针对性能问题的答复结果,关注的范围通常比性能监控更加集中。

性能分析很少在生产环境下进行,通常是在质量评估、系统测试或者开发环境下进行,是性能监控之后的步骤。

  • 打印GC日志,通过 GCviewer或者 http://gceasy.io来分析日志信息
  • 灵活运用 命令行工具, jstack,jmap, jinfo等
  • dump出堆文件,使用内存分析工具分析文件
  • 便用阿里 Arthas,或 jconsole, JVisualVM来实时查看JVM状态
  • jstack查看堆栈信息

第3步(解决问题):性能调优

一种为改善应用响应性或昋吐量而更改参数、源代码、属性配置的活动,性能调优。是在性能监控、性能分析之后的活动

  • 适当增加内存,根据业务背景选择垃圾回收器
  • 优化代码,控制内存使用
  • 增加机器,分散节点压力
  • 合理设置线程池线程数量
  • 使用中间件提高程序效率,比如缓存,消息队列等

性能评价/测试指标

  1. 停顿时间(或响应时间)

    1. 暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间。设置: -XX: MaxGCPauseMillis
  2. 吞吐量

    1. 对单位时间内完成的工作量请求的量度
    2. 在GC中:运行用户代码的时间占总运行时间的比例(总运行时间:程序的运行时间+内存回收的时间)
      吞吐量为:1-1/(1+n) -XX: GCTimeRatio=n
  3. 并发数

    同一时刻,对服务器有实际交互的请求数

  4. 内存占用
    Java堆区所占的内存大小

JVM监控及诊断工具命令行

可能造成Java应用出现性能问题的因素非常多,例如线程控制、磁盘读写、数据库访问
网络I/O、垃圾收集等。想要定位这些问题,一款优秀的性能诊断工具必不可少。

体会1:使用数据说明问题,使用知识分析问题,使用工具处理问题

体会2:无监控、不调优!

jps

查看正在运行的ava进程

显示指定系统内所有的 HotSpot虚拟机进程(查看虚拟机进程信息),可用于查询正在运行的虚拟机进程。

说明:对于本地虚拟机进程来说,进程的本地虚拟机ID与操作系统的进程ID是一致的,是唯一的。

它的基本使用语法为:

jps [options] [hostid]

我们还可以通过追加参数,来打印额外的信息

  • -q:仅仅显示LVID(local virtual machine id),即本地虚拟机唯一id。不显示主类的名称等
  • -l:输出应用程序主类的全类名或如果进程执行的是jar包,则输出jar完整路径
  • -m:输出虚拟机进程启动时传递给主类main()的参数
  • -v:列出虚拟机进程启动时的JVM参数。比如:-Xms20m -Xmx50m是启动程序指定的jvm参数。
    说明:以上参数可以综合使用。

补充:
如果某Java进程关闭了默认开启的 UsePerfData参数(即使用参数-XX:-UsePerfData),那么jps命令(以及jstat)将无法探知该Java进程。

jstat

查看JVM统计信息

jstat( JVM Statistics Monitoring Tool):用于监视虚拟机各种运行状态信息的命令行工具。

它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。

在没有GUI图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。常用于检测垃圾回收问题以及内存泄漏问题。

基本使用语法 :

JVM 下篇:性能监控与调优
查看
  • jinfo -sysprops PID 可以查看由 System. getPrrperties()取得的参数
  • jinfo -flags PID 查看曾经赋过值的一些参数
  • jinfo -flag 具体参数PID 查看某个Java进程的具体参数的值

修改

Jinfo不仅可以查看运行时某一个Java虚拟机参数的实际取值,甚至可以在运行时修改部分数,并使之立即生效。
但是,并非所有参数都支持动态修改。参数只有被标记为 manageable的flag可以被实时修改

其实,这个修改能力是极其有限的。
可以查看被标记为 manageable的参数

Java -XX: +PrintFlagsFinal -version |grep manageable

JVM 下篇:性能监控与调优
说明:这些参数和linux下输入显示的命令多少会有不同,包括也受jdk版本的影响
JVM 下篇:性能监控与调优

jstack

打印JVM中线程快照

线程快照就是当前虚拟机内指定进程的每一条线程正在执行的方法堆栈的集合

生成线程快照的作用:可用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请
外部资源导致的长时间等待等问题。这些都是导致线程长时间停顿的常见原因。当线程出现亻
顿时,就可以用 jstack显示各个线程调用的堆栈情况。

在 thread dump中,要留意下面几种状态

  • 死锁, Deadlock(重点关注)
  • 等待资源, Waiting on condition(重点关注)
  • 等待获取监视器, Waiting on monitor entry(重点关注)
  • 阻塞,Blocked(重点关注)
  • 执行中, Runnable
  • 暂停, Suspended

基本使用语法 :
jstack option pid

stack管理远程进程的话,需要在远程程序的启动参数中增加:
-Djava. rmi .server .hostname=…
-Dcom. sun. management. imxremote
-Dcom. sun. management. jmxremote. port=8888
-Dcom. sun management. jmxremote. authenticate=false
-Dcom. sun. management. imxremote ssl=false

jcmd

多功能命令行

在JDK1.7以后,新增了一个命令行工具jcmd。

它是一个多功能的工具,可以用来实现前面除了 jstat之外所有命令的功能。比如:用它来出堆、内存使用、查看Java进程、导出线程信息、执行GC、JVM运行时间等。

jcmd拥有jmap的大部分功能,并且在 Oracle的官方网站上也推荐使用jcmd命令代jmap命令

基本语法

  • jcmd -l 列出所有的JVM进程
  • jcmd pid help 针对指定的进程,列出支持的所有命令
  • jcmd pid 具体命令 显示指定进程的指令命令的数据

jstatd

远程主机信息收集

之前的指令只涉及到监控本机的Java应用程序,而在这些工具中,一些监控工具也支持对远程计算机的监控(如jps、 jstat)。为了启用远程监控,则需要配合使用 jstatd工具。

命令 jstatd是一个RMI服务端程序,它的作用相当于代理服务器,建立本地计算机与远程监控工具的通信。jstatd服务器将本机的Java应用程序信息传递到远程计算机。

JVM 下篇:性能监控与调优

Visual VM

  • 是一个功能强大的多合一故障诊断和性能监控的可视化工具
  • 它集成了多个JDK命令行工具,使用 Visual VM可用于显示虚拟机进程及进程的配置和环境信息
    (jps, jinfo),监视应用程序的CPU、GC、堆、方法区及线程的信息( jistat、 jstack)等,甚至JConsole
  • 在JDK6 Update7以后, Visual VM便作为JDK的一部分发布( Visuals在JDK/bin目录下) 即:它完全免费。

JVM 下篇:性能监控与调优
MAT提供了一个称为支配树( Dominator Tree)的对象图。支配树体现了对象实例间的支配关系。
对象引用图中,所有指向对象B的路径都经过对象A,则认为对象A支配对象B。如果对象A是离对象B最
近的一个支配对象,则认为对象A为对象B的直接支配者。支配树是基于对象间的引用图所建立的,它
以下基本性质:
  • 对象A的子树(所有被对象A支配的对象集合)表示对象A的保留集( retained set),即深堆
  • 如果对象A支配对象B,那么对象A的直接支配者也支配对象B。
  • 支配树的边与对象引用图的边不直接对应

如下图所示:左图表示对象引用图,右图表示左图所对应的支配树。对象A和B由根对象直接支配,庄
在到对象c的路径中,可以经过A,也可以经过B,因此对象C的直接支配者也是根对象。对象F与对象
相互引用,因为到对象F的所有路径必然经过对象D,因此,对象D是对象F的直接支配者。而到对象
的所有路径中,必然经过对象C,即使是从对象F到对象D的引用,从根节点出发,也是经过对象C的
D的直接支配者为对象C

JVM 下篇:性能监控与调优
严格来说,有对象不会再被程序用到了,但是GC又不能回收他们的情况,才叫内存泄漏
但实际情况很多时候一些不太好的实践(或疏忽)会导致对象的生命周期变得很长甚至导致OOM,也可
以叫做宽泛意义上的“内存泄漏”
JVM 下篇:性能监控与调优
对象X引用对象Y,X的生命周期比Y的生命周期长;
那么当Y生命周期结束的时候,X依然引用着,这时候,垃圾回收期是不会回收对象Y的;
如果对象X还引用着生命周期比较短的A、B、C,对象A又引用着对象a、b、c,这样就可能造成大量
无用的对象不能被回收,进而占据了内存资源,造成内存泄漏,直到内存溢出。

内存泄漏与内存溢出的关系

1.内存泄漏( memory leak)
申请了内存所了不释放,比如一共有1024M的内存,分配了521M的内存一直不回收,那么可以 用的内存只有521M了,仿佛泄露掉了一部分
通俗一点讲的话,内存泄漏就是【占着茅坑不拉shi】

2.内存溢出( out of memory)

申请内存时,没有足够的内存可以使用;
通俗一点儿讲,一个厕所就三个坑,有两个站着茅坑不走的(内存泄漏),剩下最后一个坑,厕所表示接待压力很大,这时候一下子来了两个人,坑位(内存)就不够了,内存泄漏变成内存溢出了。

可见,内存泄漏和内存溢出的关系:内存泄漏的增多,最终会导致内存溢出。

泄漏的分类

经常发生:发生内存泄漏的代码会被多次执行,每次执行,泄露一块内存;

偶然发生:在某些特定情况下才会发生
一次性:发生内存泄漏的方法只会执行一次;
隐式泄漏:一直占着内存不释放,直到执行结束;严格的说这个不算内存泄漏,因为最终释放掉了,但是如果执行时间特别长,也可能会导致内存耗尽。

Java中内存泄漏的8种情况

  1. 静态集合类
  2. 单例模式
  3. 内部类持有外部类回
  4. 各种连接,如数据库连接、网络连接和IO连接等
  5. 变量不合理的作用域
  6. 改变哈希值
  7. 缓存泄漏
  8. 监听器和回调

静态集合类

静态集合类,如 HashMap、 LinkedList等等。如果这些容器为静态的,那么它们的生命周期与JVM程
序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。简单而言,长生命周期
对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的

单例模式

单例模式,和静态集合导致内存泄露的原因类似,因为单例的静态特性,它的生命周期和JVM的生命
周期一样长,所以如果单例对象如果持有外部对象的引用,那么这个外部对象也不会被回收,那么就会造成内存泄漏。

内部类持有外部类

内部类持有外部类,如果一个外部类的实例对象的方法返回了一个内部类的实例对象
这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例
对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄漏

各种连接,如数据库连接、网络连接和IO连接等

各种连接,如数据库连接、网络连接和IO连接等。
在对数据库进行操作的过程中,首先需要建立与数据库的连接,当不再使用时,需要调用close方法来
释放与数据库的连接。只有连接被关闭后,垃圾回收器才会回收对应的对象
否则,如果在访问数据库的过程中,对 Connection、 Statement或 Resultset不显性地关闭,将会造成大量的对象无法被回收,从而引起内存泄漏

变量不合理的作用

变量不合理的作用域。一般而言,一个变量的定义的作用范围大于其使用范围,很有可能会造成内存泄
漏。另一方面,如果没有及时地把对象设置为null,很有可能导致内存泄漏的发生。

如上面这个伪代码,通过 readFromNet方法把接受的消息保存在变量msg中,然后调用 saveDB方法把msg的内容保存到数据库中,此时msg已经就没用了,由于msg的生命周期与对象的生命周期相同,此时msg还不能回收,因此造成了内存泄漏。

实际上这个msg变量可以放在 receiveMsg方法内部,当方法使用完,那么msg的生命周期也就结束此时就可以回收了。还有一种方法,在使用完msg后,把msg设置为null,这样垃圾回收器也会回收msg的内存空间

改变哈希值

改变哈希值,当一个对象被存储进 HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值 的字段了。

否则,对象修改后的哈希值与最初存储进 HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去 Hashset集合中检索对象,也将返回找不到对象的结果,这也会导致无法从 HashSet集合中单独删除当前对象,造成内存泄漏

这也是 String为什么被设置成了不可变类型,我们可以放心地把 String存入 HashSet,或者把String当做 HashMap的key值;

缓存泄漏

内存泄漏的另一个常见来源是缓存,一旦你把对象引用放入到缓存中,他就很容易遗忘。比如:之前项目在一次上线的时候,应用启动奇慢直到夯死,就是因为代码中会加载一个中的数据到缓存(内存)
中,测试环境只有几百条数据,但是生产环境有几百万的数据

对于这个问题,可以使用 WeakHashMap代表缓存,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值。

监听器和回调

内存泄漏第三个常见来是监听器和其他回调,如果客户端在你实现的API中注册回调,却没有显示的取消,那么就会积聚
需要确保回调立即被当作垃圾回收的最佳方法是只保存它的弱引用,例如将他们保存成为WeakHashMap中的键

案例

出栈操作

错误写法:

elements[size] 位置的元素仍然在栈中没有回收,久而久之造成内存泄漏

正确写法

Jprofiler

来源:程序员的暴击

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2021年9月26日
下一篇 2021年9月26日

相关推荐