Java运行时数据区



前言

《The Java Virtual Machine Specification》官方文档,规定 jvm规范。
jvm规范代表了jvm的标准,任何jvm的实现都要遵守该标准,所以阅读jvm规范对理解jvm是有意义的。


一、运行时数据区是什么?

  • 栈帧Frame:一个方法执行就是一个栈帧,分为:1局部变量表 2.操作数栈 3.动态连接 4.返回地址
  • 堆heap:存放实例对象的位置,分为年轻代、老年代两部分
  • 方法区Method Area: 1.永久代perm space(Java1.8之前) 2.元空间meta space(java1.8开始)
  • 本地方法栈Native Method Stack: 调用Java代码里的native方法 JNI调用
  • 程序计数器: 记录代码执行位置,每个线程有自己的计数器
  • 堆外内存: 直接内存 1.这一部分不受gc管理,避免垃圾回收经常移动位置。2.零拷贝也是基于这部分实现

指令集设计:
基于栈的指令集:jvm操作数栈
基于寄存器的指令集:1.汇编 2.hostpot的局部变量表类似于寄存器

字符串常量:1.8之前位于永久代 1.8开始位于堆中

在这里插入图片描述

二、方法如何执行

1.代码

代码如下:

public class Cat {
    
    private int count=0;
    
    public void test1(){
        count=count++;
        System.out.println("test1");
        test2();
    }

    public void test2(){
        System.out.println("test2");
    }
}

2.执行轨迹

假如我们现在调用:new Cat().test1()方法

  1. 首先加载Cat.class,经过验证 解析 初始化等步骤进入元空间
  2. new Cat()在我们的堆区域,开辟一个空间存放对象。
  3. 调用test1()方法,在栈区域开辟一个栈空间。
  4. 压入test1()方法栈帧
  5. 里面赋值等操作在test1()方法栈帧的局部变量表和操作数栈中进行操作
  6. 当调用test2()方法的时候压入test2()栈帧,调用打印方法
  7. 调用结束test2()栈帧,弹出栈帧,返回返回地址规定的地方
  8. 接下来test1()栈帧,弹出栈帧
  9. 方法调用结束

我们看下字节码如何实现的:

public class Cat {

    private int count=0;

    public void test1(){
        count=count++;
        System.out.println("test1");
        test2();
    }

    public void test2(){
        System.out.println("test2");
    }

    public static void main(String[] args) {
        new Cat().test1();
    }
}

我们看这个方法入口 new Cat().test1();

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: new           #8  开辟内存空间   // class com/amarsoft/code/spring/pojo/Cat
         3: dup           # 这个对象压入操作数栈
         4: invokespecial #9 调用构造初始化方法,这个时候把init方法压入栈帧 // Method "<init>":()V
         7: invokevirtual #10 #调用test1方法,这个时候把test1方法压入栈帧   // Method test1:()V
        10: return #返回
      LineNumberTable:
        line 19: 0
        line 20: 10

public void test1(){
        count=count++;
        System.out.println("test1");
        test2();
    }

这里是test1方法的调用

public void test1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=5, locals=1, args_size=1
         0: aload_0  #加载局部变量表第0个位置的值  其实就是this本身
         1: aload_0
         2: dup     # this本身这个对象压入操作数栈
         3: getfield      #2  获取字段count            // Field count:I
         6: dup_x1   #字段count 压入操作数栈
         7: iconst_1  #压入操作数栈常量1
         8: iadd  #执行相加
         9: putfield      #2  count放入实例字段变量                // Field count:I
        12: putfield      #2                  // Field count:I
        15: getstatic     #3 #访问类字段System.out对象  // Field java/lang/System.out:Ljava/io/PrintStream;
        18: ldc           #4    加载"test1"到操作数栈              // String test1
        20: invokevirtual #5  调用对象的实例方法    // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        23: aload_0  #加载局部变量表第0个位置的值  其实就是this本身
        24: invokevirtual #6  调用对象的实例方法   // Method test2:()V
        27: return #返回
      LineNumberTable:
        line 9: 0
        line 10: 15
        line 11: 23
        line 12: 27


可以参考一下字节码指令解读:指令码


总结

提示:这里对文章进行总结:

这里给大家说了一下运行时数据区包括什么:

线程共享的:堆 方法区 直接内存

线程不共享的:栈 程序计数器 本地方法栈

后面给大家说了代码是如何执行的:

new 一个对象开始,在堆内放入一个对象,调用方法,就是在栈区域开辟一个线程独有的空间,每个方法的调用,就是不断的压栈,出栈而已。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值