Java垃圾回收概述

2021-07-22  本文已影响0人  小丸子的呆地

垃圾回收,就是释放无用对象占用的空间,从而减少内存占用,提升程序性能,防止内存移除。当一个对象不再被需要时,该对象就需要被回收并释放空间。

Java内存运行时数据局域划分

Java 内存运行时数据区域包括程序计数器、虚拟机栈、本地方法栈、堆等区域。
程序计数器、虚拟机栈和本地方法栈都是线程私有的,当线程结束时,这些区域的生命周期也结束了,因此不需要过多考虑回收的问题。
堆是线程间共享的,并且是虚拟机管理的内存中最大的一块,堆中的内存的分配和回收是动态的,垃圾回收主要关注的是堆空间。

如何判断对象是否可回收

垃圾回收器在对堆进行回收之前,首先需要确定哪些对象是可回收的。常用的算法有两种,引用计数算法和根搜索算法。

引用计数算法

引用计数算法给每个对象添加引用计数器,用于记录对象被引用的计数,引用计数为 0 的对象即为可回收的对象。

虽然引用计数算法的实现简单,判定效率也很高,但是在对象循环引用的场景下,计数永远不会为0,无法被回收。因此引用计数算法是有重大缺陷的。

可达性分析

主流的商用程序语言都是使用根搜索算法判断对象是否可回收。根搜索算法的思路是,从若干被称为 GC Roots 的对象开始进行搜索,不能到达的对象即为可回收的对象。

在 Java 中,GC Roots 一般包含下面几种对象:

虚拟机栈中引用的对象;
本地方法栈中的本地方法引用的对象;
方法区中的类静态属性引用的对象;
方法区中的常量引用的对象。

垃圾回收算法

标记-清除算法

标记-清除算法是最基础的垃圾回收算法,后续的垃圾收集算法都是基于标记-清除算法进行改进而得到的。标记—清除算法分为“标记”和“清除”两个阶段,首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。

标记-清除算法有两个主要缺点。一是效率问题,标记和清除的效率都不高,二是空间问题,标记清除之后会产生大量不连续的内存碎片,导致程序在之后的运行过程中无法为较大对象找到足够的连续内存。

标记-复制算法

复制算法是将可用内存分成大小相等的两块,每次只使用其中的一块,当用完一块内存时,将还存活着的对象复制到另外一块内存,然后把已使用过的内存空间一次清理掉。

复制算法解决了效率问题。由于每次都是对整个半区进行内存回收,因此在内存分配时不需要考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可。复制算法的优点是实现简单,运行高效,缺点是将内存缩小为了原来的一半,以及在对象存活率较高时复制操作的次数较多,导致效率降低。

标记-整理算法

标记-整理算法是根据老年代的特点提出的。标记过程与标记-清除算法一样,但后续步骤不是直接回收被标记的对象,而是让所有存活的对象都向一端移动,然后清除边界以外的内存。

分代收集算法

分代收集算法根据对象的存活周期不同将内存划分为多个区域,对每个区域选用不同的垃圾回收算法。

一般把 Java 堆分为新生代和老年代。在新生代中,大多数对象的生命周期都很短,因此选用复制算法。在老年代中,对象存活率高,因此选用标记—清除算法或标记—整理算法。

对象进入老年代的情况分析

https://www.jianshu.com/p/0ce649851644

finalize 方法

与垃圾回收有关的另一个方法是 finalize 方法。该方法在 Object 类中被定义,GC线程在释放对象占用的内存之前会调用该方法。
该方法的默认实现不做任何事,如果必要,子类应该重写该方法,一般建议在该方法中释放对象持有的资源。

目前这个方法已经被废弃,不推荐使用,原因是错误使用此方法,可能会导致内存泄露,甚至GC线程死锁,比如在这个方法内部重新将本对象交给其他对象引用。

触发GC的方法

调用垃圾回收器的方法是 gc,该方法在 System 类和 Runtime 类中都存在。
在 Runtime 类中,方法 gc 是实例方法,方法 System.gc 是调用该方法的一种传统而便捷的方法。
在 System 类中,方法 gc 是静态方法,该方法会调用 Runtime 类中的 gc 方法。
其实,java.lang.System.gc 等价于 java.lang.Runtime.getRuntime.gc 的简写,都是调用垃圾回收器。
方法 gc 的作用是提示 Java 虚拟机进行垃圾回收,该方法由系统自动调用,不需要人为调用。
该方法被调用之后,由 Java 虚拟机决定是立即回收还是延迟回收。

引用的分类-强软弱虚

引用计数算法和根搜索算法都需要通过判断引用的方式判断对象是否可回收。

在 JDK 1.2 之后,Java 将引用分成四种,按照引用强度从高到低的顺序依次是:强引用、软引用、弱引用、虚引用。

上一篇下一篇

猜你喜欢

热点阅读