- 什么是分代回收机制?
什么是 Java 堆内存
堆是在 JVM 启动时创建的,主要用来维护运行时数据,如运行过程中创建的对象和数组都是基于这块内存空间。Java 堆是非常重要的元素,如果我们动态创建的对象没有得到及时回收,持续堆积,最后会导致堆空间被占满,内存溢出。
因此,Java 提供了一种垃圾回收机制,在后台创建一个守护进程。该进程会在内存紧张的时候自动跳出来,把堆空间的垃圾全部进行回收,从而保证程序的正常运行。
那什么是垃圾呢?
所谓“垃圾”,就是指所有不再存活的对象。常见的判断是否存活有两种方法:引用计数法和可达性分析。
引用计数法
为每一个创建的对象分配一个引用计数器,用来存储该对象被引用的个数。当该个数为零,意味着没有人再使用这个对象,可以认为“对象死亡”。但是,这种方案存在严重的问题,就是无法检测“循环引用”:当两个对象互相引用,即时它俩都不被外界任何东西引用,它俩的计数都不为零,因此永远不会被回收。而实际上对于开发者而言,这两个对象已经完全没有用处了。
因此,Java 里没有采用这样的方案来判定对象的“存活性”。
可达性分析
这种方案是目前主流语言里采用的对象存活性判断方案。基本思路是把所有引用的对象想象成一棵树,从树的根结点 GC Roots 出发,持续遍历找出所有连接的树枝对象,这些对象则被称为“可达”对象,或称“存活”对象。其余的对象则被视为“死亡”的“不可达”对象,或称“垃圾”。
参考下图,object5,object6和object7便是不可达对象,视为“死亡状态”,应该被垃圾回收器回收。
GC Roots 究竟指谁呢?
我们可以猜测,GC Roots 本身一定是可达的,这样从它们出发遍历到的对象才能保证一定可达。那么,Java 里有哪些对象是一定可达呢?主要有以下四种:
- 虚拟机栈(帧栈中的本地变量表)中引用的对象。
- 方法区中静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中JNI引用的对象。
不少读者可能对这些 GC Roots 似懂非懂,这涉及到 JVM 本身的内存结构等等,未来的文章会再做深入讲解。这里只要知道有这么几种类型的 GC Roots,每次垃圾回收器会从这些根结点开始遍历寻找所有可达节点。
有哪些方式来回收这些垃圾呢?
上面已经知道,所有GC Roots不可达的对象都称为垃圾,参考下图,黑色的表示垃圾,灰色表示存活对象,绿色表示空白空间。
那么,我们如何来回收这些垃圾呢?
标记-清理
第一步,所谓“标记”就是利用可达性遍历堆内存,把“存活”对象和“垃圾”对象进行标记,得到的结果如上图; 第二步,既然“垃圾”已经标记好了,那我们再遍历一遍,把所有“垃圾”对象所占的空间直接清空
即可。
结果如下:
这便是标记-清理
方案,简单方便
,但是容易产生内存碎片
。
标记-整理
既然上面的方法会产生内存碎片,那好,我在清理的时候,把所有存活
对象扎堆到同一个地方,让它们待在一起,这样就没有内存碎片了。
结果如下:
这两种方案适合存活对象多,垃圾少
的情况,它只需要清理掉少量的垃圾,然后挪动下存活对象就可以了。
复制
这种方法比较粗暴,直接把堆内存分成两部分,一段时间内只允许在其中一块内存上进行分配,当这块内存被分配完后,则执行垃圾回收,把所有存活
对象全部复制到另一块内存上,当前内存则直接全部清空。
参考下图:
起初时只使用上面部分的内存,直到内存使用完毕,才进行垃圾回收,把所有存活对象搬到下半部分,并把上半部分进行清空。
这种做法不容易产生碎片,也简单粗暴;但是,它意味着你在一段时间内只能使用一部分的内存,超过这部分内存的话就意味着堆内存里频繁的复制清空
。
这种方案适合存活对象少,垃圾多
的情况,这样在复制时就不需要复制多少对象过去,多数垃圾直接被清空处理。
Java 的分代回收机制
上面我们看到有至少三种方法来回收内存,那么 Java 里是如何选择利用这三种回收算法呢?是只用一种还是三种都用呢?
Java 的堆结构
在选择回收算法前,我们先来看一下 Java 堆的结构。
一块 Java 堆空间一般分成三部分,这三部分用来存储三类数据:
- 刚刚创建的对象。在代码运行时会持续不断地创造新的对象,这些新创建的对象会被统一放在一起。因为有很多局部变量等在新创建后很快会变成
不可达
的对象,快速死去
,因此这块区域的特点是存活对象少,垃圾多
。形象点描述这块区域为:新生代
; - 存活了一段时间的对象。这些对象早早就被创建了,而且一直活了下来。我们把这些
存活时间较长
的对象放在一起,它们的特点是存活对象多,垃圾少
。形象点描述这块区域为:老年代
; - 永久存在的对象。比如一些静态文件,这些对象的特点是不需要垃圾回收,永远存活。形象点描述这块区域为:
永久代
。(不过在 Java 8 里已经把永久代
删除了,把这块内存空间给了元空间
,后续文章再讲解。)
也就是说,常规的 Java 堆至少包括了 新生代
和 老年代
两块内存区域,而且这两块区域有很明显的特征:
- 新生代:存活对象少、垃圾多
- 老年代:存活对象多、垃圾少
结合新生代/老年代的存活对象特点和之前提过的几种垃圾回收算法,可以得到如下的回收方案:
新生代-复制
回收机制
对于新生代区域,由于每次 GC 都会有大量新对象死去,只有少量存活。因此采用复制
回收算法,GC 时把少量的存活对象复制过去即可。
那么如何设计这个复制
算法比较好呢?有以下几种方式:
思路1. 把内存均分成 1:1
两等份
如下图拆分内存。
每次只使用一半的内存,当这一半满了后,就进行垃圾回收,把存活的对象直接复制到另一半内存,并清空当前一半的内存。
这种分法的缺陷是相当于只有一半的可用内存,对于新生代而言,新对象持续不断地被创建,如果只有一半可用内存,那显然要持续不断地进行垃圾回收工作,反而影响到了正常程序的运行,得不偿失。
思路2. 把内存按 9:1
分
既然上面的分法导致可用内存只剩一半,那么我做些调整,把 1:1
变成9:1
,
最开始在 9
的内存区使用,当 9
快要满时,执行复制回收,把 9
内仍然存活的对象复制到 1
区,并清空 9
区。
这样看起来是比上面的方法好了,但是它存在比较严重的问题。
当我们把 9
区存活对象复制到 1
区时,由于内存空间比例相差比较大,所以很有可能 1
区放不满,此时就不得不把对象移到 老年区
。而这就意味着,可能会有一部分 并不老
的 9
区对象由于 1
区放不下了而被放到了 老年区
,可想而知,这破坏了 老年区
的规则。或者说,一定程度上的 老年区
并不一定全是 老年对象
。
那应该如何才能把真正比较 老
的对象挪到 老年区
呢?
思路3. 把内存按 8:1:1
分
既然 9:1
有可能把年轻对象放到 老年区
,那就换成 8:1:1
,依次取名为 Eden
、Survivor A
、Survivor B
区,其中Eden
意为伊甸园,形容有很多新生对象在里面创建;Survivor
区则为幸存者,即经历 GC 后仍然存活下来的对象。
工作原理如下:
- 首先,
Eden
区最大,对外提供堆内存。当Eden
区快要满了,则进行Minor GC
,把存活对象放入Survivor A
区,清空Eden
区; Eden
区被清空后,继续对外提供堆内存;- 当
Eden
区再次被填满,此时对Eden
区和Survivor A
区同时进行Minor GC
,把存活对象放入Survivor B
区,同时清空Eden
区和Survivor A
区; Eden
区继续对外提供堆内存,并重复上述过程,即在Eden
区填满后,把Eden
区和某个Survivor
区的存活对象放到另一个Survivor
区;- 当某个
Survivor
区被填满,且仍有对象未被复制完毕时,或者某些对象在反复Survive
15
次左右时,则把这部分剩余对象放到Old
区; - 当
Old
区也被填满时,进行Major GC
,对Old
区进行垃圾回收。
[注意,在真实的 JVM 环境里,可以通过参数 SurvivorRatio
手动配置Eden
区和单个Survivor
区的比例,默认为8。]
那么,所谓的 Old
区垃圾回收,或称Major GC
,应该如何执行呢?
老年代-标记整理
回收机制
根据上面我们知道,老年代一般存放的是存活时间较久的对象,所以每一次 GC 时,存活对象比较较大,也就是说每次只有少部分对象被回收。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
总结
一般像这样的大企业都有好几轮面试,所以自己一定要花点时间去收集整理一下公司的背景,公司的企业文化,俗话说「知己知彼百战不殆」,不要盲目的去面试,还有很多人关心怎么去跟HR谈薪资。
这边给大家一个建议,如果你的理想薪资是30K,你完全可以跟HR谈33~35K,而不是一下子就把自己的底牌暴露了出来,不过肯定不能说的这么直接,比如原来你的公司是25K,你可以跟HR讲原来的薪资是多少,你们这边能给到我的是多少?你说我这边希望可以有一个20%涨薪。
最后再说几句关于招聘平台的,总之,简历投递给公司之前,请确认下这家公司到底咋样,先去百度了解下,别被坑了,每个平台都有一些居心不良的广告党等着你上钩,千万别上当!!!
Java架构学习资料,学习技术内容包含有:Spring,Dubbo,MyBatis, RPC, 源码分析,高并发、高性能、分布式,性能优化,微服务 高级架构开发等等。
还有Java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
+Spring源码合集+Java架构实战电子书。
[外链图片转存中…(img-wFwjM2pH-1713433082406)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!