JVM从入门到精通(五): Java运行时数据区和常用指令

本文深入探讨Java运行时数据区的构成,包括程序计数器、直接内存、方法区、栈空间、堆空间及方法区等关键概念。同时,解析JVM指令如i=i++与i=++i的执行过程,阐述类的生命周期,并对比PermSpace与MetaSpace的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JVM Runtime Data Area and JVM Instructions

Java运行时数据区以及JVM指令

在这里插入图片描述
i=i++结果为8
i=++i结果为9

一个class的生命周期

在这里插入图片描述以下面的规范为准:
在这里插入图片描述

运行时数据区的构成

PC:peogram counter 程序计数器
DirectMemory:直接内存,JVM可以直接访问内核空间的内存(OS管理的内存),零拷贝(不需要拷贝),NIO用到了,提高效率
MethodArea:方法区,里面有常量池
在这里插入图片描述

PC 程序计数器

在这里插入图片描述

JVM 栈空间

在每一个线程创建的时候,线程会有自己独立的JVM栈空间,栈中存放的是栈帧
在这里插入图片描述

堆空间

所有线程共享同一个堆空间。
堆空间是用来存放所有类实例数组空间分配运行时数据区
在这里插入图片描述

方法区

所有线程共享同一个方法区。
方法区用来存放 per-class structors
在这里插入图片描述Perm Space 与 Meta Space容易混淆,他们只是Method Area的两的不同版本的实现。
在这里插入图片描述

运行时常量池

在这里插入图片描述
在这里插入图片描述

栈帧
  • 局部变量
  • 操作数栈
  • 动态链接
    指向运行时常量池里面的符号链接,看有没有解析,如果没有解析,就动态解析,如果已经解析了,就拿过来使用。例如,A方法要调用B方法,B方法在哪儿呢?就要去常量池里面找。
  • 返回值地址
    A方法调用了B方法,如果有返回值,要记录返回值返回到那个地方,也就是记录继续执行的位置。

在这里插入图片描述

回到面试题
  • 理解局部变量表
  • 理解操作数栈
  • 理解一些常用的指令
package com.mashibing.jvm.c4_RuntimeDataAreaAndInstructionSet;

public class TestIPulsPlus {
    public static void main(String[] args) {
        int i = 8;
        i = i++;
//        i = ++i;
        System.out.println(i);
    }
}

i=i++ 生成的指令
在这里插入图片描述执行过程分析
在这里插入图片描述

i=++i 生成的指令
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述在这里插入图片描述
下面这个 i 因为超过了 127,所以用的是sipush,而不是上图中的bipush

在这里插入图片描述
为什么我们可以在非static方法中使用this?因为this在局部变量表中是已经存在的。
(局部变量表中,0位置是this,1位置是k,2位置是i)
在这里插入图片描述

在这里插入图片描述
下面这个例子,之前有一道面试题:DCL 单例为什么要加 volitile?因为你看下面的第一条指令,我们知道,刚new出来对象是半初始化的对象,只是赋一个默认值,而involespecial才是调用构造方法,给变量赋初始值,而这两条指令之间是可能会发生指令重排的。

在这里插入图片描述

返回值

在这里插入图片描述

递归的调用

下面是m方法的执行,没有把main方法放上来
在这个3层递归中,使用到的是3个栈

另外,我们看到指令前面的数字 0,1,2,5,6,…,没有3,4的原因,是2指令的字节数比较多,占用了后面的字节数
在这里插入图片描述

总结

总结

invokeXXX指令
invokestatic

调用一个静态方法
在这里插入图片描述

invokevirtual

new一个对象,调用一个非静态方法
自带多态:new 的是哪个对象,调用的就是哪个对象的方法
在这里插入图片描述

invokespecial

调用实例方法;对超类、私有和实例初始化方法调用的特殊处理

可以直接定位的,不需要多态的方法

  • private方法
  • 构造方法

final方法不是invokespecial的,它是invokevirtual的。

在这里插入图片描述

invokeinterface

调用接口方法
在这里插入图片描述

invokedynamic

JVM最难的指令
invokedynamic是lambda表达式或者反射或者其他动态语言scala kotlin,或者CGLib ASM,动态产生的class,会用到的指令

每一个lambda表达式都有一个自己的内部类,java没有纯粹的函数。匿名内部类每次都是动态产生的。

package com.mashibing.jvm.c4_RuntimeDataAreaAndInstructionSet;

public class T05_InvokeDynamic {
    public static void main(String[] args) {


        I i = C::n;
        I i2 = C::n;
        I i3 = C::n;
        I i4 = () -> {
            C.n();
        };
        System.out.println(i.getClass());
        System.out.println(i2.getClass());
        System.out.println(i3.getClass());

        for(;;) {I j = C::n;} //MethodArea <1.8 Perm Space (FGC不回收)
    }

    @FunctionalInterface
    public interface I {
        void m();
    }

    public static class C {
        static void n() {
            System.out.println("hello");
        }
    }
}

关于Lambda表达式的一个坑
如果你用Lambda表达式写出了这样的代码:

for(;;) {I j = C::n;} //MethodArea <1.8 Perm Space (FGC不回收)

在1.8之前有一个巨大的bug,就是你在里面产生了很多对象,但是Perm Space在FGC的时候是不会回收的。

Perm Space和Meta Space的区别?
  1. Perm Space (<1.8)
    字符串常量位于PermSpace
    FGC不会清理
    大小启动的时候指定,不能变
  2. Meta Space (>=1.8)
    字符串常量位于堆
    会触发FGC清理
    不设定的话,最大就是物理内存

思考:
如何证明1.7字符串常量位于Perm,而1.8位于Heap?
提示:结合GC, 一直创建字符串常量,观察堆,和Metaspace

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值