1.GC的概念
- Garbage Collection垃圾收集
- Java中,GC对象时堆空间和永久区
2.Garbage Collection算法
①引用计数法
通过引用计算来回收垃圾
原理:对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,当引用失效时,引用计数器就减1,只要对象A的引用计数器的值为0,则对象A就不可能再被使用,可以进行回收了。
上述从根对象(GcRoot对象)出发,向下搜索,走过的路径称为引用链,如果根对象与某个对象之间存在引用链,那么该对象就是可达的,反之,不存在引用链,即不可达。当一个对象失去了从根对象出发的引用链时,即变为了不可达对象,就对该对象进行了垃圾回收。
引用计数法存在的问题:
--引用和去引用伴随着加法和减法,影响性能
--很难处理循环引用
上述循环引用问题,即使从根对象出发,它们之间不可达,但是由于引用计数法原理,只要引用计数器的值不为零,那么该对象就不会被清理,但是实际上这三个对象都是应该被清理的对象,因为它们对根对象来说已经不可达了。
②标记清除法
原理:将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,在标记阶段,首先通过根节点,标记从根节点出发的可达对象,因此,未被标记的对象就是未被引用的垃圾对象,然后,在清除阶段,清除所有未被标记的对象。
上图中,箭头表示引用,从根节点出发,有箭头的地方经过的节点都是可达对象,即浅灰色的部分,而深灰色的对象都是不可达对象。算法对可达对象进行标记,然后将未被标记的对象进行清理。
③标记压缩法
适用于存活对象较多的场合,如老年代。他在标记-清除的基础上做了一些优化,和标记-清除算法一样,标记-压缩算法也首先需要从根节点开始,对所有可达对象做一次标记,但是之后,它并不简单的清理未标记对象,而是将所有的存活对象压缩到内存的一端,之后,清除边界外所有的空间。
将存活对象,压缩到一边,然后将其之外的所有空间全部清理。
④复制算法
--与标记-清除算法相比,复制算法是一种相对高效的回收方法
--不适用于存活对象比较多的场合,如老年代
原理:将原有的内存空间分为两块,每次只使用其中块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收
将存活对象复制到未使用的那块内存空间中,将正在使用的含有垃圾对象的内存空间,完全清空,然后清理前未使用与已使用的两块空间角色互换
--复制算法最大问题:空间浪费
通过整合标记清理思想进行改进
老年代空间作为复制算法的担保空间,第一块最大的作为主要存储空间(对象产生的地方,新生代中的eden),中间两块(from和to)作为复制算法的核心;垃圾回收开始之后,首先大对象直接进入担保空间,大对象进入复制空间不合理的原因:①复制空间可能不会很大,因为越大资源浪费越严重,因此大对象尽可能不要在复制空间分配,否则会引起两个问题:①如果大对象放下复制空间,很多小对象可能没地方去,就会排挤到老年代中②大对象根本复制空间放不进去,就只能去老年代。因此大对象一般去担保空间。老年对象进入老年代,当年轻对象每被清理一次,对象年龄就会加1,如果经过几次清理都没有被清理掉,是一个长期有效的对象,就会变成老年代。剩余对象(小对象、年轻对象)进入复制空间,然后清空原先使用的空间。
了解GC之后,重新回顾堆空间的内存分析
由上面堆信息可知, 新生代的实际大小=(0x28d80000-0x27e80000)/1024/1024=15M 而实际可使用的是total值=13824k = eden + from或to中的其中一个=12288+1536; 因为采用的是复制算法,from和to之中总有一个要拿来当复制空间,即少掉1536k大小的复制空间,所以实际可用的空间就是15M-1536K=13824k. |
⑤分代收集算法
原理:根据对象存活周期的不同将内存划分为几块,一般是把java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率较高、没有额外空间对他进行分配担保,就必须使用“标记清理”或者“标记压缩”算法进行回收。
⑥GC算法总结整理
--引用计数:没有被java采用,因为存在对象不会被清理的情况
--标记清除
--标记压缩 :两者普遍用于老年代的垃圾收集
--复制算法:用于新生代的垃圾收集
3.可触及性
①可触及:从根节点出发,可以触及到这个对象,即可达
②可复活:
--一旦所有引用被释放,就是不可达状态,不可达并不是一定不可触及
--因为在finalize()中可能复活该对象,但是同个对象finalize方法只会进行一次
③不可触及:
--在finalize()后,可能进入不可触及状态
--不可触及对象不可能复活
--可以回收
public class TestRelive{
public static TestRelive tr;
@Override
protected void finalize() throws Throwable{
super.finalize();
System.out.println("TestRelive finalize called");
obj = this;
}
@Override
public String toString(){
return "i am TestRelive object";
}
public static void main(String[] args) throws Exception{
tr = new TestRelive();
tr = null;//不可达,但不一定不可触及
System.gc();
Thread.sleep(1000);
if(tr == null){
System.out.println("tr is null");
}
else{
System.out.println("tr is useable");
}
System.out.println("the second gc");
tr = null;
System.gc();
Thread.sleep(1000);
if(tr == null){
System.out.println("tr is null");
}
else{
System.out.println("tr is useable");
}
}
}
当第一次tr设置成null的时候,tr处于不可达状态,并且系统调用gc,由于重写了finalize方法,在gc之前需要调用finalize,使得tr又重新处于可达状态,第二次gc的时候,由于finalize只会执行一次,所以tr就真正处于不可触及的状态了,故被清理了。
注意:
--经验:避免使用finalize(),操作不慎可能导致错误
--finalize 优先级低,何时被调用,不确定
--何时发生gc不确定
--如果需要释放资源,可以使用try-catch-finally来替代它
根对象:
-栈中引用的对象
-方法区中静态成员或者常量引用的对象,全局对象
-native方法栈中引用的对象
4.Stop the world
--java中一种全局暂停的现象,全局停顿,所有java代码停止,native代码可以执行,但不能和JVM交互
--多半由于GC引起
------Dump线程
------死锁检查
------堆dump
为什么GC时会产生全局停顿?
类比在聚会时打扫房间,聚会时很乱,又有新的垃圾产生,房间永远打扫不干净,只有让大家停止活动了,才能将房间打扫干净
危害
--长时间服务停止,没有响应
--遇到HA系统,可能引起主备切换,严重危害生产环境
平常由主机工作,备机不工作,一般情况下,不允许主备机同时工作,会出现系统问题。如果此时主机发生gc,系统长时间没有反应,备机以为主机出现问题,此时备机开始工作;等到主机gc结束,则主备机同时开始工作,严重危害生产环境。
图片资源来自-------------炼数成金,深入JVM内核