JVM学习01——内存区域及内存溢出
运行时数据区域
Java虚拟机在执行Java程序的过程中会把它所管理的内存区域划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。根据《Java虚拟机规范(Java SE 7版)的规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域。
1.程序计数器(Program Counter Register)
- 程序计数器是一块较小的内存空间,它可以被看作是当前线程所执行的字节码的行号指示器。
- 由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间来实现的。因此,为了线程切换后能回到正确位置,每条线程都需要一个单独的程序计数器,称这类内存区域为“线程私有的内存”。
- 如果线程正在执行Java方法,则计数器记录字节码地址;如果执行Native方法,则计数器记录为空。所以是唯一一个不存在OutOfMemoryError情况的内存区域。
2.Java虚拟机栈
- 线程私有,生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型,每个方法执行都会创建一个栈郑
- 栈帧(Stack Frame):用于存储局部变量表、操作数栈、动态链接、方法出口等信息。局部变量表存放编译时的8中基本数据类型、引用类型和returnAddress类型(指向一条字节码指令地址)。
-
该区域有两种异常:
- 线程请求栈深度大于虚拟机栈深度,抛出StackOverflowError异常。
- 动态拓展时无法申请到足够内存,抛出OutOfMemoryError异常。
3.本地方法栈
与虚拟机栈功能类似,但虚拟机栈为Java方法服务,而本地方法栈为Native方法服务。也有 StackOverflowError和 OutOfMemoryError异常。
4.Java堆
- Java堆(Java Heap)是Java虚拟机管理的最大内存区域,虚拟机启动时创建,所有线程共享该内存。该内存唯一目的就是存放对象实例,几乎所有的对象实例都在此分配内存。
-
Java堆是垃圾收集器管理的主要区域,也被成为“GC堆”。Java堆可被细分为:新生代和老年代。
- 新生代: 用来存放生命周期较短的对象,而新生代又使用复制算法进行GC ,又将其按照8:1:1的比例分为一块较大的Eden空间和2个较小的From Survivor和To Survivor空间。
- 老年代:用来存放生命周期较长的对象。
- Java堆可以处于物理上不连续内存区域,但需要逻辑上连续。
-
如果堆中没有内存进行实例分配,并且堆也无法在拓展时(通过-Xmx和-Xms控制),将会抛出OutOfMemoryError异常。
- Xmx Java堆最大内存,默认值为物理内存的1/4,当可用的Java堆内存大于70%时,JVM会将内存调整至-Xms所指定的初始值
- Xms Java堆初始内存,默认值为物理内存的1/64,当可用的Java堆内存小于40%时,JVM会将内存调整至-Xmx所允许的最大值
5.方法区
- 方法区和Java堆一样,是各个线程的共享的区域,它用于存放已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- 可以处于物理上不连续内存区域,但需要逻辑上连续。
- 该区域的垃圾收集比较少见,主要针对常量池的回收和类型的卸载。
- 方法区无法满足内存分配需求时,会抛出OutOfMemoryError异常。
6.运行时常量池
- 属于方法区的一部分。
- 存放 Class 文件中的常量池(存放编译期生成的各种字面量和符号引用);翻译出来的直接引用;运行期间产生的新的常量(譬如 String 类的 intern() 方法)。
- 常量池无法申请到内存, 会抛出OutOfMemoryError异常。
7.直接内存
- 直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范定义中的内存区域。但这部分区域被频繁使用并可能引起OutOfMemoryError异常。
- NIO(New Input/Output)类中 ,可用使用 Native 函数库直接分配堆外内存,然后通过一个存储在 java 堆里面的 DirectByteBuffer 对象作为这块内存的引用进行操作,避免了在 Java 堆和 Native 堆中来回复制数据。
- 不受 java 堆大小的限制,但受本机总内存的大小及处理器寻址空间的限制,会抛出 OutOfMemoryError异常。
注:本文来源为我的《深入理解Java虚拟机-JVM高级特性与最佳实践》读书笔记,以及部分网络资料。