vlambda博客
学习文章列表

超详细JVM虚拟机内存区域详解

    

   

理解JVM虚拟机的内存划分,对开发或者面试都很重要的。包括理解内部结构、工作原理。

本篇会详细讲解jvm内存区域划分,并比对各个JDK版本之间差异。先看下图:

超详细JVM虚拟机内存区域详解


Java运行时数据区一般我们分为五大区域,程序计数器、虚拟机栈、本地方法栈、堆、方法区。它们的用途不同,有的是伴随着JVM启动而产生、有的是伴随着用户线程创建或销毁。

一. 程序计数器
  1. 线程上下文切换和程序计数器的关系?

  2. 线程执行本地方法和Java方法,程序计数器的异同?

  3. 程序计数器有没有可能发生OutOfMemoryError

二. Java虚拟机栈(Java Virtual Machine Stack)

早期的JVM叫做java栈。是线程私有,随着线程的创建而产生,线程结束而销毁。当线程创建时,会伴随创建栈帧(Stack Frame),对应着java方法的调用。

一个方法调用的过程就对应着入栈(压栈)到出栈(弹栈)。

可以通过-Xss参数来设置虚拟机栈为固定大小,则当线程创建的时候,就会确定大小。也可以也允许通过计算结果动态来扩容和收缩大小。

会出现以下异常:

  1. 假如线程请求分配的栈容量超过了 Java 虚拟机栈允许的最大容量,Java 虚拟机将会抛出 StackOverflowError 异常。

  2. 如果 Java 虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那 Java 虚拟机将抛出一个 OutOfMemoryError 异常。

java虚拟机栈包括:

  1. 栈帧(Stack Frame)

    Java虚拟机栈的单位元素,每次方法调用都会创建。一个方法调用的过程就对应着入栈(压栈)到出栈(弹栈)。

  2. 局部变量表(Local Variable Table)

    栈帧中会存储局部变量表,包括参数和方法内部定义的局部变量。

  3. 操作数栈(Operand Stack)

    操作数栈是一个后入先出(Last In First Out)栈,方法的执行操作在操作数栈中完成,每一个字节码指令往操作数栈进行写入和提取的过程,就是入栈和出栈的过程。

  4. 动态连接(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)内容包括:

  1. 字面量,包括:文本字符串、被声明为final的常量值、基本数据类型的值(基本数据类型的包装类比如Byte、Short、Integer、Long、Character、Boolean也实现了常量池技术,在-128到127之间用常量池)。Float 和 Double没有实现常量池技术。

  2. 符号引用,类和结构的完全限定名、字段名称和描述符、方法名称和描述符号。


方法区随着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错误区域如下:

  1. 堆内存不足,抛出的错误信息是“java.lang.OutOfMemoryError:Java heap space”。

  2. Java虚拟机栈和本地方法栈,有可能出现StackOverFlowError和OutOfMemoryError。

  3. 老版本的JVM,堆内存的永久代,也会出现java.lang.OutOfMemoryError: PermGen space。随着方法区移入元数据区,异常信息变为:java.lang.OutOfMemoryError: Metaspace。




    


推荐阅读:








点赞是最大的支持