类加载机制
类加载过程
包含了加载、验证、准备、解析和初始化这 5 个阶段。
- 加载
从 类的二进制字节流 到 内存中一个代表该类的 Class 对象 - 验证
确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求 - 准备
类变量是被 static 修饰的变量,准备阶段为类变量分配内存并设置初始值,使用的是方法区的内存。 - 解析
将常量池的符号引用替换为直接引用的过程。
其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的动态绑定。 - 初始化
初始化阶段才真正开始执行类中定义的 Java 程序代码。初始化阶段是虚拟机执行类构造器() 方法的过程。
类初始化时机
- 主动引用
虚拟机规范严格规定了有且只有下列五种情况必须对类进行初始化(加载、验证、准备都会随之发生):- 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时;
- 使用 java.lang.reflect 包的方法对类进行反射调用的时候;
- 当初始化一个类的时候,如果发现其父类还没有进行过初始化;
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类);
- 当使用 JDK 1.7 的动态语言支持时;
- 被动引用(不会触发初始化)
- 通过子类引用父类的静态字段,不会导致子类初始化。
- 通过数组定义来引用类。
- 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类
类与类加载器
如果一个类由类加载器A加载,那么这个类的依赖类也优先由类加载器A加载(jvm控制?);在自定义类加载器时需要特别注意。
两个类相等,需要类本身相等,并且使用同一个类加载器进行加载。这是因为每一个类加载器都拥有一个独立的类名称空间。
类加载器分类
从 Java 开发人员的角度看,类加载器可以划分得更细致一些:
- 启动类加载器(Bootstrap ClassLoader)
仅加载 jre 下指定包,无法被 Java 程序直接引用。 - 扩展类加载器(Extension ClassLoader)
仅加载扩展包 - 应用程序类加载器(Application ClassLoader)
一般情况下这个就是程序中默认的类加载器。
双亲委派模型
指定了三种类加载器之间的层次关系。该模型要求除了顶层的启动类加载器外,其它的类加载器都要有自己的父类加载器。这里的父子关系一般通过组合关系(Composition)来实现,而不是继承关系(Inheritance)。
- 工作过程
一个类加载器首先将类加载请求转发到父类加载器,只有当父类加载器无法完成时才尝试自己加载。 - 好处
使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一。 - 实现
java.lang.ClassLoader ,其中的 loadClass() 方法运行过程如下:先检查类是否已经加载过,如果没有则让父类加载器去加载。当父类加载器加载失败时抛出 ClassNotFoundException,此时尝试自己去加载。
ContextClassLoader 使用案例
JDBC: java.sql.DriverManager#getConnection(java.lang.String, java.util.Properties, java.lang.Class<?>)
SPI 实现类: java.util.ServiceLoader#load(java.lang.Class)
问题:
- Class.forName(“”); 默认使用哪个 classloader 进行加载?? 默认是调用该方法的类的类加载器,也可以指定类加载器及是否初始化
- classpath 下存在两个全限定类名相同的类(不同的jar包),jvm 会加载哪一个?由什么决定?由程序自定义的 classloader 决定,一般是由上到下加载jar包,可以使用不同的解压工具打开验证。
- tomcat 使用 URLClassLoader,springboot 使用什么 classloader?
class 文件(by luanlouis)
基本组织结构
魔数(magic)、版本号(minor_version,major_version)、访问标志(access_flags)、类索引(this_class)、父类索引(super_class)
常量池计数器(constant_pool_count)、常量池数据区(constant_pool[contstant_pool_count-1])
每个常量池项(cp_info) 都会对应记录着 class 文件中的某种类型的字面量。tag 值表示是什么类型的字面量。
字面量型结构体:该类型的结构体内存储的是字面量的值; CONSTANT_Utf8_info,CONSTANT_Integer_info,CONSTANT_Float_info,CONSTANT_Long_info,CONSTANT_Double_info,
引用型结构体:该类型的结构体内含有指向某些字面量型结构体的 索引值; CONSTANT_Class_info,CONSTANT_String_info,CONSTANT_Fieldref_info,CONSTANT_Methodref_info,CONSTANT_NameAndType_info,
常量池能够表示那些信息?
字面量:文本字符串、被声明为final的常量值、基本数据类型的值、其他
符号引用:类的完全限定名、字段的名称和描述符、方法的名称和描述符、其他接口计数器(interfaces_count)、接口信息数据区(interfaces[interfaces_count])
字段计数器(fields_count)、字段信息数据区(fields[fields_count])
在 field_info 结构体中,紧接着访问标志的,就是字段名称索引和字段描述符索引,它们分别占有两个字节,其内部存储的是指向了常量池中的某个常量池项的索引,对应的常量池项中存储的字符串,分别表示该字段的名称和字段描述符。(这里的字段描述符即字段类型)
还包含 访问标志 及 属性集合信息方法计数器(methods_count)、方法信息数据区(methods[methods_count])
method_info 中包含的 属性表集合 –可表示的信息包括:方法的可执行的机器指令、方法声明的要抛出的异常信息等。
属性计数器(attributes_count)、属性信息数据区(attributes[attributes_count])
CONSTANT_Fieldref_info 与 CONSTANT_Methodref_info 的关系
都有引用项 CONSTANT_NameAndType_info
name_index 字段名称或方法名称
descriptor_index 字段类型或者方法描述符(包含这个方法的 若干个参数的数据类型和 一个返回值数据类型)
CONSTANT_Fieldref_info 与 field_info 的关系
field_info 包含 CONSTANT_Fieldref_info 的引用?
字段表(field_info) 和 方法表(method_info)的关系
都包含属性表集合,属性表(attribute_info)包含属性名称索引、属性长度、属性值;
属性名称索引只有四个值,Code 类型的属性表保存着 method 方法中的机器指令的信息,Code 属性表还可包含属性表集合 LineNumberTable 和LocalVariableTable;
《深入理解java虚拟机》 jvm 功能划分:
- 自动内存管理机制:内存区域、垃圾收集器、性能监控与故障处理工具;运行程序的堆大小?垃圾回收的策略?
- 执行子系统:类文件结构、类加载机制、字节码执行引擎;
- 程序编译与代码优化;
- 高效并发:内存模型与线程、线程安全与锁优化;