JVM定义:
JVM(Java Virtual Machine),是一种运行Java程序的软件实现,是虚拟的机器。Java程序与平台无关,它直接在虚拟机中运行。
JVM运行过程:
JVM将内存分划如下五个区域:
1、方法区;2、堆区;3、虚拟机栈;4、本地方法栈;5、程序计数器;
方法区(Method Area):
JVM使用类装载器装载某个类时,会找到相应的class文件,并读取该文件内容信息,以及将存储到方法,最后返回一个class实例。
方法区存放内容:1、类的全限定名(类的全路径名);2、类的直接超类的权全限定名(如果这个类是Object,则它没有超类);3、类的类型(类或接口);4、类的访问修饰符,public,abstract,final等;5、类的直接接口全限定名的有序列表;6、常量池(字段,方法信息,静态变量,类型引用(class))等;
方法区是全局共享的,但是它也是线程安全的。在一定条件下它也会被GC。当方法区使用的内存超过它允许的大小时,就会抛出OutOfMemory:PermGen Space异常。产生该错误的原因大都出于以下原因:JVM内存过小、程序不严密,产生了过多的垃圾。
JVM方法区的相关参数,最小值:--XX:PermSize;最大值:--XX:MaxPermSize。
堆区(Heap Area):
一个运行时数据区,类的实例(new创造的对象)从中分配空间,它的管理是由垃圾回收来负责的。堆区由所有线程共享,且用来存储对象实例及数组值。
对于堆区大小,可以通过参数-Xms和-Xmx来控制:
-Xms为JVM启动时申请的最新head内存,默认为物理内存的1/64但小于1GB;
-Xmx为JVM可申请的最大Head内存,默认为物理内存的1/4但小于1GB;
当剩余堆空间小于40%时,JVM会增大Head到-Xmx大小,可通过-XX:MinHeapFreeRadio参数来控制这个比例;
当空余堆内存大于70%时,JVM会减少Head大小到-Xms指定大小,可通过-XX:MaxHeapFreeRatio来指定这个比例。
虚拟机栈(JVM Stacks):
虚拟机栈是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模式:每个方法在执行的同时都会创建一个栈帧(Statck Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
局部变量表存放了编译期可知的各种基本数据类型(boolean、btye、char、short、int、float、long、double)、对象引用(reference类型,它不等于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddress类型(指向了一个字节码指令的地址)
其中64位长度的long和double类型的数据会占用2个局部变量空间(Slot),其他都是1个Slot。局部变量表所需的内存空间是在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
虚拟机栈中定义了两种异常,如果线程请求的栈深度大于虚拟机所允许深度,则抛出StatckOverFlowError(栈溢出);不过多数Java虚拟机都允许动态扩展虚拟机栈的大小(有少部分是固定长度的),所以线程可以一直申请栈,直到内存不足,则会抛出OutOfMemoryError(内存溢出)。
本地方法栈(Native Method Stack):
本地方法栈用于支持native方法的执行,存储了每个native方法调用的状态。本地方法栈和虚拟机方法栈运行机制一致,它们唯一的区别就是,虚拟机栈是执行Java方法的,而本地方法栈是用来执行native方法的,在很多虚拟机中,会将本地方法栈与虚拟机栈放在一起使用。
程序计数器(Program Counter Register):
一个比较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时,会通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一个线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,每条线程之间计数器互不影响,独立存储。
如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令地址;如果正在执行的是Native方法,这个计数器的值为空(Undefined)。此内存区域是唯一一个在Java虚拟机规范中没有任何OutOfMemoryError的区域。
JAVA对象访问方式
一个Java的引用访问涉及到3个内存区域:JVM栈、堆、方法区。
以最简单的本地变量引用,Object obj = new Object()为例:
类变量(方法区):独立于方法之外的变量,用 static 修饰;
实例变量(堆):独立于方法之外的变量,不过没有 static 修饰;
局部变量(栈):类的方法中的变量;
public class Test{
static int a=0; // 类变量
String b="hello world"; // 实例变量
public void method(){
int c =0; // 局部变量
}
}
Object obj表示一个本地引用,存储在JVM栈的本地变量表中,表示一个reference类型数据;
new Object()作为实例对象数据存储在堆中;
堆中还记录了能够查询到此Object对象的类型数据(接口、方法、field、对象类型等)的地址,实际的数据则存储在方法区中;
在Java虚拟机规范中,只规定了指向对象的引用,对于通过reference类型引用访问具体对象的方式并未做规定,不过目前主流的实现方式只要有两种:
1)、句柄访问:通过句柄访问的实现方式中,JVM堆中会划分单独一块内存区域作为句柄池,句柄池中存储了对象实例数据(在堆中)和对象类型数据(在方法区中)的指针。这种实现方法由于用句柄表示地址,因此十分稳定。
2)、指针访问:通过直接指针访问的方式中,reference中存储的就是对象在堆中的实际地址,在堆中存储的对象信息中包含了在方法区中的相应类型数据。这种方法最大的优势是速度快,在HotSpot虚拟机中用的就是这种方式。