Java运行时内存区域

Java运行时内存区域

程序计数器PC

保存当前线程要执行的下一条字节码指令的地址,是程序控制流的指示器,是线程私有的。如果一个线程正在执行的是一个java方法,这个计数器记录的是下一条字节码指令的地址,如果正在执行的是本地native方法,PC值为空。注意,这个内存区域是唯一一个不会出现OutOfMemoryError的区域

虚拟机栈

用来记录java方法的调用过程,每个方法被执行的时候都会在栈中创建一个栈帧用于存储局部变量表、操作数栈等信息。每个方法被调用直到执行结束的过程就对应着一个栈帧在虚拟机栈中入栈和出栈的过程。是线程私有的。其中局部变量表存放了存放了编译器可知的基本数据类型、对象引用和返回地址。局部变量表所需的内存空间在编译期完成分配,运行期间不会改变其大小。Java虚拟机规范对这个内存区域规定了两类异常:如果线程请求的栈的深度大于虚拟机所允许的的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常。事实上HotSpot虚拟机的栈是不可以动态扩展的,不过如果在创建线程的时候申请不到足够的虚拟机栈空间还是会抛出OutOfMemoryError异常,但是如果申请到了内存,则只会因为栈容量无法容纳新的栈帧而导致StackOverflowError异常。

本地方法栈

作用与虚拟机栈相似,也是线程私有的,区别是虚拟机栈用于处理Java方法(字节码)的调用过程,而本地方法栈是用于处理本地native方法的。与虚拟机栈一样,有可能会抛出StackOverflowError异常和OutOfMerroyError异常。

堆是所有线程共享的内存区域,在虚拟机启动时创建。作用就是用来存放对象实例和数组,几乎所有的对象实例和数组都是存放在堆里,同时堆也是GC所管理的内存区域。如果堆中没有足够的内存来完成实例对象的分配并且堆也无法再扩展时,Java虚拟机会抛出OutOfMemoryError异常。

方法区

线程共享的内存区域,它用于存储被虚拟机加载的类型信息、常量、static静态变量、即时编译器编译后的代码缓存等数据。java虚拟机规范中把方法区描述为堆的一个逻辑部分。在JDK6及以前,方法区使用永久代来实现,这样的话GC就能像管理Java堆一样来管理来管理方法区。在JDK7的时候HotSpot把原本存放在永久代的常量池和静态变量移到了堆当中,到了JDK8的时候彻底废弃了永久代,将方法区剩余的虚拟机加载的类型信息、即时编译器编译后的代码缓存等数据放到了元空间当中(元空间是用本地内存实现的)。Java虚拟机规范规定,除了和堆一样可以不需要连续的内存和可以选择固定大小或者可扩展外,方法去还可以选择不实现垃圾回收。如果方法区无法满足新的内存分配请求,将会抛出OutOfMemoryError异常。

常量池:Class文件中有一项信息是常量池表,用于存放编译器生成的各种字面量与符号引用,这部分内容将在类加载后存放到存放到方法区的常量池当中。但是并非只有预置入Class文件中常量池表的内容才能进入方法区的运行时常量池,运行期间也可以将新的常量放入到池中,如String类的intern()方法,该方法会把首次遇到的字符串实例放到方法区的字符串常量池当中,并返回这个字符串实例在常量池中的引用(JDK6时是拷贝一份到永久代里,但是JDK7的时候是在常量池中记录该字符串实例首次出现的实例在堆中引用并返回该引用)。

直接内存

首先强调一点:直接内存并不是虚拟机运行时内存区域的一部分。java中的NIO引入了一种基于通道channel和缓冲区buffer的IO方式,它可以使用native函数库直接分配堆外内存,然后通过一个存储在Java堆当中的DirectByteBuffer对象作为这块堆外内存的引用来操作,这样就避免了在java堆和native堆中来回复制数据。显然,直接内存大小不会受限于Java堆的大小,但是会受限于本机总内存的大小,默认大小与Java堆的大小一致,可以通过参数来设置。

Guess you like

Origin blog.csdn.net/zhang_qing_yun/article/details/119192117