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

JAVA JVM垃圾收集

JVM 垃圾收集是 Java 自动内存管理的核心,本文通过围绕 “哪些是垃圾、何时回收、怎么回收、用啥回收器、内存咋分配” 等展开

一、判断哪些是垃圾

  • 引用计数法:给对象分配引用计数器,有引用时计数加 1,引用失效减 1 ,计数为 0 则可回收。但无法解决循环引用问题(如两个对象相互引用,实际无外部引用,计数器却不为 0 )。
Java虚拟机并不是通过引用计数算法来判断对象是否存活的
  • 可达性分析:以 GC Roots(如虚拟机栈中引用的对象、方法区静态变量引用的对象等 )为起点,遍历对象引用链,没被链连接的对象视为可回收垃圾,主流 JVM 常用此方式。
    • 枚举根节点:需暂停应用线程(“stop the world” ),因遍历期间对象引用变化会影响结果,所以要让线程停顿,后续有优化手段(如并发标记 )缓解停顿影响。
  • 引用分类:
    • 强引用 程序正常引用,如 Object obj = new Object() ,只要强引用在,对象不回收
    • 软引用(内存不足时回收,可配合缓存场景 )用来描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。
    • 弱引用(垃圾收集时就回收 )也是用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
    • 虚引用(主要用于跟踪对象回收,回收前收到系统通知 )为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。
即使在可达性分析算法中判定为不可达的对象,也不是“非死不可”的,要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记,随后进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。假如对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,那么虚拟机将这两种情况都视为“没有必要执行”。

二、 垃圾回收时机与分代收集理论

回收时机:新生代(Minor GC ):Eden 区快满时触发,回收新生代垃圾,借助复制算法,把存活对象移到 Survivor 或老年代;老年代(Major GC/Full GC ):老年代空间不足、永久代(元空间 )满等情况触发,回收老年代及可能涉及新生代,用标记 - 整理或标记 - 清除(结合压缩 ),Full GC 耗时久,尽量避免。
分代收集建立在两个分代假说之上:
1)弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的。
2)强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡
这两个分代假说共同奠定了多款常用的垃圾收集器的一致的设计原则:收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。
设计者一般至少会把Java堆划分为新生代(Young Generation)和老年代(Old Generation)两个区域,在新生代中,每次垃圾收集时都发现有大批对象死去,而每次回收后存活的少量对象,将会逐步晋升到老年代中存放。
  • 大部分对象朝生夕死:新生代特点,对应 “标记复制” 算法思路,如 Eden 区 + Survivor 区,新对象先放 Eden ,回收时把存活对象复制到 Survivor ,减少内存碎片。
  • 少数对象存活较久:老年代特征,常用 “标记清除”“标记整理” 算法。“标记清除” 先标记可回收对象,再清除,会产生内存碎片;“标记 - 整理” 标记后让存活对象移动、紧凑排列,解决碎片问题,但需额外移动成本。
  • 跨代引用假说:老年代对象引用新生代对象,比新生代内部引用少。垃圾收集时,若扫描老年代找跨代引用,效率低。可通过在新生代设记忆集(记录老年代到新生代的引用 ),当发生Minor GC时,只有包含了跨代引用的小块内存里的对象才会被加入到GC Roots进行扫描,避免全扫老年代,优化回收效率。

三、垃圾回收算法(收集算法 )

  • 标记清除:分标记、清除阶段,标记出可回收对象,再清理内存。缺点是1.产生碎片,可能导致大对象无法分配连续内存2.执行效率不稳定。
  • 标记复制:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。实现简单,运行高效,无空间碎片,不过这种复制回收算法的代价是将可用内存缩小为了原来的一半
现在的商用Java虚拟机大多都优先采用了这种收集算法去回收新生代,IBM公司曾有一项专门研究对新生代“朝生夕灭”的特点做了更量化的诠释——新生代中的对象有98%熬不过第一轮收集。因此并不需要按照1∶1的比例来划分新生代的内存空间。
在1989年,Andrew Appel针对具备“朝生夕灭”特点的对象,提出了一种更优化的半区复制分代策略,现在称为“Appel式回收”。
Appel式回收的具体做法是把新生代分为一块较大的Eden空间和两块较小的Survivor空间,每次分配内存只使用Eden和其中一块Survivor。发生垃圾搜集时,将Eden和Survivor中仍然存活的对象一次性复制到另外一块Survivor空间上,然后直接清理掉Eden和已用过的那块Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8∶1,也即每次新生代中可用内存空间为整个新生代容量的90%(Eden的80%加上一个Survivor的10%),只有一个Survivor空间,即10%的新生代是会被“浪费”的。
  • 标记整理:标记后,让存活对象向一端移动,整理内存,解决碎片问题,适用于老年代等对象存活率高区域,不过移动对象有性能开销,还需处理对象引用更新。
  • 分代收集:结合不同代特点选算法,新生代用标记 - 复制,老年代用标记 - 清除 / 标记 - 整理,是 JVM 常用策略,如 HotSpot 虚拟机的分代垃圾收集器(Serial、ParNew、Parallel Scavenge 对应新生代,Serial Old、Parallel Old、CMS、G1 等涉及老年代 )。

四、垃圾收集器(不同实现 )

了解
  • Serial(串行 ):新生代、老年代都可用,单线程工作,“Stop - The - World” 明显,简单高效,适合客户端模式、内存小场景。
  • ParNew:Serial 多线程版,用于新生代,配合 CMS 老年代收集器(CMS 新生代需用 ParNew 或 Serial ),多线程加速新生代回收,在服务端应用常见。
  • Parallel Scavenge:新生代收集器,关注吞吐量(运行用户代码时间 /(用户代码时间 + 垃圾收集时间 )),适合后台计算等对吞吐量敏感场景,可自动调节参数(自适应调节策略 )。
  • Serial Old:Serial 老年代版本,单线程,标记 - 整理算法,可与 Parallel Scavenge 配合,或作为 CMS 后备预案(CMS 并发失败时启用 )。
  • Parallel Old:Parallel Scavenge 老年代版,多线程、标记 - 整理算法,让吞吐量优先的收集器组合(Parallel Scavenge + Parallel Old )更完善,适合追求高吞吐量场景。
主流
  • CMS(Concurrent Mark Sweep):

特点:以 “并发” 为核心,标记和清除阶段可与应用线程并行(减少 stop the world 时间 );基于 “标记 - 清除” 算法。

问题:会产生内存碎片;并发阶段占用 CPU 资源,可能影响应用;无法处理 “浮动垃圾”(并发阶段新产生的垃圾,需等下次回收 )。

适用:追求低延迟、对吞吐量要求不极致的场景(如 Web 应用 )。

  • G1(Garbage - First):

特点:面向服务端应用,把堆划分为多个 Region;基于 “标记 - 整理”,按 Region 回收,优先回收垃圾多的 Region(“Garbage - First” );可预测停顿时间(通过设置停顿目标,规划回收 Region )。

优势:兼顾吞吐量和延迟,适合大内存场景;减少碎片。

适用:对停顿敏感、堆内存较大的应用(如大型后台服务 )。

五、内存分配与回收策略

  • 对象优先在 Eden 分配:新生代 Eden 区是对象诞生地,新对象先放这,Eden 满触发 Minor GC。
  • 大对象直接进老年代:大对象(如超长数组 )不适合在新生代折腾,直接分配到老年代,避免多次 GC 拷贝。
  • 长期存活的进入老年代:对象在新生代 Survivor 区经历多次 Minor GC 仍存活(通过 “年龄计数器” 判断 ),会晋升到老年代。
  • 动态年龄判定:并非等 “年龄” 到阈值才晋升,若 Survivor 中同年龄对象总和超过该区一半,年龄≥此值的对象直接进老年代,灵活调整。
  • 空间分配担保:Minor GC 前,JVM 判断老年代剩余空间是否够放新生代存活对象。若预估够,执行 Minor GC;否则看 “担保” 机制,允许则尝试,不允许则转为 Full GC ,保障内存分配安全。

六、垃圾回收相关问题

  • 空间碎片问题:标记 清除易产生,影响大对象分配,标记整理、标记复制可缓解,不同收集器处理方式不同(如 G1 靠 Region 划分和整理,减少碎片影响 )。
  • “Stop the world”:垃圾收集时暂停用户线程,避免对象引用变化干扰回收,不同收集器尽力缩短停顿(如 G1 并发标记、CMS 并发阶段 ),但关键步骤(如根节点枚举 )仍需停顿,是垃圾收集需优化的点。
  • 什么时候回收:没有固定严格时间,JVM 依堆内存使用、分代特点等判断。新生代对象满了触发 Minor GC ,老年代空间不足、永久代(元空间 )不足等触发 Full GC ,不同收集器触发机制有差异,且要平衡回收频率和性能,避免频繁回收影响应用。
http://www.dtcms.com/a/274635.html

相关文章:

  • 上半年净利预增66%-97%,高增长的赛力斯该咋看?
  • 解决Vue页面黑底红字遮罩层报错:Unknown promise rejection reason (webpack-internal)
  • Semi-Supervised Single-View 3D Reconstruction via Prototype Shape Priors
  • LDO选型
  • 手把手一起使用Miniforge3+mamba平替Anaconda(Win10)
  • 【web应用】若依框架中,使用Echarts导出报表为PDF文件
  • Linux中LVM逻辑卷扩容
  • 第七章 愿景05 莹姐画流程图
  • 企业采购成本越来越贵?根源在哪,数据怎么分析?
  • Linux操作系统从入门到实战:怎么查看,删除,更新本地的软件镜像源
  • Python 类型注解实战:`Optional` 与安全数据处理的艺术
  • 递归与树形结构在前端的应用
  • 林吉特危机下的技术革命:马来西亚金融系统升维作战手册
  • 【深度探究系列(5)】:前端开发打怪升级指南:从踩坑到封神的解决方案手册
  • U-Net网络学习笔记(1)
  • ARM单片机OTA解析(二)
  • cesium添加原生MVT矢量瓦片方案
  • 在 Spring Boot 中使用 WebMvcConfigurer
  • 【SpringBoot】配置文件学习
  • linux kernel struct regmap_config结构详解
  • 力扣242.有效的字母异位词
  • MySQL5.7版本出现同步或插入中文出现乱码或???显示问题处理
  • vector之动态二维数组的底层
  • django queryset 去重
  • JavaSE -- StreamAPI 详细介绍(上篇)
  • Java开发新宠!飞算JavaAI深度体验评测
  • 获取华为开源3D引擎 (OpenHarmony),把引擎嵌入VUE中
  • string模拟实现
  • 信号肽预测工具PrediSi本地化
  • 《打破预设的编码逻辑:Ruby元编程的动态方法艺术》