Jvm原理解析

作者: 奔跑吧李博 | 来源:发表于2021-03-11 22:13 被阅读0次

什么是Jvm?

  • 翻译机

首先,你可以把Java虚拟机看作一个抽象的计算机,它有各种指令集和各种运行时数据区域。它就是一台翻译机器,将生成的字节码,翻译成各个操作系统能懂的指令。

  • 跨语言、跨平台

JVM是JRE(Java Runtime Environment)的一个组成部分。
虽然叫Java虚拟机,但其实在它之上运行的语言可不仅仅是Java,还包括Kotlin、Groovy等。

Java虚拟机执行流程分为两大部分,分别是编译时环境和运行时环境,当一个Java文件经过Java编译器编译后会生成Class文件,这个Class文件会由Java虚拟机来进行处理。Java虚拟机与Java语言没有什么必然的联系,它只与特定的二进制文件:Class文件有关。因此无论任何语言只要能编译成Class文件,就可以被Java虚拟机识别并执行。

而Jvm可以跑在Linux、Windows、MacOS上,是可以跨平台的。

Class文件格式是怎样的?

示例如图:


可以看到ClassFile具有很强的描述能力,将类文件的各种常量,接口,字段,方法,属性都记录下来了,其中u4、u2表示“基本数据类型”。

  • u1:1字节,无符号类型。
  • u2:2字节,无符号类型。
  • u4:4字节,无符号类型。
  • u8:8字节,无符号类型。
Jvm如何读取java语言编写的程序?

Java虚拟机内部,有一个叫做类加载器的子系统,这个子系统用来在运行时根据需要加载类。详情可见我的另一篇类加载器原理的文章。

Jvm的运行过程:

Jvm核心——运行时数据区:

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为为若干不同的数据区域,包括方法区、堆区、虚拟机栈、本地方法区、程序计数器。

  • 程序计数器(非线程共享)

指当前线程正在执行的字节码指令的地址。
我们写一个类,创建一个实例对象,调用一个方法。我们build之后,编译成这个类的class文件。


javap反编译这个文件之后,得到这个方法的偏移量,这个就是程序计数器记录字节码的地址。

为什么Jvm需要一个程序计数器的东西?
因为程序执行到方法的某一行时,由于多线程的执行,另一个线程调度了,这个线程要休息了,就突然停在这一行代码。等系统调度再度执行这个方法时,不会又从第一行执行啊,肯定是接着之前的行号执行,那么这个记录就是程序计数器来做的事情。

  • 虚拟机栈(非线程共享)

常常听到说,Jvm是基于栈的,那为什么会这么说呢?为什么程序会按照我们所写的执行顺序去执行呢?因为Jvm会将一个线程中要执行的一个个方法按照顺序,压入到虚拟机栈中。

用于存储当前线程运行方法所需的数据,指令,返回地址。虚拟机栈是线程私有的,各自线程有各个线程的虚拟机栈。

栈帧:

存放的内容包括局部变量表、操作数栈、动态连接、完成出口,对应记录了方法内的局部变量,操作数,多态,方法返回等信息。

虚拟机栈也有大小限制,用-Xss表示。


总结:一个应用会有多个线程,一个线程对应一个虚拟机栈,一个虚拟机栈中会有多个栈帧,一个栈帧即为一个要执行的方法。用栈的弹出方式,所以能让程序有序地执行。就像压入子弹一样,按照顺序一颗一颗地打出去。

  • 本地方法栈(非线程共享)

本地方法栈保存的是native方法的信息。当一个Jvm创建的线程调用native方法后,Jvm不再为其在虚拟机中创建栈帧,只是简单地动态链接并直接调用native方法。

  • 方法区(线程共享)

存放包括类信息、常量、静态变量、即时编译期编译后的代码。(线程共享)

  • 堆区(线程共享)

几乎所有的对象实例都是存储在堆区中的,数组也是存储在堆区中。

为什么设计上将变量存储分为2块区域呢?因为对象、数组这些需要大块空间的边变量,需要频繁创建和回收,而常量、静态变量这些很少回收且不便于回收,所以划分了2块区域各自存储。

堆区也有大小限制,用-Xmx表示。比如声明2G大小堆区:

org.gradle.jvmargs=-Xmx2048m -noverify
Jvm运行时数据区过程总结:

1.申请内存:先要去将堆、栈、方法区等所需的空间大小申请出来。
2.类加载: 将class类中的常量、静态变量、对象的引用等加载进方法区。
3.入栈:将类中的方法按顺序压入虚拟机栈。

  1. 将new的对象存入堆区,栈帧中对象的引用指向堆中的对象地址。

相关文章

网友评论

    本文标题:Jvm原理解析

    本文链接:https://www.haomeiwen.com/subject/umzoqltx.html