PS:文章来源《深入理解java虚拟机第三版 2.2运行时数据区域》
java虚拟机在java程序执行过程中会把内存区域划分成若干个数据区域,有的随虚拟机启动就存在,有的则随线程的启动和结束而建立和销毁。总的说来包含以下几个运行时内存区域:
1.程序计数器
程序计数器可以看做是当前线程所执行的字节码的行号指示器。字节码指示器的工作就是通过改变计数器的值选取下一条要执行的字节码指令,如分支、循环、跳转、异常处理、线程恢复等。每个线程都有一个独立的程序计数器,各线程之间的计数互不影响,因此该区域也称为“线程私有”内存。
2.java虚拟机栈
虚拟机栈也是线程私有的,描述的是java方法执行的线程内存模型:每个方法被执行时,虚拟机会同步创建一个栈帧用来存储局部变量表、操作数栈、动态连接、方法出口等信息。方法从开始执行到结束的过程也是一个栈帧在虚拟机栈中从出栈到入栈的过程。
局部变量表存放了编译期可知的java基本数据类型(byte、int、short、long、float、double、char、boolean)、对象引用和returnAddress类型(指向一条字节码指令的地址)。
若线程对栈的请求深度大于虚拟机所允许的深度,则会抛出StackOverflowError异常;若虚拟机容量可扩展,当栈扩展时无法申请到足够的内存则抛出OutOfMemoryError异常。
3.本地方法栈
与java虚拟机栈作用类似,区别在于一个是虚拟机栈为虚拟机执行java方法(字节码)服务,一个是执行虚拟机用到的本地(Native)方法服务。
4.java堆
java堆是一块被所有线程所共享的内存区域,几乎所有的对象实例以及数组都在这进行分配,java堆同时也是GC(垃圾收集器)所管理的内存区域。从内存非配的角度看,堆中可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB),以提升对象分配时的效率。堆可固定大小也可扩展,扩展可以通过参数-Xmx(最大内存)和-Xms(最小内存)设置,不过最好设置成相等,这样就不会因为不断地扩展耗费资源。若没有足够的内存非配对应的实例且无法扩展,java虚拟机将会抛出OutOfMemoryError异常。
5.方法区
java方法区也和堆一样是所有线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。JDK8之前在HotSpot虚拟机中将方法区放置在”永久代“进行管理,但永久代有-XX:MaxPermSize上限,即使不设置也有默认大小,因此容易导致内存泄露,JDK6时HotSpot开发团队就逐渐放弃永久代,采用本地内存来实现方法区,到了JDK8则完全抛弃了永久代,采用本地内存中实现的元空间来代替。
6.运行时常量池
运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项是常量池表,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量中。运行时常量不一定只有编译器才产生,例如String.Intern()方法可在运行期间动态设置到常量池中。
7.直接内存
在JDK1.4引入了NIO时,可以使用Native函数库直接分配堆外内存,避免了在java堆和Native堆中来回复制数据,在某些场景中能显著提升性能。然而这一部分内存不收java虚拟机管理,若分配的内存超出了本机物理内存同样会抛出OutOfMemoryError异常,因此需要在使用完时需要释放所申请的堆外内存。