类加载


类加载时机

隐式加载:new 创建类的实例

显式加载:loaderClass,forName等

访问类的静态变量或静态方法

使用反射方式创建某个类或者接口对象的Class对象

初始化某个类的子类

直接使用java.exe命令来运行某个主类

类加载流程

加载

  1. 通过类全路径加载Class文件二进制流
  2. 将这个类字节流代表的静态存储结构转为方法区的运行时数据结构
  3. 堆中生成一个代表此类的java.lang.Class对象,作为访问方法区这些数据结构的入口

验证

确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的自身安全

准备

  1. 为类变量分配内存
  2. 为类变量赋初值0
  3. final类型的直接赋值完毕

解析

常量池中的符号引用转换为直接引用

初始化

执行其静态初始化器(静态代码块)和静态初始化成员变量

初始化时机

  • 使用new关键字实例化对象、访问或者设置一个类的静态字段(被final修饰、编译器优化时已经放入常量池的例外)、调用类方法,都会初始化该静态字段或者静态方法所在的类。
  • 初始化类的时候,如果其父类没有被初始化过,则要先触发其父类初始化。
  • 使用java.lang.reflect包的方法进行反射调用的时候,如果类没有被初始化,则要先初始化。
  • 虚拟机启动时,用户会先初始化要执行的主类(含有main)

类加载器类别

  1. 引导类加载器
  2. 拓展类加载器
  3. 系统类加载器
  4. 自定义加载器

两个类是否相同

  1. 全限定名一致
  2. 类加载器一致

forName和loaderClass区别

  • Class.forName()得到的class是已经初始化完成的。
  • Classloader.loaderClass得到的class是还没有链接(验证,准备,解析三个过程被称为链接)的。

双亲委派

流程

  1. 检查类是否已被加载
  2. 若没有被加载则委托父加载器加载
  3. 父加载器没有加载则findClass加载类,然后defineClass返回Class对象

SPI

https://blog.csdn.net/qq_41894099/article/details/104558522

Java对象的分配流程

  1. 确保类已执行类加载过程

当虚拟机遇到new指令时,会根据new指令的参数去常量池中查找是否有对应的符号引用,以及符号引用代表的类是否已经加载、连接、初始化流程,若否,需限制性类加载过程

2.分配内存

对象的大小在类加载完成后就已经确定,根据对象的大小,进行内存分配

根据分配方式的不同,可分为指针碰撞和空闲列表两种方式,指针碰撞适用于规整的内存,即使用含整理功能的垃圾收集器,系统使用指针碰撞,否则,使用空闲列表(实际上,使用空闲列表找到一块Linear Allocation Buffer,在该缓冲区内再使用碰撞指针分配,如此可提高分配效率)

对象空间分配流程如下:

1)如果开启了栈上分配,则尝试使用栈上分配
依赖于逃逸分析和标量替换
如果未启用栈上分配或栈上分配失败,则在堆上分配,使用CAS(cmpxchg)和失败重试的方式,确保在并发下分配成功
2)尝试TLAB分配
TLAB占用Eden空间
3)大对象直接进入老年代
典型的有大字符串和大数组
4)在Eden区分配
当Eden区没有足够的空间是,发起一次Minor GC
5)长期存活的对象进入老年代
6)动态年龄判断
年龄从小到大进行累加,当加入某个年龄段后,累加和超过survivor区域*TargetSurvivorRatio(一半)的时候,就从这个年龄段之上的年龄的对象进行晋升
7)空间分配担保
YGC期间,survivor区空间不足,通过空间担保直接进入老年代

3.实例初始化零值(不含对象头)

将对象的实力字段初始化零值

4.设置对象头

设置类型指针、markword(含锁信息、GC分代年龄、hashcode等)等信息

5.实例初始化

通过 invokeSpecial指令,执行 对象初始化方法

invokeSpecail只能在一下三种情况下使用:

1)
2)private方法
3)super.method()

对象的内存布局

1.对象头

1.1 markword

未锁定:对象hash码、GC分代年龄、01
可偏向:偏向线程ID、偏向时间戳、GC分代年龄、01
轻量级锁:指向锁记录的指针、00
重量级锁:指向重量级锁的指针、10
GC标记:11

1.2 类型指针
通过类型指针定位对象所属类的元数据信息,对应直接指针访问的对象定位方式

  1. 实例数据
    默认分配顺序为 doubles/longs、ints、shorts/chars、bytes/booleans、oops(普通对象指针)

如此,相同宽度的字段被分配到一起存放,那么父类定义的变量可能出现在子类之前,子类中定义的变量可能出现在父类定义的变量的间隙中。

  1. 对齐填充
    自动内存管理系统要求对象起始地址必须是8字节的整数倍

  2. 计算对象的大小
    mark word 8位 | calss 4位(未压缩8位) | [数组长度4位] | 内容
    8字节对齐
    reference4个字节