理解了 1+2的过程,就理解了Java虚拟机

在面试的时候,在问到关于JVM相关的问题,会发现不少的面试者都是机械的在记忆,稍一细问就戛然而止。属于死记硬背型的,估计是看书里记个大概的概念或者图,并没有理解含义。

实际上这块内容,看概念的时候能对照一个简单的程序分析,可以更好的理解。下面咱们开始。

市面上常见的JVM书籍里,关于JVM的体系结构,一般划分成以下几个部分:

  1. 类加载器

  2. 程序计数器(Program Counter Register,简称PC Register)

  3. Java 虚拟机栈(Java Virtual Machine Stacks)

  4. 堆(Heap)

  5. 方法区(Method Area)

  6. 运行时常量池(Run-time Constant Pool)

  7. 本地方法栈(Native Method Stack)

  8. 栈帧(Stack Frame)

  9. 执行引擎(Execution Engine)

其中2~7项,又称为运行时数据区,毕竟这些东西只有JVM 起来才会创建。这个分类,基本都是参照 Java 虚拟机规范。

如果干巴巴的记概念没啥意思,吃个饭可能就忘了。接下来用 1+2这个程序来试着理解它。

我们来看个初学Java 编程的时候都基本都写过的,类似 Hello World的程序。

public class HelloWorld {
    public static void main(String[] args) {
        int a = 1;
        int b = 2;
        int c = a + b;
    }
}

javac 编译之后,再用javap -verbose HelloWorld 来观察一下, 你会看到类似下面的输出内容:

public class HelloWorld
  minor version: 0
  major version: 55
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #2     // HelloWorld
  super_class: #3    // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #3.#12         // java/lang/Object."<init>":()V
   #2 = Class              #13            // HelloWorld
   #3 = Class              #14            // java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               main
   #9 = Utf8               ([Ljava/lang/String;)V
  #10 = Utf8               SourceFile
  #11 = Utf8               HelloWorld.java
  #12 = NameAndType        #4:#5          // "<init>":()V
  #13 = Utf8               HelloWorld
  #14 = Utf8               java/lang/Object
{
  public HelloWorld();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0


  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: iconst_1
         1: istore_1
         2: iconst_2
         3: istore_2
         4: iload_1
         5: iload_2
         6: iadd
         7: istore_3
         8: return
      LineNumberTable:
        line 3: 0
        line 4: 2
        line 5: 4
        line 6: 8
}

好嘞。咱们都知道,上面这些就是Java的字节码。有了上面这个输出的内容,你把自己想像成虚拟机,来运行它,就理解了 Java 虚拟机里各个部分了。

首先,这部分内容,要执行,一定得先读到内存里,负载读这些内容的,就是虚拟机的类加载器

加载进来的其实是个二进制流,然后呢,需要把它整理成对应格式的内容才方便使用嘛。比如这个类叫啥名字,继承了谁,都有什么方法,方法名字叫啥,内容是什么这些东西要找个地方放着。放哪好呢?方法区就是干这个的。

所谓的运行时常量池也是方法区里的一块区域。往上看Constant Pool 在运行时会被解析成 Run-time Constant Pool。如果涉及到对其他类的引用等等,会在加载之后再链接的时候,把这里面的符号引用转化成直接引用。

另外一些部分呢?概括来讲就是Java虚拟机栈,就是咱们常说的,是用来执行方法里的具体内容的。这一部分其实可以这样理解。Java 虚拟机,和我们真实的物理机类似,都会把程序提供的指令执行,只不过虚拟机是一个提供了一套有限指令集的软件。物理机基本都是基于寄存器执行,而 Java 虚拟机的对于指令的执行实现是基于的。

既然是栈,那栈里要放点什么?没错,是栈帧,英文是 Frames,就是咱们在使用 IDE debug 的时候看到的那一层一层的内容。

每个方法调用的时候,都会出现一帧,每一帧也是个结构,方法执行用到的东西都在里面。比如在 Debug 的时候,一般都会看到每个变量和值, 这些变量称为本地变量(local variables),在上面的输出内容里也有stack=2, locals=4, args_size=1 我们看到locals就是本地变量,args_size是方法参数的长度,还有一个就是操作数(stack),数值是栈的最大深度。

每个class 的任意一个方法里,都会有 frame ,它们都有自己的 local variables 本地变量表, 自己的operand stack 操作数栈,以及到run-time constant pool 运行时常量池的引用。当然,也可以有一些扩展信息,例如debug info。

那具体上面简单的一个 1+2 这个操作,对应到 jvm 指令有这些:

         0: iconst_1
         1: istore_1
         2: iconst_2
         3: istore_2
         4: iload_1
         5: iload_2
         6: iadd
         7: istore_3
         8: return

具体当前执行到第几条指令,需要有个标识,这个活儿让程序计数器给干了。这小子一直指向下一条即将执行的指令。基本栈的实现,上面的指令大意是把常量1赋值给第一个变量,常量2赋值给第二个变量,之后,变量一入栈,变量二入栈,执行iadd操作的时候,这两个数据出栈,完成求和,再赋值给变量3,入栈,再返回。下次咱们细说JVM指令的时候,再详细说说。这些指令的执行,当然离不开执行引擎

因为不需要执行Native方法,所以我们一般不用本地方法栈,这是给类似JNI这些本地方法实现准备的。

你看,观察了1+2的过程,基本Java 虚拟机的结构是不是就理解了?:-) 如果还是记不住的话,你可以这样想啊,Java 的世界里,经常会说到。那栈用来存啥呢?想想你 debug 时候看到的那一层层的, 然后再想想今天的1+2的执行,应该就齐了。

看点别的

IO 多路复用是什么意思?

怎样计算一个Java对象大小?这儿有几种方法~

问题诊断神器BTrace

Java七武器系列霸王枪 -- 线程状态分析 jstack

官方App,GitHub 能随身携带了

Tomcat的异步Servlet实现原理

阿里问题定位神器 Arthas 的骚操作,定位线上BUG,超给力

监控诊断工具 Arthas 源码原理分析

自学三个月,成功面试大厂路线图

Java虚拟机的显微镜 Serviceability Agent

怎样回答技术面试题?

怎样了解你的线程在干嘛?

从0到1的微服务实践和优化思路

怎样设计一个好用的短链接服务?

更多常见问题,请关注公众号,在菜单「常见问题」中查看。

源码|实战|成长|职场

这里是「Tomcat那些事儿」

请留下你的足迹

我们一起「终身成长」

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java虚拟机JVM)是Java程序的运行环境,它负责将Java字节码转换为可执行的机器码并执行。深入理解Java虚拟机涉及了解JVM的工作原理、内存管理、垃圾回收、类加载机制等方面的知识。 首先,JVM的工作原理是通过解释器或即时编译器将Java字节码转换为机器码并执行。解释器逐条执行字节码指令,而即时编译器将字节码转换为本地机器码,以提高程序的执行效率。 其次,内存管理是JVM的重要任务之一。JVM将内存分为不同的区域,包括堆、栈、方法区等。堆用于存储对象实例,栈用于存储局部变量和方法调用信息,方法区用于存储类的信息。JVM通过垃圾回收机制自动回收不再使用的对象,释放内存空间。 此外,类加载机制也是深入理解JVM的关键内容之一。类加载是将类的字节码加载到内存中,并进行验证、准备、解析等操作。类加载器负责查找并加载类的字节码,而类加载器之间存在着父子关系,形成了类加载器层次结构。 还有其他一些与性能优化、调优相关的内容,如即时编译器的优化技术、垃圾回收算法的选择等,也是深入理解Java虚拟机的重要方面。 总的来说,深入理解Java虚拟机需要对JVM的工作原理、内存管理、垃圾回收、类加载机制以及性能优化等方面有较深入的了解。掌握这些知识可以帮助开发人员编写出更高效、稳定的Java程序。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值