Java的GC回收机制解决了三个主要的问题。
1.哪些对象可以被回收
2.何时回收
3.以什么样的方式回收
第一个问题:哪些对象可以被回收
1.引用计数法
当一个对象被引用时,引用计数+1
缺点:如果两个对象相互引用,则永远无法被回收。
2.根搜索算法
设立若干种根对象,当任何一个根对象到某一个对象均不可达,则该对象可以回收。
根对象可以是栈中的引用对象,类静态属性引用对象等。
第二个问题:何时回收
1.标记-清除算法
当内存被耗尽时,GC线程被触发,将程序暂停,同时标记-清除对象,然后程序继续运行。
标记:遍历所有的GC Roots,将GC Roots可达的对象标记为存活对象。
清除:遍历堆中所有对象,将没有被标记的对象全部清除。
缺点:
1.遍历效率低。
2.清理出的空闲内存不连续。
2.复制算法
将内存划分为2个区间,任意时间点,所有动态分配的对象只能分配在其中一个区间内,另外一个区间则是空闲的。
当有效的内存空间耗尽时,JVM暂停程序运行,开启复制算法GC线程。
GC线程将活动区间的存活对象全部复制到空闲区间,并清空刚才的活动区间。
同时GC线程将更新存活对象的引用,指向新的内存地址。
优点:弥补了 标记-清除算法 内存布局混乱的缺点。
缺点:浪费了一半内存。
3.标记-整理算法
标记:与 标记-清除算法 一样,遍历GC Roots,标记GC Roots,标记存活对象。
整理:移动所有存活对象,按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。
优点:
1.弥补了 标记-清除算法 内存区域分散的缺点。
2.清除了复制算法中,内存减半的代价。
缺点:效率低。
以上回收算法的共同点:
3个算法都是基于根搜索算法去判断一个对象是否该回收,
而支撑根算法可以正常工作的理论依据就是语法中变量作用域的相关内容。
因此,想要防止内存泄漏,最好的方法就是掌握好变量作用域。
4.分代收集算法(对前3个算法的实际应用)
1.生存周期最短的对象:方法内的局部变量,循环内的临时变量等。
2.生存周期中等的对象:缓存对象,单例对象等。
3.生存周期最长的对象:String堆中的对象(享元模式),加载过的类信息等。
对于生存周期最短的对象,使用复制算法。
(1)将两块10%内存作为空闲区间和活动区间,80%分配给堆,给新建对象分配内存。
一旦GC,10%活动+80%堆对象转移到10%空闲区间,将剩下的90%的内存释放。
Java将内存空间分成2部分,一部分放(1),另一部分放生命周期长的对象。
当新生代对象熬过n次GC后,将其转移到年老代堆区中。
新生代对象占用内存超过10%后,多于对象放入年老代堆区,年老代堆区就是备用仓库。
针对生命周期长的对象,采用 标记-整理 或标记-清除 算法。
GC的回收时机分为两种:
1.普通GC:只针对新生代区域的GC。
2.全局GC:针对年老代堆区的GC,偶尔伴随对新生代及永久代的GC。