8.JVM 调优

JVM概述与类的加载机制
JVM 内存模型
对象逃逸分析、JVM 内存分配和回收策略
垃圾回收算法详解、垃圾收集器全解
JVM 调优

8 JVM 调优

8.1 性能监控,故障处理工具

下面要介绍的这些工具,全部在 jdk/bin/目录下。配置了 java 环境变量后,可以直接在 dos 窗口运行启动。

8.1.1 jps:虚拟机进程状况工具

jps(JVM Process Status Tool)可以列出正在运行的虚拟机进 程,并显示虚拟机执行主类(Main Class,main()函数所在的类)名称以及这些进程的本地虚拟机唯一 ID(LVMID,Local Virtual Machine Identifier).

jps 命令格式

jps [ options ] [ hostid ] 

jps 还可以通过 RMI 协议查询开启了 RMI 服务的远程虚拟机进程状态,参数 hostid 为 RMI 注册表中注册的主机名。options 参数如下表:

选项作用
-q只输出 LVMID,省略主类名称
-m输出虚拟机进程启动时传递给主类 main()函数的参数
-l输出主类的全名。如果进程执行的是 jar 包,则输出 jar 包路径
-v输出虚拟机进程启动时的 JVM 参数

示例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iXU9PeX0-1611569476398)(https://uploader.shimo.im/f/4IpCoMk8lyFpfX3T.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

8.1.2 jstat:虚拟机统计信息监视工具

jstat(JVM Statistics Monitoring Tool)是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类加载、内存、垃圾收集、即时编译等运行时数据,

命令格式

jstat [ option vmid [interval[s|ms] [count]] ]

对于命令格式中的 VMID 与 LVMID 需要特别说明一下:如果是本地虚拟机进程,VMID 与 LVMID
是一致的;如果是远程虚拟机进程,那 VMID 的格式应当是:

[protocol:][//]lvmid[@hostname[:port]/servername] 

参数 interval 和 count 代表查询间隔和次数,如果省略这 2 个参数,说明只查询一次。假设需要每 250 毫秒查询一次进程 2764 垃圾收集状况,一共查询 20 次,那命令应当是:

jstat -gc 2764 250 20 

选项 option 代表用户希望查询的虚拟机信息,主要分为三类:类加载、垃圾收集、运行期编译状 况。参数 option 参考下表:

选项作用
-class监视类加载,卸载数量,总空间以及类加载所耗费的时间
-gc监视 Java 堆状况,包括 Eden 区、2 个 Survivor 区(from 和 to)、老年代、永久代等的容量,已用空间,垃圾收集时间合计等信息
-gccapacity监视内容与-gc 基本相同,但输出主要关注 Java 堆各个区域使用到的最大、最小空间
-gcutil监视内容与-gc 基本相同,但输出主要关注已使用空间占总空间的百分比。
-gccause与-gcutil 功能一样,但是会额外输出导致上一次垃圾收集产生的原因
-gcnew监视新生代垃圾收集状况
-gcnewcapacity监视内容与-gcnew 基本相同,输出主要关注使用到的最大、最小空间
-gcold监视老年代垃圾收集状况
-gcoldcapacity监视内容与-gcold 基本相同,输出主要关注使用到的最大、最小空间
-gcpermcapacity输出永久代使用到的最大、最小空间
-compiler输出即时编译器编译过的方法,耗时等信息
-printcompilation输出已经被即时编译的方法

示例如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Z2o5kOS-1611569476400)(https://uploader.shimo.im/f/VOwC4jY081yuJSp1.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

查询结果表明这台服务器

新生代 Eden 区(E,表示 Eden)使用了 4.01%的空间;

2 个 Survivor 区 (S0、S1,表示 Survivor0、Survivor1)各使用了 1.79%、0%;

老年代(O,表示 Old),使用 32.42%;

元数据区(M, 表实 Mete),使用 95.28%;

压缩使用比例(CCS),使用 92.34%;

程序运行以来共发生

Minor GC(YGC,表示 Young GC)36 次,耗时(YGCT, 表示 Young GC TIme)0.222 秒;

发生 Full GC(FGC,表示 Full GC)2 次,耗时(FGCT,表示 Full GC Time)为 0.053 秒;

所有 GC 总耗时(GCT,表示 GC Time)为 0.275 秒。

8.1.3 jinfo:Java 配置信息工具

jinfo(Configuration Info for Java)的作用是实时查看和调整虚拟机各项参数。

命令格式:

jinfo [ option ] pid

使用如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lTErYJFY-1611569476402)(https://uploader.shimo.im/f/7tbisogl6cTlFzIV.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

8.1.4 jmap:Java 内存映像工具

jmap(Memory Map for Java)命令用于生成堆转储快照(一般称为 heapdump 或 dump 文件)。

如果不使用 jmap 命令,要想获取 Java 堆转储快照也还有一些比较“暴力”的手段:譬如 XX:+HeapDumpOnOutOfMemoryError 参数,可以让虚拟机在内存溢出异常出现之后自动生成堆转储快照文件,通过-XX:+HeapDumpOnCtrlBreak 参数则可以使用[Ctrl]+[Break]键让虚拟机生成堆转储快照文件,又或者在 Linux 系统下通过 Kill-3 命令发送进程退出信号“恐吓”一下虚拟机,也能顺利拿到堆转储快照。

命令格式

jmap [ option ] vmid

上面 vmid 为进程号,option 参数可选如下表:

选项作用
-dump生成 java 堆转储快照。格式为:-dump:[live,]format=b,file=,其中 live 子参数说明是否只 dump 出存活对象。
-finalizerinfo显示在 F-Queue 中等待 Finalizer 线程执行 finalize 方法的对象。只在 Linux/Solaris 平台下有效
-heap显示 java 堆详细信息,如使用那种回收器、参数配置、分代状况等。只在 Linux/Solaris 平台下有效。
-histo显示堆中对象统计信息,包括类、实例数量、合计容量
-permstat以 ClassLoader 为统计口径显示永久代内存状态。只在 Linux/Solaris 平台下有效
-F当虚拟进程对-dump 选项没有响应时,可使用这个选项强制生成 dump 快照。只在 Linux/Solaris 平台下有效。

示例:

现在我的 idea 正跑着一个 springboot 项目,用-jps 查看其进程号为 3128

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JYwyO65T-1611569476405)(https://uploader.shimo.im/f/aj1c8mgx1Tlu9B7W.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

生成的文件在 C:\Users\CHENGdd\目录下,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NsEdP2QZ-1611569476406)(https://uploader.shimo.im/f/9zIgeldmrLgcMpzZ.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

Dump 出来的文件建议用 JDK 自带的 VisualVM 或 Eclipse 的 MAT 插件打开(当然也可以用下面 8.1.5 将要介绍的方法打开)。下面我们在 VisualVM 中打开如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uL78rlaY-1611569476407)(https://uploader.shimo.im/f/9akgoihRTQX5mUMN.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

8.1.5 jhat:虚拟机堆转储快照分析工具

JDK 提供 jhat(JVM Heap Analysis Tool)命令与 jmap 搭配使用,来分析 jmap 生成的堆转储快照。jhat 内置了一个微型的 HTTP/Web 服务器,生成堆转储快照的分析结果后,可以在浏览器中查看。

一般很少用 jhat 分析堆转储快照。功能简陋,多使用后面将要介绍到的 VisualVM。

格式

jhat dump 文件

示例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H5OVnDOI-1611569476408)(https://uploader.shimo.im/f/52mUFYvkGU6cJLJ0.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

浏览器访问:localhost:7000,如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UN4UMVu1-1611569476409)(https://uploader.shimo.im/f/TJXu81IF87e1hdRG.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

8.1.6 jstack:Java 堆栈跟踪工具

jstack(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照(一般称为 threaddump 或者 javacore 文件)。

线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的目的通常是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间挂起等,都是导致线程长时间停顿的常见原因。线程出现停顿时通过 jstack 来查看各个线程的调用堆栈,

就可以获知没有响应的线程到底在后台做些什么事情,或者等待着什么资源。

格式

jstack [ option ] vmid 

option 选项的合法值与具体含义如下表:

选项作用
-F当正常输出的请求不被响应时,强制输出线程堆栈
-l除堆栈外显示锁的附加信息
-m如果调用本地方法的话,可以显示 C/C++的堆栈

示例

3128 为 idea 上运行的一个 springboot 项目进程号,可用 jps 命令查看

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eq0FMOgi-1611569476410)(https://uploader.shimo.im/f/xCHJFDEIx4PV0NIo.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

8.2 可视化故障处理工具

8.2.1 jhsdb 基于服务性代理的调试工具

JHSDB 是一款基于服务性代理(Serviceability Agent,SA)实现的进程外调试工具。服务性代理是 HotSpot 虚拟机中一组用于映射 Java 虚拟机运行信息的、主要基于 Java 语言(含少量 JNI 代码)实现的 API 集合。

jhsdb 工具不仅提供包含多个其他工具功能的单个工具,而且还提供应用这些不同功能的统一方法。

8.2.1.1 启动
  • java9 乃至以后,配置了系统变量后,都可以在 cmd 窗口,运行 jhsdb 命令启动的。
// 11189 为进程号
jhsdb hsdb --pid 11180     
  • 在 java9 之前,

JAVA_HOME/lib目录下有个sa-jdi.jar。sa-jdi.jar 中的 sa 的全称为 Serviceability Agent,它之前是 sun 公司提供的一个用于协助调试 HotSpot 的组件,而 HSDB 便是使用 Serviceability Agent 来实现的。

HSDB 就是 HotSpot Debugger 的简称,由于 Serviceability Agent 在使用的时候会 attach 进程,然后暂停进程进行 snapshot,最后 deattach 进程(进程恢复运行),所以在使用 HSDB 时要注意。启动方式如下:

java -cp $JAVA_HOME\lib\sa-jdi.jar sun.jvm.hotspot.HSDB
// 这里的 ¥JAVA_HOME 为 D:\installFiles\Java\jdk1.8.0_131
java -cp D:\installFiles\Java\jdk1.8.0_131\lib\sa-jdi.jar sun.jvm.hotspot.HSDB

启动起来后,绑定了一个 java 进程时,进程号为 7516

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lfh7daFV-1611569476411)(https://uploader.shimo.im/f/VfsUZaCVVO0OEC0w.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y5Y16p9s-1611569476411)(https://uploader.shimo.im/f/MidmvHBwsOCNlEuQ.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

点击 OK,报错如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OEYY5uZN-1611569476412)(https://uploader.shimo.im/f/CwGolPCPc4g4UDGq.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

报错信息为:Can’t load library: D:\installFiles\Java\jre1.8.0_131\bin\sawindbg.dll

默认是去 jre 目录下去找了,其实这个库在 jdk 的 jrre 目录下,当然我们也可以 jdk 安装目录下搜索下这个文件。如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NxF0YwCK-1611569476413)(https://uploader.shimo.im/f/qYzXSp0VCSp0kAPf.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

那么我们从 D:\installFiles\Java\jdk1.8.0_131\jre\bin 目录下把 sawindbg.dll 文件拷贝到 D:\installFiles\Java\jre1.8.0_131\bin 的目录下。再次启动绑定要监控的 java 进程,进入界面:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vxrlKx2y-1611569476414)(https://uploader.shimo.im/f/rYeOJ9zwkrAztDWp.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

8.2.1.2 使用

查看对象参数

点击 Tools -> Heap Parameters,查看当前对参数,记住 Eden 区的起始地址为:0x000000076bb00000 如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N57zivGy-1611569476415)(https://uploader.shimo.im/f/pjJllMUlVdhsQhII.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

根据地址查看存放对象

点击 Tools -> Inspector。查看 0x000000076bb00000 地址中存放的对象,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mI6JxKwY-1611569476416)(https://uploader.shimo.im/f/TYoI9te3odiyy8BD.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

查看元数据发现, 里面包括了 Java 类型的名字(这个地址存放的是 java.io.File 类对象)、继承关

系、实现接口关系,字段信息、方法信息、运行时常量池的指针、内嵌的虚方法表(vtable)以及接口方法表(itable)等。

查看栈内存

在 Java Thread 窗口选中 Druid-ConnectionPool-Create-2048615793 线程后点击 Stack Memory 按钮查看该线程的 栈内存,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FeXgF6g8-1611569476417)(https://uploader.shimo.im/f/vw8iIlTFD8i9YkJW.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

8.2.2 jconsole Java 监视与管理控制台

JConsole(Java Monitoring and Management Console)是一款基于 JMX(Java Manage-mentExtensions)的可视化监视、管理工具。它的主要功能是通过 JMX 的 MBean(Managed Bean)对系统进行信息收集和参数动态调整

8.2.2.1 启动

cmd 窗口执行 jconsole 命令(前提是配置了 jdk 的环境变量)。选择我本地运行的一个 springboot 项目对应的进程。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6bnhjkV5-1611569476418)(https://uploader.shimo.im/f/8yiPmk7FBmmJQgkA.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

若 jconsole 本地连接“提示安全连接失败”问题。那么,启动该项目前配置 vm 参数:

// 是否支持远程 JMX 访问,默认 true
-Dcom.sun.management.jmxremote
// 监听端口号,方便远程访问
-Dcom.sun.management.jmxremote.port=8011
// 是否需要开启用户认证,默认开启
-Dcom.sun.management.jmxremote.ssl=false
// 是否对连接开启 SSL 加密,默认开启
-Dcom.sun.management.jmxremote.authenticate=false

例如在 IDEA 中配置如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dx11Qh9p-1611569476419)(https://uploader.shimo.im/f/QcBZG0tGa0aBKMGa.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

8.2.2.2 使用

再次启动项目,再次执行 jconsole 命令。选择该进程,进入下面界面:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hMF1pnMV-1611569476419)(https://uploader.shimo.im/f/DndnF6FMsSpvmQQC.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

剋查看内存、线程、类、VM、概要、MBean 等信息。

8.2.3 VisualVM:多合-故障处理工具

VisualVM(All-in-One Java Troubleshooting Tool)是功能最强大的运行监视和故障处理程序之一,曾经在很长一段时间内是 Oracle 官方主力发展的虚拟机故障处理工具.VisualVM 还有一个很大的优点:不需要被监视的 程序基于特殊 Agent 去运行,因此它的通用性很强,对应用程序实际性能的影响也较小,使得它可以直 接应用在生产环境中。

8.2.3.1 启动

cmd 窗口运行命令:

jvisualvm

启动后界面如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uc3Cx3td-1611569476420)(https://uploader.shimo.im/f/Ndtah7PUnqwZrIxj.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

8.2.3.2 安装插件

VisualVM 基于 NetBeans 平台开发工具,所以一开始它就具备了通过插件扩展功能的能力,有了插件扩展支持。我们可以在线安装或者把插件下载下来,本地安装。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hx1i0G9j-1611569476421)(https://uploader.shimo.im/f/dwSPYzao7Gb0bfLB.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

8.2.3.3 Visual GC 查看堆空间动态图示

先利用上面的方式安装 Visual GC 插件,也可以提前把插件下载下来,离线安装。如果遇到

给 jdk 自带的 jvisualvm 安装 Visual GC 插件,遇到 We’re sorry the java.net site has closed(我们很抱歉 java.net 网站已经关闭)问题。

请点击起始页上的“Visual VM 主页”,去下载。如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ceT4reIw-1611569476421)(https://uploader.shimo.im/f/XINRJSFivrSYqIar.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

安装好 Visual GC 呢,双击本地“Visual VM”进程,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sp8FdBjz-1611569476422)(https://uploader.shimo.im/f/CP6m2H1IKKXJlOnd.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

可以看到一些编译时间,加载时间,GC 时间、Eden 区、Survivor(from、to)区、老年代,元空间的一些动态图。

8.2.3.4 生成、浏览堆转储快照

在 VisualVM 中生成堆转储快照文件有两种方式,可以执行下列任一操作:

  • 在“应用程序”窗口中右键单击应用程序节点,然后选择“堆 Dump”。
  • ·在“应用程序”窗口中双击应用程序节点以打开应用程序标签,然后在“监视”标签中单击“堆 Dump”。

接着会有这样一个标签,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tJZOTI7x-1611569476423)(https://uploader.shimo.im/f/XnCuLkf9BRx0VFZS.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

  1. 堆页签中的“概要”面板可以看到应用程序 dump 时的运行时参数、System.getPro-perties()的内容、线程堆栈等信息;

2.“类”面板则是以类为统计口径统计类的实例数量、容量信息;

3.“实例”面板不能直接使用,因为 VisualVM 在此时还无法确定用户想查看哪个类的实例,所以需要通过“类”面板进入,在“类”中选择一个需要查看的类,然后双击即可在“实例”里面看到此类的其中 500 个实例的具体属性信息;

4.“OQL 控制台”面板则是运行 OQL 查询语句的,同 jhat 中介绍的 OQL 功能一样

如下图,来看 String 类的实例,在”类“面板双击 java.lang.String。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qNYSm78q-1611569476424)(https://uploader.shimo.im/f/ydwk4RpmZGMbCLLJ.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

8.2.3.5 分析程序性能

在 Profiler(抽样器)页签中,VisualVM 提供了程序运行期间方法级的处理器执行时间分析以及内存分析。做 Profiling 分析肯定会对程序运行性能有比较大的影响,所以一般不在生产环境使用这项功能,或者改用 JMC 来完成,JMC 的 Profiling 能力更强,对应用的影响非常轻微。

要开始性能分析,先选择“CPU”和“内存”按钮中的一个,然后切换到应用程序中对程序进行操作,VisualVM 会记录这段时间中应用程序执行过的所有方法。如果是进行处理器执行时间分析,将会统计每个方法的执行次数、执行耗时;如果是内存分析,则会统计每个方法关联的对象数以及这些对象所占的空间。等要分析的操作执行结束后,点击“停止”按钮结束监控过程。如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2LZPfkih-1611569476424)(https://uploader.shimo.im/f/9sHmYEhaxW9GsdXA.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

注意:在 JDK 5 之后,在客户端模式下的虚拟机加入并且自动开启了类共享——这是一个在多 虚拟机进程共享rt.jar中类数据以提高加载速度和节省内存的优化,而根据相关Bug报告的反映, VisualVM的Profiler功能会因为类共享而导致被监视的应用程序崩溃,所以读者进行Profiling前,最好在 被监视程序中使用-Xshare:off参数来关闭类共享优化。

8.2.3.6 .BTrace 动态日志跟踪

BTrace 是一个很神奇的 VisualVM 插件,它本身也是一个可运行的独立程序。BTrace 的作用是在

不中断目标程序运行的前提下,通过 HotSpot 虚拟机的 Instrument 功能[4]动态加入原本并不存在的调试代码。

先安装该插件,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8jTxXWGc-1611569476425)(https://uploader.shimo.im/f/hfyYpAH3OAwHSA4O.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

使用示例

创建这么一个类(所在包 com.example.demo.jvm.tool),并且运行起来。代码如下:

public class BTraceTest {
    public int add(int a, int b) {
        return a + b;
    }
    public static void main(String[] args) throws IOException {
        BTraceTest test = new BTraceTest();
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        for (int i = 0; i < 10; i++) {
            reader.readLine();
            int a = (int) Math.round(Math.random() * 1000);
            int b = (int) Math.round(Math.random() * 1000);
            System.out.println(test.add(a, b));
        }
    }
}

在 Visual VM 中,右键该进程,点击 “Trance application”,右边出现一个类似 程序编辑的场景,并且有了一小段代码。如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x8XDk2Ky-1611569476426)(https://uploader.shimo.im/f/p4BSGsO0kOEHkOSe.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

目的是针对 com.example.demo.jvm.tool.BTraceTest 类的 add 方法,打印该方法的一些参数。编辑该页面代码后如下:

/* BTrace Script Template */
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
@BTrace
public class TracingScript {
	/* put your code here */
    @OnMethod(clazz="com.example.demo.jvm.tool.BTraceTest", method="add", location=@Location(Kind.RETURN))
    public static void func(@Self com.example.demo.jvm.tool.BTraceTest instance,int a,int b,@Return int result) {
        println("调用堆栈:");
        jstack();
        println(strcat("方法参数 A:",str(a)));
        println(strcat("方法参数 B:",str(b)));
        println(strcat("方法结果:",str(result)));
    }
}

点击“Start”按钮后稍等片刻,编译完成后,可见 Output 面板中出现“BTrace code successfuly deployed”的字样。运行上面程序的时候在 Output 面板将会输出如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6ULOWW2y-1611569476426)(https://uploader.shimo.im/f/3pSw0Wb8tnrso3y3.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

8.2.4 Java Mission Control:可持续在线的监控工具

JFR(Java Flight Recorder 飞行记录器)是一套内建在 HotSpot 虚拟机里面的监控和基于事件的信息搜集框架,与其他的监控工具(如 JProfiling)相比,Oracle 特别强调它“可持续在线”(Always-On)的特性。JFR 在生产环境中对吞吐量 的影响一般不会高于 1%(甚至号称是 Zero Performance Overhead),而且 JFR 监控过程的开始、停止都 是完全可动态的,即不需要重启应用。JFR 的监控对应用也是完全透明的,即不需要对应用程序的源 码做任何修改,或者基于特定的代理来运行。

JMC 与虚拟机之间同样采取 JMX 协议进行通信,JMC 一方面作为 JMX 控制台,显示来自虚拟机 MBean 提供的数据;另一方面作为 JFR 的分析工具,展示来自 JFR 的数据。

启动

cmd 窗口运行命令

jmc

启动后界面如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LkEA8QnR-1611569476427)(https://uploader.shimo.im/f/Ci4jY9O37SuRiFgI.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

VM 参数设置(连接远程程序时设置,本地的不用)

被监控的程序在启动之前,要设置下面 VM 参数:

-Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=192.168.31.4 -XX:+UnlockCommercialFeatures -XX:+FlightRecorder

使用 MBean 服务器

选择一个进程,点击左边的三角符号,可以看到有两个选项“MBean 服务器”、“飞行记录器”,双击“MBean 服务器”,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mNkOMBDR-1611569476428)(https://uploader.shimo.im/f/IsafJ2fTTaKdBVpX.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

使用飞行记录器(jfr)

选择一个进程,点击左边的三角符号,可以看到有两个选项“MBean 服务器”、“飞行记录器”,双击“飞行记录器”—> "完成“,记录完成之后如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QSu9jVAZ-1611569476428)(https://uploader.shimo.im/f/43A1bkFtJJGLVciV.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

飞行记录报告里包含以下几类信息:

·一般信息:关于虚拟机、操作系统和记录的一般信息。

内存:关于内存管理和垃圾收集的信息。

代码:关于方法、异常错误、编译和类加载的信息。

线程:关于应用程序中线程和锁的信息。

I/O:关于文件和套接字输入、输出的信息。

系统:关于正在运行 Java 虚拟机的系统、进程和环境变量的信息。

事件:关于记录中的事件类型的信息,可以根据线程或堆栈跟踪,按照日志或图形的格式查看。

即使不考虑对被测试程序性能影响方面的优势,JFR 提供的数据质量通常也要比其他工具通过代 理形式采样获得或者从 MBean 中取得的数据高得多。

8.3 JVM 参数分类

8.3.1 标准参数

标准参数,顾名思义,标准参数中包括功能以及输出的结果都是很稳定的。基本上不会随着 JVM 版本的变化而变化。我们可以通过 java -help 命令查看所有的标准参数。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mZR0I2tG-1611569476429)(https://uploader.shimo.im/f/zKveJYCXXt2tpnES.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

这些命令的详细解释可以看官网:

https://docs.oracle.com/javase/7/docs/technotes/tools/solaris/java.html

8.3.2 X 参数

前面说了标准参数,这里我们来看非标准参数。即,在以后的 JVM 版本中可能会发生改变,这类以 -X 开始的参数变化的比较小。我们可以通过 java -X 命令查看所有的非标准参数。如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PTKYGGGK-1611569476430)(https://uploader.shimo.im/f/Uy08ew25z2naEHEK.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

8.3.3 XX 参数

这是我们日常开发中接触到最多的参数类型。这也是非标准化参数,相对来说不稳定随着 JVM 版本的变化可能会发生变化,主要用于JVM 调优和 debug。按书写形式分为两大类,接着往下看:

注意:这种参数是我们后续介绍 JVM 调优讲解最多的参数。

8.3.3.1 boolean 类型
格式:-XX:[+-]<name> 表示启用或者禁用 name 属性。
例子:-XX:+UseG1GC(表示启用 G1 垃圾收集器)
8.3.3.2 key/value 类型
格式:-XX:<name>=<value> 表示 name 的属性值为 value。
例子:-XX:MaxGCPauseMillis=500(表示设置 GC 的最大停顿时间是 500ms)

8.4 JVM 参数说明

8.4.1 堆的分配参数

8.4.1.1 最小最大堆容量
-Xms256M:设置堆内存初始值为 256M(最小堆容量)
-Xmx512M:设置堆内存最大值为 512M

这里的 ms 是 memory start 的简称,mx 是 memory max 的简称,分别代表最小堆容量和最大堆容量。但是别看这里是-X 参数,其实这是-XX 参数,等价于:

  -XX:InitialHeapSize
  -XX:MaxHeapSize

在通常情况下,服务器项目在运行过程中,堆空间会不断的收缩与扩张,势必会造成不必要的系统压力。所以在生产环境中,JVM 的 Xms 和 Xmx 要设置成一样的,能够避免 GC 在调整堆大小带来的不必要的压力。

8.4.1.2 年轻代的分配参数

(1)设置年轻代大小

格式:-Xmn 数值+容量单位
例如:-Xmn10m        //年轻代为 10m

(2)年轻代和老年代的比例

新生代(eden+2 个 survivor)和老年代(不包含永久区)的比值

格式:-XX:NewRatio=数值
例如:-XX:NewRatio=1  //表示新生代:老年代=1:1,即新生代占整个堆的一半

(3)survivor 区和 eden 区的比例
设置两个 Survivor 区总的大小和 eden 的比值

格式:-XX:SurvivorRatio=数值
例如:-XX:SurvivorRatio=8  //表示 1 个 Survivor:eden=1:8,表示两个 Survivor:eden=2:8,即一个 Survivor 占年轻代的 1/10

8.3.2 栈的分配参数

设置栈空间的大小。通常只有几百 K,决定了函数调用的深度。

栈空间是每个线程私有的区域。栈里面的主要内容是栈帧,而栈帧存放的是局部变量表、操作数栈、动态链接、返回地址。

格式:-Xss 数值+容量单位
例如:-Xmn128k        //年轻代为 128kb

8.4.3 永久代参数配置

设置永久区的初始空间和最大空间。也就是说,jvm 启动时,永久区一开始就占用了初始大小的空间,如果空间还不够,可以继续扩展,但是不能超过 MaxPermSize,否则会 OOM

-XX:PermSize   // 永久代初始空间
-XX:MaxPermSize  // 永久代最大空间
例如:永久代最大空间设置为 128m
-XX:MaxPermSize=120m

在 JDK1.7 以及以前的版本中,只有 Hotspot 才有 Perm 区,称为永久代,它在启动时固定大小,很难进行调优。
在某些情况下,如果动态加载类过多,容易产生 Perm 区的 OOM。比如某个实际 Web 工程中,因为功能点较多,在运行过程中,要不断动态加载很多类,就会出现类似错误:

“Exception in thread ‘dubbo client x.x.connect’ java.lang.OutOfMemoryError:PermGenspace”

为了解决这个问题,就需要在项目启动时,设定运行参数-XX:MaxPermSize。

注意:在 JDK1.8 以后面的版本,使用元空间来代替永久代。在 JDK1.8 以及后面的版本中,如果设定参数-XX:MaxPermSize,启动 JVM 不会报错,但是会提示:

Java Hotspot 64Bit Server VM warning:ignoring option MaxPermSize=1280m:support was removed in 8.0

8.4.4 gc 日志

8.4.4.1 打印 gc 日志简要信息
-verbose:gc
或
-XX:+printGC
8.4.4.2 打印 gc 日志详细信息
-XX:+PrintGCDetails  // 打印 GC 详细信息
-XX:+PrintGCTimeStamps  // 打印 GC 详细信息 带时间戳
-XX:+PrintGCDateStamps  // 打印 GC 详细信息 带日期戳

例如:

[GC[DefNew: 4416K->0K(4928K), 0.0001897 secs] 4790K->374K(15872K), 0.0002232 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

上方日志的意思是说:这是一个新生代的 GC.
方括号内部的“4416K->0K(4928K)”含义是:“GC 前该内存区域已使用容量->GC 后该内存区域已使用容量(该内存区域总容量)”。0.0001897 secs”表示该内存区域 GC 所占用的时间,单位是秒

而在方括号之外的“4790K->374K(15872K)”表示“GC 前 java 堆已使用容量->GC 后 Java 堆已使用容量(Java 堆总容量)”。0.0002232 secs”表示该 java 堆 GC 所占用的时间,单位是秒

8.4.4.3 指定 gc log 位置

解释:指定 GC log 的位置,以文件输出。帮助开发人员分析问题。

-Xloggc:./gc.log

8.4.5 其他

8.4.5.1 打印已经配置 jvm 参数
-XX:+PrintCommandLineFlags

如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BfYaOKY2-1611569476431)(https://uploader.shimo.im/f/CAtgImfdfMvNpDcF.png!thumbnail?fileGuid=Wttdh3K6tTttJwV8)]

8.4.5.2Dump 异常快照以及以文件形式导出
-XX:+HeapDumpOnOutOfMemoryError  //内存发生错误时打印堆转储快照
-XX:HeapDumpPath     //设置堆内存溢出快照输出的文件地址

例如:

-Xmx20m -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/a.dump
8.4.5.3 发生 OOM 时执行一个脚本
-XX:OnOutOfMemoryError

例如:

-XX:OnOutOfMemoryError="C:\Program Files\Java\jdk1.8.0_152\bin\jconsole.exe"
8.4.5.4 垃圾收集器配置

请参考 “垃圾收集器全解“部分,所介绍的参数配置方式。

8.5 总结

讲到这里 jvm 调优过程中可能用到的工具,根据 log 调优、配置 jvm 参数已经说完了。到实际的具体情况中要灵活运用。在周志明老师的《深入理解 java 虚拟机》列举了 8 种案例,分别为:

  • 大内存硬件上的内存部署。频繁产生大对象时,不要给一个 java 虚拟机分配过大的内存,可能会导致 Full GC 时间过长。
  • 集群间同步导致的内存溢出。当设计失败重发机制时,要考虑到是否在重发过程中内存占用不断增加,导致内存溢出的情况。
  • 堆外内存导致溢出错误。在小内存硬件上部署程序时,要充分考虑到堆之外的其他区域也会占用内存问题。这些所有区域的内存总和会受到硬件总内存的限制。
  • 外部命令导致系统缓慢。有时候在 java 程序中调用一些外部脚本,可能会产生新的进程(或内存占用),当请求繁忙时就有可能产生内存溢出。
  • 服务器虚拟机进程崩溃。在处理异步调用问题时,可能一方处理速度较慢,不断有新的待处理请求累积,可能会导致虚拟机进程崩溃(可采用消息队列解决)。
  • 不恰当数据结构导致内存占用过大。在程序开发中要注意”吝啬“使用对象类型。量体裁衣,例如一个一位长度的整型数据,用 Long 类型去存,这样就极大的浪费了内存空间。在某些时候就会导致内存泄漏。
  • 由 Windows 虚拟内存导致的长时间停顿。一些 GUI 程序在最小化时,其工作内存被交换到磁盘的页面文件了,进行垃圾收集时因为恢复页面文件导致不正常的垃圾收集而产生长时间停顿。可以加入参数“- Dsun.awt.keepWorkingSetOnMinimize=true”来解决
  • 由安全点导致长时间停顿。程序循环次数少,但是单次循环体又执行特别慢。这时候可能会产生由安全点导致的长时间停顿。安全点是以“是否具有让程序长时间执行的特征”为原则进行选定的,所以方法调用、循环跳转、异常跳 转这些位置都可能会设置有安全点,但是 Hot Spot 虚拟机为了避免安全点过多带来过重的负担,对循环 还有一项优化措施,认为循环次数较少的话,执行时间应该也不会太长,所以使用 int 类型或范围更小 的数据类型作为索引值的循环默认是不会被放置安全点的。若发生因安全点导致的程序长时间停顿,若不是 jdk1.8,有-XX:+UseCountedLoopSafepoints 参数去强制在可数循环中也放置安全点或者把循环索引的数据类型从 int 改为 long 也可以。

以上案例我们在实际中可能遇到的问题,当然了也有可能会碰到其他类型的 jvm 问题,我们需要具体问题具体对待。

9 参考资料

周志明老师的《深入理解 Java 虚拟机+JVM 高级特性与最佳实践》第二版

《java 虚拟机规范》

栈帧结构之局部变量表:https://blog.csdn.net/TuGeLe/article/details/78886522

Java8 中的 JVM 元空间是不是方法区?https://www.zhihu.com/question/358312524

王昭君老师的 《从入门到进阶,深入理解 jvm》视频教程

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值