JVM-类加载
编译
JVM默认使用的是解释器+热点代码编译的混合方式。
- -Xmixed:混合模式
- -Xint:解释模式,启动快,执行慢
- -Xcomp:编译模式,启动慢,执行快
JIT对于执行频率高的代码编译成本地代码。
-XX:CompileThreshold = 10000 --检测热点代码
类加载过程
- 装载(loading):将class文件放入内存
- 链接(linking)
- 校验(Verification):校验文件是否符合JVM规范
- 准备(Preparation):静态变量赋默认值
- 解析(Resolution):将符号引用转为直接引用,包括类、方法、属性等。其实就是将JVM常量池中的引用解析为指针等内存地址直接引用。
- 初始化(initializing):调用类初始化代码块儿,给静态变量赋初始值,执行静态代码块儿
类加载器(ClassLoader)
用来加载.class文件。
- BootstrapClassLoader:用于加载rt.jar等核心类,是由C++实现的,因此在java中看不到这个类加载器,如果一个类的类加载器为null,那么使用的就是该加载器
- ExtClassLoader:用于加载扩展jar包jre/lib/ext/*.jar
- AppClassLoader:用于加载classPath指定的内容,即我们自己写的
- CustomClassLoader:自定义类加载器
其中3是4的父加载器,2是3的夫加载器,1是2的父加载器。
注意:父加载器不是继承关系,是在源码中final修饰的一个parent变量,是指定好的。
双亲委派
类加载器加载时候,先去委派父加载器加载,父加载器先去缓存查找,如果找到返回,如果没找到再去父类执行此逻辑。直到BootStrapCLassLoad,如果没找到在依次委托子加载器去加载;
双亲委派主要是为了解决安全问题;
假如任何一个加载器都可以加载类,那么可以使用自定义加载器加载自定义一个java.lang.string也可以被加载。如果使用双亲委派,在自定义加载器加载时候,会去父加载器去找,最后加载的是java类库中的class。
自定义类加载器
- 继承ClassLoader
- 重写findClass方法
- 在findClass方法中调用super.defineClass方法
- 可以通过调用父类构造方法super(parent)指定父加载器
- 可通过重写loadClass方法打破双亲委派,tomcat的热部署就是这个逻辑。
Class类对象
比如String.class
ClassLoader将一个class文件加载至内存中时候:
- 将class文件放入到内存中。
- 生成一个class类的对象,指向class二进制文件存储的内存地址。
JVM懒加载
JVM在启动时候,不会将所有的class文件加载至内存,只有使用的时候,比如new的时候才会加载对应的class文件
类加载过程中的静态变量和new对象时成员变量的过程
- 这两个过程都有赋默认值的过程
- 类加载过程中静态变量在链接环节的准备环节,给静态变量赋默认值
- new对象时,先申请内存给成员变量赋默认值,调用构造方法时候,给成员变量赋初始值。
硬件层数据一致性问题
在CPU读取数据时候,多个cpu读取同一块儿数据会有数据一致性问题
- 通过总线锁或者MESI协议解决数据一直性问题
- 缓存行问题,用填充缓存行解决问题
乱序问题
cpu执行速度比cup在内存中读取数据的效率快很多,因此为了提高cpu执行效率,cpu会在读取内存的时候执行其他指令,前提是两条指令没有依赖关系。因此指令重排的问题;
解决乱序问题
- cpu层面使用指令建立内存屏障,如X86的sfence、ifence、mfence等。
- jvm层级,JVM规范依赖硬件实现,如LoadLoad屏障、StoreStore屏障、SoadStore屏障、StoreLoad屏障
volatile
实现:
- java代码层面:volatile标记
- class文件层面:ACC_VOLATILE标记
- JVM层面:在volatile前后加内存屏障,StoreStoreBarrier/读操作/StoreLoadBarrier LoadLoadBarrier/写操作/LoadsSoreBarrier
- 硬件层面:具体的指令实现内存屏障
synchronized
实现:
- 字节码层面:ACC_synchronized标记, 指令monitorenter 和 monitorexit指令
- JVM层面:C C++ 调用操作系统的同步机制
- 硬件层面:X86:lock指令
对象创建过程
- class load:装载
- class linking: 链接,包括校验、准备、解析
- class initializing:初始化
- 申请对象内存
- 给成员变量默认值
- 调用构造方法:成员变量顺序赋初始值,调用构造方法
对象内存存储布局
普通对象:
- 对象头: 8字节
- class pointer指针:-XX:+UseCompressedClassPointers,开启指针压缩为4字节,默认不开启8字节
- 实例数据:引用类型为8字节,-XX:+UseCompressedOops,开启指针压缩为4字节
- Padding:补齐,8的倍数
数组对象:
- 对象头:8字节
- class Pointer指针:同普通对象
- 数组长度:4字节
- 数组数据
- padding:补齐8的倍数
Java如何可以查看对象大小
- 使用java Agent,在一个jar包中使用Instrumentation在加载class文件时候截取流文件可以查看对象大小
- 在另一个项目里引入jar包,启动时候指定jar文件,-javaagent: jar包全路径