iis6建设网站浏览哈尔滨手机网站建设
2026/3/25 17:05:57 网站建设 项目流程
iis6建设网站浏览,哈尔滨手机网站建设,wordpress升级php7,南山商城网站建设垃圾回收算法的思想 垃圾回收的基本思想是考察每一个对象的可触及性#xff0c;即从根节点开始是否可以访问到这个对象#xff0c;如果可以#xff0c;则说明当前对象正在被使用#xff0c;如果从所有的根节点都无法访问到某个对象#xff0c;说明对象已经不再使用了…垃圾回收算法的思想垃圾回收的基本思想是考察每一个对象的可触及性即从根节点开始是否可以访问到这个对象如果可以则说明当前对象正在被使用如果从所有的根节点都无法访问到某个对象说明对象已经不再使用了一般来说此对象需要被回收。1. 可达性分析你可以把内存中的对象想象成一串葡萄根节点GC Roots就是你手里提着葡萄藤的位置。只要你的手抓着藤Root顺着藤连下来的葡萄对象都是“活”的不能扔。可触及Reachable从手里的藤开始顺着枝丫能找到的葡萄都是可触及的。不可触及Unreachable/Garbage如果有几颗葡萄虽然彼此连在一起但是和主藤断开了掉在地上了不管它们那一小簇葡萄内部连得有多紧密因为手提不起来了它们就是垃圾需要被扫走。核心逻辑JVM 在垃圾回收时会暂时“冻结”世界STW从所有的 GC Roots 出发给所有能连上的对象打个标记。等标记完了剩下没被标记的对象就是“孤魂野鬼”会被清除。为什么要用这种方法早期的算法是“引用计数法”对象被引用一次计数1。但它有一个死穴如果 A 引用 BB 引用 A但没人理它们俩它们的计数器永远是 1永远不会被回收。可达性分析完美解决了这个问题只要断了根内部循环引用照样死。2. 什么是根节点GC Roots附代码案例“根节点”听起来很抽象其实翻译成大白话就是程序运行当前时刻必须立刻、马上能用到的对象。在 Java 中最常见的GC Roots主要有以下几类。为了方便理解我写了一个类来演示第一类栈帧中的局部变量最常见解释你正在运行的方法中定义的变量。方法还没执行完这个变量肯定是有用的所以它是根。publicclassStackRoot{publicstaticvoidmain(String[]args){// 场景方法正在执行// user 是一个局部变量存放在栈内存中// 只要 main 方法没结束user 指向的 new User() 对象就是被根节点引用的UserusernewUser(张三);// 此时user 就是一个 GC Rootusernull;// 此时刚才那个 User 对象失去了根节点的引用下次 GC 就会被回收}}第二类方法区中的静态变量Static解释被static修饰的变量。它属于类不属于具体的对象。类加载后通常长期存在所以它引用的对象也是根。publicclassStaticRoot{// cache 是一个静态变量// 它随着类的加载而存在生命周期很长// 所以 ArrayList 这个对象一直被 static 引用抓着它是 GC RootpublicstaticListStringcachenewArrayList();publicstaticvoidmain(String[]args){cache.add(数据1);// 这个字符串被 GC Root 引用不会被回收}}第三类方法区中的常量Final解释被final修饰的常量或者字符串常量池中的引用。publicclassConstRoot{// GREETING 是常量// 它指向的 Hello World 字符串对象会被作为 GC Root 保护起来publicstaticfinalStringGREETINGHello World;}第四类本地方法栈中引用的对象JNI解释如果你的 Java 代码调用了 C/C 写的 Native 方法比如操作系统的 DLL 文件这些 C 代码中引用的 Java 对象也是根。这个平时开发很少直接接触知道即可。3. 谁活谁死Object A(Static 变量) -Object BObject C(Main 方法内的局部变量) -Object DObject E-Object F(E 和 F 互相引用但没有根连着它们)判定结果A是根Static所以A活着连带B也活着。C是根Stack所以C活着连带D也活着。E 和 F虽然互相拉着手但没人拉它们没有 Root 引用所以E 和 F 都是垃圾会被回收。总结当你困惑“这玩意儿是不是 GC Root”时问自己一个问题“代码运行到这一行时我如果把这个对象删了程序会报错吗”如果是局部变量正在用删了肯定空指针所以它是 Root。如果是静态变量全局都能用删了别人读不到所以它是 Root。如果是孤零零的对象没变量指向它删了也没人知道所以它不是 Root可以回收。引用计数法1. 核心定义与基本原理引用计数法是一种直接的内存管理技术。与可达性分析追踪式 GC不同它不依赖于特定的“垃圾回收时刻”来扫描整个内存堆而是将内存管理的职责下放到了每一个对象自身。核心机制每个对象在创建时会在其对象头Object Header或辅助结构中分配一个整型字段Integer Field称为引用计数器Reference Counter。这个计数器实时记录着当前程序中有多少个指针或引用正在指向该对象。2. 算法的逻辑流转在引用计数算法下内存管理变成了一个严格的算术问题。对象的状态仅由计数器的数值决定A. 计数器的正向变更Increment当一个对象被引用时其计数器立即加 1。这通常发生在以下技术场景赋值操作将该对象的地址赋值给一个新的变量Object a b。传参操作将该对象作为参数传递给一个方法入栈产生新的局部变量引用。容器存储将该对象加入到数组、列表或映射等集合容器中容器持有引用。B. 计数器的负向变更Decrement当一个对象的引用失效时其计数器立即减 1。这发生于引用销毁某个指向该对象的局部变量超出了作用域出栈。引用重置指向该对象的变量被赋值为null或被指向了另一个对象。移出容器该对象被从集合容器中移除。C. 回收触发Deallocation这是引用计数法最显著的特征即时性。一旦某个对象的引用计数器变为0即ref_count 0系统立即判定该对象为“不可用”并同步触发内存回收操作。该对象所占用的内存空间会被立即释放归还给空闲链表Free List。3. 技术优势从计算机科学的角度看这种算法具有以下明确的特性内存回收的实时性Real-time垃圾回收不需要等到内存耗尽时才触发也不需要暂停整个应用程序Stop-The-World。对象一旦成为垃圾其占用的资源会立即被释放。这对内存敏感的系统非常重要。执行平滑性垃圾回收的计算开销被均匀分散在每一次赋值操作中而不是集中在某一时刻爆发。判断逻辑简单不需要像可达性分析那样从 GC Roots 进行全局遍历只需要关注对象局部的计数器状态。4. 致命缺陷与技术代价尽管逻辑简单但引用计数法在现代高性能虚拟机如 JVM中被弃用主要是因为以下两个无法忽视的技术硬伤A. 无法解决“循环引用”Cyclic Reference这是引用计数法在图论层面的逻辑漏洞。现象对象 A 内部持有对象 B 的引用对象 B 内部持有对象 A 的引用。状态此时即使外部所有的引用都已断开A 和 B 对外部程序不可见A 和 B 的引用计数器依然都维持在1。结果依据算法逻辑计数器不为 0 则不回收。这两个对象将形成一个“内存孤岛”永久占用堆内存造成内存泄漏。B. 运行时开销Overhead引用计数看似分散了压力但在高并发场景下代价极高存储开销每个对象都需要额外的内存空间来存储计数器。计算开销每一次简单的指针赋值操作a b在底层都需要插入计数器的加减指令。并发安全代价在多线程环境下修改计数器必须是原子操作Atomic Operation。频繁的原子更新CAS操作或锁会引发严重的 CPU 缓存同步问题导致吞吐量大幅下降。总结引用计数法是一种基于局部状态的内存管理策略。它的信条是“只要有人指向我我就有存在的价值一旦没人指向我我立刻消失。”它的死穴是无法处理“小团体内部互相吹捧循环引用”的情况且在高并发下维护“账本计数器”的成本过于昂贵。内存泄漏1. 核心定义内存泄漏是指程序中已动态分配的堆内存由于某种原因程序未能释放且垃圾回收器GC也无法回收导致这块内存始终被占用无法被再次利用的现象。这里存在两个视角的矛盾程序的视角逻辑层面该对象已经使用完毕后续的业务逻辑中不再需要它了。GC 的视角技术层面根据“可达性分析算法”该对象依然存在一条从 GC Roots 到它的引用链。结论内存泄漏的本质是对象的生命周期被人为地或无意地延长了超过了它实际被需要的时间。2. 内存泄漏的发生机制要理解内存泄漏必须理解长生命周期对象与短生命周期对象之间的持有关系。正常情况一个对象如方法内的局部变量使用完毕后引用断开它与 GC Roots 失去连接GC 扫描时将其回收。泄漏情况当一个生命周期非常长的对象例如static变量、单例对象在内部持有了一个生命周期本该很短的对象例如一次性的临时数据的引用且在使用完后没有手动断开。结果虽然那个短对象已经没用了但因为它被长对象“抓”着而长对象又是 GC Root或连接着 GC Root导致 GC 无法回收那个短对象。长此以往堆内存中堆积的“无用但存活”的对象越来越多。3. 典型的技术性场景归类抛开具体业务从代码模式上看内存泄漏主要源于以下几种技术操作的不规范A. 静态集合类的膨胀 (Static Collections)机制static关键字修饰的成员变量其生命周期与 JVM或类加载器一致贯穿程序运行始终。泄漏点如果将对象放入一个static的List、Map等集合中却只存不取或只添加不删除这些对象将永远滞留在堆内存中直到程序结束。B. 各种连接资源未关闭 (Unclosed Resources)机制数据库连接Connection、网络连接Socket、IO 流。这些资源通常涉及到底层操作系统的句柄。泄漏点虽然 Java 对象本身可能被回收但如果未调用close()方法底层的缓冲区域或句柄可能无法释放。更严格地说这往往导致堆外内存或操作系统资源的泄漏。C. 改变哈希值导致的“丢失” (Mutable Keys in Hash Structures)机制当对象被存储在HashSet或HashMap中时是依据其hashCode放置的。泄漏点如果对象存入集合后你修改了该对象参与哈希计算的字段导致其hashCode发生变化。此时即使你调用remove()方法集合也无法根据新的哈希值找到原来的对象。这个对象就“死”在了集合里无法被删除也无法被回收。D. 内部类与外部类的隐式引用 (Inner Class References)机制非静态内部类Non-static Inner Class在实例化时会默认持有一个指向外部类实例的隐式引用。泄漏点如果内部类的实例被一个长生命周期的对象持有或者内部类开启了一个长线程那么外部类的实例即便已经没用了也无法被回收因为内部类在引用它。4. 内存泄漏的后果与演变内存泄漏通常不是突发性的崩溃而是一个累积的过程初期几乎无感知系统运行正常。中期性能下降堆内存中可用空间越来越少。GC 发现内存紧张被迫频繁触发尤其是 Full GC。由于泄漏的对象是“可达的”GC 扫描和标记它们需要消耗大量 CPU 资源但又无法回收它们导致“由于 GC 造成的应用暂停Stop The World”时间变长系统响应变慢。末期OOM堆内存被这些泄漏的对象填满。当程序试图申请新的内存空间时GC 即使拼尽全力也无法腾出空间。JVM 抛出java.lang.OutOfMemoryError: Java heap space程序崩溃。总结从逻辑上看内存泄漏就是持有Reference大于需求Need。即你依然持有该对象的强引用但你的业务逻辑已经不再需要该对象了。解决内存泄漏的根本就是确保引用的生命周期与对象的业务生命周期保持一致用完即断开置为null或从集合中移除。标记清除法1. 算法的前提条件标记-清除算法依赖于可达性分析Reachability Analysis。即在执行回收前系统必须能够暂停应用程序的执行Stop The World并从一组固定的GC Roots开始遍历对象图。2. 第一阶段标记阶段 (The Marking Phase)此阶段的目标是识别所有存活的对象。遍历过程垃圾回收器GC暂停应用程序从所有的 GC Roots 出发沿着引用链Reference Chain进行深度优先或广度优先遍历。标记动作对于遍历过程中遇到的每一个对象GC 会在其对象头Object Header中设置一个特定的标记位Mark Bit将其标识为“可达”Reachable或“存活”。状态冻结为了保证标记的准确性防止在标记过程中对象引用关系发生变化通常需要冻结用户线程。结果当遍历结束时堆内存中的对象被在逻辑上划分为两类被标记的对象确认存活需要保留。未被标记的对象被判定为不可达确认为垃圾。3. 第二阶段清除阶段 (The Sweeping Phase)此阶段的目标是回收未被标记对象占用的内存空间。线性扫描GC 会对堆内存的某一块区域进行从头到尾的线性扫描Linear Scan。判定与回收在扫描过程中GC 检查每个对象的标记位如果对象已被标记说明该对象存活。GC 会清除该标记位将其重置以便下一次 GC 周期使用然后跳过该对象。如果对象未被标记说明该对象是垃圾。GC 不会移动该对象而是将其占用的内存地址和大小记录下来放入一个**空闲列表Free List**中。逻辑删除注意“清除”通常意味着逻辑上的释放。GC 并不一定会物理地将内存中的数据清零Zero Out而是简单地将这段地址标记为“可用”下次分配新对象时可以直接覆盖这块区域。4. 核心缺陷与技术代价虽然标记-清除算法逻辑清晰且不需要移动对象但它存在两个严重的工程化问题导致现代高性能 JVM 很少单独使用它A. 效率问题 (Efficiency)标记效率标记阶段需要遍历所有存活对象。如果堆中存活对象很多递归遍历的开销很大。清除效率清除阶段需要线性扫描整个堆空间。如果堆非常大扫描耗时较长。B. 内存碎片化 (Memory Fragmentation) —— 最核心的痛点这是标记-清除算法最显著的特征。现象由于垃圾对象在堆内存中的分布是随机的清除这些对象后会产生大量不连续的内存空隙。这些空隙就像奶酪上的孔洞一样。后果大对象分配失败当程序需要为一个个头较大的对象如大数组分配内存时虽然堆中总的空闲内存足够但无法找到一块连续的足够大的空间。这会迫使 JVM 提前触发下一次垃圾回收Full GC。分配方式受限由于内存不连续JVM 无法使用高效的指针碰撞Bump Pointer方式分配内存而必须维护一个复杂的空闲列表Free List记录哪些地址块是可用的。这增加了对象分配时的计算开销。总结标记-清除算法采用了“先盘点后清理”的策略。它的贡献利用可达性分析解决了循环引用问题确立了现代 GC 的基本框架。它的局限“只管杀不管埋”。它清除了垃圾却留下了支离破碎的内存空间碎片这为后续的内存分配埋下了隐患。为了解决碎片问题后续演化出了标记-整理压缩算法Mark-Compact和复制算法Copying。复制算法复制算法Copying Algorithm是为了解决标记-清除算法中“内存碎片化”问题而提出的一种高性能回收策略。它的核心理念是通过牺牲一部分内存空间换取内存整理的高效率和内存分配的低开销。1. 内存分区策略 (Memory Partitioning)复制算法不再将整个堆内存视为一个单一的连续空间而是将其严格划分为两个大小相等的区域对象面From-Space当前正在使用的内存区域负责对象的分配。空闲面To-Space预留的内存区域在 GC 开始前始终保持为空。在任意时刻只有其中一个区域From-Space在被应用程序使用另一个区域To-Space处于闲置待命状态。2. 算法执行流程当 From-Space 的内存空间耗尽时垃圾回收被触发Stop The World流程如下A. 标记与复制 (Mark and Copy)根节点扫描GC 从 GC Roots 出发寻找所有存活的对象。对象迁移一旦发现某个对象是存活的GC不只是标记它而是立即将它从 From-Space复制到 To-Space 中。紧凑排列这是关键步骤。在向 To-Space 复制对象时GC 会按顺序一个接一个地放置对象从 To-Space 的起始地址开始往后排。这意味着复制后的对象在物理内存上是连续存储的中间没有任何空隙。引用更新由于对象在内存中的物理地址发生了改变GC 会同时更新程序中所有指向该对象的引用确保它们指向 To-Space 中的新地址。B. 清除与交换 (Clear and Swap)全量清除当所有存活对象都复制到 To-Space 后From-Space 中剩下的全是垃圾对象。GC 不需要逐个遍历回收直接将 From-Space 的内存指针重置清空整个区域。角色互换From-Space 和 To-Space 交换身份。原来的 To-Space 变成新的 From-Space继续为应用程序分配内存。原来的 From-Space 变成新的 To-Space等待下一次 GC。3. 技术优势复制算法在工程实现上极其高效主要体现在以下两点A. 彻底解决了内存碎片 (No Fragmentation)由于存活对象在 To-Space 中是按顺序连续存放的因此 GC 结束后堆内存永远是规整的。这消除了“标记-清除”算法产生的内存空洞。B. 极高的内存分配效率 (Pointer Bumping)因为内存是规整的当 JVM 需要为新对象分配内存时不需要维护复杂的“空闲列表”来查找可用块。JVM 只需要维护一个指针Bump Pointer指向 To-Space 中空闲内存的起始位置。分配内存时只需将指针向后移动一段与对象大小相等的距离即可。这种操作在汇编层面极其快速。4. 核心缺陷与代价既然这么好为什么不能完全取代其他算法因为它有两个昂贵的代价A. 内存利用率低 (Low Memory Utilization)这是最直观的缺点。为了运行该算法你必须将内存一分为二任何时刻都有一半的内存To-Space是闲置浪费的。这意味着可用内存直接缩水了50%。B. 存活率高时效率骤降复制算法的效率取决于存活对象的数量。如果大部分对象都是垃圾存活率低只需要复制极少数对象效率极高。如果大部分对象都存活存活率高GC 不仅需要复制大量对象还需要频繁更新引用地址这会导致极其严重的性能开销。5. 现代 JVM 的改良应用 (Appel 式回收)由于 50% 的内存浪费在工业界是不可接受的且根据统计Java 对象中 98% 都是“朝生夕死”的短命对象。因此现代 JVM如 HotSpot对复制算法进行了改良用于**新生代Young Generation**的回收分区调整不再是 1:1 分区而是分为一块较大的Eden 区通常占 80%和两块较小的Survivor 区各占 10%。运作方式每次使用 Eden 和其中一块 Survivor。回收时将存活对象复制到另一块闲置的 Survivor 中。空间利用率这样只有 10% 的内存被闲置Empty Survivor内存利用率从 50% 提升到了90%。总结复制算法采用了“空间换时间”的策略。逻辑把活着的对象搬家到另一边剩下的直接全部铲除。特点分配极快无碎片但如果不加改良内存浪费极其严重。适用场景绝大多数对象都会死掉的场景即 Java 堆的新生代。标记压缩法标记-压缩算法Mark-Compact也称标记-整理算法是结合了“标记-清除”和“复制”算法优点的一种综合性回收策略。它旨在解决“标记-清除”算法带来的内存碎片问题同时避免“复制”算法带来的内存空间浪费问题。1. 核心设计思路该算法的执行过程分为两个截然不同的阶段。它的核心逻辑是不仅要将垃圾清除还要将剩余的存活对象“搬运”到一起使它们在内存中紧凑排列。2. 第一阶段标记阶段 (The Marking Phase)此阶段与“标记-清除”算法完全一致。过程JVM 暂停应用程序Stop The World从 GC Roots 开始遍历对象引用图。动作所有被引用的对象都会被识别并在对象头中打上“存活”标记。结果此时堆内存中呈现出存活对象与垃圾对象交替分布的离散状态。3. 第二阶段压缩整理阶段 (The Compacting Phase)这是该算法最关键的步骤。与“标记-清除”算法直接回收垃圾不同也与“复制”算法将对象移到另一块区域不同标记-压缩算法是在当前区域内进行对象的移动。对象移动RelocationGC 将所有被标记为“存活”的对象向内存空间的一端通常是低地址端移动。紧凑排列存活对象在移动后会按顺序紧密靠拢消除原来对象之间的空隙。引用修正Reference Updating由于对象的物理地址发生了改变GC 必须同步更新所有指向这些对象的引用变量将其指向新的物理地址。这是一个极其繁重且必须保证原子性的操作。边界清理当所有存活对象都移动并排列好之后GC 会确定存活对象序列的边界Boundary。边界之后的所有内存空间即原来的垃圾对象和空隙被直接清理或标记为“空闲”。4. 技术优势标记-压缩算法在内存布局上达到了最优状态A. 消除了内存碎片 (No Fragmentation)经过整理后堆内存变为规整的连续块。这解决了“标记-清除”算法中大对象分配难的问题。B. 内存利用率高 (High Utilization)与“复制”算法相比它不需要预留一半的 Survivor 区域To-Space。它可以在同一个区域内完成整理因此内存的空间利用率可以达到 100%。C. 支持高效分配 (Bump Pointer)由于剩余的空闲内存是连续的JVM 在分配新对象时可以使用极快的**指针碰撞Bump Pointer**技术只需移动空闲指针即可无需维护空闲列表。5. 核心缺陷与代价虽然内存布局完美但该算法的时间开销是最大的A. 极高的暂停时间 (Long Stop-The-World)移动成本内存移动Memory Copy是 CPU 密集型操作。如果存活对象很多搬运这些数据需要消耗大量时间。修正成本更新引用地址需要遍历整个堆进一步增加了暂停时间。在移动对象期间应用程序必须全程暂停无法运行。6. 适用场景老年代 (Old Generation)基于上述特性标记-压缩算法通常被用于 JVM 的老年代Old Generation/Tenured。原因老年代的对象存活率非常高大部分对象长期存在。如果用复制算法需要复制大量对象效率低且没有额外的担保空间。如果用标记-清除会产生大量碎片不利于大对象如长数组、大字符串的存储。结论虽然标记-压缩算法的暂停时间较长但对于对象“死得少”且“不允许碎片”的老年代来说它是最经济、最稳定的选择。总结标记-压缩算法采用了**“整理归纳”**的策略。逻辑把活着的对象全部推到角落里排好队剩下的空间一次性腾出来。特点无碎片无空间浪费但对象移动的成本非常高也就是 GC 停顿时间最长。地位它是以“时间换空间”的典型代表是老年代垃圾回收的主流算法。分代算法分代收集算法Generational Collection严格来说并不是一种全新的垃圾回收算法而是一种管理策略。它基于对程序运行特征的统计学分析将堆内存划分成不同的区域然后根据每个区域中对象的特性选择最合适的回收算法复制、标记-清除、标记-整理。这种策略的核心思想是分而治之Divide and Conquer。1. 理论基石弱分代假说 (Weak Generational Hypothesis)分代算法的建立并非凭空想象而是基于两个在绝大多数工业级程序中都成立的经验法则绝大多数对象都是“朝生夕死”的统计表明约 98% 的对象在创建后很快就会变得不可达变成垃圾。熬过越多次垃圾回收的对象越难消亡如果一个对象已经存活了很久那么它在未来大概率也会继续存活。基于此JVM 将堆内存划分为两个物理或逻辑上隔离的区域新生代Young Generation和老年代Old Generation。2. 内存分区与算法选择A. 新生代 (Young Generation)对象特征这里是对象刚刚“出生”分配内存的地方。该区域的特点是对象存活率极低垃圾回收频繁。采用算法复制算法Copying。技术理由由于绝大多数对象都会死掉存活下来的寥寥无几。复制成本低只需要将极少数存活对象复制到 Survivor 区成本很小。无碎片复制算法天然保证内存规整适合频繁的对象分配TLAB, 指针碰撞。空间改良为了解决复制算法浪费 50% 空间的问题新生代通常被划分为Eden 区80%、Survivor 0 区10%和Survivor 1 区10%。每次只浪费 10% 的空间。B. 老年代 (Old Generation)对象特征这里存放的是经过多次 GC 依然存活的“顽固”对象或者是大对象。该区域的特点是对象存活率高且没有额外的空间进行分配担保。采用算法标记-清除Mark-Sweep或标记-整理Mark-Compact。技术理由无法复制存活对象太多如果用复制算法复制成本过高且没有第三块内存来分担风险。必须紧凑为了容纳更多长周期对象必须保证空间利用率因此主要使用标记-整理算法来压缩空间。3. 对象的生命周期流转 (Object Promotion)分代算法定义了对象在不同区域间的流动规则这被称为对象晋升Promotion分配Allocation绝大多数新对象在Eden 区分配。Minor GCYoung GC当 Eden 区满时触发 Minor GC。Eden 和正在使用的 Survivor 区中存活的对象会被复制到另一个空的 Survivor 区。对象的年龄Age加 1记录在对象头中。Eden 区和原 Survivor 区被清空。晋升Tenuring当对象的年龄达到阈值默认为 15 岁或者 Survivor 区空间不足以容纳所有存活对象时这些对象会被移动到老年代。Major GC / Full GC当老年代空间不足时会触发 Major GC或 Full GC。此时会使用标记-整理/清除算法对老年代进行清理。如果清理后依然无法容纳新晋升的对象就会抛出 OOMOut Of Memory错误。4. 跨代引用问题与卡表 (Card Table)这是一个为了实现分代算法必须解决的技术难题。问题虽然我们将内存分成了两块但老年代的对象完全可能引用新生代的对象。当进行 Minor GC只回收新生代时为了确定新生代对象是否存活是否需要扫描整个老年代来查找引用这会极大地降低 Minor GC 的效率。解决方案卡表Card Table。JVM 维护了一个数据结构叫卡表将老年代内存划分为一个个小的内存块Card。如果老年代中某个对象引用了新生代对象JVM 会通过**写屏障Write Barrier**技术将该老年代对象对应的卡表标记为“脏Dirty”。GC 时的优化在进行 Minor GC 时不需要扫描整个老年代只需要扫描卡表中状态为“脏”的区域就能快速找到跨代引用作为 GC Roots 的一部分。总结分代收集算法并非一种单一的算法而是一套组合拳在新生代利用“对象死得快”的特点用复制算法以空间换时间追求极致的回收速度。在老年代利用“对象死得慢”的特点用标记-整理算法以时间换空间追求存储效率和稳定性。它是现代商业虚拟机如 HotSpot为了平衡吞吐量和停顿时间而采用的标准解决方案。分区算法分区算法Region-Based Collection最为人熟知的实现是G1 (Garbage-First) 垃圾收集器代表了垃圾回收技术从“连续分代”向“离散分区”的重大演进。它彻底打破了传统分代算法中“新生代”和“老年代”在物理内存上必须连续隔离的限制将堆内存重新布局。1. 核心架构棋盘式内存布局在传统算法中堆被物理划分为巨大的连续块Eden, Survivor, Old。而在分区算法中离散化Discretization整个 Java 堆被划分为多个大小相等的独立区域称为Region。JVM 启动时会自动设置 Region 的大小通常为 1MB 到 32MB 之间且为 2 的幂。物理离散逻辑分代虽然不再有物理隔离的大块区域但每个 Region 依然会被动态地标记为属于不同的“逻辑代”Eden Region存放新分配的对象。Survivor Region存放经过 GC 幸存的对象。Old Region存放老年代对象。Humongous Region巨型区域这是分区算法特有的。如果一个对象的大小超过了 Region 容量的 50%它会被直接存入巨型区域。如果对象特别大可能会跨越多个连续的 Region。2. 运作机制化整为零分区算法的核心不再是“一次回收整个新生代”或“一次回收整个老年代”而是以 Region 为最小回收单元。A. 局部性回收 (Incremental Collection)GC 不再必须扫描整个堆。它可以根据实际情况只选择其中一部分最有回收价值的 Region 进行处理。这使得垃圾回收的停顿时间Pause Time变得可控。B. 核心算法复制与整理在微观层面Region 之间该算法使用的是复制算法Copying。过程当回收某个 Region 时将其中的存活对象复制到另一个空的 Region 中。结果原 Region 被清空并回收。优势这意味着在 Region 层面它天然就是**压缩Compact**的不会产生内存碎片。3. 关键技术支撑为了实现这种离散的回收必须解决“跨 Region 引用”的问题即回收 Region A 时怎么知道 Region B 是否引用了它而不用扫描 Region B。A. Remembered Set (RSet) - 记忆集定义每个 Region 都有一个对应的 RSet用于记录“谁引用了我”。作用当 Region A 中的对象引用了 Region B 中的对象时JVM 会通过写屏障Write Barrier拦截该操作并在 Region B 的 RSet 中记录下 Region A 的地址。价值在回收 Region B 时GC 只需要扫描 Region B 的 RSet就能确定哪些对象是被外部引用的而无需全堆扫描。B. Collection Set (CSet) - 回收集合定义在一次 GC 周期中GC 会选定一组“需要被回收的 Region”这组列表就是 CSet。策略CSet 可以只包含 Eden RegionsYoung GC也可以包含 Eden 部分 Old RegionsMixed GC。4. 为什么叫“Garbage First” (G1)这是分区算法最核心的启发式调度策略。价值评估JVM 会后台维护一个优先列表跟踪每个 Region 中包含的垃圾数量回收收益以及回收该 Region 所需的时间回收成本。优先回收在给定的停顿时间限制下例如用户设定 GC 停顿不能超过 200msGC 会优先选择那些垃圾占比最高、回收收益最大的 Region 加入 CSet。含义即“垃圾优先”原则。这保证了在有限的时间内获取尽可能高的收集效率。5. 回收模式的演变分区算法通常包含两种主要的回收模式Young GC年轻代回收当 Eden Region 耗尽时触发。将所有 Eden Region 和 Survivor Region 中的存活对象复制到新的 Survivor 或 Old Region 中。这是一个并行、独占式Stop-The-World的过程。Mixed GC混合回收这是分区算法的精髓。当老年代占用达到阈值时触发。回收范围包括所有的 Young Regions根据收益动态选取的“部分”Old Regions。它不是 Full GC它只是有计划地蚕食老年代的垃圾避免堆内存被填满。总结**分区算法G1采用了“化整为零、按需调度”**的策略。逻辑把堆切成小格子每次只挑最脏的几个格子清理。特点可预测的停顿用户可以指定 GC 停顿时间算法会自动调整回收的 Region 数量来满足。无碎片基于复制算法内存天然规整。内存占用较高因为每个 Region 都要维护复杂的 RSet会额外占用约 10%-20% 的堆内存空间。地位它是对传统分代算法的颠覆性升级是大内存Large Heap服务的标准选择。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询