
全文目录开篇语0. 前言从 malloc 的噩梦说起1. 标记-清除Mark-Sweep最原始的“贴条法” 伪代码模拟逻辑2. 复制算法Copying富二代的“双拼别墅”玩法 伪代码模拟逻辑3. 标记-整理Mark-Compact强迫症患者的福音4. 深度拓展分代收集才是王道大乱炖️ 实战演示如何看懂 GC 日志5. 写在最后垃圾回收也是人生哲学文末开篇语哈喽各位小伙伴们你们好呀我是喵手。运营社区C站/掘金/腾讯云/阿里云/华为云/51CTO欢迎大家常来逛逛今天我要给大家分享一些自己日常学习到的一些知识点并以文字的形式跟大家一起交流互相学习一个人虽可以走的更快但一群人可以走的更远。我是一名后端开发爱好者工作日常接触到最多的就是Java语言啦所以我都尽量抽业余时间把自己所学到所会的通过文章的形式进行输出希望以这种方式帮助到更多的初学者或者想入门的小伙伴们同时也能对自己的技术进行沉淀加以复盘查缺补漏。小伙伴们在批阅的过程中如果觉得文章不错欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持0. 前言从malloc的噩梦说起如果你写过 C/C那你肯定有过被Segmentation Fault支配的恐惧。那时候咱们就是内存的保姆借了东西malloc必须得还free要是忘了还那就是内存泄漏要是还错人了那就是野指针。那时候我就在想要是有个田螺姑娘能自动帮我收拾屋子该多爽啊后来 Java 来了带着它的 JVM 和 GCGarbage Collection来了它就像那个宠坏你的田螺姑娘“你只管造new剩下的脏活累活我来干”但是田螺姑娘也是有脾气的 如果你不知道她是怎么打扫卫生的拼命往地上扔垃圾她忙不过来的时候直接就给你来个Stop The World (STW)—— 所有人都不许动老娘要大扫除了这时候你的系统就像断网了一样尴尬。所以今天咱们就来扒一扒 GC 的三大神技标记-清除、复制、标记-整理。看看它们到底是怎么把你的内存打理得井井有条或者乱七八糟的。1. 标记-清除Mark-Sweep最原始的“贴条法”这是最基础、最老牌的算法就像是咱们打扫卫生时的第一直觉。怎么玩呢分两步走标记MarkGC 从根节点GC Roots比如栈里的变量出发顺藤摸瓜。只要是能摸到的对象就给它脑门上贴个条“这玩意儿还有用别扔”️清除Sweep全堆扫描凡是脑门上没贴条的统统干掉听起来很完美对吧简单粗暴但是…这就好比你在切披萨。 你把不爱吃的火腿抠掉了剩下的披萨虽然还能吃但是这就这就这就…千疮百孔啊它的致命缺陷叫内存碎片化Fragmentation。想象一下你的内存被清理后变成了瑞士奶酪到处都是小洞洞。这时候你需要分配一个连续的大对象比如一个大数组虽然这些小洞洞加起来空间够但因为没有一块完整的连续地盘JVM 只能两手一摊“臣妾做不到啊” 结果只能再次触发 GC简直是恶性循环。 伪代码模拟逻辑# 假装这是内存管理器defmark_and_sweep(memory_heap):# 第一步标记# 这里的 root_objects 是你正在用的变量active_objectsset()forrootinroot_objects:mark(root,active_objects)# 递归把引用的都标记上# 第二步清除# 遍历堆里所有对象不在 active_objects 里的就是垃圾forobjinmemory_heap:ifobjnotinactive_objects:memory_heap.free(obj)# 直接释放留下一块空地# 此时 memory_heap 里充满了不连续的空洞... ️2. 复制算法Copying富二代的“双拼别墅”玩法为了解决碎片化的问题有人想出了一个极端的招数复制算法。这思路简直就是土豪行为 它把内存一分为二分成A区和B区。平时咱们只在A区蹦跶。当 A 区满了垃圾回收开始把 A 区里所有活着的对象整整齐齐地搬到B区去。搬完之后直接把 A 区一把火烧光下次就在 B 区玩满了再搬回 A 区。爽不爽太爽了根本不需要考虑碎片问题每次搬家都是紧凑排列的分配内存时只需要移动指针速度快得飞起但是万恶的转折来了…这种玩法的代价是你得有一半的内存常年空着比如你买了 8G 内存结果系统告诉你“不好意思为了快你只能用 4G另外 4G 要留着搬家用。”这谁顶得住啊这不是败家子吗所以这种算法通常只用在对象死得快的区域比如 Java 的新生代因为存活的对象少搬运成本低。 伪代码模拟逻辑# 假装这是新生代 GCdefcopying_gc(from_space,to_space):free_pointer0# to_space 的起始位置forobjinfrom_space:ifis_alive(obj):# 搬家而且是紧凑地搬过去copy(obj,to_space[free_pointer])free_pointersize(obj)# 这一行代码简直霸气侧漏clear_all(from_space)# 旧家直接推平# 交换角色下次从 to_space 搬回 from_spaceswap(from_space,to_space)3. 标记-整理Mark-Compact强迫症患者的福音既然“标记-清除”有碎片“复制算法”太浪费空间那有没有折中方案这就轮到标记-整理出场了。它结合了前两者的优点特别适合老年代这种“老不死”对象扎堆的地方。怎么玩标记和第一种一样先把活着的找出来。整理Compact这一步最关键它不是直接清理垃圾而是让所有存活的对象都向一端移动。清理把边界以外的内存全部清空。这就好比咱们玩“俄罗斯方块”不管中间有多少空隙我把所有方块都往底下一压上面剩下的空间就全是干净、连续的了优点没有碎片也不浪费一半空间。缺点累啊慢啊你想想如果内存里有几万个对象你要把它们一个个挪位置还得更新它们之间错综复杂的引用地址这工作量简直爆炸。所以在整理的过程中系统不得不暂停很长时间STW你的网页可能就在那儿转圈圈。4. 深度拓展分代收集才是王道大乱炖既然这三种算法各有优劣那咱们能不能成年人不做选择全都要这就是现代 JVM 的分代收集理论。它根据对象存活周期的不同把内存分成了几块因地制宜️新生代Young Gen这里全是刚new出来的“小鲜肉”。据统计98% 的对象在这里那是“朝生夕死”。策略用复制算法的改良版并不是 1:1 分而是弄一个大的 Eden 区和两个小的 Survivor 区通常 8:1:1。每次只浪费 10% 的空间是不是聪明多了老年代Old Gen这里全是经过多次 GC 还没死掉的“老油条”或者是那种大到塞不进新生代的“巨无霸”。策略这种对象很难死搬家又贵所以用标记-整理或者标记-清除。️ 实战演示如何看懂 GC 日志光说不练假把式。咱们写一段 Java 代码故意制造点垃圾看看 GC 是怎么工作的。importjava.util.ArrayList;importjava.util.List;// 运行参数建议-Xms20M -Xmx20M -Xmn10M -XX:PrintGCDetails// 意思就是给堆内存限制在 20M新生代 10M打印 GC 详情publicclassGCDemo{publicstaticvoidmain(String[]args){Listbyte[]listnewArrayList();inti0;try{while(true){// 不断制造 1MB 的垃圾对象// 这就像是你疯狂买买买家里马上就堆满了list.add(newbyte[1*1024*1024]);i;System.out.println(分配了 iMB 内存...);}}catch(OutOfMemoryErrore){System.out.println( 完蛋OOM 了);System.out.println(一共存活了 i 次分配);}}}当你运行这段代码你会看到类似这样的日志主要看心情[GC (Allocation Failure) [PSYoungGen: 8192K-1000K(9216K)] 8192K-5024K(19456K), 0.0042314 secs]老哥帮你翻译一下Allocation Failure新生代Eden满了放不下了触发 GC。PSYoungGen: 8192K-1000K新生代原本占了 8MGC 用复制算法一顿操作猛如虎把活着的搬到 Survivor 区剩下 1M。你看这就体现了复制算法“倒腾”的过程。等到最后 OOM内存溢出的时候就是老年代也塞满了标记-整理也救不了你了直接崩溃。5. 写在最后垃圾回收也是人生哲学你看GC 算法其实特别像咱们的人生规划复制算法像年轻时候精力旺盛哪怕浪费点时间去试错浪费空间也要追求快节奏不行就换个地方重头再来。标记-整理像中年以后东西多了羁绊深了不能随便搬家了只能在现有的圈子里慢慢整理虽然动作慢了效率低但是稳重、紧凑每一步都算数。所以啊兄弟们别光顾着埋头写代码偶尔也看看这些底层的智慧。哪怕是为了面试的时候能跟面试官侃大山把这些原理搞透也是稳赚不赔的买卖好了今天的“垃圾分类”课程就到这儿。我要去手动System.gc()一下我的脑子了咱们下期见… …文末好啦以上就是我这期的全部内容如果有任何疑问欢迎下方留言哦咱们下期见。… …学习不分先后知识不分多少事无巨细当以虚心求教三人行必有我师焉wished for you successed ⭐️若喜欢我就请关注我叭。⭐️若对您有用就请点赞叭。⭐️若有疑问就请评论留言告诉我叭。版权声明本文由作者原创转载请注明出处谢谢支持