关于JVM内存结构模型的那些事

众说周知,Java语言号称跨平台,一次编写,到处运行,而这离不开Java虚拟机(JVM)的存在。

Java并非JVM的唯一语言选择,Java虚拟机是定义了自己的JVM规定(字节码文件.class),只要符合JVM规范便可在JVM上运行。如今也出现了其他一些可以在Java虚拟机上运行的语言,例如Kotlin、Scala。理解JVM内存模型,也能帮助我们更好的理解程序的运行。

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区,不同的区域有着不同的作用,具体如下:

上图中,灰色部分则是线程共享的数据区,白色的则为线程隔离。各个区域的作用简单可以总结成如下:

虚拟机栈 线程私有,生命周期跟线程相同,存储局部变量表,操作数栈,动态链接以及方法的出口等信息。每一个方法从调用到执行接口,就对应于一个栈帧在虚拟机栈中的进栈和出栈
Java堆 线程共享,在虚拟机启动时创建,存储所有创建的对象实例。
方法区 线程共享,该区域跟堆很类似,存储是已被加载的类信息,常量,静态变量以及即时编译后的代码,常量池就在该区域。
本地方法栈 线程私有,为虚拟机使用到的Native方法服务
程序计数器 线程私有,记录程序当前运用的所在的行数

Java堆是JVM管理内存中最大的一块区域,存放的是对象的实例而对象存在生命周期,因而为了避免内存溢出,也伴随着出现JVM对内存的分配和回收的问题。

内存的分配,虚拟机遇到new指令时,首先会去检查该指令的参数能否在常量池找到这个类的符号引用,并且检查这个类是否被加载、解析和初始化过。如果没有,则先执行类加载过程。如果已被加载,则为对象分配内存空间,分配的方式有两种:

  • 指针碰撞,通过移动指针分配空闲空间,因此内存绝对规整
  • 如果已使用和空闲内存相互交错,没法使用指针碰撞,则采用空闲列表。JVM需要维护一个列表记录哪些内存块可用

常量池存在于方法区中,是方法区的一部分。存放编译时期生产的各种字面量和符号引用。字面量接近于Java中的常量,而符号引用包括:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。JVM对方法区的回收主要就集中在对常量池的回收和对类的类型的卸载。

从内存回收的角度看,Java堆又分成新生代和老年代,新生代又分为Eden空间,Survivor空间,两者默认比例是8:1。对象创建时会默认存在Eden区域,新生代进行GC(垃圾回收)时候,会将存活的对象移动到Survivor空间,同时存活年龄+1,当对象的存活年龄大于15则会进入老年代。如果对象创建时太大,新生代触发一次GC后对象放不下也会直接进入老年代。

分代的主要目的是为了更好的内存回收和空间分配。年轻代中的存在许多生命周期比较短的对象,而老年代中的对象大部分生命周期较长,出于回收效率的考虑,JVM可以针对不同的空间执行不同的回收策略,像HotSpot对于新生代采用复制算法,老年代则采用标记清除算法。

猜你喜欢

转载自blog.csdn.net/hsf15768615284/article/details/91350642