Original address
The loading, linking, and initialization of Java classes. The Java byte code behaves as a byte array (byte[]), while the Java class behaves in the JVM as an object of the Java.lang.Class class. A Java class can be loaded, linked, and initialized in three steps from the byte code to the ability to be used in the JVM. In these three steps, the Java class is directly visible to the developer, and by using the Java class Loader (class loader) you can dynamically load a Java class at runtime, while linking and initializing is what happens before the Java class is used. This article describes the process of loading, linking, and initializing Java classes in detail.
Loading of Java Classes
The loading of Java classes is done by the ClassLoader. In general, the ClassLoader is divided into two classes: the Startup class loader (bootstrap) and the user-defined ClassLoader (user-defined). The difference between the two is that the Startup class loader is implemented by the JVM's native code, and the user-defined ClassLoader inherits from the Java.lang.ClassLoader class in Java. In the part of the user-defined class loader, the general JVM provides some basic implementations. Application developers can also write their own classloader as needed. The most common use of the JVM is the System class loader, which is used to initiate the loading of Java applications. This ClassLoader object can be obtained by Java.lang.ClassLoader's Getsystemclassloader () method.
The final function that the ClassLoader needs to accomplish is to define a Java class that translates Java byte code into an object of the Java.lang.Class class in the JVM. But the process of class loading is not so simple. The Java ClassLoader has two more important features: hierarchical organizational structure and proxy mode. Hierarchical organizational structure refers to each classloader has a parent class loader, which can be obtained through the GetParent () method. The ClassLoader is organized in such a way that fathers-descendants form a tree-like hierarchy. The proxy mode refers to a class loader that can either do its own work on the definition of a Java class or delegate to other ClassLoader. Because of the presence of proxy mode, the class loader that initiates the loading of a class and the class loader that ultimately defines the class may not be one. The former is called the initial ClassLoader, and the latter is called the class loader definition. The correlation between the two is that a Java class's definition class loader is the initial classloader for other Java classes that the class imports. For example, if Class A imports Class B through import, the Class A-defined ClassLoader is responsible for initiating the load process of Class B.
The generic ClassLoader will first delegate to its parent class loader before attempting to load a Java class on its own. The parent class loader will not attempt to load itself until it is found. This logic is encapsulated in the LoadClass () method of the Java.lang.ClassLoader class. In general, the parent priority strategy is good enough. In some cases, it may be necessary to take the opposite strategy, which is to try to load it yourself and then delegate to the parent class loader when it is not found. This approach is common in Java Web containers and is recommended by the servlet specification. Apache Tomcat, for example, provides a standalone classloader for every Web application, using its own priority-loading strategy. IBM WebSphere Application Server allows Web apps to choose the policy that the ClassLoader uses.
An important use of the ClassLoader is to create an isolation space for Java classes of the same name in the JVM. In the JVM, it is judged whether the two classes are the same, not only based on the binary name of the class, but also the class loader defined by the two classes. The two classes are identical only if they are exactly the same. Therefore, even if the same Java byte code is defined by two different classloader, the resulting Java classes are different. If an attempt is made to assign a value to an object of two classes, Java.lang.ClassCastException is thrown. This feature creates conditions for a Java class with the same name to exist in the JVM. In real-world applications, different versions of Java classes with the same name may be required to exist concurrently in the JVM. This needs to be met through the ClassLoader. This technique has been widely used in OSGi.
Links to Java classes
A link to a Java class refers to the process of merging the Java class's binary code into the running state of the JVM. Before linking, this class must be loaded successfully. The links to classes include validation, preparation, and parsing, among other steps. Validation is used to ensure that the binary representation of the Java class is structurally correct. If there is an error in the validation process, a Java.lang.VerifyError error is thrown. The preparation process is to create a static domain in the Java class and set the values of those fields to the default values. The preparation process does not execute code. In a Java class, it contains a formal reference to another class or interface, including its parent class, the implemented interface, the form parameters of the method, and the Java class that returns the value. The process of parsing is to make sure that the referenced classes are correctly found. The parsing process may cause other Java classes to be loaded.
Different JVM implementations may choose different parsing strategies. One approach is to parse all dependent form references recursively at the time of the link. Another approach might be to parse only when a form reference is really needed. That is, if a Java class is simply referenced, but is not actually used, then this class might not be parsed. Consider the following code:
public class Linktest {public static void Main (string[] args) { tobelinked tobelinked = null; System.out.println ("Test link.");} }
Class Linktest refers to class tobelinked, but does not actually use it, but declares a variable, does not create an instance of the class, or accesses a static domain in it. In JDK 6 of Oracle, if you delete the compiled tobelinked Java byte code and then run Linktest, the program does not throw an error. This is because the tobelinked class is not actually used, and the link strategy used by Oracle's JDK 6 allows the tobelinked class to not be loaded, so it will not be discovered that tobelinked's Java byte code actually does not exist. If you change the code to tobelinked tobelinked = new tobelinked () and then run the same way, the exception will be thrown. Because this tobelinked class is actually used, this class will need to be loaded.
Initialization of the Java class
When a Java class is first actually used, the JVM performs the initialization of that class. The primary operation of the initialization process is to execute static blocks of code and initialize the static domain. Before a class is initialized, its immediate parent class also needs to be initialized. However, the initialization of an interface does not cause the initialization of its parent interface. At initialization time, static blocks of code and initialization of the static domain are executed sequentially from top to bottom in the source code. Consider the following code:
public class Statictest {public static int X = ten; public static void Main (string[] args) { System.out.println (Y);//Output } static { X = +; } public static int Y = X * 2;}
In the code above, the initialization of the static domain and execution of the static code block are executed from top to bottom at initialization time. So the value of the variable x is first initialized to 10 and then to 30, while the value of the variable y is initialized to 60.
Initialization of Java classes and interfaces occurs only at a specific time, including:
The Java Reflection API may also cause initialization of classes and interfaces. It is important to note that when accessing a static domain in a Java class or interface, only the class or interface that actually declares the domain is initialized. Consider the following code:
Class B { static int value = +; static { System.out.println ("Class B is initialized.");//output }}class A extends B { static { System.out.println ("Class A is initialized."); does not output }}public class Inittest {public static void Main (string[] args) { System.out.println (a.value); Output of + }}
In the preceding code, the class Inittest references the static domain value declared in class B through A.value. Because value is declared in Class B, only Class B is initialized, and Class A is not initialized.
To create your own class loader
During the Java application development process, you may need to create a classloader that applies to your application. Typical scenarios include implementing specific Java byte code lookups, encrypting/decrypting byte code, and implementing isolation of Java classes of the same name. Creating your own ClassLoader is not a complex task, just inherit from the Java.lang.ClassLoader class and overwrite the corresponding method. There are a number of methods available in Java.lang.ClassLoader, and here are a few to consider when creating the ClassLoader:
- DefineClass (): This method is used to complete the conversion from the byte array of the Java byte code to the Java.lang.Class. This method is not writable and is usually implemented using native code.
- Findloadedclass (): This method is used to find Java classes that have been loaded by name. A class loader does not repeatedly load classes of the same name.
- Findclass (): This method is used to find and load Java classes by name.
- LoadClass (): This method is used to load Java classes by name.
- Resolveclass (): This method is used to link a Java class.
It is easy to confuse the Findclass () method and the LoadClass () method. As mentioned earlier, the Java class will need to be parsed during the link process, and parsing may cause other Java classes referenced by the current Java class to be loaded. At this point, the JVM loads other classes by invoking the LoadClass () method of the current class's definition ClassLoader. The Findclass () method is the extension point of the class loader that the app creates. Applying your own classloader should overwrite the Findclass () method to add custom class load logic. The default implementation of the LoadClass () method is responsible for invoking the Findclass () method.
As mentioned earlier, the class loader's proxy mode defaults to the parent class-first policy. The implementation of this strategy is encapsulated in the LoadClass () method. If you want to modify this policy, you need to overwrite the LoadClass () method.
The following code gives a common implementation pattern for custom class loading:
public class Myclassloader extends ClassLoader { protected class<?> findclass (String name) throws classnotfoundexception { byte[] b = null;//Find or generate a Java class byte code return DefineClass (name, b, 0, b.length);} }
Loading link initialization for Java classes