- 堆/方法区 : 线程共享(所有线程都共享空间)
- 栈/本地方法栈/程序计数器 : 线程独享(每个线程都会开辟这些内存空间)
程序计数器(PC寄存器)
- 用来存储指向下一条指令的地址,即将要执行的代码,再由执行引擎读取下一条指令,即每一个线程开始运行的时候,jvm都会为其分配一个独立的内存空间(程序计数器),来标识当前线程运行到的哪一行代码,
- 线程私有,与线程生命周期保持一致,不存在GC也不会OOM
- 作用 : 多线程情况下cup挂起和恢复线程的时候以此计数器来恢复当前线程从哪一行继续运行;
栈(栈内存)
- 线程私有,内部保存一个一个的栈帧,每个栈帧对应一个java方法
- 主管java程序的运行,保存局部变量(8种数据类型/对象的引用地址)和部分结果,参与方法的调用和返回,针对于每个线程独立存在,故线程栈,FILO : 栈数据结构的先进后出原则
- 不存在垃圾回收,但是会出现栈溢出StackOverflowError(如深度递归方法调用)
本地方法栈
java调用c、c++的动态链接库,运行里面的函数需要的栈,即JNI方法(java native Interface)
虚拟机栈
- java -XX:+PrintFlagsFinal -version | grep StackSize
- -Xss 设置
栈帧 :
- 栈中的数据以栈帧格式存储 , 每个方法都会在栈中开辟一块内存空间,存放本方法的局部变量,同时保证了变量的作用范围,又称栈帧内存空间,即一个方法的压栈会为此方法开辟一个栈帧空间存放该方法的数据;
栈元素:
- 局部变量表 : 最基本的存储单元为Slot(变量槽,this对象占用索引0的位置),将操作数栈赋值的数据存放到局部变量表,如局部变量值,对象类型地址值等,对象地址值直接指向堆空间,其中的变量也是垃圾回收个GC Root一个指标
- 操作数栈 : 程序在运行过程中,存放操作数的临时的内存空间(也是栈数据结构)
- 动态链接 : 指向运行时常量池中该栈帧所属的方法引用
- 方法出口 : 记录当前方法执行完成后回到哪个方法继续执行,如:一个方法包含多个方法,内部方法执行完成后需要回到这个方法继续执行
- 附加信息等,通过javap -c xxx.class反编译字节码文件,参考jvm指令手册
方法区
方法区是规范,永久代、元空间是具体实现。或者说,方法区是Java中的接口,永久代、元空间是Java中接口的实现类;
存储 : 常量 / 静态变量 / 类型信息(类信息, 变量信息, 方法信息 ) /即时编译器编译后的代码缓存
- 类型信息
a. 这个类的完整有效名称,直接父类的完整有效名称
b. 类型的修饰符public/abstract/final等
c. 类型直接接口的有序列表 - 变量信息
a. 变量名称/类型/修饰符等
b. 静态变量随着类加载而加载,被类的所有实例共享,即使没有实例也可以访问到 - 方法信息
a. 方法名称/返回类型/参数/修饰符
b. 方法的字节码/操作数栈/局部变量表等
c. 异常表
当静态变量为对象类型时,方法区的静态变量存储对象的地址值,指向堆空间;不同类加载器在元空间有各自存储
- 与java堆一样,是一个线程共享的区域,在jvm启动的时候被创建,实际的物理内存空间和java堆一样都是可以不连续的, 空间大小可配置;
- 方法区的大小决定了系统可以保存多少个类,如果系统类过多,会导致方法区溢出,抛出OOM(1.7是永久代PermGen space , 1.8是Metaspace)
- 关闭jvm就会释放方法区内存;
运行时常量池 :
- 常量池表(Constant Pool Table) :
a. class文件的一部分,
b. 用于存放编译生成的各种字面量和对类型/属性/方法的符号引用,
c. 这部分内容在被加载后存放到方法区的运行时常量池中; - 运行时常量池 :
a. 具有动态性
b. 类似于传统语言中的符号表,
c. 创建类/接口的运行时常量池时,如果所需空间超过了方法区可以提供的最大值,则会抛出OOM
永久代被元空间替代
- 永久代使用jvm内存,为永久代设置空间大小很难确定
- 元空间并不在虚拟机中,使用本地内存,默认情况下空间大小只受本地内存限制
- 对永久代的调优很困难
元空间
java -XX:+PrintFlagsFinal -version | grep Metaspace
$ java -XX:+PrintFlagsFinal -version | grep Metaspace
uintx InitialBootClassLoaderMetaspaceSize = 4194304 {product}
uintx MaxMetaspaceExpansion = 5451776 {product}
uintx MaxMetaspaceFreeRatio = 70 {product}
uintx MaxMetaspaceSize = 4294901760 //元空间最大内存 {product}
uintx MetaspaceSize = 21807104 //元空间最小内存 {pd product}
uintx MinMetaspaceExpansion = 339968 {product}
uintx MinMetaspaceFreeRatio = 40 {product}
bool UseLargePagesInMetaspace = false {product}
java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)
元空间调优
- -XX:MetaspaceSize 最小内存
- -XX:MaxMetaspaceSize 最大内存
- 设置最小内存和最大内存一样大
○ 防止内存开辟/削减带来的内存抖动 - 大小调成多少
○ 物理内存的1/32
堆空间
- java堆区在jvm启动的时候被创建,空间大小也就确定了(大小可以调节),是jvm管理的最大的一块空间,所有的线程共享堆空间,但是也可以划分线程私有的缓冲区(TLAB : Thread Local Allocation Buffer);
- 几乎所有的对象实例/数组实例都分配到堆空间(可能存在栈上分配)
- 方法执行结束后,堆中的对象不会马上被移除,只有在垃圾回收的时候才会被移除,堆是垃圾回收的重点区域;
Eden : s0 : s1 = 8 : 1 : 1(默认,可调节)
堆内存对象分配
- Eden区内存满的时候,会由字节码执行引擎触发minor gc(young GC轻GC),进行初步垃圾回收
a. GC root : 线程本地变量,静态变量,本地方法栈的局部变量(方法局部对象变量)等
b. 从栈和方法区找出所有gc root(栈的对象类型的成员变量,方法区的对象类型的静态变量等),从gc root出发,找其引用的对象(对象可达性分析),在引用链的对象都是非垃圾对象,将整个链路的所有对象通过垃圾收集算法移到Survivor区 (如s0)
c. 没有在gc root引用链的对象会留在Eden区
d. jvm虚拟机会在bc操作之后直接清除Eden区内存,实现垃圾清除(垃圾回收) - 当上一次minor GC后 ,Eden区再次满的时候,会再次出发minor Gc,此时会将年轻代的Eden区和有对象的Survivor区(如s0)一起做一次回收,将再次存活的对象移到Survivor没有对象的区(如s1)
- 最开始从的Eden到s0,再从s0到s1,s1再到s0,循环往复每一次都会对存活对象分代年龄+1
- 当满足新生代进入老年代规则时,会通过垃圾收集算法移到老年代 (如: 对象类型静态变量,缓存对象,连接池对象,Spring容器中的Bean等)
- 当老年代满的时候,就会通过字节码执行引擎开启Full GC,进行整改堆空间的内存回收,
- 当full GC之后,老年代还是满,此时就会OOM
对象的创建过程
- 对象的类是否被加载/链接/初始化
- 虚拟机接收到一个new指令,首先去检查这个指令的参数能否在MetaSpace的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经加载/解析/初始化(即判断类元信息是否存在), 如果没有,那么在双亲委派模式下,找到对应的class文件,如果没有找到文件就会抛出ClassNotFoundException异常,如果找到则进行类的加载,并生成对应的Class类对象;
字节码执行引擎
- 操作方法区代码执行,调用方法实现
- 负责修改记录程序计数器
- 对 minor gc(轻GC) 开启垃圾收集线程,进行垃圾回收