类从编译到执行经历的的过程
➢编译器将Robot.java源文件编译为Robot.class字节码文件 ➢ClassLoader将字节码转换为JVM中的Class <Robot>对象 ➢JVM利用Class <Robot>对象实例化为Robot对象
ClassLoader
Classloader在Java中有着非常重要的作用 它主要工作在Class装载的加载阶段,所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过将Class文件里的二进制数据流或者是从系统外部获得Class二进制数据流装载进系统,然后交给Java虚拟机进行连接、初始化等操作。
ClassLoader的种类
- BootStrapClassLoader:C 编写,java的核心自带类库的java.* (用户不可见除非看JVM代码)
- ExtClassLoader:Java编写,扩展库javax.*
- AppClassLoader:Java编写,加载程序所在目录主要加载我们的src程序代码
- 自定义ClassLoader:Java编写,定制化加载
ClassLoader的源码和解析过程分析:
ClassLoader里面包含了许多方法制定了加载流程和方式
其中loadClass是比较重要的,可以根据类名查找当前ClassLoader是否加载了该class,如果有的话就返回类的class实例,找不到就会返回null
手写classloader关键
代码语言:javascript复制手写ClassLoader
package jvm;
import java.io.*;
public class MyClassLoader extends ClassLoader{
private String path;
private String MyClassLoaderName;//定义自己的ClassLoader名字 实际上没啥用
public MyClassLoader(String path, String MyClassLoaderName) {
this.path = path;
this.MyClassLoaderName = MyClassLoaderName;
}
@Override
//用于寻找类文件
public Class findClass(String name){
byte[] b=loadClassData(name);
return defineClass(name,b,0,b.length);
}
//用于加载class文件为字节码
private byte[] loadClassData(String name) {
String url=path name ".class";
InputStream inp=null;
ByteArrayOutputStream out=null;
try {
inp=new FileInputStream(new File(url));
out=new ByteArrayOutputStream();
int i=0;
while ((i=inp.read())!=-1){
out.write(i);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try{
out.close();
inp.close();
}catch (Exception e)
{
e.printStackTrace();
}
}
return out.toByteArray();
}
}
代码语言:javascript复制package jvm;
public class ClassLoaderCheck {
public static void main(String[] args) throws Exception {
MyClassLoader myl=new MyClassLoader("C:\Users\Zyh\Desktop\","ZyhClassLoader");//我的桌面C:\Users\Zyh\Desktop\上有个ClassTest.class文件,ZyhClassLoader没有啥左右就是给我的classloader起个名字而已
Class c = myl.loadClass("ClassTest");
System.out.println(c.getClassLoader());
c.newInstance();
}
}
为什么我们要手写ClassLoader,有什么好处呢?
可以加载任意字节流,比如访问远程网络加载二进制流并生成我们需要的类 我们也可以对某些敏感的class进行加密,自定义findClass()进行解密处理
ClassLoader的双亲委派机制
双亲委派模型的工作过程是:
- 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。
- 每一个层次的类加载器都是如此。因此,所有的加载请求最终都应该传送到顶层的启
- 只有当父加载器反馈自己无法完成这个加载请求时(搜索范围中没有找到所需的类),子加载器才会尝试自己去加载。
为什么要使用双亲委派机制去加载类
- 避免一个class被多次装载
- 父类加载器已经加载过的类,不用再次加载,而且对于一些系统类,用户自定义的不起作用了,有一定安全保证。
类加载方式
隐式加载:new 显式加载:loadClass,forName等
类加载过程
loadClass和forName的区别
- Class.forName得到的class是已经初始化完成的
- Classloder.loadClass得到的class是还没有链接的
举例子:
forName是执行了初始化的有时候我们需要得到已经初始化的class比如注册mysql驱动
有时候我们为了加快初始化速度会使用lazy-load(比如springIOC读取bean的配置文件),而使用ClassLoader不用进行链接和初始化省去执行static方法等的时间花费,加快加载速度,等需要时候再进行使用