JVM监控及诊断工具

jps:查看正在运行的 Java 进程

基本情况

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

基本语法

1
jps [options] [hostid]

options 参数

1
2
3
4
5
-q:仅仅显示 LVMID(local virtual machine id),即本地虚拟机唯一 ID。不显式主类的名称等
-l:输出应用程序主类的全类名或如果进程执行的是 jar 包,则输出 jar 完整路径
-m:输出虚拟机进程启动时传递给主类 main() 的参数
-v:列出虚拟机进程启动时的 JVM 参数。比如:-Xms20m -Xmx50m 是启动程序指定的 JVM 参数
补充:如果某 Java 进程关闭了默认开启的 UsePerfData 参数(即使用参数 -XX:-UsePerfData),那么 jps 命令(以及下面介绍的 jstat)将无法探知该 Java 进程。

hostid 参数

RMI 注册表中注册的主机名。 如果想要远程监控主机上的 Java 程序,需要安装 jstatd。

对于具有更严格的安全实践的网络场所而言,可能使用一个自定义的策略文件来显示对特定的可信主机或网络访问,尽管 这种技术容易受到 IP 地址欺诈攻击。

如果安全问题无法使用一个定制的策略文件来处理,那么最安全的操作是不运行 jstatd 服务器,而是在本地使用 jstat 和 jps 工具。

jstat:查看 JVM 统计信息

基本情况

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

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

基本语法

1
jstat -<option> [t] [-h<lines>] <vmid> [<interval> [<count>]]

查看命令相关参数:jstat -hjstat -help

option 参数

选项 option 可以由以下值构成:

  • 类装载相关的
    • -class:显示 ClassLoader 的相关信息,类的装载、卸载数量、总空间、类装载所消耗的时间等。
  • 垃圾回收相关的
    • -gc:显示与 GC 相关的堆信息。包括 Eden 区、两个 Survivor 区、老年代、永久代等的容量、已用空间、GC 时间合计等信息。
    • -gccapacity:显示内容与 -gc 基本相同,但输出主要关注 Java 堆各个区域使用到的最大、最小空间。
    • -gcutil:显示内容与 -gc 基本相同,但输出主要关注已使用空间占总空间的百分比。
    • -gccause:与 -gcutil 功能一样,但是会额外输出导致最后一次或当前正在发生的 GC 产生的原因。
    • -gcnew:显示新生代 GC 状况。
    • -gcnewcapacity:显示内容与 -gcnew 基本相同,输出主要关注使用到的最大、最小空间。
    • -geold:显示老年代 GC 状况。
    • -gcoldcapacity:显示内容与 -gcold 基本相同,输出主要关注使用到的最大、最小空间。
    • -gcpermcapacity:显示永久代使用到的最大、最小空间。
  • JIT 相关的
    • -compiler:显示 JIT 编译器编译过的方法、耗时等信息。
    • -printcomilation:输出已经被 JIT 编译的方法。

新生代相关:

  • S0C 是第一个幸存者区的大小(字节)
  • S1C 是第二个幸存者区的大小(字节)
  • S0U 是第一个幸存者区已使用的大小(字节)
  • S1U 是第二个幸存者区已使用的大小(字节)
  • EC 是 Eden 空间的大小(字节)
  • EU 是 Eden 空间已使用大小(字节)

老年代相关:

  • OC 是老年代的大小(字节)
  • OU 是老年代已使用的大小(字节)

方法区(元空间)相关:

  • MC 是方法区的大小
  • MU 是方法区已使用的大小
  • CCSC 是压缩类空间的大小
  • CCSU 是压缩类空间已使用的大小

其他:

  • YGC 是指从应用程序启动到采样时 Young GC 次数
  • YGCT 是指从应用程序启动到采样时 Young GC 消耗的时间(秒)
  • FGC 是指从应用程序启动到采样时 Full GC 次数
  • FGCT 是指从应用程序启动到采样时 Full GC 消耗的时间(秒)
  • GCT 是指从应用程序启动到采样时 GC 的总时间

interval 参数

用于指定输出统计数据的周期,单位为毫秒。即:查询间隔。

count 参数

用于指定查询的总次数。

-t 参数

可以在输出信息前加上一个 Timestamp 列,显示程序的运行时间。单位:秒。

1
2
3
我们可以比较 Java 进程的启动时间以及总 GC 时间(GCT 列),或者两次测量的间隔时间以及总 GC 时间的增量,来得出 GC 时间占运行时间的比例。

如果该比例超过 20%,则说明目前堆的压力较大;如果该比例超过 90%,则说明堆里几乎没有可用空间,随时都可能抛出 OOM 异常。

-h 参数

可以在周期性数据输出时,输出多少行数据后输出一个表头信息。

补充

jstat 还可以用来判断是否出现内存泄漏。

  • 第 1 步:在长时间运行的 Java 程序中,我们可以运行 jstat 命令连续获取多行性能数据,并取这几行数据中 OU 列(即已占用的老年代内存)的最小值。
  • 第 2 步:然后,我们每隔一段较长的时间重复一次上述操作,来获得多组 OU 最小值。如果这些值呈上涨趋势,则说明该 Java 程序的老年代内存已使用量在不断上涨,这意味着无法回收的对象在不断增加,因此很有可能存在内存泄漏。

jinfo:实时查看和修改 JVM 配置参数

基本情况

jinfo(Configuration Info for Java):查看虚拟机配置参数信息,也可用于调整虚拟机的配置参数。

image-20220518205410967

官方帮助文档:https://docs.oracle.com/en/java/javase/11/tools/jinfo.html

基本语法

1
jinfo [options] pid

说明:Java 进程 ID 必须要加上。

选项选项说明
no option输出全部的参数和系统属性
-flag name输出对应名称的参数
-flag [+-] name开启或关闭对应名称的参数,只有被标记为 manageable 的参数才可以被动态修改
-flag name=value设定对应名称的参数
-flags输出全部的参数
-sysprops输出系统属性

查看

1
2
3
jinfo -sysprops PID:可以查看由 System.getProperties() 取得的参数。
jinfo -flags PID:查看曾经赋过值的一些参数。
jinfo -flag 具体参数 PID:查看某个 Java 进程的具体参数的值。

修改

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

1
2
针对 boolean 类型:jinfo -flag[+|-] 具体参数 PID。
针对非 boolean 类型:jinfo -flag 具体参数=具体参数值 PID。

拓展

1
2
3
java -XX:+PrintFlagsInitial:查看所有 JVM 参数启动的初始值。
java -XX:+PrintFlagsFinal:查看所有 JVM 参数的最终值。
java -XX:+PrintCommandLineFlags:查看那些已经被用户或者 JVM 设置过的详细的 XX 参数的名称和值。

jmap:导出内存映像文件&内存使用的情况

基本情况

jmap(JVM Memory Map):作用一方面是获取 dump 文件(堆转储快照文件,二进制文件),它还可以获取目标 Java 进程的内存相关信息,包括 Java 堆各区域的使用情况、堆中对象的统计信息、类加载信息等。

可以在控制台输入命令 jmap -help 查阅 jmap 工具的具体使用方式和一些标准选项配置。

官方帮助文档:https://docs.oracle.com/en/java/javase/11/tools/jmap.html

基本语法

它的基本使用语法为:

1
2
3
jmap [option] <pid>
jmap [option] <executable <core>>
jmap [option] [server_id@]<remote server IP or hostname>

其中 option 包括:

选项作用
-dump生成 dump 文件
-finalizerinfo以 ClassLoader 为统计口径输出永久代的内存状态信息
-heap输出整个堆空间的详细信息,包括 GC 的使用、堆配置信息、以及内存的使用信息等
-histo输出堆空间中对象的统计信息,包括类、实例数量和合计容量
-permstat以 ClassLoader 为统计口径输出永久代的内存状态信息
-F当虚拟机进程对 -dump 选项没有任何响应时,强制执行生成 dump 文件

说明:这些参数和 Linux 下输入显示的命令多少会有不同,包括也受 JDK 版本的影响。

1
2
3
4
5
6
7
8
-dump:生成 Java 堆转储快照:dump 文件,特别的:-dump:live 只保存堆中的存活对象。
-heap:输出整个堆空间的详细信息,包括 GC 的使用、堆配置信息,以及内存的使用信息等。
-histo:输出堆中对象的统计信息,包括类、实例数量和合计容量。特别的,-histo:live 只统计堆中的存活对象。
-permstat:以 ClassLoader 为统计口径输出永久代的内存状态信息,仅 Linux/Solaris 平台有效。
-finalizerinfo:显示在 F-Queue 中等待 Finalizer 线程执行 finalize 方法的对象,仅 Linux/Solaris 平台有效。
-F:当虚拟机进程对 -dump 选项没有任何响应时,可使用此选项强制执行生成 dump 文件,仅 Linux/Solaris 平台有效。
-h | -help:jmap 工具使用的帮助命令。
-j<flag>:传递参数给 jmap 启动的 JVM。

使用 1:导出内存映像文件

一般来说,使用 jmap 指令生成 dump 文件的操作算得上是最常用的 jmap 命令之一,将堆中所有存货对象导出至一个文件之中。

Heap Dump 又叫做堆转储文件,指一个 Java 进程在某个时间点的内存快照。Heap Dump 在触发内存快照的时候会保存此刻的信息如下:

1
2
3
4
All Object`:`Class, fields, primitive, values and reference
All Classes`:`ClassLoader, name, super class, static fields
Garbage Collection Roots`:`Objects defined to be reachable by the JVM
Thread Stacks and Local Variables`:`The call-stacks of threads at the moment of the snapshot, and per-frame information about local objects

说明:

  1. 通常在写 Heap Dump 文件前会触发一次 Full GC,所以 Heap Dump 文件里保存的都是 Full GC 后留下的对象信息。
  2. 由于生成 dump 文件比价耗时,因此大家需要耐心等待,尤其是大内存镜像生成 dump 文件则需要耗费更长的时间来完成。
  • 手动的方式
1
2
jmap -dump:format=b,file=<filename.hprof><pid>
jmap -dump:live,format=b,file=<filename.hprof><pid>
  • 自动的方式

当程序发生 OOM 退出系统时,一些瞬时信息都随着程序的终止而消失,而重现 OOM 问题往往比较困难或者耗时。此时若能在 OOM 时,自动导出 dump 文件就显得十分迫切。

这里介绍一种比较常用的取得堆快照文件的方法,即使用:

1
2
3
-XX:+HeapDumpOnOutOfMemoryError:在程序发生 OOM 时,导出应用程序的当前堆快照。
-XX:HeapDumpPath=<filename.hprof>:可以指定堆快照的保存位置。
例如: -Xmx100m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\m.hprof

使用 2:显示堆内存相关信息

1
2
jmap -heap PID
jmap -histo PID

使用 3:其他作用

1
2
jmap -permstat PID:查看系统的 ClassLoader 信息。
jmap -finalizerinfo:查看堆积在 finalizer 队列中的对象

小结

由于 jmap 将访问堆中的所有对象,为了保证在此过程中不被应用线程干扰,jmap 需要借助安全点机制,让所有线程停留在不改变堆中数据的状态。也就是说,由 jmap 导出的堆快照必定是安全点位置的。这可能导致基于该堆快照的分析结果存在偏差。

举个例子,假设在编译生成的机器码中,某些对象的生命周期在两个安全点之间,那么 :live 选项将无法探知到这些对象。

另外,如果某个线程长时间无法跑到安全点,jmap 将一直等下去。与前面讲得 jstat 则不同,垃圾回收器会主动将 jstat 所需要的摘要数据保存至固定位置中,而 jstat 只需直接读取即可。

jstack:打印 JVM 中线程快照

基本情况

jstack(JVM Stack Trace):用于生成虚拟机指定进程当前时刻的线程快照(虚拟机堆栈跟踪)。线程快照就是当前虚拟机内指定进程的每一条线程正在执行的方法堆栈的集合。

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

官方帮助文档:https://docs.oracle.com/en/java/javase/11/tools/jstack.html。

在 Thread Dump 中,要留意下面几种状态:

  • 死锁,Deadlock(重点关注)
  • 等待资源,Waiting on condition(重点关注)
  • 等待获取监视器,Waiting on monitor entry(重点关注)
  • 阻塞,Blocked(重点关注)
  • 执行中,Runnable
  • 暂停,Suspended
  • 对象等待中,Object.wait() 或 TIMED_WAITING
  • 停止,Parked

基本语法

image-20220518205218585

它的基本使用语法为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
jstack option PID
jstack 管理远程进程的话,需要在远程程序的启动参数中增加:

-Djava.rmi.server.hostname="hostname"
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=8888
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
option 参数:

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

jstatd:远程主机信息收集

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

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

image-20220506193716253

Visual VM

1
Preferences --> Plugins --> 搜索 Visual VM Launcher,安装重启即可。

本地连接:监控本地 Java 进程的 CPU、类、线程等。

远程连接:

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

主要功能

  1. 生成/读取堆内存快照
  2. 查看 JVM 参数和系统属性
  3. 查看运行中的虚拟机进程
  4. 生成/读取线程快照
  5. 程序资源的实时监控
  6. 其他功能:
    1. JMX 代理连接
    2. 远程环境监控
    3. CPU 分析和内存分析

Eclipse MAT

分析堆 dump 文件

//todo

JProfiler

官网下载地址:https://www.ej-technologies.com/products/jprofiler/overview.html

1
IDEA插件

Java 中内存泄漏的 8 种情况

  1. 静态集合类

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

    1
    2
    3
    4
    5
    6
    7
    8
    public class MemoryLeak {
    static List list = new ArrayList();

    public void comTests() {
    Object obj = new Object();
    list.add(obj);
    }
    }
  2. 单例模式

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

  3. 内部类持有外部类

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

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

    在对数据库进行操作的过程中,首先需要建立与数据库的连接,当不再使用时,需要调用 close 方法来释放与数据库的连接。只有连接被关闭后,垃圾回收器才会回收对应的对象。

    否则,如果在访问数据库的过程中,对 Connection、Statement 或 ResultSet 不显性地关闭,将会造成大量的对象无法被回收,从而引起内存泄漏。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public static void main(String[] args) {
    try {
    Connection conn = null;
    Class.forName("com.mysql.jdbc.Driver");
    conn = DriverManager.getConnection("url", "", "");
    Statement stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery("...");
    }catch (Exception e) {
    // 异常日志
    }finally {
    // 1.关闭结果集 Statement
    // 2.关闭声明的对象 ResultSet
    // 3.关闭连接 Connection
    }
    }
  5. 变量不合理的作用域

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

    1
    2
    3
    4
    5
    6
    7
    8
    public class UsingRandom {
    private String msg;

    public void receiveMsg() {
    readFromNet(); // 从网络中接受数据保存到 msg 中
    saveDB(); // 把 msg 保存到数据库中
    }
    }

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

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

  6. 改变哈希值

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

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

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

    当我们想把自己定义的类保存到散列表的时候,需要保证对象的 hashCode 不可改变。

  7. 缓存泄露

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

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

  8. 监听器和回调

    内存泄漏第三个常见来源是监听器和其他回调,如果客户端在你实现的 API 中注册回调,却没有显示的取消,那么就会积聚。

    需要确保回调立即被当作垃圾回收的最佳方法是只保存它的弱引用,例如将他们保存成为 WeakHashMap 中的键。

Arthas

官方使用文档:https://arthas.aliyun.com/zh-cn/

1
wget https://alibaba.github.io/arthas-boot.jar
1
2
3
4
5
6
7
java -jar arthas-boot.jar
java -jar arthas-boot.jar [PID]
查看日志:cat ~/logs/arthas/arthas.log
查看帮助:java -jar arthas-boot.jar -h
Web Console http://127.0.0.1:8563
使用 quit\exit:退出当前客户端。
使用 stop\shutdown:关闭 Arthas 服务端,并退出所有客户端。

JVM运行时参数

类型一:标准参数选项

运行 Java 或者 java -help 可以看到所有的标准选项。

类型二:-X 参数选项

运行 java -X 命令可以看到所有的 X 选项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-Xmixed								混合模式执行(默认)
-Xint 仅解释模式执行
-Xcomp 仅采用即时编译器模式
-Xbootclasspath:<用;分隔的目录和 zip/jar 文件> 设置搜索路径以引导类和资源
-Xbootclasspath/a:<用;分隔的目录和 zip/jar 文件> 附加在引导类路径末尾
-Xbootclasspath/p:<用;分隔的目录和 zip/jar 文件> 置于引导类路径之前
-Xdiag 显示附加诊断消息
-Xnoclassgc 禁用类垃圾收集
-Xincgc 启用增量垃圾收集
-Xloggc:<file> 将 GC 状态记录在文件中(带时间戳)
-Xbatch 禁用后台编译
-Xms<size> 设置初始 Java 堆大小
-Xmx<size> 设置最大 Java 堆大小
-Xss<size> 设置 Java 线程堆栈大小
-Xprof 输出 CPU 配置文件数据
-Xfuture 启用最严格的检查,预期将来的默认值
-Xrs 减少 Java/VM 对操作系统信号的使用
-Xcheck:jni 对 JNI 函数执行其他检查
-Xshare:off 不尝试使用共享类数据
-Xshare:auto 在可能的情况下使用共享类数据(默认)
-Xshare:on 要求使用共享类数据,否则将失败
-XshowSettings 显示所有设置并继续
-XshowSettings:all 显示所有设置并继续
-XshowSettings:vm 显示所有与 VM 相关的设置并继续
-XshowSettings:properties 显示所有属性设置并继续
-XshowSettings:locale 显示所有与区域设置相关的设置并继续

-Xmx -Xms -Xss 属于 XX 参数?

  • -Xms<size>:设置初始 Java 堆大小,等价于 -XX:InitialHeapSize
  • -Xmx<size>:设置最大 Java 堆大小,等价于 -XX:MaxHeapSize
  • -Xss<size>:设置 Java 线程堆栈大小,-XX:ThreadStackSize

类型三:-XX 参数选项

特点

  • 非标准化参数
  • 使用的最多的参数类型
  • 实验性选项
  • -XX 开头

用于开发和调试 JVM。

打印设置的 XX 选项及值

  • -XX:+PrintCommandLineFlags:可以让在程序运行前打印出用户手动设置或者 JVM 自动设置的 XX 选项。
  • -XX:+PrintFlagsInitial:表示打印所有 XX 选项的默认值。
  • -XX:+PrintFlagsFinal:表示打印出 XX 选项在运行程序时生效的值。
  • -XX:+PrintVMOptions:打印 JVM 的参数。

堆、栈、方法区等内存大小设置

栈:

  • ```
    -Xss128k

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    - 设置每个线程的栈大小为 128k。
    - 等价于 `-XX:ThreadStackSize=128k`。

    堆内存:

    - `-Xms3550m`:等价于 `-XX:InitialHeapSize`,设置 JVM 初始堆内存为 3550M。

    - `-Xmx3550m`:等价于 `-XX:MaxHeapSize`,设置 JVM 最大堆内存为 3550M。

    - ```
    -Xmn2g
    • 设置年轻代大小为 2G。
    • 官方推荐配置为整个堆大小的 3/8。
  • -XX:NewSize=1024m:设置年轻代初始值为 1024M。

  • -XX:MaxNewSize=1024m:设置年轻代最大值为 1024M。

  • -XX:SurvivorRatio=8:设置年轻代中 Eden 区与一个 Survivor 区的比值,默认为 8。

  • -XX:+UseAdaptiveSizePolicy:自动选择各区大小比例。

  • -XX:NewRatio=4:设置老年代与年轻代(包括 1 个 Eden 和 2 个 Survivor 区)的比值。

  • ```
    -XX:PretenureSizeThreadshold=1024

    1
    2
    3
    4
    5
    6

    - 设置让大于此阈值的对象直接分配在老年代,单位为字节。
    - 只对 Serial、ParNew 收集器有效。

    - ```
    -XX:MaxTenuringThreshold=15
    • 默认值为 15。
    • 新生代每次 MinorGC 后,还存活的对象年龄 +1,当对象的年龄大于设置的这个值时就进入老年代。
  • -XX:+PrintTenuringDistribution:让 JVM 在每次 MinorGC 后打印出当前使用的 Survivor 中对象的年龄分布。

  • -XX:TargetSurvivoRatio:表示 MinorGC 结束后 Survivor 区域中占用空间的期望比例。

方法区:

  • 永久代:
    • -XX:PermSize=256m:设置永久代初始值为 256M。
    • -XX:MaxPermSize=256m:设置永久代最大值为 256M。
  • 元空间:
    • -XX:MetaspaceSize:初始空间大小。
    • -XX:MaxMetaspaceSize:最大空间,默认没有限制。
    • -XX:+UseCompressedOops:压缩对象指针。
    • -XX:CompressedClassSpaceSize:设置 Class Metaspace 的大小,默认 1G。

直接内存:

  • -XX:MaxDirectMemorySize:指定 DirectMemory 容量,若未指定,则默认与 Java 堆最大值一样。

OutOfMemory 相关的选项

  • -XX:+HeapDumpOnOutOfMemoryError:表示在内存出现 OOM 的时候,把 Heap 转存(Dump)到文件以便后续分析。
  • -XX:+HeapDumpBeforeFullGC:表示在出现 FullGC 之前,生成 Heap 转储文件。
  • -XX:HeapDumpPath=<path>:指定 Heap 转存文件的存储路径。
  • -XX:OnOutOfMemoryError:指定一个可行性程序或者脚本的路径,当发生 OOM 的时候,去执行这个脚本。

对 OnOutOfMemoryError 的运维处理

以部署在 Linux 系统 /opt/Server 目录下的 Server.jar 为例

  1. 在 run.sh 启动脚本添加 JVM 参数:-XX:OnOutOfMemoryError=/opt/Server/restart.sh
  2. restart.sh 脚本

Linux 环境:

1
2
3
4
#!/bin/bash
pid=${ps -ef | grep Server.jar | awk '{if($8=="java") {print $2}}'}
kill -9 pid
cd /opt/Server/;sh run.sh

Windows 环境:

1
2
3
4
echo off
wmic process where Name='java.exe' delete
cd D:\Server
start run.bat

总结

JVM系统系学习结束,革命尚未成功,同志还需努力。

《深入理解Java虚拟机》

https://zzc-sso.oss-cn-hangzhou.aliyuncs.com/bolg/JVM/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA%EF%BC%9AJVM%E9%AB%98%E7%BA%A7%E7%89%B9%E6%80%A7%E4%B8%8E%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%EF%BC%88%E7%AC%AC3%E7%89%88%EF%BC%89%E5%91%A8%E5%BF%97%E6%98%8E.pdf