JVM 调优

JVM 调优

一、调优命令

Sun JDK监控和故障处理命令有jps jstat jmap jhat jstack jinfo 。

1.1、jps

JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程。

1.2、jstat

JVM statistics Monitoring是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。

1、基本语法:

# 基本格式
jstat -<option> <vmid> [interval] [count]

# 常用示例
jstat -gc 1234 1000 10  # 每秒输出进程号1234的GC信息,共输出10次

2、查看类加载统计:

# 查看类加载信息
jstat -class <pid>

# 输出示例:
Loaded  Bytes  Unloaded  Bytes     Time   
  7035  14506        0     0       3.67

# 字段说明:
Loaded    : 加载class的数量
Bytes     : 加载class的字节数
Unloaded  : 未加载class的数量
Bytes     : 未加载class的字节数
Time      : 加载时间

3、查看GC统计:

# 查看GC统计信息
jstat -gc <pid>

# 输出示例:
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
1024.0 1024.0   0.0    0.0    8192.0   8171.9   20480.0   20480.0   4864.0 3720.0 512.0  324.8    2    0.002   1      0.003    0.005

# 字段说明:
S0C: Survivor0区容量
S1C: Survivor1区容量
S0U: Survivor0区已使用
S1U: Survivor1区已使用
EC : Eden区容量
EU : Eden区已使用
OC : 老年代容量
OU : 老年代已使用
MC : 方法区容量
MU : 方法区已使用
YGC: 年轻代GC次数
YGCT: 年轻代GC时间
FGC: Full GC次数
FGCT: Full GC时间
GCT: 总GC时间

4、查看GC详细信息:

# 查看新生代GC统计
jstat -gcnew <pid>

# 输出示例:
 S0C    S1C    S0U    S1U   TT MTT  DSS      EC       EU     YGC     YGCT  
 1024.0 1024.0    0.0  1024.0  1  15 1024.0   8192.0   7623.5      2    0.002

# 字段说明:
TT  : Tenuring threshold(晋升阈值)
MTT : Maximum tenuring threshold(最大晋升阈值)
DSS : Desired survivor size(期望的survivor大小)

实际使用示例:

# 监控内存使用情况
while true; do
    # 每5秒输出一次GC信息
    jstat -gc $(jps | grep MyApp | awk '{print $1}') 5000
done

# 输出到文件进行分析
jstat -gc $(jps | grep MyApp | awk '{print $1}') 1000 > gc.log

5、性能分析脚本:

#!/bin/bash
# gc_monitor.sh

PID=$1
INTERVAL=${2:-1000}  # 默认1000ms
COUNT=${3:-""}       # 默认持续输出

if [ -z "$PID" ]; then
    echo "Usage: $0 <pid> [interval] [count]"
    exit 1
fi

# 输出表头
echo "Time YGC YGCT FGC FGCT GCT"

# 监控GC情况
jstat -gc $PID $INTERVAL $COUNT | awk '
    NR>1 {
        printf "%s %d %.3f %d %.3f %.3f\n", 
        strftime("%H:%M:%S"), 
        $15,    # YGC
        $16,    # YGCT
        $17,    # FGC
        $18,    # FGCT
        $19     # GCT
    }'

6、内存分析脚本:

#!/bin/bash
# memory_monitor.sh

PID=$1
INTERVAL=${2:-1000}
COUNT=${3:-""}

if [ -z "$PID" ]; then
    echo "Usage: $0 <pid> [interval] [count]"
    exit 1
fi

# 输出表头
echo "Time Eden% Old% Meta%"

# 监控内存使用率
jstat -gc $PID $INTERVAL $COUNT | awk '
    NR>1 {
        eden_used=$6
        eden_capacity=$5
        old_used=$8
        old_capacity=$7
        meta_used=$10
        meta_capacity=$9
        
        printf "%s %.1f%% %.1f%% %.1f%%\n",
        strftime("%H:%M:%S"),
        eden_used/eden_capacity*100,
        old_used/old_capacity*100,
        meta_used/meta_capacity*100
    }'

7、常见用法:

# 1. 查看类加载情况
jstat -class $(jps | grep MyApp | awk '{print $1}')

# 2. 查看GC情况
jstat -gc $(jps | grep MyApp | awk '{print $1}')

# 3. 查看JIT编译情况
jstat -compiler $(jps | grep MyApp | awk '{print $1}')

# 4. 持续监控
jstat -gcutil $(jps | grep MyApp | awk '{print $1}') 1000

# 5. 输出到文件
jstat -gc $(jps | grep MyApp | awk '{print $1}') 1000 > gc.log

1.3、jmap

1、基本语法:

# 基本格式
jmap [options] <pid>

# 常用选项
-heap         # 显示堆内存信息
-histo        # 显示对象统计信息
-dump         # 生成堆转储文件

查看堆内存信息:

# 查看堆内存概况
jmap -heap <pid>

# 输出示例:
Attaching to process ID 1234, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.271-b09

using thread-local object allocation.
Parallel GC with 4 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 2147483648 (2048.0MB)
   NewSize                  = 715653120 (682.5MB)
   MaxNewSize               = 715653120 (682.5MB)
   OldSize                  = 1431830528 (1365.5MB)
   NewRatio                 = 2
   SurvivorRatio           = 8
   MetaspaceSize           = 21807104 (20.8MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize        = 17592186044415 MB
   G1HeapRegionSize        = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 679477248 (648.0MB)
   used     = 424089480 (404.44MB)
   free     = 255387768 (243.56MB)
   62.41% used
From Space:
   capacity = 18087936 (17.25MB)
   used     = 0 (0.0MB)
   free     = 18087936 (17.25MB)
   0.0% used
To Space:
   capacity = 18087936 (17.25MB)
   used     = 0 (0.0MB)
   free     = 18087936 (17.25MB)
   0.0% used
PS Old Generation
   capacity = 1431830528 (1365.5MB)
   used     = 474547616 (452.56MB)
   free     = 957282912 (912.94MB)
   33.14% used

2、生成堆转储文件:

# 生成堆转储文件
jmap -dump:format=b,file=heap.hprof <pid>

# 在发生OOM时自动生成堆转储文件
jmap -dump:live,format=b,file=heap.hprof <pid>

# 使用示例:
public class HeapDumpDemo {
    public static void main(String[] args) {
        List<byte[]> list = new ArrayList<>();
        while (true) {
            list.add(new byte[1024 * 1024]); // 1MB
            Thread.sleep(100);
        }
    }
}

3、查看对象统计信息:

# 显示堆中对象统计信息
jmap -histo <pid>

# 只显示存活对象
jmap -histo:live <pid>

# 输出示例:
 num     #instances         #bytes  class name
----------------------------------------------
   1:        500000       20000000  java.lang.String
   2:         30000       15000000  byte[]
   3:          1000        800000  java.lang.Class

4、实际应用脚本:

#!/bin/bash
# heap_monitor.sh

PID=$1
DUMP_PATH=${2:-"./dumps"}
INTERVAL=${3:-3600}  # 默认每小时

if [ -z "$PID" ]; then
    echo "Usage: $0 <pid> [dump_path] [interval]"
    exit 1
fi

mkdir -p $DUMP_PATH

while true; do
    DATE=$(date +%Y%m%d_%H%M%S)
    DUMP_FILE="$DUMP_PATH/heap_$DATE.hprof"
    
    # 生成堆转储文件
    jmap -dump:live,format=b,file=$DUMP_FILE $PID
    
    # 生成对象统计信息
    jmap -histo:live $PID > "$DUMP_PATH/histo_$DATE.txt"
    
    sleep $INTERVAL
done

5、内存泄漏分析:

#!/bin/bash
# leak_analyzer.sh

PID=$1
THRESHOLD=${2:-90}  # 默认内存使用率阈值

if [ -z "$PID" ]; then
    echo "Usage: $0 <pid> [threshold]"
    exit 1
fi

while true; do
    # 获取堆使用率
    HEAP_USAGE=$(jmap -heap $PID | grep "used" | awk '{sum+=$3} END {print sum/1024/1024}')
    
    if [ $(echo "$HEAP_USAGE > $THRESHOLD" | bc) -eq 1 ]; then
        echo "Memory usage above threshold: $HEAP_USAGE MB"
        DUMP_FILE="heap_$(date +%Y%m%d_%H%M%S).hprof"
        jmap -dump:live,format=b,file=$DUMP_FILE $PID
    fi
    
    sleep 60
done

6、常见问题分析:

# 1. 查看最大的对象
jmap -histo:live <pid> | sort -k 3 -n -r | head -n 20

# 2. 检查字符串常量池
jmap -clstats <pid>

# 3. 分析内存泄漏
jmap -dump:live,format=b,file=leak.hprof <pid>

# 4. 查看类加载情况
jmap -clstats <pid>

7、性能优化建议:

# 1. 定期监控
0 * * * * /path/to/heap_monitor.sh <pid>

# 2. 设置告警
if [ $(jmap -heap <pid> | grep "used" | awk '{print $3}') -gt 1000000000 ]; then
    echo "High memory usage alert!"
fi

# 3. 自动清理旧转储文件
find /path/to/dumps -name "heap_*.hprof" -mtime +7 -delete

1.4、jhat

jhat 是一个用于分析 Java 堆转储文件的工具。它可以帮助开发者查看和分析 Java 应用程序的内存使用情况。
1、基本语法:

jhat [options] <heap_dump_file>

2、生成堆转储文件:
在使用 jhat 之前,首先需要生成一个堆转储文件。可以使用 jmap 或 jcmd 命令来生成堆转储文件。

# 使用 jmap 生成堆转储文件
jmap -dump:live,format=b,file=heap_dump.hprof <pid>

# 或者使用 jcmd
jcmd <pid> GC.heap_dump filename=heap_dump.hprof

3、使用 jhat 分析堆转储文件:

# 启动 jhat 分析堆转储文件
jhat heap_dump.hprof

4、jhat 启动后的输出:
当你运行 jhat 后,它会启动一个 HTTP 服务器,通常在 http://localhost:7000 上。你可以在浏览器中访问这个地址来查看分析结果。

5、jhat 的常用选项:

-J<flag>: 传递参数给 JVM。例如,-J-Xmx512m 可以设置最大堆大小。
-port <port>: 指定 jhat 服务器的端口,默认是 7000。
-h 或 --help: 显示帮助信息。

6、示例:

# 生成堆转储文件
jmap -dump:live,format=b,file=heap_dump.hprof <pid>

# 使用 jhat 分析堆转储文件
jhat heap_dump.hprof

# 访问分析结果
# 打开浏览器,访问 http://localhost:7000

7、分析结果:
在浏览器中,你将看到以下信息:

  • Classes: 显示堆中所有类的实例数量和占用内存。
  • Instances: 显示每个类的实例详细信息。
  • Paths: 显示从根对象到特定对象的路径,帮助分析内存泄漏。

8、使用 jhat 的注意事项:
性能: jhat 在处理大型堆转储文件时可能会消耗大量内存和时间。
过时: jhat 在某些情况下可能不再是最佳选择,考虑使用更现代的工具,如 Eclipse Memory Analyzer (MAT) 或 VisualVM。

9、现代替代方案:
Eclipse Memory Analyzer (MAT): 提供更强大的分析功能和图形界面。
VisualVM: 也可以分析堆转储文件,并提供实时监控功能。

1.5、jstack

jstack 是一个用于生成 Java 线程堆栈跟踪的工具,通常用于分析 Java 应用程序的线程状态和死锁情况。

1、基本语法:

jstack [options] <pid>

2、查看线程堆栈:

# 查看指定进程的线程堆栈
jstack <pid>

3、示例:
假设你有一个 Java 应用程序正在运行,进程 ID 为 12345,你可以使用以下命令查看线程堆栈:

jstack 12345

4、输出示例:
jstack 的输出将显示所有线程的状态,包括线程名称、状态、锁信息等。例如:

"main" #1 prio=5 os_prio=0 tid=0x00007f8c4c001800 nid=0x1b03 waiting for monitor entry [0x00007f8c4c7f2000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting to lock <0x00000000f8c4c8b0> (a java.lang.Object)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.Thread.join(Thread.java:1252)
        at com.example.MyClass.main(MyClass.java:10)

5、生成线程转储文件
如果想将线程堆栈输出保存到文件中,可以使用重定向:

jstack 12345 > thread_dump.txt

6、检查死锁
jstack 还可以帮助你检查死锁情况。你可以在输出中查找 Found one Java-level deadlock 的信息。

# 检查死锁
jstack -l <pid>

1.6、jinfo

jinfo 是一个用于获取和修改 Java 进程的配置信息的工具。它可以帮助开发者查看 JVM 参数、系统属性以及其他运行时信息。

1、基本语法:

# 基本格式
jinfo [option] <pid>

# 常用选项
-flag <name>          # 打印指定名称的参数
-flag [+|-]<name>     # 启用/禁用指定名称的参数
-flag <name>=<value>  # 设置指定参数的值
-flags               # 打印所有参数
-sysprops           # 打印系统属性

2、查看 JVM 参数:

# 查看所有 JVM 参数
jinfo -flags <pid>

# 查看特定参数
jinfo -flag MaxHeapSize <pid>
jinfo -flag UseG1GC <pid>

# 输出示例:
-XX:MaxHeapSize=2147483648
-XX:+UseG1GC

3、查看系统属性:

# 查看所有系统属性
jinfo -sysprops <pid>

# 输出示例:
java.runtime.name=Java(TM) SE Runtime Environment
java.vm.version=25.271-b09
java.home=/path/to/java
user.dir=/path/to/working/directory

4、修改运行时参数:
你可以使用 -flag 选项来修改 JVM 参数。例如,修改最大堆大小:

# 启用/禁用布尔类型参数
jinfo -flag +PrintGC <pid>      # 启用GC日志打印
jinfo -flag -PrintGC <pid>      # 禁用GC日志打印

# 设置数值类型参数
jinfo -flag MaxHeapSize=2147483648 <pid>

5、实际应用示例:

public class JInfoDemo {
    public static void main(String[] args) throws Exception {
        // 打印进程ID
        String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
        System.out.println("Process ID: " + pid);
        
        // 保持程序运行
        Thread.sleep(Integer.MAX_VALUE);
    }
}

6、监控脚本:

#!/bin/bash
# jvm_monitor.sh

PID=$1
INTERVAL=${2:-60}  # 默认60秒

if [ -z "$PID" ]; then
    echo "Usage: $0 <pid> [interval]"
    exit 1
fi

while true; do
    echo "=== $(date) ==="
    
    # 打印堆内存使用情况
    jinfo -flag MaxHeapSize $PID
    jinfo -flag HeapSize $PID
    
    # 打印GC相关参数
    jinfo -flag UseG1GC $PID
    jinfo -flag UseParallelGC $PID
    
    sleep $INTERVAL
done

7、参数检查脚本:

#!/bin/bash
# check_params.sh

PID=$1
PARAM_FILE="jvm_params.txt"

if [ -z "$PID" ]; then
    echo "Usage: $0 <pid>"
    exit 1
fi

# 保存当前参数
jinfo -flags $PID > $PARAM_FILE

# 检查关键参数
echo "Checking critical parameters..."

# 检查堆大小
MAX_HEAP=$(grep "MaxHeapSize" $PARAM_FILE | awk '{print $2}')
if [ $MAX_HEAP -lt 2147483648 ]; then  # 2GB
    echo "Warning: MaxHeapSize is less than 2GB"
fi

# 检查GC参数
if ! grep -q "UseG1GC" $PARAM_FILE; then
    echo "Warning: G1GC is not enabled"
fi

8、常见用法:

# 1. 查看基本信息
jinfo <pid>

# 2. 查看所有标志
jinfo -flags <pid>

# 3. 查看特定标志
jinfo -flag UseG1GC <pid>

# 4. 修改标志
jinfo -flag +PrintGC <pid>

# 5. 查看系统属性
jinfo -sysprops <pid>

二、调优工具

常用调优工具分为两类,jdk 自带监控工具:jconsole 和 jvisualvm,第三方有:MAT (Memory Analyzer Tool)、GChisto。
1、jconsole,Java Monitoring and Management Console 是从 java5 开始,在 JDK 中自带的 java 监控和管理控制台,用于对 JVM 中内存,线程和类等的监控
2、jvisualvm,jdk 自带全能工具,可以分析内存快照、线程快照;监控内存变化、GC 变化等。
3、MAT,Memory Analyzer Tool,一个基于 Eclipse 的内存分析工具,是一个快速、功能丰富的 Javaheap 分析工具,它可以帮助我们查找内存泄漏和减少内存消耗。
4、GChisto,一款专业分析 gc 日志的工具

评论

暂无

添加新评论