Java垃圾收集

Java内存模型中的程序计数器、Java栈和本地方法栈为各线程私有,随着线程的创建而生,随线程的终结而灭,这几个区域的内存基本上是在类结构确定下来的时候就已经确定的,因此这三个区域的内存的分配回收都具有确定性,回收价值不大。Java的垃圾收集主要发生在线程共享的发放区和Java堆中。

确定Java对象是否存活

要进行对象回收,首先要确定的是Java对象是否还存活,只有对失去引用的对象才可以进行回收。

引用计数法

引用计数法的算法是给对象添加一个引用计数器,当有一个地方引用该对象时,引用计数器加1;当引用失效时,引用计数器的值减1;任何引用计数器为0的对象就是不可能再被引用的对象,便可以有垃圾回收器进行回收。
引用计数法实现简单,效率也比较高,但是该方法不能回收那些相互引用,除此之外再无其他对象引用的对象。对于这些对象,引用计数器无法通知GC回收它们。

可达性分析算法

可达性分析算法的基本思路是通过一系列”GC Roots”的对象作为起始点,从这些节点开始向下搜素,搜索所经过的路径称为“引用链”。当一个对象到GC Roots没有任何引用链相连时,即GC Roots到该对象不可达时,证明此对象是不可用的,将会被垃圾回收器回收。
在Java语言中,可以作为GC Roots的对象包括以下几种:

  • 虚拟机栈中引用的数据
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(一般说的是Java native方法)引用的对象。

java中对象的引用

jdk1.2之后,Java对引用的概念进行了扩充,将应用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)4种,4种引用额强度依次逐渐减弱。

  1. 强引用:强应用是程序中普遍存在的引用,只要强引用存在,垃圾回收器永远不会回收该对象;
  2. 软引用:用来描述一些还有用但是不是必须的引用。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。
  3. 弱引用:描述非必须对象。弱引用对象只能存活到下一次垃圾回收之前,当垃圾收集器工作时,不论当前的内存是否够用,都会回收弱引用关联的对象。
  4. 虚引用也成为幽灵引用或者幻影引用。一个对象是否有虚引用,完全不会对其生存时间产生影响,也无法通过一个虚引用获得一个对象实例。

对象的死亡

要宣告一个对象的死亡,至少要经过两次标记过程,若果对象在可达性分析时没有GC Roots与对象相连,那么该对象将会被第一次标记并且进行一次筛选,筛选的条件是该对象是否执行了finalize方法,如果该方法没有被复写或者已经执行过了,虚拟机将会将以上两种情况视为没有必要执行。如果被判断为有必要执行finalize方法,那么可以在调用finalize方法时给该对象一个引用,该对象将在第二次标记中移除回收集合,如果该对象没有被移除,那么第二次标记时将会被真正回收。

回收方法区

Java虚拟机规范中不要求方法区必须进行垃圾回收,可以通过配置来决定是否进行垃圾回收。方法区主要回收两部分内容,废弃的常量和无用的类,回收废弃常量和回收对象基本相似。
Java中无用的类需要满足以下条件;

  1. 所有该类的实例已经被回收,Java堆中不存在该类的任何实例。
  2. 加载类的ClassLoarder已经被回收
  3. 该类对应的Class对象没有任何地方被引用,无法在任何地方通过反射技术访问该类的方法。

满足以上3个条件的无用类可以被回收,但是是否进行回收,可以通过虚拟机提供的参数进行控制。

垃圾收集算法

标记清除算法

标记清除算法分为两个阶段,首先标记出所有需要回收的对象,在标记后统一进行回收。该方法是收集算法的基础,后续方法都是对该方法的不足进行改进实现的。
这里写图片描述
标记清除算法有两个主要的不足:

  1. 效率问题:标记和清除两个过程的效率都不高;
  2. 标记清除之后产生大量的不连续空间,后面如果有大对象分配内存空间,有可能会提前出发垃圾回收。

复制算法

将可用内存按容量分成大小相等的两块,每次只使用其中一块,当这块内存使用完了,就将还存活的对象复制到另一块内存上去,然后把使用过的内存空间一次清理掉。这样使得每次都是对其中一块内存进行回收,内存分配时不用考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。但是该算法对内存空间的浪费较为严重。
这里写图片描述
现在的商业虚拟机大都采用这种算法回收新生代,将内存划分为一块较大的Eden区和两块相同大小的相对较小的Survivor区(hotspot中Eden区:survivor区=8:1),每次使用其中一块Survivor区喝Eden区,回收时将Eden区和Suvivor区的活着的对象复制到另外一块Survivor区。

标记—整理算法

标记-整理算法在标记-清除算法基础上做了改进,标记阶段是相同的标记出所有需要回收的对象,在标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫做整理。
这里写图片描述
标记-整理算法相比标记-清除算法的优点是内存被整理以后不会产生大量不连续内存碎片问题。复制算法在对象存活率高的情况下就要执行较多的复制操作,效率将会变低,而在对象存活率高的情况下使用标记-整理算法效率会大大提高。

分代收集算法

根据内存中对象的存活周期不同,将内存划分为几块,java的虚拟机中一般把内存划分为新生代和年老代,当新创建对象时一般在新生代中分配内存空间,当新生代垃圾收集器回收几次之后仍然存活的对象会被移动到年老代内存中,当大对象在新生代中无法找到足够的连续内存时也直接在年老代中创建。
这里写图片描述
在新生代运用复制算法进行回收,在老年代运用标记清除或者标记整理算法进行回收。

内存分配和回收策略

对象的内存分配,主要分配在新生代的Eden区,如果启动了本地线程分配缓冲,将按线程优先分配在TLAB,少数情况有可能直接分配在老年代,分配的规则并不是固定的,主要取决于使用的是哪一种垃圾收集器组合,虚拟机中内存相关打得参数的配置。
以下是几条普遍的内存分配规则。

  1. 对象优先在Eden区分配
  2. 大对象直接进入老年代
  3. 长期存活的对象进入老年代
  4. 动态年龄判定:虚拟机并不总是要求对象的年龄达到了MaxTenuringThreshold才能进入老年代,如果survivor区中所有相同年龄的对象的总和大于survivor的一半,年龄大于或者等于该年龄的 对象就可以直接进入老年代。
  5. 空间分配担保:如果新生代的垃圾回收,存活的对象较多,大于Survivor区,那么就要考虑将一部分对象交由老年代保存,但是老年代的大小能否允许本次垃圾回收存在一定的风险。一般老年代会把以往每次需要的平均值和剩余空间进行比较,如果该平均值小于剩余空间,那么会冒险进行一次Minor GC,如果失败,会进行一次Full GC。

内存回收和垃圾收集器在很多时候都是影响系统性能和并发性能的主要因素之一,虚拟机提供了多种不同的垃圾收集器以及大量的虚拟机调节参数,是因为只有根据实际需求、实现方式选择最优的收集方式才能获得最高的性能。

参考资料

周志明 《深入理解Java虚拟机》

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值