# 分代&回收算法
垃圾收集算法一般有标记-清除、复制算法、标记-整理、分代收集
JVM |
CLR |
- 年轻代 8(Eden) : 1(Survivor from) : 1(Survivor to)
- 老年代
分代收集,新生代->复制算法。老年代->标记-整理 |
会根据每次回收的大小动态调整每代的内存容量,回收算法->标记-清除-压缩 |
# 触发条件
不讨论例如代码手动调用GC、系统内存不足、进程退出等情况
JVM |
CLR |
新生代(minor gc) |
老年代(full gc) |
- 第0代内存不够分配当前所需内存时回收第0代
- 第1代内存不够分配当前所需内存时回收第1代
- 第2代内存不够分配当前所需内存时回收第2代
|
- eden无法分配足够内存时开始minor gc
|
- 老年代无法分配所需内存时(新生代代提升对象到老年代)
- 每次minor gc时判断之前每次存入老年代对象的平均内存是否大于当前 剩余内存,如果大于当前剩余内存,再判断HandlePromotionFailure是否允许担保失败,如果允许则放弃full gc继续minor gc,如果不允许(默认不允许) 则开始一次full gc
|
# 如何判断对象是否可以被回收
常用算法有引用计数、根搜索
JVM |
CLR |
- 通过名为"GC Roots"的根作为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链则认为这个对象不可达,可以被回收。在回收之前,如有需要(是否重写finalize)还会执行对象的finalize方法,如果在此方法中重新将对象赋值给外部变量,则放弃对该对象的回收,否则回收该对象。单一对象只会执行一次finalize方法,下次回收时不会再次执行,所以对象有且只有一次自救机会。
|
- 首先标记所有对象为可回收(同步块索引字段的一位设置为0),然后CLR检查所有活动根,查看它们引用了哪些对象,被引用的对象修改同步块索引的位设为1,标记不可被回收
|
java中可作为GC Roots的对象包括下面几种:
- 虚拟机栈(栈帧中的本地变量表)中的引用的对象
- 方法区中的类静态属性引用的对象
- 方法区中的常量引用的对象
- 本地方法栈中JNI(即一般说的Native方法)的引用的对象
|
C#中可作为根的对象包括类的静态和实例字段、方法的参数和局部变量 |
# 如何回收
JVM |
CLR |
新生代(复制算法) |
老年代(标记-整理) |
- 标记所有不可达对象,回收所有不可达对象,压缩内存(标记-清除-压缩)
|
- 新建对象分配到eden,如果eden剩余内存不够时,启动minor gc,回收eden和survivor from中不可达对象,并将eden、survivor from中存活的对象移动到survivor to中,互换survivor from 和survivor to
|
- 回收不可达对象,移动存活对象到老年代的一端,保证内存的连续性
|
# 提升代的条件
JVM |
CLR |
- 回收次数大于MaxTenuringThreshold(默认15)值还存活的对象(长期存活)
- 每次回收存活对象总和大于survivor空间的所有对象(分配担保)
- 内存超过PretenureSizeThreshold值的对象(大对象)
- Survivor中同龄对象大小总和大于Survivor的一半,当前年龄及以上的对象(动态年龄判断)
|
- 每次存活下来的对象都会提升一代
|