JAVA 圾收集原理垃 @gongyin
“Garbage collection (GC) is a form of automatic memory management. The garbage collector, or just collector, attempts to reclaim garbage, or memory occupied by objects that are no
longer in use by the program ” - Wikipedia
自动内存管理年代
还有必要学习和了解GC ?
垃圾收集器做什么?
•释放非存活对象占据的内存空间
•管理内存,决定了内存分配机制
垃圾收集器如何做?
• 检测出垃圾对象 直接方式:引用计数
间接方式 : 追踪对象引用图
• 回收垃圾对象所占用的内存空间 直接清除 压缩 拷贝
• 必须决定什么时候进行回收
垃圾算法的基本要求
• 必须是安全的,存活数据不能被错误回收
• 应该是全面的,垃圾对象会在固定的收集周期被回收
• 应该有合理的开销,时间 / 空间 / 运行频率
• 尽可能少的内存碎片
• 应该是可扩展的,不会成为可扩展瓶颈
常用的 GC 算法和策略
• 引用计数器(渐进式)
• 标记 - 清扫垃圾收集
• 节点复制垃圾收集
• 标记 - 缩并垃圾收集
• 分代垃圾收集
• 并发垃圾收集
• 分布式垃圾收集
• 自适应动态垃圾收集
引用计数器
Pros• 实现简单,能快速判断对象是否在使用
• 交织在程序中执行,不会挂起应用
Cons• 无法处理循环引用
• 给程序执行带来额外的开销
• 与用户程序紧密的耦合
标记 - 清扫算法
Pros• 非常自然的处理环形结构
• 操纵指针没有额外的 开销
Cons• 停止 - 启动 算法, STW 问题
• 内存碎片问题
渐进复杂度正比与堆的大小
节点复制算法
Scavenger 清道夫,从垃圾中捡起有价值的并带走
Pros• 所有存活的数据都缩并的排列在一起
• 无内存碎片,能够高效的分配对象
Cons• 使用两个半区,存储容量加倍
• 重复拷贝
性能随着内存占用率提高而下降,复杂度正比于存活数据结构的大小,而不是堆的大小
标记 - 缩并算法
缩并方式
任意的 / 线性的 / 滑动的
Pros• 不需要占用额外空间
Cons• 缩并带来性能损失
渐进复杂度类似与节点复制算法
分代式垃圾收集器
Weak generational hypothesis 理论• 大部分对象都是短暂的,生命周期很短
• 只有很少的长生命对象会引用短生命对象
• 把工作集中在回收最有可能是垃圾的对象上
Pros• 不同的分代可以使用不同的算法和不同的搜集频率,能减少垃圾收集的整体开销
Cons• 如果根集合巨大
• 分代间引用 以及占位垃圾 问题
分代式垃圾收集器
• 需要合理设置 提升策略 和提升阀值
• 提升太快
Major GC 占位垃圾 和 庇护现象 程序的局部性有负面影响,工作集稀疏 增大写拦截器的开销
• 自适应提升策略 在缩短中断时间和占位垃圾之间进行折衷
• 调度策略
•隐藏在用户最不注意到时刻
• 最多垃圾时触发
渐进式和并发收集器
三色标记 已访问 \ 需重访问 \未访问
Pros• 大幅缩短垃圾收集带来的中断时间
Cons• 吞吐量,垃圾收集总耗时上升
• “ 多个读,多个写”一致性问题,需要和用户程序同步,漂浮垃圾问题
• 需提前进行收集,可能并发收集失败
自适应垃圾收集算法
• 监视服务器和应用情况,自动调整和选择合适的收集算法
• JAVA 中的 Eronomic
Java
• 基于分代策略
• 每个分代可以使用不同的垃圾收集器
Java 基于分代垃圾收集策略
Java 中如何检测垃圾对象的?
找到 GC Root 集合
• 本地变量和局部变量引用的对象
• 方法区中类静态属性引用的对象
• 方法区中常量引用的对象
• 本地方法栈中 JNI 所引用的对象
• 跨分区引用的对象,分代间引用
• 引用对象被提升
• 被重新赋值指向新生分代对象
Java 中如何检测垃圾对象的?
• 写拦截器 Write-barrier Dirty Card
JVM维护一个 dirty cards 表 每 512 byte 内存页关联到一个 dirty card 内存页发生变化,则标记 dirty page GC完成,重设 dirty card
Snapshot-at-the-beginning (SATB) G1
Java 中如何检测垃圾对象的?
根据对象存活状态( Reachable )标记出垃圾对象
• 强引用
• 软引用( SoftReference )
• -XX:SoftRefLRUPolicyMSPerMB
• 弱引用 (WeakReference)• 可复活对象
• 被 finalize 方法复活 ( 需两遍扫描 )
• 虚引用 (PhantomReference)• 对象被回收时得到系统通知 ReferenceQueue
• 不可达对象
垃圾清扫阶段
释放被占用的垃圾
非压缩,直接释放
清道夫复制
滑动缩并
新对象分配
• 直接分配在新生代 Eden或 TLAB• TLAB thread local allocation block
• -XX:+PrintTLAB
• -XX:+UseTLAB -XX:+ResizeTLAB
• -XX:TLABSize=<size> -XX:MinTLABSize=<size>
• 大对象和大数组直接分派在旧生代• -XX:PretenureSizeThreshold=n
• 如果对象大于 Eden 大小
• 堆外分配• DirectByteBuffer
• Unsafe.allocateMemory
• -XX:MaxDirectMemorySize=<value>
对象提升策略
• -XX:+AlwaysTenure ,直接从新生代到旧生代
• Survivor 区域满后,其他存活对象提升
• 对象已经存活一定次数的收集周期
• –XX:MaxTenuringThreshold
• –XX:TargetSurvivorRatio
垃圾收集器性能度量指标
• 吞吐量 throughput没有花在GC 上的时间百分比,比如GC花了 1%的时间,那么吞吐量是
99%
• GC负载GC Overhead• 停顿时间 pause Time
• 新生代暂停时间 = 栈扫描时间+DirtyCard 扫描时间+旧生代扫描时间+ 存活对象赋值时间
• 垃圾收集器执行的频率
• 内存大小 Footprint• 敏捷性 Promptness
对象变成垃圾和此块内存可以被利用的时间
Java 提供 4种类型的 GC
• Serial Collector –XX:+UseSerialGC
• Parallel Collector –XX:+UseParallelGC
• Parallel Compacting Collector(1.5R6)–XX:+UseParallelOldGC
• CMS Collector–XX:+UseConcMarkSweepGC
串行收集器
• STW• –XX:+UseSerialGC• 新生代使用串行GC• 旧生代和持久代也使用串行GC• 适用于小内存客户端应用
并行收集器
• STW• –XX:+UseParallelGC Parallel
• 新生代使用并行复制收集算法
• 旧生代使用并行压缩mark-sweep-compacting
• –XX:+UseParallelOldGC• –XX:ParallelGCThreads=n
并发收集器
• 过程• Initial mark ,STW, 收集GC Roots
栈 新生代对象应用旧生代对象
速度依赖于新生代存活对象的多少 – XX:CMSWaitDuration= n
• Concurrent Mark ,标记存活对象
• Concurrent pre clean,统计被改变的引用
• Remark , STW ,重新标记被改变的引用 –XX:+CMSScavengeBeforeRemark //强制新生代GC
• Concurrent sweep
• Concurrent reset
并发收集器
• –XX:+UseConcMarkSweepGC• 不会压缩堆,不同大小碎片有各自的 free memory list• –XX:+CMSIncrementalMode
• –XX:ParallelGCThreads=n
• 何时触发
• 最好在新生代GC之后• ‑XX:+UseCMSInitiatingOccupancyOnly =
• ‑XX:+ExplicitGCInvokesConcurrent
• 导致 Full GC• 并行模式问题 Concurrent mode failure
正在执行 CMS ,但是没有足够的空间分配给新对象
• 提升失败问题 Promotion failure提升的对象太多,旧生代放不下
并发收集器
• 针对多核优化参数
• ‑XX:+CMSConcurrentMTEnabled 并发阶段使用多核
• ‑XX:+ConcGCThreads=
• ‑XX:+ParallelGCThreads=
STW阶段并行线程数目,默认 CPU数目
• ‑XX:+UseParNewGC 新生代使用并行复制算法
• 限制
• 对 CPU资源敏感
• 漂浮垃圾问题
• 内存碎片问题
‑XX:+UseCMSCompactAtFullCollection ‑XX:+CMSFullGcsBeforeCompaction =
Garbge First
G1 ( jdk16.R14 )
• 基于 标记 - 压缩算法
• 可以精准的控制暂停时间
• 将 Heap 分为多个大小的独立区域
• G1维护优先列表,检测哪个区域内存使用最快,然后仅仅收集此区域
• 清除空区域代价很小
• 复制小数量的对象很快
Young collector Old collector JVM option
Serial (DefNew) Serial Mark-Sweep-Compact -XX:+UseSerialGC
Parallel scavenge (PSYoungGen)
Serial Mark-Sweep-Compact (PSOldGen)
-XX:+UseParallelGC
Parallel scavenge (PSYoungGen)
Parallel Mark-Sweep-Compact (ParOldGen)
-XX:+UseParallelOldGC
Serial (DefNew) Concurrent Mark Sweep -XX:+UseConcMarkSweepGC-XX:-UseParNewGC
Parallel (ParNew) Concurrent Mark Sweep -XX:+UseConcMarkSweepGC-XX:-UseParNewGC
G1 -XX:+UseG1GC
GC 组合
Ergonomics
• 基于平台和操作系统自动选择collertor、 heap size 和 hotspot client or server
• 目标是提供尽可能好的性能而不需要设置太多命令参数,自动化
Behavior-based parallel
• 最大停顿时间
• -XX :MaxGCPauseMillis=n
• 最大吞吐量
• -XX :GCTimeRatio=n
• Ratio = 1/ ( 1+N )
• Default value = 1%
• 缺省优先级:先满足停顿时间,然后吞吐量,最后 footprint
一些常用配置
• 单处理器系统或小内存, 100M左右内存-XX:+UseSerialGC
• 多处理器系统,需要最大吞吐量不关心暂停时间-XX:+UseParallelGc -XX:+UseParallelOldGC
• 最小停顿时间-XX:+UseConcMarkSweepGC
• -XX:+ParNewGC, 有内存碎片,内存越大越好
优化原则
• Do Nothing,让 JVM 自动选择
• 不存在一个简单而普适的原则,必须充分理解系统的需求和特征
• 先measure ,在 tune• 不要过度 tune• 需要在各个指标之间进行权衡
常用监控和分析工具
• GC参数• -XX :+PrintGCDetails
• -XX: +PrintHeapAtGC
• -XX :+PrintGCTimeStamps
• -XX:+HeapDumpAfterFullGC
• -XX:+HeapDumpBeforeFullGC
• -XX:+HeapDumpOnOutOfMemoryError
• Jmap –histo | -heap | -permstat• Jstat• Jconsole• VisualVM• HAT : Heap Analysis Tool
References• http://www.oracle.com/technetwork/java/javase/gc-tuning-6-140523.html• http://java.sun.com/performance/reference/whitepapers• http://www.oracle.com/technetwork/java/faq-140837.html• http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html#PerformanceTuning• http://blogs.oracle.com/jonthecollector/entry/our_collectors• http://java.sun.com/docs/hotspot/VMOptions.html
Thank you