下图为Java虚拟机运行时的内存图,其中虚拟机栈,本地方法栈,程序计数器为线程私有,方法区和堆为线程共享区域。
程序计数器
程序计数器是一块较小的内存空间,可以看作当前执行的字节码行号指示器,字节码解释器改变计数器选取下一条执行的字节码指令。程序控制流中的分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。由于Java虚拟机通过线程轮流切换分配处理器执行时间实现多线程,因此为了线程切换后可恢复到正确位置每个线程都需要一个独立的计数器。如果正在执行的native方法则这个计数器的响应值为空。
虚拟机栈
虚拟机栈描述的是Java执行的线程的内存模型,每个方法执行的时候,虚拟机都会创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等,每一个方法从调用到执行完毕对应的虚拟机栈入栈到出栈的操作。
局部变量存放编译器可知的基本类型如(int、boolean、char 等),引用类型则是使用一个指针指向对象的起始地址。因此当进入一个方法时,这个方法在虚拟机上分配多少栈帧都是可知的,在虚拟机运行期间不改变局部变量表的大小。
本地方法栈
与虚拟机栈功能类似,不过服务对象变为了native方法
Java堆
堆是虚拟机管理的最大的一块内存,也是线程共享的区域,在虚拟机启动时堆只有一个任务那便是存储Java对象实例。
堆也是垃圾收集器管理的内存区域,现代大部分垃圾回收期都是基于分代收集理论所设计的,所以在Java堆中会出现 Eden 新生代、From Surivivor 老年代和 To Surivivor永久代等名词。hotspot也同样采用分代垃圾回收机制,G1垃圾回收期的出现是分代垃圾回收的分水岭。
Java堆即可实现固定大小也可以是可扩展的通过参数 -Xmx和-Xms设定,如果堆无法再满足对象实例存储且堆也无法扩展时,虚拟机会抛出OutOfMemoryError异常。
方法区
与Java堆一样,是线程共享区域,存放被虚拟机加载的类型信息、常量、静态变量、即使编译器编译后的代码缓冲区等数据。《Java虚拟机规范》中对方法区的约束十分宽松,除了和堆一样可选择固定大小或可扩容外,甚至还可以选择不实现垃圾回收,相对而言垃圾回收在这个区域较少出现。方法区的回收主要涉及常量池的回收和类型卸载,条件苛刻但是回收却又是有必要的。以前在sun公司的bug列表中就有方法区的内存泄露。
运行时常量池
运行时常量池是方法区的一部分。Class文件除了类的模板、字段、方法、接口等描述信息外,还有一项信息便是常量池表,用于存放编译器生成的各种字面量与符号引用,这部分内容在类加载后存放到方法区的运行时常量池中。
运行时常量池相比于Class文件常量池的重要特征是具备动态性,Java语言不要求只有编译时才能产生常量(反射),运行时常量池在方法区中同样收到内存的限制。
直接内存
直接内存并不是Java虚拟机运行时数据区的一部分,但也被频繁使用没可能抛出OutOfMemoryError异常。
在Jdk4中引入nio,一种基于Channel 通道与 Buffer 缓存区的一种IO方式,可使用Native函数库直接分配内存,后通过Java堆中DirectByteBuffer对象作为这块内存的引用进行操作。这样可避免频繁的复制数据(零拷贝)。
本地内存不会收到Java虚拟机的大小限制,但是受限于物理机的内存大小,动态扩展时有抛出OutOfMemoryError异常的可能。