Java虚拟机结构

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Sophisticated_/article/details/89364418

Java虚拟机规范描述的是一种抽象化的虚拟机行为,而不是一种任何一种广泛使用虚拟机的实现。要实现一台java虚拟机,只需要正确的读取class文件中每一条字节码的指令,并且能正确执行这些指令所蕴含的操作即可。虚拟机规范中没有明确描述的实现细节,都不应成为虚拟机设计者发挥创造性的牵绊,设计者完全自主决定规范中不曾描述的虚拟机内部细节。如:运行时数据区内存布局,选用哪种GC算法,是否对字节码指令进行内部优化(如使用即时编译器把字节码编译为机器码)。

class文件格式

编译后被java虚拟机所执行的代码使用一种平台中立的二进制格式表示,这种格式称class文件格式。class文件格式精确的定义了类与接口的表示形式,包括平台相关的目标文件格式中的惯例,例如字节序等。

数据类型

Java虚拟机可以操作的数据类型可以分为两类:原始类型引用类型。与之对应,也存在原始值引用值,它们可用于变量赋值、参数传递、方法返回、运算操作。
Java虚拟机是直接支持对象,这里的对象可以是动态分配的某个类的实例,也可以是某个数组。虚拟机中使用reference类型来表示对某个对象的引用,reference类型可以想象为指向对象的指针,每一个对象都可能存在多个指向它的引用,对象的操作、传递和检查都通过引用它的reference类型的数据进行

原始类型与值

Java虚拟机支持的原始数据类型包括数值类型boolean类型returnAddress类型

  • 数值类型:byte、short、int、long、char、float、double
  • boolean类型:值为布尔值true和false,java虚拟机中没有boolean值专用的字节码指令,boolean值在编译后都是用java虚拟机中的int数据类型代替
  • returnAddress类型:指向某个操作码的指针,此操作码与虚拟机指令对应,在虚拟机支持的原始类型中,只有returnAddress是不能直接与Java语言的数据类型对应

引用类型与值

Java虚拟机有三种引用类型:类类型数组类型接口类型。这些引用类型的值分别指向动态创建的类实例、数组实例、实现了某个接口的类实例或数组实例。

引用类型中还有一个特殊的值:null,一个引用不指向任何对象时,它的值就为null。一个为null的引用,并不具备任何实际运行期类型,但可以转型为任意的引用类型。引用类型默认值就为nul

运行时数据区

在这里插入图片描述

pc寄存器

Java虚拟机支持多条线程同时执行,每一个线程都有自己的pc寄存器,任意时刻,一个java虚拟机线程都只会执行一个方法的代码,这个正在被线程执行的方法称为该线程的当前方法。寄存器存储指令相关的现场信息,由于 CPU 时间片轮限制,众多线程在并发执行过程中,任何一个确定的时刻,一个处理器或者多核处理器中的一个内核,只会执行某个线程中的一条指令。这样必然导致经常中断或恢复,如何保证分毫无差呢?每个线程在创建后,都会产生自己的程序计数器和枝帧,程序计数器用来存放执行指令的偏移量和行号指示器等,线程执行或恢复都要依赖程序计数器。

如果这个方法不是native,那pc寄存器就保存Java虚拟机正在执行的字节码指令的地址,如果该方法是native,那pc寄存器值为undefined。pc寄存器至少应能保存一个returnAddress类型的数据或一个与平台相关的本地指针的值

Java虚拟机栈

每一个java虚拟机线程都有自己私有的Java虚拟机栈,这个栈与线程同时创建,用于存储栈帧,作用是存储局部变量与一些尚未计算好的结果,并且它在方法调用和返回中也扮演很重要的角色
在这里插入图片描述

Java堆

堆是可供各个线程共享的运行时内存区域,也是供所有类实例和数组对象分配内存的区域。堆在虚拟机启动时创建,存储了被GC所管理的各种对象,这些受管理的对象无需也无法显示的销毁

GC过程:堆分成两大块 新生代和老年代。对象产生之初在新生代 , 步入暮年时进入老年代 , 但是老年代也接纳在新生代无法容纳的超大对象。新生代= 1 个 Eden 区+ 2 个Survivor 区。绝大部分对象在 Eden 区生成 ,当 Eden 区装填满的时候 , 会触发 YoungGarbage Collection , 即YGC。垃圾回收的时候 , 在 Eden 区实现清除策略 , 没有被引用的对象则直接回收。依然存活的对象会被移送到 Survivor 区 , 这个区真是名副其实的存在。 Survivor 区分为 so 和 Sl 两块内存空间 , 送到哪块空间呢?每次 YGC 的时候, 它们将存活的对象复制到未使用的那块空间,然后将当前正在使用的空间完全清除 , 交换两块空间的使用状态。如果 YGC 要移送的对象大于 Survivor 区容量的上限 ,贝lj直接移交给老年代。假如一些没有进取心的对象以为可以一直在新生代的Survivor 区交换来交换去,那就错了。每个对象都有一个计数器,每次 YGC 都会加l 。-XX:MaxTenuringThreshold 参数能配置计数器的值到达某个阐值的时候 , 对象从新生代晋升至老年代。如果该参数配置为 I ,那么从新生代的 Eden 区直接移至老年代。默认值是 15 , 可以在 Survivor 区交换 14 次之后 , 晋升至老年代。

元空间

元空间的前身 Perm 区已经被淘汰,区别于永久代,元空间在本地内存中分配。在 JDK8 里,Perm 区中的所有内容中字符串常量移至堆内存,其他内容包括类元信息、字段、静态属性、方法、常量等都移动至元空间内
在这里插入图片描述

  • object 类元信息、静态属性 System.out、整型常量 10000000 等存在元空间里
  • 常量池中的 String, 其实际对象是被保存在堆内存中的
本地方法栈

java虚拟机的实现可能会用到传统的栈来支持native方法,这个栈就是本地方法栈,当java虚拟机使用其他语言(C语言)来实现指令解释器时,也可以使用本地方法栈

栈帧

栈帧是用来存放数据和部分过程结果的数据结构,也用来处理动态链接、方法返回值和异常分派。栈帧随方法调用创建,随方法结束销毁,栈帧的存储空间由创建它的线程分配在java虚拟机栈,每一个栈帧都有自己的本地变量表、操作数栈和指向当前方法所属类的运行时常量池的引用。栈帧是线程本地私有数据,不可能在一个栈帧中引用另一个线程的栈帧

局部变量表

局部变量表是存放方法参数和局部变量的区域。相对于类属性变量的准备阶段和初始化阶段来说,局部变量没有准备阶段,必须显式初始化。如果是非静态方法,则在 index[0]位置上存储的是方法所属对象的实例引用,随后存储的是参数和局部变量。

操作数栈

操作数栈是一个初始状态为空的桶式结构栈。在方法执行过程中,会有各种指令往栈中写人和提取信息。 JVM 的执行引擎是基于栈的执行擎,其中的栈指的就是操作栈。字节码指令集的定义都是基于栈类型的,栈的深度在方法元信息的 stack 属性中

动态链接

每个栈帧中包含一个在常量池中对当前方法的引用 , 目的是支持方法调用过程的动态连接。

方法返回地址

方法执行时有两种退出情况:第一,正常退出,即正常执行到任何方法的返回字节码指令,第二,异常退出。无论何种退
出情况,都将返回至方法当前被调用的位置。 方法退出的过程相当于弹出当前栈帧

对象的表示

Java虚拟机规范不强制规定对象内部结构的表示,在oracle某些虚拟机实现中,指向对象实例的引用是一个指向句柄的指针,这个句柄又包含两个指针,其中一个指针指向一张表格,表格包含该对象的各个方法,还包含指向class对象的指针,这个class对象用来表示对象的类型;句柄另一个指针指向分配在堆中的对象实例数据

Java的线程与内存

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Sophisticated_/article/details/89364418
今日推荐