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

news2024/11/16 10:16:57

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.coloradmin.cn/o/1541233.html

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

相关文章

Java基础【上】韩顺平(反射、类加载、final接口、抽象类、内部类)

涵盖知识点&#xff1a;反射、类加载、单例模式、final、抽象类、接口、内部类&#xff08;局部内部类、匿名内部类、成员内部类、静态内部类&#xff09; P711 反射机制原理 创建如下目录结构&#xff0c;在模块下创建src文件夹&#xff0c;文件夹要设置为Sources文件夹&…

Git使用:实现文件在不同设备之间进行同步

一、注册Gitee&#xff0c;创建远程仓库 注册网址&#xff1a;登录 - Gitee.com 打开Gitee&#xff0c;注册完进行登录&#xff0c;点击右上角【】创建一个仓库 新建仓库&#xff1a; 点击创建&#xff0c;仓库创建完毕。 二、下载Git安装包&#xff0c;并创建本地仓库 下载网…

正则表达式具体用法大全~持续更新

# 正则表达式&#xff1a; ## 单字符匹配&#xff1a; python # 匹配某个字符串&#xff1a; # text "abc" # ret re.match(b,text) # print(ret.group()) # 点&#xff08;.&#xff09;&#xff1a;匹配任意的字符(除了\n)&#xff1a; # text "\nabc&quo…

day02_mysql-DDLDMLDQL_课后练习 - 参考答案

文章目录 day02_mysql_课后练习第1题第2题 day02_mysql_课后练习 第1题 案例&#xff1a; 1、创建数据库test02_library 2、创建表格books 字段名字段说明数据类型b_id书编号int(11)b_name书名varchar&#xff08;50&#xff09;authors作者varchar(100)price价格floatpub…

【C语言】——指针四:字符指针与函数指针变量

【C语言】——指针四&#xff1a;字符指针与函数指针变量 一、字符指针二、函数指针变量2.1、 函数指针变量的创建2.2、两段有趣的代码 三、typedef关键字3.1、typedef的使用3.2、typedef与define比较 四、函数指针数组 一、字符指针 在前面的学习中&#xff0c;我们知道有一种…

FaceBook广告账号验证教程

1.登录facebook账号,点击左边的ads manager。 2.点击Create ad创建广告。 3.选择广告投放意向。 4.填写广告信息。 5.创建广告后选择付款方式&#xff0c;这里我是使用信用卡付款。这里我是使用Fomepay的虚拟卡进行绑定的。 6.填写信用卡的持卡人姓名 卡号 有效期 安全码 7.填写…

【Jenkins】群晖 配置 ssh over push 插件

群晖 配置 ssh over push 插件 前提 部署好 Jenkins 且 安装 好 ssh over push 插件 开启 群晖 ssh 服务 及 SFTP 服务 配置 Jenkins Jenkins ——系统管理——publish over ssh 测试下&#xff1a; 遇到的问题&#xff1a; jenkins.plugins.publish_over.BapPublishe…

数据容器-序列-集合-Python

师从黑马程序员 序列 序列的常用操作-切片 切片&#xff1a;从一个序列中&#xff0c;取出一个子序列 语法&#xff1a;序列[起始下标:结束下标&#xff0c;步长] 注&#xff1a;此操作不会影响序列本身&#xff0c;而是会得到一个新的序列 my_list[0.1,2,3,4,5,6] result1…

LeetCode-热题100:79. 单词搜索

题目描述 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c;其中“相邻”单元格是那些水平相…

解决长尾问题,BEV-CLIP:自动驾驶中复杂场景的多模态BEV检索方法

解决长尾问题&#xff0c;BEV-CLIP&#xff1a;自动驾驶中复杂场景的多模态BEV检索方法 理想汽车的工作&#xff0c;原文&#xff0c;BEV-CLIP: Multi-modal BEV Retrieval Methodology for Complex Scene in Autonomous Driving 链接&#xff1a;https://arxiv.org/pdf/2401.…

C++函数返回机制,返回类型

return语句终止当前正在执行的函数并将控制权返回到调用该函数的地方。 return语句有两种形式 return;return expression; 无返回值函数 没有返回值的return语句只能用在返回类型是void的函数中。 返回void的函数不要求必须有return语句&#xff0c;因为这类函数的最后一句…

手撕算法-接雨水

描述 分析 i位置能积累的雨水量&#xff0c;等于其左右两边最大高度的最小值。为了能获取i位置左右两边的最大高度。使用动态规划。两个dp数组&#xff1a; leftMaxrightMax 其中 leftMax[i] 代表i位置左边的最大高度rightMax[i] 代表i位置右边的最大高度 初始状态&#x…

新手装修:卫生间渗水原因及解决方法。福州中宅装饰,福州装修

引言 瓷砖渗水问题常常发生在卫生间区域&#xff0c;需要及时处理以免造成地面滑倒和墙面霉菌等问题&#xff0c;为了解决这一问题&#xff0c;我们应该怎么做呢&#xff1f; 首先要检查水管是否漏水&#xff0c;可以进行打压测试来确认是否存在漏水情况。如果发现水管破损造成…

php 快速入门(一)

一、配置系统环境 1.1 安装软件 1、安装php的开发软件&#xff1a;phpstorm 在这个软件中写代码 2、安装php的运行软件&#xff1a;phpstduy 写好的php程序需要放到phpstduy中&#xff0c;用户才能访问和测试 安装过程注意事项&#xff1a;安装的路径中不能有空格和中文字符&…

什么是 PDAF?它是如何工作的?相位检测自动对焦解释

常见问题解答 什么是相位对焦 PDAF 代表相位检测自动对焦。这是一种自动对焦方法,可以检测光线进入相机时的行进和交汇位置。在智能手机中,这是在传感器级别完成的。为了使物体聚焦,光线应该在同一点相遇。如果不这样做,系统将确定如何调整镜头以达到焦点。 PDAF 好用吗…

HTTP --- 下

目录 1. HTTP请求方式 1.1. HTML 表单 1.2. GET && POST方法 1.2.1. 用 GET 方法提交表单数据 1.2.2. 用 POST 方法提交表单数据 1.2.3. 总结 1.3. 其他方法 2. HTTP的状态码 2.1. 重定向 2.1.1. 临时重定向 && 永久重定向 2.1.2. 302 &&…

UE5 C++ 3D血条 响应人物受伤 案例

一.3Dwidget 1.创建C Userwidget的 MyHealthWidget&#xff0c;声明当前血量和最大血量 UCLASS() class PRACTICEC_API UMyHealthWidget : public UUserWidget {GENERATED_BODY() public:UPROPERTY(EditAnywhere,BlueprintReadWrite,Category "MyWidget")float C…

基于Springboot+Vue的在线考试系统!免费领取源码

今天给大家分享一套基于SpringbootVue的在线考试系统源码&#xff0c;在实际项目中可以直接复用。(免费提供&#xff0c;文末自取) 一、系统运行图 1、登陆页面 2、后台管理 3、全套环境资源 二、源码免费领取方式 关注本号&#xff0c;回复 考试 关注本号&#xff0c;回复…

【数据结构】快速排序(用递归)

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解快速排序&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 一. 基本思想二. 快速排序2.1 hoare版本2.2 挖坑法2.3 前后指针法2.4 快速排序优化三数取中法…

数据结构:堆和二叉树遍历

堆的特征 1.堆是一个完全二叉树 2.堆分为大堆和小堆。大堆&#xff1a;左右节点都小于根节点 小堆&#xff1a;左右节点都大于根节点 堆的应用&#xff1a;堆排序&#xff0c;topk问题 堆排序 堆排序的思路&#xff1a; 1.升序排序&#xff0c;建小堆。堆顶就是这个堆最小…