- 堆内存:你的Java对象住在哪?
简单比喻:堆就像公司的办公区
· 新生代:新员工的工位(频繁进出)
· 老年代:老员工的固定工位(比较稳定)
· 元空间:公司规章制度(方法、类信息)
常见问题:对象怎么从新生代到老年代?
java
public class Employee {
private String name;
public static void main(String[] args) {
Employee emp = new Employee(); // 对象出生在新生代
// 经过15次GC还存活(默认阈值)
// 或者新生代放不下大对象
byte[] bigData = new byte[10 * 1024 * 1024]; // 10MB,直接进老年代
}
}
- GC垃圾回收:保洁阿姨什么时候来?
保洁规则(GC算法):
· 年轻代:Minor GC - 频繁打扫新员工区域
· 老年代:Major GC/Full GC - 全公司大扫除(业务暂停!)
常见问题:什么时候会发生Full GC?
回答:"老年代空间不足、System.gc()调用、或者CMS/G1的并发失败时"
- 类加载:Java程序如何启动?
三步加载过程:
java
// 1. 加载(Load):从磁盘找到class文件
// 2. 链接(Link):验证格式,准备内存
// 3. 初始化(Init):执行静态代码块
public class HelloWorld {
static {
System.out.println("我在初始化阶段执行");
}
}
双亲委派机制(就像公司汇报流程):
员工(自定义类加载器) → 经理(扩展类加载器) → 总监(应用类加载器) → CEO(启动类加载器)
· 好处:防止核心API被篡改
- 常见内存问题与排查
问题1:内存泄漏(东西只进不出)
java
// ❌ 错误示范:静态Map无限增长
public class Cache {
private static Map<String, Object> cache = new HashMap<>();
public void addData(String key, Object value) {
cache.put(key, value); // 永远不remove,导致内存泄漏
}
}
问题2:OOM内存溢出(仓库爆满)
java
// 堆内存溢出
List<byte[]> list = new ArrayList<>();
while(true) {
list.add(new byte[1024 * 1024]); // 不停申请1MB数组
}
// 栈溢出(递归太深)
public void recursive() {
recursive(); // 无限递归
}
- 实战场景
场景1:线上CPU飙高怎么办?
1. top命令找到Java进程
2. top -Hp [pid] 找到高CPU线程
3. printf "%x\n" [线程id] 转16进制
4. jstack [pid] | grep -A 10 [16进制id]
场景2:内存占用高怎么分析?
bash
# 1. 查看堆内存概览
jmap -heap [pid]
# 2. 生成堆转储文件
jmap -dump:format=b,file=heap.hprof [pid]
# 3. 用MAT或JVisualVM分析
- JVM参数调优(记住这几个就够了)
bash
# 启动参数示例
java -Xms512m # 初始堆大小(公司初始办公区)
-Xmx2048m # 最大堆大小(最大可扩张办公区)
-Xmn256m # 新生代大小(新员工区)
-XX:+HeapDumpOnOutOfMemoryError # OOM时自动dump
-XX:HeapDumpPath=./ # dump文件路径
-jar your-app.jar
- 简单诊断工具箱
快速检查命令:
bash
# 查看JVM进程
jps -l
# 查看GC情况
jstat -gc [pid] 1000 5 # 每秒1次,共5次
# 线程快照
jstack [pid] > thread.txt
- 避免常见误区
✅ 正确认知:
· 不是-Xmx越大越好,太大会延长GC时间
· 频繁GC不一定是问题,要看停顿时间
· 不同的业务场景需要不同的GC算法
❌ 常见误解:
· "我的程序从不GC,性能很好"(可能内存泄漏!)
· "Full GC是正常的"(应该尽量避免!)
· "OOM一定是代码bug"(可能是配置问题)
记住三句话:
- 对象有生有死 - 大部分对象活不过一次GC
- 垃圾回收是成本 - GC时业务会暂停
- 监控比调优重要 - 先知道问题在哪