JVM执行的对象就是大家非常熟悉的class文件我们也称为类文件JVM规范定义的这个编译完成的代码文件(虽然并非强制要求是实际的文件)的格式非常的详实但是我们这里只说一些宏观的内容以后有机会再研究细节的内容吧JVM要求的类文件的格式是和硬件和操作系统无关的一种二进制格式它精确定义了类或者接口的表示它甚至包含了字节顺序这样的细节而字节顺序在特定平台的目标文件格式中一般都是固定的不会进行说明 JVM所支持的数据类型和Java语言规范中定义的几乎一样请注意是几乎一样!也就是原始类型和引用类型他们可以被存储在变量表中也可以作为参数传递被方法返回更通常的就是成为操作的对象为什么和Java语言规范中定义的不完全一样呢?因为JVM中有一种Java语言所没有的原始类型返回地址类型(returnAddress type)该类型是jsr ret以及jsr_w指令需要使用到的它的值是JVM指令的操作码的指针并且它的值是不能被运行中的程序所修改的 另外需要提到的就是布尔类型的值虽然在Java语言中它是完全独立的值但是在JVM中只提供了对它的有限支持表现在 没有单独的操作布尔类型的指令源代码中的布尔类型的操作在编译以后是作为int类型的值进行操作的 JVM直接支持布尔数组newarray指令可以创建布尔数组而它的访问和修改操作却是使用byte类型的数组的操作指令进行的baloadbastore(在JDK以及中布尔数组被编码为byte数组每个元素是位) JVM用代表true用代表false编译器将源代码中的布尔类型映射为JVM中的int类型而且必须和JVM的要求一致 另外JVM规范中对于浮点类型的数据有大段的说明我没有怎么看主要是讨论JVM的浮点型和IEEE 的关系的 关于类型的另外一个需要提一下的是类型检查JVM期望几乎所有的类型检查已经在运行之前完成了(通常是由编译器进行检查的)而不用JVM自己来检查原始类型的值不需要被标记或者在运行时被检查以确定他们的类型同样他们也不用和引用类型的值进行区分区分工作是由JVM的指令集来完成的JVM的指令集使用不同指令来区分它要操作的值的类型例如iadd ladd fadd以及dadd是用于将两个数字相加并产生数字类型结果的所有JVM指令但是每个指令都是针对特定类型的分别对应int long float以及double JVM包含对对象的显式支持类是动态分配的类实例或者是一个数组JVM中的引用类型就是对一个对象的引用引用类型的值可以想象为对象的指针一个对象同时可能存在多个对它的引用对象总是通过引用被操作传递或者测试的 对于引用类型需要提及的一点就是关于null它最初是没有运行时类型的但是它可以被转换为任何类型而且对于nullJVM并没有要求任何具体的值与之对应 说完上面这些我们就开始进入我学习JVM时最想了解的部分了大家可要打起精神哦 JVM为运行一个程序定义了几种数据区(Data Area)包括pc寄存器JVM堆栈堆方法区(Method Area)运行时常量池(Runtime Constant Pool)以及本机方法堆栈(Native Method Stacks)这些数据区根据其生存期可以分为两种一种就是和JVM的生存期相同(包括堆和方法区)一种和线程的生存期相同(其它的)和JVM生存期相同的数据区在JVM启动的时候被创建并在JVM退出的时候被销毁而和线程生存期相同的数据区是每个线程一个的他们在线程创建的时候被创建在线程被销毁的时候被销毁 由于JVM可以同时支持运行多个线程因此每个线程必然需要各自的PC(program counter)寄存器无论从什么角度讲每个JVM线程只能在一个时间只能执行一个方法该方法也就是线程的当前方法如果该方法不是本机方法那么PC寄存器保存的就是当前指令(JVM的指令)的地址如果是当前方法是本机方法PC寄存器的值就没有被定义JVM的PC寄存器的大小足够大可以容纳一个returnAddress类型或者特定平台的本机指针 每个JVM线程还拥有一个私有的JVM堆栈它存储帧(下一篇文章会讲到)JVM堆栈和像C这样的传统编程语言中的堆栈是类似的它保存局部变量和部分结果并且在方法调用和返回中也担任一些职责因为除了对帧的压入和弹出操作外对JVM堆栈不能直接进行操作因此帧可能是在堆上分配的如果一个线程中计算所需的JVM堆栈大于允许的大小JVM会抛出StackOverflowError错误如果JVM堆栈是可以动态伸缩的如果需要扩展但是又没有足够的内存可用或者没有足够的内存为一个新线程创建JVM堆栈JVM会抛出OutOfMemoryError错误 JVM只有一个为所有线程所共享的堆所有的类实例和数组都是在堆中创建的堆所存储的对象被一个自动存储管理系统回收(也就是我们所熟知的垃圾收集器(gc))对象不能被显式的释放JVM假设没有特定类型的自动存储管理系统存储管理技术可以根据实现者的系统需求进行选择如果计算所需的内存堆大于自动存储管理系统可以使用的大小JVM会抛出OutOfMemoryError错误 JVM只有一个为所有的线程所共享的方法区方法区类似传统语言的已编译代码的存储区或者UNIX进程的文本段它存储类结构例如运行时常量池成员和方法数据以及方法构造方法的代码(包括用于类和实例的初始化以及接口类型初始化的特定方法(这些特定方法以后会讲到))虽然从逻辑上讲方法区是堆的一部分但是JVM的简单实现可以选择不对方法区进行垃圾收集或者压缩(以笔者的理解就是类不能进行卸载)最新版本(第二版)的JVM规范没有要求方法区的位置或者管理已编译代码的策略如果方法区的内存不能满足一个分配请求JVM会抛出OutOfMemoryError 运行时常量池是类文件中的常量池表的运行时表示它包含几种常量范围从编译时就已知的数字常量到运行时必须进行解析的方法和成员引用运行时常量池扮演的功能类似于传统编程语言中的符号表(symbol table)但是它所包含的数据比典型的符号表更多 每个运行时常量池时从JVM的方法区中分配的对于特定方法或者接口的运行时常量池是JVM在创建类或者接口的时候创建的 当创建一个类或者接口时如果创建运行时常量池需要的内存比方法区中的可用内容更多的内存JVM会抛出OutOfMemoryError 关于常量池创建的更多内容以后可能会更详细的讲解 JVM的实现可能使用传统的堆栈(更通常的讲就是C栈)以支持本机方法(不是使用JAVA语言编写的方法)本机方法堆栈也可以用于在像C语言这样的语言中为JVM指令集实现解析器对于不能加载本机方法以及自身不依赖传统堆栈的JVM实现而言它可以不提供本机方法堆栈如果提供本机方法堆栈通常在线程创建的时候为每个线程分配(以笔者的理解应该是需要使用本机方法的线程)如果线程计算所需的内存比本机方法堆栈所允许的大JVM会抛出StackOverflowError错误如果本机方法堆栈可以动态伸缩而当需要扩展的时候又没有足够的内存时或者没有足够的内容用于创建一个本机方法堆栈JVM会抛出OutOfMemoryError 对于上面的这些数据区JVM规范允许它们的大小是固定尺寸的也可以是根据计算的需要动态伸缩的如果是固定尺寸的其尺寸可以在创建时自主选择JVM的实现可以给程序员或者用户提供控制JVM堆栈的初始大小的方法同样在动态伸缩的情况下可以控制最大大小和最小大小并且它们所使用的内存空间可以不是连续的 |