当前位置: 首页 > news >正文

JVM 垃圾回收

目录

  • 一、内存分配原则
    • 1.对象优先在Eden区分配
    • 2.需要大量连续内存空间的对象直接分配到Tenured区
    • 3.长期存活的对象转移到Tenured区
    • 4.内存分配过程
      • 4.1 JDK9 G1
      • 4.2 JDK8 CMS
  • 二、内存回收原则
    • 1.GC类型
    • 2.死亡对象判断方法
      • 2.1 引用计数法
      • 2.2 可达性分析算法
    • 3.垃圾收集算法
      • 3.1 标记-清除算法
      • 3.2 标记-复制算法
    • 4.内存回收过程
      • 4.1 三色标计法
      • 4.2 CMS
      • 4.3 G1

一、内存分配原则

1.对象优先在Eden区分配

对象首先在Eden区分配内存空间。当Eden区没有足够空间进行分配时,JVM触发Minor GC/Young GC

  • 将Eden区中所有存活对象的对象头部记录年龄的4bit位+1,并移动到S1区。
  • 将S0区的存活对象年龄+1,如果年龄达到1111,将对象移动到Tenured区,否则移动到S1区。
  • 如果S0区中相同年龄的所有对象大小的总和超过了S0空间的一半,那么年龄大于或等于该年龄的对象会移动到Tenured区。
  • 此时Eden和S0被清空,将S0和S1身份互换,下次GC移动到S0。
  • 为新对象在Eden区分配内存空间,初始化年龄为0000
  • 如果S1区在Minor GC过程中空间不足,JVM会采用分配担保机制,将超出S1容量的对象直接移动到Tenured。如果Tenured区空间也不足,就会触发Full GC

这里可能会问为什么要有Eden、S0、S1,直接用Eden不行吗?
首先这是一种垃圾回收算法,分区机制是考虑到将存活的对象转移到新的连续内存空间中保证死亡对象回收后不会有内存碎片问题,导致空间不足。
其次这种转移方式是为了统一更新存活对象的年龄,在转移时更新避免遗漏。

2.需要大量连续内存空间的对象直接分配到Tenured区

大对象直接进入Tenured区是为了避免大对象在S0\S1区之间来回移动的成本、避免占用太大的S1区内存、大对象需要连续空间会导致S1区内存碎片,降低存储效率。

3.长期存活的对象转移到Tenured区

对象在S0\S1每熬过一次Minor GC/Young GC,对象的年龄就增加1岁,当它的年龄增加到一定程度(默认为 15 岁),就会被移动到Tenured区中。

4.内存分配过程

4.1 JDK9 G1

  1. 类加载后,G1采用页式离散管理+可变分区连续管理,基于-XX:G1HeapRegionSize内存划分为大小相同的Page。JVM按照地址升序(页号,页内地址[TLAB号,内地址])维护一个全局空闲分区链表
  2. 为了解决多线程并发的效率问题,JVM的思路是为每个线程预分配一块堆空间TLAB,线程实例化对象时直接在TLAB低地址划分一块连续内存空间给当前对象,当前线程的后续对象依次在该TLAB中分配,直到该TLAB满了。
  3. 当该线程的TLAB空间不足时,从最近(之前的肯定都分配满了)状态为Eden的Page中CAS修改分配指针(每个Page页内地址从0x0000开始)取一块连续的TLAB给当前线程。
  4. 当Eden的Page空间不足时,使用首次适应算法(低地址开始分配)从空闲分区链表获取一个状态Free的Page,使用CAS标记为Eden,所以各代由一组非连续的Page构成,且各代空间占用动态变化。
  5. 如果空闲分区链表为,那么触发Young GC
  6. 对于内存占用超过Page/2的大对象,直接分配到Tenured区,遍历空闲分区链表获取一个或多个连续的状态为Free的Page标记为Humongous,不同的是Page内即使有空闲空间也不会再被使用

4.2 JDK8 CMS

  1. CMS的Eden区和Tenured区都是一片连续的存储空间,各代的空间占用固定-XX:SurvivorRatio
  2. Eden区维护一大片连续的存储空间,每个线程还是私有TLAB,当TLAB不足时在Eden区低地址部分CAS修改分配指针分配连续空间。
  3. Tenured区维护空闲列表,对于-XX:PretenureSizeThreshold的大对象,JVM使用首次适应/最佳适应算法找到一个足够大的、连续的内存块来存放对象。

二、内存回收原则

1.GC类型

  • Minor GCEden内存区空间不足时触发,只清理Eden和S0、S1中的对象。
  • Major GC:当Tenured内存区空间不足时触发,只清理Tenured中的对象。
  • Full GC:当方法区、Tenured内存区空间不足时触发,清理整个堆空间(Eden、S0、S1、方法区Metaspace)

2.死亡对象判断方法

2.1 引用计数法

对象头中添加一个引用计数器,每当该对象被引用,计数器就加 1;当引用失效,计数器就减 1;计数器为 0 的对象默认为死亡。

这种方法的缺点是当多个对象相互引用,但是这些对象除了相互引用外没有被使用,此时GC就无法回收。

voidtest(){Nodea=newNode();// 对象1Nodeb=newNode();// 对象2a.next=b;b.next=a;// 方法结束,对象1和对象2就用不到了,但是由于他们相互引用,GC无法回收他们}

2.2 可达性分析算法

活跃栈帧本地变量表中的引用、方法区中引用类型的静态变量作为GC Roots,每个GC Roots可以引出一棵树结构,在JVM堆中(包括字符串常量池)不在任何一棵树下的对象被定义为死亡,等待下次GC,死亡对象的内存空间就会被释放。

Java 引用:强引用、软引用、弱引用

大白话就是只要方法中用到的堆对象就不会被回收(前提是强引用),方法执行完了实例化的对象就没用了,那就可以回收了。

voidtest(){Nodea=newNode();// 对象1Nodeb=newNode();// 对象2a.next=b;b.next=a;// 方法结束,栈帧被释放// 虽然对象1和对象2互相引用,但没有GC Roots指向它们// 结果:对象1和对象2都不可达,都会被回收}

对于方法区中的类,判定为死亡需要满足三个条件:

  • 该类所有在堆中的实例都已经被回收。
  • 加载该类的ClassLoader已经被回收。
  • 内存中没有使用反射访问该类

3.垃圾收集算法

3.1 标记-清除算法

CMS垃圾回收器在老年代用的就是标记清除算法,老年代是一块固定的连续存储空间,垃圾回收后通过维护空闲列表来记录可用内存。标记清除算法首先会标记存活的对象,统一回收所有没有被标记的对象,缺点是内存碎片问题严重。

3.2 标记-复制算法

CMS垃圾回收器在青年代用的就是标记复制算法,Eden和Survivor是固定的连续存储空间,分别维护一个碰撞指针每次从低地址分配内存,标记复制算法将存活对象移动到Survivor低地址,便于维护碰撞指针。将内存分为大小相同的两块(Eden+Survivor[P0、P1]),只向Eden和P0中存数据,执行垃圾回收时将存活的对象全部转移到P1内存中,清空Eden+P0的,很好的解决了内存碎片问题

G1垃圾回收器基于标记复制算法,将多个Page中所有存活的对象移动到新的空闲Page中并标记为Survivor,将原Page放回空闲链表

4.内存回收过程

4.1 三色标计法

  1. STW在基于可达性分析标记垃圾时会阻塞应用程序的所有线程,保证标记完整(一边扫地一边嗑瓜子)。三色标记法实现了并发标记不影响应用线程的执行。
  2. 三色标计法在标记时会基于上次标记的结果树,只修改发生变化的子树,减少了重复的扫描。为每个对象涂色,每次标记只扫描灰色集合中的对象并扫描其子树
    白色:对象没有被标记过
    灰色:对象已经被标记过了,但该对象下还有部分属性没被标记
    黑色:对象已经被标记过了,且对象下的属性也被标记过了

    因为标记是在并发条件下的,当本轮标记后-准备清除前死亡对象又被重新引用了,就会导致对象被错误的回收,但是CMS和G1已经解决了这个问题。

4.2 CMS

  1. 初始标记:单线程并Stop the World,标记GC Roots能直达的对象。
  2. 并发标记:无需中断,和用户线程同时运行,三色标记法从GC Roots直达对象开始遍历整个对象树
  3. 重新标记:多线程并STW,标记并发标记阶段引用发生变化的对象(扫描三色标记法灰色集合)
  4. 并发清除:和用户线程同时运行,清理标记为死亡的对象。
  5. 筛选回收:STW,老年代执行清除算法,年轻代执行复制算法

4.3 G1

  1. 获取对应区域的Page集合
  2. 初始标记:单线程并Stop the World,标记GC Roots能直达的对象。
  3. 并发标记:无需中断,和用户线程同时运行,三色标记法从GC Roots直达对象开始遍历整个对象树
  4. 重新标记:多线程并STW,标记并发标记阶段引用发生变化的对象(扫描三色标记法灰色集合),防止对象被。
  5. 并发清除:和用户线程同时运行,清理标记为死亡的对象。
  6. 筛选回收:STW,执行复制算法,选择多个Page构成回收集,执行复制算法把回收集中的对象复制到新的Page中标记为Survivor,将回收集的Page清空并放回空闲链表
http://www.jsqmd.com/news/490902/

相关文章:

  • DeOldify图像风格参考学习:输入参考图指导整体色调倾向
  • 教你掌握万爱通礼品卡回收的技巧和回收流程! - 团团收购物卡回收
  • Stable Yogi Leather-Dress-Collection实战案例:动漫OST专辑封面皮衣主题视觉生成
  • GME-Qwen2-VL-2B快速部署:Jupyter Notebook本地调用+Gradio远程访问双模式
  • Z-Image-Turbo-rinaiqiao-huiyewunv实操教程:批量生成任务队列管理与进度条反馈实现
  • 2026六大城市高端腕表“计时码表”终极档案:从导柱轮到归零锤,这项最复杂功能的维修密码 - 时光修表匠
  • 多维复高斯分布PDF表达式、协方差矩阵意义探究
  • Jimeng AI Studio实战教程:LoRA模型命名规范与自动识别逻辑
  • Qwen3-ForcedAligner-0.6B入门必看:标点符号处理规范(句号/逗号/顿号影响)
  • Cogito-v1-preview-llama-3B实战案例:用Ollama API接入企业低代码平台
  • Nanbeige4.1-3B快速部署:镜像免配置+WebShell验证+提问测试三合一
  • Clawdbot汉化版案例展示:AI自动将客户需求转化为PRD文档框架
  • 浦语灵笔2.5-7B部署教程:ins-xcomposer2.5-dual-v1镜像启动排错指南
  • Qwen3-Reranker-0.6B实操手册:自定义评估脚本+业务指标自动化计算
  • ClawdBot一键部署:docker-compose.yml内置proxy/SSL/healthcheck全配置
  • GLM-Image一键启动脚本详解:--port/--share参数配置与远程访问实操
  • Neeshck-Z-lmage_LYX_v2实操手册:错误堆栈定位——模型加载失败排查全流程
  • 2026年Java面试总结(持续更新)
  • translategemma-4b-it惊艳案例:Ollama本地运行含艺术字体海报图翻译效果
  • 打造 AI 冒险团:HagiCode 多 Agent 协作配置实战
  • MongoDB(44)什么是引用?
  • Stable Yogi Leather-Dress-Collection显存优化教程:enable_model_cpu_offload实测
  • LongCat-Image-Editn多场景应用:电商换装、海报文案插入、教育图解修改
  • Nunchaku-flux-1-devLogo设计:品牌关键词生成矢量感草图
  • Git-RSCLIP多场景支持效果展示:水域识别、机场定位、林地覆盖分析
  • SiameseAOE中文-base快速部署:NVIDIA T4显卡下1.2s完成整句ABSA推理
  • Qwen3-4B Instruct-2507部署教程:镜像免配置+HTTP一键访问全流程
  • 2026年3月北京心理咨询师实习/督导/实战/培训机构哪家好 - 2026年企业推荐榜
  • DAMOYOLO-S部署教程:无需下载权重,内置模型路径直启方案
  • Lingyuxiu MXJ LoRA GPU友好型教程:24G显存下多版本LoRA并行测试