JVM 性能监控与调优篇-3-GUI监控诊断工具
工具概述
命令行工具及其组合局限
- 无法获取方法级别的分析数据,如方法间的调用关系、各方法的调用次数和调用时间等
- 要求用户登录到目标Java应用所在的宿主机上,使用起来不是很方便
- 分析数据通过终端输出,结果展示不够直观
GUI
jconsole
概述
从Java5开始JDK中自带java监控和管理控制台,用于对JVM中内存、线程和类等的监控,是一个基于JMX(java management extensions)的GUI性能监控工具。
启动
在jdk/bin
目录下启动jconsole.exe
即可,无需使用jps
命令查询
连接方式
连接方式 | 说明 |
---|---|
Local | 使用JConsole连接一个正在本地系统运行的JVM,并且执行程序的和运行JConsole的需要是同一个用户。JConsole使用文件系统的授权通过RMI连接器连接到平台的MBean服务器上。这种从本地连接的监控能力只有Sun的JDK具有。 |
Remote | 使用下面的URL通过RMI连接器连接到一个MX代理service:jmx:rmi://hostName:portNum/imxrmi 。JConsole为建立连接,需要在环境变量中设置mx.remote.credentials 来指定用户名和密码,从而进行授权。 |
Advanced | 使用一个特殊的URL连接JMX代理。一般情况使用自己定制的连接器而不是RMI提供的连接器来连接JMX代理,或者是一个使用JDK1.4的实现了JMX和JMX Rmote的应用。 |
Visual VM
概述
- Visual VM是一个功能强大的多合一故障诊断和性能监控的可视化工具
- 它集成多个JDK命令行工具,使用Visual VM可用于显示虚拟机进程及进程的配置和环境信息(jps,jinfo),监视应用程序的CPU、GC、堆、方法区及线程的信息(jstat、jstack)等,甚至代替JConsole。
启动
连接方式
本地连接:
监控本地Java进程的CPU、类、线程等
远程连接:
- 确定远程服务器的ip地址
- 添加JMX(通过JMX技术具体监控远端服务器的Java进程)
- 修改
bin/catalina.sh
文件,连接远程的tomcat - 在
.../conf
中添加jmxremote.access
和jmxremote.password
文件 - 将服务器地址改为公网ip地址
- 设置阿里云安全策略和防火墙策略
- 启动tomcat,查看tomcat启动日志和端口监听
- JMX中输入端口号、用户名、密码登录
主要功能
- 生成/读取堆内存快照
- 查看JVM参数和系统属性
- 查看运行中的虚拟机进程
- 生成/读取线程快照
- 程序资源的实时监控
- JMX代理连接
- 远程环境监控
- CPU分析和内存分析
Arthas
Arthas是Alibaba开源的Java诊断工具,在线排查问题,无需重启,动态跟踪 Java代码,实时监控JVM状态。
Arthas支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的Tab自动补全功能,进一步方便进行问题的定位和诊断。
安装与使用
安装
1 | wget https://alibaba.github.io/arthas/arthas-boot.jar |
卸载
工程目录
启动
指令
指令 | 说明 |
---|---|
cat ~/logs/arthas/arthas.log |
查看日志 |
java -jar arthas-boot.jar -h |
查看帮助 |
quit\exit |
退出当前客户端 |
stop\shutdown |
关闭arthas服务器,并推出所有客户端 |
基础指令
JVM相关指令
class/classloader相关指令
monitor/watch/trace相关指令
聊聊内存泄漏
WHAT
可达性分析算法来判断对象是否是不再使用的对象,本质都是判断一个对象是否还被引用。那么对于这种情况下,由于代码的实现不同就会出现很多种内存泄漏问题(让JVM误以为此对象还在引用中,无法回收,造成内存泄漏)。
理解
严格来说只有对象不会再被程序用到,但是GC又不能回收他们的情况,才叫内存泄漏。
但实际情况很多时候一些不太好的实践(或疏忽)会导致对象的生命周期变得很长甚至导致OOM,也可以叫做宽泛意义上的“内存泄漏”。
栗子
关系
内存泄漏:申请了内存用完不释放,比如一共有1024M的内存,分配512M的内存一直不回收,那可用的内存只有512M
内存溢出:申请内存时,没有足够的内存可以使用
内存泄漏和内存溢出的关系:内存泄漏的增多,最终会导致内存溢出。
分类
经常发生:发生内存泄露的代码会被多次执行,每次执行,泄露一块内存
偶然发生:在某些特定情况下才会发生
一次性:发生内存泄露的方法只会执行一次
隐式泄漏:一直占着内存不释放,直到执行结束。严格的说这个不算内存泄漏,因为最终释放,但是如果执行时间特别长,也可能会导致内存耗尽。
内存泄漏典例
静态集合类
静态集合类,如HashMap、LinkedList等等。如果这些容器为静态的,那么它们的生命周期与JVM程序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。
长生命周期的对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收。
单例模式
因为单例的静态特性,它的生命周期和JVM的生命周期一样长,所以如果单例对象如果持有外部对象的引用,那么这个外部对象也不会被回收,那么就会造成内存泄漏。
内部类持有外部类
内部类持有外部类,如果外部类的实例对象的方法返回内部类的实例对象。
内部类对象被长期引用,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄漏。
各种连接
各种连接,如数据库连接、网络连接和Io连接等。
在对数据库进行操作的过程中,首先需要建立与数据库的连接,当不再使用时,需要调用close方法来释放与数据库的连接。只有连接被关闭后,垃圾回收器才会回收对应的对象。
否则,如果在访问数据库的过程中,对Connection、Statement或ResultSet不显性地关闭,将会造成大量的对象无法被回收,从而引起内存泄漏。
变量不合理的作用域
变量不合理的作用域。一般而言,一个变量的定义的作用范围大于其使用范围,很有可能会造成内存泄漏。另一方面,如果没有及时地把对象设置为null,很有可能导致内存泄漏的发生。
改变哈希值
改变哈希值,当对象被存储进HashSet集合后,就不能修改该对象中的那些参与计算哈希值的字段
否则对象修改后的哈希值与最初存储进HashSet集合中时的哈希值不同,此时即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这会导致无法从HashSet集合中单独删除当前对象,造成内存泄漏。