【JVM】Java中的GC垃圾回收机制

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

1、基本介绍:

• 在Java中,程序员不需要去关心内存动态分配和垃圾回收的问题,这一切都交给了JVM来处理。
• 它让开发者无需关注空间的创建和释放,而是以守护进程的形式在后台自动回收垃圾。该进程会在内存紧张的时候自动跳出来,把堆空间的垃圾全部进行回收,从而保证程序的正常运行。
• 顾名思义,垃圾回收就是释放垃圾占用的空间,那么在Java中,什么样的对象会被认定为“垃圾”?那么当一些对象被确定为垃圾之后,采用什么样的策略来进行回收(释放空间)。

2、如何确定一个对象是垃圾:【垃圾就是所有不再存活的对象】

2.1、引用计数法:【java没有采用】

• 在java中是通过引用来和对象进行关联的,也就是说如果要操作对象,必须通过引用来进行。那么很显然一个简单的办法就是通过引用计数来判断一个对象是否可以被回收。如果一个对象没有任何引用与之关联,则说明该对象基本不太可能在其他地方被使用到,那么这个对象就成为可被回收的对象了。
• 这种方式的特点是实现简单,而且效率较高,但是它无法解决循环引用的问题,因此在Java中并没有采用这种方式(Python采用的是引用计数法)。

    //对象循环引用的问题:
    public class Main {
        public static void main(String[] args) {
            MyObject object1 = new MyObject();
            MyObject object2 = new MyObject();

            object1.object = object2;
            object2.object = object1;

            object1 = null;
            object2 = null;
        }
    }

    class MyObject{
        public Object object = null;
    }

将object1和object2赋值为null,也就是说object1和object2指向的对象已经不可能再被访问,但是由于它们互相引用对方,导致它们的引用计数都不为0,那么垃圾收集器就永远不会回收它们。

2.2、可达性分析法:【java中采用】

• 基本思路是把所有引用的对象想象成一棵树,从树的根结点 GC Roots 出发,持续遍历找出所有连接的树枝对象,这些对象则被称为“可达”对象,或称“存活”对象。其余的对象则被视为“死亡”的“不可达”对象,或称“垃圾”。
• 通过一系列的“GC Roots”对象作为起点进行搜索,如果在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的,不过要注意的是被判定为不可达的对象不一定就会成为可回收对象。被判定为不可达的对象要成为可回收对象必须至少经历两次标记过程,如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了。

2.2.1、GC Roots讲解:

• GC Roots 本身一定是可达的,这样从它们出发遍历到的对象才能保证一定可达。
• 每次垃圾回收器会从这些GC Roots根结点开始遍历寻找所有可达节点,所有 GC Roots 不可达的对象都称为垃圾。
• java主要有以下4种GC Roots对象:
- 虚拟机栈(帧栈中的本地变量表)中引用的对象。
- 方法区中静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中 JNI 引用的对象。

2.3、java中将对象判定为可回收对象的情况:

1. 显示地将某个引用赋值为null或者将已经指向某个对象的引用指向新的对象:
Object obj = new Object();
obj = null;
Object obj1 = new Object();
Object obj2 = new Object();
obj1 = obj2;
2. 局部引用所指向的对象:
//这是一个方法
void fun() { 
.....
    for(int i=0;i<10;i++) {
        Object obj = new Object();
        System.out.println(obj.getClass());
    }   
}

循环每执行完一次,生成的Object对象都会成为可回收的对象。
3. 只有弱引用与其关联的对象:

3、垃圾回收算法:【垃圾收集器所使用的回收垃圾的算法】

3.1、Mark-Sweep(标记-清除)算法:

分为两个阶段:
标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。
缺点:
标记-清除算法实现起来比较容易,但是有一个比较严重的问题就是容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。

3.2、Mark-Compact(标记-整理)算法:

  • 该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的可回收内存。
  • 适合 存活对象多,垃圾少 的情况,它只需要清理掉少量的垃圾,然后挪动下存活对象就可以了。

3.3、Copying(复制)算法:

  • 为了解决Mark-Sweep算法的缺陷,Copying算法就被提了出来。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。
  • 适合 存活对象少,垃圾多 的情况,这样在复制时就不需要复制多少对象过去,多数垃圾直接被清空处理。
缺点:
    这种算法虽然实现简单,运行高效且不容易产生内存碎片,但是却对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。 而且Copying算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。

3.4、Generational Collection(分代收集)算法【java采用】:

3.4.1、Java 的堆结构:
• 一块 Java 堆空间一般分成三部分,这三部分用来存储三类数据:
    ○ 刚刚创建的对象:
        § 在代码运行时会持续不断地创造新的对象,这些新创建的对象会被统一放在一起。因为有很多局部变量等在新创建后很快会变成 不可达 的对象,快速死去 ,因此这块区域的特点是 存活对象少,垃圾多 。形象点描述这块区域为: 新生代;
    ○ 存活了一段时间的对象:
        § 这些对象早早就被创建了,而且一直活了下来。我们把这些 存活时间较长 的对象放在一起,它们的特点是 存活对象多,垃圾少 。形象点描述这块区域为: 老年代;
    ○ 永久存在的对象:
        § 比如一些静态文件,这些对象的特点是不需要垃圾回收,永远存活。形象点描述这块区域为:永久代 。(不过在 Java 8 里已经把 永久代 删除了,把这块内存空间给了 元空间,后续文章再讲解。)
• 也就是说,常规的 Java 堆至少包括了 新生代 和 老年代 两块内存区域,而且这两块区域有很明显的特征:
    ○ 新生代:存活对象少、垃圾多
    ○ 老年代:存活对象多、垃圾少
3.4.2、新生代-复制 回收机制:
思路:把内存按 8:1:1 分
  • 9:1 的内存划分有可能把年轻对象放到 老年区 ,那就换成 8:1:1,依次取名为 Eden[‘i:dn]、SurvivorA [sə’vaɪvə(r)] 、Survivor B 区,其中 Eden 意为伊甸园,形容有很多新生对象在里面创建;Survivor区则为幸存者,即经历 GC 后仍然存活下来的对象。
  • 工作原理如下:
    • 首先,Eden区最大,对外提供堆内存。当 Eden 区快要满了,则进行 Minor GC,把存活对象放入 Survivor A 区,清空 Eden 区;
    • Eden区被清空后,继续对外提供堆内存;
    • 当 Eden 区再次被填满,此时对 Eden 区和 Survivor A 区同时进行 Minor GC,把存活对象放入 Survivor B 区,同时清空 Eden 区和Survivor A 区;
    • Eden区继续对外提供堆内存,并重复上述过程,即在 Eden 区填满后,把 Eden 区和某个 Survivor 区的存活对象放到另一个 Survivor 区;
    • 当某个 Survivor 区被填满,且仍有对象未被复制完毕时,或者某些对象在反复 Survive 15 次左右时,则把这部分剩余对象放到Old老年代 区;
    • 当 Old 区也被填满时,进行 Major GC,对 Old 区进行垃圾回收。
    注意:
在真实的 JVM 环境里,可以通过参数 SurvivorRatio 手动配置 Eden 区和单个 Survivor 区的比例,默认为 8:1:1
3.4.3、老年代-标记整理 回收机制:
  • 老年代一般存放的是存活时间较久的对象,所以每一次 GC 时,存活对象比较较大,也就是说每次只有少部分对象被回收。
  • 选择 存活对象多,垃圾少 的标记整理 回收机制,仅仅通过少量地移动对象就能清理垃圾,而且不存在内存碎片化。
注意:
在堆区之外【JVM的方法区里】还有一个永久代(Permanet Generation),它用来存储class类、常量、方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类。

猜你喜欢

转载自blog.csdn.net/www1575066083/article/details/80957389