1.内存区域划分
jvm在执行java程序过程中会将管理的内存划分成若干不同的数据区域,他们分别是程序计数器,堆,方法区,虚拟机栈,本地方法栈。
1.1指令计数器
指令计数器是线程私有的,每个线程都有独立的指令计数器,计数器记录着虚拟机正在执行的字节码的指令地址,分支,循环,跳转,异常处理和线程恢复等操作都依赖这个计数器完成,如果线程执行的native方法,则这个计数器为空。
1.2虚拟机栈
虚拟机栈是线程私有的,主要用于存放局部变量表,操作栈,动态链接,方法出口等信息,由于每个方法被执行都会创建对应的线帧,方法被调用到直至完成调用的过程,实际对应线帧在操作栈中入栈和出栈的过程。在java虚拟机规范中,对这个区域规定了两种异常情况:如果线程请求的栈深度大于规定的深度,则抛出StackOverFlowError异常;如果虚拟机栈的动态扩展到了无法申请的足够内存时候将抛出OutOfMemberError异常。
1.3本地方法栈
本地方法栈和虚拟栈的功能相似,包括上述2个异常情况也一样,区别在于虚拟机栈是为虚拟机执行的java服务,而本地方法栈是为虚拟机使用的Native方法服务。
1.4堆
堆是内存中最大的区域,并且它是所有线程共享的区域。它的唯一作用就是存放对象实例,根据jvm规范的规范,它的内存空间可以使不连续的,只要在逻辑上连续的即可。
1.5 方法区
方法区和堆一样,是被所有线程共享的运行时区域,它用于存放被虚拟机加载的累信息,常量,静态变量,即时编译后的代码等数据,跟堆的情况一样,当方法区无法满足内存分配需求时,也会抛出OutOfMemberError的异常。
运行时常量池也属于方法区的一部分。class文件除了有版本,字段,方法,接口等描述信息外,其中还有信息是常量池,用于存放编译后的各种字面量和符号引用,这部分将在类加载后存放到方法区的常量池中。另外java语言并非要求常量一定一定在编译期间产生,即是并非预置入的class文件常量池的内存才能进入方法区的运行时常量池,运行期间同样也能进入。
1.6直接内存
直接内存并不属于虚拟机运行时的数据区的一部分, 也不是java虚拟机规范中定义的内存区域,但这部分内存被频繁使用到,并且也会爆outofmemoryError异常。
在java jdk1.4中加入了NIO类,引入了基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以直接使用Native函数分配堆外的内存,然后通过存储在java堆中DirectByteBuffer对象作为这块内存的引用直接操作,这样避免了java堆和Native堆来回复制的问题,提升了行性能。
1.7对象访问方式
java虚拟机规定了一个对象变量指向一个对象的引用,并没有定义这个引用以何种方式去定位,以及访问到java堆的具体位置,所以不同的虚拟机实现对象访问的方式略有不同,大概主流的分为:句柄和直接指针。
使用句柄访问方式,java堆会划出一个内存区域作为句柄池,对象的变量存储的就是句柄池的地址,而句柄池中就存放了对象实例的数据以及对象类型信息的地址信息。若使用直接访问方式,对象变量中存储的直接是对象实例的数据以及对象类型信息的地址信息。
两种访问方式各有优势,句柄访问的优势在于对象变量可存储稳定的地址,当对象移动时,只需改变句柄池的地址,变量本身无需修改。直接访问的优势明显在于访问速度快,sun HotSpot就是采用第二种对象访问方式。