Java
As one of the core features of Java, class loading has many features. Understanding and mastering the way and mechanism of class loading can better understand the execution process of the program. This article will introduce Java's class loading mechanism and its related necessary knowledge in detail.
Let's get right to it
1. File Compilation
Before introducing the class loader, first introduce Java
the basic compilation and execution commands of the file for subsequent use.
1. Class compilation
-
file compilation
Compile the file into a file through
bashjavac
the command , and output to the current directory by default..java
.class
copy code
javac MyBean.java
-
encoding process
When
bashJava
the file contains encodings such as Chinese, you need to specify the encoding format when compiling, otherwise it will compile abnormally.copy code
javac -encoding utf-8 MyBean.java
-
Package name processing
The file compiled by the default
bashjavac
command does not contain the package path, and-d
the file generated by parameter compilation will create a package name hierarchy folder in the same level directory. It.
means to save to the current directory, and the directory can be specified as required.copy code
javac -d . MyBean.java
-
specified version
When there are multiple
bashJDK
versions in the programming environment, you can specify the compiled version by entering the full pathJDK
.copy code
"C:\Program Files\Java\jdk1.8.0_202\bin\javac" -d <target_path> MyBean.java
2. File Execution
-
Class operation
bashClass
It is relatively simple to run the file, just passjava
+ directly文件名
. It should be noted that if the package name is set when compiling the file, the package name also needs to be specified when running.copy code
# 运行不含包名 class 文件 java MyBean # 运行含包名 class 文件 java xyz.ibudai.MyBean
-
jar run
Running
bashJar
a file is similar to runningClass
a file, just add-jar
parameters.copy code
java -jar jar-name.jar
Second, the class loader
1. Basic categories
Java
The class loaders in China are divided into the following four categories, and the descriptions of each type of loader are as follows:
Loader | describe |
---|---|
BootstrapClassLoader | The top-level loader is mainly responsible for loading core class libraries (java.lang.*, etc.). |
ExtClassLoader | It is mainly responsible for loading some extended JAR packages under the jre/lib/ext directory. |
AppClassLoader | It is mainly responsible for loading the main function main of the application program, etc. |
CustomClassLoader | The custom class loader, that is, we inherit ClassLoad and the custom loader belongs to the bottom loader. |
2. Loading method
There are two ways Java
to load classes in , which are introduced below.Class.forName()
Classloader.laodClass()
-
Class.forName()
Loading by method will not only load the current class into the memory but also initialize the object. You can add a static block print prompt
Class.forName()
to the class to be loaded. When using the loaded class, you can find that the static block code will be executed, indicating that it is created at this time. object.static
Class.forName()
If you want to implement class loading without creating objects, you can use
javaClass.forName(className, false, classLoader)
method loading, where the second parameter is used to specify whether to create objects, and the third parameter specifies the class loader.copy code
public void initDemo2() { String className = "xyz.ibudai.bean.User"; ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); try { Class<?> clazz1 = Class.forName(className); Class<?> clazz2 = Class.forName(className, false, classLoader); System.out.println("clazz1 loader: " + clazz1.getClassLoader()); System.out.println("clazz2 loader: " + clazz2.getClassLoader()); } catch (ClassNotFoundException e) { e.printStackTrace(); } }
-
Classloader.laodClass()
Loading by
javalaodClass()
means does not resolve the class, only implements loading and does not create the corresponding object, and is only initialized when the object is referenced.copy code
public void initDemo2() { String className = "xyz.ibudai.bean.User"; ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); try { Class<?> clazz3 = classLoader.loadClass(className); System.out.println("clazz3 loader: " + clazz3.getClassLoader()); } catch (Exception e) { e.printStackTrace(); } }
It should be noted java.lang.String
that the core class library is JVM
loaded at startup, they are located Java
in the core class library of the virtual machine, and are Bootstrap ClassLoader
loaded by . Therefore, when obtaining the class loaders of these classes, the result is usually null
, it should be noted that null
the class loader is unknown or cannot be determined, rather than that there is no class loader.
3. Class loading mechanism
1. Loading mechanism
The class loader is responsible for loading the bytecode of classes from different sources (such as file system, network, memory, etc.), such as the most common .class
files, and converting them into Java
objects so that programs can use these classes.
The class loader is also responsible for resolving class dependencies, that is, finding and loading other classes that the current class depends on, and determining which class loader should load each class.
Java
The class loading mechanism has the following characteristics:
- Lazy loading : A class is only loaded when it is needed to save memory and loading time.
- Parental delegation : The class loader will load classes according to the hierarchy, that is, first entrust the parent class loader to load, and if the parent class loader cannot load, then hand it over to itself to load.
- Caching mechanism : Classes that have already been loaded will be cached to avoid loading the same class repeatedly.
- Destroy the parental delegation mechanism : Allow users to customize the class loader to load classes, so that some custom class loading strategies can be implemented, such as hot deployment, plug-in, etc.
2. Loading process
The so-called class loading process, in simple terms, the compiled class
file is loaded into the virtual machine memory in a specific way JVM
, and then it should be able to be read in the virtual machine memory.
The class loader mainly goes through three stages to load bytecode files into memory 加载 -> 连接 -> 实例化
.
Note that it is .class
not loaded into memory all at once, but Java
only when the application needs it. That is to say, when JVM
a class is requested to be loaded, the class loader will try to find and locate the class, and after finding the corresponding class, load its fully qualified class definition into the runtime data area.
4. Appointment by parents
The parent delegation mechanism is a very important type of loading mechanism in class loading, through which the safety and integrity of the class are guaranteed.
JDK
The parental delegation mechanism is used by default. You can find java.lang
the ClassLoader
class under the package. The following is an analysis of the parental delegation mechanism from the perspective of source code.
1. Method description
Go through the table below before parsing the core source code. Each method will be involved later. Here is a brief description of its function and will not be described in detail later.
method | effect |
---|---|
getClassLoadingLock() | Prevent classes from being loaded concurrently. |
findLoadedClass() | Determine whether the class has been loaded, and return the loader if it has been loaded, otherwise return null. |
findBootstrapClassOrNull() | Delegate the parent loader to load, if no parent loader exists, return null. |
findClass() | Read the .class file based on the full class name and call defineClass() to find the corresponding java.lang.Class object. |
defineClass() | Convert byte stream to java.lang.Class object, byte stream can come from .class file and other ways. |
resolveClass() | Bytecode files are loaded into the JVM during class loading and are not immediately converted into java.lang.Class objects. Only when using this class will resolveClass() be called for conversion, and operations such as creating an instance can be performed. |
2. Interpretation of source code
The core of the class loading implementation is loadClass()
the method, which uses synchronized
keywords to ensure that the class will not be loaded repeatedly. The source code is as follows JDK
, loadClass()
and I have provided comments at the key points.
Among them parent
is the parent loader of the current loader.
java
copy code
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 1. 判断类是否已经加载过 Class<?> c = findLoadedClass(name); // 1.1 若不为空表明已加载则返回 if (c == null) { try { // 2. 未加载 -> 判断是否存在父加载器 if (parent != null) { // 2.1 存在 -> 递归调用直至获取顶层父加载器 c = parent.loadClass(name); } else { // 2.2 不存在(已达顶层加载器) -> 委托父类进行加载 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found } // 3. 没有父加载器可以加载类 -> 由类自身实现加载 if (c == null) { c = findClass(name); } } return c; } }
5. Custom loading
There are two most common types of custom class loaders, following the parental delegation mechanism and breaking the parental delegation mechanism. The latter is mainly discussed here.
1. Parent class inheritance
Create a new custom loader class CustomClassLoader
and inherit it , and define basic information such as ClassLoader
the construction method and the default file storage path in the class . The and class
in the constructor are used to specify the parent loader of the current loader. The former defaults to obtain the class loader of the current context as the parent loader, in most cases .super()
super(parent)
AppClassLoader
In practical applications, usually only specific classes need to implement custom loading, so the collection is defined here to targetList
store the target classes that need to implement custom loading, and the default loading method is used for classes that are not in this collection.
java
copy code
public class CustomClassLoader extends ClassLoader { private final Path classDir; private static final Path DEFAULT_CLASS_DIR = Paths.get("E:\\Workspace\\Class"); private List<String> targetList = new ArrayList<>(); static { targetList.add("xyz.ibudai.bean.TestA"); targetList.add("xyz.ibudai.bean.TestB"); } /** * 使用默认的信息 */ public CustomClassLoader() { super(); this.classDir = DEFAULT_CLASS_DIR; } /** * 指定文件目录与父类加载器 */ public CustomClassLoader(String path, ClassLoader parent) { super(parent); this.classDir = Paths.get(path); } }
2. loadClass()
The next step is also the focus of custom class loading. JDK
The core process of loading a class is loadClass()
controlled by a method. If you want to break the default parent delegation mechanism, you must rewrite this method.
The rewritten loadClass()
implementation process of its concrete loading is as follows:
-
Check if the current class has already been loaded?
- If loaded, return the result.
- If not loaded, go to the next step.
-
Is the loaded class in the target collection?
- If so, implement custom
.class
file reading and loading. - If not, call
loadClass()
the loading retry of the parent loader, that is, still follow the parental delegation mechanism.
- If so, implement custom
-
Determine whether the custom loading is successful?
- If so, return the result.
- If not, call
loadClass()
the loading retry of the parent loader, that is, still follow the parental delegation mechanism.
The code implementation corresponding to the above logic flow is as follows:
java
copy code
@Override public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class<?> c = findLoadedClass(name); if (c != null) { // 若已加载则返回 return c; } if(targetList.contains(name)) { try { // 若在目标集合则自定义加载 c = customFindClass(name); } catch (ClassNotFoundException ignored) { // 加载异常重新委派父类加载 c = super.loadClass(name, resolve); } } if (c == null) { // 加载不成功重新委派父类加载 c = super.loadClass(name, resolve); } return c; } }
3. findClass()
loadClass()
It is used to control the loading process of the class in the loading of the class , and findClass()
it is used to control the specific .class
file loading implementation, that is, how to .class
load it into the file JVM
.
Here I chose to create a new customFindClass()
method instead of rewriting. The purpose is to prevent confusion and conflicts when calling the parent class when findClass()
the custom class fails to load .loadClass()
findClass()
customFindClass()
Loading the compiled class file is realized in , JVM
that is, the compiled .class
file is read from the local and converted into byte data and defineClass()
the corresponding java.lang.Class
object is searched.
where name
is the full class name of the class file, for example: xyz.ibudai.bean.TestA
.
java
copy code
private Class<?> customFindClass(String name) throws ClassNotFoundException { // 读取 class 的二进制数据 byte[] classBytes = this.readClassBytes(name); if (classBytes == null || classBytes.length == 0) { throw new ClassNotFoundException("The class byte " + name + " is empty."); } // 调用 defineClass 方法定义 class return this.defineClass(name, classBytes, 0, classBytes.length); } /** * 将 class 文件转为字节数组以供后续内存载入 */ private byte[] readClassBytes(String name) throws ClassNotFoundException { // 将包名分符转换为文件路径分隔符 String classPath = name.replace(".", "/"); Path classFullPath = classDir.resolve(Paths.get(classPath + ".class")); if (!classFullPath.toFile().exists()) { throw new ClassNotFoundException("Class file " + classFullPath + " doesn't exists."); } try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { Files.copy(classFullPath, out); return out.toByteArray(); } catch (IOException e) { throw new ClassNotFoundException("Read class " + classFullPath + " file to byte error."); } }
4. Test example
After completing the above example, verify the effect of our class loading through an example.
Create a new test class TestA
and java -d . TestA.java
compile it into .class
a file through the command, and move the compiled .class
file with the package name hierarchy to CustomClassLoader
the default directory defined in the class.
java
copy code
public class TestA { public void sayHello() { System.out.println("Hello world!"); } }
CustomClassLoader
After completing the above operations, write the corresponding test example, realize TestA
the loading of the class through the default class loading and two methods, and output TestA
the specific loading class information respectively.
The corresponding test sample code is as follows:
java
copy code
@Test public void loadTest() throws Exception { // 默认类加载器加载类 Class<?> clazz1 = Class.forName("xyz.ibudai.bean.TestA"); System.out.println("Loader-1: " + clazz1.getClassLoader()); // 自定义类加载器加载类 CustomClassLoader myLoader = new CustomClassLoader(); Thread.currentThread().setContextClassLoader(myLoader); Class<?> clazz2 = myLoader.loadClass("xyz.ibudai.bean.TestA"); System.out.println("Loader-2: " + clazz2.getClassLoader()); }
The result of running the above program is as follows. From the result, we can see that CustomClassLoader
we have successfully implemented TestA
custom loading.
java
copy code
Loader-1: sun.misc.Launcher$AppClassLoader@18b4aac2 Loader-2: xyz.ibudai.loader.CustomClassLoader@1b9e1916
6. Advanced operation
1. Context Loader
In the previous example, Thread.currentThread().getContextClassLoader()
the class loading of the current context was passed, so what is the use of context class loading?
When Java
the program starts, the core class will Bootstrap Classloader
be loaded by the client, but there are some standard interfaces that are only defined by the service provider interface (SPI)
. For example, the most common JDBC
specific implementations are implemented by the major database manufacturers themselves. And this part of the interface implementation class obviously cannot be loaded by the system class loader (the system class loader will only load the core package, not the third-party source package), and the default class loading mechanism ( ) does not support the 双亲委派机制
parent loader to delegate downward , the context loader is born for this purpose, and SPI
breaks the parental delegation mechanism by means of child delegation (the implementation class is loaded by the current thread context loader).
Java
The context class loader of the initial thread when the application is running is the system class loader ( AppClassLoader
), and the context class loader of the thread can be obtained and set through Thread.currentThread().getContextClassLoader()
the Thread.currentThread().setContextClassLoader()
method. If a thread is not specified, it will inherit its parent thread context class loader by default. .
2. Dynamic loading
Java
Relevant classes will be loaded at startup, and URLClassLoader
dynamic class loading during program running can be realized by passing through.
URLClassLoader
Inherited from ClassLoader
, it can be loaded by URL
loading classes from resource files, such as by URLClassLoader
loading JAR
the driver package in the following example.
java
copy code
public static ClassLoader getClassLoader(String driverPath) { // 获取当前线程上下文加载器 ClassLoader parent = Thread.currentThread().getContextClassLoader(); URL[] urls; try { File driver = new File(driverPath); // 驱动包不存在抛出异常 if (!driver.exists()) { throw new FileNotFoundException(); } // File 转 URL 资源格式 list.add(driver.toURI().toURL()); urls = list.toArray(new URL[0]); } catch (Exception e) { throw new RuntimeException(e); } return new URLClassLoader(urls, parent); }
3. Class unloading
Although URLClassLoader
the dynamic loading of classes can be realized, Java
there is no explicit way to realize the unloading of classes. Unloading a loaded class can only be achieved through garbage collection.
If a class wants to be unloaded by garbage collection, it needs to meet the following three conditions:
- There are no instances of the current class, that is, all object instances have been destroyed.
- Reference (
Reference
) does not exist for the current class.- The class load for the current class has been garbage collected (
GC
).
Seven, class isolation
In the above, we introduced the loading method of classes and focused on the analysis of the parent delegation mechanism. Next, we will introduce the application scenarios of class loading.
1. Basic introduction
Similar to the thread context loader mentioned before (the child thread inherits the parent thread loader by default), the referenced class of a class will also be loaded by the class loader of the application class, that is, if it is referenced in , it will also ClassA
be ClassB
loaded ClassB
by ClassA
the Class loading is used to load, which is one of the realization principles of class isolation.
So what is the role of class isolation? As mentioned above, class loading is transitive by reference, and a class in a module will only be loaded once. When the class is loaded, it is judged findLoadedClass()
whether to load it, and if so, the loading is skipped, that is, JVM
each class in the virtual machine has and only There is one.
2. Example introduction
According to the above introduction, this method will cause a lot of problems when the program needs to rely on multiple versions of the same class.
module-a
For example, there are two modules and in the project module-b
, they depend on two versions module-c
of 1.0
and respectively 2.0
, but because they are loaded by the same project, only one version will be loaded in the end module-a
, module-b
not the two versions.AppClassLoader
JVM
module-c
According to Maven
the first definition in the first import order, if module-a
the definition is first, the version will be loaded finally module-c
. 1.0
At this time, if the new feature module-b
is referenced , 2.0
it will be thrown at runtime ClassNotFoundException
.
Java
The same class loaded by different class loaders JVM
is two different classes, so for the above situation, we only need to customize the class loading and load module-a
them module-b
through two different classes (here different class loading and It does not refer to different class loading categories, just need to be loaded by two different instance objects). As mentioned above, we have customized the implementation of class loading . We load the class loading reference inheritance principle through CustomClassLoader
its two instances loader1
and loader2
respectively .module-a
module-b
module-c
It should be noted that here CustomClassLoader1
and CustomClassLoader2
the corresponding custom class loader must rewrite loadClass()
the method and destroy its parental delegation mechanism, otherwise it will still be proxied layer by layer, and the loader that finally loads the class will be the system class loader.