从零开始,浅入深出JVM---01(基础篇)

前言
最近开始补jvm知识。作为一名Java开发,JVM是我们必须要学习了解的基础,也是通向高级及更高层次的必修课。但JVM的体系非常庞大,且术语非常多,所以初学者对此非常的头疼。

JVM 基础知识

其实一个java程序,首先会经过javac编译成.class文件,然后jvm会将其加载到方法区,执行引擎会执行这些字节码。执行时,会翻译成操作系统相 关的函数。JVM 作为 .class 文件的翻译存在,输入字节码,调用操作系统函数。

过程:Java 文件->java编译器>字节码->JVM->机器码

事实上JVM就是java虚拟机,它能识别 .class 后缀的文件,并且能够解析它的指令,最终调用操作系统上的函数,完成我们想要的操作。

JVM 知识模块

JVM所涉及的知识体系非常庞大,大致分为以下模块:内存结构、GC垃圾回收、类的加载、调优、类文件结构、执行引擎、监控等。但所有的知识体系都离不开内存。JVM其实就是一个虚拟化的操作系统,所以跟我们的操作系统有很大一部分相似。编译后的代码也是会运行再jvm虚拟中,并且这块也是由虚拟机管理,我们不需要关系内存的释放,它实现了自动垃圾回收机制。所以内存结构处于 JVM 中核心位置。也是属于我们入门 JVM 学习的最好的选择。

JVM 的运行时数据区域

运行时数据区的定义:其实JVM在运行java程序的时候会将它所管理的内存划分为若干个不同的数据区域。在JVM中,JVM内存主要分为:堆、程序计数器、方法区、虚拟机栈和本地方法栈等。同时按照线程的关系也可以分为:线程私有区域(一个线程拥有单独的一份内存区域)、线程共享区域(被所有线程共享,且只有一份)、直接内存(这个不是运行时数据去的一部分,但是会被频繁的使用。指的数没有被JVM虚拟化的部分)。
在这里插入图片描述

JAVA 方法的运行与虚拟机栈

虚拟机栈是线程运行 java 方法所需的数据,指令、返回地址。其实在我们实际的代码中,一个线程是可以运行多个方法的。 比如:

public class TestStack {
    public static void main(String[] args) {
        A();
    }
    private static void A(){
        System.out.println("A被调用执行");
        B();
    }
    private static void B(){
        System.out.println("B被调用执行");
        C();
    }
    private static void C(){
        System.out.println("C被调用执行");
    }
}

这段代码很简单,就是起一个 main 方法,在 main 方法运行中调用 A 方法,A 方法中调用 B 方法,B 方法中运行 C 方法。 在执行每个方法的时候都会打包成一个栈帧。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200722114754128.p

最后 main 方法运行完了,main 方法这个栈帧就 出栈了。 这个就是 Java 方法运行对虚拟机栈的一个影响。虚拟机栈就是用来存储线程运行方法中的数据的。而每一个方法对应一个栈帧。

虚拟机栈

栈的数据结构是先进后出,虚拟机栈的作用是在 JVM 运行过程中存储当前线程运行方法所需的数据,指令、返回地址。虚拟机栈是基于线程的,哪怕只有一个 main() 方法,也是以线程的方式运行的。在线程的生命周期中,参与计算的数据会频繁地入栈和出栈,栈的生 命周期是和线程一样的。 虚拟机栈的大小缺省为 1M,可用参数 –Xss 调整大小,例如-Xss250k。
官方文档地址:爪哇官方文档
在这里插入图片描述
栈帧:java程序的方法被调用的时候,都会创建一个栈帧(俗称的压 栈),当方法执行完后再出栈。栈帧大体包含四个区域(局部变量表、操作数栈、动态连接、返回地址)。

  • 局部变量表:顾名思义就是局部变量的表,用于存放我们的局部变量的(方法中的变量)。首先它是一个 32 位的长度,主要存放我们的 Java 的八大基础数据 类型,一般 32 位就可以存放下,如果是 64 位的就使用高低位占用两个也可以存放下,如果是局部的一些对象,比如我们的 Object 对象,只需要存放它的一个引用地址即可。

  • 操作数据栈:存放 java 方法执行的操作数的,它就是一个栈,先进后出的栈结构,操作数栈,就是用来操作的,操作的的元素可以是任意的 java 数据类型,所以一个方法刚刚开始的时候,这个方法的操作数栈就是空的。操作数栈本质上是 JVM 执行引擎的一个工作区,也就是方法在执行,才会对操作数栈进行操作,如果代码不不执行,操作数栈其实就是空的。

  • 动态连接:Java 语言特性多态。

  • 返回地址:正常返回(调用程序计数器中的地址作为返回)、异常的话(通过异常处理器表<非栈帧中的>来确定)同时,虚拟机栈这个内存也不是无限大,它有大小限制,默认情况下是 1M。 如果我们不断的往虚拟机栈中入栈帧,但是就是不出栈的话,那么这个虚拟机栈就会爆掉。
    在这里插入图片描述
    最后说了那么多也是云里雾里就看看代码里面是怎么一回事。
    下面是一个普通的代码:

package com.li.work01;


public class DemoStack {
    public static void main(String[] args) {
       new DemoStack().work();

    }


    public int work(){
        int a=3;
        int b=4;
        int sum=10*(a+b);
        return sum;
    }

}

然后我们用javap -c DemoStack .class反汇编命令就能得到如下代码

 public com.li.work01.DemoStack();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class com/li/work01/DemoStack
       3: dup
       4: invokespecial #3                  // Method "<init>":()V
       7: invokevirtual #4                  // Method work:()I
      10: pop
      11: return

  public int work();
    Code:
       0: iconst_3
       1: istore_1
       2: iconst_4
       3: istore_2
       4: bipush        10
       6: iload_1
       7: iload_2
       8: iadd
       9: imul
      10: istore_3
      11: iload_3
      12: ireturn
}

通过以上反汇编后(字节码指令)的就能清楚看出work方法里面:首先执行引擎会通过iconst_3字节码指令将常量3加载到操作数栈,接着通过istore_1操作数栈取出来存储到局部变量表里面的1号位置。同样iconst_4istore_2进行同样的操作。然后bipush指令将常量值10推送至栈顶。应为这里有括号所以会先执行括号里面的内容,通过iload_1iload_2局部变量表中的值取出放入操作数栈中,然后执行iadd指令进行加法指令(所有二元算数指令会从操作数栈中取出顶部的两个变量进行计算,计算结果自动加入到栈中)接着将常量10压入到栈中进行imul乘法运算。完成后需要通过istore_3指令将数值从操作数栈存储到局部变量表中,接着通过iload_3将一个局部变量加载到操作数栈中,最后通过ireturn返回(不管我们方法是否定义了返回值都会调用该指令,只是当我们定义了返回值时,首先会通过iload指令加载局部变量表的值并返回给调用者)。以上就是栈帧的运行原理了。

后续也会跟大家分享后面的章节(加油搬砖,少年们!)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值