一、前言
Java运行时数据区域划分,Java虚拟机在执行Java程序时,将其所管理的内存划分为不同的数据区域,每个区域都有特定的用途和创建销毁的时间。
其中,有些区域在虚拟机进程启动时就存在,而有些区域则是随着用户线程的启动和结束而建立和销毁。这些数据区域包括程序计数器、虚拟机栈、本地方法栈、堆、方法区等,每个区域都有其自身的特点和作用。
了解这些数据区域的使用方式和特点,可以更好地理解Java虚拟机的内存管理机制和运行原理。
JVM的内存模型划分在java1.8与java1.8之前是不一样的,本文将对Java运行时数据区域划分进行详细的介绍,理解清楚了概念之后,才能更好的进行调优分析。
二、运行时数据区域划分
首先提个概念,经常听人一会提到虚拟机的内存结构,一会又是运行时数据区域划分,其实就是一个意思。因为在《Java虚拟机规范》中用的是【运行时数据区】术语的,并没有内存结构这么一说法。内存结构只是听着更加贴切,更加形象,因此知道内存结构就是运行时数据区的意思就好了。
运行时数据区域划分
1. java1.8之前运行时数据区域划分
2. java1.8运行时数据区域划分
在Java中,JVM(Java虚拟机)的运行时数据区主要包括以下几个部分:
①. 程序计数器:
1. 它是一块很小的内存空间,几乎可以忽略不计,也是运行速度最快的存储区域。
2. 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。
3. 为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
4. 它是程序控制流的指示器,分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖这个计数器来完成。
5. 它是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
②. Java虚拟机栈:
1. 栈是运行时的单位,即栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。Java 虚拟机栈(Java Virtual Machine Stack),早期也叫 Java 栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个栈帧,对应着一次方法的调用。
2. Java虚拟机栈是线程私有的,主管Java程序的运行,它保存方法的局部变量(8种基本数据类型,对象的引用地址),部分结果,并参与方法的调用和返回。
3. 每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
2.1 栈帧的内部结构
1. 局部变量表
局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用。
2. 操作数栈
栈最典型的一个应用就是用来对表达式求值。在一个线程执行方法的过程中,实际上就是不断执行语句的过程,而归根到底就是进行计算的过程。因此可以这么说,程序中的所有计算过程都是在借助于操作数栈来完成的。
3. 动态链接
因为在方法执行的过程中有可能需要用到类中的常量,所以必须要有一个引用指向运行时常量。
4. 方法返回地址
当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。
③. 本地方法栈:
1. 与虚拟机栈所发挥的作用非常相似,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。本地方法栈也是线程私有的。
2. 允许被实现成固定或者是可动态扩展的内存大小;
3. 内存溢出方面也是相同的,如果线程请求分配的栈容量超过本地方法栈允许的最大容量抛出 StackOverflowError;
4. 本地方法是用 C 语言写的;它的具体做法是在Native Method Stack 中登记native方法,在Execution Engine执行时加载本地方法库。
④. Java堆:
1. Java 堆区在 JVM 启动时的时候即被创建,其空间大小也就确定了,是Java虚拟机所管理的内存中最大的一块。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
2. 堆内存的大小是可以调节:例如: -Xms:10m(堆起始大小) -Xmx:30m(堆最大内存大小)。一般情况可以将起始值和最大值设置为一致,这样会减少垃圾回收之后堆内存重新分配大小的次数,提高效率。
3.《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但逻辑上它应该被视为连续的;
4. 所有的线程共享 Java 堆,在这里还可以划分线程私有的缓冲区;
5. 在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。所以堆是 GC(Garbage Collection,垃圾收集器)执行垃圾回收的重点区域。
了解GC调优方面的知识点,可以参考我的文章 《JVM中常见的垃圾回收算法详解》 《JVM中的垃圾收集器详解》
java8之前与java8的区别主要是下面两个区域
⑤.方法区:
又称为“永久代”(PermGen space),但在Java 8之后,这个区域被元空间(Metaspace)所替代。它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
方法区,是一个被线程共享的内存区域。其中主要存储加载的类字节码、class/method/field 等元数据、static final 常量、static 变量、即时编译器编译后的代码等数据。另外,方法区包含了一个特殊的区域“运行时常量池”。
Java虚拟机规范中明确说明:尽管所有的方法区在逻辑上是属于堆的一部分,但对HotSpotJVM 而言,方法区还有一个别名叫做 Non-Heap(非堆),目的就是要和堆分开。
⑥. 元空间:
1. 在Java 1.8中,方法区(Method Area)被元空间(Metaspace)所取代。元空间是Java虚拟机规范对方法区的实现方式的一种要求。
2. 元空间并不在虚拟机中,而是使用本地内存。默认情况下,元空间的大小仅受本地内存限制。
3. 类元数据放入本地内存,字符串常量池和静态变量放入Java堆中,这样可以加载多少类的元数据就不再由MaxPermSize控制,而由系统的实际可用空间来决定。
三、堆内存模型
1. java1.8之前堆内存模型
Young年轻区(代)
Young区被划分为三部分,Eden区和两个大小严格相同的Survivor区,其中,Survivor区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用,在Eden区间变满的时候,GC就会将存活的对象移到空闲的Survivor区间
中,根据JVM的策略,在经过几次垃圾收集后,任然存活于Survivor的对象将被移动到Tenured区间。
Tenured年老区
Tenured区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在Young 复制转移一定的次数以后,对象就会被转移到Tenured区,一般如果系统中用了application级别的缓存,缓存中的对象往往会被转移到这一区间。
Perm永久区
Perm代主要保存class,method,filed对象,这部份的空间一般不会溢出,除非一次性加载了很多的类,不过在涉及到热部署的应用服务器的时候,有时候会遇到java.lang.OutOfMemoryError:PermGenspace的错误,造成这个错误的很大原因就有可能是每次都重新部署,但是重新部署后,类的class没有被卸载掉,这样就造成了大量的class对象保存在了perm中,这种情况下,一般重新启动应用服务器可以解决问题。
Virtual区
最大内存和初始内存的差值,就是Virtual区
2. java1.8堆内存模型
由上图可以看出,jdk1.8的内存模型是由2部分组成,年轻代+年老代。
年轻代:Eden + 2*Survivor
年老代:OldGen
在jdk1.8中变化最大的Perm区,用Metaspace(元数据空间)进行了替换。需要特别说明的是:Metaspace所占用的内存空间不是在虚拟机内部,而是在本地内存空间中,这也是与1.7的永久代最大的区别所在。
为什么要废弃1.7中的永久区?
官网给出了解释:http://openjdk.java.net/jeps/122
This is part of the JRockit and Hotspot convergence effort.
JRockit customers do not need to configure the permanent generation (since JRockit does not have a permanent generation)
and are accustomed to not configuring the permanent generation. 移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。
现实使用中,由于永久代内存经常不够用或发生内存泄露,出现异常java.lang.OutOfMemoryError:PermGen。基于此,将永久区废弃,而改用元空间,改为了使用本地内存空间。
总结:
充分理解Java虚拟机(JVM)的运行时数据区(也称为运行时内存区域)是进行JVM调优的基础。JVM的内存管理是其核心功能之一,而理解其内存布局和各个区域的功能,可以帮助我们更有效地进行性能调优。