Tomcat 内存调优
Memory Tuning
概述
内存调优是Tomcat性能优化的核心。本文介绍JVM内存配置、垃圾回收优化、内存泄漏检测与解决方案。
1. JVM内存配置策略
1.1 基础内存配置
# setenv.sh - 不同规模应用的内存配置
# 小型应用 (1-2GB)
export CATALINA_OPTS="$CATALINA_OPTS -Xms512m -Xmx1024m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:NewRatio=4"
# 中型应用 (2-8GB)
export CATALINA_OPTS="$CATALINA_OPTS -Xms2048m -Xmx4096m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:NewRatio=3 -XX:SurvivorRatio=8"
# 大型应用 (8GB+)
export CATALINA_OPTS="$CATALINA_OPTS -Xms8192m -Xmx8192m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=1024m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:NewRatio=2"
1.2 自动内存配置脚本
#!/bin/bash
# auto-memory-config.sh
get_optimal_memory() {
total_mem_gb=$(free -g | awk 'NR==2{print $2}')
if [ $total_mem_gb -le 2 ]; then
heap_size="1024m"; metaspace="256m"
elif [ $total_mem_gb -le 4 ]; then
heap_size="2048m"; metaspace="512m"
elif [ $total_mem_gb -le 8 ]; then
heap_size="4096m"; metaspace="512m"
else
heap_size="8192m"; metaspace="1024m"
fi
cat > "$CATALINA_HOME/bin/setenv.sh" << EOF
export CATALINA_OPTS="\$CATALINA_OPTS -Xms$heap_size -Xmx$heap_size"
export CATALINA_OPTS="\$CATALINA_OPTS -XX:MetaspaceSize=$metaspace"
EOF
echo "已配置: 堆=$heap_size, 元空间=$metaspace"
}
get_optimal_memory
2. 垃圾回收器优化
2.1 G1GC配置
# G1GC - 推荐用于大堆内存
export CATALINA_OPTS="$CATALINA_OPTS -XX:+UseG1GC"
export CATALINA_OPTS="$CATALINA_OPTS -XX:MaxGCPauseMillis=200"
export CATALINA_OPTS="$CATALINA_OPTS -XX:G1HeapRegionSize=16m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:G1NewSizePercent=20"
export CATALINA_OPTS="$CATALINA_OPTS -XX:G1MaxNewSizePercent=40"
export CATALINA_OPTS="$CATALINA_OPTS -XX:InitiatingHeapOccupancyPercent=45"
2.2 ParallelGC配置
# ParallelGC - 适用于吞吐量优先
export CATALINA_OPTS="$CATALINA_OPTS -XX:+UseParallelGC"
export CATALINA_OPTS="$CATALINA_OPTS -XX:ParallelGCThreads=8"
export CATALINA_OPTS="$CATALINA_OPTS -XX:MaxGCPauseMillis=150"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+UseAdaptiveSizePolicy"
2.3 GC日志配置
# Java 8 GC日志
export CATALINA_OPTS="$CATALINA_OPTS -XX:+PrintGCDetails"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+PrintGCTimeStamps"
export CATALINA_OPTS="$CATALINA_OPTS -Xloggc:$CATALINA_HOME/logs/gc.log"
# Java 11+ GC日志
export CATALINA_OPTS="$CATALINA_OPTS -Xlog:gc*:$CATALINA_HOME/logs/gc.log:time"
3. 内存监控
3.1 内存监控工具
// MemoryMonitor.java
package com.example.memory;
import java.lang.management.*;
public class MemoryMonitor {
public void printMemoryInfo() {
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
// 堆内存
MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
double usedMB = heapUsage.getUsed() / 1024.0 / 1024.0;
double maxMB = heapUsage.getMax() / 1024.0 / 1024.0;
double percent = (heapUsage.getUsed() * 100.0) / heapUsage.getMax();
System.out.printf("堆内存: %.2f MB / %.2f MB (%.1f%%)%n", usedMB, maxMB, percent);
// GC统计
for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) {
System.out.printf("GC %s: 次数=%d, 时间=%dms%n",
gcBean.getName(), gcBean.getCollectionCount(), gcBean.getCollectionTime());
}
}
public boolean isMemoryPressureHigh() {
MemoryUsage usage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
return (usage.getUsed() * 100.0 / usage.getMax()) > 80.0;
}
}
3.2 内存监控脚本
#!/bin/bash
# memory-monitor.sh
JAVA_PID=$(jps -l | grep Bootstrap | awk '{print $1}')
monitor_memory() {
if [ -z "$JAVA_PID" ]; then
echo "Tomcat进程未找到"
return
fi
echo "=== 内存监控 $(date) ==="
# 堆内存使用
jstat -gc $JAVA_PID | awk 'NR==2 {
used = ($2 + $3 + $4) / 1024
total = ($1 + $2 + $3 + $4 + $5) / 1024
printf "堆内存: %.2f MB / %.2f MB (%.1f%%)\n", used, total, used*100/total
}'
# GC统计
jstat -gccapacity $JAVA_PID | awk 'NR==2 {
printf "新生代容量: %.2f MB, 老年代容量: %.2f MB\n", $4/1024, $10/1024
}'
}
case "$1" in
"once") monitor_memory ;;
"watch")
while true; do
monitor_memory
sleep 30
done
;;
*) echo "用法: $0 {once|watch}" ;;
esac
4. 内存泄漏检测
4.1 内存泄漏检测脚本
#!/bin/bash
# leak-detector.sh
JAVA_PID=$(jps -l | grep Bootstrap | awk '{print $1}')
# 生成堆转储
generate_heap_dump() {
timestamp=$(date +%Y%m%d_%H%M%S)
dump_file="/tmp/tomcat_heap_$timestamp.hprof"
echo "生成堆转储: $dump_file"
jcmd $JAVA_PID GC.run
jcmd $JAVA_PID VM.gc
jhsdb jmap --heap-dump="$dump_file" --pid $JAVA_PID
echo "堆转储完成: $dump_file"
}
# 分析内存使用
analyze_memory() {
echo "=== 内存使用分析 ==="
# 堆直方图
jcmd $JAVA_PID GC.class_histogram | head -20
# 线程分析
echo "线程状态统计:"
jstack $JAVA_PID | grep "java.lang.Thread.State" | sort | uniq -c
}
case "$1" in
"dump") generate_heap_dump ;;
"analyze") analyze_memory ;;
*) echo "用法: $0 {dump|analyze}" ;;
esac
4.2 内存泄漏预防
// MemoryLeakPrevention.java
package com.example.memory;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.sql.Driver;
import java.sql.DriverManager;
import java.util.Enumeration;
public class MemoryLeakPrevention implements ServletContextListener {
@Override
public void contextDestroyed(ServletContextEvent sce) {
// 清理JDBC驱动
cleanupJDBCDrivers();
// 清理线程
cleanupThreads();
// 强制GC
System.gc();
}
private void cleanupJDBCDrivers() {
try {
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
if (driver.getClass().getClassLoader() == getClass().getClassLoader()) {
DriverManager.deregisterDriver(driver);
}
}
} catch (Exception e) {
System.err.println("清理JDBC驱动失败: " + e.getMessage());
}
}
private void cleanupThreads() {
Thread[] threads = new Thread[Thread.activeCount()];
Thread.enumerate(threads);
for (Thread thread : threads) {
if (thread != null && thread.isAlive() &&
!thread.isDaemon() &&
thread.getClass().getClassLoader() == getClass().getClassLoader()) {
thread.interrupt();
}
}
}
}
5. 内存优化最佳实践
5.1 JVM参数优化
# 生产环境推荐配置
export CATALINA_OPTS="$CATALINA_OPTS -server"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+DisableExplicitGC"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+UseCompressedOops"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+UseStringDeduplication"
export CATALINA_OPTS="$CATALINA_OPTS -XX:+OptimizeStringConcat"
5.2 内存监控告警
#!/bin/bash
# memory-alert.sh
check_memory_usage() {
JAVA_PID=$(jps -l | grep Bootstrap | awk '{print $1}')
THRESHOLD=80
usage=$(jstat -gc $JAVA_PID | awk 'NR==2 {
used=($2+$3+$4); total=($1+$2+$3+$4+$5)
print int(used*100/total)
}')
if [ $usage -gt $THRESHOLD ]; then
echo "警告: 内存使用率 ${usage}% 超过阈值 ${THRESHOLD}%"
# 发送告警邮件
echo "Tomcat内存使用率过高: ${usage}%" | mail -s "内存告警" admin@example.com
fi
}
check_memory_usage
5.3 内存优化清单
#!/bin/bash
# memory-checklist.sh
echo "=== Tomcat内存优化检查清单 ==="
check_item() {
local desc="$1"
local cmd="$2"
if eval "$cmd"; then
echo "✓ $desc"
else
echo "✗ $desc"
fi
}
# 检查JVM参数
check_item "设置了初始堆大小" "ps aux | grep java | grep -q Xms"
check_item "设置了最大堆大小" "ps aux | grep java | grep -q Xmx"
check_item "配置了元空间" "ps aux | grep java | grep -q MetaspaceSize"
check_item "启用了压缩指针" "ps aux | grep java | grep -q UseCompressedOops"
# 检查GC配置
check_item "配置了GC日志" "ps aux | grep java | grep -q 'gc.*log'"
check_item "选择了合适的GC" "ps aux | grep java | grep -qE 'UseG1GC|UseParallelGC'"
echo "内存优化检查完成"
小结
通过本文学习,你应该掌握:
- JVM内存配置策略和最佳实践
- 不同垃圾回收器的选择和调优
- 内存使用监控和分析方法
- 内存泄漏检测和预防技术
- 生产环境内存优化配置
- 内存问题诊断和解决方案
下一篇文章将介绍Tomcat集群配置技术。