堆区(heap)
主要存储着几乎所有的实例对象,占用内存空间是所有区域中最大的,线程之间共享使用,堆由垃圾收集器自动回收;堆的大小可以设置,可以设置为在一定大小范围内动态调整,也可以将最大最小值设置为一样来表示为固定大小,线上生产环境中为了避免不必要的GC后调整堆大小带来的额外压力,建议将其大小固定;堆也是OOM故障的主要发生地;
区域主要分为新生代和老年代,新生代有Eden区和Survivor区,Survivor区有S0和S1区域;
新生代
- Eden区,绝大多数对象在Eden区生成,只要生成时所需要内存新生代无法容纳才会将其直接放在老年代;当Eden区满了之后会触发Young Garbage Collection 即YGC进行垃圾回收;
- Servivor区,有S0和S1,在垃圾回收过程中,将Eden区中的对象和S中的所有存活对象转移到未使用的那块S,并将正在使用的空间全部清除;该区域中的对象有一个计数器,当对象在两个S中反复横跳超过阈值(默认15),则会被移到老年代,如果该值为1,则进入s后直接移到老年代;如果该区域无法容纳某个对象,该对象也会被移到老年代;
老年代
主要存在新生代中无法容纳的大对象,以及从Servivor中存活过久转移过来的对象;并且在老年代也无法分配空间给对象时,会触发Full Garbage Collection即FGC;
YGC
流程可以看下图,在Eden无法分配内存给对象时会触发,将没有引用的对象全部回收,并将存活的对象移动到Servivor区域中;
FGC
待了解;
方法区(元数据区)
在本地内存中分配,存放类元信息(类结构信息)、字段、静态属性、方法代码、常量等;线程共享;
运行时常量池
这是方法区的一部分。如果仔细分析过反编译的类文件结构,你能看到版本号、字段、方法、超类、接口等各种信息,还有一项信息就是常量池。Java 的常量池可以存放各种常量信息,不管是编译期生成的各种字面量,还是需要在运行时决定的符号引用,所以它比一般语言的符号表存储的信息更加宽泛。
虚拟机栈
参考第一个图和下面这个图,一个虚拟机栈中有很多栈帧,一个栈帧描述一个方法调用执行的内存区域,每个方法从开始调用到执行完成的过程,就是栈帧的入栈到出栈的过程,是方法运行的基本结构;虚拟机栈是线程私有的;在一个时间点,一个线程对应的只有一个活动的栈帧,叫做当前栈,如果该方法调用了其他方法,则会为该方法创建一个新的栈帧,一直到它返回结果或者执行结束。
局部变量表
存放方法参数和局部变量的区域;
操作栈
初始是一个状态为空的桶式结构栈,在方法执行过程中会有各种指令往栈中写入和提取信息;JVM的执行引擎是基于栈的执行引擎,这里的栈指的就是操作栈;字节码指令集定义都是基于栈类型的;
动态连接
每个栈帧中包含一个在常量池中对当前方法的引用,目的是支持方法调用过程中的动态连接;(不懂)
方法返回地址
- 正常退出:正常执行到任何地方的返回字节码指令;
- 异常退出:
两种退出方式都将返回至当前被调用的位置;
退出的方式
- 返回值压入上层调用栈帧;
- 异常信息抛给能够处理的栈帧;
- PC计数器指向方法调用后的下一个指令;
本地方法栈
线程对象私有,这个栈主要通过JNI(Java Natice Interface)来提供被本地方法,使其能够访问虚拟机运行时的数据区;因为本地方法调用后会进入JVM无法约束的世界(调用系统底层的本地方法),
程序计数器
每个线程创建后,都会产生自己的程序计数器和栈帧,用来保存需要运行的指令和记录下一跳运行的指令。程序计数器用来存放执行指令的偏移量和行号指示器等。线程私有;任何时间,一个线程都只有一个方法在执行。也就是所谓的当前方法。
一个类,类中方法,以及变量和实例对应存放的位置关系
class Fruit {
static int x = 10;
static BigWaterMelon bigWaterMelon_1 = new BigWaterMelon(x);
int y = 20;
BigWaterMelon bigWaterMelon_2 = new BigWaterMelon(y);
public static void main(String[] args) {
final Fruit fruit = new Fruit();
int z = 30;
BigWaterMelon bigWaterMelon_3 = new BigWaterMelon(z);
new Thread() {
@Override
public void run() {
int k = 100;
setWeight(k);
}
void setWeight(int waterMelonWeight) {
fruit.bigWaterMelon_2.weight = waterMelonWeight;
}
}.start();
}
}
class BigWaterMelon {
public BigWaterMelon(int weight) {
this.weight = weight;
}
public int weight;
}
首先,是类自身相关的,类本身的基础信息,存放在元数据区(方法区)中;而与类有关的字段,类字段都存放在堆中(静态变量也在1.7后放在堆中);而方法中声明的变量,引用对象本身存放在线程的栈中,而实际对象存储在堆上,基本类型变量则存放在线程栈中;
常见OOM的区域
- 堆:可能存在内存泄漏问题,也可能时堆的大小不合理,或者jvm处理引用不及时,导致堆积起来,内存无法释放。
- java虚拟机栈和本地方法栈:不断递归调用没有退出条件,导致不断压栈。jvm会抛出StackOverFlowError。但是,如果jvm试图扩展线程栈空间的时候失败,则会抛出OutOfMemoryError。