转载

憨人笔记之JVM-运行时数据区(堆空间)

在Java虚拟机运行时数据区中,堆内存是各类内存中最大的一块。堆内存的创建伴随着虚拟机的启动而创建。所有对象实例的创建都是在堆内存中。在Java虚拟机规范中明确的描述了: 所有对象实例以及数组都要在堆上分配内存空间 。垃圾回收的主要区域也是发生在堆内存中。

从内存回收的角度来看,现在的垃圾收集器基本上都采用了分代收集算法,所以,堆内存又可以分为 新生代老年代

在为新创建的对象分配内存时,为了 保证并发的安全,在堆空间中会为每个线程创建一个TLAB(本地线程分配缓冲区)区域 ,所以虽然堆内存是线程共享的,但是在内存分配的角度来看,它又能够划分出多个线程私有的TLAB区域。

当然,重点关注的还是堆内存中的新生代与老年代,首先看一下堆空间中新生代与老年代的划分比例:

憨人笔记之JVM-运行时数据区(堆空间)

新生代与老年代的空间大小比例为1:2,而在新生代中还划分了三个区域: Eden区、Surivivor From区、Surivivor To区 。它们之间的比例分别为8:1:1。下面来详细分析不同年龄代的区域。

新生代

新生代主要作用是用来放置新创建的对象,任何一个对象初次创建时都会被分配至此区域。在新生代中,98%的对象都是"朝生夕死"的,并不需要一比一来划分内存空间,而是讲内存空间划分为两个大的区域: Eden区、Survivor区(其中,Surivivor区域又被划分成两个Survivor From区,Survivor To区域) 。每一次内存分配都是使用Eden区和Survivor区域中的一块。既然划分了三个区域,那么就来说说对象在这三个区域中怎么流转的。

  1. 首先,新生对象会被分配在Eden区域,会触发一次Monir GC,此时会讲Eden区中还存活的对象复制到第一个Surivor区域(Surivor From)
  2. 继续创建对象,当Eden区内存不足以为新对象分配内存空间的时候,会再次触发Monir GC,此时就会将Eden区和第一个Surivor区域中还存活的对象复制到另外一个Survivor区中(Survivor To),然后Eden区和第一个Survivor区会被清空,以便为新对象腾出内存空间。
  3. 继续创建对象,此时第一个Survivor区域已经为空,第二个Survivor区域中已经存在了上次被复制过来的对象,那么再创建对象的时候,就只会在Eden区和第二个Survivor区中操作。
  4. 如此往复循环,当对象被复制的次数达到16次时,就会被送至老年代中。

强调的一点是, 新生代中,只会Eden区和Survivor区域其中的一块被同时使用 ,另一块Survivor区域始终为空的。

为什么要划分出两个Survivor区域呢?

上面说到,Survivor区域中,始终会有一块Survivor区域被空置,那么在有限的堆内存中,岂不是造成了内存的浪费。首先了解一下划分出Survivor区域的意义在哪里。

试想如果没有划分Survivor区域,那么Eden区每进行一次Monir GC,都会将对象直接送入老年代,老年代将会很快被填满,从而触发Major GC。Major GC的执行效率相对Monir GC来说效率慢了十倍以上。那么与有Survivor区的情况相比,Major GC触发的频率则会相对提高,严重的将会影响到程序执行及相应的速度。

如果存在Survivor区域,它可以作为一个缓冲区,当Eden区触发Monir GC后,对象不直接送往老年代,而是复制到Survivor区,相对来说就可以降低触发Major GC的机率。所以,Survivor区存在的根本意义是: 减少对象被送往老年代的频率,从而减少Major GC和Full GC的发生,Survivor可以保证在经历了16次Monir GC还能在新生代存活的对才会被送到老年代

另外一个方面就是Survivor区有效的 解决了内存的碎片化 。回顾之前说到的新生代对象流转流程。新创建的对象都被分配在Eden区,一旦该区域的内存满了,会触发Monir GC,然后对象会被从Eden送至Survivor区,往复循环。由此,问题来了,在进行Monir GC时,Eden和Survivor区都有一切存活的对象,此时将Eden取中的对象强行存放到Survivor区时,明显两部分的对象所占用的内存空间是不连续的,也就导致了内存的碎片化。

内存碎片化最终的结果就是会严重影响到程序的性能。试想当堆空间被散布的对象占据了不连续的内存,当有一个内存需求较大的对象被创建的时,堆中可能就没有足够大的连续内存空间来分配给该对象,那么就会触发Full GC。

如果将Survivor区划分成两块。在Eden区刚刚创建新对象时,经历一次Monir GC,Eden区存活的对象就被被复制移动到第一块Survivor区中,Eden被清空,等Eden区再满了的时,再次触发Monir GC,Eden区和第一块Survivor区存活的对象会被复制移动到第二块Survivor区。Eden和第一块Survivor区域被清空。

所以,为什么要划分两个Survivor区呢?

  • Survivor区作为新生代与老年代之间的缓冲区,可以降低将对象送往老年代的频率,老年代也就没那么快就被对象堆满而导致发生垃圾回收
  • Survivor中采用的复制算法,复制算法能有效的降低内存的碎片化

老年代

老年代主要存放的是经历过几次垃圾回收之后还存活的对象,刚刚在说到新生代对象的复制转移的时候,当被标记了16次的对象如果还存活着,就会被送入到老年代。

另外一种就是较大的对象,较大的对象也就被直接送入到老年代中。

不怕路歹行不怕大雨淋,心上一字敢 面对我的梦,甘愿来作憨人。 --<憨人>

原文  https://juejin.im/post/5eaff7735188256d703f3c67
正文到此结束
Loading...