Class loading is one of the most powerful mechanisms provided by the Java language. Although class loading is not a hot topic, all programmers should understand its working mechanism and understand how to make it meet our needs. This effectively saves our coding time and frees us from the work of continuously debugging classnotfoundexception and classcastexception.
This article starts from the basics, such as what is the difference between code and data, and how they form an instance or object. Then we will discuss in depth how the Java Virtual Machine (JVM) uses the class loader to read code and the main types of class loaders in Java. Next, we will use a basic algorithm for class loading to see how the Class Loader loads an internal class. The next section of this article demonstrates the necessity of extending and developing your own class loaders. Next I will explain how to use a custom class loader to complete a general task, so that it can load the code of any remote client, define in JVM, instantiate and execute it. This article includes the J2EE specification for class loading-in fact, this has become one of the J2EE standards.
Class and Data
A class indicates the code to be executed, and data indicates its related status. The status changes frequently, but the Code does not. When we compare a specific state with a class, it means to make a class case. Although instances of the same class have different statuses, they all correspond to the same code. In Java, a class usually has a. Class file, but there are exceptions. In the Java Runtime Environment (Java runtime), each class has a Java object with the first class as the code that appears. It is Java. lang. class instance. We compile a Java file, and the compiler will embed a public, static, final modified type of Java. Lang. Class, the domain variable named class in its bytecode file. Because the public modifier is used, we can access it in the following form:
java.lang.Class klass = Myclass.class;
Once a class is loaded into JVM, the same class will not be loaded again (Remember, the same class ). There is a problem here: what is "the same class "? Just as an object has a specific State, that is, identifier, an object is always associated with its code (class. Similarly, the classes loaded into JVM also have a specific Identifier. Let's take a look.
In Java, a class uses its fully-matched Class Name (fully qualified class name) as its identifier. Here, the fully-matched class name includes the package name and class name. However, in JVM, a class uses its full name and an instance that loads classloader as a unique identifier. Therefore, if a package named PG contains a class named Cl that is loaded by an instance kl1 of the Class Loader klassloader, that is, c1.class is expressed as (CL, PG, kl1) in JVM ). This means that the instances (CL, PG, kl1) and (CL, PG, kl2) of the two class loaders are different, so the classes loaded by them are completely different, incompatible. So how many types of loaders are there in the JVM? In the next section, we will reveal the answer.
Class Loader
In JVM, every class is. lang. some instances of classloader to load. class classloader is in the package Java. lang, developers can freely inherit it and add their own functions to load classes.
Whenever we type Java mymainclass to start running a new JVM, the bootstrap class loader is responsible for some key Java classes, such as Java. lang. objects and other runtime code are first loaded into the memory. The runtime class is in the JRE/lib/RT. jar package file. Because this is the underlying execution action of the system, we cannot find the working details of the bootstrap loader in the Java documentation. For the same reason, the behavior of the bootstrap loader varies significantly across JVM.
Similarly, if we use the following method:
log(java.lang.String.class.getClassLoader());
To obtain the loader of the core runtime class of Java, and then get null.
Next we will introduce the Java extension class loader. The extension Library provides more features than the Java running code. We can save the extension library in the path provided by the java. Ext. dirs attribute.
(Edit note: the java. Ext. dirs attribute refers to a key under the system attribute. All system attributes can be obtained through the system. getproperties () method. In the editor's system, the value of Java. Ext. dirs is "C:/program files/Java/jdk1.5.0 _ 04/JRE/lib/EXT ". As mentioned below, java. Class. PATH also belongs to a key of the system attribute .)
The extclassloader class is used to load. Jar files under all java. Ext. dirs. Developers can add their own. Jar files or library files to the classpath of the extension directory so that they can be read by the extension class loader.
From the developer's point of view, the third and most important type of loader is appclassloader. This type of loader is used to read all classes corresponding to the path of the Java. Class. Path System attribute.
In Sun's Java guide, the article "Understanding extension class loading" (understanding extension class loading) provides a more detailed explanation of the paths of the above three class loaders, this is the classloader of several other JDK versions.
● Java.net. urlclassloader
● Java. Security. secureclassloader
● Java. RMI. server. rmiclassloader
● Sun. Applet. appletclassloader
Java. Lang. Thread contains the public classloader getcontextclassloader () method, which returns the context class loader for a specific thread. These loaders are provided by the thread Creator for the code running in this thread to be used when classes or resources need to be loaded. If this loader is not created, it is the context class loader of its parent thread by default. The original Class Loader is generally created by the class loader that reads the application program.
How does the Class Loader Work?
In addition to the bootstrap loader, all classloaders have a parent loader. In addition, all classloaders are of the Java. Lang. classloader type. The above two types of loaders are different, and they are also important for the normal operation of the class loaders customized by developers. The most important aspect is to correctly set the parent class loader. For any class loader, its parent class loader is the class loader instance that loads the class loader. (Remember, the Class Loader itself is also a class !)
The loadclass () method can be used to obtain the class from the class loader. We can use the source code of Java. Lang. classloader to understand the details of this method, as follows:
protected synchronized Class<?> loadClass
(String name, boolean resolve)
throws ClassNotFoundException{
// First check if the class is already loaded
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// If still not found, then invoke
// findClass to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
We can use the classloader's two constructor methods to set the parent Class Loader:
public class MyClassLoader extends ClassLoader{
public MyClassLoader(){
super(MyClassLoader.class.getClassLoader());
}
}
Or
public class MyClassLoader extends ClassLoader{
public MyClassLoader(){
super(getClass().getClassLoader());
}
}
The first method is more commonly used, because it is generally not recommended to call the getclass () method in the constructor, because the initialization of the object is completed only at the exit of the constructor. Therefore, if the parent class loader is correctly created and you want to obtain a class from an instance of the Class Loader, if it cannot find this class, it should first access its parent class. If the parent class cannot be found (that is, its parent class cannot be found, and so on), and if the findbootstrapclass0 () method also fails, the findclass () method is called. The default implementation of the findclass () method will throw classnotfoundexception. developers need to implement this method when they inherit java. Lang. classloader to customize class loaders. The default Implementation of findclass () is as follows:
protected Class<?> findClass(String name)
throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
Within the findclass () method, the Class Loader needs to obtain bytecode from any source. The source can be a file system, URL, database, another application that can generate bytecode, and other similar sources that can generate Java-standard bytecode. You can even use bcel (byte code Engineering Library: bytecode Engineering Library) to create classes at runtime. Bcel has been successfully used in the following aspects: compiler, optimizer, obfuscator, code generator and other analysis tools. Once the bytecode is retrieved, this method calls the defineclass () method, which varies with the loading instances of different classes. Therefore, if two classes load instances to define a class from the same source, the defined results are different.
Java language specification (specification) describes in detail the loading, linking, or initialization processes of classes or interfaces in the Java execution engine.
Figure 1 shows an application called mymainclass. According to the previous descriptions, mymainclass. class will be loaded by appclassloader. Mymainclass creates two class loaders: customclassloader1 and customclassloader2. They can obtain the bytecode named target from a data source (such as the network. This indicates that the class definition of the target class is not in the application class path or extension class path. In this case, if mymainclass wants to use a custom class loader to load the target class, customclassloader1 and customclassloader2 will independently load and define the target. Class class. This is of great significance in Java. If the target class has some static initialization code and we only want the code to be executed once in JVM, the code will be executed twice in our current step-loaded and executed by different mmclassloaders. If the class target is loaded by two mmclassloaders and two instances target1 and TARGET2 are created, it is displayed that they are not compatible with the type. In other words, the following code cannot be executed in JVM:
Target target3 = (Target) target2;
The above code throws a classcastexception. This is because JVM treats them as different classes because they are defined by different class loaders. In this case, the same error occurs when we use different instances of the same class loader, instead of using two different class loaders customclassloader1 and customclassloader2. The code will be described later in this article.
Figure 1. Load the same target class to multiple class loaders in the same JVM
For more information about class loading, definitions, and links, see Andreas Schaefer's "inside class loaders ."
Why do we need our own class loaders?
One of the reasons is that developers write their own class loaders to control class loading behaviors in JVM. Classes in Java are identified by their package names and class names. io. serializable interface class, serialversionuid plays an important role in identifying the class version. This unique identifier is a 64-bit hash field consisting of a class name, Interface Name, member method, and attribute, and there is no other quick way to identify the version of a class. Strictly speaking, if all of the above matches, it belongs to the same class.
But let's think about the following situation: we need to develop a general execution engine. You can execute any task that implements a specific interface. When a task is submitted to this engine, the code of the task needs to be loaded first. Assuming that different customers submit different tasks for this engine, coincidentally, all these tasks have the same class name and package name. The question is whether the engine can respond differently to the information submitted by different users. The sample code for downloading is provided in the reference section below. samepath and differentversions are used to demonstrate this concept.
Figure 2 shows the file directory structure. There are three subdirectories samepath, differentversions, and differentversionspush. The example is as follows:
Figure 2. folder structure organization example
In samepath, the class version. version is stored in the V1 and V2 subdirectories. The two classes have the same class name and package name. The only difference is the following line:
public void fx(){
log("this = " + this + "; Version.fx(1).");
}
In V1, the log records include version. FX (1), while in V2 it is version. FX (2 ). Place the two classes with slight differences under a classpath and run the test class:
Set classpath =.; % current_root %/V1; % current_root %/v2
% Java_home %/bin/Java Test
Figure 3 shows the console output. We can see that the code corresponding to version. FX (1) is executed, because the Class Loader first sees the code of this version in classpath.
Figure 3. samepath test first in the class path version 1
Run the program again and make the following minor changes to the class path.
Set classpath =.; % current_root %/V2; % current_root %/V1
% Java_home %/bin/Java Test
Figure 4 shows the output of the console. The code corresponding to version. FX (2) is loaded, because the Class Loader first finds its path in classpath.
Figure 4. samepath test version 2 at the top of the class path
According to the above example, it is obvious that the class loader loads the elements first found in the class path. If we delete version in V1 and V2. to make a non-version. in version format. JAR file, such as myextension. jar, put it in the corresponding Java. ext. in the dirs path, run the command again and you will see the version. version is no longer loaded by appclassloader, but by the extension class loader. 5.
Figure 5. appclassloader and extclassloader
In this example, the folder differentversions contains an Rmi execution engine. The client can provide it to the execution engine for any task that implements the common. taskintf interface. The sub-folders Client1 and Client2 contain the client. taskimpl class which have two slightly different versions. The differences between the two classes are as follows:
static{
log("client.TaskImpl.class.getClassLoader
(v1) : " + TaskImpl.class.getClassLoader());
}
public void execute(){
log("this = " + this + "; execute(1)");
}
In Client1 and Client2, there are log statements for getclassloader (V1), execute (1), getclassloader (V2), and execute (2) respectively. In addition, in the Code of the execution engine RMI server, we randomly put the Client2 task implementation in front of the class path.
Classpath = % current_root %/common; % current_root %/server;
% Current_root %/Client2; % current_root %/Client1
% Java_home %/bin/Java Server. Server
6, 7, 8 screens, on the client Vm, their respective client. taskimpl classes are loaded, instantiated, and sent to the server Vm for execution. From the server console, we can see that the client. taskimpl code is executed only once by the server's VM. This single code version is used by many instances on the server and executes tasks.
Figure 6. execution engine server console
Figure 6 shows the console of the server, loading and executing two different client requests, as shown in 7 and 8. Note that the code is loaded only once (as can be seen from the log of the static initialization block), but this method is executed twice for client calls.
Figure 7. execution engine client 1 Console
In Figure 7, the client VM loads the taskimpl code containing the log Content of client. taskimpl. Class. getclassloader (V1) and provides it to the server's execution engine. Figure 8 the client VM loads another taskimpl code and sends it to the server.
Figure 8. execution engine client 2 Console
In the VM of the client, client. taskimpl is loaded, initialized, and sent to the server for execution. Figure 6 also reveals that the client. taskimpl code is loaded only once in the VM on the server side, but this "unique time" creates and executes many instances on the server side. Maybe client 1 should be unhappy because it is not its client. taskimpl (V1) method call that is executed by the server, but some other code. How can this problem be solved? The answer is to implement a custom class loader.
Custom Class Loader
To better control the loading of classes, you must implement a custom class loader. All custom class loaders should inherit from Java. Lang. classloader. In addition, we should also set the parent class loader in the constructor. Then rewrite the findclass () method. The differentversionspush folder contains a custom Class Loader called filesystemclassloader. Its structure 9 is shown in.
Figure 9. Custom Class Loader relationship
The main method implemented in common. filesystemclassloader is as follows:
public byte[] findClassBytes(String className){
try{
String pathName = currentRoot +
File.separatorChar + className.
replace('.', File.separatorChar)
+ ".class";
FileInputStream inFile = new
FileInputStream(pathName);
byte[] classBytes = new
byte[inFile.available()];
inFile.read(classBytes);
return classBytes;
}
catch (java.io.IOException ioEx){
return null;
}
}
public Class findClass(String name)throws
ClassNotFoundException{
byte[] classBytes = findClassBytes(name);
if (classBytes==null){
throw new ClassNotFoundException();
}
else{
return defineClass(name, classBytes,
0, classBytes.length);
}
}
public Class findClass(String name, byte[]
classBytes)throws ClassNotFoundException{
if (classBytes==null){
throw new ClassNotFoundException(
"(classBytes==null)");
}
else{
return defineClass(name, classBytes,
0, classBytes.length);
}
}
public void execute(String codeName,
byte[] code){
Class klass = null;
try{
klass = findClass(codeName, code);
TaskIntf task = (TaskIntf)
klass.newInstance();
task.execute();
}
catch(Exception exception){
exception.printStackTrace();
}
}
This class is used by the client to convert client. taskimpl (V1) to a byte array, which is then sent to the RMI server. On the server side, the same class is used to convert the content of the byte array back to the code. The client code is as follows:
public class Client{
public static void main (String[] args){
try{
byte[] code = getClassDefinition
("client.TaskImpl");
serverIntf.execute("client.TaskImpl",
code);
}
catch(RemoteException remoteException){
remoteException.printStackTrace();
}
}
private static byte[] getClassDefinition
(String codeName){
String userDir = System.getProperties().
getProperty("BytePath");
FileSystemClassLoader fscl1 = null;
try{
fscl1 = new FileSystemClassLoader
(userDir);
}
catch(FileNotFoundException
fileNotFoundException){
fileNotFoundException.printStackTrace();
}
return fscl1.findClassBytes(codeName);
}
}
In the execution engine, the Code received from the client is sent to the custom class loader. The custom Class Loader defines it as a class from the byte array, instantiate and execute it. It should be noted that for each customer request, we use different instances of the filesystemclassloader class to define the client. taskimpl submitted by the client. Besides, client. taskimpl is not in the server class path. This means that when we call the findclass () method in filesystemclassloader, findclass () calls the internal defineclass () method. Class client. taskimpl is defined by a specific class loader instance. Therefore, when a new instance of filesystemclassloader is used, the class is redefined as a byte array. Therefore, if each client request class client. taskimpl is defined multiple times, we can execute different client. taskimpl codes in the same execution engine JVM.
public void execute(String codeName, byte[] code)throws RemoteException{
FileSystemClassLoader fileSystemClassLoader = null;
try{
fileSystemClassLoader = new FileSystemClassLoader();
fileSystemClassLoader.execute(codeName, code);
}
catch(Exception exception){
throw new RemoteException(exception.getMessage());
}
}
The example is in the differentversionspush folder. The console interfaces of the server and client are as follows:
Figure 10. Custom Class Loader execution engine
Figure 10 shows the custom classloader console. We can see that the client. taskimpl code is loaded multiple times. Classes are loaded and initialized for each client.
Figure 11. Custom class loader, client 1
In Figure 11, the taskimpl code containing the log records of client. taskimpl. Class. getclassloader (V1) is loaded by the client Vm and then sent to the server. Figure 12 another client loads the class code containing client. taskimpl. Class. getclassloader (V1) and sends it to the server.
Figure 12. Custom class loader, client 1
This Code demonstrates how to use different classloader instances to execute different versions of code on the same VM.
J2EE Class Loader
The J2EE server tends to discard the original class and reload the new class at a certain interval. In some cases, it is executed in this way, but in some cases it is not. Similarly, if a web server is to discard a servlet instance, it may be a manual operation by the server administrator, or the instance has not been matched for a long time. When a JSP page is requested for the first time, the container translates the JSP page into a servlet code with a specific form. Once the servlet code is created, the container will translate the servlet into a class file for use. For each request submitted to the container, the container first checks whether the JSP file has been modified. If yes, re-translate the file to ensure that every request is updated in a timely manner. Enterprise-level deployment solutions use files in the form of. Ear,. War, and. rar. They also need to be loaded repeatedly. They may be randomly or periodically executed according to certain configuration schemes. For all of these cases-loading, detaching, and reloading classes ...... All of them are based on the class loading mechanism that we control the application server. To implement these class loaders that need to be extended, it can execute classes defined by itself. Brett peterson has provided a detailed description of the class loading solution for the J2EE application server in his article understanding J2EE application server class loading ubuntures. For details, see theserverside.com.
Close
This article discusses how a class is uniquely identified when loaded to a virtual machine, and issues that occur when the class has the same class name and package name. Because there is no directly available class version management mechanism, if we want to load classes as needed, we need to customize the class loader to expand its behavior. We can use the "hot deployment" feature provided by many J2EE servers to reload a new version of the class without modifying the server's VM. Even if the application server is not involved, we can use a custom class loader to control the specific behavior of Java applications when loading classes. Ted neward's book server-based Java programming details Java class loading, J2EE APIs, and the best way to use them.