超详细JVM虚拟机内存区域详解
理解JVM虚拟机的内存划分,对开发或者面试都很重要的。包括理解内部结构、工作原理。
本篇会详细讲解jvm内存区域划分,并比对各个JDK版本之间差异。先看下图:
Java运行时数据区一般我们分为五大区域,程序计数器、虚拟机栈、本地方法栈、堆、方法区。它们的用途不同,有的是伴随着JVM启动而产生、有的是伴随着用户线程创建或销毁。
一. 程序计数器
线程上下文切换和程序计数器的关系?
线程执行本地方法和Java方法,程序计数器的异同?
程序计数器有没有可能发生OutOfMemoryError?
二. Java虚拟机栈(Java Virtual Machine Stack)
早期的JVM叫做java栈。是线程私有,随着线程的创建而产生,线程结束而销毁。当线程创建时,会伴随创建栈帧(Stack Frame),对应着java方法的调用。
一个方法调用的过程就对应着入栈(压栈)到出栈(弹栈)。
可以通过-Xss参数来设置虚拟机栈为固定大小,则当线程创建的时候,就会确定大小。也可以也允许通过计算结果动态来扩容和收缩大小。
会出现以下异常:
假如线程请求分配的栈容量超过了 Java 虚拟机栈允许的最大容量,Java 虚拟机将会抛出 StackOverflowError 异常。
如果 Java 虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那 Java 虚拟机将抛出一个 OutOfMemoryError 异常。
java虚拟机栈包括:
栈帧(Stack Frame)
Java虚拟机栈的单位元素,每次方法调用都会创建。一个方法调用的过程就对应着入栈(压栈)到出栈(弹栈)。
局部变量表(Local Variable Table)
栈帧中会存储局部变量表,包括参数和方法内部定义的局部变量。
操作数栈(Operand Stack)
操作数栈是一个后入先出(Last In First Out)栈,方法的执行操作在操作数栈中完成,每一个字节码指令往操作数栈进行写入和提取的过程,就是入栈和出栈的过程。
动态连接(Dynamic Linking)
每个栈帧都包含一个指运行时常量池(JVM 运行时数据区域)中该栈帧所属性方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。方法的重写,java中的多态,需要动态连接找到具体的方法去执行,JVM会通过多种方式优化查找过程,包括通过缓存。
三. 堆(Heap)
堆是JVM最大的一块区域,是线程共享的区域,在JVM启动时创建,存储Java实例对象,通过-Xmx指定堆最大空间。
堆内存分为新生代 (Young) 和老年代 (Old) ,新生代 (Young) 又被划分为三个区域:Eden、From Survivor、To Survivor。
堆内存是逻辑上连续,物理上可以不连续的空间。堆空间可以动态分配,如果扩展无法申请足够内存,会抛出OutOfMemoryError。
四. 本地方法栈(Native Method Stacks)
本地方法栈和Java 虚拟机栈发挥的作用类似,区别是,Java 虚拟机栈为运行Java方法服务,本地方法栈是为Native方法服务。在Hotspot JVM,两者是同一块区域。
五. 方法区(Method Area)
该区域被线程共享。早期的Jdk版本作用是类信息、常量、字符串常量、类静态变量、即时编译器编译后的代码等数据。
现在JVM规范中,描述为Non-Heap(非堆),与堆做区分。包括运行时常量池(Runtime Constant Pool)。
运行时常量池(Runtime Constant Pool)内容包括:
字面量,包括:文本字符串、被声明为final的常量值、基本数据类型的值(基本数据类型的包装类比如Byte、Short、Integer、Long、Character、Boolean也实现了常量池技术,在-128到127之间用常量池)。Float 和 Double没有实现常量池技术。
符号引用,类和结构的完全限定名、字段名称和描述符、方法名称和描述符号。
方法区随着JDK的升级有如下变化:
1.JDK6
Klass 元数据信息
每个类的运行时常量池(字段、方法、类、接口等符号引用)、编译后的代码
静态字段(无论是否有final)在 instanceKlass 末尾(位于 PermGen 内)
oop(Ordinary Object Pointer(普通对象指针)) 其实就是 Class 对象实例
全局字符串常量池 StringTable,本质上就是个 Hashtable
符号引用(类型指针是 SymbolKlass)
2.JDK7
Klass 元数据信息
每个类的运行时常量池(字段、方法、类、接口等符号引用)、编译后的代码
静态字段从 instanceKlass 末尾移动到了 java.lang.Class 对象(oop)的末尾(位于 Java Heap 内)
oop 与全局字符串常量池移到 Java Heap 上
符号引用被移动到 Native Heap 中
3.JDK8
移除永久代
Klass 元数据信息
每个类的运行时常量池、编译后的代码移到了另一块与堆不相连的本地内存 -- 元空间(Metaspace)
总结:有可能发生OutOfMemoryError错误区域如下:
堆内存不足,抛出的错误信息是“java.lang.OutOfMemoryError:Java heap space”。
Java虚拟机栈和本地方法栈,有可能出现StackOverFlowError和OutOfMemoryError。
老版本的JVM,堆内存的永久代,也会出现java.lang.OutOfMemoryError: PermGen space。随着方法区移入元数据区,异常信息变为:java.lang.OutOfMemoryError: Metaspace。
点赞是最大的支持