java类加载以及双亲委派机制
java类加载运行过程
类加载过程 (classLoader.loadClass
)
加载: 在硬盘上查找并通过IO读入字节码文件,使用到类时才会加载,例如调用类的main()方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的
java.lang.Class对象,作为方法区这个类的各种数据的访问入口
验证: 校验字节码文件的正确性
准备: 给类的静态变量分配内存,并赋予默认值
解析: 将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用。(将符号转成内存地址的过程)
初始化: 对类的静态变量初始化为指定的值,执行静态代码块
类加载器和双亲委派机制
上面的类加载过程主要是由类加载器来实现的,java里有如下几种加载器
1.引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等
2.扩展类加载器(ExtClassLoader): 负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包
3.应用程序类加载器(AppClassLoader): 负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类
4.自定义加载器: 负责加载用户自定义路径下的类包
常见的类加载器
public static void main(String[] args) { //引导类加载器 System.out.println(String.class.getClassLoader()); //应用程序类加载器 System.out.println(TestClassLoader.class.getClassLoader()); //扩展类加载器 System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader()); } null sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$ExtClassLoader@77459877
结论:
- 由于引导类加载器是c++实现,java层面是获取不到引导类加载器的对象,所以String类的加载器为null
- AppClassLoader和ExtClassLoader都是
com.misc.Launcher
的静态类
关于ClassLoader的 parent
属性
public abstract class ClassLoader { //ClassLoader里面定义了一个parent属性,用于标识父类加载器 private final ClassLoader parent; }
关于URLClassLoader
1.URLClassLoader继承SecureClassLoader,
2.SecureClassLoader继承ClassLoader
3.AppClassLoader和ExtClassLoader都继承了URLClassLoader
类加载过程中Launcher.getLauncher()
public class Launcher { private static URLStreamHandlerFactory factory = new Factory(); //具体new Launcher()方法 private static Launcher launcher = new Launcher(); private static String bootClassPath = System.getProperty("sun.boot.class.path"); //默认的类加载器,在初始化的时候loader的值被赋予应用程序类加载器 private ClassLoader loader; private static URLStreamHandler fileHandler; //返回launcher对象 public static Launcher getLauncher() { return launcher; } }
new Launcher()
public Launcher() { ExtClassLoader extClassLoader; try { //获取扩展类加载器 extClassLoader = Launcher.ExtClassLoader.getExtClassLoader(); } catch (IOException var10) { throw new InternalError("Could not create extension class loader", var10); } try { //应用程序类加载器 this.loader = Launcher.AppClassLoader.getAppClassLoader(extClassLoader); } catch (IOException var9) { throw new InternalError("Could not create application class loader", var9); } Thread.currentThread().setContextClassLoader(this.loader); String var2 = System.getProperty("java.security.manager"); if (var2 != null) { SecurityManager var3 = null; if (!"".equals(var2) && !"default".equals(var2)) { try { var3 = (SecurityManager)this.loader.loadClass(var2).newInstance(); } catch (IllegalAccessException var5) { } catch (InstantiationException var6) { } catch (ClassNotFoundException var7) { } catch (ClassCastException var8) { } } else { var3 = new SecurityManager(); } if (var3 == null) { throw new InternalError("Could not create SecurityManager: " + var2); } System.setSecurityManager(var3); } }
关于new URLClassLoader()
public URLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) { //指定父类加载器 super(parent); SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkCreateClassLoader(); } acc = AccessController.getContext(); ucp = new URLClassPath(urls, factory, acc); }
获取扩展类加载器
public static ExtClassLoader getExtClassLoader() throws IOException { if (instance == null) { Class var0 = ExtClassLoader.class; synchronized(ExtClassLoader.class) { if (instance == null) { //往下走 instance = createExtClassLoader(); } } } return instance; } private static ExtClassLoader createExtClassLoader() throws IOException { try { return (ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<ExtClassLoader>() { public ExtClassLoader run() throws IOException { //获取系统扩展类jar包的文件 File[] var1 = Launcher.ExtClassLoader.getExtDirs(); int var2 = var1.length; for(int var3 = 0; var3 < var2; ++var3) { MetaIndex.registerDirectory(var1[var3]); } return new ExtClassLoader(var1); } }); } catch (PrivilegedActionException var1) { throw (IOException)var1.getException(); } } public ExtClassLoader(File[] extJarFiles) throws IOException { //初始化父类URLClassLoader的相关逻辑, //其中第二个参数为指定父加载器,此处父加载器应该是引导类加载器,但是java层面获取不到,所以传null super(getExtURLs(extJarFiles), (ClassLoader)null, Launcher.factory); SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this); }
获取应用程序类加载器
//获取应用类加载器,这里classerLoader是上一步获取的扩展类加载器 public static ClassLoader getAppClassLoader(final ClassLoader classLoader) throws IOException { //获取程序的类路径定义信息,类似classpath的地址 final String classPath = System.getProperty("java.class.path"); final File[] var2 = classPath == null ? new File[0] : Launcher.getClassPath(classPath); return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<AppClassLoader>() { public AppClassLoader run() { URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2); return new AppClassLoader(var1x, classLoader); } }); } AppClassLoader(URL[] var1, ClassLoader classLoader) { /初始化父类URLClassLoader的相关逻辑, //其中第二个参数为指定父加载器,这里将扩展类加载器指定为应用程序类加载器的父加载器(不是父类,他们是平级的) super(var1, classLoader, Launcher.factory); this.ucp.initLookupCache(this); }
各类加载器加载的类地址
public static void main(String[] args) { ClassLoader appClassLoader = ClassLoader.getSystemClassLoader(); ClassLoader extClassLoader = appClassLoader.getParent(); ClassLoader bootstrapClassLoader = extClassLoader.getParent(); System.out.println(appClassLoader); System.out.println(extClassLoader); System.out.println(bootstrapClassLoader); System.out.println(); System.out.println("bootstrapClassLoader加载路径:"); bootstrapClassLoader加载路径: //file:/C:/application/java/jre/lib/resources.jar //file:/C:/application/java/jre/lib/rt.jar //file:/C:/application/java/jre/lib/sunrsasign.jar //file:/C:/application/java/jre/lib/jsse.jar //file:/C:/application/java/jre/lib/jce.jar //file:/C:/application/java/jre/lib/charsets.jar //file:/C:/application/java/jre/lib/jfr.jar //file:/C:/application/java/jre/classes URL[] urLs = Launcher.getBootstrapClassPath().getURLs(); for (URL urL : urLs) { System.out.println(urL); } System.out.println(); System.out.println("extClassLoader加载路径:"); //C:\application\java\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext System.out.println(System.getProperty("java.ext.dirs")); System.out.println(); System.out.println("appClassLoader加载路径:"); //这里也会打印出ext和bootstrap的类路径地址,但appClassLoader不会去加载这些类。因为双亲委派机制 //appClassLoader主要加载target目录下的类 System.out.println(System.getProperty("java.class.path")); }
双亲委派机制
JVM类加载器的亲子层结构图
加载流程:
1.当需要加载Some.class时,首先由appClassLoader查找自身有没有加载过该类,如果有,则直接返回,如果没有,则委托上一级加载
2.extClassLoader同样查找自身有没有加载过该类,如果有,则直接返回,如果没有,则委托上一级加载
3.bootstrapClassLoader同样查找自身有没有加载过该类,如果有,则直接返回,如果没有,则试着去加载。如果在自己类库中找不到该类,则加载不成功,将会委托下一级classLoader去加载该类。
4.extClassLoader尝试加载该类,如果在自己类库中找不到该类,则加载不成功,将会委托下一级classLoader去加载该类。
5.appClassLoader在自己目录下,也就是target目录下查找该类,找到,并加载执行该类。
双亲委派机制简单点就是,先找父加载器加载,不行再由子加载器加载
源码
当需要加载一个自定义Some.class时,从应用程序类加载器开始
ClassLoader关于类加载的方法,所有的类加载器均继承了该方法
1.查找当前加载器是否加载过该类(findLoadedClass)
//查找当前类加载器是否加载过该类,如果有,直接返回,如果没有,返回null protected final Class<?> findLoadedClass(String name) { if (!checkName(name)) return null; return findLoadedClass0(name); } //C++方法 private native final Class<?> findLoadedClass0(String name);
- 查找引导类加载器是否加载过该类(
findBootstrapClassOrNull
)
private Class<?> findBootstrapClassOrNull(String name){ if (!checkName(name)) return null; return findBootstrapClass(name); } //C++方法,查找引导类加载器是否加载过该类 private native Class<?> findBootstrapClass(String name);
- findClass()方法
从URL搜索路径中通过指定的类名称查找并加载类。
加载步骤:
appClassLoader
//appClasssLoader的loadClass方法 public Class<?> loadClass(String var1, boolean resolve) throws ClassNotFoundException { int var3 = var1.lastIndexOf(46); if (var3 != -1) { SecurityManager var4 = System.getSecurityManager(); if (var4 != null) { var4.checkPackageAccess(var1.substring(0, var3)); } } if (this.ucp.knownToNotExist(var1)) { //appClassLoader尝试着从自己已经加载过的类中查找该类。 Class var5 = this.findLoadedClass(var1); if (var5 != null) { if (resolve) { this.resolveClass(var5); } return var5; } else { throw new ClassNotFoundException(var1); } } else { //调用URLClassLoader加载loadClass,注意这里super不是extClassLoader,这个是继承关系,找的是父类方法。 //extClassLoader是appClassLoader的父加载器 return super.loadClass(var1, resolve); } }
urlClassLoader
public final Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // First check if we have permission to access the package. This // should go away once we've added support for exported packages. SecurityManager sm = System.getSecurityManager(); if (sm != null) { int i = name.lastIndexOf('.'); if (i != -1) { sm.checkPackageAccess(name.substring(0, i)); } } //classLoader.loadClass(); return super.loadClass(name, resolve); }
- ClassLoader
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { //第一步,从已经加载过的class中找,注意这里的this对象是appClassLoader Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { //此时parent是extClassLoader if (parent != null) { //向extClassLoader委托加载类 c = parent.loadClass(name, false); } else { //extClassLoader的parent为bootstrapClassLoader,但是java获取不到,所以为null, //此时查找引导类加载器是否加载过该类 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); //extClassLoader尝试加载该类,如果没有加载,则c为null,交由下一级类加载器,也就是appClassLoader尝试加载 c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
为什么要设计双亲委派机制
1.沙箱安全机制: 自己写的java.lang.String.class类不会被加载,这样防止核心api库被随意篡改(核心类库永远都是bootstrap加载,所以appClassLoader无法加载和核心类库全路径一样的类)
2.避免类的重复加载:当父加载器已经加载了该类,就没有必要子类加载器再加载一次,保证被加载的类的唯一性
全盘委托机制
全盘负责是指当一个ClassLoader装在一个类时,除非显示的使用另外一个ClassLoader,该类所依赖及引用的类也由这个ClassLoader载入。
自定义类加载器
自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是loadClass(Sting,boolean),实现了双亲委派机制,还有一个
方法是findClass,默认实现是空方法,所以我们自定义类加载器主要是重写findclass方法。
自定义类加载的器的父加载器默认为appClassLoader,这样默认满足双亲委派机制。
打破双亲委派机制
以Tomcat类加载为例,Tomcat 如果使用默认的双亲委派类加载机制行不行?
我们思考一下:Tomcat是个web容器,那么它要解决什么问题:
1.一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类都是独立,保证相互隔离。
2.部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加进虚拟机。
3.web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。
4.web容器要支持jsp的修改,我们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已,经是司空见惯的事情,web容器要支持jsp的修改后不用重启。