Java堆(Java Heap)是JVM所管理的内存中最大的一块,堆又是垃圾收集器管理的主要区域,这里我们主要分析一下Java堆的结构。
Java堆主要分为2个区域,年轻代与老年代,其中年轻代又分Eden区和Survivor区,其中Survivor区域又分为From和to两个区。
可能这时候大家会有疑问,为什么需要Survivor区,为什么Survivor还要分为2个区?请继续往下看!
1、Eden区
IBM公司的专业研究表明,有将近98%的对象是朝生夕死,所以针对这一现状,大多数情况下,对象会在新生代Eden区中进行分配。当Eden区没有足够空间进行分配时,虚拟机会发起一次Minor GC,Minor GC相比Major GC更为频繁,回收速度也更快。通过Minor GC之后,Eden区会被清空,Eden区中绝大部分对象会被回收,而那些无需回收的存活对象,将会进到Survivor的From区(若From区不够,则直接进入Old区)。
2、Survivor区
Survivor区相当于Eden区和Old区一个缓冲,类似于我们交通中的黄灯。
Survivor又分为2个区:
1) From区
2) To区
每次执行Minor GC,会将Eden区和From存活的对象放到Survivor的To区(如果To区不够,则直接进入Old区)。
1) 为啥需要?
不就是新生代到老年代么,直接Eden到Old不好了吗,为啥要这么复杂?
想想如果没有Survivor区,Eden区每进行一次Minor GC,存活的对象就会被送到老年代,老年代很快就很被填满。而有很多对象虽然不止一次Minor GC没有消灭,但其实也并不会蹦跶多久,或许第二次、第三次就需要被清除。这时候移入老年区,很明显不是一个明智的决定。所以,Survivor的存在意义就是减少被送到老年代的对象,进而减少Major GC的发生。Survivor的预筛选保证,只有经历了16次Minor GC还能再新生代中存活的对象,才会被送到老年代。
2) 为啥需要俩?
设置两个 Survivor 区最大的好处就是解决内存碎片化。我们先假设一下,Survivor 如果只有一个区域会怎样?Minor GC执行后,Eden区被清空了,存活的对象放到了Survivor区,而之前Survivor区中的对象,可能也有一些需要被清除的。问题来了,这时候我们怎么清除它们?在这种场景下,我们只能标记清除,而我们知道标记清除最大的问题就是内存碎片化,在新生代这种经常会消亡的区域,采用标记清除必然会让内存产生严重的碎片化。因为Survivor有2个区域,所以每次Minor GC,会将之前Eden区和From区中的存活对象复制到To区域。第二次 Minor GC 时,From 与 To 职责兑换,这时候会将 Eden 区和 To 区中的存活对象再复制到 From 区域,以此反复。
这种机制最大的好处就是,整个过程中,永远有一个 Survivor space 是空的,另一个非空的 Survivor space 是无碎片的
那么,Survivor 为什么不分更多块呢?比方说分成三个、四个、五个?显然,如果 Survivor 区再细分下去,每一块的空间就会比较小,容易导致 Survivor 区满,两块 Survivor 区可能是经过权衡之后的最佳方案。
3、Old 区
老年代占据着 2/3 的堆内存空间,只有在 Major GC 的时候才会进行清理,每次 GC 都会触发“Stop-The-World”。
内存越大,STW 的时间也越长,所以内存也不仅仅是越大就越好。由于复制算法在对象存活率较高的老年代会进行很多次的复制操作,效率很低,所以老年代这里采用的是标记-整理算法。
除了上述所说,在内存担保机制下,无法安置的对象会直接进到老年代,以下几种情况也会进入老年代。
1) 大对象
大对象指需要大量连续内存空间的对象,这部分对象不管是不是“朝生夕死”,都会直接进到老年代。这样做主要是为了避免在 Eden 区及 2 个 Survivor 区之间发生大量的内存复制。当你的系统有非常多“朝生夕死”的大对象时,得注意了。
2) 长期存活对象
虚拟机给每个对象定义了一个对象年龄(Age)计数器。正常情况下对象会不断的在 Survivor 的 From 区与 To 区之间移动,对象在 Survivor 区中每经历一次 Minor GC,年龄就增加1岁。当年龄增加到 15 岁时,这时候就会被转移到老年代。当然,这里的 15,JVM 也支持进行特殊设置
3) 动态对象年龄
虚拟机并不重视要求对象年龄必须到 15 岁,才会放入老年区,如果 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于等于该年龄的对象就可以直接进去老年区,无需等你“成年”。这其实有点类似于负载均衡,轮询是负载均衡的一种,保证每台机器都分得同样的请求。看似很均衡,但每台机的硬件不同,健康状况不同,我们还可以基于每台机接受的请求数,或每台机的响应时间等,来调整我们的负载均衡算法
参考出处:阿里巴巴中间件微信公众号
你的鼓励是我分享技术最大的动力!如有错误之处,请指正,不胜感激。