20190722 - Java垃圾回收详解

一、什么是垃圾回收

垃圾回收(Garbage Collection,GC):释放垃圾占用的空间,防止内存泄漏。对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。GC并不是Java的专利,它的历史比Java更久远哦~

二、什么样的就算垃圾呢

2.1 引用计数算法(Reachability Counting)

通过在对象头中分配一个空间保存该对象被引用的次数。
如果该对象被引用,则引用计数 + 1,如果引用被删除,则引用计数 - 1,当该对象的引用技术为0的时候,该对象就会被回收。但是有一个问题,如下:

public class ReferenceCountingLogic {
    public Object instance;
    public ReferenceCountingLogic(Object instance){
        this.instance = instance;
    }
}


   public static void main(String[] args) {
        ReferenceCountingLogic a = new ReferenceCountingLogic("anne");
        ReferenceCountingLogic b = new ReferenceCountingLogic("cheng");
        a.instance = b;
        b.instance = a;
        a = null;
        b = null;
    }

在这里插入图片描述
如上,a和b最后已经为null了,但是他们还相互引用着对方,所以他们的引用计数永远不会为0,GC也就无法回收他们,会存在内存泄露。所以JVM的垃圾回收并没有用这个方法。

2.2 可达性分析算法(Reachability Analysis)

通过一些被称为引用链(GC Roots)的对象做为起点,从这些节点开始向下搜索。搜索过的路径被称为Reference Chain,如果这个对象到GC Roots没有任何引用链,则证明该对象不可用。
在这里插入图片描述
那么,哪些属于GC root呢?

三、哪些属于GC root呢?

Java内存区域:
在这里插入图片描述
在Java语言中,可作为GC Root的对象包括以下四种:

3.1 虚拟机栈(栈帧中的本地变量表)中引用的对象。

public class StackLocalParamter {
    public StackLocalParamter(String name){}
}

public static void main(String[] args) {
        StackLocalParamter s = new StackLocalParamter("local");
        s = null;
    }

如上所示,s就为GC Root,如果s==null,那么"local"对象与s的关系也就断了,那么"local"对象将被回收。

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

public class MethodAreaStaticProperties {
    public static MethodAreaStaticProperties m;
    public MethodAreaStaticProperties(String name){}
}

public static void main(String[] args) {
        MethodAreaStaticProperties s = new MethodAreaStaticProperties("properties");
        s.m = new MethodAreaStaticProperties("paramer");
        s = null;
}

s 为GC Root,S置为null,那么s指向的properties对象将会与s断开链接,那么,properties对象会被回收。
但是m做为类的静态属性,也属于GC Root,所以paramer对象仍然与GC Root建立着链接,所以paramer对象不会被回收。

3.3 方法区中常量引用的对象

public class MethodAreaStaticProperties {
    public static final MethodAreaStaticProperties m = new MethodAreaStaticProperties("final");
    public MethodAreaStaticProperties(String name){}
}
public static void main(String[] args) {
        MethodAreaStaticProperties s = new MethodAreaStaticProperties("properties");
        s = null;
 }

m为方法区的常量引用,为GC Root,所以s置为null,final对象依旧会和GC Root保持连接而不会被回收。

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

任何Native接口都会使用某种本地方法栈,如果调用本地方法栈,不再线程的java栈压入新的帧,而是通过简单地动态连接并直接调用指定地本地方法。

四、怎么高效地回收垃圾?

4.1 标记 - 清除算法

把内存区域的对象进行标记,哪些是可回收标记出来,然后再拎出来清理掉,然后清理掉的内存就会变成未使用的内存,等待被再次使用,但是会存在很多内存碎片的问题。
在这里插入图片描述

4.2 复制算法

从标记清除算法上演变而来,解决内存碎片的问题。
将可用内存按大小平均分成两块,每次只使用其中一块,当一块用完了,就把存活对象复制到另一块,然后把已使用过的内存清理掉。
这样可以保证内存的连续使用。
但是存在问题是代价太大,有效的空间只有真正空间的一半。
在这里插入图片描述

4.3 标记整理法

与标记清除法一样,先对内存进行分类标记,然后把所有存活的对象向着一方挪动,然后未使用的一起清理掉。
但是会存在对内存的频繁移动,效率会很低。
在这里插入图片描述

4.4 分代收集算法

融合上述三种算法思想,组合使用。
根据对象的存活周期把内存分块。比如新生代和老年代。
新生代,每次垃圾收集时都会有大批对象死去,只有少量存活,那就使用复制算法,只需要复制少量活的对象即可完成收集。
老年代,对象的存活率高,使用标记清除或者标记整理算法来进行回收。

五、具体的内存模型和回收策略?

Java堆内存模型。
在这里插入图片描述
新生代 = eden区 + survivor区
survivor区 = From survivor区 + To survivor区

5.1 Eden区

存活率较低的对象,分配在新生代Eden区,如果Eden区没有足够空间,虚拟机会发起Minor GC操作,Eden会被清空,绝大部分对象会被回收,如果还存活的对象,就会进入survivor的From区(如果From区空间不够,则会进入Old区)

5.2 Survivor 区

每次执行 Minor GC,会将 Eden 区和 From 存活的对象放到 Survivor 的 To 区(如果 To 区不够,则直接进入 Old 区)。
存在的原因:
(1)、让新生代到老年代中间有个缓冲区,避免每一次Minor GC都会引起对象进入老年区。
(2)、虽然新生区的很多对象一次Minor GC未被消灭,但其实有可能两次三次就被消灭了,所以直接放在老年区也不合适,其实还是起到一个缓冲的作用叭。
所以,Survivor 的存在意义就是 减少被送到老年代的对象,进而减少 Major GC 的发生。
Survivor 的预筛选保证,只有经历 16 次 Minor GC 还能在新生代中存活的对象,才会被送到老年代。

为啥需要from和to两个区?
解决内存碎片化。
如果说Minor GC之后,Eden区被清空,存活的对象都被放在Survivor区,之前的Survivor区可能还有一些被清除的,如果采用标记清除的方式,就会产生很多内存碎片,而新生代更新换代又快,所以会有碎片化的情况会很严重。那么,如果有两个区,每次Minor GC,会将 Eden 区和 From 区中的存活对象复制到 To 区域,第二次的时候,From 与 To 职责兑换,这时候会将 Eden 区和 To 区中的存活对象再复制到 From 区域,以此反复。所以,会一直有一个空的区域,一个非空的没有碎片化的区域。(有点复制算法的感jio哦~),当然也不是越多越好,越多会容易引起内存快速满,也比较麻烦啦。

5.3 Old 区

老年代占据着 2/3 的堆内存空间,只有在 Major GC 的时候才会进行清理,每次 GC 都会触发“Stop-The-World”。
内存越大,STW的时间就越长,所以内存不是越大越好。复制算法并不适用于存活率较高的场景,所以老年代采用标记-整理算法。
有哪些对象会进入老年区呢?
(1)、无法安置的对象会直接进入老年区。
(2)、大对象,需要大量连续内存,会直接进入老年代,避免在Eden区以及Survivor区之间发生大量的内存复制。
(3)、长期存活对象,虚拟机会给每个对象定义一个年龄,正常情况下,对象会不断地在Survivor的from区与To区之间移动,对象经历一次 Minor GC,年龄就增加1岁,当年龄增加到15岁的时候,会进入老年代(JVM 设置)。
(4)、动态对象年龄,也不是说非得等到15岁,如果Survivor空间种相同年龄所有对象大小总和大于Survivor空间的一半,那么大于该年龄的对象就可以直接进入老年区(也就是新生代装不下了,分给老年区一些)。

参考链接:https://mp.weixin.qq.com/s?__biz=MjM5ODI5Njc2MA==&mid=2655825531&idx=1&sn=c0bcbb5169996f87bef267c7c58e7da7&chksm=bd74e3ac8a036aba71fd6fd125e42e26650a19a70985422afc866af398377d12cfc1e3339a93&mpshare=1&scene=1&srcid=&rd2werd=1#wechat_redirect

原创文章 88 获赞 21 访问量 3万+

猜你喜欢

转载自blog.csdn.net/cfy1024/article/details/96906499