• JVM学习四


    运行时数据区(Runtime Data Area)

    参考jvms(java virtual machine specification)和jls(java language specification)

    从一则面试题说起:

    public class TestIPulsPlus {
        public static void main(String[] args) {
            int i = 8;
            //此方式输出8
            i = i++;
            //此方式输出9
           // i = ++i;
            System.out.println(i);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述
    在这里插入图片描述
    PC(Program Counter 程序计数器):
    1、存放指令位置
    2、虚拟机的运行,类似于这样的循环:
    while(not end) {
    取PC中的位置,找到对应位置的指令
    执行该指令:
    PC++
    }
    在这里插入图片描述

    JVMStack【线程独有的】:
    在这里插入图片描述

    java运行中写的每个线程对应的一个栈,每个方法对应一个栈帧
    native method stacks:指的是本地方法(C/C++),调用的JNI
    Direct Memory(直接内存):属于NIO的内容,java 1.4新增的
    ,为了增加IO的效率。归操作系统管【从JVM里面可以直接访问操作系统
    管理的内存的,用户空间直接访问内核空间的内存】,实现了0拷贝

    Heap【线程共享的】:
    在这里插入图片描述
    Method Area【线程共享的】:
    存储每一个类的结构
    在这里插入图片描述
    方法区实现【1.8以前和以后是两个不同的实现】:
    1、perm Space(<1.8)永久区
    字符串常量位于PermSpace
    FGC不会清理
    大小启动的时候指定,不能变

    2、Meta Space(>=1.8) 元空间
    字符串常量位于堆
    会触发FGC清理
    Runtime Constant Pool:class文件里面的常量池,运行的时候会扔到这里
    不设定的话,最大就是物理内存
    在这里插入图片描述
    如何证明1.7字符串常量位于perm,而1.8位于Heap?
    结合GC,一直创建字符串常量,观察堆和MetaSpace的情况,看溢出时的报错也可以。

    总结:
    VMS:virtual machine stack
    nms:native method stack
    在这里插入图片描述

    栈帧【每个方法对应一个栈帧】

    dynamic linking:参照jvms2.6.3
    一个线程有自己的线程栈,每个线程栈里面装着一个个的栈帧,每个栈帧里面有个dynamic linking,这个linking指向的是
    运行时常量池里面的符号链接,看是否解析了,如果解析了则直接用,如果没解析则动态解析。如a方法调用了b方法,b的内容得去运行时常量池里面去找。
    在这里插入图片描述

    在这里插入图片描述
    return address(返回值地址):a()->b(),方法a调用了方法b,b方法的返回值放什么地方。

    回到面试题目:

    public class TestIPulsPlus {
        public static void main(String[] args) {
            int i = 8;
            //此方式输出8
            i = i++;
            //此方式输出9
           // i = ++i;
            System.out.println(i);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    查看它的指令:
    首先看局部变量表:里面有两个变量分别是args,和i
    在这里插入图片描述

    在这里插入图片描述
    首先执行bipush 8,将它当成一个int类型扔到栈里面
    在这里插入图片描述
    istore_1
    把栈顶元素出栈,放在下标值为1的局部变量表里面。
    至此i=8完成
    在这里插入图片描述
    iload_1
    将局部变量表1位置上的数压栈,就是8压栈
    在这里插入图片描述
    iinc 1 by 1; 将局部变量表为1的位置上面的数加1,此时变成9
    istore_1:将操作数栈中的8拿回去赋值到局部变量表1位置所对应的值,此时将8
    赋值回去局部变量表1对应的值修改为8
    因此最后的返回结果为8。

    指令集补充: 1、基于栈的指令集(JVM)
    2、基于寄存器的指令集(汇编涉及)
    hotspot的local variable table 类似于寄存器

    栈的执行流程

    Java虚拟机栈(Java栈)每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧,对应着一次次的Java方法调用。是线程私有的。
    操作数栈:每一个独立的栈帧中除了包含局部变量表以外,还包含一个后进先出的操作数栈,也可以称为表达式栈。操作数栈,在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈和出栈,操作数栈是为了存计算的临时中间结果的,而局部遍历表一般是固定的,用来放方法的参数和方法内部定义的变量

    在这里插入图片描述
    当大于127的时候采用sipush
    在这里插入图片描述
    注意下图的this,只要不是static方法的情况,这个方法的调用是需要对象的,然后这个对象
    被扔进了局部变量表里面了
    在这里插入图片描述
    如图首先局部变量表0-3位置上对应的变量依次是this,a,b,c。
    依次把3压栈
    依次吧4压栈
    然后再把3+4的结果压栈
    然后弹出栈顶元素 7
    压在c里面
    在这里插入图片描述

    public class Hello_02 {
        public static void main(String[] args) {
            Hello_02 h = new Hello_02();
            h.m1();
        }
    
        public void m1() {
            int i = 200;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    m1字节码
    在这里插入图片描述
    main方法字节码:
    new完对象之后会把这个对象的地址压栈,对象此时是一个默认值
    dup命令是将栈里面的那个地址再复制一份,现在栈里面就有两个元素指向了那个对象了,
    再调用invokespecial执行默认构造方法,会将栈顶的元素给弹出来,做计算,这个过程中
    对象里面的值就不再是默认值而是初始值了,并且也调用了初始化方法。
    此时栈里面只有一个对象地址了。然后执行astore_1,将栈顶元素赋值给本地变量表的1位置上
    。aload1是将h入栈,然后调用invokevirtual将这个h弹出栈顶,然后调用m1方法。
    注意指令重排是:调用invokespecial和astore_1进行指令重排
    在这里插入图片描述
    main本地变量表:
    在这里插入图片描述
    由于main调用了m1方法,当main要调用m1方法的时候,main停止执行,等m1栈帧执行弹出完毕再执行
    在这里插入图片描述
    递归调用:
    递归的阶乘。
    在这里插入图片描述
    这里我们来分析上图的m方法的字节码:
    本地变量表是0位置this,1位置n
    1、iload_1 首先将n=3入栈
    2、iconst_1,将常量值1压栈
    3、if_icmpne 如果不等于1【注意这里是load_1和iconst_1都弹出去才能比较】,
    就跳到第七条指令去执行 iload_1又将3压栈,再执行aload_0,将this压栈,iload_1
    又将3压栈,iconst_1,将常量1压栈,isub再将两数相减。再调用m方法【注意此时栈中包含元素
    为3 this 2 】,执行m方法的时候需要两个参数,第一个参数是n就是栈顶的2,还要把this传给它,因为
    调用的时候还需要知道是哪个对象在调用【此时弹出this 和 2】,此时n的值变成2。
    直到当n=1的时候则返回,执行m(2)的后续逻辑,最后再执行m3的后续逻辑【imul】执行相乘的逻辑。

    小总结

    clinit :静态语句块的初始化【没有显示的调用看不出来】
    init:构造方法初始化

    invoke指令:

    1. InvokeStatic:调用静态方法
    public class T01_InvokeStatic {
        public static void main(String[] args) {
            m();
        }
    
        public static void m() {}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. InvokeVirtual:自带多态的根据对象来调用方法,看压栈的是哪个对象
    public class T02_InvokeVirtual {
        public static void main(String[] args) {
            new T02_InvokeVirtual().m();
        }
    
        public void m() {}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. InvokeInterface
        public static void main(String[] args) {
        //作为interface来调用的
            List<String> list = new ArrayList<String>();
            list.add("hello");
    		//这个是invokevirtual
            ArrayList<String> list2 = new ArrayList<>();
            list2.add("hello2");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. InovkeSpecial
      可以直接定位,不需要多态的方法
      private 方法 , 构造方法
      注意final也是invokevirtual的,不是invokespecial的。
    public class T03_InvokeSpecial {
        public static void main(String[] args) {
            T03_InvokeSpecial t = new T03_InvokeSpecial();
            t.m();
            t.n();
        }
    
        public final void m() {}
        private void n() {}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    1. InvokeDynamic
      JVM最难的指令
      lambda表达式或者反射或者其他动态语言scala kotlin,或者CGLib ,ASM,
      运行时动态产生的class,会用到的指令
      类似于一个函数式的指针,C::n指向的时n这个函数
    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不回收)小于1.8会产生OOM
        }
    
        @FunctionalInterface
        public interface I {
            void m();
        }
    
        public static class C {
            static void n() {
                System.out.println("hello");
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
  • 相关阅读:
    基本的Dos命令
    哈达玛矩阵与克罗内克积
    Java 八股文能不背吗?Java 面试都只是背答案吗?
    恒生电子面试经验
    怎样吃透一个java项目?
    POSTGRESQL中的groupping函数详解
    -级数求和-
    MySQL数据类型
    LeetCode每日一题(963. Minimum Area Rectangle II)
    使用 Socks5 来劫持 HTTPS(TCP-TLS) 之旅
  • 原文地址:https://blog.csdn.net/lsdstone/article/details/126772907