Java虚拟机-运行时数据区

简图

image
线程私有区域:虚拟机栈、本地方法栈和程序计数器
线程共享区域:堆和方法区(元空间区)

线程私有区域

程序计数器

作用:读取程序计数器的值来选取下一条字节码指令,并完成分支、循环、跳转、异常处理、线程回复等
  • 程序计数器是一个很小的内容空间,可以看做是当前线程所执行的字节码的行号指示器。在Java虚拟机的概念模型中,字节码解释器工作是通过改变这个计时器的值来选取下一条需要执行的字节码指令,他是程序控制流的指示器,分支、循环、跳转、异常处理、线程回复等基础功能都需要依赖这个计数器来完成
  • 每个线程都有一个独立的线程计数器,各条线程之间计时器互不影响,独立存储。生命周期与线程的生命周期保持一致
  • 如果线程正在执行的是一个Java方法,程序计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地方法(调用C类库执行)程序计数器为空(undefined)
  • 它是唯一一个在《Java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域

编译如下代码后使用javap反编译工具查看字节码文件Test.class

public class Test {
    public static void main(String[] args) {
        int a = 1;
        int b = 2;
        System.out.println(a + b);
    }
}
//    指令:javap -v Test.class

在反编译结果中找到main()方法

image

途图中红框内的数字为指令地址,执行引擎通过读取指令地址完成方法的运行

虚拟机栈

作用:在Java程序运行期间,它保存方法的局部变量、部分结果,并参与方法的调用和返回
  • 每个方法执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息,对应着一个栈帧在虚拟机栈中从入栈到出栈的过程
  • 属线程私有区域
  • 生命周期与线程生命周期相同
  • 此区域规定了两类异常状况:如果线程请求的栈深度大于虚拟机的所允许的深度,将抛出StackOverFlowError异常;如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常
  • 使用-Xss设置栈的大小;栈的大小直接决定了函数调用的最大可达深度
  • 栈的深度通常只有几百K,但是它决定了函数调用深度;如果程序使用了很深的递归函数,而栈空间非常小,此时很有可能发生栈溢出(java.lang.StackOverflowError)
  • 对于多线程任务,应缩小栈,反之
虚拟机栈的特点:
  1. 是一种快速有效的分配存储方式,访问速度仅次于程序计数器
  2. JVM直接对Java栈的操作只有两个:

    1. 方法的执行对应入栈
    2. 方法结束执行对应出栈
  3. 虚拟机栈不存在垃圾回收问题
调整栈的大小

通过在Idea中设置-Xss参数,观察代码的执行情况

  • 设置栈大小为128k,当栈深度为967时抛出栈溢出异常

image

  • 设置栈大小为256k,当栈深度为2250时抛出栈溢出异常

    image

栈帧的内部结构
  • 局部变量表

    • 定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括八种基本数据类型、对象引用,以及returnAddress类型
    • 局部变量表建立在线程的栈上,是线程私有数据,因此不存在数据安全问题
    • 局部变量表所需的容量大小是编译期确定下来的,在运行期间不会改变局部变量表的大小

image

  • 操作数栈

    在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈和出栈。使用javap指令可查看操作数栈的深度

    image

  • 动态链接
每一个栈帧内部都包含一个指向运行时常量池中该帧所属方法的引用。包含这个引用的目的就是为了当前方法的代码能够实现 动态链接。动态链接的作用是将符号引用转换为调用方法的直接引用。
image
  • 方法返回地址

    存放调用该方法的的PC寄存器的值 ;程序正常退出时返回该值,异常退出时不返回该值
  • 一些附加信息

本地方法栈

本地方法栈用于管理本地方法的调用(c或c++),会抛出 StackOverFlowErrorOutOfMemoryError

线程共享区域

  • 一个Java进程对应一个JVM实例,一个JVM实例存在一个运行时数据区,多个线程共享堆和方法区
  • JVM启动时堆被创建且空间大小以确定,是JVM 管理的最大的一个内存区域

    • 堆的大小是可调节的
  • 堆可以处于物理不连续,但在逻辑上应该是视为连续的
  • 所有的线程共享堆,还可以划分出线程私有缓冲区(TLAB)
  • 大多数对象实例和数组都分配在堆上
  • 数组和对象可能永远不会存储在栈上,栈帧中保存引用,引用指向堆中的位置
  • 方法执行结束后,堆中的对象不会立即移除,在垃圾收集的时候才会被移除
  • 此区域是GC的主要工作区域
细分堆内存
  • Java7及之前

    • 年轻代
    • 老年代
    • 永久代
  • Java8及之后

    • 年轻代

      • Eden Space、Survivor0、Survivor1
    • 老年代 :Old Gen
    • 元空间:Metaspace
设置堆空间的的大小

-Xms :memory start; 用来设置堆(年轻代+老年代)的初始内存大小

-Xmx:memory max;用来设置堆(年轻代+老年代)的最大内存大小

-XX:+PrintGCDetails 输出GC信息

年轻代和老年代

image

  • 使用-XX:NewRatio调整年轻代和老年代的比例,例如:-XX:NewRatio=3,表示年轻代占1份,老年代占3份;对应四分之一和四分之三
  • 默认情况下-XX:NewRatio=2
  • 年轻代中的分配比例为8:1:1,使用-XX:-UseAdaptiveSizePolicy关闭自适应内存分配策略
  • 使用-Xn设置年轻代的空间

方法区

参考资料

  1. 【Java虚拟机探究】5.常用JVM配置参数-栈的分配参数
  2. 深入理解Java虚拟机-周志明(第二版)

你可能感兴趣的:(java,jvm,jps,虚拟机)