这一次,终于系统的学习了JVM内存结构

  

最近在看《JAVA并发编程实践》这本书,里面涉及到了JAVA内存模型,通过JAVA内存模型顺理成章的了解到JVM内存结构,关于JVM内存结构也许大学的课堂上老师给我们讲过,也许没有,反正我对这一块有一点点的了解,但是从来没有系统的学习过,所以这一次我把《深入理解JAVA虚拟机JVM高级特性与最佳实践》、《JAVA虚拟机规范JAVA SE 8版》这两本书中关于JVM内存结构的部分都看了一遍,算是对JVM内存结构有了新的认识.JVM内存结构是指:JAVA虚拟机定义了若干种程序运行期间会使用的运行时数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁,另一些则与线程一一对应,随着线程的开始而创建,随着线程的结束而销毁。具体的运行时数据区如下图所示:

  

JVM内存结构
在Java虚拟机规范中,定义了五种运行时数据区,分别是Java堆,方法区,虚拟机栈,本地方法区,程序计数器,其中Java堆和方法区是线程共享的。接下来就具体看看这五种运行时数据区。

  

Java堆(Heap)
Java堆是所有线程共享的一块内存区域,它在虚拟机启动时就会被创建,并且单个JVM进程有且仅有一个Java堆. Java堆是用来存放对象实例及数组,也就是说我们代码中通过新关键字新出来的对象都存放在这里。所以这里也就成为了垃圾回收器的主要活动营地了,于是它就有了一个别名叫做GC堆,根据垃圾回收器的规则,我们可以对Java堆进行进一步的划分,具体Java堆内存结构如下图所示:

  

Java堆内存结构
我们可以将Java堆划分为新生代和老年代两个大模块,在新生代中,我们又可以进一步分为伊甸园空间,从幸存者空间(s0),为幸存者空间(s1),幸存者空间有一个为空,用于发生GC时存放存活对象,老年代存放的是经过多小次GC仍然存活的对象或者是一些大对象,第五代计算机就是发生在老年代。

  

上面就是Java堆的具体结构,我们也知道Java堆中的各空间大小,我们是可以动态控制的,这个在图中我也进行了简单的标注、下面我们一起来详细的了解一下这三个参数:

  

xms: JVM启动时申请的初始堆值,默认为操作系统物理内存的1/64,例如-Xms20m

  

- xmx: JVM可申请的最大的堆值,默认值为物理内存的1/4,例如-Xmx20m,我们最好将xms和- xmx设为相同值,避免每次垃圾回收完成后JVM重新分配内存;

  

厦门:设置新生代的内存大小,厦门是将NewSize与MaxNewSize设为一致,我们也可以分别设置这两个参数

  

在Java堆中会发生伯父异常,当我们的Java堆内有足够的空间去完成实例分配时,并且堆也无法扩展,将会抛出我们常见的OutOfMemoryError异常,如下图所示:

  

OutOfMemoryError异常
关于伯父异常,我还是想多说一句,网上有一道非常火的面试题:JVM堆内存溢出后,其他线程是否可继续工作吗?,我个人觉得不少回答是错误的,有兴趣的可以研究一下。

  

方法区(法区)
方法区面积(方法)与Java堆一样,是各个线程共享的内存区域,是Java虚拟机中唯二的内存共享区域。在Java虚拟机规范中是这样定义方法区的:它存储了每个类的结构信息,例如运行时常量池,字段,方法数据,构造函数和普通方法的字节码内容,还包括一些在类,实例,接口初始化时用到的特殊方法。

  

方法区在虚拟机启动的时候被创建,虽然方法区是堆的逻辑组成部分,但是简单的虚拟机实现可以选择在这个区域不实现垃圾收集与压缩,方法区在实际内存空间中可以不是连续的,对于方法区的容量,你可以是固定的,也可以随着程序的执行动态扩展,并且在不需要过多空间时自动收缩。

  

上面都是Java虚拟机中的规范,来看看具体的实现,拿我们常用的热点虚拟机来说,在JDK1.8之前,方法区也被称作为永久代,这个方法区会发生我们常见的. lang。OutOfMemoryError: PermGen空间异常,我们也可以通过启动参数来控制方法区的大小:

  

- xx: PermSize设置最小空间

  

- xx: MaxPermSize设置最大空间

  

在JDK1.8之后,热点虚拟机对方法区进行了不小的改动,彻底移除了永久代,将原来存放在永久代的数据迁移至Java堆或者Metaspace,方法区被移至到了Metaspace,字符串常量移至Java堆,换句话说就是JDK1.8开始,Metaspace也就是我们所谓的方法区,为什么要做这个改变呢?也许是基于以下两点原因:

  

由于PermGen内存经常会溢出,引发恼人的. lang。OutOfMemoryError: PermGen,因此JVM的开发者希望这一块内存可以更灵活地被管理,不要再经常出现这样的伯父

这一次,终于系统的学习了JVM内存结构