java classloader原理深究

前面已经写过一篇关于java classloader的拙文java classloader原理初探html

时隔几年,再看一遍,以为有些地方显得太过苍白,因而再来一篇:java

完成一个Java类以后,通过javac编译,会生成一个class文件,这个class文件中包含跟这个类相关的全部基本信息:属性字段,方法等。这些都属于一个类的元数据,是不变的部分。在执行过程,则须要根据类的元数据信息生成一个实例对象,这个实例对象能够根据不一样场景拥有不一样状态。也就是说同一个class对应了运行过程当中的不一样状态。(请注意这里是class,不是object)每个java文件在被编译以后,编译器都会给加入一个public的静态字段叫:class。在程序中,咱们就能够经过SomeObject.class的方式来获取一个Class对象。
Java中经过包路径和类名来肯定一个类,经过java api咱们知道Class中有个方法是getClassLoader(),但若是一个类被两个不一样classloader加载, 这个方法就会返回不一样结果,在这种状况下,虽然是同一个class,但被不一样class loader加载,那对应的Class对象就不是同一个。

为何要设置环境变量:JAVA_HOME, CLASSPATH?JAVA_HOME对应的是java的安装根路径,为了能在系统中随处都能用到java给咱们提供的命令行工具,因此要把JAVA_HOME/bin的路径添加到PATH中。可是CLASSPATH呢,为何须要在CLASSPATH中指定那三个jar(tools.jar, dt.jar, rt.jar)?
先说明一下tools.jar主要是一些java 工具类,能够从openjdk上下载jdk的源码,找langtools这个路径下的文件来看看, 若是想写一些工具类,好比经过java来分析或编译java文件,能够查看一下tools里面的javac相关的api;dt.jar主要是swing相关的一些类。而rt.jar则是运行时相关的,是java的核心库中的java类。
再来理解一下java在启动时类的代理模式加载顺序。JAVA中除了Bootstrap class loader 都有一个parent class loader。参阅java 官方对于“ Understanding Extension Class Loading”的说明。里面提到了三个方面的加载内容:一是rt.jar和i18n.jar等基础类包;二是扩展包;三就是咱们classpath指定的依赖包。对于第一部分,由Bootstrap class loader负责加载,因此java包中的类的class loader就是bootstrap class loader,而扩展路径(通常是在jre/lib/ext)下由 extension class loader(ExtClassLoader)负责. 第三部分的加载就须要应用class loader(AppClassLoader)来负责了。通常状况下,直接经过java启动时,会注明一下-classpath 或 -cp来标识出依赖包列表(注意,不是一个路径,而是文件列表)。代码中负责初始化相关class loader的过程是在sun.misc.Launcher类中, 下面是Launcher的constructor方法中的实现。 public Launcher() {         // Create the extension class loader         ClassLoader extcl;         try {             extcl = ExtClassLoader.getExtClassLoader();         } catch (IOException e) {             throw new InternalError(                 "Could not create extension class loader", e);         }         // Now create the class loader to use to launch the application         try {             loader = AppClassLoader.getAppClassLoader(extcl);         } catch (IOException e) {             throw new InternalError(                 "Could not create application class loader", e);         }         // Also set the context class loader for the primordial thread.         Thread.currentThread().setContextClassLoader(loader); ….. } 对于前面提到的相关加载路径也都在这个类中有相关代码,不一一列举。 Java Api中的ClassLoader类中的loadClass方法: 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                 }                 if (c == null) {                     // If still not found, then invoke findClass in order                     // to find the class.                     long t1 = System.nanoTime();                     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;         }     } 加载某个类时,当前classloader都会先委托parent class loader尝试加载。这种处理方式,有几个方面的考虑:一是安全问题,若是攻击者模拟实现了对应的类如java.lang.String, 当经过loadClass来加载类时,会首先到parent classloader中查找,很明显能够在bootstrap class loader中找到,这样能够尽量保护最关键的代码。一是冗余问题,经过这种方式能够最大限度的下降重复加载。每一个层次的classloader负责对应路径下的类库加载,而对于应用实现来讲就能够集中在应用系统中。这里能够考虑一下web container的实现,如tomcat。每一个jsp页面都会最终被编译成一个class文件,并放到work路径中,因此每一个web容器都会自建一个classloader来加载指定路径中的class文件。 经过上面的说明,能够了解到jvm在启动过程当中,若是发现有相同包路径的状况(不一样jar,但相同包)下的同名类,则仅会加载一次,主要看哪一个包在最前面。这里有个问题:为何是在前面的包能够起做用,后面不行?若是是由同一个classloader加载还不会被覆盖么?其实若是是本身实现的classloader,那就能够调整这种策略,但java中约定的就是先加载的class会根据起binary name,将class metadata存于永久区,因而后面再有同名的类,均可以被找到,而不须要从新加载。 
相关文章
相关标签/搜索