了解JVM运行时的内存分配
前言
上文中,在介绍运行时数据区域中的 JAVA 堆时,提到了 JVM 中的堆,一般分为三大部分:新生代、老年代、永久代,本文将进一步了解运行时的内存分配情况。
正文
1.新生代
主要用来存放新生(new)的对象。一般占据堆的 1/3 空间。由于频繁创建对象,所以新生代会频繁的触发 MinorGC 进行垃圾回收。
新生代又分为 Eden(伊甸园)、SurvivorFrom、SurvivorTo三个区。
- Eden区:Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。
- SurvivorTo:保留了一次MinorGC过程中的幸存者。
- SurvivorFrom:上一次GC的幸存者,作为这一次GC的被扫描者。
当Eden区内存不够的时候就会触发MinorGC,对新生代区进行一次垃圾回收,当触发GC后,JVM会将Eden和其中一个Survivor的对象全部复制到另外一个Survivor中(例如从from 到 to),即采用的复制算法(垃圾回收算法中的一种),在复制过程中,如果对象达到了老生代的要求就会被复制到老生代,复制到Servivor的每个对象的年龄加一,然后清空Eden和之前的Servivor区域。从这里就可以看出在任意时刻一定会存在一个Survivor区域处于空闲状态。
2.老年代:
主要存放应用程序中生命周期长的内存对象。老年代的对象比较稳定,所以MajorGC不会频繁执行。
在进行MajorGC前一般都先进行了一次MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次MajorGC进行垃圾回收腾出空间。
MajorGC采用标记—清除算法(垃圾回收算法中的一种):首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。MajorGC的耗时比较长,因为要扫描再回收。MajorGC会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。
当老年代也满了装不下的时候,就会抛出OOM(Out of Memory)异常。
3.永久代
在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 Native memory, 字符串池和类的静态变量放入Java堆中. 这样可以加载多少类的元数据就不再由MaxPermSize控制, 而由系统的实际可用空间来控制.
总结
前边巴拉巴拉讲了一堆,终于到了总结部分,首先我们了解到堆内存主要分为三部分:新生代、老年代、永久代。
- 新生代存放新生对象,又被分为 Eden 区、2块 Survivor 区,频繁发生MinorGC,采用的是 复制算法。
- Minor GC 每次收集后, Eden 区和1块 Survivor 区都被清空;
- 老年代存放 MinorGC 存留下来的对象,发生MajorGC,采用的是 标记-清除算法。
- 永久代在java8中被元空间所替代,划分到本地内存,不再使用 JVM 内存,受本地内存限制。
上边提到的垃圾回收算法有,复制算法、标记-清除算法,未提到的还有 标记-整理算法、分代收集算法(下一篇讲解算法部分),当前商业虚拟机(如HotSpot)的垃圾收集都采用 分代收集算法,接下来将以 分代收集算法 描述对象的从创建到 GC 的过程。
1、一个人(对象)出来(new 出来)后会在Eden Space(伊甸园)无忧无虑的生活,直到GC到来打破了他们平静的生活。GC会逐一问清楚每个对象的情况,有没有钱(此对象的引用)啊,因为GC想赚钱呀,有钱的才可以敲诈嘛。然后富人就会进入Survivor Space(幸存者区),穷人的就直接kill掉。
2、并不是进入Survivor Space(幸存者区)后就保证人身是安全的,但至少可以活段时间。GC会定期(可以自定义)会对这些人进行敲诈,亿万富翁每次都给钱,GC很满意,就让其进入了Genured Gen(养老区)。万元户经不住几次敲诈就没钱了,GC看没有啥价值啦,就直接kill掉了。
3、进入到养老区的人基本就可以保证人身安全啦,但是亿万富豪有的也会挥霍成穷光蛋,只要钱没了,GC还是kill掉。
分区的目的:新生区由于对象产生的比较多并且大都是朝生夕灭的,所以直接采用标记-清理算法。而养老区生命力很强,则采用复制算法,针对不同情况使用不同算法。