当前位置: 首页 > 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.lryc.cn/news/584917.html

相关文章:

  • 【C语言网络编程】HTTP 客户端请求(域名解析过程)
  • Django老年健康问诊系统 计算机毕业设计源码32407
  • 华为VS格行VS中兴VS波导随身WIFI6怎么选?流量卡OR随身WIFI,长期使用到底谁更香?
  • 优学教育实战03跟进管理
  • 亿级流量下的缓存架构设计:Redis+Caffeine多级缓存实战
  • 力扣-142.环形链表 II
  • 学习笔记(34):matplotlib绘制图表-房价数据分析与可视化
  • Anaconda及Conda介绍及使用
  • 基于生产者消费者模型的线程池【Linux操作系统】
  • React之旅-05 List Key
  • 《探索电脑麦克风声音采集多窗口实时可视化技术》
  • 基于MuJoCo的宇树科技G1机器人基础动作仿真研究
  • Java 大视界 -- Java 大数据在智能医疗远程手术机器人操作数据记录与分析中的应用(342)
  • 两台电脑通过网线直连形成局域网,共享一台wifi网络实现上网
  • 项目开发日记
  • 【web应用】若依框架中,使用Echarts导出报表为PDF文件
  • Kafka——应该选择哪种Kafka?
  • XPath 语法【Web 自动化-定位方法】
  • 【操作系统】线程
  • [特殊字符] 扫描式处理:Python 自动提取 PDF 中关键词相关表格并导出为 Excel
  • 云、实时、时序数据库混合应用:医疗数据管理的革新与展望(下)
  • lodash不支持 Tree Shaking 而 lodash-es可以
  • 零基础入门指南:华为数通认证体系详解
  • 代码随想录|图论|10水流问题
  • 视频人脸处理——人脸面部动作提取
  • 静电式 vs UV 光解:哪种油烟净化技术更适合你的餐厅?
  • python的病例管理系统
  • 【JMeter】执行系统命令
  • VS 按F12 提示cannot navigate to the symbol under the caret
  • 机器学习详解