1. 类加载器是什么?
当我们在程序中调用一个类的方法或者new一个类的对象时,JVM必须先把.java文件编译后的.class文件加载进去才行。
类加载器就是用来加载.class文件的工具。
2. 类加载器有几种?
- 启动(Bootstrap)类加载器
它负责将 <JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中,只加载特定名称的jar和特定名称的包。对jar包名称(rt.jar)和包名(java.javax.sun.)都有严格要求。 - 扩展(Extension)类加载器
它负责加载<JAVA_HOME>/lib/ext目录下或者由系统变量-Djava.ext.dir指定的类库。 - 应用(Application)类加载器
它负责加载java -classpath或-D java.class.path 指定的类库,也就是我们经常用到的classpath路径。
3. 类加载器的机制是什么?
JVM采用双亲委派的类加载机制。应用类加载器(AppClassLoader)用到某一个类时,自己先不加载,先委派给扩展类加载器(ExtClassLoader)来加载,扩展类加载器(ExtClassLoader)再先委派给启动类加载器BootstrapClassLoader)加载,上一级找不到class的话,自己再加载。
这种经过两次委派的机制称为双亲委派机制,这里的双亲并不是指继承关系,指的是逻辑上的亲子关系。
4. 为什么要采用双亲委派?
1.为了安全,核心class只能由启动类加载器来加载,随意篡改的核心class文件无法加载进jvm。
2. 防止重复加载class,java中的class全名中是包含类加载器名称的,一个类加载器加载的class所new出来的对象 用instansof type来和另一个类加载器加载的class比较的话,结果是false。
比如,Aclassloader 加载进来一个 Demo.class文件,然后new 了一个 demo对象
Bclassloader也加载同一个Demo.class文件,然后 System.out.println(demo instanceof B.Demo.class),结果为false
5. 类加载的步骤?
- 加载: 加载class文件。
- 验证: 验证class文件,保证二进制字节码在结构上的正确性。
- 准备: 准备阶段主要是创建静态域,分配空间,给这些域设默认值。
- 解析
解析的过程就是对类中的接口、类、方法、变量的符号引用进行解析并定位,解析成直接引用(符号引用就是编码是用字符串表示某个变量、接口的位置,直接引用就是根据符号引用翻译出来的地址),并保证这些类被正确的找到。 - 初始化: 创建class对象。
- 初始化时机
- 创建对象的实例:我们new对象的时候,会引发类的初始化,前提是这个类没有被初始化
- 调用类的静态属性或者为静态属性赋值
- 调用类的静态方法
- 通过class文件反射创建对象
- 初始化一个类的子类:使用子类的时候先初始化父类
- java虚拟机启动时被标记为启动类的类:就是我们的main方法所在的类
- 在编译的时候能确定下来的静态变量(编译常量),不会对类进行初始化:调用某个类的static final变量不会初始化类,static final常量是在编译阶段就初始化好了的
- 如果这个类有父类并且这个父类没有被初始化,则先初始化父类.
- 如果类中存在初始化语句,依次执行初始化语句.
- 初始化步骤
- 父类的静态属性
- 父类的静态代码块
- 子类的静态属性
- 子类的静态代码块
- 父类的非静态属性
- 父类的非静态代码块
- 父类构造方法
- 子类非静态属性
- 子类非静态代码块
- 子类构造方法
- 初始化时机
6. 怎么破坏双亲委派?
-
线程上下文类加载器
Java中提倡面向接口编程,所有Java有很多服务提供者接口(Service Provider Interface,SPI),这些接口都属于Java的核心类库,但是它们的实现类都是第三方提供的,属于应用类库。那么启动类加载器怎么加载这些实现类呢。为了解决这个问题,Java引入了线程上下文类加载器(ContextClassLoader),默认为应用类加载器。
-
插件热插拔系统
插件热插拔系统的类加载机制不是树状结构,每个插件都有独立的类加载器,不属于双亲委派。 -
自定义classloader
自定义的classloader可以不遵循双擎委派规则。