最近打算将java虚拟机的相关知识点整理出来。本文先介绍下java虚拟机运行时分配的各个区域的作用
运行时数据区域
java虚拟机在程序执行的过程中会把它所管理的内存划分为若干个不同的数据区域,每个区域都有各自的用途。结构图如下:
程序计数器
程序计数器(Program Counter Register)是一块较小的内存空间,这块区域是线程私有的。作用是当前线程所执行的字节码的行号指示器(记录器)。
java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现。在任何一个确定的时间点,一个处理器(对于多核处理器就是一个内核)都只会执行一条线程中的指令,因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器。各个线程之间计数器是相互不影响的。独立存储。所以程序计数器的作用也比较突出。保存当前线程执行的位置。
java虚拟机栈
和程序计数器一样java虚拟机栈也是线程私有的。它的生命周期和线程相同。
虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧,用来存储局部变量表,操作栈,动态链接,方法出口等信息。每个方法从调用直到执行完成的过程,都对应一个栈幁在虚 拟机栈中从入栈到出栈的过程。
在编译程序代码的时候,栈帧需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到方法表的code属性中,因此一个栈帧需要分配多少内存,不会受到运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。
一个线程中的方法调用链路可能会很长,很多方法都处于同时执行的状态。对于执行引擎来说,在活动线程中,只有处于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法。
执行引擎运行的所有字节码指令只针对当前栈帧进行操作,在概念模型上,典型的栈帧结构如图所示:
本地方法栈
本地方法栈和虚拟机栈所发挥的作用是非常相似的。他们的区别仅仅是java虚拟机栈是为java方法调用服务的,而本地方法栈主要是为虚拟机调用到native方法服务的。
如果某个虚拟机实现的本地方法接口是使用C连接模型的话,那么它的本地方法栈就是C栈。本地方法接口需要回调Java虚拟机中的Java方法,在这种情况下,该线程会保存本地方法栈的状态并进入到另一个Java栈。如下是虚拟机栈和本地方法栈的图示
java堆
Java堆(Java Head)是java虚拟机中所管理的内存中最大的一块。
java堆是被所有线程共享的一块内存区域。java堆的唯一目的就是存放对象实例。几乎所有的对象实例都在这里分配内存。
Java堆是垃圾收集器管理的主要区域。因此垃圾收集器也被称作"GC堆",在垃圾收集器的角度Java堆还可以细分为:新生代和老年代在细致的区分有:Eden空间,From Survivor空间,To survivor空间等从内存分配的角度来看,线程共享的Java堆中还可以划分出每个线程私有的分配缓冲区(Thread Local Allocation Buffer TLAB)。
不管如何划分,都与存放内容无关,无论哪个区域,存放的都是对象实例,划分的目的是为了更好的回收内存或者更快的分配内存。后面文章中具体介绍各个区域如何分配的等。
java堆中的内存地址物理上可以不是连续的。只要逻辑上连续即可。
-Xxm和-Xms 控制堆的大小。
方法区
方法区(Method Area)与java堆一样,是各个线程共享的内存区域。用来存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
方法区的大小不必是固定的,jvm可以根据应用的需要动态调整。同样方法区也不必是连续的。方法区可以在堆(甚至是虚拟机自己的堆)中分配。jvm可以允许用户和程序指定方法区的初始大小,最小和最大尺寸。
方法区同样存在垃圾收集,因为通过用户定义的类加载器可以动态扩展Java程序,一些类也会成为垃圾。jvm可以回收一个未被引用类所占的空间,以使方法区的空间最小。
对每个加载的类型,jvm必须在方法区中存储以下类型信息:
这个类型的完整有效名
这个类型直接父类的完整有效名(除非这个类型是interface或是 java.lang.Object,两种情况下都没有父类)
这个类型的修饰符(public,abstract, final的某个子集)
这个类型直接接口的一个有序列表
除了以上的基本信息外,jvm还要为每个类型保存以下信息:
类型的常量池( constant pool)
域(Field)信息
方法(Method)信息
除了常量外的所有静态(static)变量
运行时常量池
运行时常量池(Runtime Constant Pool) 是方法区的一部分。jvm为每个已加载的类型都维护一个常量池。常量池就是这个类型用到的常量的一个有序集合,包括实际的常量(string, integer, 和floating point常量)和对类型,域和方法的符号引用。池中的数据项象数组项一样,是通过索引访问的。
因为常量池存储了一个类型所使用到的所有类型,域和方法的符号引用,所以它在java程序的动态链接中起了核心的作用。
直接内存
直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分。但是这部分内存也会被频繁的使用到。
在java中使用NIO的时候,Channel和缓冲区(Buffer),它可以使用Native函数库直接分配堆外内存。然后通过存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,这样可以在某些场景中显著提高性能。避免了在java堆和Native堆中来回复制数据。