面试之类加载器
https://blog.csdn.net/javazejian/article/details/73413292
- 加载,通.class文件创建Class对象
- 验证,包括安全验证
- 准备,为类变量(static修饰)赋初始值,默认值比如int为0,finnal修饰的在编译阶段完成了。
- 解析,析:主要将常量池中的符号引用替换为直接引用的过程,有类或接口的解析,字段解析,类方法解析,接口方法解析
- 初始化,类加载最后阶段,若该类具有超类,则对其进行初始化,成员变量真正赋值阶段。
加载器
- 启动(Bootstrap)类加载器,
主要加载主要加载的是JVM自身需要的类,出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类。它负责将 <JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中
2.扩展(Extension)类加载器,
它负责加载<JAVA_HOME>/lib/ext
3.系统(System)类加载器
它负责加载系统类路径java -classpath或-D java.class.path 指定路径下的类库
Java的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,Java虚拟机对class文件采用的是按需加载的方式,Java虚拟机采用的是双亲委派模式即把请求交由父类处理
启动类加载器,由C++实现,没有父类。
拓展类加载器(ExtClassLoader),由Java语言实现,父类加载器为null
系统类加载器(AppClassLoader),由Java语言实现,父类加载器为ExtClassLoader
自定义类加载器,父类加载器肯定为AppClassLoader。
要求除了顶层加载器之外,必须有自己的父类加载器。
类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,如果没有,才由子加载器来加载。
loadClass通过双亲委派方式最后倘若仍没有找到,则使用findClass()方法去加载
this.getClass().getClassLoder.loadClass("className")方法获取到class对象。
一般情况下,在自定义类加载器时,会直接覆盖ClassLoader的findClass()方法并编写加载规则,取得要加载类的字节码后转换成流,然后调用defineClass()方法生成类的Class对
defineClass()方法是用来将byte字节流解析成JVM能够识别的Class对象,通过网络传输字节,加载class
如果直接调用defineClass()方法生成类的Class对象,这个类的Class对象并没有解析(也可以理解为链接阶段,毕竟解析是链接的最后一步),其解析操作需要等待初始化阶段进行
esolveClass(Class≺?≻ c)
使用该方法可以使用类的Class对象创建完成也同时被解析
4.其他类结构图
即拓展类加载器ExtClassLoader和系统类加载器AppClassLoader,这两个类都继承自URLClassLoader,是sun.misc.Launcher的静态内部类。sun.misc.Launcher主要被系统用于启动主应用程序,ExtClassLoader和AppClassLoader都是由sun.misc.Launcher创建的,其类主要类结构如下
Launcher 构造器
public Launcher() { // 首先创建拓展类加载器 ClassLoader extcl; try { extcl = ExtClassLoader.getExtClassLoader(); } catch (IOException e) { throw new InternalError( "Could not create extension class loader"); } // Now create the class loader to use to launch the application try { //再创建AppClassLoader并把extcl作为父加载器传递给AppClassLoader loader = AppClassLoader.getAppClassLoader(extcl); } catch (IOException e) { throw new InternalError( "Could not create application class loader"); } //设置线程上下文类加载器,稍后分析 Thread.currentThread().setContextClassLoader(loader); //省略其他没必要的代码...... } }
Lancher初始化时首先会创建ExtClassLoader类加载器,然后再创建AppClassLoader并把ExtClassLoader传递给它作为父类加载器
5.那么编写自定义类加载器的意义何在呢?
---当class文件不在ClassPath路径下,默认系统类加载器无法找到该class文件,在这种情况下我们需要实现一个自定义的ClassLoader来加载特定路径下的class文件生成class对象。
---当一个class文件是通过网络传输并且可能会进行相应的加密操作时,需要先对class文件进行相应的解密后再加载到JVM内存中,这种情况下也需要编写自定义的ClassLoader并实现相应的逻辑。
---当需要实现热部署功能时(一个class文件通过不同的类加载器产生不同class对象从而实现热部署功能),需要实现自定义ClassLoader的逻辑。
谓的热部署就是利用同一个class文件不同的类加载器在内存创建出两个不同的class对象(关于这点的原因前面已分析过,即利用不同的类加载实例),由于JVM在加载类之前会检测请求的类是否已加载过(即在loadClass()方法中调用findLoadedClass()方法),如果被加载过,则直接从缓存获取,不会重新加载。注意同一个类加载器的实例和同一个class文件只能被加载器一次,多次加载将报错,因此我们实现的热部署必须让同一个class文件可以根据不同的类加载器重复加载,以实现所谓的热部署
————————————————
版权声明:本文为CSDN博主「zejian_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/javazejian/article/details/73413292