java类加载器机制
参考https://blog.csdn.net/zhangjg_blog/article/details/16102131 https://www.jianshu.com/p/b6547abd0706 https://www.jianshu.com/p/8c8d6cba1f8e https://www.ibm.com/developerworks/cn/java/j-lo-classloader/ 类的加载机制简介JVM除了比较类是否相等还要比较加载这两个类的类加载器是否相等,只有同时满足条件,两个类才能被认定是相等的。 ? JVM就是按照下面的顺序一步一步的将字节码文件加载到内存中并生成相应的对象的。
首先将字节码加载到内存中,然后对字节码进行连接,连接阶段包括了验证准备解析这3个步骤,连接完毕之后再进行初始化工作;下面我们一一了解。 ? 获取Class对象的方式获取Class对象的方式有3种:
? 这几种获取Class对象的不同
Class.forName(String className)源码: public static Class<?> forName(String className) throws ClassNotFoundException { Class<?> caller = Reflection.getCallerClass(); return forName0(className,true,ClassLoader.getClassLoader(caller),caller); } private static native Class<?> forName0(String name,boolean initialize,ClassLoader loader,Class<?> caller) ? 就是说第二个参数initialize是控制是否对类进行静态初始化。而Class.forName(String className)内部是true,所以会执行静态初始化工作。 ? 什么是类的加载?虚拟机加载类有两种方式,一种方式ClassLoader.loadClass()方法,另一种是使用反射API,Class.forName()方法,其实Class.forName()方法内部也是使用的ClassLoader。 ? 类的加载指的是将类的.class文件中的二进制数据读入内存中,将其放在运行时数据区域的方法区内,然后在堆中创建java.lang.Class对象,用来封装类在方法区的数据结构.只有java虚拟机才会创建class对象,并且是一一对应关系,这样才能通过反射找到相应的类信息。 我们上面提到过Class这个类,这个类我们并没有new过,这个类是由java虚拟机创建的。通过它可以找到类的信息,我们来看下源码: /* * Constructor. Only the Java Virtual Machine creates Class * objects. */ private Class() {...} ? 从上面贴出的Class类的构造方法源码中,我们知道这个构造器是私有的,并且只有虚拟机才能创建这个类的对象。 讲到类加载,我们不得不了解类加载器。 ? 类加载器及各类加载器关系java中(指的是javase)有几种类加载器。每个类加载器在创建的时候已经指定他们对应的目录,也就是说每个类加载器去哪里加载类是确定的。
这几种ClassLoader并不是继承的关系,而是一种委托关系。 那么类加载器是如何工作的呢?可以参看jdk中ClassLoader类的源码。 private final ClassLoader parent; protected Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First,check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name,false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } return c; } } ... ? 从源码可以总结出三个特性,分别为委派,可见性和单一性,其他文章上对这三个特性的介绍如下:
? 其中,委派机制是基础,在其他资料中也把这种机制叫做类加载器的双亲委派模型,其实说的是同一个意思。可加性和单一性是依赖于委派机制的。 ? 他们之间的关系可以通过例子展示: ClassLoader appClassLoader = ClassLoaderTest.class.getClassLoader(); System.out.println(appClassLoader); //[email?protected] ClassLoader extClassLoader = appClassLoader.getParent(); System.out.println(extClassLoader); //[email?protected] //AppClassLoader的父加载器是ExtClassLoader System.out.println(extClassLoader.getParent()); //null //ExtClassLoader的父加载器是null,也就是BootStrap,这是由c语言实现的 ? 由打印结果可知,
? 自定义ClassLoader验证package com.puppet.test; import java.io.IOException; import java.io.InputStream; public class MyClassLoader { public static void main(String args[]) throws ClassNotFoundException,InstantiationException,IllegalAccessException { ClassLoader loader = new ClassLoader() { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class"; InputStream inputStream = getClass().getResourceAsStream(fileName); if (inputStream == null) { return super.loadClass(name); } else { try { byte[] bytes = new byte[inputStream.available()]; inputStream.read(bytes); return defineClass(name,bytes,0,bytes.length); } catch (IOException e) { e.printStackTrace(); throw new ClassNotFoundException(name); } } } }; Object object = loader.loadClass("jvm.classloader.MyClassLoader").newInstance(); System.out.println(object instanceof com.puppet.test.MyClassLoader); } } ? 结果为false。 ? 所以,我们如果开发自己的类加载器,只需要继承jdk中的ClassLoader类,并覆盖findClass方法就可以了,剩下的而工作,父类会完成。其他java平台有的根据自己的需求,实现了自己特定的类加载器,例如javaee平台中的tomcat服务器,android平台中的dalvik虚拟机也定义了自己的类加载器。 ? ? 系统类加载器和线程上下文类加载器在java中,还存在两个概念,分别是系统类加载器和线程上下文类加载器。 ? 其实系统类加载器就是AppClassLoader应用程序类加载器,它两个指的是同一个加载器,以下代码可以验证: ClassLoader appClassLoader = ClassLoaderTest.class.getClassLoader(); System.out.println(appClassLoader); //[email?protected] ClassLoader sysClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(sysClassLoader); //[email?protected] //由上面的验证可知,应用程序类加载器和系统类加载器是相同的,因为地址是一样的 ? 这两个类加载器对应的输出,不仅类名相同,连对象的哈希值都是一样的,这充分说明系统类加载器和应用程序类加载器不仅是同一个类,更是同一个类的同一个对象。 ? 每个线程都会有一个上下文类加载器,由于在线程执行时加载用到的类,默认情况下是父线程的上下文类加载器,也就是AppClassLoader。 new Thread(new Runnable() { @Override public void run() { ClassLoader threadcontextClassLosder = Thread.currentThread().getContextClassLoader(); System.out.println(threadcontextClassLosder); //[email?protected] } }).start(); ? 这个子线程在执行时打印的信息为[email?protected],可以看到和主线程中的AppClassLoader是同一个对象(哈希值相同)。 ? 也可以为线程设置特定的类加载器,这样的话,线程在执行时就会使用这个特定的类加载器来加载使用到的类。如下代码: Thread th = new Thread(new Runnable() { @Override public void run() { ClassLoader threadcontextClassLosder = Thread.currentThread().getContextClassLoader(); System.out.println(threadcontextClassLosder); //[email?protected] } }); th.setContextClassLoader(new ClassLoader() {}); th.start(); ? 在线程运行之前,为它设置了一个匿名内部类的类加载器对象,线程运行时,输出的信息为:[email?protected],也就是我们设置的那个类加载器对象。 ? 类的连接讲完了类的加载之后,我们需要了解一下类的连接。类的连接有三步,分别是验证,准备,解析。下面让我们一一了解 首先我们看看验证阶段。验证阶段主要做了以下工作
准备阶段java虚拟机为类的静态变量分配内存并赋予默认的初始值。如int分配4个字节并赋值为0,long分配8字节并赋值为0; 解析阶段解析阶段主要是将符号引用转化为直接引用的过程。比如 A类中的a方法引用了B类中的b方法,那么它会找到B类的b方法的内存地址,将符号引用替换为直接引用(内存地址)。 ? 初始化类加载进内存后不一定会初始化,下边是几种触发类的主动初始化(类的静态初始化)的方式:
只有上面6种情况才是主动使用,也只有上面六种情况的发生才会引发类的初始化。 ? 同时我们需要注意下面几个Tips:
?
这里都是指的final static修饰的属性,而只有static修饰的属性就属于触发初始化的第二条。 ?
? 例子: public class Test1 { public static void main(String args[]){ System.out.println(FinalTest.x); } } class FinalTest{ public static final int x =6/3; static { System.out.println("FinalTest static block"); } } ? ? 上面和下面的例子大家对比下,然后自己看看输出的是什么? public class Test2 { public static void main(String args[]){ System.out.println(FinalTest2.x); } } class FinalTest2{ public static final int x =new Random().nextInt(100); static { System.out.println("FinalTest2 static block"); } } ? ? 第一个输出的是 2
? 第二个输出的是 FinalTest2 static block 61(随机数) ? ? 那么将第一个例子的final去掉之后呢?输出又是什么呢? 这就是对类的首次主动使用,引用类的静态变量,输出的当然是: FinalTest static block 2 ? ? 类的初始化步骤讲到这里我们应该对类的加载-连接-初始化有一个全局概念了,那么接下来我们看看类具体初始化执行步骤。 我们分两种情况讨论,一种是类有父类,一种是类没有父类。(当然所有类的顶级父类都是Object) 没有父类的情况:
? 有父类的情况:
? 在这要说明下,静态代码块和静态属性是等价的,他们是按照代码顺序执行的。 ? 例子public class Singleton { private static Singleton singleton = new Singleton(); public static int counter1; public static int counter2 = 0; private Singleton() { counter1++; counter2++; } public static Singleton getSingleton() { return singleton; } } public class TestSingleton { public static void main(String args[]){ Singleton singleton = Singleton.getSingleton(); System.out.println("counter1="+singleton.counter1); System.out.println("counter2="+singleton.counter2); } } ? 输出是: ? 注:调整counter1,counter2和singleton的顺序会得到不同结果。 ? 总结在Android中,QQZone团队提出的基于Dex分包的热修复解决方案就属于加载外部的类,本来应当由开发者自己实现classloader来实现加载过程,但是Android本身已经为我们封装好了一个classloader,就是DexClassLoader。 事实上,如今Java中很多插件化开发,动态部署,热修复等动态技术都是基于Java的类加载器来展开的。因此,我才会想专门用一篇文章总结Java的类加载器和加载机制。后面我会找时间基于HotFix详细的分析其中的类加载过程。毕竟理论总要落实到代码才会让人印象深刻。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |