1. jvm是干什么的?
初步理解:
jvm是运行 java等语言源码编译后的class文件 的一个应用。
2. class文件是什么?
class文件时字节码文件,宽泛的说也就是由程序员写的代码翻译而成的机器识别的二进制码文件。
class文件包含了哪些信息:
基本分析:
编号 | 源码及编译相关信息 | 推class对应的 | 字节数 | 基本逻辑 |
---|---|---|---|---|
1 | class文件的标识 | 魔数cafe babe | 4 | jvm需要一眼就知道这是不是我能处理的文件。 |
2 | 编译器版本 | 大小两个版本 | 4 | jvm需要知道编译他的是哪个版本,不同版本有差异 |
3 | 类信息 | 类信息表 | - | - |
4 | 方法信息 | 方法信息表 | - | - |
5 | 属性信息 | 属性信息表 | - | - |
实际情况:
编号 | 类型 | 描述 | 备注 |
---|---|---|---|
1 | u4 | magic | 魔数:0xCAFEBABE |
2 | u2 | minor_version | 小版本号 |
3 | u2 | major_version | 主版本号 |
4 | u2 | constant_pool_count | 常量池大小,从1开始 |
5 | cp_info | constant_pool[constant_pool_count - 1] | 常量池信息 |
6 | u2 | access_flags | 访问标志 |
7 | u2 | this_class | 类索引 |
8 | u2 | super_class | 父类索引 |
9 | u2 | interfaces_count | 接口个数 |
10 | u2 | interfaces[interfaces_count] | 接口类索引信息 |
11 | u2 | fields_count | 字段数 |
12 | field_info | fields[fields_count] | 字段表信息 |
13 | u2 | methods_count | 方法数 |
14 | method_info | methods[methods_count] | 方法表信息 |
15 | u2 | attributes_count | 属性个数 |
16 | attribute_info | attributes[attributes_count] | 属性表信息 |
java空类示例:
package com.jgp.practice.bytecode;
import java.io.Serializable;
/**
* 用jclasslib插件查看class码内容
*/
public class ByteCode01 implements Serializable {
}
对应字节码文件:
ca fe ba be 00 00 00 34 00 12 0a 00 03 00 0e 07
00 0f 07 00 10 07 00 11 01 00 06 3c 69 6e 69 74
3e 01 00 03 28 29 56 01 00 04 43 6f 64 65 01 00
0f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65
01 00 12 4c 6f 63 61 6c 56 61 72 69 61 62 6c 65
54 61 62 6c 65 01 00 04 74 68 69 73 01 00 26 4c
63 6f 6d 2f 6a 67 70 2f 70 72 61 63 74 69 63 65
2f 62 79 74 65 63 6f 64 65 2f 42 79 74 65 43 6f
64 65 30 31 3b 01 00 0a 53 6f 75 72 63 65 46 69
6c 65 01 00 0f 42 79 74 65 43 6f 64 65 30 31 2e
6a 61 76 61 0c 00 05 00 06 01 00 24 63 6f 6d 2f
6a 67 70 2f 70 72 61 63 74 69 63 65 2f 62 79 74
65 63 6f 64 65 2f 42 79 74 65 43 6f 64 65 30 31
01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a
65 63 74 01 00 14 6a 61 76 61 2f 69 6f 2f 53 65
72 69 61 6c 69 7a 61 62 6c 65 00 21 00 02 00 03
00 01 00 04 00 00 00 01 00 01 00 05 00 06 00 01
00 07 00 00 00 2f 00 01 00 01 00 00 00 05 2a b7
00 01 b1 00 00 00 02 00 08 00 00 00 06 00 01 00
00 00 08 00 09 00 00 00 0c 00 01 00 00 00 05 00
0a 00 0b 00 00 00 01 00 0c 00 00 00 02 00 0d
在命令行使用“javap -v class文件名”或者再idea中使用jclasslib插件查看字节码文件对应的汇编
3. class文件如何被加载到jvm虚拟机
jvm想使用class文件内容必须将class加载到内存中,并且保持对应内存的位置。
实际上用于保持class加载到的内存的是class对象,其存在于Metaspace(元空间)中。
3.1. loading(加载)
加载由类加载器完成
类加载器有哪些:
编号 | 名称 | 备注 |
---|---|---|
1 | Bootstrap | 加载lib/rt.jar charset.jar等核心类,c++实现 |
2 | Extension | 加载扩展jar包 jre/lib/ext/*.jar或者由-Djava.ext.dirs指定 |
3 | App | 加载classpath指定内容 |
4 | CustomClassLoader | 自定义ClassLoader |
1. 双亲委派机制:
从下向上检查该类是否已经加载未加载则沿parent方向检查是否被加载一旦有被加载的就返回,如果所有加载器都没有进行加载,则自顶向下沿着child方向委托加载,在自己加载范围内则自己加载,不在则委托child加载,如果找到则加载,找不到就报出class not found异常。
为什么要有双亲委派:
主要是为了安全,避免了子加载器覆盖掉父加载器加载的类。
其次是避免加载重复
注意,对于加载器来说:父加载器不是类加载器的加载器,也不是类加载器的父类
源码中父加载器是加载器对象中parent通过getParent获取
2. LazyLoading 五种情况
-
–new getstatic putstatic invokestatic指令,访问final变量除外
–java.lang.reflect对类进行反射调用时
–初始化子类的时候,父类首先初始化
–虚拟机启动时,被执行的主类必须初始化
–动态语言支持java.lang.invoke.MethodHandle解析的结果为REF_getstatic REF_putstatic REF_invokestatic的方法句柄时,该类必须初始化
3. ClassLoader的源码
- findInCache -> parent.loadClass -> findClass()
4. 自定义类加载器
- extends ClassLoader
- overwrite findClass() -> defineClass(byte[] -> Class clazz)
- 加密
-
第一节课遗留问题:parent是如何指定的,打破双亲委派,学生问题桌面图片
- 用super(parent)指定
- 双亲委派的打破
- 如何打破:重写loadClass()
- 何时打破过?
- JDK1.2之前,自定义ClassLoader都必须重写loadClass()
- ThreadContextClassLoader可以实现基础类调用实现类代码,通过thread.setContextClassLoader指定
- 热启动,热部署
- osgi tomcat 都有自己的模块指定classloader(可以加载同一类库的不同版本)
5. 混合执行 编译执行 解释执行
- 检测热点代码:-XX:CompileThreshold = 10000
3.2. linking(链接)
3.2.1. verification(校验)
- 验证文件是否符合JVM规定
3.2.2. preparation(赋默认值)
- 静态成员变量赋默认值
3.2.3. resolution(简历内存关联)
- 将类、方法、属性等符号引用解析为直接引用
常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用
3.3. initializing(初始化)
- 调用类初始化代码 ,给静态成员变量赋初始值
3.4. 小总结
- load - 默认值 - 初始值
- new - 申请内存 - 默认值 - 初始值