JVM 内存分区

目录

一、JVM内存分区

Java虚拟机(Java Virtual Machine, JVM)是在运行阶段执行Java字节码(.class) 的虚拟操作系统。引入JVM是为了实现了对象内存的自动分配和回收 ,不用向C++一样手动分配和回收内存,编码更简单,但有得有失,由于引入了JVM,编译阶段无法直接编译成指令序列,而是运行阶段实时编译,运行速度相较于编译型语言慢

JVM在运行过程中会把它管理的内存划分成若干个不同的数据区域:

1.程序计数器

CPU对于指令是顺序执行的,每次读取内存空间中的一部分数据(指令)执行,程序计数器就是下一条要执行的指令的内存地址,所以每个线程(CPU)都有自己的程序计数器。

线程之间的切换可能当前线程并未执行完,那么就要保存程序计数器的内容,等到线程再次获得CPU后恢复程序计数器现场,实现线程从上次中断的位置继续执行

2.栈

当调用 start()方法 时开启新的线程,JVM就会为新的线程分配独立栈空间 ,并在栈空间中执行run()方法。当run()方法执行完毕,线程被销毁时栈空间也会被释放

Java 线程

每一次方法 调用都会有一个对应的栈帧被压入栈中,每一个方法执行完(正常/异常)栈帧被弹出,每个栈帧中都拥有:

  • 局部变量表:存放数据、对象引用。
  • 操作数栈:用于存放方法执行过程中产生的中间计算结果、临时变量。
  • 动态链接所有方法的可执行二进制指令都保存在方法区中 ,而要调用这些方法,就需要拿到方法在常量池中的符号引用才能定位到方法区的方法代码执行 ,由于多态的存在(父类引用指向一个子类对象),编译阶段javac无法确定引用执行的是父类的方法还是子类的方法,就需要动态链接确认要执行的方法并指向常量池中的符号引用。

3.堆

堆是所有线程共享的一块内存区域,用于存放对象实例,是垃圾回收器管理的主要区域。

堆又细分为多个空间,目的是更有效的分配和回收内存 。默认首先在Eden为对象分配空间,在垃圾回收器执行后,如果该对象还存在那么将对象转移到S0、S1,当对象用于记录年龄的4bit=1111时会进入Tenured

这里可能会问为什么要有Eden、S0、S1,直接用Eden不行吗?

首先这是一种垃圾回收算法,分区机制 是考虑到将存活的对象转移到新的连续内存空间中保证死亡对象回收后不会有内存碎片问题,导致空间不足。

其次这种转移方式是为了统一更新存活对象的年龄,在转移时更新避免遗漏。

具体见:JVM 垃圾回收机制

4.方法区(元空间MetaSpace)

线程共享的内存区域,运行阶段,JVM中的类加载器读取.class文件,在方法区中创建该类的Class对象用于反射操作,对象中保存了类的格式和方法的字节码指令

5.字符串常量池

字符串常量池位于堆空间中,是JVM为了减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免引用类型String对象的重复创建。

  • 每一个唯一字符串会在常量池中创建对应的对象,堆中String对象存储常量池中对象的内存地址,并返回给引用
  • String s1="abcdef";直接将常量池中的字符串对象赋值给当前引用,不会再堆中实例化额外对象,如果常量池中没有"abcdef"则会在常量池中创建字符串对象。
  • String s2 = new String("xy");首先在堆空间实例化对象并指向常量池中的对象,然后返回给当前引用,如果常量池中没有"xy"则会在常量池中创建新的字符串对象。

以下代码创建了几个对象?

复制代码
    String s1 = new String("abc");
    String s2 = new String("abc");

答案:3个。字符串常量池中创建一个"abc", 堆内存中有两个String 对象。

二、对象创建过程

1.类加载

运行阶段,JVM中的类加载器读取.class文件,在方法区中创建该类的Class对象用于反射操作,对象中保存了类的格式和方法的字节码指令 ,。如果该类的父类还没有被加载,那么会首先加载父类

JVM 类加载器

2.类加载检查

虚拟机遇到一条new指令时,首先根据全限定类名检查Class对象是否存在,如果没有,那必须先进行类加载。

2.分配内存

类加载后,JVM从堆中划分一块内存空间给当前对象。分配方式有两种:

  • 指针碰撞 :已分配的内存全部整合到一边,空闲内存放在另一边,中间有一个分界指针,向空闲内存方向移动指针即可划分一块内存空间
  • 空闲列表 :JVM维护一个列表,该列表中会记录哪些内存块是可用的,从列表中找一块儿足够大的内存块划分给对象,最后更新列表记录。

内存分配的并发问题:分配内存的过程由于多处理器也会产生并发问题,JVM的思路是首先为每个线程预分配一块堆空间 ,线程操作自己的堆空间不会有并发问题,当分配的空间不够了,使用CAS+自旋保证内存分配的原子性

3.初始化0值

内存分配完成后,JVM将分配到的内存空间内容都初始化为0,这一步是为了保证将原来使用这片内存空间的对象数据清空,防止脏读

4.设置对象头

JVM对对象进行必要的设置:

  • 标记字段(Mark Word):对象的锁状态标志 、线程持有的锁、4bit对象年龄(熬过一次GC年龄+1)。
  • 类型指针(Klass pointer):指向类定义在方法区存放的位置

5.执行init()方法(构造方法)

设置成员变量的初始值。

相关推荐
成为你的宁宁3 小时前
【Zabbix运维监控实战(附图文教程):Nginx 服务可用性、连接请求状态、CPU 内存占用与 JVM(Jar 包 / Tomcat)全维度监控】
运维·jvm·nginx·zabbix
姓蔡小朋友3 小时前
JVM 垃圾回收
jvm
杨杨杨大侠5 小时前
深入理解 LLVM:从编译器原理到 JIT 实战
java·jvm·编译器
鱼跃鹰飞6 小时前
怎么排查线上CPU100%的问题
java·jvm·后端
小当家.1057 小时前
JVM/八股详解(下部):垃圾收集、JVM 调优与类加载机制
java·jvm·面试
芒克芒克7 小时前
JVM性能监控
java·jvm
码农阿豪8 小时前
远程调试不再难!Remote JVM Debug+cpolar 让内网 Java 程序调试变简单
java·开发语言·jvm
程序猿20231 天前
MAT(memory analyzer tool)主要功能
jvm
期待のcode1 天前
Java虚拟机的非堆内存
java·开发语言·jvm