JVM初学之JVM的运行时数据区

什么是JVM的运行时数据区:

看下图:
在这里插入图片描述
java虚拟机在该虚拟机进程运行过程中定义了各种各样的运行时数据区。用于存储java程序运行时各种不同的数据。有些运行时数据区是在java虚拟机进程开始时就创建,进程结束时销毁,但是一些运行时数据区是在线程创建时跟着创建,线程终止时销毁。

JVM六大运行时数据区:

在这里插入图片描述

  1. 堆。
  2. 方法区。
  3. 虚拟机栈。
  4. 本地方法栈。
  5. 程序计数器。
  6. 运行时常量池。

由于运行时常量池是在方法区中分配的,所以又通常会分为五大运行时数据区。

堆(Heap):

定义:

  1. 堆是java虚拟机所管理的内存中最大的一块,在虚拟机进程启动时创建,在虚拟机进程终止时销毁,被所有的线程共享。
  2. 几乎所有的java对象实例以及数组都在堆上分配。
  3. 当堆无法满足内存分配的需求时,将会抛出OutOfMemoryError异常。

方法区(Method Area):

定义:

  1. 方法区会在虚拟机进程启动时创建,在虚拟机进程终止时销毁,被所有线程共享。
  2. 方法区主要用于存储已经被虚拟机加载的类信息、常量(存储在运行时常量池中)、静态变量、编译器编译后的代码,一些元信息等数据。
  3. 虽然java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫作Non-heap(非堆),主要目的是跟java堆区分开来。
  4. 当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。
  5. 方法区在jdk 8 中就是metaspace(元空间),在jdk6或7中就是Perm Space(永久代)。
  6. 运行时常量池在方法区中分配。

运行时常量池(Runtime constant Pool):

运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

虚拟机栈(Java Virtual Machine Stacks)

定义:

  1. 虚拟机栈是一个线程执行的区域,是一个先进后出数据结构,保存着一个java线程的调用状态。换句话说,一个java线程的运行状态是由一个虚拟机栈保存的。所以虚拟机栈是线程私有的,随着线程的创建而创建,随着线程的终止而销毁。
  2. 虚拟机栈中存储的结构是栈帧(后面讲解)。
  3. 一个方法的执行开始,会把一个栈帧压入虚拟机栈中,方法执行完成时就把栈帧弹出虚拟机栈。
  4. 假如有如下代码:
public class XuNiJiZhan {

    public static void a(){
        d();
    }
    public static void c(){

    }

    public static void d(){
        c();
    }


    public static void main(String[] args) {
        a();
    }
}

那么大概的线程就是这样:

在这里插入图片描述
解释:

  1. 从代码看出 线程先执行main方法,所以把main方法的栈帧压入虚拟机栈。
  2. 然后main方法中调用a方法,所以把a方法的栈帧压入虚拟机。
  3. 然后a方法调用d方法,所以把d方法的栈帧压入虚拟机。
  4. 然后d方法调用c方法,所以把c方法的栈帧压入虚拟机。
  5. 待得c方法执行完并返回时,c方法的栈帧会弹出,其他方法以此类推,直至虚拟机栈帧为空。

注意:

  1. 当一个线程的方法调用链太长,也就是说压入虚拟机栈的栈帧数量太大,超出一定数量时,就会报出StackOverflowError异常。
  2. 如果虚拟机可动态扩展的话,如果扩展时申请不到足够的内存,将会报出OutOfMemoryError异常。
栈帧:
  1. 每一个栈帧就对应一个一个方法的调用,可以理解为一个方法的运行空间。
  2. 每个栈帧中包括局部变量表、操作数栈(先进后出的栈结构)、动态链接(执行运行时常量池的引用),方法返回地址和一些附加信息。
  3. 局部变量表:存放了编译期可知的各种基本数据类型(int、double、char、float、boolean、short、byte、long)和对象引用、还有returnAddress(指向了一天字节码指令地址)。
  4. 对64位的double和long会占用两个局部变量空间(可以看作局部变量表的内存单位)。其余的数据类型占用一个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法的栈帧的局部变量表所需的内存空间大小是完全确定的,且在方法运行区间不会改变。
    看以下代码:
public class ZhanZhen {

    public static int testFrame(){
        int p1 = 3;
        int p2 = 5;
        int result = p1 + p2;
        return result;
    }

    public static void main(String[] args) {
        System.out.println(testFrame());
    }
}

javap反编译后代码以下字节码指令(取testFrame方法部分,对照源码看食用效果更佳):

public static int testFrame();
    descriptor: ()I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=0
         0: iconst_3  	//将int型3压入操作数栈栈顶。
         1: istore_0     //将操作数栈栈顶数值弹出并存入局部变量表中的第一个局部变量中。
         2: iconst_5   //将int型5压入操作数栈栈顶。
         3: istore_1    //将操作数栈栈顶数值弹出并存入局部变量表中的第二个局部变量中。
         4: iload_0   //将局部变量表第一个局部变量的值压入操作数栈中。
         5: iload_1  //将局部变量表第二个局部变量压的值入操作数栈中。
         6: iadd     //将栈顶的两个int型数值弹出并相加,相加后的值压入栈顶中。
         7: istore_2   //将操作数栈栈顶数值弹出并存入局部变量表中的第三个局部变量中。
         8: iload_2   //将局部变量表第三个局部变量的值压入操作数栈中。
         9: ireturn   //方法返回操作数栈栈顶的值。

在这里插入图片描述

本地方法栈(Native Method Stacks):

基本效果与虚拟机栈相似。只不过它是在线程执行本地native方法时才会使用该栈,也是线程私有的。

程序计数器(The PC Register):

  1. 占据一块较小的内存,记录着当前线程执行的虚拟机字节码指令地址。如果执行的方法时native方法,则该计数器为空。
  2. 因为在多线程环境下,cpu的调度会使得线程间切换。所以为了能在每个线程重新获取cpu时间片时能继续执行。所以每个线程会有一个记录当前执行的指令地址的程序计数器,使得线程能够继续执行。
  3. 每个线程都有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储。
  4. 此内存区域是唯一一个在java虚拟机规范中没有任何规定OutOfMemoryError情况的区域。

谢谢查看。一起进步,加油!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值