today we’re taking about classLoader
topic
- what is classLoader
- parent classLoader
- why need three classLoader
- source of classLoader
- more details
ok, let‘s go
what is classLoader
首先 ,什么是classLoader ,首先看下官方解释
A class loader is an object that is responsible for loading classes. The class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class. A typical strategy is to transform the name into a file name and then read a "class file" of that name from a file system.
简单来说就是类加载器, 负责把class 文件 加载到虚拟机(将class文件从硬盘加载到内存的一个过程)
思考下, 加载某个class需要className , 接下来通过这个className去哪里查找,怎么查找
Normally, the Java virtual machine loads classes from the local file system in a platform-dependent manner. For example, on UNIX systems, the virtual machine loads classes from the directory defined by the CLASSPATH environment variable.
However, some classes may not originate from a file; they may originate from other sources, such as the network, or they could be constructed by an application.
通常, 虚拟机所加载的类来自文件系统,通过CLASSPATH 指定路径, 当然也可以来自网络传输(通过URL抽象出这些resource)
parent classLoader
parent classLoader, 通常被称为“双亲委派” , 一直觉得这个翻译有点问题 , 双亲 , 其实只有一个parent
The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself. The virtual machine's built-in class loader, called the "bootstrap class loader", does not itself have a parent but may serve as the parent of a ClassLoader instance.
ClassLoader使用的是设计模式中的委派模式, 通过parent去加载,如果parent为空,则依赖BootstrapClassLoader, 注意这个类加载器是虚拟机实现的一部分.而且我们程序中是无法获取到的,例如如下代码
public class ClassLoaderDemo {
public static void main(String[] args) {
System.out.println(ClassLoaderDemo.class.getClassLoader());
System.out.println(String.class.getClassLoader());
System.out.println(Integer.class.getClassLoader());
}
}
运行结果如下(在看结果之前,先有自己的思考,不应该以结果导向作为推论)
至于为什么,让我们先来了解类加载的层级关系,通常我们常见的classLoader有如下几个
- Bootstrap ClassLoader:最顶层的加载类,主要加载核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等;
- Extention ClassLoader:扩展的类加载器,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件;
- Appclass Loader:也称为SystemAppClass,加载当前应用的classpath的所有类;
类关系图如下
OK,回到刚刚的问题,首先 classLoader也是一个class ,所以我们可以看到结果,我们自己定义的类ClassLoaderDemo
的类加载器是 sun.misc.Launcher$AppClassLoader@18b4aac2
($表示内部类,@表示对象hashcode,ps:类名生成规则我找不到哪里有, 看了虚拟机规范、JLS都没有找到,应该是编译器的,讲道理应该是虚拟机)
从类名称我们可以知道, 加载我们自定义类的类加载的一些基本信息,首先它是 sun.misc.Launcher
类的一个内部类AppClassLoader
,为了理解虚拟机的层级关系, 我们需要知道虚拟机的启动过程
至于String 、Integer 为啥获取为null, 这是因为是BootStrap加载的(至于为啥获取不到,因为在jdk是没有这个类的,安全性的考虑吧可能)
虚拟机的启动过程
详细参考 https://www.cnblogs.com/bhlsheji/p/4017816.html
或参阅OpenJDK下的sun.misc.Launcher
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);
// Finally, install a security manager if requested
String s = System.getProperty("java.security.manager");
if (s != null) {
SecurityManager sm = null;
if ("".equals(s) || "default".equals(s)) {
sm = new java.lang.SecurityManager();
} else {
try {
sm = (SecurityManager)loader.loadClass(s).newInstance();
} catch (IllegalAccessException e) {
} catch (InstantiationException e) {
} catch (ClassNotFoundException e) {
} catch (ClassCastException e) {
}
}
if (sm != null) {
System.setSecurityManager(sm);
} else {
throw new InternalError(
"Could not create SecurityManager: " + s);
}
}
}
双亲委派的好处
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。因此,使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处:类随着它的类加载器一起具备了一种带有优先级的层次关系。
例如类java.lang.Object
,它由启动类加载器加载。双亲委派模型保证任何类加载器收到的对java.lang.Object
的加载请求,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。
相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为java.lang.Object的类,并用自定义的类加载器加载,那系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。
既然这样是不是只要一个classLoder就不会有上述的问题了?
why need three classLoader
Java中有三种基础的类加载器 BootStrap, Extension and System
他们都有一个职能,就是从不同的包中加载类。
但是一个类加载器完全可以加载所有的类,为什么要有3种基础的类型的类加载器呢?
Java中有三种基础的类加载器主要为了安全。(另一个是为了扩展,所有应用都需要用同一个jar)
1.2版本的JVM中,只有一个类加载器,就是现在的“Bootstrap”类加载器。
类加载器加载类的方式是,加载器先调用父加载器对类进行加载,如果父加载器找不到该类,此加载器才会去加载该类。
最关键的是, 除非是同一个类加载器加载的类 ,否则JVM不会保证包访问级别(如果不指明private/public或protected,则方法和属性具有包访问级别)。
因此,假如用户调用他编写的java.lang.MyClass类。理论上该类可以访问和改变java.lang包下其他类的默认访问修饰符的属性和方法的能力。Java语言本身并没有阻止这种行为。但是JVM则会阻止这种行为,因为java核心类库的java.lang包下的类是由bootstrap类加载器加载的。不是同一个类加载器加载的类等于不具有包级别的访问权限。
类加载器中的其他安全特性也会阻止这种类型侵入。
所以为什么有三种基础的类加载器?是因为他们代表三种不同的信任级别。最可信的级别是java核心API类。然后是安装的拓展类,最后才是在类路径中的类(属于你本机的类)。
其他解释1
类加载主要的应用场景是在应用服务器上。
如果你想启动Tomcat。这至少需要一个类加载器来运行Tomcat自己。
然后你想在Tomcat容器中部署项目。因此Tomcat需要加载和分析甚至在Tomcat启动前都不存在的类。
然后你又想在Tomcat中部署另外一个应用。 第二个应用可能也会用到第一个应用使用的库,但是版本不同。
因此你就需要每个应都有相互隔离的类加载器,否则第二个应用的类可能会覆盖第一个应用之前加载的类,从而造成一些意想不到的后果。
然后你想停掉某个web应用。该应用的类加载器应该会被销毁,垃圾也随之被回收,这样可以避免内存泄露。
或许类加载器还有其他用途,但这是我印象中最常见的用法。
其他解释2
多种类加载器待办可以同时加载多个应用程序(一个加载服务,另外的加载器用来服务器内部的部署)。
每种加载器都有对应的层级来加载某些特定的类,来保证他们之间的安全性。
参考
The Extension Mechanism — How to make custom APIs available to all applications running on the Java platform.
source of classLoader
ClassLoader
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;
}
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError{
protectionDomain = preDefineClass(name, protectionDomain);
String source = defineClassSourceLocation(protectionDomain);
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
postDefineClass(c, protectionDomain);
return c;
}
findClass 见URLClassLoader实现,最后会调用defineClass
URLClassLoder
protected Class<?> findClass(final String name)
throws ClassNotFoundException{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
类加载的过程 加载 、验证、 准备、 链接 、初始化 、使用 、卸载
more details
security(安全)
Class loaders may typically be used by security managers to indicate security domains.
Parallel(并发)
Class loaders that support concurrent loading of classes are known as parallel capable class loaders and are required to register themselves at their class initialization time by invoking the
ClassLoader.registerAsParallelCapable
method. Note that theClassLoader
class is registered as parallel capable by default. However, its subclasses still need to register themselves if they are parallel capable.
In environments in which the delegation model is not strictly hierarchical, class loaders need to be parallel capable, otherwise class loading can lead to deadlocks because the loader lock is held for the duration of the class loading process (seeloadClass
methods).protected Object getClassLoadingLock(String className) { Object lock = this; if (parallelLockMap != null) { Object newLock = new Object(); lock = parallelLockMap.putIfAbsent(className, newLock); if (lock == null) { lock = newLock; } } return lock; }
ClassNotFoundException and NoClassDefFoundError(LinkageError)
这个议题太大了,查阅了好多资料…
涉及虚拟机编译 、类格式、类加载过程、虚拟机启动过程、hotspot、openjdk
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 951488791@qq.com