jvm,java 虚拟机,是java语言实现平台无关性的具体实现,是一个虚拟的计算机设备。
作为jvm笔记记录,从本节开始,记录jvm知识的点点滴滴。
以java se 7为标准,java虚拟机在执行java程序时把所管理的内存划分为若干不同的数据区域:
由于下面有些讲解的需要,先说明几个概念:
1.java方法:指程序员手动编写的方法或者java源码里的方法,主要是为了区别于native方法.
程序计数器(Program Counter Register)
直译应该是pc寄存器。
仅从功能上来说,jvm中的pc和通用计算机中的pc含义一致:用于存放下一条指令所在的地址。(出自计算机原理)
因此pc寄存器是一块很小的地方,《深入理解java虚拟机》中将其解释为当前线程所执行的字节码的行号指示器。通过改变pc寄存器的值,可以选择下一条需要执行的字节码指令。
既然是存放的指令的地址,指令有两种类型:一个是程序员手写的java方法,另一个是native方法;pc寄存器也存在两种情况:
1.正在执行的是java方法,pc寄存器存的则是正在执行的编译好的字节码的指令的地址;
2.正在执行的是native方法,pc寄存器为空。
该区域时唯一一个不会outofmemoryerror的区域。
由于java多线程的需求,为了多线程的切换能正确工作,每个线程都有一个独立的pc寄存器,各线程之间pc寄存器互不影响,独立存储,pc寄存器是“线程私有”的内存。
虚拟机栈(VM Stack)
想象一下,java在执行各种方法,尤其是递归方法时,是如何保证顺序的?
一种最简单的思想就是使用栈。遇到一个方法时将其入栈,该方法执行完则出栈,如果遇到递归,则当前未执行完的方法入栈,并将递归方法中的方法再次入栈,直到递归结束。
实际上虚拟机栈(VM Stack)就是按照这种方式运行的:
虚拟机栈是这样描述java方法执行的:每个方式执行时都会创建一个栈帧(Stack Frame),用于保存局部变量表,操作数栈,动态链接,返回地址等。
每一个方法从调用直至执行完成的过程,对应着一个栈帧在vm stack中入栈到出栈的过程
局部变量表
顾名思义,局部变量表就是用于存放编译期间(注意是编译期间)各种变量的值(基本类型变量)和引用对象地址还有returnAddress的空间。
也就是说,对于基本类型的变量(8种),其值是直接保存在局部变量表中的,引用类型(String,对象)是保存了一个该应用的地址,而对象的实际数据是在堆(Heap)中,还有一个就是returnAddress。
returnAddress可以理解为该函数返回时应该返回到哪里,即函数返回的地址。
操作数栈
操作数栈中保存的也是数据,但不同的是,操作数栈的作用是实际完成函数中的具体操作,比如各种运算等。
熟悉栈的都知道,一般栈可以用来完成各种操作运算。实际上在java虚拟机中,这种运算操作就是通过栈来实现的。
为了理解局部变量表和操作数栈的区别以及两者的工作原理,可以看下图:
图中函数实现的功能是计算和。
局部变量表一开始就有一个该函数的数据变量的快照,即保存了所有变量,然后使用操作数栈来完成具体的计算操作,先是两个操作数入栈(a和b),然后出栈进行运算,运算结果再入栈,最后结果保存到局部变量表的变量c中。
由此可见,虚拟机栈也是线程私有的,其生命周期和线程相同。
关于局部变量表和操作数栈,参考博客:局部变量表和操作数栈
动态链接
动态链接即表明该栈帧属于哪一个函数。
通过上面的介绍,我们知道了虚拟机栈是线程私有的,那么函数本身肯定就不在虚拟机栈中了,因为函数显然是可以多次被调用的。
实际上函数是保存在方法区中的运行时常量池中的(后文会讲),那么如何知道栈帧是属于哪一方法呢?这就是动态链接的作用了。实际上这个所谓的动态引用可能就是一个地址而已。
方法返回地址
顾名思义,就是该方法返回时返回到先前调用该方法的地址的地方,以便程序能接着执行。
异常
在java虚拟机规范中,对于虚拟机栈规定了两种异常状况:StackOverflowError和OutOfMemoryError。
在虚拟机栈中和其操作数栈中,栈都是有深度限制的,若栈帧数或者操作数的数量超过了其栈的允许值,那么就会报StackOverflowError.
而如果虚拟机栈的数量限制可以动态扩展(大部分虚拟机都支持动态扩展),而在扩展时无法申请到足够的内存,则会抛出OutOfMemoryError。
本地方法栈
和虚拟机栈类似,不过该部针对的不是java方法,而是native方法。
同样会抛出StackOverflowError和OutOfMemoryError。
堆(Heap)
几乎所有的对象实例都在堆上分配,即堆空间的唯一目的就是用于存放对象。
java虚拟机规范对其描述是:The heap is the runtime data area from which memory for all instances and arrays is allocated.
也因此,堆是垃圾回收作用的空间。
对具体的划分会在后续博客介绍。
目前大部分的jvm实现,堆的大小都是可扩展的(通过-Xmx和-Xms来控制),如果在堆中没有内存完成实例分配,并且堆无法再扩展时,会抛出OutOfMemoryError。
可以知道,堆是被线程共享的。
方法区(Method Area)
同样是各个线程共享的区域,方法区是保存类的信息。
举个例子
main创建一个类的对象,那么一开始内存中是没有该类的信息的(不可能一开始就把所有类的信息加载到内存中),于是,类加载器(ClassLoader)首先定位到这个类的.class文件,然后读取这个文件(IO操作),jvm提取类信息,并将其存到方法区中。那么下一次在需要创建该类的对象时,就从方法区中取该类的信息。
方法区的大小可以可以动态调整的,当无法再扩展时就抛出OutOfMemoryError.
当方法区中的某各类不在被需要(不可达时),该类的信息就被卸载,即被垃圾回收。
方法区保存的类的信息一般包括常量,类的类型(Class or Interface),访问修饰符(public private等)等。
方法区中还细分一块区域叫做运行时常量池(Runtime Constant Pool),用于存放字段、方法信息、静态变量等。
本文介绍了Java虚拟机(JVM)的内存划分,包括程序计数器、虚拟机栈、本地方法栈、堆和方法区。每个区域都有其特定的功能,如程序计数器用于记录执行的字节码指令,虚拟机栈通过栈帧管理方法执行,局部变量表存储变量,操作数栈执行运算,动态链接用于标识方法归属,方法返回地址指明返回位置。堆是对象实例的存储空间,方法区则保存类信息。各区域在多线程环境下具有线程私有或共享的特点,并可能引发StackOverflowError和OutOfMemoryError。
174万+

被折叠的 条评论
为什么被折叠?



