总结了JVM的内存区域、垃圾回收算法、常用垃圾回收器,期待新的G1和ZGC。
1. JVM 内存区域
运行时数据区域
堆和栈
对象的内存布局和访问定位
- 类加载检查
- 分配内存:指针碰撞、空闲列表
- 初始化零值
- 设置对象头:类的元数据信息、对象的哈希吗、对象的 GC 分代年龄等信息
- 执行init方法
句柄和直接引用
内存参数设置和溢出
溢出类型 | 参数设置 | 报错信息 |
---|---|---|
堆溢出 | -Xms -Xmx | java.lang.OutOfMemoryError: GC overhead limit exceeded 不断分配对象java.lang.OutOfMemoryError: Java heap space 分配巨型对象 |
新生代配置 | -Xmn -XX:NewRatio -XX:SurvivorRatio | |
方法区和运行时常量池溢出 | -XX:MetaspaceSize -XX:MaxMetaspaceSize | java.lang.OutOfMemoryError |
本地方法栈和虚拟机栈溢出 | -Xss | java.lang.StackOverflowError |
本机直接内存溢出 | -XX:MaxDirectMemorySize | Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory |
JDK提供的分析工具
作用 | 示例 | |
---|---|---|
jps | 虚拟机进程状况工具 | jps [-q] [-mlvV] |
jstat | 虚拟机统计信息监视工具 | jstat -gc jstat -gccause jstat -gcutil <pid> |
jinfo | Java配置信息工具 | jinfo -flags <pid> jinfo -sysprops <pid> jinfo -flag +PrintGC <pid> |
jmap | Java内存映像工具 | jmap -dump:live,format=b,file=heap.bin <pid> |
虚拟机堆转储快照分析工具 | 分析jmap生成的转储文件 | |
jstack | Java堆栈跟踪工具 | 线程栈信息,死锁检测,Thread.getAllStackTraces() |
JConsole | Java监视与管理控制台 | |
JVisualVM | 多合一故障处理工具 | 比jconsole更强大,支持插件 |
jmc | 飞行记录器,监控数据比jvisualvm更加丰富 |
2. 垃圾回收和内存分配策略
Where回收谁:堆内存、常量池,引用类型
When什么时候回收:内存空间不足、
How怎么回收:GC算法
判断对象存活
原理 | 优劣 | |
---|---|---|
引用计数 | 记录对象被引用次数,为0时则不可用 | 方便简单,但存在相互引用的问题 |
可达性分析 | 以GC Roots作为起始点开始搜索,搜索路径称为引用链,对象不与引用链相连则此对象不可用 |
GC Roots
- 虚拟机栈中引用的对象
- 本地方法栈中JNI引用的对象
- 方法区中类型静态属性引用的对象
- 方法区中常量引用的对象
引用类型
示例 | 说明和回收时机 | |
---|---|---|
强引用 | Object obj = new Object() | |
软引用 SoftReference | SoftReference<User> user = new SoftReference<>(new User()); | 有用非必需的对象,即将OOM之前被回收,用在内存资源紧张、缓存 |
弱引用 WeakReference | WeakReference<User> user = new WeakReference<>(new User()); | 稍微有用但非必需,GC发生即被回收,用在缓存、HashMap |
虚引用 PhantomReference | 一般使用不上,析构函数finalize不建议使用 | 通过虚引用无法拿到对象实例,被回收时收到一个通知 |
垃圾回收算法
工作原理 | 优劣 | 劣势 | |
---|---|---|---|
Mark-Sweep 标记清除算法 | 先标记出需回收的对象,然后统一回收 | 简单 | 空间碎片 |
Copying 复制算法 | 内存划分为两部分,将活着的对象复制到另一部分 | 空间连续 | 浪费一半空间、复制数据 |
Mark-Compact 标记整理算法 | 先标记出需回收的对象,然后把向一端移动 | 空间连续 | 复制数据 |
分代收集
根据对象存活周期将内存分块,应用不同的收集算法:
- 新生代:Eden : Survivor : Survivor = 8 : 1 : 1 复制算法,minor gc
- 老年代:标记清除或标记整理,full gc
垃圾回收器
并行:垃圾收集的多线程的同时进行。
并发:垃圾收集的多线程和应用的多线程同时进行。
算法 | 收集器类型 | 特性和适用场景 | |
---|---|---|---|
Serial GC | 新生代,复制算法 | 单线程 | stop the world,简单高效 |
ParNew | 新生代,复制算法 | 并行多线程 | 关注停顿时间,搭配CMS首选,适合用户交互 |
Parallel Scavenge | 新生代,复制算法 | 并行多线程 | 关注吞吐量,适合后台任务 |
Serial Old | 老年代,标记整理算法 | 单线程 | |
Parallel Old | 老年代,标记整理算法 | 并行多线程 | 关注吞吐量,搭配Parallel Scavenge |
CMS | 老年代,标记清除算法 | 并行并发 | 关注停顿时间 |
Serial + Serial Old :+1:单CPU
基于串行回收的垃圾回收器适用于大多数对于暂停时间要求不高的 Client 模式下的 JVM
ParNew + Serial Old
Parallel Scavenge + Serial Old
Parallel Scavenge + Parallel Old :+1:高吞吐量
程序吞吐量优先的应用场景中,在 Server 模式下内存回收的性能较为不错
ParNew + CMS :+1:低停顿时间
并发低延迟,吞吐量较低。经过CMS收集的堆会产生空间碎片,会带来堆内存的浪费
初始标记 -> 并发标记 -> 重新标记 -> 并发清除
G1 :+1:全能型选手
基于并行和并发、低延迟以及暂停时间更加可控的区域化分代收集的垃圾回收器
没有采用传统物理隔离的新生代和老年代的布局方式,仅仅以逻辑上划分为新生代和老年代,选择的将 Java 堆区划分为 2048 个大小相同的独立 Region 块
参数 | 新生代 | 老年代 | 适用场景 |
---|---|---|---|
-XX:+UseSerialGC | Serial | Serial Old | 单CPU |
-XX:+UseParNewGC | ParNew | Serial Old | |
-XX:+UseParallelGC | Parallel Scavenge | Serial Old | |
-XX:+UseParallerOldGC | Parallel Scavenge | Parallel Old | 后台计算,高吞吐量 |
-XX:+UseConcMarkSweepGC | ParNew | CMS | 用户交互,低停顿时间 |
-XX:+UseG1GC |
Stop The World
暂停用户线程进行垃圾回收的现象。调优的目标就是尽可能的减少STW的时间和次数
内存分配与回收策略
- 对象优先在Eden分配:空间不足则Minor GC
- 大对象直接进入老年代:如长字符串或大数组
- 长期存活的对象进入老年代:默认15,
-XX:MaxTenuringThreshold
调整 - 动态对象年龄判定:survivor中相同年龄的对象总和大于空间的一半时直接进入老年代
- 空间分配担保:survivor空间不足,无法容纳的对象直接进入老年代,老年代的连续空间大于新生代对象的总大小或者历次晋升的平均大小,就进行Minor GC,否则FullGC
内存泄漏、内存溢出
OOM
- 内存溢出:内存空间不足导致
- 内存泄漏:该释放的对象没有释放
GC调优的原则和步骤
- 大部分的java应用不需要调优
- 优先改善代码问题
- GC调优时最后的手段
- 选择合适的GC收集器
- 选择合适的堆大小
- 选择合适的年轻代在堆中的占比
步骤
- 监控GC状态
- 分析结果,判断是否要优化
- Minor GC时间 <50ms, 频率10s
- Full GC时间执行时间 <1s,频率10min