JVM基础 -- Java内存模型
JIT的重排序Java代码1234567891011121314public class JMM { private int a = 0; private int b = 0; public void method1() { int r2 = a; // A1 b = 1; // A2 } public void method2() { int r1 = b; // B1 a = 2; // B2 }} 单线程,method1->method2,**(r1,r2)=(1,0)** 单线程,method2->method1,**(r1,r2)=(0,2)** 多线程,没有重排序,A1->B1->A2->B2,**(r1,r2)=(0,0)** 多线程,重排序,A2->B1->B2->A1,**(r1,r2)=(1,2)** As-If-Serial 在单线程情...
JVM基础 -- 垃圾回收基础
判定对象存亡垃圾回收标记的是非垃圾 引用计数法 为每个对象添加一个引用计数器,用来统计指向该对象的引用个数 如果有一个引用,被赋值为某一对象,那么将该对象的引用计数器+1 如果指向某一对象的引用,被赋值为其他值,那么该对象的引用计数器-1 一旦某个对象的引用计数器为0,说明对象已经死亡 缺点 额外的空间来存储计数器 + 繁琐的更新操作 无法处理循环引用的场景,造成内存泄露 可达性分析 将一系列GC Roots作为初识存活对象合集 标记:从该集合出发,探索所有能够被该集合引用到的对象,并将其加入到该集合中 最终未被探索到的对象便是死亡,可以被回收 GC Roots:堆外指向堆内的引用,一般包括 Java方法栈帧中的局部变量 已加载类的静态变量 已启动且未停止的Java线程 JNI MethodHandles STW + 安全点 JVM中的STW是通过安全点机制来实现的 当JVM收到STW请求时,会等待所有的线程都到达安全点,才允许请求STW的线程进行独占地工作 安全点的初衷并不是让其他线程停下,而是找到一个稳定的执行状态 在这个执行状态下,JVM的堆栈不会发生变化 垃圾回收器能够安...
JVM基础 -- Java对象的内存布局
创建对象 new + 反射 通过调用构造器来初始化实例字段 Object.clone + 反序列化 通过直接复制已有的数据,来初始化新建对象的实例字段 Unsafe.allocateInstance 不会初始化实例字段 123456// Foo foo = new Foo();对应的字节码// new指令:请求内存0: new // class me/zhongmingmao/basic/jol/Foo3: dup// invokespecial指令:调用构造器4: invokespecial // Method "<init>":()V Java构造器默认构造器如果一个类没有定义任何构造器,那么Java编译器会自动添加一个无参数的构造器 Java代码123456// 未定义任何构造器public class Foo { public static void main(String[] args) { Foo foo = new Foo(); }} 字节...
JVM基础 -- 浅谈反射
反射API获取Class对象 Class.forName() object.getClass() 类名.class Integer.TYPE指向int.class 数组类型:类名[].class 1public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int"); 常规用法 newInstance() 生成该类实例 需要无参构造器 isInstance(Object) 判断一个对象是否为该类的实例 语法上等同于instanceOf,在JIT优化时会有所差别 Array.newInstance(Class<?>, int) 构造该类型的数组 getFields()/getConstructors()/ getMethods() 访问类成员 带Declared的方法不会返回父类成员,但会返回私有成员;不带Declared的方法恰好相反 获取类成员后 Field/Construct...
JVM基础 -- 异常处理
抛出异常 + 捕获异常抛出异常 显式抛异常的主体是应用程序,使用throw关键字 隐式抛异常的主体是JVM,在JVM执行过程中,碰到无法继续执行的异常状态时,自动抛出异常 例如ArrayIndexOutOfBoundsException 捕获异常 try代码块 标记需要异常监控的代码 catch代码块 定义了针对指定类型的异常处理器 多个catch代码块,JVM会从上至下匹配异常处理器 前面catch代码块所捕获的异常类型不能覆盖后边的,否则编译器会报错 finally代码块 声明一段必定运行的代码 程序正常执行,未抛出异常,try -> finally 程序抛出异常未被捕获,try(throw A) -> finally -> throw A 程序抛出异常并被捕获,try(throw A) -> catch(A) -> finally 程序抛出异常并被捕获,并且catch代码块也抛出异常,try(throw A) -> catch(A, throw B) -> finally -> throw B finally代码块抛出异常,中断fina...
JVM基础 -- 桥接方法
背景Java语言的重写与JVM的重写并不一致,当在Java语言中为重写而在JVM中为非重写,编译器会通过生成桥接方法来实现Java中的重写语义 桥接方法 – 返回类型Java代码1234567891011121314151617181920@Slf4jpublic class Father { public Number work() { return 1.0; } public static void main(String[] args) { Father father = new Son(); // 实际调用的是桥接方法 Number work = father.work(); log.info("{}", work); }}class Son extends Father { @Override public Double work() { return 2....
JVM基础 -- 方法调用
重载+重写 重载:方法名相同,但方法描述符不相同的方法之间的关系 重写:方法名相同,并且方法描述符也相同的方法之间的关系 方法描述符 Java:参数类型 JVM:参数类型+返回类型 重载 重载的方法在编译过程即可完成识别 具体到在每个方法调用时,Java编译器会根据传入参数的声明类型(不是实际类型)来选取重载方法 三阶段 在不允许自动装拆箱和可变长参数的情况下,选取重载方法 允许自动装拆箱,但不允许可变长参数的情况下,选取重载方法 在允许自动装拆箱和可变长参数的情况下,选取重载方法 Java编译器在同一阶段找到多个适配的方法,依据形式参数的继承关系,选择最贴切的方法,原则:子类优先 重载来源 同一个类中定义 继承父类非私有同名方法 重写 子类中定义了与父类中非私有的同名实例方法,且参数类型相同 如果是静态方法,那么子类中的方法会隐藏父类中方法 方法重写是Java多态最重要的一种体现形式 静态绑定与+动态绑定 JVM识别重载方法的关键在于类名,方法名和方法描述符 方法描述符:参数类型 + 返回类型 如果在同一个类中出现多个方法名和方法描述符也相同的方法,那么JVM会在类的验证...
JVM基础 -- 类加载
引用类型 类(字节流) 接口(字节流) 数组类(由JVM直接生成) 泛型参数(类型擦除,伪泛型) 类加载过程加载 加载:查找字节流,并且据此创建类的过程 对于数组类,没有对应的字节流,而是由JVM直接生成的 对于其他类而言,JVM需要借助类加载器来完成查找字节流的过程 类加载器 启动类加载器(boot class loader):由C++实现,没有对应的Java对象,在Java中只能用null来指代 除了启动类加载器外,其他的类加载器都是java.lang.ClassLoader的子类,有对应的Java对象 这些类加载器需要先由另一个类加载器(如启动类加载器),加载至Java虚拟机中,方能执行类加载 双亲委派模型 每当一个类加载器接收到加载请求时,会先将请求转发给父类加载器 在父类加载器没有找到所请求的类的情况下,该类加载器才会尝试自己加载 Before Java9 启动类加载器(boot class loader):负责加载最基础、最重要的类(-Xbootclasspath) 扩展类加载器(extension class loader):父类加载器为启动类加载器,负责加载相对次要、...
JVM基础 -- 基本类型
boolean类型Java代码1234567public class Foo { public static void main(String[] args) { boolean flag = true; if (flag) System.out.println("Hello, Java!"); if (flag == true) System.out.println("Hello, JVM!"); }} 编译运行12345$ javac Foo.java$ java FooHello, Java!Hello, JVM! 修改字节码运行12# jasm与javap的输出比较类似$ java -cp ./asmtools.jar org.openjdk.asmtools.jdis.Main Foo.class > Foo.jasm.bak 12345678910111213141516171819202122$ tail -n 23 Foo.jasm.bak...
JVM基础 -- 运行过程+运行效率
虚拟机视角 将class文件加载到JVM中,加载后的Java类会被存放在方法区,实际运行时,虚拟机会执行方法区内的代码 JVM同样会将内存划分出堆和栈来存储运行时数据,栈会细分本地方法栈和Java方法栈 PC寄存器:用于记录各个线程的执行位置 在运行过程中,每当调用进入一个Java方法,JVM会在当前线程的Java方法栈中生成一个栈帧 栈帧用于存放局部变量表和操作数 栈帧的大小是提前计算好的,并且JVM不要求栈帧在内存空间里连续分布 当退出当前执行的方法时,不管是正常返回还是异常返回,JVM都会弹出并舍弃当前线程的当前栈帧 硬件视角 Java字节码无法直接执行,需要JVM将字节码翻译成机器码,有两种形式:解析执行+即时编译 解释执行:逐条将字节码翻译成机器码并执行,无需等待编译 即时编译(JIT):将一个方法中包含的所有字节码编译成机器码后再执行,实际运行速度更快 HotSpot默认采用混合模式,先解析执行 字节码,然后将其反复执行的热点代码,以方法为单位 进行即时编译 即时编译建立在2-8定律的假设之上 对于占据大部分的不常用代码,无需耗费时间将其编译成机器码,而是采用解释执行的...













