目录
备注:https://louluan.blog.csdn.net/article/details/40043991 无意间浏览到一个大神写的博客,更加清楚详细,建议直接阅读大神的。
一般情况下,运行时数据区域主要包含:程序计数器、Java 虚拟机栈、本地方法栈、Java 堆、方法区这几个大的部分。对于此结构,通过图的方式进行如下展示:
1.程序计数器
程序计数器(Program Counter Register) 是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条执行字节码指令。
Java 虚拟机中多线程的执行原理是通过线程轮流切换并分配执行时间的方式来实现的,一般情况下在单核的处理器中,处理器在某一时刻只能处理一条线程中的指令。为了线程切换后能恢复到上次执行的位置,每个线程需要一个独立的计数器,每个线程之间的计数器是互不影响的。因此计程序计数器区域为“线程私有” 的内存。
如果执行的是java方法,这个计数器记录的是正在执行的虚拟机字节码指令地址。如果是native方法,计数器为空Undefined。此内存区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
2.Java虚拟机栈
同样是线程私有,描述Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。一个方法对应一个栈帧,每个方法从调用到执行完成,对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
虚拟机栈中的局部变量表存放在编译期间可知的基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型)和 returnAddress类型(指向一条字节码指令的地址)。其中 64 位长度的 long 和 double 会占用 2 个局部变量空间,其它的只占用 1 个局部变量空间。一般局部变量在帧中分配多大的局部变量空间在程序编译期间完成分配,程序运行期间不会改变局部变量表的大小。
规定的异常情况有两种:1.线程请求的栈的深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;2.如果虚拟机可以动态扩展,如果扩展时无法申请到足够的内存,就抛出OutOfMemoryError异常。
3.本地方法栈
本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用非常相似,它们之间的区别在于虚拟机栈为虚拟机执行 Java 方法(执行字节码)服务,而本地方法栈为虚拟机使用的 Native方法服务。虚拟机规范中没有对本地方法栈做强制的规定,由具体的虚拟机自己实现,Sun HotSpot 虚拟机字节将本地方法栈和虚拟机栈合二为一。同虚拟机栈类似,本地方法栈也会抛出 StackOverflowError 和 OutOfMemoryError 异常。
4.Java堆
是Java虚拟机所管理的内存中最大的一块。由所有线程共享,在虚拟机启动时创建。堆区唯一目的就是存放对象实例。Java 虚拟机规范规定:所有的对象实例以及数组都要在堆上进行分配,随着 JIT 编译器的发展与内存逃逸技术的成熟,栈上分配、标量替换优化技术使所有对象分配在堆上的结论不是那么“绝对”。
Java 堆是垃圾回收的主要区域,可将 Java 堆划分为:新生代和老年代;更细的划分新生代包含 Eden 空间、From Survivor、To Survivor 空间等。
Java 虚拟机规范规定,Java 堆可以处于物理机器上不连续的内存空间中。当堆空间无法分配扩展时,将会抛出 OutOfMemoryError 异常。
5.方法区
所有线程共享,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
当方法区无法满足内存分配需求时,抛出OutOfMemoryError
6.运行时常量池
它是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项是常量池(Const Pool Table),用于存放编译期生成的各种字面量和符号引用。并非预置入Class文件中常量池的内容才进入方法运行时常量池,运行期间也可能将新的常量放入池中(动态性),这种特性被开发人员利用得比较多的便是String类的intern()方法。
当方法区无法满足内存分配需求时,抛出OutOfMemoryError
7.直接内存
直接内存(Direct Memory) 不是虚拟机运行时数据区域的一部分,也不是 Java 虚拟机规范中定义的内存区域。这部分的内存也会被频繁使用,当内存分配空间不足时,也会抛出 OutOfMemoryError 异常。
对于直接内存使用的典型案例便是,在 JDK 1.4 之后,新加入了 NIO(New Input/Output) 类,其核心原理是基于通道 (Channel) 与 缓冲区 (Buffer) 的 I/O 方式,可以通过 Native 函数库直接分配堆外内存,然后通过存储在 Java 堆中的 DirectByteBuffer 对象作为内存的引用进行操作。 这样的优点就是在一些场景中提高性能,避免了 Java 堆和 Native 来回赋值数据的开销。
本地内存的分配不会受到 JVM 堆大小的限制,但是会受到系统本身内存大小的限制,当系统本身内存空间不够用时,也会出现 OutOfMemory 异常。