classloader类加载相关
java代码执行流程
当 .java 源码被 javac.exe 编译器编译成 .class 字节码文件后,接下来的工作就交给JVM处理。JVM首先通过类加载器(ClassLoader),将class文件和相关Java API加载装入JVM,以供JVM后续处理。
类的加载过程
类的生命周期包括:加载、链接、初始化、使用、卸载
其中前三个属于类的加载过程
加载-loading
把.class
文件加载到JVM里的过程
- 通过类的全限定名来获取定义此类的二进制字节流
- 将此二进制字节流所代表的静态存储结构转化成方法区的运行时数据结构
- 在内存中生成代表此类的java.lang.Class对象,作为该类访问入口.
链接-linking
链接分为三步
验证:验证的目的是确保class文件的字节流中信息符合虚拟机的要求,不会危害虚拟机安全,使得虚拟机免受恶意代码的攻击,这一步至关重要。
文件格式验证
源数据验证
字节码验证
符号引用验证准备:准备阶段的工作就是为类的静态变量分配内存并设为jvm默认的初值,对于非静态的变量,则不会为它们分配内存。静态变量的初值为jvm默认的初值,而不是我们在程序中设定的初值。(仅包含类变量,不包含实例变量).
解析:虚拟机将常量池中的符号引用替换为直接引用,解析动作主要针对类或接口,字段,类方法,方法类型等等。
初始化-initialization
在该阶段,才真正意义上的开始执行类中定义的java程序代码,该阶段会执行类构造器。
暂讨论关于类生命周期的三种,之后在学习内存的时候细嗦
类加载器ClassLoader
ClassLoader核心方法
ClassLoader的核心方法有:
loadClass
(加载指定的Java类)findClass
(查找指定的Java类)findLoadedClass
(查找JVM已经加载过的类)defineClass
(定义一个Java类)resolveClass
(链接指定的Java类)
ClassLoader的作用
Java程序在运行前需要先编译成
class文件
,Java类初始化的时候会调用java.lang.ClassLoader
加载类字节码,ClassLoader
会调用JVM的native方法(defineClass0/1/2
)来定义一个java.lang.Class
实例。
另外一个作用是确认每个类应该由哪个类加载器加载。
第二个作用也用于判断JVM运行时的两个类是否相等,影响的判断方法有equals()、isAssignableFrom()、isInstance()以及instanceof关键字。
何时触发类加载
类加载的触发可以分为隐式加载和显示加载。
隐式加载
隐式加载包括以下几种情况:
- 遇到new、getstatic、putstatic、invokestatic这4条字节码指令时
- 对类进行反射调用时
- 当初始化一个类时,如果其父类还没有初始化,优先加载其父类并初始化
- 虚拟机启动时,需指定一个包含main函数的主类,优先加载并初始化这个主类
显式加载
1 | // 反射加载TestHelloWorld示例 |
显示加载包含以下几种情况:
- 通过ClassLoader的loadClass方法
- 通过Class.forName(反射获取Class)
- 通过ClassLoader的findClass方法
ClassLoader分类
从上到下认识他们
启动类/引导类:Bootstrap ClassLoader
这个类加载器使用C/C++语言实现的,嵌套在JVM内部,java程序无法直接操作这个类。它用来加载Java核心类库,如:JAVA_HOME/jre/lib/rt.jar
、resources.jar
、sun.boot.class.path
路径下的包,用于提供jvm运行所需的包。
并不是继承自java.lang.ClassLoader,它没有父类加载器
它加载扩展类加载器
和应用程序类加载器
,并成为他们的父类加载器
出于安全考虑,启动类只加载包名为:java、javax、sun开头的类
扩展类加载器:Extension ClassLoader
Java语言编写,由sun.misc.Launcher$ExtClassLoader
实现,我们可以用Java程序操作这个加载器派生继承自java.lang.ClassLoader,父类加载器为启动类加载器
从系统属性:java.ext.dirs
目录中加载类库,或者从JDK安装目录:jre/lib/ext
目录下加载类库。我们就可以将我们自己的包放在以上目录下,就会自动加载进来了。
应用程序类加载器:Application Classloader
程序默认的类加载器,我们编写的类就是由他来加载。
Java语言编写,由sun.misc.Launcher$AppClassLoader实现。
派生继承自java.lang.ClassLoader
,父类加载器为ExtClassloader
它负责加载环境变量classpath或者系统属性java.class.path指定路径下的类库
我们可以通过ClassLoader#getSystemClassLoader()
获取并操作这个加载器
自定义加载器
为了实现自己的功能,比如加强安全传输,我们可以自己编写加载器。
继承java.lang.ClassLoader
类,重写findClass()方法如果没有太复杂的需求,可以直接继承URLClassLoader
类,重写loadClass
方法,具体可参考AppClassLoader
和ExtClassLoader
。
获取ClassLoader
除了启动类加载器,其他加载器都是继承自java.lang.ClassLoader
这个抽象类。
1 | // 方式一:获取当前类的 ClassLoader |
类加载流程
- 调用
loadClass
加载 - 调用
findLoadedClass
检查是否已加载,若已加载则直接返回已加载的类 - 如果创建ClassLoader时传入了父类加载器(
new ClassLoader(父类加载器)
)则使用父类加载器先加载,否则使用JVM的Bootstrap ClassLoader
加载 - 若父类加载器无法加载则调用自身
findClass
加载 - 如果调用loadClass的时候传入的
resolve
参数为true,那么还需要调用resolveClass
方法链接类,默认为false - 加载失败或返回加载后的
java.lang.Class
类对象
类加载机制-双亲委派
jvm对class文件采用的是按需加载的方式,当需要使用该类时,jvm才会将它的class文件加载到内存中产生class对象。
在加载类的时候,是采用的双亲委派机制
,即把请求交给父类处理的一种任务委派模式。
这也被叫做双亲委派模型
,在jdk1.2之后引入
其中,两个用户自定义类加载器的父加载器是AppClassLoader,AppClassLoader的父加载器是ExtClassLoader,ExtClassLoader是没有父类加载器的,在代码中,ExtClassLoader的父类加载器为null。BootstrapClassLoader也并没有子类,因为他完全由JVM实现。
工作逻辑
(1)如果一个类加载器
接收到了类加载
的请求,它自己不会先去加载,会把这个请求委托给父类加载器
去执行。
(2)如果父类还存在父类加载器,则继续向上委托,一直委托到启动类加载器:Bootstrap ClassLoader
(3)如果父类加载器可以完成加载任务,就返回成功结果,如果父类加载失败,就由子类自己去尝试加载,如果子类加载失败就会抛出ClassNotFoundException
异常,这就是双亲委派模式
使用目的
双亲委派模型能够保证类在内存中的唯一性,能够保证系统级别类的安全,因为当启动类ClassLoader加载过了之后,子ClassLoader便不会再加载:
1、防止重复加载同一个.class
。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
2、保证核心.class
不能被篡改。通过委托方式,不会去篡改核心.clas
,即使篡改也不会去加载,即使加载也不会是同一个.class
对象了。不同的加载器加载同一个.class
也不是同一个Class
对象。这样保证了Class
执行安全。
运行原理
loadClass方法如下,代码中做了注释解析
双亲委派模型实现的核心就是这个loadClass方法
1 | protected Class<?> loadClass(String name, boolean resolve) |
自定义ClassLoader
我们可以实现自己的类加载器,加载指定路径下的class文件
- 通过loadClass在指定的路径下查找文件。
- 通过findClass方法解析class字节流,并实例化class对象。
编写一个自己的ClassLoader
defineClass:将字节码变成内存中的class
1 | package com.xianbei.test3; |
然后我们尝试加载自己电脑上指定路径的class文件:
这是我要加载的class文件,之前用JAVAssist控制字节码编写的恶意类
这里有个静态块,就是最后的static
在实例化的时候会被自动加载
关于JAVAssist不在此多做赘述
1 | public class TestCLDemo { |
成功弹出计算机,也就是成功加载了指定路径下的class文件并且用反射将其实例化了。
URLClassLoader類代碼示例
URLClassLoader是ClassLoader的子类,它用于从指向 JAR 文件和目录的 URL 的搜索路径加载类和资源。也就是说,通过URLClassLoader就可以加载指定jar中的class到内存中。
https://vimsky.com/zh-tw/examples/detail/java-class-java.net.URLClassLoader.html
参考
https://www.cnblogs.com/goloving/p/14438785.html讲双亲委派的
https://www.jianshu.com/p/fe8a01b0c3b7讲java代码运行机制的