类加载器子系统与SPI

类加载

JVM中有两种类型的类加载器,由C++编写的及由Java编写的。除了启动类加载器(Bootstrap Class Loader)是由C++编写的,其他都是由Java编写的。由Java编写的类加载器都继承自类java.lang.ClassLoader。
在这里插入图片描述

启动类加载器

因为启动类加载器是由C++编写的,通过Java程序去查看显示的是null
因此,启动类加载器无法被Java程序调用
启动类加载器不像其他类加载器有实体,它是没有实体的,JVM将C++处理类加载的一套逻辑定义为启动类加载器

查看启动类加载器的加载路径(也可以通过-Xbootclasspath指定)

URL[] urLs = Launcher.getBootstrapClassPath().getURLs();for (URL urL : urLs) {System.out.println(urL);}

从openjdk源码

int JNICALL
JavaMain(void * _args)
{……mainClass = LoadMainClass(env, mode, what);……
}static jclass
LoadMainClass(JNIEnv *env, int mode, char *name)
{jmethodID mid;jstring str;jobject result;jlong start, end;jclass cls = GetLauncherHelperClass(env);NULL_CHECK0(cls);if (JLI_IsTraceLauncher()) {start = CounterGet();}NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,"checkAndLoadMain","(ZILjava/lang/String;)Ljava/lang/Class;"));str = NewPlatformString(env, name);CHECK_JNI_RETURN_0(result = (*env)->CallStaticObjectMethod(env, cls, mid, USE_STDERR, mode, str));if (JLI_IsTraceLauncher()) {end   = CounterGet();printf("%ld micro seconds to load main class\n",(long)(jint)Counter2Micros(end-start));printf("----%s----\n", JLDEBUG_ENV_ENTRY);}return (jclass)result;
}jclass
GetLauncherHelperClass(JNIEnv *env)
{if (helperClass == NULL) {NULL_CHECK0(helperClass = FindBootStrapClass(env,"sun/launcher/LauncherHelper"));}return helperClass;
}jclass
FindBootStrapClass(JNIEnv *env, const char* classname)
{if (findBootClass == NULL) {findBootClass = (FindClassFromBootLoader_t *)dlsym(RTLD_DEFAULT,"JVM_FindClassFromBootLoader");if (findBootClass == NULL) {JLI_ReportErrorMessage(DLL_ERROR4,"JVM_FindClassFromBootLoader");return NULL;}}return findBootClass(env, classname);
}JVM_ENTRY(jclass, JVM_FindClassFromBootLoader(JNIEnv* env,const char* name))JVMWrapper2("JVM_FindClassFromBootLoader %s", name);// Java libraries should ensure that name is never null...if (name == NULL || (int)strlen(name) > Symbol::max_length()) {// It's impossible to create this class;  the name cannot fit// into the constant pool.return NULL;}TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL);Klass* k = SystemDictionary::resolve_or_null(h_name, CHECK_NULL);if (k == NULL) {return NULL;}if (TraceClassResolution) {trace_class_resolution(k);}return (jclass) JNIHandles::make_local(env, k->java_mirror());
JVM_END

这套逻辑做的事情就是通过启动类加载器加载类sun.launcher.LauncherHelper,执行该类的方法checkAndLoadMain,加载main函数所在的类,启动扩展类加载器、应用类加载器也是在这个时候完成的。

扩展类加载器
 ClassLoader classLoader = ClassLoader.getSystemClassLoader().getParent();URLClassLoader urlClassLoader = (URLClassLoader) classLoader;URL[] urls = urlClassLoader.getURLs();for (URL url : urls) {System.out.println(url);}

可以通过java.ext.dirs指定

应用类加载器
 URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();URL[] urls1 = classLoader.getURLs();for (URL url : urls1) {System.out.println(url);}

可以通过java.class.path指定

自定义类加载器
public class MyClassLoad extends ClassLoader {@Overrideprotected Class<?> findClass(String className) throws ClassNotFoundException {System.out.println("---------------华丽分割线------------------");byte[] data = getData(className.replace('.', '/'));return defineClass(className, data, 0, data.length);}public static final String SUFFIX = ".class";private byte[] getData(String name) {InputStream inputStream = null;ByteArrayOutputStream outputStream = null;File file = new File(name + SUFFIX);if (!file.exists()) return null;try {inputStream = new FileInputStream(file);outputStream = new ByteArrayOutputStream();int size = 0;byte[] buffer = new byte[1024];while ((size = inputStream.read(buffer)) != -1) {outputStream.write(buffer, 0, size);}return outputStream.toByteArray();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return null;}public static void main(String[] args) throws ClassNotFoundException {MyClassLoad classLoad = new MyClassLoad();Class<?> clazz = classLoad.loadClass(MyClassLoad.class.getName());System.out.println(clazz);System.out.println(clazz.getClassLoader());}
}

类加载器创建链

上面已经讲了启动类加载器没有实体,只是将一段加载逻辑命名成启动类加载器。启动类加载器做的事情是:加载类sun.launcher.LauncherHelper,执行该类的方法checkAndLoadMain……启动类、扩展类、应用类加载器逻辑上的父子关系就是在这个方法的调用链中生成的?
1、\openjdk\jdk\src\share\classes\sun\launcher\LauncherHelper.java
核心代码:ClassLoader.getSystemClassLoader();

public enum LauncherHelper {
.......private static final ClassLoader scloader = ClassLoader.getSystemClassLoader();
......public static Class<?> checkAndLoadMain(boolean printToStderr,int mode,String what) {......mainClass = scloader.loadClass(cn);......

2、\openjdk\jdk\src\share\classes\java\lang\ClassLoader.java
核心代码:sun.misc.Launcher.getLauncher();

    public static ClassLoader getSystemClassLoader() {initSystemClassLoader();if (scl == null) {return null;}SecurityManager sm = System.getSecurityManager();if (sm != null) {checkClassLoaderPermission(scl, Reflection.getCallerClass());}return scl;}private static synchronized void initSystemClassLoader() {if (!sclSet) {if (scl != null)throw new IllegalStateException("recursive invocation");sun.misc.Launcher l = sun.misc.Launcher.getLauncher();……

3、\openjdk\jdk\src\share\classes\sun\misc\Launcher.java
核心代码:
• private static Launcher launcher = new Launcher();
• extcl = ExtClassLoader.getExtClassLoader();
• loader = AppClassLoader.getAppClassLoader(extcl);
• Thread.currentThread().setContextClassLoader(loader);

public class Launcher {private static URLStreamHandlerFactory factory = new Factory();private static Launcher launcher = new Launcher();private static String bootClassPath =System.getProperty("sun.boot.class.path");public static Launcher getLauncher() {return launcher;}private ClassLoader loader;public Launcher() {// Create the extension class loaderClassLoader 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 applicationtry {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);……

4、扩展类加载器的创建流程

public static ExtClassLoader getExtClassLoader() throws IOException{……return new ExtClassLoader(dirs);……public ExtClassLoader(File[] dirs) throws IOException {super(getExtURLs(dirs), null, factory);}URLClassLoader(URL[] urls, ClassLoader parent,AccessControlContext acc) {

第二个参数传的是null,其实就是parent=null

5、应用类加载器的创建流程

public static ClassLoader getAppClassLoader(final ClassLoader extcl)throws IOException {final String s = System.getProperty("java.class.path");final File[] path = (s == null) ? new File[0] : getClassPath(s);// Note: on bugid 4256530// Prior implementations of this doPrivileged() block supplied// a rather restrictive ACC via a call to the private method// AppClassLoader.getContext(). This proved overly restrictive// when loading  classes. Specifically it prevent// accessClassInPackage.sun.* grants from being honored.//return AccessController.doPrivileged(new PrivilegedAction<AppClassLoader>() {public AppClassLoader run() {URL[] urls =(s == null) ? new URL[0] : pathToURLs(path);return new AppClassLoader(urls, extcl);}});
}AppClassLoader(URL[] urls, ClassLoader parent) {super(urls, parent, factory);
}

整体流程
在这里插入图片描述

类加载器加载的类如何存储

在这里插入图片描述

双亲委派(为了安全)

如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;只有当父类加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载。
在这里插入图片描述
递归调用

protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> 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 statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}

打破双亲委派

自定义类加载器

根据类加载调用链,重写loadClass方法

 @Overrideprotected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {Class<?> c = this.findLoadedClass(name);if (c == null) {if (name.startsWith("com.zy")) {c = findClass(name);} else {c = this.getParent().loadClass(name);}}return c;}
SPI

在这里插入图片描述
SPI的全名为Service Provider Interface.大多数开发人员可能不熟悉,因为这个是针对厂商或者插件的。在java.util.ServiceLoader的文档里有比较详细的介绍。简单的总结下java spi机制的思想。我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,xml解析模块、jdbc模块的方案等。面向对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不使用实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候动态指定具体实现类,这就需要一种服务发现机制。 java spi就是提供这种功能的机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。

java SPI应用场景很广泛,在Java底层和一些框架中都很常用,比如java数据驱动加载和Dubbo。Java底层定义加载接口后,由不同的厂商提供驱动加载的实现方式,当我们需要加载不同的数据库的时候,只需要替换数据库对应的驱动加载jar包,就可以进行使用。

要使用Java SPI,需要遵循如下约定:

1、当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
2、接口实现类所在的jar包放在主程序的classpath中;
3、主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
4、SPI的实现类必须携带一个不带参数的构造方法;
在这里插入图片描述

线程上下文类加载器

Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。这些 SPI 的接口由 Java 核心库来提供,而这些 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径(CLASSPATH)里。SPI接口中的代码经常需要加载具体的实现类。那么问题来了,SPI的接口是Java核心库的一部分,是由启动类加载器(Bootstrap Classloader)来加载的;SPI的实现类是由系统类加载器(System ClassLoader)来加载的。引导类加载器是无法找到 SPI 的实现类的,因为依照双亲委派模型,BootstrapClassloader无法委派AppClassLoader来加载类。而线程上下文类加载器破坏了“双亲委派模型”,可以在执行线程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器。

1、是什么(一种特殊的类加载器,可以通过Thread获取,基于此可实现逆向委托加载)
线程上下文类加载器(context class loader)是从 JDK 1.2 开始引入的。类 Java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。

2、为什么
线程上下文从根本解决了一般应用不能违背双亲委派模式的问题。使java类加载体系显得更灵活。随着多核时代的来临,相信多线程开发将会越来越多地进入程序员的实际编码过程中。因此,在编写基础设施时, 通过使用线程上下文来加载类,应该是一个很好的选择。

Java默认的线程上下文类加载器是系统类加载器(AppClassLoader)。以下代码摘自sun.misc.Launch的无参构造函数Launch()。

// 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" );  
}  // Also set the context class loader for the primordial thread.  
Thread.currentThread().setContextClassLoader(loader);

使用线程上下文类加载器,可以在执行线程中抛弃双亲委派加载链模式,使用线程上下文里的类加载器加载类。典型的例子有:通过线程上下文来加载第三方库jndi实现,而不依赖于双亲委派。大部分Java application服务器(jboss, tomcat…)也是采用contextClassLoader来处理web服务。还有一些采用hot swap特性的框架,也使用了线程上下文类加载器,比如 seasar (full stack framework in japenese)。
  线程上下文从根本解决了一般应用不能违背双亲委派模式的问题。使java类加载体系显得更灵活。随着多核时代的来临,相信多线程开发将会越来越多地进入程序员的实际编码过程中。因此,在编写基础设施时, 通过使用线程上下文来加载类,应该是一个很好的选择。

3、怎么做

//获取
Thread.currentThread().getContextClassLoader()// 设置
Thread.currentThread().setContextClassLoader(new Classloader_4());

沙箱安全

1、什么是沙箱?
Java安全模型的核心就是Java沙箱(sandbox),什么是沙箱?沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么?——CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。
  所有的Java程序运行都可以指定沙箱,可以定制安全策略。

2、java中的安全模型
在Java中将执行程序分成本地代码和远程代码两种,本地代码默认视为可信任的,而远程代码则被看作是不受信的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期的Java实现中,安全依赖于沙箱 (Sandbox) 机制。如下图所示 JDK1.0安全模型
在这里插入图片描述
但如此严格的安全机制也给程序的功能扩展带来障碍,比如当用户希望远程代码访问本地系统的文件时候,就无法实现。因此在后续的 Java1.1 版本中,针对安全机制做了改进,增加了安全策略,允许用户指定代码对本地资源的访问权限。如下图所示 JDK1.1安全模型
在这里插入图片描述
在 Java1.2 版本中,再次改进了安全机制,增加了代码签名。不论本地代码或是远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机中权限不同的运行空间,来实现差异化的代码执行权限控制。如下图所示 JDK1.2安全模型
在这里插入图片描述
当前最新的安全机制实现,则引入了域 (Domain) 的概念。虚拟机会把所有代码加载到不同的系统域和应用域,系统域部分专门负责与关键资源进行交互,而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域 (Protected Domain),对应不一样的权限 (Permission)。存在于不同域中的类文件就具有了当前域的全部权限,如下图所示 最新的安全模型(jdk 1.6)
在这里插入图片描述
以上提到的都是基本的Java 安全模型概念,在应用开发中还有一些关于安全的复杂用法,其中最常用到的 API 就是 doPrivileged。doPrivileged 方法能够使一段受信任代码获得更大的权限,甚至比调用它的应用程序还要多,可做到临时访问更多的资源。有时候这是非常必要的,可以应付一些特殊的应用场景。例如,应用程序可能无法直接访问某些系统资源,但这样的应用程序必须得到这些资源才能够完成功能。

3、组成沙箱的基本组件
字节码校验器(bytecode verifier):确保Java类文件遵循Java语言规范。这样可以帮助Java程序实现内存保护。但并不是所有的类文件都会经过字节码校验,比如核心类。

类装载器(class loader):其中类装载器在3个方面对Java沙箱起作用

  1. 它防止恶意代码去干涉善意的代码;
  2. 它守护了被信任的类库边界;
  3. 它将代码归入保护域,确定了代码可以进行哪些操作。

虚拟机为不同的类加载器载入的类提供不同的命名空间,命名空间由一系列唯一的名称组成,每一个被装载的类将有一个名字,这个命名空间是由Java虚拟机为每一个类装载器维护的,它们互相之间甚至不可见。

类装载器采用的机制是双亲委派模式。

  • 从最内层JVM自带类加载器开始加载,外层恶意同名类得不到加载从而无法使用;
  • 由于严格通过包来区分了访问域,外层恶意的类通过内置代码也无法获得权限访问到内层类,破坏代码就自然无法生效。
  • 存取控制器(access controller):存取控制器可以控制核心API对操作系统的存取权限,而这个控制的策略设定,可以由用户指定。
    安全管理器(security manager):是核心API和操作系统之间的主要接口。实现权限控制,比存取控制器优先级高。
    安全软件包(security
    package):java.security下的类和扩展包下的类,允许用户为自己的应用增加新的安全特性,包括:
  • 安全提供者 消息摘要 数字签名 加密 鉴别


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部