深入理解JVM(一)-- 基本原理

运行流程:

        java一次编译,到处执行。

        java程序经过一次编译之后得到字节码文件(class文件),在不同的平台(操作系统)上依靠不同的java虚拟机进行解释,从而转化为不同机器的机器码,最终得到执行。

程序从编译到运行所经历的步骤:

java代码通过编译得到字节码文件(class文件),通过java HelloWorld执行,java根据系统版本寻找jvm.cfg文件

其中-server KNOWN就表示名称为server的jvm可用。

通过jvm.cfg文件找到jvm.dll文件,jvm.dll文件是java虚拟机的主要实现。

接下来初始化JVM并获取JNI接口,JNI(java native implement)就是java本地接口,通过JNI接口(还常用于java与操作系统、硬件的交互)找到class文件并装载到JVM中,执行main方法。

JVM基本结构

class文件被JVM装载后,经过JVM的内存空间调配,最终由执行引擎完成class文件的执行。

内存空间

  • 方法区:

       方法区属于线程共享的内存区域,又称non-Heap(非堆),主要用于存储已被虚拟机加载的类的信息、常量、静态变量、以及编译器编译后的代码等数据,根据java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

       值得注意的是在方法区存在一个叫做运行时常量池(Runtime Constant Pool)的区域,它是每一个类或接口的常量池的运行时表现形式,在类或接口被加载到JVM后,对应的运行时常量池就被创建出来。存放编译器生成的各种字面量和符号引用,这些内容将在类加载后存放到运行时常量池中。

  • JVM堆

       java堆也是属于线程共享的内存区域,它在虚拟机启动时创建,是java虚拟机所管理的内存中最大的一块,主要用于存放对象实例,几乎所有的对象实例都在这里分配内存。Java堆是垃圾收集器管理的主要区域,因此Java堆也被称作GC堆。如果堆中没有内存完成实例分配,并且堆也无法扩展时,将会抛出OutOfMemoryError异常。

  • 程序计数器(PC寄存器)

       属于线程私有的数据区域,是一小块内存空间,主要代表当前线程所执行的字节码行号指示器。字节码解释器工作时,通过改变这个计数器的值来选取下一条需要执行的指令。

       在JVM中,多线程是通过线程轮流切换来获得CPU执行时间的,因此,在任一具体时刻,一个CPU的内核只会执行一条线程中的指令,因此,为了能够使得每个线程都在线程切换后能够恢复在切换之前的程序执行位置,每个线程都需要有自己独立的程序计数器,并且不能互相干扰,否则就会影响程序的正常执行。所以说,程序计数器是相乘私有的。

       在JVM规范中规定,如果线程执行的是非native方法,则程序计数器中保存的是当前需要执行的指令的地址;如果线程执行的是native方法,则程序计数器中的值是undefined。

       由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,所以,对于程序计数器不会发生内存溢出现象(OutOfMemory)。

  • 虚拟机栈

       属于线程私有的数据区域,与线程同时创建,总数与线程关联,代表java方法执行的内存模型。

       Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向当前方法所属的类的运行时常量池的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些额外的附加信息。当线程执行一个方法时,就会创建一个栈帧,并将栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。如果java栈空间不足了,程序会抛出StackOverflowError异常,想一想什么情况下会容易产生这个错误,对,递归,递归如果深度很深,就会执行大量的方法,方法越多java栈的占用空间越大。

  

       局部变量表:存储方法中的局部变量(包括在方法中声明的非静态变量以及函数形参)。对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用。

       操作数栈:栈最典型的一个应用就是用来对表达式求值。因此可以这么说,程序中的所有计算过程都是在借助于操作数栈来完成的。

       指向运行时常量池的引用:因为在方法执行的过程中可能需要用到类中的常量,所以必须要有一个引用指向运行时常量。

       方法返回地址:当一个方法执行完毕之后,要返回之前调用它的方法,因此必须在栈帧中保存一个方法返回地址。

       由于每个线程正在执行的方法可能不同,因此每个线程都会有一个自己的Java栈,互不干扰。

  • 本地方法栈

       本地方法栈属于线程私有的数据区域,这部分主要与虚拟机用到的Native方法相关,一般情况下,我们无需关心。

猜你喜欢

转载自blog.csdn.net/weixin_38178449/article/details/82937311