Java垃圾收集器(Garbage Collector)

一、垃圾收集(Garbage Collection, GC)需要完成的3件事情:
1、哪些内存需要回收?
2、什么时候回收?
3、如何回收?
总结成一句话:GC对什么东西,在什么时间,做了什么事情?
二、为什么要了解和学习GC和内存分配?
排查各种内存溢出、内存泄漏;进行性能调优,使系统为了达到更高并发量。
三、垃圾回收的两个步骤:标记、清除
1、标记:判定对象已死(对象存活判定算法)
(1)引用计数算法:给对象添加一个引用计数器,每当该对象增加一个引用,计数器的值就加 1,当引用失效时,计数器减 1,当计数器值为零时,则判定该对象不可能再被使用。
引用计数算法实现简单,效率高,在大多数情况下他都是一个不错的算法,但是java虚拟机并没有选择这个算法,其中最主要的原因是它很难解决对象间相互循环引用的问题,下面的代码用来说明这个问题:

Class A{
    public Object instance;
    public void m(){
        A a1 = new A();
        A a2 = new A();
        a1.instance = a2;
        a2.instance = a1;

        a1 = null;
        a2 = null;

        system.gc();//假设这时发生垃圾回收,a1和a2能被回收吗?
    }
}

事实证明,a1和a2被回收了,这是因为java虚拟机并没有使用引用计数算法,而是使用了另外一种算法:可达性分析算法
(2)可达性分析算法:
该算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时(即从GC Roots到这个对象不可达),则证明此对象是不可用的,将会被判为可回收的对象。
那么,在java语言中,哪些对象可以作为GC Roots呢?有下面四种:
a)虚拟机栈(栈中的本地变量表)中引用的对象:虚拟机栈是管理java方法执行的内存模型,也就是方法中的成员变量。
b)方法区中类静态属性引用的对象
c)方法区中常量(final关键字修饰)引用的对象
d)本地方法栈中Native方法(JNI)应用的对象
(3)谈谈引用
上边讨论的两种算法都与“引用”有关,在JDK1.2之前,对象只存在引用和没有被引用两种状态,当遇到下面这种情况时,这种绝对的关系则显得有些无能为力:我们希望能描述这样一类对象,当内存空间还足够时,则保留在内存中,如果内存空间在进行垃圾回收后还是很紧张,则进行再次回收以抛弃这些对象。
所以,在JDK1.2之后,java对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用和虚引用,这四种引用强度逐渐减弱。
强引用普遍存在,如new 出来的对象,这类引用永远不会被回收,
软引用正是用来解决上述问题的关键,在系统将要发生内存溢出之前,则会把这类引用列进回收范围内进行二次回收(第一次不回收,第二次才进行回收),如果这次回收还没有足够的内存,才会抛出内存溢出异常。
弱引用则是用来描述普通非必须对象的,只要发生垃圾回收,该引用关联的对象就会被回收
虚引用不太懂,好像没啥影响
(4)对象到底死没死
在可达性分析算法中不可达的对象一定会死吗?答案是不一定,要宣告一个对象死亡,至少要经历两次标记过程:经过可达性分析后判定为不可达的对象将会被进行第一次标记并进行筛选,筛选的条件是此对象是否有必要执行finalize()方法,当对象没有覆盖finalize()方法或者finalize()方法已经被虚拟机调用过,则虚拟机判定此对象没有必要执行finalize()方法。
如果该对象被判定有必要执行finalize()方法,这时候该对象会放置在一个叫做F-Queue的队列中,并在稍后由一个虚拟机自动建立的,低优先级的Finalizer线程去执行它,这里的执行仅仅是出发对象的finalize方法,并不会承诺等到他执行结束,这是因为万一对象的finalize方法执行缓慢或是发生了死循环,就可能导致队列中的其他对象永久处于等待,从而导致整个内存回收系统崩溃,finalize()方法是对象迎来唯一一次拯救自己的机会,对象如果重写了finalize()方法,并且在方法里给自己重新添加了引用,那么在第二次标记中该对象就会被移出“即将回收”的集合,这时候如果对象还没被逃脱,则就真的被宣判死亡了。
总结:这一段回答了“GC对哪些内存需要回收”这个问题,在GC Roots搜索不到,并且在第一次标记、清理(队列中执行finalize()方法)后仍没有复活的对象将会被回收。
2、清除:进行回收(垃圾收集算法)
(1)标记-清除算法
最基础的收集算法,有两点不足:一是效率问题,标记和清除的效率都不高;二是空间问题,标记清除后会产生大量不连续的碎片,这可能会导致后续在分配比较大的对象时无法找到连续的内存。标记-清理算法的执行过程如下图所示。
这里写图片描述
(2)复制算法
为了解决效率问题,提出了复制算法,它将内存划分为大小相等的两块,每次只使用其中一块,当这一块使用完了,就将还存活的对象复制到另一块上面,然后再把已使用过的那一块一次清理掉,这样在内存分配时也就不用考虑内存碎片等情况。这样做的代价就是将内存缩小到了原来的一半。复制算法的执行过程如下图所示。
这里写图片描述
复制收集算法在对象存活率较高的时候需要进行较多的复制操作,效率会变低,所以多用于新生代,又由于新生代中的对象大都朝生夕死(98%),所以并不需要按照1:1的比例来划分内存空间,HotSpot jvm将年轻代划分了三个区域,Eden区和两个Survivor区,每次只使用Eden区和其中一个Survivor区(from区),一般情况下,新生对象都会放在Eden区(大对象直接进入老年代),这些对象在经历一次Minor GC后,Eden区存活的对象会复制到另一块空的survivor区(to区),而from区的对象则会根据年龄大小决定去哪个区,年龄达到年龄阈值(通过-XX:MaxTenuringThreshold可设定阈值)则送往老年代(如果Survivor区放不下也直接送往老年代),年龄未达到阈值则送往to区,(对象每在survivor区经历一次Minor GC,年龄加1),GC结束后,eden和from区就已经被清空,这时,from和to就互换身份,新生代就又回到eden和from存放对象,to为空的状态了,然后开始迎接下一次的GC。
(注:Minor GC:发生在新生代的垃圾回收
Major GC/Full GC:发生在老年代的垃圾回收)
(3)标记-整理算法
复制收集算法在对象存活率较高的时候需要进行较多的复制操作,效率会变低,所以老年代一般不直接选用这种算法。根据老年代的特点,提出了另外一种“标记-整理”算法,该算法过程与“标记-清理”一样,但后续步骤不是直接清理,而是让所有存活的对象向一段移动,然后直接清理掉存活边界以外的所有内存,其执行过程如下图所示。
这里写图片描述
(4)分带收集算法
该算法的思想就是根据对象存活的周期的不同将其分为新生代和老年代,然后根据各个年代的特点选择最适当的收集算法,在新生代,每次都有大批的对象死去,只有少量存活,所以选用复制算法,而老年代因为对象存活率较高,没有额外的空间对他进行分配担保,就必须要使用“标记清理”或者“标记整理”来进行回收。
**总结:这段回答了“GC做了什么事情”,做的事情自然是对不用的对象进行回收,对年轻代进行复制清理(讲清楚eden区,survivor区怎么工作的),对年老代进行标记整理或是标记清理,下面的垃圾收集器则是对GC工作的具体实现。
同时,该段也回答了“在什么时候”,eden满了进行minor gc,升到老年代的对象大于老年代剩余空间进行full gc,(不止于此,还有其他情况)。
四、垃圾收集器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值