说一下Java的垃圾回收机制

1.Java垃圾回收概述

垃圾回收(Garbage Collection,GC)是Java虚拟机自动管理堆内存的机制,负责识别不再使用的对象并释放其占用的内存。

垃圾回收的触发机制如下:

  • **内存不足时:**​ 当JVM检测到堆内存不足,无法为新的对象分配内存时,会自动触发垃圾回收。

  • 手动请求: ​ 虽然垃圾回收是自动的,开发者可以通过调用System.gc() 或**Runtime.getRuntime().gc()**建议 JVM 进行垃圾回收。不过这只是一个建议,并不能保证立即执行。

  • JVM参数: ​ 启动 Java 应用时可以通过 JVM 参数来调整垃圾回收的行为,比如:-Xmx(最大堆大小)、-Xms(初始堆大小)等。

  • **对象数量或内存使用达到阈值:**​ 垃圾收集器内部实现了一些策略,以监控对象的创建和内存使用,达到某个阈值时触发垃圾回收。


2.Java垃圾回收判断算法

引用计数法

基本原理:每个对象 分配一个专有的引用计数器,当一个对象被引用后,计数器+1,引用失效后,计数器-1,计数器为0时,表示对象不再被任何变量引用,可以被回收。

这个算法非常简单易懂,可是它有一个致命的缺点,就是循环引用的问题,接下来我们来看个循环引用的例子

java 复制代码
// 一个简单的循环引用例子
class RefObject {
    public Object instance = null;
}

public class Main {
    public static void main(String[] args) {
        RefObject objA = new RefObject(); // objA引用计数 = 1
        RefObject objB = new RefObject(); // objB引用计数 = 1

        objA.instance = objB; // objB引用计数 = 2 (被objA.instance引用)
        objB.instance = objA; // objA引用计数 = 2 (被objB.instance引用)

        objA = null; // objA引用计数减为1 (仅被objB.instance引用)
        objB = null; // objB引用计数减为1 (仅被objA.instance引用)

        // 此时,两个对象已经无法被外界访问(objA和objB变量都指向了null),
        // 但它们彼此引用,引用计数均为1,无法被引用计数算法回收。
    }
}

可以看到,当objA和objB设置为null时,正常来说应该被垃圾回收,可是由于objA被objB引用,objB被objA引用,导致计数器不为0无法被回收,这就是循环引用的问题所在。所以大部分情况我们都是使用下面这种算法。

可达性分析算法

基本原理: 通过一系列称为 "GC Roots" ​ 的根对象作为起始节点,从这些根节点开始,根据引用关系向下搜索, 的如果某个对象到GC Roots间没有任何引用链相连 ,则证明此对象是不可用的,可以被回收。

它与引用计数法最核心的区别在于:它不关心对象被引用了多少次,只关心能否从根节点触达

该算法从根本上解决了循环引用的问题。在上面的例子中,虽然A和B互相引用,但只要它们无法从GC Roots到达,就会被判定为垃圾。

话又说回来了,难道可达性分析算法就没用缺点吗?

从原理那我们也不难看出,可达性分析算法的实现相比于引用计数法更加复杂了,同时,为了保证分析结果的准确性,在进行可达性分析时,必须在一个一致性快照 中进行。这意味着在分析期间,整个执行系统必须被冻结,不允许对象的引用关系发生变化。这个过程就是著名的Stop The World(STW),是垃圾收集器产生停顿的主要原因之一。


3.Java垃圾回收算法

标记-清除算法

**基本原理:**先标记所有存活对象,再清除未标记对象

一个简单的实现如下:

java 复制代码
// 算法伪代码示意
public class MarkSweep {
    void mark(Object obj) {
        if (obj != null && !obj.isMarked()) {
            obj.setMarked(true);
            mark(obj.references); // 递归标记所有引用对象
        }
    }
    
    void sweep(Heap heap) {
        for (Object obj : heap.objects) {
            if (!obj.isMarked()) {
                heap.free(obj); // 释放未标记对象
            } else {
                obj.setMarked(false); // 重置标记位
            }
        }
    }
}

该算法简单直接 ,不过效率不高 而且会产生内存碎片

复制算法

**基本原理:**内存分成两块,每次申请内存时都使用其中的一块,当内存不够时,将这一块内存中所有存活的复制到另一块上。然后将然后再把已使用的内存整个清理掉。

该算法解决了不会产生内存碎片的问题,可又带来了新的问题:每次申请内存时只能申请一半的内存空间,内存利用率严重不足。

标记-整理算法

基本原理: 标记-整理算法的"标记"过程与"标记-清除算法"的标记过程一致,但标记之后不会直接清理。而是将所有存活对象都移动到内存的一端。移动结束后直接清理掉剩余部分。

该算法既解决了内存碎片问题的产生,也提高内存利用率,不过整理阶段需要移动对象,开销较大。

分代收集算法

基本原理: 分代收集是将内存划分成了新生代和老年代。分配的依据是对象的生存周期,或者说经历过的垃圾回收次数。对象创建时,一般在新生代申请内存,当经历一次 GC 之后如果对还存活,那么对象的年龄 +1。当年龄超过一定值(默认是15)后,如果对象还存活,那么该对象会进入老年代。

不同分代使用的回收机制也不用,我们来介绍下这两种内存划分

新生代:

  • Eden区(80%):新对象分配区

  • Survivor区(20%):Minor GC后存活对象存放区

  • 使用复制算法,回收频率高,由于

老年代:

  • 存放长期存活对象

  • 使用标记-清除或标记-整理算法


4.Java中常见的垃圾回收器

Serial收集器

  • 垃圾回收算法:复制算法
  • 特点:单线程,简单高效
  • 工作模式:暂停所有用户线程(STW),进行垃圾回收

JVM配置示例:

java 复制代码
# JVM 参数
-XX:+UseSerialGC

Parallel收集器

实际上是Serial收集器的多线程版本

JVM配置示例:

java 复制代码
# JVM 参数
-XX:+UseParallelGC           # 新生代Parallel Scavenge,老年代Serial Old
-XX:+UseParallelOldGC        # 新生代Parallel Scavenge,老年代Parallel Old
-XX:+UseParNewGC             # 新生代ParNew,需配合CMS使用
-XX:ParallelGCThreads=n      # 设置并行GC线程数

适合对延迟不敏感,高吞吐量需求的场景。

并发标记清除回收器 (CMS)

老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿 的特点,追求最短GC回收停顿时间

收集过程

  1. 初始标记:STW,标记GC Roots直接关联对象

  2. 并发标记:与应用程序并发,遍历整个对象图

  3. 重新标记:STW,修正并发标记期间的变动

  4. 并发清除:与应用程序并发,清理死亡对象

JVM配置示例:

java 复制代码
# JVM 参数
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=68  # 老年代使用率触发阈值
-XX:+UseCMSCompactAtFullCollection     # 开启碎片整理

相应的,CMS回收器也有部分缺点,如对CPU资源敏感无法处理浮动垃圾 (并发时新产生的垃圾),**内存碎片问题(**标记-清除算法)

G1 回收器

这是目前使用率最高的垃圾回收器,它做了一个革命性的设计:不再物理分代,而是逻辑分区的Region, 此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前几种收集器回收的范围仅限于新生代或老年代

下面是G1垃圾回收器的基本流程:

JVM配置示例:

java 复制代码
# JVM 参数
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200      # 目标最大停顿时间
-XX:G1HeapRegionSize=n        # Region大小(1M~32M,2的幂)

制作不易,如果对你有帮助请**点赞,评论,收藏,**感谢大家的支持

相关推荐
superman超哥2 小时前
Rust 与数据库连接池的集成:从理论到生产实践
开发语言·rust·编程语言·rust与数据库连接池的集成
tqs_123452 小时前
@transactional事务失效场景
java·数据库·mybatis
fl1768312 小时前
基于python+tkinter实现的Modbus-RTU 通信工具+数据可视化源码
开发语言·python·信息可视化
cyforkk2 小时前
01、Java基础入门:JDK、JRE、JVM关系详解及开发流程
java·开发语言·jvm
黎雁·泠崖2 小时前
Java static避坑:静态与非静态访问规则全解析
java·开发语言
掘根2 小时前
【jsonRpc项目】基本的宏定义,抽象层和具象层的实现
开发语言·qt
步步为营DotNet2 小时前
深度解析.NET中IEnumerable<T>.SelectMany:数据扁平化与复杂映射的利器
java·开发语言·.net
aaa最北边2 小时前
进程间通信-1.管道通信
android·java·服务器
Dreamy smile2 小时前
JavaScript 实现 HTTPS SSE 连接
开发语言·javascript·https