类加载器ClassLoader
ClassLoader本身是一个类,是由启动类加载器来加载的(启动类加载器属于JVM内置的,不属于java实现,根据不同的虚拟机实现而实现,可能是C语言等)
加载类的过程
通过给定一个binary name:
- “java.lang.String”的字符串;
- ““com.jvm.Test i n n e r C l a s s ” 代 表 T e s t 类 的 内 部 类 i n n e r C l a s s , 中 间 用 innerClass”代表Test类的内部类innerClass,中间用 innerClass”代表Test类的内部类innerClass,中间用隔开”;
- “com.csf.Test i n n e r C l a s s innerClass innerClassChild1$1” 代表Test的内部类innerClass的内部类Child的第一个内部类;
- “com.csf.Test$3$1” 代表Test的第三个内部类的第一个内部类;
ClassLoader会尝试根据给定的name去定位该资源(本身存在的类)或者尝试生成类的数据(运行期动态生成的类)
类加载到虚拟机内存中经历的几个阶段包括:
加载:将类的二进制数据加载进内存(方法区中),然后生成这个类的Class对象,
作为方法区中这个类的各种资源的访问入口(类似一个镜像),JVM虚拟机并没有固定该对象存放在哪,Class对象比较特殊,在HotSpot虚拟机而言,它存放在方法区中
每一个Class对象都会保留加载它的类加载器的引用,即可以通过
Class.getClassLoader()获取到加载该类的类加载器
其中:数组类的Class对象并不是类加载器加载生成的,而是由JVM虚拟机运行期自动创建的,其中非原生类型的数据getClassLoader返回的是加载数组元素的类加载器,而原生类型并没有类加载器,返回null
JVM虚拟机并没有规定这个二进制数据从哪里来的,所以有很多种方式:
-
从压缩文件读取,常见的就是jar包依赖
-
从网络获取,applet技术
-
动态生成(Spring动态代理)
-
其他文件获取(JSP技术)
连接:
1、验证:对于加载进来的字节码文件进行必要的验证,比如魔数验证(JDK版本),class文件格式验证,class文件内容验证等等
2、准备:为类的静态变量分配内存,并赋予一个初始值
3、解析:将类的符号引用解析为直接引用
初始化:对类的静态变量赋予正确的初始值
类的初始化时机
类的初始化需要类被主动使用,JVM虚拟机规范规定的有且仅有五种,类才会被初始化,其他情形都不能触发类的初始化:
1 遇到new、getstatic、putstatic、invokestatic字节码指令(对应的new对象、获取对象静态属性,赋值类的静态属性,调用类的静态方法)
2、使用java.lang.reflect包对类进行反射调用
3、当初始化一个类时,他的父类没有被初始化,则必须初始化其父类
4、指定为程序入口的那个类
5、当使用JDK1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析 结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的 类没有被初始化时,则必须进行初始化
当调用的是类的常量,即final static修饰的变量时,并不会触发类的初始化,java编译期在编译期间会执行很多优化操作,这里就是常量优化,即把被调用的类的常量编译到自己的类的常量池中,使用时直接使用本类的那个常量
6、注意:**当类没有被主动使用时,即使类加载过程出现了ERROR错误,JVM虚拟机也不能进行异常提示,除非用户主动去使用该类,否则加载类出错,虚拟机也不会抛出异常**
如何去寻找类
在oracle的jvm中使用的是经典的双亲委托模型(该模型是JVM虚拟机规范推荐的,但不是强制的),当自己尝试去加载类时,会委托给他的父类去加载,当父类无法加载时,才有本身去尝试加载
loadclass这个方法用来去加载类:
- 它是一个同步方法
- 他首先会调用findLoadedclass方法区检查类是否被加载过了,没有?
- 调用父类的loadClass方法去尝试加载,如果没有父类,则会调用启动类加载器尝试加载
- 父类加载失败,就是调用自身的findClass方法去尝试加载
当用户需要自定义类加载器时,需要实现的就是这个findClass方法,如何找到需要加载的类,以下是个人示例,仅供参考
protected Class<?> findClass(String className) throws ClassNotFoundException{
byte[] data = this.loadClassData(className);
System.out.println("findClass invoke class:"+className);
System.out.println("findClass invoke classLoadName:"+classLoaderName);
return super.defineClass(className,data,0,data.length);
}
/**
* 自定义class的二进制数据从哪里获取,写一个用户自定义的ClassLoader类,唯一需要实现的是,
* 指定class数据的获取方式,获取到class 的二进制数据
* @param binaryName
* @return
*/
private byte[] loadClassData(String binaryName ) {
InputStream is = null;
byte[] bytes = null ;
ByteArrayOutputStream bos = null;
binaryName = binaryName.substring(binaryName.lastIndexOf(".")+1);
try{
is = new FileInputStream(new File(this.path+binaryName +this.finaNameExtession ));
bos = new ByteArrayOutputStream();
int length ;
while ( -1 != (length = is.read())){
bos.write(length);
}
bytes = bos.toByteArray();
}catch ( Exception e ){
e.printStackTrace();
} finally {
try {
if ( null != is )
is.close();
if ( null != bos )
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return bytes;
}