导言:java虚拟机是可以在多个系统中运行的,以windows举例,当java虚拟机开始运行时,系统硬盘就会给她分配一个内存空间,这个空间由它自己去分配管理和维护。
- JVM运行时数据区域
(1). 程序计数器。可以理解为线程执行时的行指示器,在虚拟机的原理中可以认为字节码解释器的工作就是改变这个行指示器来选择下一条指令。在多线程的情况下,一般处理器在一个时间段里面都是只运行一个线程的,在线程切换后就是依靠程序计数器来将线程恢复到正确的执行位置,因此每一个线程都拥有自己的一个程序计数器,各个计数器之间互相不影响,独立存储。可认为是“线程私有”的内存。
如果线程执行的是java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址;如果是native方法,则这个计数器的值为空。
这个区域是唯一一个不会OutOfMemoryError的区域。(2). 虚拟机栈。线程私有,生命周期与线程相同。是java方法执行的内存模型,内部存储着局部变量表、操作数栈、方法出口、动态链接等。
栈帧:支持虚拟机进行方法调用和方法执行的数据结构。每个方法从调用到执行完成就对应一个栈帧的入栈和出现。
局部变量表:保存函数的参数以及局部变量信息,作用域为当前调用的函数,当前函数结束,则局部变量表也会随之销毁。存放boolean、byte、char、short、int、float、reference和returnAddress八种数据类型。通过索引访问。
操作数栈:也称为操作栈,通过压栈和出栈来操作,如add操作,首先将两个变量从局部变量表中提取出来,然后压入操作数栈,然后由执行引擎执行加法运算,然后将结果压入栈,最后结果出栈存储进入局部变量区中。
动态链接:在java代码编译成功后会成为一个个的程序块,然而在程序运行时,需要找到对应的程序块,动态链接存储的就是这些程序块的运行顺序。(作为不懂C的同学,表示这个很难理解,学过C++的同学应该会比较好理解吧)。
每个栈帧都有一个运行时常量池的引用。这个引用指向栈帧当前运行方法所在类的常量池。通过这个引用支持动态链接(dynamic linking)。
C/C++ 代码一般被编译成对象文件,然后多个对象文件被链接到一起产生可执行文件或者 dll。在链接阶段,每个对象文件的符号引用被替换成了最终执行文件的相对偏移内存地址。在 Java中,链接阶段是运行时动态完成的。
当 Java 类文件编译时,所有变量和方法的引用都被当做符号引用存储在这个类的常量池中。符号引用是一个逻辑引用,实际上并不指向物理内存地址。JVM 可以选择符号引用解析的时机,一种是当类文件加载并校验通过后,这种解析方式被称为饥饿方式。另外一种是符号引用在第一次使用的时候被解析,这种解析方式称为惰性方式。无论如何 ,JVM 必须要在第一次使用符号引用时完成解析并抛出可能发生的解析错误。绑定是将对象域、方法、类的符号引用替换为直接引用的过程。绑定只会发生一次。一旦绑定,符号引用会被完全替换。如果一个类的符号引用还没有被解析,那么就会载入这个类。每个直接引用都被存储为相对于存储结构(与运行时变量或方法的位置相关联的)偏移量。
方法出口:returnAddress,要么抛出异常,要么return完成,完成后若再没有引用GC可能就会进行销毁。(3) 本地方法栈。为native方法服务,当一个类第一次被使用到时,这个类的字节码会被加载到内存,并且只会加载一次。在这个被加载的字节码的入口维持着一个该类所有方法描述符的list,这些方法描述符包含这样一些信息:方法代码存于何处,它有哪些参数,方法的描述符(public之类)等等。如果一个方法描述符内有native,这个描述符块将有一个指向该方法的实现的指针。这些实现在一些DLL文件内,但是它们会被操作系统加载到java程序的地址空间。
(4) java 堆。所有线程共享的一块区域,也是GC主要管理的区域。
(5) 方法区。所有线程共享的一块区域,存储已被虚拟机加载的类信息,常量,静态变量等,即时编译器编译后的代码等,
(6) 运行时常量池。存放编译期生成的各种字面量和符号引用,将在类加载后进入方法区中的运行时常量池保存。
(7) 直接内存。本身不属于运行时数据区的一部分,也是为了处理native方法调用的性能问题。在JDK1.4之后出现的NIO(New Input/Output)(基于通道和缓冲区的I/O方式)之后,通过native函数库直接分配堆外内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。可以避免java堆和native堆中来回复制数据。