Java垃圾收集原理

1,930 views
1,742 views

Published on

0 Comments
4 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,930
On SlideShare
0
From Embeds
0
Number of Embeds
10
Actions
Shares
0
Downloads
64
Comments
0
Likes
4
Embeds 0
No embeds

No notes for slide

Java垃圾收集原理

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

×