一、JVM运行时内存区域
(一)线程私有区
1、程序计数器
(1)当前线程执行时的字节码行号指示器;
(2)每条线程都有且只有一个程序计数器,线程之间不相互干扰,生命周期与线程一致;
(3)是JVM所有内存区域中唯一不会发生OOM的区域,GC机制不会触及的区域;
2、虚拟机栈
(1)每个Java方法的调用到执行结束,对应着虚拟机栈中的一个栈帧的从入栈到出栈的过程,生命周期与线程一致;
(2)一个栈帧需要分配多大的内存空间,在编译期就已经确定了;
(3)局部变量表:
text
a、是一个由槽(slot)组成的数组,用于存放当前实例对象的引用信息、方法参数以及方法体内定义的基本数据类型变量、对象引用以及返回地址等信息;
b、在执行方法时,执行引擎会根据索引值去访问局部变量表的指定槽位,然后将数据加载到操作数栈中进行执行;
c、当局部变量表的一个数据失去作用并没有保持引用关系时,虚拟机会尝试将原本存储该数据的槽位用于分配新的数据;
(4)操作数栈:
text
a、用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间;
b、局部变量表是通过下标索引去访问存储的数据,而操作数栈中则是通过标准的压栈、出栈的方式完成数据访问;
(5)动态链接:
text
a、虚拟机栈中的每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用;
b、动态链接的作用就是为了将符号引用转换为调用方法的直接引用;
c、常量池位于编译后生成的class字节码文件中,运行时常量池位于运行期间的元数据空间/方法区中;
(6)方法出口:
text
a、正常完成出口:①复原上层方法的局部变量表以及操作数栈;②如果当前方法有返回值的情况下,把返回值压入调用者方法栈帧的操作数栈中;③将PC计数器的地址指向改为方法下一条指令的位置;
b、异常完成出口:返回地址通过异常处理器表来确定;不会给上层调用者返回任何值;
(7)附加信息:各大厂商在实现JVM时,会增加一些《虚拟机规范》里没有描述的信息到栈帧中;
(8)特点:
text
a、虚拟机栈这块内存区域不存在垃圾回收,但是存在OOM;
b、StackOverflowError:当前线程请求的栈深度大于虚拟机栈所允许的深度时抛出该异常;
c、OutOfMemoryError:如果扩展时无法申请到足够的内存空间会抛出OOM异常;
d、可以通过'-Xss'参数调整每条线程的虚拟机栈的大小;
3、本地方法栈
(1)用于执行C所编写的Native本地方法;
(2)本地方法执行是在os中执行的,并非在JVM中执行的;
(二)线程共享区
1、Java堆空间
(1)可以通过参数-Xms
和-Xmx
指定堆的起始内存大小和最大内存大小;
(2)堆空间在物理上可以是不连续的,只需要逻辑上视为连续即可;
(3)由GC器决定了运行时的堆空间会被划分为何种结构(分代堆空间、不分代堆空间);
(4)JDK7及之前的堆空间内存划分:
(5)JDK8的堆空间内存划分:取消永久代,将方法区、元数据空间、运行时常量池移出堆外,加入本地内存的元数据区;
(6)JDK9:G1将Java堆划分为多个大小相等的独立的Region区域,逻辑分代,物理不分代;
(7)JDK11:ZGC中的Region区不存在分代的概念,它只是简单的将所有Region区分为了大、中、小三个等级,逻辑和物理都不分代;
2、本地内存
(1)元数据空间和直接内存这两块区域,并不处于OS为JVM分配的内存中,而是直接使用物理机的内存进行数据存放,但是本地内存还是会被JVM管理;
(2)元数据空间:是JDK8移除掉方法区之后的产物,主要用于存放运行时常量池(字面量、符号引用)和类信息,字符串常量池和静态变量被放置到堆中;
(3)直接内存:访问直接内存的速度会超出堆内存,Java的NIO可以允许Java程序直接使用本地的直接内存存储数据缓冲;可以手动管理回收直接内存;
二、内存溢出OOM
(一)堆空间OOM
1、引发原因
(1)堆中存活对象过多无法回收,新对象没有足够内存进行分配;
(2)因代码原因导致运行过程中出现OOM,如无限递归、死循环、用完后不释放等;
(3)运行过程中出现了内存泄露,泄露问题一点点将内存蚕食掉了,导致最终可用内存变得很小;
2、问题解决
(1)如果确定是代码问题,则通过工具定位到具体的代码,然后对代码进行改正;
(2)如果确实是所分配的堆空间无法保障JVM的正常运行了,那么应该分配更大的堆空间;
(3)如果是因为内存泄露导致的OOM,那么则应该进一步定位内存泄露出现的原因;
(二)虚拟机栈OOM
1、引发原因
(1)无限递归导致产生大量栈帧(SOF问题);
(2)无限创建新线程导致耗尽了物理内存;
2、问题解决
(1)优化代码,不要产生无限递归;
(2)合理使用线程,使用线程池管理线程;
(三)元数据空间OOM
1、引发原因
(1)加载的类信息或者动态生成的类过多;
(2)JIT生成的热点代码过多;
(3)运行时常量池溢出;
2、问题解决
(1)通过对应的参数调大分配的空间;
(2)合理使用即时编译策略;
(3)代码中合理定义常量;
(四)直接内存OOM
1、引发原因
(1)申请后没有合理释放,在FullGC来临之前耗尽了分配的所有空间;
(2)申请的内存大小超出了直接内存的可用内存大小;
2、问题解决
(1)尽量保证自在使用完直接内存后手动回收,不要依赖JVM的GC机制管理内存;
(2)调大直接内存的空间大小,确保有足够的内存使用;
(五)内存泄漏
1、概念
(1)申请的内存空间没有被正确释放,存储在该区域的数据使用完后没有被回收;
(2)数据已经失效,引用链依旧保持,GC无法回收;
(3)最终导致后续程序里这块内存被永远占用,内存逐渐被耗尽;
(4)典型案例:ThreadLocal、大量的static成员、未正确关闭连接、不正确的equals()
和hashCode()
、引用了外部类的内部类、非正确的重写finalize()
方法、常量字符串等;
(5)堆中的循环引用不会引发内存泄漏,因为垃圾回收使用可达性算法;
2、内存溢出与内存泄漏的区别
(1)内存溢出:内存不够用;
(2)内存泄漏:内存漏回收;