JVM-GC垃圾回收机制

java中的引用类型

强引用

  • 最常见的引用类型

  • 只要强引用存在,垃圾回收器就不会回收该对象

  • 使用=操作符创建的就是强引用

  • 几乎所有对象引用都是强引用

    Object obj = new Object(); // obj是一个强引用

软引用

  • 用于描述一些还有用但是非必须的对象

  • 在内存不足的时候,垃圾回收器会回收软引用指向的对象

  • 适用于缓存场景

  • 场景:缓存大对象、如图片、音频等资源

    import java.lang.ref.SoftReference;

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

    import java.lang.ref.SoftReference;
    import java.util.HashMap;
    import java.util.Map;

    public class ImageCache {
    private Map<String, SoftReference<byte[]>> cache = new HashMap<>();

    复制代码
      public byte[] getImage(String key) {
          SoftReference<byte[]> ref = cache.get(key);
          if (ref != null) {
              byte[] image = ref.get(); // 获取实际对象
              if (image != null) {
                  return image; // 对象还在内存中
              }
          }
          // 对象已被GC回收,重新加载
          byte[] image = loadImageFromDisk(key);
          cache.put(key, new SoftReference<>(image));
          return image;
      }
      
      private byte[] loadImageFromDisk(String key) {
          // 模拟从磁盘加载
          return new byte[1024 * 1024]; // 1MB的图片数据
      }

    }

    弱引用

    • 强度比软引用更弱

    • 只要发生垃圾回收,就会回收弱引用指向的对象

    • 常用于避免内存泄漏,如ThreadLoacl

    • 用于避免内存泄漏,实现自动清理的映射表

    • ThreadLoacl、监听器注册、缓存键等

      import java.lang.ref.SoftReference;

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

      import java.lang.ref.WeakReference;
      import java.util.ArrayList;
      import java.util.List;

      public class ObserverManager {
      private List<WeakReference> observers = new ArrayList<>();

      复制代码
        public void addObserver(Observer observer) {
            observers.add(new WeakReference<>(observer));
        }
        
        public void notifyObservers(String message) {
            // 清理已回收的观察者
            observers.removeIf(ref -> ref.get() == null);
            
            for (WeakReference<Observer> ref : observers) {
                Observer obs = ref.get();
                if (obs != null) {
                    obs.update(message);
                }
            }
        }

      }

      interface Observer {
      void update(String message);
      }

      虚引用

      • 最弱的引用类型

      • 无法通过虚引用获取对象实例

      • 主要用于跟踪对象被垃圾回收的状态

      • 必须与引用队列(ReferenceQueue)一起使用

      • 跟踪对象呗垃圾回收的活动

      • 管理堆外内存、资源清理等

        import java.lang.ref.PhantomReference;
        import java.lang.ref.ReferenceQueue;

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

        import java.lang.ref.PhantomReference;
        import java.lang.ref.ReferenceQueue;

        public class ResourceCleaner {
        private static ReferenceQueue queue = new ReferenceQueue<>();

        复制代码
          static class TrackedResource {
              private byte[] buffer; // 占用大量内存的资源
              
              public TrackedResource() {
                  this.buffer = new byte[1024 * 1024]; // 1MB
              }
              
              @Override
              protected void finalize() throws Throwable {
                  System.out.println("Finalizing TrackedResource");
                  super.finalize();
              }
          }
          
          public static void main(String[] args) {
              TrackedResource resource = new TrackedResource();
              
              // 创建虚引用并关联到引用队列
              PhantomReference<TrackedResource> phantomRef = 
                  new PhantomReference<>(resource, queue);
              
              resource = null; // 移除强引用
              
              // 触发GC
              System.gc();
              
              // 等待对象被加入引用队列
              try {
                  Thread.sleep(1000);
                  if (queue.poll() != null) {
                      System.out.println("对象已被垃圾回收");
                      // 在这里可以执行额外的资源清理工作
                  }
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }

        }

        垃圾回收的基本原理

        引用计数算法

        • 每个对象都有一个引用计数器
        • 每当有一个地方引用该对象的时候,计数器值加1
        • 当引用失效的时候,技术其值减1
        • 计数器为0的对象被认为是可回收的
        • 缺点:无法解决循环引用的问题

        可达性分析算法(目前主流实现)

        • 从一系列成为"GC Roots"的对象作为起始点进行搜索
        • 走过的路径被称为引用链(Reference Chain)
        • 当一个对象没有被任何引用链连接的时候,则证明此对象不可达
        • 不可达的对象被认为可以回收
        • 以GC Roots为起点,向下搜索所有可达对象
        • 不可达的对象被认为是可回收的
        • 解决了循环引用的问题
        • GC Roots包括以下几种对象:
          • 虚拟机栈(栈帧中的局部变量表)中引用的对象
          • 方法区中类静态属性引用的对象
          • 方法区中常量引用的对象
          • 本地方法栈中JNI(即native方法)引用的对象
        • 标记引用链可达的,其他未标记的是可以删除的

        JVM内存结构与分代回收

        基于一下经验观察:

        • 绝大多数对象都是朝生夕死(弱分代假说)
        • 熬过越多次垃圾收集过程的对象就越难消亡(强分代假说)
        • 跨代引用相对于同代引用只占少数(跨代引用假说)

        因此将内存内存区域划分:

        • 新生代:采用复制算法,因为大部分对象很快就会死亡
          • Eden区:新创建的对象的初始存放位置
          • Survivor区:包含From和To两个大小相等的区域
          • 大部分对象在Eden区创建,经过Minor GC后存活的对象进入Survivor区
        • 老年代:采用标记-清除、标记-整理算法,因为对象存活率很高
          • 存放经过多次Minor GC依然存活的对象
          • 或者大对象直接进入老年代
          • 老年代满了会触发Major GC(Full GC)
        • 永久代/元空间
          • 存放类信息、常量、静态变量等
          • java8之后改为元空间,使用本地内存

        垃圾回收算法

        标记-清除算法

        • 阶段1:标记出所有需要回收的对象
        • 阶段2:同意回收被标记的对象
        • 缺点:效率低,产生碎片内存

        复制算法

        • 将内存分为两块、每次只使用其中一块
        • 将存活的对象复制到另一块上,然后清理当前块
        • 优点:效率高、无碎片;
        • 缺点:内存利用率只有百分之五十
        • 主要用于新生代

        标记-整理算法

        • 阶段1:标记所有需要回收的对象
        • 阶段2:将存活对象向另一端移动,然后清理边界外的内存
        • 用于老年代

        内存分配策略

        对象有限在Eden分配

        • 大多数情况下,对象在新生代Eden区中分配
        • 当Eden区没有足够空间进行分配的时候,发起一次Minor GC

        大对象直接进入老年代

        • 需要大量连续内存空间的对象成为大对象
        • 避免大对象在Eden和Surivor区之间复制

        长期存活的对象将进入老年代

        • 对象每经历一次Minor GC后仍然存活,年龄+1
        • 达到一定年龄阈值(默认15)的对象进入老年代

        动态对象年龄判定

        • 如果Survivor空间中相同年龄所有对象大小综合大于Survivor空间一半
        • 年龄大于或等于该年龄的对象可以直接进入老年代

        垃圾收集器类型

        新生代收集器

        • Serial收集器:单线程,适用于客户端模式
        • ParNew收集器:多线程版本的Serial
        • Parallel Scavenge收集器:关注吞吐量

        老年代收集器

        • Serial Old收集器:单线程,标记-整理算法
        • Parallel Old收集器:多线程,标记-整理算法
        • CMS收集器:关注停顿时间,标记-清楚算法
        • G1收集器面向服务端应用,可预测的停顿时间模型

        CMS收集器工作步骤

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

        2、并发标记:根据可达性分析标记对象

        3、重新标记:修正并发期间发生变化的对象标记,STW

        4、并发清楚:清理未标记的对象

        G1收集器特点

        • 将堆划分为多个Region
        • 追求可预测的停顿时间
        • 支持并发与并行
        • 支持分代收集

        常见的垃圾回收器以及配置

        垃圾回收器类型以及配置参数

        Serial收集器(串行收集器)

        • 适用场景:客户端模式下的简单应用
        • 配置参数:-XX:+UseSerialGC
        • 特点:单线程工作,使用复制算法

        ParNew收集器(并行收集器)

        • 适用场景:多核环境下的新生代收集
        • 配置参数::-XX:+UseParNewGC
        • 特点:多线程并行工作,使用复制算法

        Parallel Scavenge收集器(吞吐量优先收集器)

        • 适用场景:注重吞吐量的应用
        • 配置参数:-XX:+UseParallelGC 或 -XX:+UseParallelOldGC
        • 特点:关注吞吐量,新生代适用复制算法

        Serial Old收集器

        • 适用场景:客户端模式或老年代收集
        • 配置参数:-XX:+UseSerialOldGC
        • 特点:单线程,使用标记-整理算法

        Parallel Old收集

        • 适用场景:注重吞吐量的服务端应用
        • 配置参数:-XX:+UseParallelOldGC
        • 特点:多线程,使用标记-整理算法

        CMS收集器(Concurrent Mark Sweep)

        • 适用场景:注重响应时间的应用
        • 配置参数:-XX:+UseConcMarkSweepGC
        • 特点:低延迟,使用标记-清除算法

        G1收集器(Garbage First)

        • 适用场景:大堆内存、低延迟要求的应用
        • 配置参数:-XX:+UseG1GC
        • 特点:可预测的停顿时间,分Region管理内存

        JVM默认垃圾收集器选择

        • java8以及以前:服务器模式下默认使用Parallel GC
        • java9以后:默认适用G1收集器
        • 客户端模式:通常适用Seria收集器

        如何查看当前使用的垃圾收集器

        复制代码
        # 查看当前JVM使用的垃圾收集器
        java -XX:+PrintCommandLineFlags -version
        
        # 在运行中的Java进程查看
        jstat -gc <pid>
        
        # 使用jinfo查看
        jinfo -flag +PrintCommandLineFlags <pid>

        垃圾收集器的搭配关系

        • 新生代+老年代
          • Serial + Serial Old
          • ParNew + CMS
          • Parallel Scavenge + Parallel Old
          • G1(统一管理整个堆)

        实际配置示例

        复制代码
        # 使用G1收集器
        java -XX:+UseG1GC -Xmx4g -Xms4g MyApp
        
        # 使用CMS收集器
        java -XX:+UseConcMarkSweepGC -XX:+UseParNewGC MyApp
        
        # 使用并行收集器
        java -XX:+UseParallelGC -XX:ParallelGCThreads=4 MyApp

        选择垃圾收集器的考虑因素

        • 应用类型:是否注重响应时间或吞吐量
        • 堆内存大小:小堆(<4GB)、中堆(4-16GB)、大堆(>16GB)
        • CPU核心数:多核环境适合并行收集器
        • 停顿时间要求:是否允许长时间的STW
        • 总之,垃圾回收器是可以根据应用需求进行配置的,开发者可以根据应用特点选择最合适的垃圾回收器

        GC触发时机

        • Minor GC:当Eden区满时触发,回收新生代
        • Major GC/Full GC:
          • 老年代空间不足
          • 方法区空间不足
          • 通过Minor GC进入老年代的平均大小大于老年代的可用内存
          • System.gc()被显式调用

        常见的调优参数

        • -Xms:设置堆最小值
        • -Xmx:设置堆最大值
        • -XX:NewRatio:设置老年代与新生代的比例
        • -XX:SurvivorRatio:设置Eden区与Survivor区比例
        • -XX:+UseG1GC:使用G1收集器

        STW

        STW是指在执行垃圾回收时,JVM暂停所有正在执行的应用程序线程,等待垃圾回收操作完成后在恢复这些线程的过程

        特点

        • 全局性:所有应用程序线程都会被暂停
        • 临时性:暂停时间通常很短,但会影响应用程序响应
        • 必要性:为确保垃圾回收过程中的内存一致性而必须采取的措施

        发生时机

        • 标记阶段:如CMS的初始标记和G1的初始标记
        • 重新标记阶段:如CMS的重新标记和G1的最终标记
        • Minor GC:大部分新生代垃圾回收时
        • Full GC:老年代垃圾回收时

        影响

        • 应用程序在STW期间完全停止响应
        • 对实时性要求高的应用,STW会影响用户体验
        • STW时间越长,对应用的影响越大

        各种垃圾回收器的STW

        • Serial/Parallel:STW时间较长,但吞吐量高
        • CMS:STW时间较短,主要在初始标记和重新标记阶段
        • G1:STW时间可预测,通过控制Region数量控制停顿
        • ZGC/Shenandoah:极短的STW,接近实时性

        总结

        STW是垃圾回收过程中不可避免的现象,现代JVM一直在女里减少STW的时间和频率,以提高应用程序的响应性能

        元空间和堆的垃圾回收机制比较

        特性比较

        • 存储对象实例,即程序中创建的各种对象
        • 是GC的主要区域,包括新生代和老年代
        • 使用java的垃圾回收器进行管理(如G1、CMS、parallel等)
        • 回收算法包括 标记-清除、复制、标记-整理等

        元空间

        • java8引入,代替了永久代(PermGen)
        • 存储类的元数据信息,如类定义、方法信息、常量池等
        • 位于本地内存(native Memory)而不是java堆
        • 由本地内存管理,而非javaGC管理

        元空间垃圾回收机制

        触发条件

        • 元空间大小达到阈值时触发回收
        • 通常伴随Full GC一起发生
        • 类卸载(Class Unloading)是元空间回收的主要形式

        回收过程

        • 主要是类的卸载,当类不再被引用时候,其元数据就会被回收
        • 元空间的内存由操作系统管理,使用malloc/free分配和释放
        • 与堆的GC算法不同,元空间不适用传统的标记-清楚等算法

        具体差异对比

        |--------|--------|--------|
        | 特性 | 特性 | 特性 |
        | 存储内容 | 存储内容 | 存储内容 |
        | 对象实例 | 对象实例 | 对象实例 |
        | GC机制 | GC机制 | GC机制 |
        | 专用GC算法 | 专用GC算法 | 专用GC算法 |
        | 类卸载机制 | 类卸载机制 | 类卸载机制 |
        | 管理方式 | 管理方式 | 管理方式 |

        相关JVM参数

        元空间相关参数

        • -XX:MetaspaceSize:初始元空间大小
        • -XX:MaxMetaspaceSize:最大元空间大小
        • -XX:MinMetaspaceFreeRatio:最小元空间空闲比例
        • -XX:MaxMetaspaceExpansion:元空间扩展上限

        类卸载相关参数

        • -XX:+CMSClassUnloadingEnabled:启用CMS类卸载
        • -XX:+DisableExplicitGC:禁用System.gc()

        实际表现

        • 堆的GC会直接影响应用程序的性能和STW时间
        • 元空间的GC通常与Full GC同时发生,影响相对较小
        • 元空间不足会导致OutOfMemoryError:Metaspace错误
        • 元空间的回收主要是类加载器不再引用某个类时的类卸载
        • 总结来说元空间和堆使用不同的垃圾回收机制,元空间的回收主要通过类卸载机制实现,而堆则使用标准的java垃圾回收算法
        相关推荐
        未若君雅裁17 小时前
        JVM 运行时数据区:程序计数器、堆、虚拟机栈与栈帧
        java·jvm
        killerbasd19 小时前
        总结 6.9
        jvm
        IT龟苓膏20 小时前
        Java 并发基础:进程、线程、线程状态、synchronized、volatile 一篇讲清
        java·开发语言·jvm
        周末也要写八哥20 小时前
        线程的生命周期之线程睡眠
        java·开发语言·jvm
        瓦特what?21 小时前
        位运算核心技巧与应用
        java·jvm·算法
        程序员二叉1 天前
        【JVM】类加载全过程&双亲委派机制深度解析
        java·jvm·面试
        cfm_29141 天前
        JVM 深度入门:Class文件结构 + 字节码指令详解
        jvm
        吴声子夜歌1 天前
        JVM——线程池实现原理
        java·jvm·线程池
        Full Stack Developme1 天前
        JVM的类加载机制
        jvm
        fengxin_rou1 天前
        Java垃圾回收机制深度解析:从原理到实战
        java·jvm·性能优化·gc·垃圾回收