JVM — 垃圾回收机制

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Love667767/article/details/79825865

一、概述

《JVM — 内存模型》中,我们了解了JVM的内存模型,得到以下结论:

  1. 程序计数器、虚拟机栈、本地方法栈 都是线程私有的;
  2. 程序计数器、虚拟机栈、本地方法栈 随着线程的创建而创建,随着线程的销毁而回收;
  3. 程序计数器、虚拟机栈、本地方法栈 这几个区域的内存分配和回收都具备确定性,因此无需过多考虑回收的问题;
  4. Java堆、方法区 这两块内存区域在程序运行期会动态分配内存,具有不确定性;

小结: 因此,垃圾回收主要针对 Java堆、方法区 这两块内存区域;


二、如何判断对象是否被引用

判断一个对象是否被引用有两种方法:1.引用计数算法; 2.可达性分析算法;

2.1 引用计数算法

原理:

给对象中添加一个引用计数器,分如下三种情况:

  1. 当对象在一个地方被引用时,计数器值加1;
  2. 当引用时效时,计数器减1;
  3. 在任何时刻,计数器为0的对象就是没有被使用的对象,可以被回收;

缺点:

主流的JVM没有使用引用计数算法来管理内存,因为很难解决 对象之间相互循环引用 的问题;


2.2 可达性分析算法

这里写图片描述

原理:

  1. 从一系列称为 GC Roots 的对象作为起点,开始向下搜索,搜索所走过的路径称为 引用链
  2. 当一个对象到 GC Roots 没有任何引用链相连时,就说明该对象是不可用的(可被回收的);
  3. 上图中的 object5、object6、object7 虽然有被引用,但没有一条到 GC Roots 的引用链,所以是可回收的对象;


Java中可以作为 GC Roots 对象的有如下几种:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象;
  2. 方法区中静态属性引用的对象;
  3. 方法区中静态常量的对象;
  4. 本地方法栈中Native方法引用的对象;

2.3 方法区的回收

方法区主要回收两部分内容:废弃常量、无用的类

废弃常量:

类似回收Java堆中的对象;

回收 无用的类 需要 同时满足 3个条件:

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

三、垃圾收集算法

垃圾收集算法有4种:1.标记-清除算法;2.复制算法;3.标记-整理算法;4.分代收集算法


3.1 标记 - 清除算法

原理:

该算法分为 标记清除 两个阶段:首先标记处所有需要回收的对象,在标记之后统一回收所有被标记的对象;

缺点:

  1. 效率问题: 标记和清除两个过程效率都不高;
  2. 空间问题: 清除之后会产生大量不连续的内存碎片,当之后需要创建一个较大内存的对象时,会因为无法找到找到这么一块满足需求的内存区域而提前出发 GC 操作;

3.2 复制算法 (解决“标记 - 清除算法”的效率问题)

原理:

将内存分为大小相等的两块(A、B),每次只使用一块(A),当一块(A)内存用完时,将(A)中还存活的对象复制到另一块内存(B)中,然后清除内存(A),避免了内存回收之后的碎片化,简单、高效

缺点:

  1. 内存会缩小到原先的一半;
  2. 当内存中对象存活率较高时,要进行多次的复制操作,效率会降低;

3.3 标记 - 整理算法

原理:

和 “标记 - 清除算法” 类似,先标记需要清理的对象,然后将所有存活的对象都向一端移动,最后直接清理掉端边界以外的内存;


3.4 分代收集算法

目前JVM垃圾收集都采用“分代收集”;

原理:

将Java堆分为新生代和老年代,根据不同代的特点采用不同的垃圾收集算法;

  1. 新生代:大批量对象死亡,少量存活,使用 复制算法
  2. 老年代:对象存活率高,使用 标记 - 清除标记 - 整理 算法;

四、内存分配

这里写图片描述

说明:
上图是Java堆中的分代图:分为新生代、老年代、永久代
新生代又分为:Eden区、Survivor from区、Survivor to区 三部分;

问1: 新创建的对象,在哪里分配内存空间 ?
答: 多数情况下,对象的创建是在新生代Eden区中分配内存的;

问2: 为什么新生代需要两个 Survivor区
答: 避免存活对象复制时,碎片化的发生;
参考:《为什么新生代内存需要有两个Survivor区》

问3: 为什么新生代内存需要 Eden区Survivor区
答: 延缓新生代中的对象进入年老代的速度(主要还是新生代内存和年老代内存GC策略不同);从GC角度看,年老代GC对系统影响比新生代的大;


五、对象如何进入老年代

5.1 大对象直接进入老年代

由于新生代使用复制算法,为避免在Eden区和两个Survivor区之间发生大量的内存复制,因此当对象内存达到阀值后会直接进入老年代。

5.2 新生代对象年龄到一定程度后进入老年代

虚拟机给每个对象定义了一个Age的计数器,初始值为0,每经过一次GC并且存活,这个对象的Age就会加1,当增加到一定程度(默认为15),那么就会进入老年代中。

5.3 动态对象年龄判定

如果新生代Survivor区空间中相同年龄所有对象大小的总和大于Survivor区的一半,年龄大于或等于该年龄的对象就会直接进入老年代。


六、参考

猜你喜欢

转载自blog.csdn.net/Love667767/article/details/79825865