JVM

JVM的组成

线程共享区

  • 方法区

线程私有区

  • 虚拟机栈
  • 本地方法栈
  • 程序计数器

虚拟机栈

每个线程都会持有一块虚拟机栈, 是线程执行时的主要处理模块, 虚拟机栈包含栈帧
栈帧:由局部变量表,操作数栈,动态连接,方法出口组成

  • 局部变量表: 存放着各种基本数据类型和对象的引用
  • 操作数栈:用于执行各种计算指令
  • 动态连接:Class中存在大量的符号引用, 类加载时会转化一部分为直接引用, 方法运行时也会转化一部分为直接引用, 这部分就为动态连接, 需要将符号引用转化为直接引用
  • 方法出口:方法执行结束时的出口,返回结果或结束执行 包括异常, 返回, 和无返回

本地方法栈

由Native修饰的方法, 具体指向JVM底层的一个实现方法 , Hotspot虚拟机则是C++提供的实现, 与虚拟机栈的区别为, 虚拟机栈是由虚拟机执行Java方法(也就是字节码)服务, 本地方法栈则是本地方法服务


程序计数器

在JVM中唯一不会发生内存OOM的一块区域, 存储着当前JVM执行的字节码的行号, 主要用于线程执行时间片切换的时候, 记录线程具体执行到了哪一行


Java堆

堆主要存放对象, 根据使用的垃圾回收器不同, 堆空间的划分也不同, 大部分垃圾回收器都是基于分代划分, 所以经常会出现新生代,老年代, From区, To区, 而新的ZGC, Shenandoah则不是按照分代划分


方法区

方法区主要存已加载的类信息和字符串常量池, 基本书局类型常量池, 静态变量, 常量。是JDK6以前堆中的永久代


对象的创建

  1. 对象的创建首先会检查类是否已经被加载、解析和初始化过, 如果没有则会先执行类加载过程.
  2. 加载检查完成后, 会开始在堆中分配内存, 根据Java堆是否连续完整采用的分配策略也不同, 连续完整的则使用指针碰撞, 不连续的则使用空闲列表, Java堆是否连续完整取决于选择的垃圾回收器是否有空间整理功能,
    对于分配时带来的并发安全问题, JVM采用CAS+失败重试机制和TLAB(本地线程分配缓冲)方式
  3. 内存分配完成后会开始零值的初始化工作, 如果使用了TLAB, 这一步可以提前至TLAB分配时直接执行。这一步保证了对象的实例字段在没有初始值时就能直接使用。
  4. 执行对象头的初始化工作
  5. 真正的对象初始化工作, 赋值变量, 构造方法执行等

对象的内存布局

对象主要由对象头、实例数据、对齐填充三个部分组成

  • 对象头:主要由Mark word和类型指针组成, 如果对象是个数组,则还会包含数组的长度
  • 实例数据:主要包含我们定义的各种字段内容
  • 对齐填充:没有具体的实际意义, 主要起着占位符的作用, HotSpot虚拟机要求对象大小必须为8的整数倍, 如果定义的对象不是8的整数倍则会通过对齐填充来补全

对象头

  • Mark Word:主要存储对象自身的一些基本数据,如:hashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程的ID、偏向线程的时间戳等, 这部分数据长度在64位或32位
  • 类型指针:主要指向对象的类型元数据的指针, 虚拟机通过这个指针来确定这个对象是哪个类的实例

GC-垃圾回收器

  • Serial与Serial Old:
  • Parallel Scavenge与Parallel Old:
  • ParNew与CMS:
  • G1:
  • ZGC:

分代回收算法

  • 引用计数法:
  • 可达性分析算法:
  • 标记-整理:
  • 标记-清除:
  • 标记-复制:
  • 空间分配担保:
  • 逃逸分析:

CMS、G1、ZGC的内存划分

CMS分代划分

  • Eden区:
  • From区:
  • To区:
  • Old区:

G1内存划分

  • Region的概念:
  • Humongous:

ZGC的内存划分

  • 小对象区:
  • 中对象区:
  • 大对象区:

类加载过程


双亲委派机制

  • 破坏双亲委派机制:

posted @ 2022-02-10 11:22  和蔼的马叔叔  阅读(58)  评论(0)    收藏  举报