工具概述

命令行工具及其组合局限

  • 无法获取方法级别的分析数据,如方法间的调用关系、各方法的调用次数和调用时间等
  • 要求用户登录到目标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、类、线程等

远程连接

  1. 确定远程服务器的ip地址
  2. 添加JMX(通过JMX技术具体监控远端服务器的Java进程)
  3. 修改bin/catalina.sh文件,连接远程的tomcat
  4. .../conf中添加jmxremote.accessjmxremote.password文件
  5. 将服务器地址改为公网ip地址
  6. 设置阿里云安全策略和防火墙策略
  7. 启动tomcat,查看tomcat启动日志和端口监听
  8. JMX中输入端口号、用户名、密码登录

主要功能

  • 生成/读取堆内存快照
  • 查看JVM参数和系统属性
  • 查看运行中的虚拟机进程
  • 生成/读取线程快照
  • 程序资源的实时监控
  • JMX代理连接
  • 远程环境监控
  • CPU分析和内存分析

Arthas

Arthas是Alibaba开源的Java诊断工具,在线排查问题,无需重启,动态跟踪 Java代码,实时监控JVM状态。

Arthas支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的Tab自动补全功能,进一步方便进行问题的定位和诊断。

安装与使用

安装

1
2
3
4
wget https://alibaba.github.io/arthas/arthas-boot.jar

wget https://arthas.gitee.io/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集合中单独删除当前对象,造成内存泄漏。