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.pswp.cn/bicheng/88841.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/88841.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/88841.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

UniHttp生命周期钩子与公共参数实战:打造智能天气接口客户端

> 通过灵活的生命周期钩子,我们让HTTP请求从机械操作进化为智能对话 在现代应用开发中,高效处理HTTP请求是核心能力。本文将深入探索UniHttp框架中强大的**HttpApiProcessor生命周期钩子**,并演示如何利用其**公共参数填充机制**优雅地处理第三方接口。我们将以百度天…

C++高级编程,类模版成员函数类外实现

#include <iostream> #include <string>//类模版成员函数类外实现 template<class T1,class T2> class Person {//Person构造函数 public:Person(T1 name,T2 age);// {// this->m_Namename;// this->m_Ageage;// }//Person的成员函数void show…

[Linux入门 ] RAID存储技术概述

一.数据存储架构 1️⃣存储系统 2️⃣主机系统 3️⃣互连部件 4️⃣存储设备与磁盘阵列 二.数据存储技术 1️⃣数据冗余技术 2️⃣RAID 0 3️⃣RAID 1 4️⃣RAID 2 5️⃣RAID 3 6️⃣RAID 4 三.基于硬件的RAID磁盘阵列 1️⃣阵列卡(RAID控制器) 2️⃣阵列卡种类 …

AI绘画生成章邯全身像提示词

融合了历史元素和视觉表现力&#xff0c;力求生成符合秦末名将章邯身份的全身像。 核心提示词结构&#xff1a; [主体描述]&#xff0c;[服装/盔甲细节]&#xff0c;[姿态/神情]&#xff0c;[武器]&#xff0c;[背景/氛围]&#xff0c;[风格/质量]&#xff0c;[参数] 选项一&…

iOS高级开发工程师面试——关于优化

iOS高级开发工程师面试——关于优化 一、TableView 有什么好的性能优化方案?二、界面卡顿和检测你都是怎么处理?三、谈谈你对离屏渲染的理解?四、如何降低APP包的大小?五、日常如何检查内存泄露?六、APP启动时间应从哪些方面优化?一、TableView 有什么好的性能优化方案?…

线性基学习笔记

我们称一个线性空间 V V V 的一个极大线性无关集为这个线性空间的线性基,简称基。 异或线性基 在异或空间下,我们定义如下内容。 异或和 设 S S

ESP-Timer入门(基于ESP-IDF-5.4)

主要参考资料&#xff1a; ESP 定时器&#xff08;高分辨率定时器&#xff09;: https://docs.espressif.com/projects/esp-idf/zh_CN/stable/esp32s3/api-reference/system/esp_timer.html 目录ESP-Timer与FreeRTOS TimerAPI 使用1.创建定时器2.启动定时器3.管理定时器4.时间管…

014_批处理与大规模任务

批处理与大规模任务 目录 批处理概述核心优势技术规格API使用管理和监控应用场景最佳实践 批处理概述 什么是批处理 批处理&#xff08;Batch Processing&#xff09;是一种异步处理大量Claude API请求的方法&#xff0c;允许您一次性提交多个消息请求&#xff0c;系统将在…

Python淘宝拍立淘按图搜索API接口,json数据示例参考

淘宝拍立淘按图搜索API接口示例淘宝的拍立淘(图片搜索)功能通常是通过淘宝开放平台提供的API实现的。以下是一个模拟的JSON数据示例和接口调用参考&#xff1a;模拟API请求示例import requestsimport base64# 示例图片路径image_path "example.jpg"# 读取图片并编码…

静默的田野革命—人工智能重构农业生态的技术风暴与文明悖论

一、饥饿困局的数字突围当全球粮食损失率高达30%&#xff08;约13亿吨&#xff09;与8亿人营养不良并存&#xff0c;当农药滥用导致传粉昆虫种群崩溃与地下水资源枯竭&#xff0c;传统农业的生态死结日益收紧。这场危机的核心是生物复杂性对工业化农业的报复&#xff1a;小麦基…

【大模型推理论文阅读】 Thinking Tokens are Information Peaks in LLM Reasoning

Demystifying Reasoning Dynamics with Mutual Information&#xff1a;Thinking Tokens are Information Peaks in LLM Reasoning 摘要 大语言推理模型&#xff08;LRM&#xff09;在复杂问题解决方面展现出了令人瞩目的能力&#xff0c;但其内部推理机制仍未得到充分理解。…

【TCP/IP】14. 远程登录协议

14. 远程登录协议14. 远程登录协议14.1 基本概念14.2 Telnet 命令14.3 Telnet 选项及协商14.4 Telnet 子选项协商14.5 Telnet 操作模式本章要点14. 远程登录协议 14.1 基本概念 Telnet 协议是 TCP/IP 协议族的重要成员&#xff0c;核心功能是实现本地计算机对远程主机的终端仿…

Flink1.20.1集成Paimon遇到的问题

flinkcdc mysql 到paimon 1&#xff1a;Caused by: java.lang.ClassNotFoundException: org.apache.kafka.connect.data.Schema 可以参考这个文章 明确指出了flink-connector-mysql-cdc-3.4.0.jar存在这个包&#xff0c;但是flink-sql-connector-mysql-cdc-3.4.0.jar中没有这个…

C++高频知识点(十)

文章目录46. 智能指针是什么&#xff1f;怎么使用?1. std::unique_ptr2. std::shared_ptr3. std::weak_ptr47. 什么是野指针&#xff1f;1. 使用已释放的指针2. 未初始化的指针3. 指针超出作用域如何避免野指针1. 立即将指针置空2. 初始化指针3. 使用智能指针4. 避免返回局部变…

c#中Random类、DateTime类、String类

C# 中 Random 类分析Random 类用于生成伪随机数&#xff0c;位于 System 命名空间。它的核心机制是基于一个种子值 (seed)&#xff0c;通过算法生成看似随机的数列。相同种子会生成相同的随机数序列&#xff0c;这在需要可重现的随机场景中很有用。核心特点种子与随机性默认构造…

Vscode 下载远程服务器失败解决方法

今天在使用 vscode 连接远程主机时&#xff0c;突然再次遇到这个问题&#xff0c;按照以往的经验&#xff0c;直接按照这个博主的文章其实就能解决&#xff0c;但是不知道为什么&#xff0c;今天这个方案失效了&#xff0c;然后卸载安装服务器和本机的vscode什么的也都试过了&a…

【算法】贪心算法:柠檬水找零C++

文章目录前言题目解析算法原理代码示例策略证明前言 题目的链接&#xff0c;大家可以先试着去做一下再来看一下思路。 860. 柠檬水找零 - 力扣&#xff08;LeetCode&#xff09; 题目解析 首先我们要认真去拿到题目中的关键有用信息。 认真的去阅读题目给的示例&#xff0c;然…

27.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--币种服务(一)

从本篇文章开始&#xff0c;我们将用两篇内容详细介绍币种服务的实现。币种服务本身结构较为简单&#xff0c;核心功能包括内置币种的初始化、币种汇率的同步以及汇率的查询。在本篇中&#xff0c;我们将重点讲解如何实现内置币种的初始化功能&#xff0c;为后续的服务打下基础…

(2)从零开发 Chrome 插件:实现 API 登录与本地存储功能

从零开发 Chrome 插件&#xff1a;实现 API 登录与本地存储功能 Chrome 插件作为浏览器功能的重要扩展&#xff0c;能极大提升用户的工作效率。本文将以一个「登录功能插件」为例&#xff0c;带你从零构建一个可调用 API 验证身份、并将用户信息存储在本地的 Chrome 插件。 基…

Flink时间窗口详解

一、引言在大数据流处理的领域中&#xff0c;Flink 的时间窗口是一项极为关键的技术&#xff0c;想象一下&#xff0c;你要统计一个电商网站每小时的订单数量。由于订单数据是持续不断产生的&#xff0c;这就形成了一个无界数据流。如果没有时间窗口的概念&#xff0c;你就需要…