Java进阶—GC回收(垃圾回收)

1. 什么是垃圾回收

垃圾回收(Garbage Collection,GC)是Java虚拟机(JVM)的一项重要功能,用于自动管理程序中不再使用的内存。在Java中,程序员不需要手动释放内存,因为GC会自动检测并回收不再使用的对象,从而减少内存泄漏的风险。

2. 垃圾回收的空间

Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时,Java 自动内存管理最核心的功能是 堆内存中对象的分配与回收。
Java 堆是垃圾收集器管理的主要区域,因此也被称作 GC 堆(Garbage Collected Heap)。
从垃圾回收的角度来说,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆被划分为了几个不同的区域,这样我们就可以根据各个区域的特点选择合适的垃圾收集算法。
记住jdk8中的垃圾回收区域就好
在这里插入图片描述

3. 垃圾回收器的分类

3.1按实现方式分类

  1. 串行垃圾回收器(Serial GC):使用单个线程进行垃圾回收,适用于单核CPU环境。可以通过 -XX:+UseSerialGC 参数启用。
  2. 并行垃圾回收器(Paraller GC):使用多个线程同时进行垃圾回收,提高回收效率,适用于多核CPU环境。可以通过 -XX:+UseParallelGC 参数启用。
  3. 并发垃圾回收器(Concurrent GC):在应用程序运行的同事进行垃圾回收,减少停顿时间,提高响应性能。可以通过 -XX:+UseConcMarkSweepGC 参数启用。
  4. G1垃圾回收器(Garbage-First Garbage Collector):一种面向服务端应用的垃圾回收器,具有高吞吐量和地停顿时间的特点,适用于大内存应用。可以通过 -XX:+UseG1GC 参数启用。
  5. ZGC:一种低延迟的垃圾回收器,适用于需要更短停顿时间的应用场景。可以通过 -XX:+UseZGC 参数启用。

Java虚拟机的默认垃圾回收器随着Java版本和虚拟机的不同而有所变化。一般来说,在较早的Java版本中(如Java 8及之前的版本),默认的垃圾回收器是串行垃圾回收器(Serial GC),适用于单核CPU环境。

从Java 9开始,默认的垃圾回收器是G1垃圾回收器(Garbage-First Garbage Collector)。G1垃圾回收器是一种面向服务端应用的垃圾回收器,具有高吞吐量和低停顿时间的特点,适用于大内存应用。

3.2 按作用范围分类

  1. 新生代垃圾收集(Minor GC/Young GC):只对新生代进行垃圾回收。新生代一般使用复杂算法进行收集,将存货的对象复制到另一个区域,并清理掉原区域中的无用对象。因为新生代的对象生命周期较短,所以新生代的垃圾回收频率比较高。
  2. 老年代垃圾收集Major GC/Old GC):只对老年代进行垃圾收集。老年代一般使用标记-清除(Mark and Sweep)或者标记-整理(Mark and Compact)算法进行收集,清理掉不再使用的对象。老年代的垃圾回收相对较少,但由于老年代中存放着长期存活的对象,因此垃圾收集的效率和停顿时间会影响到整个应用的性能。
  3. 混合收集(Mixed GC):对整个新生代和部分老年代收集。混合收集通常是为了提高垃圾收集的效率,将部分老年代中的对象以一并清理掉,减少老年代的内存占用。
  4. 整堆收集(Full GC):收集整个Java堆和方法区。在有些语境中,Major GC也可以用来指代整堆收集。整堆收集通常发生在老年代空间不足或元空间(在Java 8及之后的版本中)空间不足时,或者在执行显式的System.gc()方法时。

这些不同的垃圾收集类型在不同的情况下会被JVM自动触发,以维护Java应用程序的内存使用和性能。

- 下面是触发不同类型垃圾回收的一些情况:

  1. 新生代收集(Minor GC / Young GC)
    • 当新生代中的Eden区满时,触发Minor GC。在Minor GC中,会将存活的对象复制到Survivor区,并清理掉Eden区和其中的无用对象。
    • 当Survivor区无法容纳所有存活的对象时,存活较长时间的对象会被移动到老年代,而不是进行Minor GC。
  2. 老年代收集(Major GC / Old GC)
    • 当老年代的空间不足以存放新分配的对象时,会触发Major GC。Major GC会清理老年代中的无用对象,以释放空间给新的对象使用。
    • 在并发标记-清除算法中,当老年代的内存使用达到一定阈值时,会触发并发标记,然后在空闲时进行垃圾回收。
  3. 混合收集(Mixed GC)
    • 混合收集通常在老年代垃圾回收的同时,对新生代也进行部分回收。这种方式可以更均衡地处理新生代和老年代的内存回收,提高整体性能。
  4. 整堆收集(Full GC)
    • 在老年代空间不足时,或者在永久代(在Java 8之前的版本中)或元空间(在Java 8及之后的版本中)空间不足时,会触发整堆收集。
    • 显式调用System.gc()方法时,也可能触发整堆收集,但并不保证一定会执行。

4. 堆中对象的生命周期

  1. 加载阶段:当程序使用new关键字创建一个对象时,该对象会被加载堆内存中。它通常会被分配到新生代的Eden区,有一种特殊情况,大对象会直接分配到老年区。

大对象(LargeObject)通常会直接分配到老年代。在Java中,大对象是指占用内存较大的对象,例如大数组或大集合。由于大对象占用的内存较大,将其分配到新生代可能会导致频繁的内存复制和回收,影响程序的性能。
G1 垃圾回收器会根据 -XX:G1HeapRegionSize 参数设置的堆区域大小和-XX:G1MixedGCLiveThresholdPercent 参数设置的阈值,来决定哪些对象会直接进入老年代。
Parallel Scavenge垃圾回收器中,默认情况下,并没有一个固定的阈值(XX:ThresholdTolerance是动态调整的)来决定何时直接在老年代分配大对象。而是由虚拟机根据当前的堆内存情况和历史数据动态决定。

  1. 存活阶段:在对象被加载到堆内存后,如果该对象仍然被引用着,则认为该对象处于存活状态。如果对象不再被任何引用引用,则认为该对象是垃圾,将在下一次垃圾回收时被回收。
    如果对象在新生代经过一次Minor GC后仍然存活,它将被移动到新生代的Survivor区。在Survivor区中经过多次存活后,对象可能被晋升到老年代。
    • 怎么判断对象进入老年代
      • 年龄判断:在新生代中,每个对象被创建时都会被赋予一个年龄计数器。经过一次Minor GC,如果对象仍然存活,它的年龄就会增加。当对象的年龄达到一定阈值时(通常是15),就会晋升到老年代。
      • Survivor空间不足:Survivor空间用来存放新生代中的存活对象。如果Survivor空间不足以容纳对象,那么这些存活对象会被直接晋升到老年代。
      • 空间分配担保:在进行Minor GC时,如果新生代中的对象无法全部晋升到老年代,但是老年代的剩余空间不足以存放新生代中的所有存活对象时,JVM会进行一次Full GC,确保能够为新生代中的对象分配足够的空间。

3.垃圾回收阶段:当对象不再被引用时,JVM的垃圾回收器会识别并回收这些对象所占用的内存。垃圾回收的具体时间取决于垃圾回收器的策略和堆的使用情况。

  • 怎么判断对象可以被回收
    • 引用计数器:引用计数器是一种简单的方法,它通过在对象上维护一个引用计数器来记录对象被引用的次数。当引用计数器为0时,表示对象不再被引用,可以被回收。然而,引用计数器无法处理循环引用的情况,因此在Java中并没有使用这种方法。

    • 可达性分析:Java使用可达性分析来判断对象是否不再被引用。可达性分析是从一组称为"GC Roots"的根对象开始,递归地遍历所有对象的引用关系,标记所有被引用的对象为存活对象,未被标记的对象则被认为是垃圾。这样,不再被引用的对象最终会被垃圾收集器回收。

    • 在Java中,GC Roots就是根集合,包括:

      • 虚拟机栈(Java Stack)中的引用对象,即局部变量和参数。
      • 方法区(Method Area)中的类静态属性引用的对象。
      • 方法区中常量引用的对象。
      • 本地方法栈(Native Method Stack)中JNI(Java Native Interface)引用的对象。
    • 如何判断一个常量是废弃常量?
      假如在字符串常量池中存在字符串 “abc”,如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 “abc” 就是废弃常量,如果这时发生内存回收的话而且有必要的话,“abc” 就会被系统清理出常量池了。

    • 如何判断一个类是无用的类?
      正常很难满足这三个条件的。

      • 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
      • 加载该类的 ClassLoader 已经被回收。
      • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
  1. 终结阶段:在对象被回收之前,JVM会调用对象的finalize()方法进行清理和释放资源。但是,finalize()方法并不保证一定会被调用,因此不应该依赖于finalize()方法来释放资源。finalize()的调用时机是不确定的,不能保证一定会被调用
  2. 内存回收阶段:在垃圾回收阶段,如果对象经过finalize()方法后仍然未被引用,则会被回收,并释放其占用的内存空间。对于新生代的对象,会经过Minor GC;对于老年代的对象,会经过Major GC。

5. 对象的引用对垃圾回收的影响

  • 强引用、软引用、弱引用、虚引用
    在Java中,引用(Reference)是用来描述对象之间的关系的。Java提供了几种不同类型的引用,包括强引用、软引用、弱引用和虚引用,它们主要用于控制对象的可达性,从而影响垃圾回收的行为。
    1. 强引用(Strong Reference):强引用是最常见的引用类型。如果一个对象具有强引用,即使内存空间不足,垃圾收集器也不会回收这个对象。例如:

      Object obj = new Object(); // obj是一个强引用
    2. 软引用(Soft Reference):软引用用于描述那些还有用但并非必须的对象。在系统内存不足时,垃圾收集器会根据软引用的情况来决定是否回收对象。软引用可以通过java.lang.ref.SoftReference类来创建:

      SoftReference<Object> softRef = new SoftReference<>(new Object());

      例如,缓存中的对象可以使用软引用,当内存不足时,垃圾回收器可以根据软引用情况来回收缓存中的对象,从而释放内存。

    3. 弱引用(Weak Reference):弱引用比软引用更弱,只要垃圾回收器运行,无论内存是否足够,都会回收只被弱引用指向的对象。弱引用可以通过java.lang.ref.WeakReference类来创建:

      WeakReference<Object> weakRef = new WeakReference<>(new Object());

      弱引用通常用于实现一些缓存中的临时对象,可以随时被回收而不会占用太多内存。

    4. 虚引用(Phantom Reference):虚引用是所有引用中最弱的一种。一个持有虚引用的对象,和没有引用几乎是一样的,随时可能被垃圾回收器回收。虚引用主要用于在对象被回收时收到一个系统通知或执行一些清理操作。虚引用可以通过java.lang.ref.PhantomReference类来创建:

      ReferenceQueue<Object> queue = new ReferenceQueue<>();
      PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue);

      虚引用本身并不能阻止对象被回收,它的主要作用是在对象被回收时执行一些特定的操作,例如清理对象关联的资源或发送通知。
      以下是一个简单的示例,演示了如何使用虚引用和引用队列:

      import java.lang.ref.PhantomReference;
      import java.lang.ref.Reference;
      import java.lang.ref.ReferenceQueue;public class PhantomReferenceExample {public static void main(String[] args) {Object obj = new Object();ReferenceQueue<Object> queue = new ReferenceQueue<>();PhantomReference<Object> phantomRef = new PhantomReference<>(obj, queue);// 显示判断对象是否被回收System.out.println("Is object still alive? " + (phantomRef.get() != null));// 释放对象引用obj = null;// 强制垃圾回收System.gc();// 检查引用队列是否有引用对象Reference<?> refFromQueue = queue.poll();if (refFromQueue != null) {System.out.println("Object is in the queue.");} else {System.out.println("Object is not in the queue.");}}
      }

      在这个例子中,当对象被垃圾回收器回收时,phantomRef将会被添加到queue引用队列中,我们可以通过检查引用队列中是否有引用来判断对象是否被回收。

这些引用类型可以帮助开发人员更灵活地控制对象的生命周期和内存回收行为,特别是在处理大量数据或需要特定内存管理策略的情况下。

6. 垃圾回收算法

  1. 标记-清除(Mark and Sweep)算法
    标记-清除算法(Mark and Sweep Algorithm)是一种基本的垃圾回收算法,用于标记和清除不再被引用的对象。该算法分为两个阶段:标记阶段和清除阶段。

    • 标记阶段
      • 从根对象(如虚拟机栈中的引用对象、静态变量等)开始,遍历所有可以访问到的对象,并标记这些对象为活动对象。
      • 对于无法访问到的对象,即不可达对象,将其标记为待清除对象。
    • 清除阶段
      • 遍历堆内存中的所有对象,将标记为待清除的对象进行清除操作,即回收其所占用的内存。

    标记-清除算法的优点是简单高效,但也存在一些缺点:

    • 内存碎片问题:由于标记-清除算法会在清除阶段产生不连续的内存碎片,可能导致无法找到足够大的连续内存块来分配新对象。
    • 效率问题:标记和清除过程可能会耗费较长的时间,且在清除阶段会暂停应用程序的运行。
  2. 复制算法(Copying Algorithm)

    复制算法(Copying Algorithm)是一种垃圾回收算法,通常用于新生代的内存管理。它的核心思想是将内存空间分为两个大小相等的区域,通常称为"From"区和"To"区。在垃圾回收过程中,所有存活的对象都会被复制到"To"区,而非存活的对象则会被丢弃。
    复制算法的具体步骤如下:

    1. 初始分配:将新生代内存空间分为两个大小相等的区域,通常称为"From"区和"To"区。
    2. 新创建的对象首先会被分配到"From"区。
    3. 标记存活对象:从根对象开始,通过可达性分析标记所有存活的对象。
    4. 复制存活对象:将所有存活的对象复制到"To"区,同时按照对象的地址顺序依次排列,确保对象之间的地址是连续的。
    5. 更新引用:更新所有指向存活对象的引用,使其指向"To"区中的地址。
    6. 交换空间:将"From"区和"To"区的角色互换,使得下一次垃圾回收时仍然使用这两个区域,而不需要重新分配内存空间。

    复制算法的优点是简单高效,并且可以解决标记-清除算法中的内存碎片问题。但是,它也有一些缺点,主要是需要额外的内存空间来存储复制后的对象,以及在复制过程中需要暂停应用程序的运行。为了减少这些缺点,通常会将新生代划分为更多的存活区域,并使用"对象年龄"来决定何时将对象晋升到老年代。
    总结一下:
    可用内存变小:可用内存缩小为原来的一半。
    不适合老年代:如果存活对象数量比较大,复制性能会变得很差。

  3. 标记-整理算法(Mark-Sweep-Compact Algorithm)
    标记-整理算法(Mark-Sweep-Compact Algorithm)是一种垃圾回收算法,通常用于老年代的内存管理。它结合了标记-清除算法和内存整理的思想,用于解决标记-清除算法可能产生的内存碎片问题。
    标记-整理算法的主要步骤包括:

    1. 标记阶段:从根对象开始,通过可达性分析标记所有存活的对象。
    2. 清除阶段:遍历整个堆内存,将未被标记的对象清除,即回收其占用的内存空间。
    3. 整理阶段:将存活的对象向一端移动,使得所有存活对象在内存中连续排列,从而将内存空间合并为一个大的连续空间。

    标记-整理算法的优点是可以避免内存碎片问题,使得堆内存中的空闲空间更加连续,有利于提高内存分配的效率。但是,与复制算法相比,标记-整理算法需要更多的计算和移动操作,可能会影响应用程序的性能。因此,通常只在老年代等内存碎片严重的情况下使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/767464.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

力扣|两数相加|链表

给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&#xff0c;这两个数都不会以 0 …

面试算法-98-随机链表的复制

题目 给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成&#xff0c;其中每个新节点的值都设为其对应的原节点的值。新节…

Git的原理和使用(四)

目录 远程操作 理解分布式版本控制系统 远程仓库 新建远程仓库 克隆远程仓库 向远程仓库推送 拉取远程仓库 配置Git 忽略特殊文件 为命令配置别名 标签管理 理解标签 创建标签 操作标签 远程操作 理解分布式版本控制系统 1、每个人的电脑上都是一个完整的版本库…

网络行为管理系统招标模板

项目名称&#xff1a;网络行为管理系统招标 一、项目背景 随着信息技术的迅猛发展&#xff0c;网络安全和数据保护已成为企业和组织面临的关键挑战。为了确保网络环境的安全、合规&#xff0c;并实现对网络行为的有效管理和审计&#xff0c;我们特此启动网络行为管理系统的招…

AI程序员革命:探析Devin的登场与编程未来

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

基于python+vue超市货品信息管理系统flask-django-php-nodejs

在此基础上&#xff0c;结合现有超市货品信息管理体系的特点&#xff0c;运用新技术&#xff0c;构建了以 python为基础的超市货品信息管理信息化管理体系。首先&#xff0c;以需求为依据&#xff0c;根据需求分析结果进行了系统的设计&#xff0c;并将其划分为管理员和用户二种…

每日一练:LeeCode-498、对角线遍历【二维数组+边界判断】

给你一个大小为 m x n 的矩阵 mat &#xff0c;请以对角线遍历的顺序&#xff0c;用一个数组返回这个矩阵中的所有元素。 示例 1&#xff1a; 输入&#xff1a;mat [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2,4,7,5,3,6,8,9] 示例 2&#xff1a; 输入&#xff1a;ma…

C语言分支和循环

目录 一.分支 一.if 二.if else 三.if else嵌套 四.else if 五.switch语句 二.循环 一.while (do while&#xff09;break : 二.for函数&#xff1a; 三.goto语句: 四.猜数字: 一.分支 一.if if要条件为真才执行为假不执行而且if只能执行后面第一条如果要执行多条就…

Ubuntu共享文件夹创建及访问 Windows 最简单的方法!

第一步&#xff1a;在Windows下随便建一个文件夹&#xff0c;这里我是在D盘建了一个文件夹叫share 第二步&#xff1a;安装VMware tools&#xff0c;这里就不细说了 第三步&#xff1a;vmware的上方选择 虚拟机-->设置 第四步&#xff1a; 在虚拟机设置里面选择 选项-…

EFcore的实体类配置

1 约定配置 约定大于配置&#xff0c;框架默认了许多实体类配置的规则&#xff0c;在约定规则不满足要求时&#xff0c;可以显示地定义规则 1 数据库表明在不指定的情况下&#xff0c;默认使用的是数据库上下文类【DBContext】中DbSet 的属性名&#xff1b; 2 数据库表列的名字…

19、【qlib】【其他组件/特性/主题】任务管理

简介 工作流部分介绍了如何松耦合地运行研究流程,但使用qrun时只能执行单个任务。为了自动地生成和执行不同的任务,任务管理模块提供了一整套流程,包括任务生成、任务存储、任务训练及任务收集。借助这个模块,用户可以在不同时间段、不同损失函数或甚至不同模型下自动运行…

摘录笔记——2024年3月22日

目录 一、背景 1.1 新人的选择困局 1.2 高人才密度环境下普通员工的成长效率困局 1.3 业务发展和个人成长的二元对立困局 1.4 中年打工人低费效比引发的职场生涯终结困局 二、人的本质 2.1 人的本质的定义 2.2 由“人的本质”引出的几个关键过程 2.2.1 认知指引实践&a…

IPV6协议之RIPNG

目录 前言&#xff1a; 一、RIPNG与RIP的区别 二、如何配置RIPNG 如何解决RIPNG环路问题呢&#xff1f; 控制RIPNG的选路 1、修改RIPNG默认优先级 2.配置接口附加开销值从而干涉RIPNG的选路 RIPNG拓展配置 1.RIPNG的认证 配置RIPNG进程下的IPsec认证&#xff1a; 配…

C++默认构造函数(二)

目录 构造函数补充 构造函数初始化列表的使用 赋值运算符重载函数 运算符重载函数介绍 运算符重载函数的使用 赋值运算符重载函数 赋值运算符重载函数的使用 拷贝构造函数和赋值运算符重载函数 重载前置和后置 前置 后置 重载流插入<<与流提取>> 流插…

Navicat 干货 | 探索 PostgreSQL 的外部数据包装器和统计函数

PostgreSQL 因其稳定性和可扩展性而广受青睐&#xff0c;为开发人员和数据管理员提供了许多有用的函数。在这些函数中&#xff0c;file_fdw_handler、file_fdw_validator、pg_stat_statements、pg_stat_statements_info 以及 pg_stat_statements_reset 是其中的重要函数&#x…

鸿蒙:@Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化

在实际应用开发中&#xff0c;应用会根据开发需要&#xff0c;封装自己的数据模型。对于多层嵌套的情况&#xff0c;比如二维数组&#xff0c;或者数组项class&#xff0c;或者class的属性是class&#xff0c;他们的第二层的属性变化是无法观察到的。这就引出了Observed/Object…

AJAX介绍使用案例

文章目录 一、AJAX概念二、AJAX快速入门1、编写AjaxServlet&#xff0c;并使用response输出字符&#xff08;后台代码&#xff09;2、创建XMLHttpRequest对象&#xff1a;用于和服务器交换数据 & 3、向服务器发送请求 & 4、获取服务器响应数据 三、案例-验证用户是否存…

由浅入深一步步了解什么是哈希(概念向)

文章目录 什么是哈希哈希函数直接定址法除留余数法 哈希冲突闭散列线性探测法二次探测法负载因子和闭散列的扩容 开散列开散列的扩容 非整形关键码 什么是哈希 我们来重新认识一下数据查找的过程&#xff1a; 在顺序结构以及平衡树中&#xff0c;记录的关键码与其存储位置之间…

【自然语言处理】统计中文分词技术(一):1、分词与频度统计

文章目录 一、词与分词1、词 vs 词素2、世界语言分类 二、分词的原因与基本原因1、为什么要分词2、分词规范3、分词的主要难点-切分歧义如何排除切分歧义利用词法信息利用句法信息利用语义信息利用语用、语境信息 4、分词的主要难点-未登录词未登录词如何识别未登录词 三、分词…

Docker入门到实践之环境配置

Docker入门到实践之环境配置 docker 环境安装 Ubuntu/Debian: sudo apt update sudo apt install docker.ioCentOS/RHEL: sudo yum install dockerArch Linux: sudo pacman -S docker如果未安装成功&#xff0c;或者env的path未设置成功&#xff0c;运行时会报错 Bash: Do…