Home >Java >javaTutorial >Java class loading mechanism
For a long time, I was very resistant to Java's class loading mechanism because I thought it was too difficult to understand. But in order to become a good Java engineer, I decided to bite the bullet and study it.
01. Bytecode
Before talking about the Java class loading mechanism, you need to understand the Java bytecode first, because It is closely related to the class loading mechanism.
Computers only recognize 0 and 1, so programs written in any language need to be compiled into machine code to be understood and then executed by the computer, and Java is no exception.
When Java was born, it shouted a very awesome slogan: "Write Once, Run Anywhere". In order to achieve this goal, Sun has released many products that can run on different platforms (Windows, Linux) The Java Virtual Machine (JVM) - responsible for loading and executing Java compiled bytecode.
What does Java bytecode look like? Let’s take a look at it with the help of a simple code.
The source code is as follows:
package com.cmower.java_demo; public class Test { public static void main(String[] args) { System.out.println("版权声明"); } }
After the code is compiled, check the bytecode file through the xxd Test.class command.
xxd Test.class 00000000: cafe babe 0000 0034 0022 0700 0201 0019 .......4."...... 00000010: 636f 6d2f 636d 6f77 6572 2f6a 6176 615f com/cmower/java_ 00000020: 6465 6d6f 2f54 6573 7407 0004 0100 106a demo/Test......j 00000030: 6176 612f 6c61 6e67 2f4f 626a 6563 7401 ava/lang/Object. 00000040: 0006 3c69 6e69 743e 0100 0328 2956 0100 ..<init>...()V.. 00000050: 0443 6f64 650a 0003 0009 0c00 0500 0601 .Code........... 00000060: 000f 4c69 6e65 4e75 6d62 6572 5461 626c ..LineNumberTabl
Feeling a little confused, right?
That’s right.
The cafe babe in this bytecode is called the "magic number", which is a sign for the JVM to recognize .class files. The customizer of the file format is free to choose the magic number (as long as it is not used), for example, the magic number for .png files is 8950 4e47.
As for other content, you can choose to forget it.
02. Class loading process
After understanding Java bytecode, let’s talk about Java’s class loading process.
Java's class loading process can be divided into 5 stages: loading, verification, preparation, parsing and initialization. These 5 phases generally occur sequentially, but in the case of dynamic binding, the parsing phase occurs after the initialization phase.
1) Loading
The main purpose of JVM at this stage is to transfer bytecode from different data sources (which may be class files, jar packages, or even networks ) is converted into a binary byte stream and loaded into memory, and a java.lang.Class object representing the class is generated.
2) Verification
The JVM will verify the binary byte stream at this stage. Only those that comply with the JVM bytecode specifications can be correctly executed by the JVM. This stage is an important barrier to ensure the security of the JVM. Here are some major checks.
Make sure the binary byte stream format is as expected (for example, whether it starts with cafe bene).
Whether all methods comply with the restrictions of access control keywords.
Whether the number and type of parameters in the method call are correct.
Make sure variables are properly initialized before use.
Check whether the variable is assigned a value of the appropriate type.
3) Preparation
The JVM will allocate memory and initialize class variables (also called static variables, modified by the static keyword) at this stage (corresponding to the default initialization of the data type) value, such as 0, 0L, null, false, etc.).
In other words, if there is such a piece of code:
public String chenmo = "沉默"; public static String wanger = "王二"; public static final String cmower = "沉默王二";
chenmo will not be allocated memory, but wanger will; but the initial value of wanger is not "王二" but null.
It should be noted that variables modified by static final are called constants, which are different from class variables. Once a constant is assigned a value, it will not change, so the value of cpower in the preparation phase is "silent king two" instead of null.
4) Resolution
This stage converts symbol references in the constant pool into direct references.
what? Symbolic reference, direct reference?
Symbol reference uses a set of symbols (any form of literal, as long as it can unambiguously locate the target when used) to describe the referenced target.
At compile time, Java classes do not know the actual address of the referenced class, so they can only use symbolic references instead. For example, the com.Wanger class refers to the com.Chenmo class. When compiling, the Wanger class does not know the actual memory address of the Chenmo class, so it can only use the symbol com.Chenmo.
Direct reference parses the symbol reference to find the actual memory address of the reference.
5) Initialization
This phase is the last step of the class loading process. In the preparation phase, the class variables have been assigned default initial values, and in the initialization phase, the class variables will be assigned the values expected by the code. In other words, the initialization phase is the process of executing the class constructor method.
Oh, no, the above paragraph is very abstract and difficult to understand, right? Let me give you an example.
String cpower = new String("Silent Wang Er");
The above code uses the new keyword to instantiate a string object, then at this time, String will be called The constructor of the class instantiates cpower.
03. Class loader
After talking about the class loading process, we have to talk about the class loader.
一般来说,Java 程序员并不需要直接同类加载器进行交互。JVM 默认的行为就已经足够满足大多数情况的需求了。不过,如果遇到了需要和类加载器进行交互的情况,而对类加载器的机制又不是很了解的话,就不得不花大量的时间去调试
ClassNotFoundException 和 NoClassDefFoundError 等异常。
对于任意一个类,都需要由它的类加载器和这个类本身一同确定其在 JVM 中的唯一性。也就是说,如果两个类的加载器不同,即使两个类来源于同一个字节码文件,那这两个类就必定不相等(比如两个类的 Class 对象不 equals)。
站在程序员的角度来看,Java 类加载器可以分为三种。
1)启动类加载器(Bootstrap Class-Loader),加载 jre/lib 包下面的 jar 文件,比如说常见的 rt.jar。
2)扩展类加载器(Extension or Ext Class-Loader),加载 jre/lib/ext 包下面的 jar 文件。
3)应用类加载器(Application or App Clas-Loader),根据程序的类路径(classpath)来加载 Java 类。
来来来,通过一段简单的代码了解下。
public class Test { public static void main(String[] args) { ClassLoader loader = Test.class.getClassLoader(); while (loader != null) { System.out.println(loader.toString()); loader = loader.getParent(); } } }
每个 Java 类都维护着一个指向定义它的类加载器的引用,通过 类名.class.getClassLoader() 可以获取到此引用;然后通过 loader.getParent() 可以获取类加载器的上层类加载器。
这段代码的输出结果如下:
sun.misc.Launcher$AppClassLoader@73d16e93 sun.misc.Launcher$ExtClassLoader@15db9742
第一行输出为 Test 的类加载器,即应用类加载器,它是 sun.misc.Launcher$AppClassLoader 类的实例;第二行输出为扩展类加载器,是 sun.misc.Launcher$ExtClassLoader 类的实例。那启动类加载器呢?
按理说,扩展类加载器的上层类加载器是启动类加载器,但在我这个版本的 JDK 中, 扩展类加载器的 getParent() 返回 null。所以没有输出。
04、双亲委派模型
如果以上三种类加载器不能满足要求的话,程序员还可以自定义类加载器(继承 java.lang.ClassLoader 类),它们之间的层级关系如下图所示。
这种层次关系被称作为双亲委派模型:如果一个类加载器收到了加载类的请求,它会先把请求委托给上层加载器去完成,上层加载器又会委托上上层加载器,一直到最顶层的类加载器;如果上层加载器无法完成类的加载工作时,当前类加载器才会尝试自己去加载这个类。
PS:双亲委派模型突然让我联想到朱元璋同志,这个同志当上了皇帝之后连宰相都不要了,所有的事情都亲力亲为,只有自己没精力没时间做的事才交给大臣们去干。
使用双亲委派模型有一个很明显的好处,那就是 Java 类随着它的类加载器一起具备了一种带有优先级的层次关系,这对于保证 Java 程序的稳定运作很重要。
上文中曾提到,如果两个类的加载器不同,即使两个类来源于同一个字节码文件,那这两个类就必定不相等——双亲委派模型能够保证同一个类最终会被特定的类加载器加载。
The above is the detailed content of Java class loading mechanism. For more information, please follow other related articles on the PHP Chinese website!