jvm内存布局

jvm

Posted by CHuiL on April 6, 2021

jvm内存布局

堆区(heap)

主要存储着几乎所有的实例对象,占用内存空间是所有区域中最大的,线程之间共享使用,堆由垃圾收集器自动回收;堆的大小可以设置,可以设置为在一定大小范围内动态调整,也可以将最大最小值设置为一样来表示为固定大小,线上生产环境中为了避免不必要的GC后调整堆大小带来的额外压力,建议将其大小固定;堆也是OOM故障的主要发生地;

区域主要分为新生代和老年代,新生代有Eden区和Survivor区,Survivor区有S0和S1区域;

新生代

  1. Eden区,绝大多数对象在Eden区生成,只要生成时所需要内存新生代无法容纳才会将其直接放在老年代;当Eden区满了之后会触发Young Garbage Collection 即YGC进行垃圾回收;
  2. Servivor区,有S0和S1,在垃圾回收过程中,将Eden区中的对象和S中的所有存活对象转移到未使用的那块S,并将正在使用的空间全部清除;该区域中的对象有一个计数器,当对象在两个S中反复横跳超过阈值(默认15),则会被移到老年代,如果该值为1,则进入s后直接移到老年代;如果该区域无法容纳某个对象,该对象也会被移到老年代;

老年代

主要存在新生代中无法容纳的大对象,以及从Servivor中存活过久转移过来的对象;并且在老年代也无法分配空间给对象时,会触发Full Garbage Collection即FGC;

YGC

流程可以看下图,在Eden无法分配内存给对象时会触发,将没有引用的对象全部回收,并将存活的对象移动到Servivor区域中;

FGC

待了解;

对象分配简要与gc流程

方法区(元数据区)

在本地内存中分配,存放类元信息(类结构信息)、字段、静态属性、方法代码、常量等;线程共享;

运行时常量池

这是方法区的一部分。如果仔细分析过反编译的类文件结构,你能看到版本号、字段、方法、超类、接口等各种信息,还有一项信息就是常量池。Java 的常量池可以存放各种常量信息,不管是编译期生成的各种字面量,还是需要在运行时决定的符号引用,所以它比一般语言的符号表存储的信息更加宽泛。

虚拟机栈

参考第一个图和下面这个图,一个虚拟机栈中有很多栈帧,一个栈帧描述一个方法调用执行的内存区域,每个方法从开始调用到执行完成的过程,就是栈帧的入栈到出栈的过程,是方法运行的基本结构;虚拟机栈是线程私有的;在一个时间点,一个线程对应的只有一个活动的栈帧,叫做当前栈,如果该方法调用了其他方法,则会为该方法创建一个新的栈帧,一直到它返回结果或者执行结束。

操作栈

局部变量表

存放方法参数和局部变量的区域;

操作栈

初始是一个状态为空的桶式结构栈,在方法执行过程中会有各种指令往栈中写入和提取信息;JVM的执行引擎是基于栈的执行引擎,这里的栈指的就是操作栈;字节码指令集定义都是基于栈类型的;

动态连接

每个栈帧中包含一个在常量池中对当前方法的引用,目的是支持方法调用过程中的动态连接;(不懂)

方法返回地址

  1. 正常退出:正常执行到任何地方的返回字节码指令;
  2. 异常退出:

两种退出方式都将返回至当前被调用的位置;

退出的方式
  1. 返回值压入上层调用栈帧;
  2. 异常信息抛给能够处理的栈帧;
  3. PC计数器指向方法调用后的下一个指令;

本地方法栈

线程对象私有,这个栈主要通过JNI(Java Natice Interface)来提供被本地方法,使其能够访问虚拟机运行时的数据区;因为本地方法调用后会进入JVM无法约束的世界(调用系统底层的本地方法),

程序计数器

每个线程创建后,都会产生自己的程序计数器和栈帧,用来保存需要运行的指令和记录下一跳运行的指令。程序计数器用来存放执行指令的偏移量和行号指示器等。线程私有;任何时间,一个线程都只有一个方法在执行。也就是所谓的当前方法。

java线程与内存

一个类,类中方法,以及变量和实例对应存放的位置关系


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;
}

image 首先,是类自身相关的,类本身的基础信息,存放在元数据区(方法区)中;而与类有关的字段,类字段都存放在堆中(静态变量也在1.7后放在堆中);而方法中声明的变量,引用对象本身存放在线程的栈中,而实际对象存储在堆上,基本类型变量则存放在线程栈中;

常见OOM的区域

  • 堆:可能存在内存泄漏问题,也可能时堆的大小不合理,或者jvm处理引用不及时,导致堆积起来,内存无法释放。
  • java虚拟机栈和本地方法栈:不断递归调用没有退出条件,导致不断压栈。jvm会抛出StackOverFlowError。但是,如果jvm试图扩展线程栈空间的时候失败,则会抛出OutOfMemoryError。