1.简介
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.
Every Class object contains a reference to the ClassLoader that
defined it.
类加载器负责加载类。给定类的二进制名,类加载器会尝试定位或生成构成类定义的数据。典型的策略是将名称转换为文件名,然后从文件系统中读取该名字的类文件。
每个Class对象都包含定义它的ClassLoader。
Class objects for array classes are not created by class loaders, but
are created automatically as required by the Java runtime. The class
loader for an array class, as returned by Class.getClassLoader() is
the same as the class loader for its element type; if the element type
is a primitive type, then the array class has no class loader.
数组Class不是由类加载器创建的,而是由JVM自动创建的。Class.getClassLoader()返回的与其元素类型的类加载器一样;如果是基本类型,则数组类没有类加载器。
示例:
int[] iArray = new int[10];
System.out.println(iArray.getClass().getClassLoader());
Integer[] intArray = new Integer[10];
System.out.println(intArray.getClass().getClassLoader());
String[] strings = new String[10];
System.out.println(strings.getClass().getClassLoader());
结果:
null
null
null
Applications implement subclasses of ClassLoader in order to extend
the manner in which the Java virtual machine dynamically loads
classes.
Class loaders may typically be used by security managers to indicate
security domains.
应用程序可以通过继承ClassLoader扩展JVM动态加载类的方式。
安全管理器可以通过使用类加载器来显示安全域。
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类使用委派模型来搜索类和资源,其每个实例都有一个关联的父类加载器。当请求查找类或资源时,会首先委派给父加载器,然后才是自己尝试加载。JVM内置的"bootstrap class loader"没有父亲,但可以作为ClassLoader实例的父亲。
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 the
ClassLoader 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 (see loadClass
methods).
支持并发加载类的类加载器称为并发类加载器,需要通过调用ClassLoader.registerAdParallelCapable方法在类初始化时注册自己。ClassLoder类默认注册为并行。但是,其子类仍然需要注册自己。
在委派模型不是严格分层的环境中,类加载器需要具有并行能力,否则会导致死锁。因为在类加载过程中一直持有loader lock (参见loadClass方法)
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.
通常,JVM以与平台相关的方式从本地文件系统加载类。例如,在UNIX系统上,JVM从CLASSPATH环境变量定义的目录中加载类。
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. The method defineClass converts an
array of bytes into an instance of class Class. Instances of this newly
defined class can be created using Class.newInstance.
但是,某些类可能不是源自文件,它们的来源可能是网络,或是由应用程序创建。方法defineClass将字节数组转换为Class类实例。可以使用Class.newInstance创建此类的实例。
The methods and constructors of objects created by a class loader
may reference other classes. To determine the class(es) referred to,
the Java virtual machine invokes the loadClass method of the class
loader that originally created the class.
类加载器创建的对象和构造器可能会引用其他类。要确定所引用的类,JVM将调用该类类加载器的loadClass方法。
ClassLoader loader = new NetworkClassLoader(host, port);
Object main = loader.loadClass("Main", true).newInstance();
. . .
class NetworkClassLoader extends ClassLoader {
String host;
int port;
public Class findClass(String name) {
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
private byte[] loadClassData(String name) {
// load the class data from the connection
. . .
}
}
NetworkClassLoader的loadClassData方法需要从网络下载类的字节数据。方法defineClass将字节数组转换为Class类实例。
二进制类名需要符合规范:
"java.lang.String"
"javax.swing.JSpinner$DefaultEditor"
"java.security.KeyStore$Builder$FileBuilder$1"
"java.net.URLClassLoader$3$1"
2.loadClass
/**
* Loads the class with the specified <a href="#name">binary name</a>.
* This method searches for classes in the same manner as the {@link
* #loadClass(String, boolean)} method. It is invoked by the Java virtual
* machine to resolve class references. Invoking this method is equivalent
* to invoking {@link #loadClass(String, boolean) <tt>loadClass(name,
* false)</tt>}.
*
* @param name
* The <a href="#name">binary name</a> of the class
*
* @return The resulting <tt>Class</tt> object
*
* @throws ClassNotFoundException
* If the class was not found
*/
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
/**
* Loads the class with the specified <a href="#name">binary name</a>. The
* default implementation of this method searches for classes in the
* following order:
*
* <ol>
*
* <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
* has already been loaded. </p></li>
*
* <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
* on the parent class loader. If the parent is <tt>null</tt> the class
* loader built-in to the virtual machine is used, instead. </p></li>
*
* <li><p> Invoke the {@link #findClass(String)} method to find the
* class. </p></li>
*
* </ol>
*
* <p> If the class was found using the above steps, and the
* <tt>resolve</tt> flag is true, this method will then invoke the {@link
* #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
*
* <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
* #findClass(String)}, rather than this method. </p>
*
* <p> Unless overridden, this method synchronizes on the result of
* {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
* during the entire class loading process.
*
* @param name
* The <a href="#name">binary name</a> of the class
*
* @param resolve
* If <tt>true</tt> then resolve the class
*
* @return The resulting <tt>Class</tt> object
*
* @throws ClassNotFoundException
* If the class could not be found
*/
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;
}
}
加载指定二进制名binary name的类,方法的默认实现按照如下的顺序搜索类:
- step1.调用findLoadedClass(String)检查该类是否已经加载
- step2.调用父加载器的loadClass(String),如果父亲是null,则调用JVM内置的加载器
- step3.调用findClass(String)查找类
如果通过上面的步骤找到了类,并且resolve标识是true,则调用resolveClass(Class)。
ClassLoader子类建议重写findClass而不是该方法(loadClass)。
除非重写,该方法在整个类加载过程中会进行同步synchronized (getClassLoadingLock(name))。
下图来源【JVM】深度分析Java的ClassLoader机制(源码级别)
3.findLoadedClass
/**
* Returns the class with the given <a href="#name">binary name</a> if this
* loader has been recorded by the Java virtual machine as an initiating
* loader of a class with that <a href="#name">binary name</a>. Otherwise
* <tt>null</tt> is returned.
*
* @param name
* The <a href="#name">binary name</a> of the class
*
* @return The <tt>Class</tt> object, or <tt>null</tt> if the class has
* not been loaded
*
* @since 1.1
*/
protected final Class<?> findLoadedClass(String name) {
if (!checkName(name))
return null;
return findLoadedClass0(name);
}
private native final Class<?> findLoadedClass0(String name);
如果该加载器被JVM标记为该类的初始加载器,则返回该类。否则返回null。
4.resolveClass
/**
* Links the specified class. This (misleadingly named) method may be
* used by a class loader to link a class. If the class <tt>c</tt> has
* already been linked, then this method simply returns. Otherwise, the
* class is linked as described in the "Execution" chapter of
* <cite>The Java™ Language Specification</cite>.
*
* @param c
* The class to link
*
* @throws NullPointerException
* If <tt>c</tt> is <tt>null</tt>.
*
* @see #defineClass(String, byte[], int, int)
*/
protected final void resolveClass(Class<?> c) {
resolveClass0(c);
}
private native void resolveClass0(Class<?> c);
链接指定类。如果该类已经被链接过,则直接返回,否则按照Java语言规范中"Chapter12. Execution"章描述的那样进行链接。
类被加载后,需要在初始化前被链接。链接涉及校验、准备和(可选的)解析。
- 校验会检查加载的类是否是良构的,是否具有正确的符号表。校验还会检查代码是否遵循Java编程语言和JVM的语义要求。
- 准备工作涉及静态存储的内存分配,以及所有在JVM需要使用的数据结构的内存分配,例如方法表
- 解析:检查对其他类和接口的符号引用的过程,通过加载相关类和接口,来检查这些引用是正确。
5.getClassLoadingLock(name)
/**
* Returns the lock object for class loading operations.
* For backward compatibility, the default implementation of this method
* behaves as follows. If this ClassLoader object is registered as
* parallel capable, the method returns a dedicated object associated
* with the specified class name. Otherwise, the method returns this
* ClassLoader object.
*
* @param className
* The name of the to-be-loaded class
*
* @return the lock for class loading operations
*
* @throws NullPointerException
* If registered as parallel capable and <tt>className</tt> is null
*
* @see #loadClass(String, boolean)
*
* @since 1.7
*/
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;
}
返回类加载操作的锁对象。
为了向后兼容,此方法默认实现如下:
- 如果此ClassLoader对象注册了并行功能,则此方法会返回与指定类名关联的专用对象
- 否则返回此ClassLoader对象。
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
domains =
Collections.synchronizedSet(new HashSet<ProtectionDomain>());
assertionLock = new Object();
} else {
// no finer-grained lock; lock on the classloader instance
parallelLockMap = null;
package2certs = new Hashtable<>();
domains = new HashSet<>();
assertionLock = this;
}
}
示例:
public class TestClassLoader {
public static final MyClassLoader WZ_LOADER = new MyClassLoader(null,"E:\\IdeaProjects\\javabasics\\target\\classes\\","wz");
public static void main(String[] args) throws Exception {
new Thread() {
@Override
public void run() {
Class<?> c2 = null;
try {
c2 = WZ_LOADER.loadClass("com.enjoy.learn.core.jvm.Test");
c2.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}.start();
Class<?> c = WZ_LOADER.loadClass("com.enjoy.learn.core.jvm.Test");
c.newInstance();
}
}
主线程和thread线程在WZ_LOADER.loadClass处会竞争锁。
修改MyClassLoader,加入:
static {
ClassLoader.registerAsParallelCapable();
}
并发时,会不断以(类名,new Object())更新parallelLockMap,如果指定的类名项已存在,则返回之前的Object。对于加载同一个类,还是会阻塞,但是对于该类加载器加载其他不同类,就不会阻塞。因此,并发性提高了。
6.示例
6.1 同一类加载器不同实例加载同一个类
MyClassLoader loader = new MyClassLoader(null, "E:\\IdeaProjects\\javabasics\\target\\classes\\", "wz");
Class<?> c = loader.loadClass("com.enjoy.learn.core.jvm.Test");
c.newInstance();
MyClassLoader loader2 = new MyClassLoader(null, "E:\\IdeaProjects\\javabasics\\target\\classes\\", "wz");
Class<?> c2 = loader2.loadClass("com.enjoy.learn.core.jvm.Test");
c2.newInstance();
System.out.println(c == c2);
System.out.println(c.equals(c2));
结果:
false
false
Class的equals调用的是Object,是用==判断两个对象是否相等。
这种隔离特性可以用来解决钻石依赖问题:
maven 会从多个冲突的版本中选择一个来使用,如果不同的版本之间兼容性很糟糕,那么程序将无法正常编译运行。Maven 这种形式叫「扁平化」依赖管理。依赖于虚拟机的默认懒惰加载策略,运行过程中如果没有显示使用定制的 ClassLoader,那么从头到尾都是在使用 AppClassLoader,而不同版本的同名类必须使用不同的 ClassLoader 加载,所以 Maven 不能完美解决钻石依赖。
ClassLoader 固然可以解决依赖冲突问题,不过它也限制了不同软件包的操作界面必须使用反射或接口的方式进行动态调用。
SOFAArk官网
可以参考SOFAArk Project,SOFAArk 是一款基于 Java 实现的轻量级类隔离容器,由蚂蚁金服公司开源贡献;主要提供类隔离和应用(模块)动态部署能力;基于 Fat Jar 技术,可以将多个应用(模块)打包成一个自包含可运行的 Fat Jar,应用既可以是简单的单模块 Java 应用也可以是 Spring Boot/SOFABoot 应用。
同一个类加载器加载的类是相等的:
MyClassLoader loader = new MyClassLoader(null, "E:\\IdeaProjects\\javabasics\\target\\classes\\", "wz");
Class<?> c = loader.loadClass("com.enjoy.learn.core.jvm.Test");
c.newInstance();
Class<?> c2 = loader.loadClass("com.enjoy.learn.core.jvm.Test");
c2.newInstance();
System.out.println(c == c2);
true
6.2 自定义类加载器
public class MyClassLoader extends ClassLoader {
private String path;
private String name;
public MyClassLoader(ClassLoader parent, String path, String name) {
super(parent);
this.path = path;
this.name = name;
}
public MyClassLoader(String path, String name) {
super();
this.path = path;
this.name = name;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = readClassFile2ByteArray(name);
return this.defineClass(name, data,0, data.length);
}
private byte[] readClassFile2ByteArray(String name) {
InputStream is = null;
byte[] returnData = null;
name = name.replaceAll("\\.", Matcher.quoteReplacement(File.separator));
String filePath = this.path + name + ".class";
File file = new File(filePath);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
is = new FileInputStream(file);
int tmp = 0;
while ((tmp = is.read()) != -1){
byteArrayOutputStream.write(tmp);
}
returnData = byteArrayOutputStream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(is != null) {
is.close();
}
if(byteArrayOutputStream != null) {
byteArrayOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return returnData;
}
@Override
public String toString() {
return "MyClassLoader{" +
"name='" + name + '\'' +
'}';
}
}
public class Test {
public Test() {
System.out.println("A ClassLoader:" + this.getClass().getClassLoader()
+ " from user");
}
}
public class TestClassLoader {
public static void main(String[] args) throws Exception {
MyClassLoader loader = new MyClassLoader(null, "E:\\IdeaProjects\\javabasics\\target\\classes\\", "wz");
Class<?> c = loader.loadClass("com.enjoy.learn.core.jvm.Test");
c.newInstance();
结果:
A ClassLoader:MyClassLoader{name='wz'} from user
两个注意事项:
- 类名binary name应该是com.enjoy.learn.core.jvm.Test,而不是Test
- name.replaceAll("\.", Matcher.quoteReplacement(File.separator))而不是name.replaceAll("\.", File.separator)
6.3 静态变量初始化在哪里调用?
修改Test,添加静态变量:
public class Test {
public static int a = 1;
static {
System.out.println(a);
}
public Test() {
System.out.println("A ClassLoader:" + this.getClass().getClassLoader()
+ " from user");
}
}
可知,<clinit>是在c.newInstance()中调用的,对静态变量进行了初始化。