• 极客专栏正式上线!欢迎访问 https://www.jikewenku.com/topic.html
  • 极客专栏正式上线!欢迎访问 https://www.jikewenku.com/topic.html

java安全沙箱之ClassLoader双亲委派机制

技术杂谈 勤劳的小蚂蚁 2个月前 (02-09) 83次浏览 已收录 0个评论 扫描二维码

简介


“类加载体系”及ClassLoader双亲委派机制。java程序中的 .java文件编译完会生成 .class文件,而 .class文件就是通过被称为类加载器的ClassLoader加载的,而ClassLoder在加载过程中会使用“双亲委派机制”来加载 .class文件,先上图:


看着图从上往下介绍:

BootStrapClassLoader

启动类加载器,该ClassLoader是jvm在启动时创建的,用于加载 $JAVA_HOME/jre/lib下面的类库(或者通过参数-Xbootclasspath指定)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不能直接通过引用进行操作。

ExtClassLoader

扩展类加载器,该ClassLoader是在sun.misc.Launcher里作为一个内部类ExtClassLoader定义的(即 sun.misc.Launcher$ExtClassLoader),ExtClassLoader会加载 $JAVA_HOME/jre/lib/ext下的类库(或者通过参数-Djava.ext.dirs指定)。

AppClassLoader

应用程序类加载器,该ClassLoader同样是在sun.misc.Launcher里作为一个内部类AppClassLoader定义的(即 sun.misc.Launcher$AppClassLoader),AppClassLoader会加载java环境变量CLASSPATH所指定的路径下的类库,而CLASSPATH所指定的路径可以通过System.getProperty(“java.class.path”)获取;当然,该变量也可以覆盖,可以使用参数-cp,例如:java -cp 路径 (可以指定要执行的class目录)。

CustomClassLoader

自定义类加载器,该ClassLoader是指我们自定义的ClassLoader,比如tomcat的StandardClassLoader属于这一类;当然,大部分情况下使用AppClassLoader就足够了。

ClassLoader初始化源码


下面贴下jdk关于类加载的源码,上述四种类加载器中CustomClassLoader是用户自定义的,BootStrapClassLoader是jvm创建的,就不展示了;

这里展示下AppClassLoader和ExtClassLoader的启动过程,前面介绍过,AppClassLoader和ExtClassLoader都是在sun.misc.Launcher里定义的,而我的sun.misc.Launcher没有源码,大家将就看看反编译的代码吧。如果想看sun.*包下的类源码,大家可以下载openjdk来查看。

public Launcher(){
   ExtClassLoader extclassloader;
   try{
       extclassloader = ExtClassLoader.getExtClassLoader();
   }
   catch(IOException ioexception) {
       thrownewInternalError(“Could not create extension class loader”);
   }
   try{
       loader = AppClassLoader.getAppClassLoader(extclassloader);
   }
   catch(IOException ioexception1){
       thrownewInternalError(“Could not create application class loader”);
   }
   Thread.currentThread().setContextClassLoader(loader);
   String s = System.getProperty(“java.security.manager”);
   if(s != null){
       SecurityManager securitymanager = null;
       if(“”.equals(s) || “default”.equals(s))
           securitymanager = new SecurityManager();
       else
           try{
               securitymanager = (SecurityManager)loader.loadClass(s).newInstance();
           }
           catch(IllegalAccessException illegalaccessexception) { }
           catch(InstantiationException instantiationexception) { }
           catch(ClassNotFoundException classnotfoundexception) { }
           catch(ClassCastException classcastexception) { }
       if(securitymanager != null)
           System.setSecurityManager(securitymanager);
       else
           thrownewInternalError((new StringBuilder()).append(“Could not create SecurityManager: “).append(s).toString());
   }
}

可以看到在Launcher构造函数的执行过程如下:


再贴下ExtClassLoader源码:

publicstatic ExtClassLoader getExtClassLoader()
       throws IOException{
 
 File afile[] = getExtDirs();
   return (ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction(afile) {
       public Object run()throws IOException{
           int i = dirs.length;
           for(int j = 0; j < i; j++)
               MetaIndex.registerDirectory(dirs[j]);
           returnnew ExtClassLoader(dirs);
       }

       final File val$dirs[];

       {
           dirs = afile;
           super();
       }
   });
   PrivilegedActionException privilegedactionexception;
   privilegedactionexception;
   throw (IOException)privilegedactionexception.getException();
}
   
privatestatic File[] getExtDirs() {
   String s = System.getProperty(“java.ext.dirs”);
   File afile[];
   if(s != null)
   {
       StringTokenizer stringtokenizer = new StringTokenizer(s, File.pathSeparator);
       int i = stringtokenizer.countTokens();
       afile = new File[i];
       for(int j = 0; j < i; j++)
           afile[j] = new File(stringtokenizer.nextToken());

   } else
   {
       afile = new File[0];
   }
   return afile;

}


反编译的源码,大家将就看下;这里大家关注下getExtDirs()这个方法,它会获取属性”java.ext.dirs”所对应的值,然后通过系统分隔符分割,然后加载分割后的字符串对应的目录作为ClassLoader的类加载库。

下面看看AppClassLoader源码:

publicstatic ClassLoader getAppClassLoader(ClassLoader classloader)throws IOException{
   String s = System.getProperty(“java.class.path”);
   File afile[] = s != null ? Launcher.getClassPath(s) : new File[0];
   return (AppClassLoader)AccessController.doPrivileged(new PrivilegedAction(s, afile, classloader) {
       public Object run(){
           URL aurl[] = s != null ? Launcher.pathToURLs(path) : new URL[0];
           returnnew AppClassLoader(aurl, extcl);
       }

       final String val$s;
       final File val$path[];
       final ClassLoader val$extcl;

       {
           s = s1;
           path = afile;
           extcl = classloader;
           super();
       }
   });
}

首先获取”java.class.path”对应的属性,并转换为URL[]并设置为ClassLoader的类加载库,注意这里的方法入参classloader就是ExtClassLoader,在创AppClassLoader会传入ExtClassLoader作为parent ClassLoader。

上面就是ClassLoader的启动和初始化过程,后面会把loader作为应用程序的默认ClassLoader使用,看下面的测试用例:

package com.jvm.study.classload;

publicclassTest{
   publicstaticvoidmain(String… args){
       ClassLoader loader = Test.class.getClassLoader();
       System.err.println(“loader:”+loader);
       while (loader != null) {
           loader = loader.getParent();
           System.err.println(“in while:”+loader);
       }
   }

}


可以看到ClassLoader的层次结构,输出结果为:


ClassLoader双亲委派机制源码


前面谈到了ClassLoader的几类加载器,而ClassLoader使用双亲委派机制来加载class文件的。

ClassLoader的双亲委派机制是这样的(这里先忽略掉自定义类加载器CustomClassLoader):

(1)当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。

(2)当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。

(3)如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;

(4)若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。

下面贴下ClassLoader的loadClass(String name, boolean resolve)源码:

protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
   // First, check if the classhasalreadybeenloaded
   Class c = findLoadedClass(name);
   if (c == null) {
       try {
           if (parent != null) {
               c = parent.loadClass(name, false);
           } else {
               c = findBootstrapClassOrNull(name);
           }
       } catch (ClassNotFoundException e) {
           // ClassNotFoundException thrown ifclassnotfound
           //from the non-null parent classloader
       }
       if (c == null) {
           // If still not found, then invoke findClass in order
           // to find the class.
           c = findClass(name);
       }
   }
   if (resolve) {
       resolveClass(c);
   }
   return c;
}

代码很明朗:首先找缓存(findLoadedClass),没有的话就判断有没有parent,有的话就用parent来递归的loadClass,然而ExtClassLoader并没有设置parent,则会通过findBootstrapClassOrNull来加载class,而findBootstrapClassOrNull则会通过JNI方法”private native Class findBootstrapClass(String name)“来使用BootStrapClassLoader来加载class。

然后如果parent未找到class,则会调用findClass来加载class,findClass是一个protected的空方法,可以覆盖它以便自定义class加载过程。

另外,虽然ClassLoader加载类是使用loadClass方法,但是鼓励用 ClassLoader 的子类重写 findClass(String),而不是重写loadClass,这样就不会覆盖了类加载默认的双亲委派机制。

双亲委派机制为什么安全


前面谈到双亲委派机制是为了安全而设计的,但是为什么就安全了呢?举个例子,ClassLoader加载的class文件来源很多,比如编译器编译生成的class、或者网络下载的字节码。而一些来源的class文件是不可靠的,比如我可以自定义一个java.lang.Integer类来覆盖jdk中默认的Integer类,例如下面这样:

package java.lang;

/**
* hack
*/
publicclassInteger {
   publicInteger(int value){
       System.exit(0);
   }

}


初始化这个Integer的构造器是会退出JVM,破坏应用程序的正常进行,如果使用双亲委派机制的话该Integer类永远不会被调用,以为委托BootStrapClassLoader加载后会加载JDK中的Integer类而不会加载自定义的这个,可以看下下面这测试个用例:

publicstaticvoidmain(String… args){
 Integer i = new Integer(1);
 System.err.println(i);
}

执行时JVM并未在new Integer(1)时退出,说明未使用自定义的Integer,于是就保证了安全性。


丨极客文库, 版权所有丨如未注明 , 均为原创丨
本网站采用知识共享署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议进行授权
转载请注明原文链接:java安全沙箱之ClassLoader双亲委派机制
喜欢 (0)
[247507792@qq.com]
分享 (0)
勤劳的小蚂蚁
关于作者:
温馨提示:本文来源于网络,转载文章皆标明了出处,如果您发现侵权文章,请及时向站长反馈删除。

您必须 登录 才能发表评论!

  • 精品技术教程
  • 编程资源分享
  • 问答交流社区
  • 极客文库知识库

客服QQ


QQ:2248886839


工作时间:09:00-23:00