JVM-GC垃圾回收机制

java中的引用类型

强引用

  • 最常见的引用类型

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

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

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

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

软引用

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

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

  • 适用于缓存场景

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

    import java.lang.ref.SoftReference;

    SoftReference<Object> 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<Object> softRef = new SoftReference<>(new Object());

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

    public class ObserverManager {
    private List<WeakReference<Observer>> 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<Object> queue = new ReferenceQueue<>();
    PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue);

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

    public class ResourceCleaner {
    private static ReferenceQueue<Object> 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垃圾回收算法
相关推荐
2301_7903009610 小时前
Python单元测试(unittest)实战指南
jvm·数据库·python
码农水水10 小时前
得物Java面试被问:消息队列的死信队列和重试机制
java·开发语言·jvm·数据结构·机器学习·面试·职场和发展
2401_8384725110 小时前
使用Scikit-learn构建你的第一个机器学习模型
jvm·数据库·python
u01092727110 小时前
使用Python进行网络设备自动配置
jvm·数据库·python
执草书云11 小时前
项目优化要点
java·jvm
OnYoung11 小时前
自动化机器学习(AutoML)库TPOT使用指南
jvm·数据库·python
2401_8361216011 小时前
机器学习与人工智能
jvm·数据库·python
zhiyLt11 小时前
如何从Python初学者进阶为专家?
jvm·数据库·python
yufuu9811 小时前
Python面向对象编程(OOP)终极指南
jvm·数据库·python
程序员敲代码吗11 小时前
用Pygame开发你的第一个小游戏
jvm·数据库·python