1.JVM内存结构

  • Java虚拟机在执行Java程序过程中会把他所管理内存区域划分为若干个不同的数据区域。分别为堆、虚拟机栈、本地方法栈、程序计数器、方法区。
  • 这些区域都有各自的用途,以及创建和销毁的时间,堆和方法区是线程共享的因此他们随着虚拟机进程的启动而存在,本地方法栈、虚拟机栈、程序计数器属于线程私有,因此他们随着线程的创建和结束而创建和销毁。
  • JVM内存结构如图

2. 各个内存区域详解

2.1 程序计数器

程序计数器是当前线程所执行的字节码的行号指示器,字节码解释器的工作就是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成。
主要特点为:

  • 程序计数器是JVM运行数据区中非常小的一块内存区域,属于线程私有,是当前线程所执行的字节码的行号指示器。
  • 字节码解释器通过改变程序计数器的值来选取下一条需要执行的字节码指令。
  • 每条线程都有自己独立的程序计数器。
  • 如果执行带有native方法时,计数器的值为空(undefined)。
  • 这部分内存区域是唯一一个不会抛出任务OutOfMemoryError的情况。

2.2 堆

  • 堆内存是JVM所管理的内存中最大的一块。线程共享,在虚拟机启动时就会被创建,并且仅有一个,主要是存放对象实例和数组。
  • 堆内存可以位于物理上不连续,但是逻辑上必须连续。如果堆中没有内存完成实例的分配,并且堆也无法拓展时,则抛出OutOfMemoryError Java heap space。
  • 堆内部会划分多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。
  • Jave堆主要用来存放对象实例和数组,也就是说我们代码抓中通过new关键字创建出来的对象都会放在这里,因此堆也就是垃圾回收器的主要营地了,所以堆还有一个别名称之为“GC堆”。
  • 如下图:JDK8之前,堆内存是由新生代(Young generation)+老年代(Old generation)+持久代(Permanent generation)组成,JDK8之后,持久代被废弃,而是使用了本地内存的“元空间”,一个对象被创建以后首先被放到新生代的Eden内存中,其中持久代/元空间都是用来存放对象的党发、变量等元数据信息

2.3 虚拟机栈

Java有结构体内存对齐么 java内存结构和jvm内存结构_JVM五大内存区域


Java 栈也被称之为虚拟机栈,也就是我们常说的栈,跟C语言的数据段中的栈类似。事实上,Java栈是Java方法执行的内存模型。为什么会这么说呢?接下来解释下原因:

Java栈中存放的是一个个栈帧,每个栈帧都是一个调用的方法,在栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向当前方法所属的类到的运行时常量池的引用(Reference to runtine constant pool)、方法返回地址(Return Address)和动态链接。当线程执行一个方法时,就会随之创建一个对应的栈帧,并将创建的栈帧压栈。当方法执行完毕之后,便会进行出栈。因此可以得知,当前执行的方法所对应的栈帧必定位于Java栈的顶部。讲到这里,大家一定就明白了为什么使用递归方法容易造成栈内存溢出了吧。

局部变量表:用来存储方法中的局部变量(包括在方法中声明的非静态变量以及函数形参)。对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用。局部变量表的大小在编译器就可以确定其大小了,因此在程序执行期间局部变量表的大小是不会改变的。
操作数栈:想必学过数据结构中的栈的朋友想必对表达式求值问题不会陌生,栈最典型的一个应用就是用来对表达式求值。想想一个线程执行方法的过程中,实际上就是不断执行语句的过程,而归根到底就是进行计算的过程。因此可以这么说,程序中的所有计算过程都是在借助于操作数栈来完成的。
指向运行时常量池的引用:因为在方法执行的过程中有可能需要用到类的常量,所以必须要有一个引用指向运行时常量。
方法返回地址:当一个方法执行完毕后,要返回之前调用他的地方,因此栈中必须保存一个方法的返回地址。

2.4 方法区

Java有结构体内存对齐么 java内存结构和jvm内存结构_JVM五大内存区域_02


方法区在JVM中是非常重要的一个区域,也是线程共享的,在方法区内,存储了每个类的信息(包括类的版本、字段的描述信息、方法描述信息、接口和父类等描述信息、class文件常量池机静态常量池)、静态常量、字符串常量池、运行时常量池等。

方法区中有个非常重要的部分就是运行时常量池,它是每一个类和接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就会被创建出来。当然并非Class文件常量池中内容才会进入运行时常量池,在运行期间也可将新的常量放入运行时常量池,比如String和Inter方法。

在JVM规范中,没有强制要求方法区必须实现垃圾回收。很多人习惯将方法区称为“永久代”,是因为HotSpot虚拟机以永久代来实现方法区,从而JVM的垃圾收集器可以像管理堆区一样管理这部分区域,从而不需要专门为这部分设计垃圾回收机制。不过自从JDK7之后,HotSpot虚拟机便将运行时常量池从永久代移除了,JDK8之后,这一块区域为元空间(Meta Space)

2.5 本地方法栈

本地方法栈与Java栈的作用和原理非常相似。区别只不过是Java栈是为执行Java方法服务的,而本地方法栈则是为执行本地方法(Native Method)服务的。在JVM规范中,并没有对本地方发展的具体实现方法以及数据结构作强制规定,虚拟机可以自由实现它。在HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一。