了解虚拟机是怎么使用内存的, 有助于我们解决和排查内存泄漏和溢出方面的问题. 详解 java 虚拟机内存的各个区域, 分析这些区域的作用服务对象以及可能发生的问题.
一, 运行时数据区域
java 虚拟机在执行 java 程序的过程中会把它所管理的内存区域划分为若干个不同的数据区域. 这些数据区域都有各自的用途, 以及创建时间和销毁时间. 有的随着虚拟机进程的而存在, 有些区域则依赖用户线程的启动和结束而建立和销毁. 根据 java 虚拟机的规范, jvm 管理的内存将包括以下几个运行时数据区域.
1.1 程序计数器
程序计数器是当前线程所执行程序字节码的行号计数器, 字节码解释器根据这个程序计数器的值来选取下一条需要执行的字节码, 实现程序的分支, 循环, 跳转, 异常处理 和线程控制等基本功能. 在 java 中, 每个线程的执行需要操作系统轮换的分配处理机, 因此每个线程都有自己的程序计数器, 各个线程之间的计数器互不影响, 独立存储, 计数器占用空间不大. 因此计数器是线程私有的内存. 当线程正在执行 java 方法时, 计数器的值为当前字节码指令的地址. 当执行的是本地方法是, 计数器的值为空. 计数器是唯一一个不会发生 OOM 的数据区域.
1.2 java 虚拟机栈
虚拟机栈和计数器一样, 和线程私有的, 它的生命周期和线程相同. 虚拟机栈是 java 方法执行的内存描述: 每一个方法的执行都会为其创建一个栈帧, 用于存储局部变量表, 操作数栈, 动态链接和方法的出口信息, 局部变量表存储了编译期可知的基本数据类型[byte,int,float,double,char,short,long,boolean], 对象引用类型(不是实际的对象, 可能只是对象的内存地址), 进入一个方法后, 局部变量表的内存空间在编译是完全确认的, 方法运行期间不会改变其大小. 每一个方法从调用到执行完成, 都对应着一个栈帧在虚拟机栈中从入栈到出栈的过程.
该区域可能有两类异常: StackOverflowError: 线程请求的深度大于虚拟机允许的深度; OutOfMemory: 如果虚拟机栈可以动态扩展, 但是扩展的时候无法申请到足够的内存.
1.3 本地方法栈
与 java 虚拟机栈类似, 但是不同的是 Java 虚拟机栈是为 java 方法服务(字节码), 本地方法栈是为非 java 语言但是虚拟机又使用到的方法服务的(比如类加载器中最顶层的实现使用的是 c++). 同样的, 本地方法栈也会抛出 StackOverflowError 和 OutOfMemory 异常.
1.4 java 堆
java 堆是被所有线程共享的一大片内存区域, 其作用就是为创建的实例对象或者数组分配内存, 也因此 java 堆是 GC 的主要针对区域, 为了更好实现 GC 的效果, 还把这一大块 java 堆内存分为不同的区域: 叫什么 Eden 区, From 区, Survivor,To Survivor; 还有的从线程的角度甚至把这线程共享的 java 堆给各个线程分配了自己的缓冲区. 真是树大招风啊!! 但是无论 java 堆内存从什么角度被怎么划分, 在这 java 堆中存放实例对象或者数组这一事实无法更改. 进一步划分的目的是为了更加高效滴利用这一内存中的宝地罢了. 根据规范, java 堆的内存在物理上不要求连续, 只要逻辑上连续就好了.
该区域可能抛出的异常: OutOfMemory: 堆中没有内存足以给实例分配了
1.5 方法区
与 java 堆一样, 也是线程共享的内存区域, 主要用于存放已经被虚拟机加载的类信息, 常量, 静态变量, 以及实时编译的数据. 虽然我们很不愿意把方法区和 java 堆混为一谈, 但是从 GC 的角度, GC 就是将方法区看成是 java 堆的永久代. 虽然 GC 在这 "永久代" 上的效果总不是那么理想, 但是却必不可少, GC 在永久代的目的是针对常量池的回收以及类型卸载.
该区域可能出现的异常: OutOfMemory: 方法区无法满足内配时.
1.6 运行时常量池
线程共享的内存区域, 运行时常量池属于方法区的一部分. java 文件编译的 Class 文件中, 除了类的基本信息 (版本, 字段, 方法, 接口等) 之外, 还有就是常量池, 用于存放编译期间生成的字面量和符号引用(我觉得就是 static,final 修饰的字段吧), 这部分内容在类被加载后就被放入运行时常量池了. 除了 Class 文件中的符号引用外, 还把翻译出来的直接引用也存储在运行时常量池了. 运行时常量池还可以动态滴加入程序运行时生成的常量, 比如 String.
运行时常量池是方法区的一部分, 那么自然也受到方法区内存的限制, 当无法申请到内存时将抛出 OutOfMemory.
二, 直接内存
直接内存并不是 java 虚拟机运行时数据区域的一部分, 也不是虚拟机规范中定义的内存区域, 但是这部分内存常常也被频繁使用, 而且也可能导致 OutOfMemory, 直接内存的分配虽然不受 java 堆大小的限制, 但是既然是内存, 那么必然受到本机物理内存的限制, 我们在配置 java 虚拟机内存的时候, 一定要注意 java 虚拟机各部分的内存总和不能大于本机实际的物理内存.
三, No Picture You Say a j8
来源: https://www.cnblogs.com/ytuan996/p/10612468.html