JVM生产环境实战指南:从入门到精通的完整攻略
前言
作为一名在生产环境摸爬滚打多年的Java开发者,经历过不少因为JVM配置不当导致的线上故障。本文将分享在实际项目中积累的JVM调优经验,帮你避开那些致命的坑。
前置知识:JVM 自带命令系统指南
一、生产环境常见问题及解决方案
1.1 内存溢出:最常见的线上杀手
场景重现:凌晨3点,监控告警疯狂响起,应用频繁重启,用户无法访问。
快速诊断:
# 快速查看进程状态
jps -ml | grep your-app
# 查看线程状态
jstack pid > thread_dump.txt
# 立即生成内存快照
jmap -dump:live,format=b,file=heap_$(date +%Y%m%d_%H%M%S).hprof <pid>
# 查看内存使用分布
jmap -histo <pid> | head -20
# 分析GC状态
jstat -gc <pid> 1s 5
根因分析:
- 90%的OOM都是因为对象无法被回收
- 常见元凶:ThreadLocal未清理、事件监听器累积、大集合未释放
解决方案:
// 危险代码示例
ThreadLocal<UserContext> userContext = new ThreadLocal<>();
// 正确做法
public class UserContextHolder {
private static final ThreadLocal<UserContext> context = new ThreadLocal<>();
public static void setContext(UserContext ctx) {
context.set(ctx);
}
public static void clearContext() {
context.remove(); // 关键:必须手动清理
}
}
// 在请求结束时清理
@RestController
public class UserController {
@PostMapping("/user")
public Result createUser(@RequestBody User user) {
try {
UserContextHolder.setContext(new UserContext(user));
return userService.createUser(user);
} finally {
UserContextHolder.clearContext(); // 确保清理
}
}
}
其他解决方案:
设计模式
观察者模式:提供取消订阅机制
工厂模式:统一管理对象生命周期
单例模式:避免重复创建大对象
其他内存泄露场景
集合类泄漏
// 示例:Map中的key-value对未及时清理
public class CacheManager {
private static Map<String, Object> cache = new HashMap<>();
public void addToCache(String key, Object value) {
cache.put(key, value); // 如果不清理,会持续积累
}
// 缺少清理机制
}
监听器和回调
// 示例:注册监听器但未注销
public class EventManager {
private List<EventListener> listeners = new ArrayList<>();
public void addListener(EventListener listener) {
listeners.add(listener);
}
// 忘记提供removeListener方法
}
内部类持有外部类引用
// 示例:非静态内部类
public class OuterClass {
private String data = "large data";
public class InnerClass {
public void doSomething() {
// 隐式持有OuterClass的引用
}
}
}
静态变量引用
// 示例:静态集合持有大量对象
public class StaticHolder {
private static List<Object> staticList = new ArrayList<>();
public void addData(Object data) {
staticList.add(data); // 静态引用,GC无法回收
}
}
线程相关泄漏
// 示例:ThreadLocal使用不当
public class ThreadLocalExample {
private static ThreadLocal<LargeObject> threadLocal = new ThreadLocal<>();
public void setData(LargeObject data) {
threadLocal.set(data);
// 忘记调用threadLocal.remove()
}
}
资源未关闭
// 示例:IO流、数据库连接未关闭
public void readFile(String fileName) {
FileInputStream fis = null;
try {
fis = new FileInputStream(fileName);
// 处理文件
} catch (IOException e) {
// 异常处理
}
// 忘记关闭流
}
1.2 应用响应慢:CPU飙升的真相
现象:接口响应时间从200ms暴涨到5秒以上,CPU使用率持续90%+。
排查步骤:
# 1. 找出CPU使用率最高的线程
top -H -p <pid>
# 2. 转换线程ID为16进制
printf "%x\n" <线程ID>
# 3. 分析线程堆栈
jstack <pid> | grep -A 10 <16进制ID>
# 4. 如果是GC导致的,查看GC情况
jstat -gc <pid> 1s 10
真实案例: 系统在某促销活动时CPU飙升,通过上述方法发现是Jackson序列化大量商品对象导致。解决方案是引入对象池:
// 优化前:每次都创建新的ObjectMapper
public String serialize(Object obj) {
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(obj);
}
// 优化后:复用ObjectMapper实例
public class JsonUtil {
private static final ObjectMapper MAPPER = new ObjectMapper();
public static String serialize(Object obj) {
return MAPPER.writeValueAsString(obj);
}
}
1.3 频繁Full GC:性能杀手
症状:应用卡顿,响应时间不稳定,GC日志显示频繁Full GC。
分析工具:
# 查看GC详情
jstat -gcutil <pid> 1s
# 如果Old区使用率持续上升,说明有内存泄漏
# 如果Young区回收频繁,说明新生代太小
调优策略:
# 针对不同场景的GC参数配置
# 高吞吐量场景(批处理系统)
-XX:+UseParallelGC
-XX:ParallelGCThreads=8
-XX:NewRatio=2
# 低延迟场景(在线服务)
-XX:+UseG1GC
-XX:MaxGCPauseMillis=100
-XX:+G1UseAdaptiveIHOP
# 超低延迟场景(交易系统)
-XX:+UnlockExperimentalVMOptions
-XX:+UseZGC
二、JVM参数配置实战
2.1 基础参数
# 生产环境标准配置
java -server \
-Xms8g -Xmx8g \ # 堆内存设置为相同值
-XX:NewRatio=3 \ # 新生代:老年代 = 1:3
-XX:SurvivorRatio=8 \ # Eden:Survivor = 8:1
-XX:MetaspaceSize=256m \ # 元空间初始大小
-XX:MaxMetaspaceSize=512m \ # 元空间最大大小
-XX:+UseG1GC \ # 使用G1垃圾收集器
-XX:MaxGCPauseMillis=200 \ # GC暂停时间目标
-XX:+HeapDumpOnOutOfMemoryError \ # OOM时生成堆转储
-XX:HeapDumpPath=/tmp/heapdump/ \ # 堆转储文件路径
-XX:+PrintGCDetails \ # 打印GC详情
-XX:+PrintGCTimeStamps \ # 打印GC时间戳
-Xloggc:/var/log/gc.log \ # GC日志文件
-XX:+UseGCLogFileRotation \ # GC日志轮转
-XX:NumberOfGCLogFiles=5 \ # 保留5个GC日志文件
-XX:GCLogFileSize=100M \ # 每个GC日志文件大小
-jar application.jar
2.2 容器化环境特殊配置
# Docker环境下的JVM配置
ENV JAVA_OPTS="-server \
-XX:+UseContainerSupport \
-XX:InitialRAMPercentage=70.0 \
-XX:MaxRAMPercentage=80.0 \
-XX:+ExitOnOutOfMemoryError \
-XX:+UseG1GC \
-XX:+UseStringDeduplication"
# Kubernetes环境下的资源感知配置
apiVersion: v1
kind: ConfigMap
metadata:
name: jvm-config
data:
JAVA_OPTS: |
-server
-XX:+UseContainerSupport
-XX:MaxRAMPercentage=75.0
-XX:+UnlockExperimentalVMOptions
-XX:+UseCGroupMemoryLimitForHeap
-XX:+PreferContainerQuotaForCPUCount
2.3 不同应用场景的调优策略
高并发Web应用
# 优化响应时间
-XX:+UseG1GC
-XX:MaxGCPauseMillis=50
-XX:+ParallelRefProcEnabled
-XX:+UseStringDeduplication
-XX:+OptimizeStringConcat
# 优化并发性能
-XX:+UseBiasedLocking
-XX:+DoEscapeAnalysis
-XX:+EliminateLocks
大数据处理应用
# 优化吞吐量
-XX:+UseParallelGC
-XX:+UseParallelOldGC
-XX:ParallelGCThreads=16
-XX:+AggressiveOpts
# 大堆内存配置
-Xms32g -Xmx32g
-XX:NewRatio=1
-XX:+UseLargePages
微服务应用
# 快速启动
-XX:+TieredCompilation
-XX:TieredStopAtLevel=1
-Xverify:none
# 内存效率
-XX:+UseCompressedOops
-XX:+UseCompressedClassPointers
-XX:CompressedClassSpaceSize=256m
三、性能监控与故障诊断
3.1 建立完善的监控体系
关键指标监控
#!/bin/bash
# 生产环境监控脚本
PID=$1
LOG_FILE="jvm_metrics_$(date +%Y%m%d_%H%M%S).log"
echo "timestamp,heap_used,heap_max,gc_count,gc_time,thread_count,cpu_usage" > $LOG_FILE
while true; do
TIMESTAMP=$(date +%s)
# 堆内存使用情况
HEAP_INFO=$(jstat -gc $PID | tail -1 | awk '{print $3+$4+$6+$7 "," $1+$2+$5+$8}')
# GC次数和时间
GC_INFO=$(jstat -gc $PID | tail -1 | awk '{print $12+$14 "," $13+$15}')
# 线程数量
THREAD_COUNT=$(jstack $PID | grep -c "java.lang.Thread.State")
# CPU使用率
CPU_USAGE=$(top -b -n1 -p $PID | tail -1 | awk '{print $9}')
echo "$TIMESTAMP,$HEAP_INFO,$GC_INFO,$THREAD_COUNT,$CPU_USAGE" >> $LOG_FILE
sleep 10
done
告警阈值设置:
- 堆内存使用率 > 80%:告警
- Full GC频率 > 每小时10次:告警
- 单次GC时间 > 5秒:告警
- 线程数 > 1000:告警
3.2 故障诊断实战案例
案例1:内存泄漏排查
某系统在运行一周后出现内存泄漏,通过以下步骤定位:
// 1. 生成两个时间点的内存快照
jmap -dump:format=b,file=heap_before.hprof <pid>
// 运行24小时后
jmap -dump:format=b,file=heap_after.hprof <pid>
// 2. 使用MAT工具对比分析
# 发现com.example.cache.UserCache$Entry对象数量增长10倍
// 3. 检查代码发现缓存没有过期机制
public class UserCache {
private static final Map<String, UserInfo> cache = new ConcurrentHashMap<>();
// 问题:只有put,没有remove
public void putUser(String userId, UserInfo user) {
cache.put(userId, user);
}
}
// 4. 修复:添加过期机制
public class UserCache {
private static final Map<String, CacheEntry> cache = new ConcurrentHashMap<>();
public void putUser(String userId, UserInfo user) {
cache.put(userId, new CacheEntry(user, System.currentTimeMillis()));
}
// 定期清理过期缓存
@Scheduled(fixedRate = 300000) // 5分钟清理一次
public void cleanExpiredCache() {
long now = System.currentTimeMillis();
cache.entrySet().removeIf(entry ->
now - entry.getValue().timestamp > 3600000); // 1小时过期
}
}
案例2:死锁检测与解决
某订单系统出现死锁,通过以下方法解决:
// 1. 检测死锁
jstack <pid> | grep -A 5 -B 5 "Found deadlock"
// 2. 分析死锁链
# 发现:Thread-1持有锁A等待锁B,Thread-2持有锁B等待锁A
// 3. 代码分析
// 问题代码:锁顺序不一致
public void transferMoney(Account from, Account to, BigDecimal amount) {
synchronized (from) {
synchronized (to) {
// 转账逻辑
}
}
}
// 附死锁案例
public class DeadLock {
public static void main(String[] args) {
final Object o1 = new Object();
final Object o2 = new Object();
new Thread(() -> {
try {
new MyDeadLockExample().minsOne(o1, o2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "Thread-1").start();
new Thread(() -> {
try {
new MyDeadLockExample().minsOne(o2, o1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "Thread-2").start();
}
}
class MyDeadLockExample {
private static int shareResource = 10;
public void minsOne(Object o1, Object o2) throws InterruptedException {
synchronized (o1) {
Thread.sleep(1000);
synchronized (o2) {
shareResource--;
}
}
System.out.println(Thread.currentThread().getName() + ": " + shareResource);
}
}
四、高级调优技巧
4.1 JIT编译器优化
# 降低编译阈值,提前优化热点代码
-XX:CompileThreshold=1000
# 内联优化
-XX:MaxInlineSize=128
-XX:FreqInlineSize=325
# 打印编译信息(调试时使用)
-XX:+PrintCompilation
-XX:+PrintInlining
4.2 内存分配优化
# 启用逃逸分析
-XX:+DoEscapeAnalysis
-XX:+EliminateLocks
-XX:+EliminateAllocations
# 大对象直接进入老年代
-XX:PretenureSizeThreshold=1048576 # 1MB
# 优化对象晋升
-XX:MaxTenuringThreshold=7
-XX:+PrintTenuringDistribution
4.3 极端性能优化
NUMA优化
# 在 Java 中,NUMA(非均匀访存模型) 架构影响多线程应用的性能,因为线程访问本地内存比远程内存更快,JVM 通过参数如 -XX:+UseNUMA 优化内存分配和线程调度以减少远程访问开销。
# 查看NUMA信息
numactl --hardware
# 绑定JVM到特定NUMA节点
numactl --cpunodebind=0 --membind=0 java -XX:+UseNUMA -jar app.jar
大页内存优化
# 查看NUMA信息
numactl --hardware
# 绑定JVM到特定NUMA节点
numactl --cpunodebind=0 --membind=0 java -XX:+UseNUMA -jar app.jar
五、容器化最佳实践
5.1 Docker环境优化
# 多阶段构建优化
FROM openjdk:11-jdk-slim AS builder
WORKDIR /app
COPY . .
RUN ./gradlew build -x test
FROM openjdk:11-jre-slim
COPY --from=builder /app/build/libs/app.jar /app.jar
# 容器内JVM参数
ENV JAVA_OPTS="-server \
-XX:+UseContainerSupport \
-XX:MaxRAMPercentage=75.0 \
-XX:+UseG1GC \
-XX:+UseStringDeduplication \
-XX:+ExitOnOutOfMemoryError"
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
CMD ["sh", "-c", "java $JAVA_OPTS -jar /app.jar"]
5.2 Kubernetes环境配置
apiVersion: apps/v1
kind: Deployment
metadata:
name: java-app
spec:
replicas: 3
selector:
matchLabels:
app: java-app
template:
metadata:
labels:
app: java-app
spec:
containers:
- name: java-app
image: java-app:latest
env:
- name: JAVA_OPTS
value: "-server -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
resources:
requests:
memory: "2Gi"
cpu: "500m"
limits:
memory: "4Gi"
cpu: "2000m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 60
periodSeconds: 30
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
六、应急处理预案
6.1 紧急故障处理流程
#!/bin/bash
# 紧急故障处理脚本
PID=$1
BACKUP_DIR="/tmp/jvm_emergency_$(date +%Y%m%d_%H%M%S)"
mkdir -p $BACKUP_DIR
echo "=== 应急处理开始 $(date) ==="
# 1. 保存现场信息
echo "保存JVM状态..."
jstack $PID > $BACKUP_DIR/thread_dump.log
jmap -dump:format=b,file=$BACKUP_DIR/heap_dump.hprof $PID
jstat -gc $PID > $BACKUP_DIR/gc_status.log
jinfo $PID > $BACKUP_DIR/jvm_info.log
# 2. 系统状态
echo "保存系统状态..."
top -b -n1 > $BACKUP_DIR/system_status.log
netstat -anp | grep $PID > $BACKUP_DIR/network_status.log
lsof -p $PID > $BACKUP_DIR/file_handles.log
# 3. 应用日志
echo "保存应用日志..."
tail -10000 /var/log/app.log > $BACKUP_DIR/app_recent.log
# 4. 紧急处理
echo "执行紧急处理..."
# 强制GC
jcmd $PID GC.run
# 检查内存使用率
MEMORY_USAGE=$(jstat -gc $PID | tail -1 | awk '{print ($8/$9)*100}')
if (( $(echo "$MEMORY_USAGE > 90" | bc -l) )); then
echo "内存使用率过高($MEMORY_USAGE%),准备重启..."
# 这里可以调用服务重启脚本
fi
echo "=== 应急处理完成,现场信息保存在: $BACKUP_DIR ==="
6.2 故障自愈机制
#!/bin/bash
# JVM自愈监控脚本
JAVA_PID_FILE="/var/run/java-app.pid"
MAX_MEMORY_USAGE=85
MAX_GC_TIME=10000 # 10秒
CHECK_INTERVAL=30
while true; do
if [ -f "$JAVA_PID_FILE" ]; then
PID=$(cat $JAVA_PID_FILE)
# 检查进程是否存在
if ! ps -p $PID > /dev/null 2>&1; then
echo "进程已死亡,重启应用..."
/etc/init.d/java-app restart
continue
fi
# 检查内存使用率
MEMORY_USAGE=$(jstat -gc $PID | tail -1 | awk '{print ($8/$9)*100}')
if (( $(echo "$MEMORY_USAGE > $MAX_MEMORY_USAGE" | bc -l) )); then
echo "内存使用率过高: $MEMORY_USAGE%,触发GC..."
jcmd $PID GC.run
fi
# 检查GC时间
GC_TIME=$(jstat -gc $PID | tail -1 | awk '{print $13+$15}')
if (( $(echo "$GC_TIME > $MAX_GC_TIME" | bc -l) )); then
echo "GC时间过长: ${GC_TIME}ms,重启应用..."
/etc/init.d/java-app restart
fi
fi
sleep $CHECK_INTERVAL
done
七、总结与建议
7.1 生产环境部署检查清单
JVM参数配置完整(堆内存、GC、监控等)
GC日志配置正确(日志轮转、文件路径)
监控告警配置(内存、GC、线程等)
故障处理脚本准备(内存dump、线程dump)
容器资源限制配置合理
健康检查配置正确
日志收集配置完善
7.2 性能优化建议
预防为主:在开发阶段就要考虑内存使用,避免常见的内存泄漏模式
监控先行:建立完善的监控体系,及时发现问题
逐步优化:不要一次性调整太多参数,每次只调整一个参数并观察效果
压测验证:所有优化都要通过压力测试验证效果
文档记录:记录每次调优的参数变化和效果,便于后续维护
7.3 常见误区避免
误区1:盲目增加堆内存大小,导致GC时间过长
误区2:不关注GC日志,出现问题时无法定位
误区3:在容器环境中不使用容器感知参数
误区4:忽略线程数量限制,导致创建线程失败
误区5:不做压力测试就上线,线上才发现性能问题
通过本文的实战指南,相信你能够更好地应对JVM在生产环境中的各种挑战。记住,JVM调优是一个持续的过程,需要结合具体的业务场景和系统特点来制定合适的策略。
最后的忠告:永远不要在生产环境中进行未经测试的JVM参数调整,任何优化都应该在测试环境中充分验证后再上线。
=============================== 持续完善 =================================
故障现象判断
| 现象 | 可能原因 | 立即执行 | 检查命令 |
| 应用卡死不响应 | 死锁/线程阻塞 | 生成线程dump | jstack <pid> | grep -A 10 BLOCKED |
| 内存持续增长 | 内存泄漏 | 生成堆dump | jmap -dump:live,format=b,file=leak.hprof <pid> |
| CPU使用率100% | 死循环/GC频繁 | 查看热点线程 | top -H -p <pid> |
| 响应时间突然变慢 | Full GC频繁 | 查看GC状态 | jstat -gc <pid> 1s 5 |
| 启动失败 | 参数配置错误 | 检查JVM参数 | jinfo <pid> |
| 内存溢出OOM | 堆内存不足 | 检查内存配置 | java -XX:+PrintFlagsFinal -version | grep HeapSize |
| 网络连接异常 | 连接池/Socket泄漏 | 检查网络连接 | netstat -anp | grep <pid> | wc -l |
问题诊断
一键诊断
#!/bin/bash
# 文件名: jvm_emergency_diagnosis.sh
# 使用: ./jvm_emergency_diagnosis.sh <pid>
PID=$1
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
RESULT_DIR="diagnosis_${PID}_${TIMESTAMP}"
if [ -z "$PID" ]; then
echo "使用方法: $0 <java_process_id>"
echo "可用的Java进程:"
jps -l
exit 1
fi
echo "开始诊断 PID: $PID"
mkdir -p $RESULT_DIR
# 1. 基础信息收集
echo "收集基础信息..."
echo "=== 进程信息 ===" > $RESULT_DIR/basic_info.txt
ps -p $PID -o pid,ppid,cmd,vsz,rss,pcpu,pmem,etime >> $RESULT_DIR/basic_info.txt
echo "=== JVM参数 ===" >> $RESULT_DIR/basic_info.txt
jinfo $PID >> $RESULT_DIR/basic_info.txt
# 2. 内存分析
echo "分析内存状态..."
jmap -histo $PID | head -30 > $RESULT_DIR/memory_histogram.txt
jstat -gc $PID > $RESULT_DIR/gc_status.txt
# 3. 线程分析
echo "分析线程状态..."
jstack $PID > $RESULT_DIR/thread_dump.txt
grep -c "java.lang.Thread.State: BLOCKED" $RESULT_DIR/thread_dump.txt > $RESULT_DIR/blocked_threads_count.txt
grep -c "java.lang.Thread.State: WAITING" $RESULT_DIR/thread_dump.txt > $RESULT_DIR/waiting_threads_count.txt
# 4. 系统资源
echo "检查系统资源..."
echo "=== CPU Top 10 ===" > $RESULT_DIR/system_status.txt
top -b -n1 | head -20 >> $RESULT_DIR/system_status.txt
echo "=== 内存使用 ===" >> $RESULT_DIR/system_status.txt
free -h >> $RESULT_DIR/system_status.txt
echo "=== 磁盘空间 ===" >> $RESULT_DIR/system_status.txt
df -h >> $RESULT_DIR/system_status.txt
# 5. 网络连接
echo "检查网络状态..."
netstat -anp | grep $PID > $RESULT_DIR/network_connections.txt
lsof -p $PID | grep -E "(TCP|UDP)" > $RESULT_DIR/open_sockets.txt
# 6. GC分析
echo "分析GC状态..."
jstat -gc $PID 1s 5 > $RESULT_DIR/gc_realtime.txt
# 7. 智能分析
echo "执行智能分析..."
cat > $RESULT_DIR/analysis_report.txt << EOF
=== JVM诊断报告 ===
诊断时间: $(date)
进程ID: $PID
EOF
# 分析内存使用率
HEAP_USAGE=$(jstat -gc $PID | tail -1 | awk '{used=$3+$4+$6+$7; total=$1+$2+$5+$8; print (used/total)*100}')
echo "堆内存使用率: ${HEAP_USAGE}%" >> $RESULT_DIR/analysis_report.txt
# 分析GC频率
GC_COUNT=$(jstat -gc $PID | tail -1 | awk '{print $12+$14}')
echo "GC总次数: $GC_COUNT" >> $RESULT_DIR/analysis_report.txt
# 分析线程状态
TOTAL_THREADS=$(grep -c "java.lang.Thread.State" $RESULT_DIR/thread_dump.txt)
BLOCKED_THREADS=$(cat $RESULT_DIR/blocked_threads_count.txt)
WAITING_THREADS=$(cat $RESULT_DIR/waiting_threads_count.txt)
echo "线程统计:" >> $RESULT_DIR/analysis_report.txt
echo " 总线程数: $TOTAL_THREADS" >> $RESULT_DIR/analysis_report.txt
echo " 阻塞线程数: $BLOCKED_THREADS" >> $RESULT_DIR/analysis_report.txt
echo " 等待线程数: $WAITING_THREADS" >> $RESULT_DIR/analysis_report.txt
# 问题识别
echo "" >> $RESULT_DIR/analysis_report.txt
echo "=== 问题识别 ===" >> $RESULT_DIR/analysis_report.txt
if (( $(echo "$HEAP_USAGE > 85" | bc -l) )); then
echo "高内存使用警告: 堆内存使用率${HEAP_USAGE}%,建议检查内存泄漏" >> $RESULT_DIR/analysis_report.txt
fi
if [ "$BLOCKED_THREADS" -gt 10 ]; then
echo "线程阻塞警告: $BLOCKED_THREADS 个线程被阻塞,可能存在死锁" >> $RESULT_DIR/analysis_report.txt
fi
if (( $(echo "$GC_COUNT > 100" | bc -l) )); then
echo "GC频繁警告: GC次数过多($GC_COUNT),可能需要调整堆内存大小" >> $RESULT_DIR/analysis_report.txt
fi
echo "诊断完成! 结果保存在: $RESULT_DIR/"
echo "查看分析报告: cat $RESULT_DIR/analysis_report.txt"
问题处理脚本
memory_leak_handler
#!/bin/bash
# 文件名: memory_leak_handler.sh
# 处理内存泄漏问题
PID=$1
THRESHOLD=80
if [ -z "$PID" ]; then
echo "使用方法: $0 <pid>"
exit 1
fi
echo "检查内存泄漏 PID: $PID"
# 检查当前内存使用率
CURRENT_USAGE=$(jstat -gc $PID | tail -1 | awk '{used=$3+$4+$6+$7; total=$1+$2+$5+$8; print (used/total)*100}')
echo "当前堆内存使用率: ${CURRENT_USAGE}%"
if (( $(echo "$CURRENT_USAGE > $THRESHOLD" | bc -l) )); then
echo "内存使用率超过${THRESHOLD}%,开始处理..."
# 1. 生成内存快照
DUMP_FILE="heap_dump_$(date +%Y%m%d_%H%M%S).hprof"
echo "生成内存快照: $DUMP_FILE"
jmap -dump:live,format=b,file=$DUMP_FILE $PID
# 2. 强制GC
echo "执行强制GC..."
jcmd $PID GC.run
# 3. 检查GC后的内存使用率
sleep 5
AFTER_GC_USAGE=$(jstat -gc $PID | tail -1 | awk '{used=$3+$4+$6+$7; total=$1+$2+$5+$8; print (used/total)*100}')
echo "GC后内存使用率: ${AFTER_GC_USAGE}%"
# 4. 分析内存分布
echo "分析内存对象分布..."
jmap -histo $PID | head -20
# 5. 如果GC后仍然很高,可能需要重启
if (( $(echo "$AFTER_GC_USAGE > $THRESHOLD" | bc -l) )); then
echo "GC后内存使用率仍然过高,建议重启应用"
echo "内存快照已保存: $DUMP_FILE"
echo "可使用MAT工具分析: mat $DUMP_FILE"
else
echo "GC后内存使用率正常"
fi
else
echo "内存使用率正常"
fi
deadlock_detector
#!/bin/bash
# 文件名: deadlock_detector.sh
# 检测和处理死锁问题
PID=$1
if [ -z "$PID" ]; then
echo "使用方法: $0 <pid>"
exit 1
fi
echo "检测死锁 PID: $PID"
# 生成线程dump
THREAD_DUMP="thread_dump_$(date +%Y%m%d_%H%M%S).txt"
jstack $PID > $THREAD_DUMP
# 检查是否有死锁
DEADLOCK_COUNT=$(grep -c "Found [0-9]\+ deadlock" $THREAD_DUMP)
if [ $DEADLOCK_COUNT -gt 0 ]; then
echo "发现死锁! 死锁数量: $DEADLOCK_COUNT"
echo "死锁详情:"
grep -A 20 "Found [0-9]\+ deadlock" $THREAD_DUMP
echo "建议处理方案:"
echo "1. 检查代码中的锁使用顺序"
echo "2. 考虑使用超时锁机制"
echo "3. 减少锁的持有时间"
echo "4. 如果紧急,可以考虑重启应用"
else
echo "未发现死锁"
# 检查是否有大量阻塞线程
BLOCKED_COUNT=$(grep -c "java.lang.Thread.State: BLOCKED" $THREAD_DUMP)
if [ $BLOCKED_COUNT -gt 50 ]; then
echo "警告: 发现 $BLOCKED_COUNT 个阻塞线程"
echo "阻塞线程详情:"
grep -B 5 -A 10 "java.lang.Thread.State: BLOCKED" $THREAD_DUMP | head -50
fi
fi
echo "线程dump已保存: $THREAD_DUMP"
紧急情况JVM参数调整
# 场景1: 内存不足,频繁Full GC
# 临时解决方案(需要重启)
-Xms4g -Xmx4g # 增加堆内存
-XX:NewRatio=2 # 调整新生代比例
-XX:+UseG1GC # 使用G1减少STW时间
-XX:MaxGCPauseMillis=200 # 限制GC暂停时间
# 场景2: 应用启动慢
# 快速启动参数
-XX:+TieredCompilation # 开启分层编译
-XX:TieredStopAtLevel=1 # 仅C1编译
-Xverify:none # 跳过字节码验证
-XX:+UseParallelGC # 使用并行GC加速启动
# 场景3: 高并发场景CPU使用率过高
# 并发优化参数
-XX:+UseBiasedLocking # 偏向锁
-XX:+DoEscapeAnalysis # 逃逸分析
-XX:+EliminateLocks # 消除锁
-XX:+UseStringDeduplication # 字符串去重
# 场景4: 容器OOM被杀死
# 容器感知参数
-XX:+UseContainerSupport # 容器支持
-XX:MaxRAMPercentage=75.0 # 最大内存百分比
-XX:+ExitOnOutOfMemoryError # OOM时退出
jvm_monitor
#!/bin/bash
# 文件名: jvm_monitor.sh
# 实时监控JVM性能指标
PID=$1
INTERVAL=${2:-5}
if [ -z "$PID" ]; then
echo "使用方法: $0 <pid> [interval_seconds]"
echo "当前Java进程:"
jps -l
exit 1
fi
echo "实时监控 PID: $PID (间隔: ${INTERVAL}秒)"
echo "按 Ctrl+C 停止监控"
# 创建监控日志文件
LOG_FILE="jvm_monitor_$(date +%Y%m%d_%H%M%S).log"
echo "timestamp,heap_used_mb,heap_max_mb,heap_usage_percent,gc_count,gc_time_ms,thread_count,cpu_percent" > $LOG_FILE
while true; do
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
# 获取堆内存信息
HEAP_INFO=$(jstat -gc $PID 2>/dev/null | tail -1)
if [ $? -eq 0 ]; then
HEAP_USED=$(echo $HEAP_INFO | awk '{printf "%.1f", ($3+$4+$6+$7)/1024}')
HEAP_MAX=$(echo $HEAP_INFO | awk '{printf "%.1f", ($1+$2+$5+$8)/1024}')
HEAP_USAGE=$(echo $HEAP_INFO | awk '{printf "%.1f", (($3+$4+$6+$7)/($1+$2+$5+$8))*100}')
GC_COUNT=$(echo $HEAP_INFO | awk '{print $12+$14}')
GC_TIME=$(echo $HEAP_INFO | awk '{printf "%.1f", ($13+$15)*1000}')
# 获取线程数
THREAD_COUNT=$(jstack $PID 2>/dev/null | grep -c "java.lang.Thread.State" || echo "N/A")
# 获取CPU使用率
CPU_USAGE=$(top -b -n1 -p $PID 2>/dev/null | tail -1 | awk '{print $9}' || echo "N/A")
# 输出到屏幕
printf "\r\033[K"
printf "%s | 堆内存: %sMB/%sMB (%.1f%%) | GC: %s次/%.1fms | 线程: %s | CPU: %s%%" \
"$TIMESTAMP" "$HEAP_USED" "$HEAP_MAX" "$HEAP_USAGE" "$GC_COUNT" "$GC_TIME" "$THREAD_COUNT" "$CPU_USAGE"
# 记录到日志
echo "$TIMESTAMP,$HEAP_USED,$HEAP_MAX,$HEAP_USAGE,$GC_COUNT,$GC_TIME,$THREAD_COUNT,$CPU_USAGE" >> $LOG_FILE
# 检查告警条件
if (( $(echo "$HEAP_USAGE > 85" | bc -l) )); then
echo -e "\n 告警: 堆内存使用率过高 (${HEAP_USAGE}%)"
fi
if [ "$THREAD_COUNT" != "N/A" ] && [ "$THREAD_COUNT" -gt 1000 ]; then
echo -e "\n 告警: 线程数过多 ($THREAD_COUNT)"
fi
else
echo -e "\n 无法获取进程信息,进程可能已退出"
break
fi
sleep $INTERVAL
done
echo -e "\n 监控数据已保存到: $LOG_FILE"
gc_analyzer
#!/bin/bash
# 文件名: gc_analyzer.sh
# 分析GC性能和趋势
PID=$1
DURATION=${2:-300} # 默认监控5分钟
if [ -z "$PID" ]; then
echo "使用方法: $0 <pid> [duration_seconds]"
exit 1
fi
echo "GC性能分析 PID: $PID (持续时间: ${DURATION}秒)"
# 创建临时文件
TEMP_FILE="/tmp/gc_analysis_$$.txt"
RESULT_FILE="gc_analysis_$(date +%Y%m%d_%H%M%S).txt"
# 收集GC数据
echo "收集GC数据..."
jstat -gc $PID 1s $DURATION > $TEMP_FILE
# 分析GC数据
echo "分析GC性能..." > $RESULT_FILE
echo "分析时间: $(date)" >> $RESULT_FILE
echo "监控时长: ${DURATION}秒" >> $RESULT_FILE
echo "================================" >> $RESULT_FILE
# 计算GC统计信息
FIRST_LINE=$(head -2 $TEMP_FILE | tail -1)
LAST_LINE=$(tail -1 $TEMP_FILE)
# 初始和最终的GC次数
INITIAL_YOUNG_GC=$(echo $FIRST_LINE | awk '{print $12}')
FINAL_YOUNG_GC=$(echo $LAST_LINE | awk '{print $12}')
INITIAL_FULL_GC=$(echo $FIRST_LINE | awk '{print $14}')
FINAL_FULL_GC=$(echo $LAST_LINE | awk '{print $14}')
# 计算GC频率
YOUNG_GC_COUNT=$((FINAL_YOUNG_GC - INITIAL_YOUNG_GC))
FULL_GC_COUNT=$((FINAL_FULL_GC - INITIAL_FULL_GC))
echo "Young GC次数: $YOUNG_GC_COUNT" >> $RESULT_FILE
echo "Full GC次数: $FULL_GC_COUNT" >> $RESULT_FILE
echo "Young GC频率: $(echo "scale=2; $YOUNG_GC_COUNT*60/$DURATION" | bc) 次/分钟" >> $RESULT_FILE
echo "Full GC频率: $(echo "scale=2; $FULL_GC_COUNT*60/$DURATION" | bc) 次/分钟" >> $RESULT_FILE
# 分析内存使用趋势
echo "" >> $RESULT_FILE
echo "内存使用趋势:" >> $RESULT_FILE
awk 'NR>1 {
heap_used = $3+$4+$6+$7
heap_total = $1+$2+$5+$8
usage = (heap_used/heap_total)*100
print "时间点", NR-1, "堆使用率:", usage"%"
}' $TEMP_FILE | tail -10 >> $RESULT_FILE
# 性能建议
echo "" >> $RESULT_FILE
echo "性能建议:" >> $RESULT_FILE
if [ $FULL_GC_COUNT -gt $((DURATION/60)) ]; then
echo "Full GC过于频繁,建议增加堆内存大小" >> $RESULT_FILE
fi
if [ $YOUNG_GC_COUNT -gt $((DURATION/5)) ]; then
echo "Young GC较为频繁,建议增加新生代大小" >> $RESULT_FILE
fi
# 清理临时文件
rm -f $TEMP_FILE
echo "分析完成,结果保存在: $RESULT_FILE"
cat $RESULT_FILE
intelligent_diagnosis
#!/bin/bash
# 文件名: intelligent_diagnosis.sh
# 智能诊断JVM问题
PID=$1
if [ -z "$PID" ]; then
echo "使用方法: $0 <pid>"
exit 1
fi
echo "智能诊断系统启动..."
echo "目标进程: $PID"
# 创建诊断报告
REPORT_FILE="intelligent_diagnosis_$(date +%Y%m%d_%H%M%S).txt"
cat > $REPORT_FILE << EOF
=== JVM智能诊断报告 ===
诊断时间: $(date)
进程ID: $PID
诊断系统版本: v2.0
EOF
# 1. 进程健康检查
echo "Step 1: 进程健康检查"
if ! ps -p $PID > /dev/null 2>&1; then
echo "进程不存在或已退出" >> $REPORT_FILE
exit 1
fi
# 2. 内存健康检查
echo "Step 2: 内存健康检查"
MEMORY_INFO=$(jstat -gc $PID 2>/dev/null | tail -1)
if [ $? -eq 0 ]; then
HEAP_USED=$(echo $MEMORY_INFO | awk '{print $3+$4+$6+$7}')
HEAP_TOTAL=$(echo $MEMORY_INFO | awk '{print $1+$2+$5+$8}')
HEAP_USAGE=$(echo "$HEAP_USED $HEAP_TOTAL" | awk '{printf "%.1f", ($1/$2)*100}')
echo "内存使用率: ${HEAP_USAGE}%" >> $REPORT_FILE
if (( $(echo "$HEAP_USAGE > 90" | bc -l) )); then
echo "严重: 内存使用率过高 (${HEAP_USAGE}%)" >> $REPORT_FILE
echo "建议: 立即检查内存泄漏或增加堆内存" >> $REPORT_FILE
elif (( $(echo "$HEAP_USAGE > 80" | bc -l) )); then
echo "警告: 内存使用率较高 (${HEAP_USAGE}%)" >> $REPORT_FILE
echo "建议: 监控内存增长趋势" >> $REPORT_FILE
else
echo "正常: 内存使用率健康 (${HEAP_USAGE}%)" >> $REPORT_FILE
fi
fi
# 3. GC健康检查
echo "Step 3: GC健康检查"
GC_COUNT=$(echo $MEMORY_INFO | awk '{print $12+$14}')
GC_TIME=$(echo $MEMORY_INFO | awk '{print $13+$15}')
echo "GC总次数: $GC_COUNT" >> $REPORT_FILE
echo "GC总时间: ${GC_TIME}秒" >> $REPORT_FILE
if (( $(echo "$GC_TIME > 30" | bc -l) )); then
echo "严重: GC时间过长 (${GC_TIME}秒)" >> $REPORT_FILE
echo "建议: 调整GC策略或增加内存" >> $REPORT_FILE
fi
# 4. 线程健康检查
echo "Step 4: 线程健康检查"
THREAD_DUMP=$(jstack $PID 2>/dev/null)
if [ $? -eq 0 ]; then
TOTAL_THREADS=$(echo "$THREAD_DUMP" | grep -c "java.lang.Thread.State")
BLOCKED_THREADS=$(echo "$THREAD_DUMP" | grep -c "java.lang.Thread.State: BLOCKED")
WAITING_THREADS=$(echo "$THREAD_DUMP" | grep -c "java.lang.Thread.State: WAITING")
echo "线程总数: $TOTAL_THREADS" >> $REPORT_FILE
echo "阻塞线程数: $BLOCKED_THREADS" >> $REPORT_FILE
echo "等待线程数: $WAITING_THREADS" >> $REPORT_FILE
if [ $BLOCKED_THREADS -gt 20 ]; then
echo "严重: 过多阻塞线程 ($BLOCKED_THREADS)" >> $REPORT_FILE
echo "建议: 检查是否存在死锁或锁竞争" >> $REPORT_FILE
fi
if [ $TOTAL_THREADS -gt 2000 ]; then
echo "警告: 线程数过多 ($TOTAL_THREADS)" >> $REPORT_FILE
echo "建议: 检查线程泄漏或使用线程池" >> $REPORT_FILE
fi
fi
# 5. 系统资源检查
echo "Step 5: 系统资源检查"
CPU_USAGE=$(top -b -n1 -p $PID 2>/dev/null | tail -1 | awk '{print $9}')
if [ "$CPU_USAGE" != "" ]; then
echo "CPU使用率: ${CPU_USAGE}%" >> $REPORT_FILE
if (( $(echo "$CPU_USAGE > 80" | bc -l) )); then
echo "警告: CPU使用率过高 (${CPU_USAGE}%)" >> $REPORT_FILE
echo "建议: 检查是否存在死循环或热点代码" >> $REPORT_FILE
fi
fi
# 6. 生成综合评分
echo "Step 6: 综合评分"
SCORE=100
if (( $(echo "$HEAP_USAGE > 90" | bc -l) )); then
SCORE=$((SCORE - 30))
elif (( $(echo "$HEAP_USAGE > 80" | bc -l) )); then
SCORE=$((SCORE - 15))
fi
if [ $BLOCKED_THREADS -gt 20 ]; then
SCORE=$((SCORE - 25))
fi
if [ $TOTAL_THREADS -gt 2000 ]; then
SCORE=$((SCORE - 20))
fi
if (( $(echo "$GC_TIME > 30" | bc -l) )); then
SCORE=$((SCORE - 20))
fi
echo "" >> $REPORT_FILE
echo "=== 综合评分 ===" >> $REPORT_FILE
echo "健康评分: $SCORE/100" >> $REPORT_FILE
if [ $SCORE -ge 80 ]; then
echo "应用健康状态良好" >> $REPORT_FILE
elif [ $SCORE -ge 60 ]; then
echo "应用存在一些问题,建议关注" >> $REPORT_FILE
else
echo "应用存在严重问题,需要立即处理" >> $REPORT_FILE
fi
# 7. 推荐的监控命令
echo "" >> $REPORT_FILE
echo "=== 推荐的监控命令 ===" >> $REPORT_FILE
echo "实时监控: jstat -gc $PID 1s" >> $REPORT_FILE
echo "内存分析: jmap -histo $PID" >> $REPORT_FILE
echo "线程分析: jstack $PID" >> $REPORT_FILE
echo "生成堆dump: jmap -dump:format=b,file=heap.hprof"

浙公网安备 33010602011771号