JVM内存结构之运行时数据区

Jvm结构

主要构成部分

  • 类装载器子系统;
  • 运行时数据区;
  • 执行引擎;
  • 本地方法接口;
  • 垃圾收集模块;

jvm结构图

运行时数据区

上面为jvm的结构图,而今天主要分析的是运行时数据区,也就是运行内存,运行时数据区由堆、元空间、栈、本地方法栈、程序计数器构成,而由堆、元空间可线程共享,栈、本地方法栈、程序计数器则为线程独享。

 

定义:

栈为线程私有,每个线程对应一个栈,栈是由栈帧组成,每个方法为一个栈帧,当线程结束时,栈被释放,不存在垃圾回收问题,栈的生命周期和线程一样。

组成:

  • 局部变量
    • 一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量;
    • 由若干个Slot(32位)组成,一个Slot可以存放boolean、int、char、byte,若需要存放long和double,则需要两个Slot;
  • 操作数栈
    • 操作数栈遵循FILO(先进后出)原则;
    • java虚拟机栈中的一个用于计算的临时数据存储区;
  • 动态链接
    • Class文件的常量池中存在大量的符号引用,字节码的调用指令就是使用这种符号作为参数,这些符号在程序运行时会转化为直接引用,称为动态链接;
    • 在程序运行时,子译码在内存中的位置称为动态链接;
  • 方法出口
    • 方法的出口有两种情况:一种是正常返回,执行引擎遇到返回方法的字节码指令,此为正常完成出口;另一种是执行过程中遇到了异常,这种称为异常完成出口;

演示:

下面使用一段代码演示一下数据在栈中的过程:

代码如下:

package com.xiaolinzi;

/**
 * @Author xiaolinzi
 * @Date 2020/2/14 2:24 下午
 * @Email xiaolinzi95_27@163.com
 * @Description
 */
public class JvmStackDemo {
    private int add() {
        int a = 5;
        int b = 6;
        int c = a + b;
        return c;
    }

    public static void main(String[] args) {
        JvmStackDemo demo = new JvmStackDemo();
        int add = demo.add();
    }
}

使用 javap -c 命令 对java字节码进行反汇编,生成文件如下:

 

 

Compiled from "JvmStackDemo.java"
public class com.xiaolinzi.JvmStackDemo {
  public com.xiaolinzi.JvmStackDemo();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public int add();
    Code:
       0: iconst_5           --将int类型常量5压入栈
       1: istore_1           --将int类型值存入局部变量1
       2: bipush        6    --常数6到操作数
       4: istore_2           --将int类型值存入局部变量2 
       5: iload_1            --从局部变量1中装载int类型值
       6: iload_2            --从局部变量2中装载int类型值
       7: iadd               --执行int类型的加法 
       8: istore_3           --将int类型值存入局部变量3
       9: iload_3            --从局部变量3中装载int类型值
      10: ireturn            --从方法中返回int类型的数据

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class com/xiaolinzi/JvmStackDemo
       3: dup
       4: invokespecial #3                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #4                  // Method add:()I
      12: istore_2
      13: return
}

从反汇编的文件中我们可以看出,这个线程有两个栈帧(每个方法一个栈帧),栈帧main和栈帧add,我们可以先分析一下栈帧add来分析一下数据在栈帧的的过程,通过查询jvm指令集可以将栈帧add翻译成以下描述:

    

  1. 程序计数器为0的那一行表示:将int类型的5压入操作数栈;
  2. 程序计数器为1的那一行表示:将int类型值(也就是5)从操作数栈取出来,放入局部变量1中(也就是局部变量a);
  3. 程序计数器为2的那一行表示:将常数6压入操作数栈;
  4. 程序计数器为4的那一行表示:将int类型值(也就是6)从操作数栈取出来,赋值给局部变量2中(也就是局部变量b);
  5. 程序计数器为5的那一行表示:从局部变量1(局部变量a)中将int类型值(5)压入操作数栈;
  6. 程序计数器为6的那一行表示:从局部变量2(局部变量b)中将int类型值(6)压入操作数栈;
  7. 程序计数器为7的那一行表示:在操作数栈中执行int类型的加法(使用栈顶两个数进行计算);
  8. 程序计数器为8的那一行表示:将上一步计算结果存入局部变量3(局部变量c);
  9. 程序计数器为9的那一行表示:从局部变量3(c)中取出计算结果压入操作数栈;
  10. 程序计数器为10的那一行表示:从方法中返回int类型的数据。

 

本地方法栈

用native修饰如: System.currentTimeMillis();调用的是本地方法库。

程序计数器

一个指针,指向方法区中的字节码。指向正在执行或即将执行的指令代码。如下图标出的数字即为程序计数器:

元空间

用于存放类所有的字段和方法字节码,包括一些特殊的构造函数,接口定义等。简单来说就是所有定义的方法信息都储存在该区域,包括:静态变量、常量、类信息(构造方法/接口定义)、运行时常量池。元空间称为Non-Heap(非堆),与java堆分开。

虚拟机启动时创建,用户存放实例对象,几乎所有的对象(包括常量池)都分配在堆上,当对象无法获取到空间时会抛出OutOfMemoryError异常。同时此为垃圾收集器管理的主要区域。可通过Xmx Xms 分别设置最大、最小堆。

堆分为两个部分:

  1. 新生代:

新生区分为Eden区(伊甸园区)和survivor区(幸存者区),所有类都是在Eden区被new出来。Survivor区分为0区(From区)和1区(To区)。当Eden区内存满了之后,又需要继续创建新的对象,就会发生MinorGc,Eden区就会把不用的对象进行销毁,剩余的对象移动到survivor区中的0区,如果0区满了,将会移动到1区,如果1区满了,这时候0区和1区发生角色互换,1区将对象移动到0区,每次互换分代年龄都会+1,当分代年龄达到15时候,就会移动到老年代。

  1. 老年代:

新生区的对象经过多次回收到大老年代,若老年代内存满了之后,就会触发FullGc,这时候就会Stop THe World,虚拟机除了垃圾收集的线程都会被挂起,进行垃圾回收。也就是我们平常遇到的程序卡顿。

 

 

 

 

 

 

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值