了解jvm的过程就像从喜欢到恋爱的过程,刚开始懵懵懂懂,然后心生胆怯,最后沉迷于此。
Java虚拟机结构
这里将会从class文件格式、JVM中的数据类型讲到JVM的运行时数据区
class文件格式
“ Write once, run anywhere”,这句话对Java开发者来说都是无比的熟悉了。那么这种说法是怎么来的呢,为什么Java会有这样的口号呢?这其中的奥秘就在与class文件。在Java中,源文件(.java)首先会被编译为字节码文件(.class),然后再将字节码文件通过jvm去执行。在字节码的格式中,精确定义了类与接口的表示形式,包括了在平台相关的目标文件格式中的一些细节上的惯例,列如字节序(byte ordering)等。
这里请不要认为此处的平台相关的目标文件格式是指在特定平台编译出的class文件无法再其他平台中使用。相反,正是因为强制,明确的定义了本来会跟平台相关的细节,所以才达到了Java语言是平台无关的效果。
也就是说Java语言的确是平台无关的。但这种平台无关性,正是由于class文件的平台有关有JVM的平台有关造就的。
数据类型
同Java语言中的数据类型相似,JVM可以操作的数据类型也分为两大类:原始类型(primitive type,也经常被翻译成原生类型或者基本类型)和引用类型。与之对应,也存在原始值(primitive value)和引用值(reference value)两种类型的数值。他们可用于变量赋值,参数传递,方法返回和运算操作。
Java虚拟机希望可能多的类型检查能在程序运行之前完成,换句话说,编译器应当在编译期间尽最大的努力完成尽可能的类型检查,使得虚拟机在运行期间无需进行这些操作,原始类型的值不需要通过特殊标记或额外识别手段来在运行期确定他们的实际数据类型,也无需刻意见他们与引用类型的值区分开。虚拟机的字节码指令本身就可以确定他的指令操作数的类型是什么,所以可以利用这种特性直接确定操作数的数值类型。
Java虚拟值是直接支持对象的。这里的对象可以是指动态分配的某个类的实例,也可以是指某个数组。虚拟机中使用reference
类型来表示某个对象的引用。关于reference
类型的值,你可以想象成指向对象的指针。没一个对象都可能存在多个指向它的引用,对象的操作,传递和检查都通过引用它的reference
类型的值来进行。
这里的reference类型与int、long、double等类型是同一层次的概念。reference
是前面提到过的引用类型(reference type)的一种。而int、long、double等则是前面提到过的原始类型(primitive type)的一种。前者是具体的数据类型,后者是某种数据类型的统称。
原始类型与值
Java虚拟机所支持的原始数据类型包括数值类型,boolean类型和returnAddress类型。
数值类型
byte、short、int、long、float、double、char。
boolean类型
虽然虚拟机中定义了这种类型,但是却没有提供专门操作boolean的字节码指令,Java语言表达式所操作的boolean值在编译之后都是用虚拟机中的int数据类型来代替。Java虚拟机直接支持boolean类型的数组。boolean类型数组的访问和修改共用byte数组的baload
和bastore
指令。在Oracle的虚拟机实现中。Java语言中的boolean数组将会被编译层Java虚拟机的byte数组,每个元素占8位。不管是用int表示还是byte,,虚拟机都会将映射后的true用1来表示。false用0来表示。
returnAddress
returnAddress类型是指向某个操作码的指针,此操作码与Java虚拟机指令相对应。在虚拟机所有支持的原始类型中,只有returnAddress类型是不能直接与Java语言的数据类型相对应。
引用类型与值
Java中有三种引用类型,类类型(class type)、数组类型(array type和接口类型(interface type)。这些引用类型的值分别指向动态创建的类实例,数组实例和实现了某个接口的类实例或数组实例。在引用类型中还有一个特殊的值null,当一个引用不指向任何对象的时候,他的值就用null来表示。一个为null的引用,起初并不具备任何实际的运行期类型。但它可以转换为任意的引用类型。引用类型的默认值就是null。
Java虚拟机规范并没有规定null在虚拟机实现应该怎么用编码来表示。
运行时数据区
Java虚拟机定义了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机的启动而创建,随着虚拟机的退出而销毁。另外一些则则是与线程一一对应的,这些与线程对应的数据区域会随着线程开始和结束而创建和销毁。
Java运行时数据区图示如下:
程序计数器(Program Counter Register)
Java虚拟机可以支持多条线程同时执行,每一条Java虚拟机线程都有自己的程序计数器。在任意的时刻,一个Java虚拟机线程只会执行一个方法的代码,这个正在被执行的方法被称为当前方法,因此为了能够在切换线程后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条程序计数器之间互不影响,独立存储。我们层这类内存区域为“线程”私有的。如果这个方法不是native
的,那么PC寄存器就保存Java虚拟机正在执行的字节码指令的地址。如果该方法是native
的,那PC寄存器的值是空(Undefined)。PC寄存器的容量至少应该保存一个returnAddress类型的数据或者一个与平台相关的本地指针的值。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError的区域。
Java虚拟机栈(Java Virtual Machine Stack)
每一台哦Java虚拟机线程都有自己私有的Java虚拟机栈,这个栈与线程同时创建,用于存储栈帧(Frame),Java虚拟机栈的作用与传统语言中的栈非常相似,用于存储局部变量与一些尚未计算好的结果,另外,他在方法的调用和返回中也扮演了重要的角色,因为除了栈帧的出栈与入栈之外,Java虚拟机不会再受其他因素影响,所以栈帧可以在堆中分配(从虚拟机的实现来看),Java虚拟机栈所使用的内存不需要是连续的。
本地方法栈(Native Method Stack)
Java虚拟机实现可能会用到传统的栈(通常称为C Stack)来支持本地方法(除了Java以为其他方法编写的方法)的执行,这个栈就是本地方法栈。与Java虚拟机栈的功能类似,只不过一个是为了Java方法而服务的,一个是为了本地方法服务的。
在Java虚拟机规范中有提到,Java虚拟机实现应当提供给程序员或者最终用户调节本地方法栈初始容量的手段,对于长度可动态变化的本地方法栈而言,则应当提供调节其最大最小容量的手段。
另外值得一提的是,HotSpot虚拟机将 Java虚拟机栈和本地方法栈的实现合并在一起!
Java 堆
这里请注意不要混淆,Stack
,Heap
和JVM Stack
,Java Heap
的概念。Java虚拟机的实现本质上是由其他语言所编写的应用程序,Java语言程序里分配在Java Stack
中的数据,从实现虚拟机的程序角度来看则可能分配在Heap之中。
在Java虚拟机中。堆(Heap)是可供各个线程共享的运行时内存区域。也是提供几乎所有类实例和对象分配内存的区域。
这里在Java虚拟机规范中描述的是,所有类实例和数组分配的内存的区域。但是随着JIT编译器的发展与逃逸分析技术日益陈述,栈上分配,标量替换等优化技术将会导致一些微妙的变化发生,所有的对象都分配在堆上也渐渐变得不那么绝对了。
Java堆在虚拟机启动的时候就被创建,他存储了动态内存管理系统(automatic storage management system),也就是常说的garbage collector(垃圾收集器)所管理的对象。Java堆的容量可以是固定的,也可以是随着程序执行的需求动态扩展,并在不需要过多空间是自动回收。Java堆所使用的内存同样不要求是连续的。当实际所需要的堆超过了能提供的最大容量时,系统会抛出一个OutOfMemoryError。
从内存回收的角度来看,由于现在的收集器采用分代收集算法,所以Java堆还可以细分为:新生代和老年代,再细致一点的有Eden空间,From Survivor空间,To Survivor空间等
从内存分配的角度来看,线程共享的Java堆中可能划分中多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。
方法区
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器所产生的本地代码数据等。
更多内容请穿越至走进方法区的前世今生
运行时常量池
运行时常量池(runtime constant pool)是class文件中每一个类或接口的常量池表(constant_pool table)的运行时表现,它包括了若干种不同的常量,从编译期可知的数字字面量到必须在运行时期解析后才能获得的方法或字段引用。每一个运行时常量池都在Java虚拟机的方法区中分配,在加载类和接口到虚拟机后,就创建对应的运行时常量池。在创建类和接口的运行时常量池时,可能会发生如下异常。
- 当创建类或接口的时候,如果构造运行时常量池所需要的内存空间超过了方法区所能提供的最大值,那么Java虚拟机将会抛出OutOfMemoryError异常。
以上就是关于Java虚拟机结构的一些内容了,需要注意的是,运行时数据区就是我们常说的Java内存结构(JVM内存结构),而不是Java内存模型,更不是jvm内存模型(根本没有这个概念)。除此之外还有一个概念叫做Java对象模型,如果你对这几个东西还不甚了解的话,可以看下JVM内存结构 VS Java内存模型 VS Java对象模型。
本文参考:
《深入理解Java虚拟机》
《Java虚拟机规范(Java SE 8 版)》