垃圾回收算法

什么要去了解GC和内存分配呢?

当前的内存动态分配技术和 内存回收技术已经相当成熟,但是当需要排查各种内存溢出和内存泄漏时,当垃圾收集升为系统达到更高并发量的瓶颈的时候,我们就需要对这些“自动化的技术”实施必要的监控和调节。

判断对象的生死

java堆里几乎存放了所有的实例化对象,垃圾收集器在对堆进行回收之前,首先要判断哪些对象还活着,哪些对象已经死去。

判断对象是否存活的算法

1引用计数器算法

对对象添加一个引用计数器,每当一个地方引用它时,计数器加1,每当一个地方引用失效时,计数器减一,当计数器值为0时,判断该对象已经不再被使用。

引用计数器算法实现简单,判定效率也高,缺点是难解决对象之间循环引用的问题。

2可达性分析算法

通过一系列的被称为GC Roots的对象作为起始点,从这些点开始向下搜索,搜索走过的路径被称为引用链,当一个对象到任何一个GC Roots没有引用链相连,说明该对象是不可用的,可以被判定为可回收的对象。

可以作为GC Roots的对象有:

虚拟机栈中引用的对象

方法区中类静态属性引用的对象

方法区中常量引用的对象

本地方法栈中JNI引用的对象

关于引用

在JDK1.2之前,关于引用的定义:如果一个refrence类型的数据中存放的是数值是另外一块内存的起始地址,就称这块内存代表着一个引用。

在jdk1.2之后,对引用的定义进行了扩充:

强引用

强引用是指在程序代码中普遍存在的,类似Object object = new Object()这类的引用,只要是强引用,永远不会被垃圾回收器回收

弱引用

弱引用用来描述一些有用但是非必须的引用。对于软引用关联的对象,在系统要发生内存溢出之前,会把这些对象列进回收范围进行第二次回收,如果回收之后还是没有足够的内存,才会抛出内存溢出的异常。

软引用

软引用用于描述非必需对象,被软引用关联的对象,只能活到下一次垃圾收集发生之前。当垃圾收集器工作的时候,无论当前的内存是否足够,都会回收掉只被弱引用关联的对象。

虚引用

一个对象是否有虚引用完全不会对其生存时间构成任何影响,为一个对象设置关联虚引用关联的唯一的目的就是,在这个对象被收集器回收时,收到一个系统通知。

回收方法区

永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。

判断是否是无用的类需要同时满足的三个条件:

1.该类的所有实例都已经被回收。

2.加载该类的ClassLoader已经被回收。

3.该类对应的java.lang.Class对象没有任何地方被引用,无法再任何地方通过该类的反射访问该类的方法。

垃圾回收算法

标记-清除算法

分为两个阶段:标记和清除

首先标记出所有需要回收的对象,在标记完成后统一回收所有需要回收的对象。

有两点不足:

效率方面,标记和清除的效率都不太高。

空间方面,清除完之后会产生大量的不连续的内存碎片。

复制算法

将内存分为大小相等的两块,每次只用其中的一块,当这一块的内存用完了之后,就将还存活的对象按顺序复制到另外一半内存上去,然后把使用过的前一半内存一次清理掉。

这样做,每次是对整个半区进行内存回收,内存分配时也不需要考虑内存碎片等复杂情况,实现简单运行高效。缺点是每次需要内存缩小到了原来的一半。

大部分商业虚拟机都采用这种方式来回收新生代。研究表明,大部分的新生代对象百分之98都是朝生暮死,不需要按照1:1的空间来划分,而是分为了一块Eden空间和两块较小的survivor空间。HotSpot默认的大小比例是8:1。当回收时,将Eden和其中一块survivor中还存活的对象一次性复制到另外一块survivor空间中。没法保证每次新生代对象的存活率不高过百分十10,当survivor空间不够时,需要用其他内存进行分配担保。

标记-整理算法

复制算法在对象存活率高的时候,就要进行较多的复制操作,效率就会变低。所以老年代一半不采用复制算法。

标记-整理算法让所有存活的对象的朝一端移动,然后直接清理掉端边界以外的内存。

分代收集算法

当前的商业虚拟机的垃圾收集都采用分代收集的算法。一般是把java堆分为了老年代和新生代。

新生代采用复制算法,老年代采用标记-清除算法或者标记-整理算法。

Last modification:November 6th, 2019 at 04:17 pm
如果觉得我的文章对你有用,请随意赞赏