内存结构
内存结构是 JVM 中非常重要的一部分,是非常重要的系统资源,是硬盘和 CPU 的桥梁,承载着操作系统和应用程序的实时运行,又叫运行时数据区
JVM 内存结构规定了 Java 在运行过程中内存申请、分配、管理的策略,保证了 JVM 的高效稳定运行
- Java1.8 以前的内存结构图:
- Java1.8 之后的内存结果图:
线程运行诊断:
定位:jps 定位进程 ID
jstack 进程 ID:用于打印出给定的 Java 进程 ID 或 core file 或远程调试服务的 Java 堆栈信息
常见 OOM 错误:
java.lang.StackOverflowError
java.lang.OutOfMemoryError:java heap space
java.lang.OutOfMemoryError:GC overhead limit exceeded
java.lang.OutOfMemoryError:Direct buffer memory
java.lang.OutOfMemoryError:unable to create new native thread
java.lang.OutOfMemoryError:Metaspace
虚拟机栈
基本概念
Java 虚拟机栈:Java Virtual Machine Stacks,每个线程运行时所需要的内存
每个方法被执行时,都会在虚拟机栈中创建一个栈帧 stack frame(一个方法一个栈帧)
Java 虚拟机规范允许 Java 栈的大小是动态的或者是固定不变的
虚拟机栈是每个线程私有的,每个线程只能有一个活动栈帧,对应方法调用到执行完成的整个过程
每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存,每个栈帧中存储着:
局部变量表:存储方法里的 Java 基本数据类型以及对象的引用
动态链接:也叫指向运行时常量池的方法引用
方法返回地址:方法正常退出或者异常退出的定义
操作数栈或表达式栈和其他一些附加信息
设置大小
设置栈内存大小:-Xss size
-Xss 1024k
- 在 JDK 1.4 中默认为 256K,而在 JDK 1.5+ 默认为 1M
虚拟机栈特点
栈内存不需要进行GC,方法开始执行的时候会进栈,方法调用后自动弹栈,相当于清空了数据
栈内存分配越大越大,可用的线程数越少(内存越大,每个线程拥有的内存越大)
方法内的局部变量是否线程安全:
如果方法内局部变量没有逃离方法的作用访问,它是线程安全的(逃逸分析)
如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全
异常:
栈帧过多导致栈内存溢出 (超过了栈的容量),会抛出 OutOfMemoryError 异常
当线程请求的栈深度超过最大值,会抛出 StackOverflowError 异常
局部变量
局部变量表也被称之为局部变量数组或本地变量表,本质上定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量
表是建立在线程的栈上,是线程私有的数据,因此不存在数据安全问题
表的容量大小是在编译期确定的,保存在方法的 Code 属性的 maximum local variables 数据项中
表中的变量只在当前方法调用中有效,方法结束栈帧销毁,局部变量表也会随之销毁
表中的变量也是重要的垃圾回收根节点,只要被表中数据直接或间接引用的对象都不会被回收
局部变量表最基本的存储单元是 slot(变量槽):
参数值的存放总是在局部变量数组的 index0 开始,到数组长度 -1 的索引结束,JVM 为每一个 slot 都分配一个访问索引,通过索引即可访问到槽中的数据
存放编译期可知的各种基本数据类型(8种),引用类型(reference),returnAddress 类型的变量
32 位以内的类型只占一个 slot(包括 returnAddress 类型),64 位的类型(long 和 double)占两个 slot
局部变量表中的槽位是可以重复利用的,如果一个局部变量过了其作用域,那么之后申明的新的局部变量就可能会复用过期局部变量的槽位,从而达到节省资源的目的
操作数栈
栈:可以使用数组或者链表来实现
操作数栈:在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push)或出栈(pop)
保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间,是执行引擎的一个工作区
Java 虚拟机的解释引擎是基于栈的执行引擎,其中的栈指的就是操作数栈
如果被调用的方法带有返回值的话,其返回值将会被压入当前栈帧的操作数栈中
栈顶缓存技术 ToS(Top-of-Stack Cashing):将栈顶元素全部缓存在 CPU 的寄存器中,以此降低对内存的读/写次数,提升执行的效率
基于栈式架构的虚拟机使用的零地址指令更加紧凑,完成一项操作需要使用很多入栈和出栈指令,所以需要更多的指令分派(instruction dispatch)次数和内存读/写次数,由于操作数是存储在内存中的,因此频繁地执行内存读/写操作必然会影响执行速度,所以需要栈顶缓存技术
动态链接
动态链接是指向运行时常量池的方法引用,涉及到栈操作已经是类加载完成,这个阶段的解析是动态绑定。当一个方法要调用其他方法,需要将常量池中指向方法的符号引用转化为其在内存地址中的直接引用。
-
为了支持当前方法的代码能够实现动态链接,每一个栈帧内部都包含一个指向运行时常量池或该栈帧所属方法的引用
- 在 Java 源文件被编译成的字节码文件中,所有的变量和方法引用都作为符号引用保存在 class 的常量池中
返回地址
Return Address:存放调用该方法的 PC 寄存器的值
方法的结束有两种方式:正常执行完成、出现未处理的异常,在方法退出后都返回到该方法被调用的位置,两者区别:通过异常完成出口退出的不会给上层调用者产生任何的返回值。
正常:调用者的 PC 计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址
异常:返回地址是要通过异常表来确定
正常完成出口:执行引擎遇到任意一个方法返回的字节码指令(return),会有返回值传递给上层的方法调用者
异常完成出口:方法执行的过程中遇到了异常(Exception),并且这个异常没有在方法内进行处理,本方法的异常表中没有搜素到匹配的异常处理器,导致方法退出