• 24小时服务热线:400-088-1128

当前位置 南顺网络>> 知识拓展

JVM系列分析- 内存模型

JVM的内存模型是java语言绕不开的一个话题。要进行java的性能调优,首先就要了解其内存模型。在诸多的面试笔试中,这也是很多面试官会考察的内容。

本篇文章简单介绍JVM内存模型的概念,结构和对应的参数设置,并根据具体的代码案例讲解一下内存分配情况。

1.JVM内存结构

图片描述

由图可以较为清楚的看到,JVM的内存空间分为3大部分,分别是堆内存、方法区和栈内存。其中栈内存可以再细分为java虚拟机栈和本地方法栈。堆内存可以划分为新生代和老年代。新生代中还可以再次划分为Eden区、From Survivor区和To Survivor区。划分出来的各个区,分别保存不同的数据,并且在一般情况下,其采取的内存回收策略也是不同的。

2.JVM内存区域功能

图片描述

通过上图可以看到JVM运行时的数据区:

2.1 堆内存

堆内存是JVM内存模型中最大的一块区域,被所有线程共享,是在JVM启动时候进行创建的。几乎所有的对象的空间分配都是在堆内存上进行分配的。

考虑到JVM的内存回收机制,堆内存可以划分为新生代和老年代两个区域(默认新生代与老年代的空间大小为1:2)。新生代可以再划分为Eden区、From Survivor区和To Survivor区(三者比例为8:1:1)。几乎所有的新对象的创建都是在Eden区进行的。在垃圾回收(GC)过程中,Eden中的活跃对象会被转移到Survivor区,当再到达一定的年龄(经历过的Minor GC的次数),会被转移到老年代中。

堆可以处于物理上不连续的内存空间中,但是需要满足逻辑上的连续。在实现时,可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的

2.2 方法区

方法区又被成为永久代(HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法区),同样也是被所有的线程共享的。该区域中主要保存类的信息、静态变量、常量和即时编译器编译后的代码等数据。

JDK1.7中,已经把放在永久代的字符串常量池移到堆中。JDK1.8撤销永久代,引入元空间。

方法区不需要连续的内存,可以选择固定大小或者可扩展。并且还可以选择不实现垃圾收集。相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入了方法区就如永久代的名字一样“永久”存在了。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区域的回收确实是有必要的。当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

2.3 程序计数器

程序计数器是用于标识当前线程执行的字节码文件的行号指示器。多线程情况下,每个线程都具有各自独立的程序计数器,所以该区域是非线程共享的内存区域。

当执行java方法时候,计数器中保存的是字节码文件的行号;当执行Native方法时,计数器的值为空。

2.4 Java栈

java栈是线程私有的内存区域,其中存储的是栈帧。对于一个java的方法,其开始调用,则会创建一个栈帧,保存到java栈中;当该方法执行完成,则对应的是出栈的过程。

如果方法methodOne方法调用了methodTwo,那么methodOne就会先入栈创建一个栈桢,接着methodTwo再入栈成为栈顶(假设没有其他的方法执行),methodTwo执行完先出栈,接着methodOne执行完出栈。

每个栈帧中,保存执行对应方法所必须的信息,主要包含局部变量表,操作栈,动态连接和方法出口等信息。

局部变量表中,可以存放的数据有8种基本数据类型(boolean,type,char,short,int,float,long,double),对象引用和returnAddress类型。其中long和double因为是64位,会占用两个局部变量的空间。

在Java虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度(比如递归调用的时候),将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展(当前大部分的Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。

2.5 本地方法栈

本地方法栈也是线程私有的内存区域,与java栈比较相似,不同之处在于该区域主要是保存Native方法相关的数据。Native方法是非Java语言编写的方法。

与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。

3.JVM内存参数设置

图片描述

  • -Xms设置堆的最小空间大小。

  • -Xmx设置堆的最大空间大小。

  • -XX:NewSize设置新生代最小空间大小。

  • -XX:MaxNewSize设置新生代最大空间大小。

  • -XX:PermSize设置永久代最小空间大小。

  • -XX:MaxPermSize设置永久代最大空间大小。

  • -Xss设置每个线程的堆栈大小。

    4.代码案例讲解

    使用如下的一段简单代码案例,说明一下各个内存区域中保存的信息。
    图片描述

  • 堆中进行对象的空间分配,比如Hashtable对象和String对象。

  • 方法中保存类信息(TestJVM),方法(put方法,print方法,test方法)和静态变量(NUM)

  • java栈中保存对象引用(score)