(我在知乎的专栏文章地址: https://zhuanlan.zhihu.com/p/... )
简介
C/C++等语言中需要手动管理内存,操作繁琐,还容易忘记释放内存。
为了把程序员从这些繁琐的操作中解放出来,所以引入了GC。
GC的主要工作就是寻找不再需要的对象,释放其内存。这句话中蕴含着两个操作:
- 定位不需要的对象
- 释放内存
定位不需要的对象
引用计数 Reference counting
每个对象都有一个变量,记录了自己被引用的次数。每被引用一次,引用次数加一。相反,不再被引用时,引用计数减一。当引用次数为0的时候,说明对象需要被回收。
优点:实现简单
缺点:无法解决循环引用
改进:
- 添加弱引用
- 用trial deletion解决循环引用
- 使用tracing GC例如mark-sweep GC辅助
跟踪 Tracing
基于可达性(reachable)分析。从被称为GC Roots的一些对象出发,找到其引用的对象,再到其间接引用的对象,形成一条引用链。凡是不在引用链里面的对象,被称为不可达(Unreachable)对象,被标记为需要回收。
优点: 目前主流的GC算法,解决了引用计数的问题。
缺点:实现较为复杂
跟踪算法如何垃圾回收
标记-清除
标记清除算法分为“标记”和“清除”两个阶段:首先标记出需要回收的对象,标记完成之后统一清除对象。它的优点是效率高,缺点是容易产生内存碎片。
复制
它将可用内存容量划分为大小相等的两块,每次只使用其中的一块。当这一块用完之后,就将还存活的对象复制到另外一块上面,然后在把已使用过的内存空间一次理掉。它的优点是实现简单,效率高,不会存在内存碎片。缺点就是需要2倍的内存来管理。
标记-整理
标记操作和“标记-清理”算法一致,后续操作不只是直接清理对象,而是在清理无用对象完成后让所有 存活的对象都向一端移动,并更新引用其对象的指针。因为要移动对象,所以它的效率要比“标记-清理”效率低,但是不会产生内存碎片。
总结
标记-整理和复制算法是对标记-清除算法的改进,解决了一些标记-清除算法的缺点。但是并不意味着后两种算法优于标记-清除算法,只是有所取舍,在工程上权衡。
分代
分代不是一种新的算法,而是对已有的算法的工程上调优。
由于对象的存活时间有长有短,所以对于存活时间长的对象,减少被gc的次数可以避免不必要的开销。这样我们就把内存分成新生代和老年代,新生代存放刚创建的和存活时间比较短的对象,老年代存放存活时间比较长的对象。这样每次仅仅清理年轻代,老年代仅在必要时时再做清理可以极大的提高GC效率,节省GC时间。
实现
历史
Serial -XX:+UseSerialGC
Parallel -XX:+UseParallelGC -XX:+UseParallelOldGC
CMS -XX:+UseParNewGC -XX:+UseConcMarkSweepGC
G1 -XX:+UseG1GC
CMS
默认gc,分代gc,经典的Eden, Survivor, OldGen, PermGen。
1.8中PerGen被废弃,部分被MetaSpace代替。
改进的mark-sweep算法。
年轻代使用Parallel,老年代才使用CMS。可以通过参数配置不同的回收器。
初始标记(CMS-initial-mark stop the world) -> 并发标记(CMS-concurrent-mark) -> 重新标记(CMS-remark stop the world) -> 并发清除(CMS-concurrent-sweep) ->并发重设状态等待下次CMS的触发(CMS-concurrent-reset)
G1
多个Region, 每个Region都有Eden,Survivor,Old,Humongous。
因为划分多个Region,所以单个Region可能比较小,对于那些较大的对象用Humongous储存
对象分配策略
TLAB(Thread Local Allocation Buffer)线程本地分配缓冲区
Eden区
Humongous区
TLAB是为了避免同步的预分配区。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。