Java 垃圾收集技术

简介:

Java 垃圾收集技术
前言
在计算机科学中,垃圾收回(GC: garbage collection)是内存自动管理的一种方式,它并不是同 Java 语言一起诞生的,实际上,早在 1959 年为了简化 Lisp 语言的手动内存管理,该语言的作者就开始使用了内存自动管理技术。 垃圾收集和手动内存管理刚好相反,后者需要编程人员自己去指定需要释放的对象然后将内存归还给操作系统,而前者不需要关心给对象分配的内存回收问题。Java 语言使用自动垃圾收集器来管理对象生命周期中的内存,要进行垃圾收集首先需要明确三个问题:1. 哪些内存需要回收、2. 什么时候进行回收、3. 怎么进行内存回收。接下来让我们一起看看 Java 语言对这些问题是如何处理的。

哪些内存需要回收
为了方便管理和跨平台,Java 虚拟机规范规定在执行 Java 程序的时候把它所管理的内存划分为若干个不同的数据区域。这些区域都有着各自不同的用途以及创建和销毁的时间,有的数据区域随着用户线程的启动和结束而建立和销毁,有的区域会随着虚拟机进程的启动和停止而存在和销毁。更多有关运行时数据区域的内容请看 Java 运行时数据区域。
由于 Java 运行时数据区域中的 程序计数器、虚拟机栈和本地方法栈和线程的生命周期一致,随线程的启动和结束而建立和销毁。而且当我们的类结构确定了之后,在编译期间,一个栈帧需要分配内存的大小基本上也就确定下来了,这三个区域的内存分配和收回都是具备确定性的,不需要我们过多的去考虑内存回收问题。主要考虑Java 堆和方法区的内存回收的问题。

什么时候进行回收
在 Java 语言中,一个对象的生命周期分为以下三个阶段:

对象创建阶段 通常我们使用 new 关键字进行对象创建 e.g. Object obj = new Object();,当我们创建对象时,Java 虚拟机将分配一定大小的内存来存储该对象,分配的内存量可能会根据虚拟机厂商的不同而有所不同。
对象使用阶段 在这个阶段,对象被应用程序的其它对象使用(其它活动对象拥有指向它的引用)。在使用期间,该对象会一直驻留在内存当中,并且可能包含对其它对象的引用。
对象销毁阶段 垃圾收集系统监视对象,如果发现对象不被任何对象引用了,则进行该对象内存回收操作。
那么问题来了,该如何去判断一个对象有没有被引用呢?目前,主要有两种判断对象是否存活的算法,分别是 引用计数算法(Reference counting algorithm)和可达性分析算法(Accessibility analysis algorithm)。

引用计数算法
首先我们看看引用计数算法是如何判断的,该算法的主要思想就是给每个对象都添加一个引用计数器,当该对象被变量或者另一个对象引用时该计数器值就会加 1,同时当对象的一个引用无效时,对象计数器的值会相应的减 1。当对象引用计数器的值为 0 时,说明该对象已经不再被引用了,那么就可以销毁对象进行内存回收操作了。这个算法的实现比较简单,对象是否“存活”的判断效率也比较高,这个算法看起来确实不错,但是它有个致命的缺点就是:无法解决对象间相互引用的问题。相互引用简单来说就是,有两个对象 object1 和 object2 都有一个引用类型字段 ref,并且做了如下赋值操作:

object1.ref = object2;
object2.ref = object1;
这两个对象除了上面这个赋值之外,不被其它任何对象引用,实际上这两个对象都不可能再被访问了,但是因为它们俩都互相引用了对方,导致引用计数器不为 0,导致使用引用计数器算法的 垃圾收集器 无法收集它们,它们就会一直存在于内存之中直到虚拟机进程结束。正是因为这个原因,市场上主流的 Java 虚拟机大部分都没有选用这个算法来管理内存,下面介绍的 可达性分析算法 就可以很好的避免了对象间相互引用的问题。

可达性分析算法
Java 虚拟机是通过可达性分析算法来判断对象是否存活的,该算法的主要思想是将一系列称为 GC Root 的对象作为起点,向下进行搜索,搜索经过的路径称为引用链(Reference chain),当一个对象到 GC Root 对象没有任何引用链的时候,则表示该对象是不可达的,可以对其进行内存回收。

在 Java 虚拟机中,规定以下几种情况可以作为 GC Root 对象:

虚拟机栈中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中 Native 方法引用的对象
怎么进行内存回收
当我们创建的对象不可达之后,Java 虚拟机会在后台自动去收集回收不可达对象的内存,自 Java 语言诞生以来,在垃圾收集算法上进行了许多更新,主要有标记-清除算法(Mark and sweep algorithm)、复制算法(Copying algorithm)、标记—整理算法(Mark and compact algorithm)和分代收集算法(Generational collection algorithm),根据这些算法实现的垃圾收集器在后台默默运行以释放内存,下面让我们看看它们是如何工作的。

标记-清除算法(mark and sweep algorithm)
标记—清除算法是初始且非常基本的算法,主要分为以下两个阶段:

标记需要回收对象,找出程序中所有需要回收的对象并标记。
清除所有标记对象,在标记完成后统一回收被标记对象。
首先标记出需要回收的对象,标记完成后再统一回收被标记对象。这个算法是最基础的垃圾收集算法,后面将要介绍的几个算法都是在它的基础上优化改进的,算法主要有两个不足的地方:① 效率不高,标记和清除过程的效率都不高。② 空间利用率不高,标记清除之后会产生大量不连续的内存碎片,后面如果要分配大对象的时候由于连续内存不足可能会再次触发垃圾收集操作。

复制算法(copying algorithm)
复制算法就是为了解决标记—清除算法的效率问题的,主要思想就是将可用的内存分为大小相等的两个部分,每一次都只使用其中的一块,当这块内存使用完了之后,就将依然存活的对象复制到另一块内存上去,然后再把这块含有可回收对象的内存清理掉,这样每次都是清理一半的连续内存了,就不会存在内存碎片的情况。但是这个算法的缺点也很明显,它把可用内存的大小缩小到了一半。

标记-整理算法(mark and compact algorithm)
如果对象的存活率比较低的情况下,上面介绍的复制算法效率还是很高的,毕竟只要复制少部分存活对象到另一块内存中即可,但是当对象的存活率比较高时就会进行多次复制操作。比如老年代,老年代的对象是经过多次垃圾回收依然存活的对象,对象的存活率相对来说比较高,根据老年代的这个特点,于是针对这种情况就有了另一个算法称之为标记-整理算法,主要思想和其名字一样也是分为标记和整理两个阶段,第一个标记阶段依然和标记—清除算法一样,后面的第二个整理阶段就不是直接对可回收对象进行清理了,而是让所有存活的对象都向内存的同一侧移动,然后就直接清除掉另一侧的内存。

分代收集算法(generational collection algorithm)
根据不同分代的特点,现在商业上的虚拟机针对不同的分代采取适合的垃圾收集,一般是把 Java 堆分为新生代和老年代。在新生代中,对象大部分存活时间都很短每次垃圾收集都会有很多的对象被清除,只有少部分对象可以存活下来,那么此时就可以使用复制算法,只需要复制出少部分存活的对象即可效率高。然而在老年代中大部分对象的存活时间比较长,则需采用标记-清除算法或者标记-整理算法来进行垃圾收集。
垃圾收集算法对于垃圾回收来说类似于我们程序中的接口,是一套垃圾回收的指导算法,算法的具体实现我们称之为垃圾收集器。但是 Java 虚拟机规范中并没有对垃圾收集器的实现有任何规定。所以不同的厂商和不同版本的虚拟机实现的垃圾收集器也不一样,不过一般都会提供一些配置参数来让用户根据自身情况来设置所需的垃圾收集器。

JVM 相关 GC 配置
Java 虚拟机部分垃圾收集(Garbage Collection,GC)相关配置如下

参数 描述
-Xms2048m 设置初始堆大小(新生代 + 老年代)
-XX:InitialHeapSize=3g 设置初始堆大小(新生代 + 老年代)
-Xmx3g 设置最大堆大小(新生代 + 老年代)
-XX:MaxHeapSize=3g 设置最大堆大小(新生代 + 老年代)
-XX:NewSize=128m 设置堆初始新生代大小
-XX:MaxNewSize=128m 设置堆最大新生代大小
-XX:PermSize=512m(JDK 1.7) 设置初始永久代(元空间)大小
-XX:MetaspaceSize=512m(JDK 1.8+) 设置初始永久代(元空间)大小
-XX:MaxPermSize=1g(JDK 1.7) 设置最大永久代(元空间)大小
-XX:MaxMetaspaceSize=1g(JDK 1.8+) 设置最大永久代(元空间)大小
-XX:+DisableExplicitGC 忽略应用程序对 System.gc() 方法的任何调用
-XX:+PrintGCDetails 打印输出 GC 收集相关信息
参考文章

深入理解Java虚拟机(第2版)
Garbage collection (computer science)
The Java® Virtual Machine Specification(Java SE 8 Edition)
原文地址https://www.cnblogs.com/mghio/p/12539566.html

相关文章
|
监控 Cloud Native Java
Quarkus 云原生Java框架技术详解与实践指南
本文档全面介绍 Quarkus 框架的核心概念、架构特性和实践应用。作为新一代的云原生 Java 框架,Quarkus 旨在为 OpenJDK HotSpot 和 GraalVM 量身定制,显著提升 Java 在容器化环境中的运行效率。本文将深入探讨其响应式编程模型、原生编译能力、扩展机制以及与微服务架构的深度集成,帮助开发者构建高效、轻量的云原生应用。
1162 44
|
安全 Java API
Java Web 在线商城项目最新技术实操指南帮助开发者高效完成商城项目开发
本项目基于Spring Boot 3.2与Vue 3构建现代化在线商城,涵盖技术选型、核心功能实现、安全控制与容器化部署,助开发者掌握最新Java Web全栈开发实践。
948 1
|
安全 Java 编译器
new出来的对象,不一定在堆上?聊聊Java虚拟机的优化技术:逃逸分析
逃逸分析是一种静态程序分析技术,用于判断对象的可见性与生命周期。它帮助即时编译器优化内存使用、降低同步开销。根据对象是否逃逸出方法或线程,分析结果分为未逃逸、方法逃逸和线程逃逸三种。基于分析结果,编译器可进行同步锁消除、标量替换和栈上分配等优化,从而提升程序性能。尽管逃逸分析计算复杂度较高,但其在热点代码中的应用为Java虚拟机带来了显著的优化效果。
438 4
|
Java API Maven
2025 Java 零基础到实战最新技术实操全攻略与学习指南
本教程涵盖Java从零基础到实战的全流程,基于2025年最新技术栈,包括JDK 21、IntelliJ IDEA 2025.1、Spring Boot 3.x、Maven 4及Docker容器化部署,帮助开发者快速掌握现代Java开发技能。
1947 1
|
人工智能 Java
Java多任务编排技术
JDK 5引入Future接口实现异步任务处理,但获取结果不够灵活。Java 8新增CompletableFuture,实现异步任务编排,支持流式处理、多任务组合及异常处理,提升执行效率与代码可读性,简化并发编程复杂度。
336 0
|
Java 测试技术 API
2025 年 Java 开发者必知的最新技术实操指南全览
本指南涵盖Java 21+核心实操,详解虚拟线程、Spring Boot 3.3+GraalVM、Jakarta EE 10+MicroProfile 6微服务开发,并提供现代Java开发最佳实践,助力开发者高效构建高性能应用。
1611 5
|
JavaScript 安全 前端开发
Java开发:最新技术驱动的病人挂号系统实操指南与全流程操作技巧汇总
本文介绍基于Spring Boot 3.x、Vue 3等最新技术构建现代化病人挂号系统,涵盖技术选型、核心功能实现与部署方案,助力开发者快速搭建高效、安全的医疗挂号平台。
558 3
|
存储 Java Linux
操作系统层面视角下 Java IO 的演进路径及核心技术变革解析
本文从操作系统层面深入解析Java IO的演进历程,涵盖BIO、NIO、多路复用器及Netty等核心技术。分析各阶段IO模型的原理、优缺点及系统调用机制,探讨Java如何通过底层优化提升并发性能与数据处理效率,全面呈现IO技术的变革路径与发展趋势。
322 3
|
安全 Cloud Native Java
Java 模块化系统(JPMS)技术详解与实践指南
本文档全面介绍 Java 平台模块系统(JPMS)的核心概念、架构设计和实践应用。作为 Java 9 引入的最重要特性之一,JPMS 为 Java 应用程序提供了强大的模块化支持,解决了长期存在的 JAR 地狱问题,并改善了应用的安全性和可维护性。本文将深入探讨模块声明、模块路径、访问控制、服务绑定等核心机制,帮助开发者构建更加健壮和可维护的 Java 应用。
1165 0
|
安全 Java 微服务
Java 最新技术和框架实操:涵盖 JDK 21 新特性与 Spring Security 6.x 安全框架搭建
本文系统整理了Java最新技术与主流框架实操内容,涵盖Java 17+新特性(如模式匹配、文本块、记录类)、Spring Boot 3微服务开发、响应式编程(WebFlux)、容器化部署(Docker+K8s)、测试与CI/CD实践,附完整代码示例和学习资源推荐,助你构建现代Java全栈开发能力。
1175 1