前言
在上一篇博客中,还遗留了一个问题:JVM的内存如何分配最高效?换一种说法就是:JVM的内存是如何的分配以及回收的?通过前面两篇博客的铺垫:java虚拟机JVM–java虚拟机的结构, java虚拟机JVM–java虚拟机垃圾的回收机制详解, 本篇将从JVM的内存如何分配的以及内存是如何回收的 角度来介绍java虚拟机的内存管理,来回答这一个遗留下的的问题。
再贴一下JVM内存结构图:
学java时应该看过这么一句话:new 出来的对象都是在堆中的。事实上, 堆内存是JVM中最大的一块内存, 而GC也是针对堆内存进行回收,所以下面我们将进入堆内存, 去看看堆内存的结构。
内存的分配及回收–堆中的新生代和老年代
java堆被分为两部分, 一部分被称为新生代, 一部分称为老年代, 他们的比例通常为 1:2:
新生代
新对象被创建后一般来说都是进入新生代。新生代的特点是每次垃圾回收都需要回收大量的对象。JAVA中的对象大多数都是朝生夕死,所以很多对象创建后很快就没用了,需要被回收,所以在新生代中使用了前一篇博客介绍到的 复制算法, 因为这样是最高效的,所以我们把新生代又划分为了三个区域,一个Eden区, 两个Survivor区,比例为 8:1:1,这种比例提升了复制算法的内存使用效率:
每次对象会存在于Eden区和一个Survivor区, 当内存不够时,触发GC,然后把依然存活的对象复制到另一个空白的Survivor区, 然后直接清空其余新生代内存,然后如此循环。总有一个Survivor区是空白的。每进行一次GC,存活对象的年龄+1, 默认情况下,对象年龄达到15时,就会移动到老年代中。
老年代
老年代的特点是每次回收都只回收少量对象。当对象的年龄达到15就会存放到老年代,还有一种情况就是对象需要分配较大空间是,也会直接存放到老年代,从上面的图也可看出, 老年代的空间是要比新生代大的。老年代空间大的原因在于, 老年代的对象都是生命周期比较长的, 会被引用的时间比较久,如果GC太频繁,会严重影响效率,因为每次被回收的对象总是很少的, 确需要把整个老年代扫一次, 所以给老年代分配更多空间,减少GC回收的频次,有利于提升效率。这就是为什么新生代和老年代的比例为 1:2.
针对老年代的特点,采用的垃圾回收算法是标记整理算法, 就是将标记不回收的对象移动到一端,然后清除边界以外的内存。
老年代的GC触发,一般都伴随着新生代的GC,因为新生代触发一次GC,就可能有对象年龄大于15而移动到老年代, 导致老年代内存满了。
永久代
在上图中的方法区, 还画了一个永久代。我们都知道方法区中(即永久代)存放类及方法的信息、静态常量等(在JDK1.7以前,不包括1.7)。但是类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。所以从JDK 1.7开始, 存在于永久代的常量池移动到了 堆区中,从JDK1.8开始,就没有永久代了, 永久代被元空间的概念所替代。
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过参数来指定元空间的大小。
到此,我们最开始的疑问:JVM的内存是如何的分配以及回收的? 就讲完了~
喜欢的朋友,麻烦点个赞吧~