您的位置:首页 > 编程语言 > Java开发

Internals of Java Class Loading

2012-06-23 23:05 267 查看
Thanks for Binildas Christudas,Link:"http://onjava.com/pub/a/onjava/2005/01/26/classloading.html" for more.
Class loading is one of the most powerful mechanisms provided by the Java language specification. Even though the internals of class loading falls under the "advanced topics" heading, all Java programmers should know how the mechanism works and what can
be done with it to suit their needs. This can save time that would otherwise have been spent debugging
ClassNotFoundException
,
ClassCastException
,
etc.

This article starts from the basics, such as the difference between code and data, and how they are related to form an instance or object. Then it looks into the mechanism of loading code into the JVM with the help of class loaders, and the main type of
class loaders available in Java. The article then looks into the internals of class loaders, where we cover using the basic algorithm (orprobing), followed by class loaders before it loads a class. The next section of the article uses code examples
to demonstrate the necessity for developers to extend and develop their own class loaders. This is followed by explanation on writing your own class loaders and how to use them to make a generic task-execution engine that can be used to load the code supplied
by any remote client, define it in the JVM, and instantiate and then execute it. The article concludes with references to J2EE-specific components where custom class loading schemas becomes the norm.

Class and Data

A class represents the code to be executed, whereas data represents the state associated with that code. State can change; code generally does not. When we associate a particular state to a class, we have an instance of that class. So different
instances of the same class can have different state, but all refer to the same code. In Java, a class will usually have its code contained in a .class file, though there are exceptions. Nevertheless, in the Java runtime, each and every class will have its
code also available in the form of a first-class Java object, which is an instance of
java.lang.Class
. Whenever we compile any Java file, the compiler will
embed a public, static, final field named
class
, of the type
java.lang.Class
, in the emitted byte code. Since this field is public, we can access it using dotted notation, like this:

java.lang.Class klass = Myclass.class;

Once a class is loaded into a JVM, the same class (I repeat, the same class) will not be loaded again. This leads to the question of what is meant by "the same class." Similar to the condition that an object has a specific state, an identity,
and that an object is always associated with its code (class), a class loaded into a JVM also has a specific identity, which we'll look at now.

In Java, a class is identified by its fully qualified class name. The fully qualified class name consists of the package name and the class name. But a class is uniquely identified in a JVM using its fully qualified class name along with the instance of
the
ClassLoader
that loaded the class. Thus, if a class named
Cl
in the package
Pg
is loaded by an instance
kl1
of the class loader
KlassLoader
, the class instance of
C1
, i.e. C1.class is keyed in the JVM as
(Cl, Pg, kl1)
. This means that the two class loader instances
(Cl, Pg, kl1)
and
(Cl, Pg, kl2)
are not one and the same, and classes loaded by them are also completely different and not type-compatible to each other. How many class loader instances do we have in a JVM? The next section explains
this.

Class Loaders

In a JVM, each and every class is loaded by some instance of a
java.lang.ClassLoader
. The
ClassLoader
class is located in the
java.lang
package and developers are free to subclass it to add their own functionality to class loading.

Whenever a new JVM is started by typing
java MyMainClass
, the "bootstrap class loader" is responsible for loading key Java classes like
java.lang.Object
and other runtime code into memory first. The runtime classes are packaged inside
of theJRE\lib\rt.jar file. We cannot find the details of the bootstrap class loader in the Java documentation, since this is a native implementation. For the same reason, the behavior of the bootstrap class loader will also differ across JVMs.

In a related note, we will get
null
if we try to get the class loader of a core Java runtime class, like this:

log(java.lang.String.class.getClassLoader());

Next comes the Java extension class loader. We can store extension libraries, those that provide features that go beyond the core Java runtime code, in the path given by the
java.ext.dirs
property. The
ExtClassLoader
is responsible for loading all .jar files kept in the
java.ext.dirs
path. A developer can add his or her own application .jar files or whatever libraries he or she might need to add to the classpath to this extension
directory so that they will be loaded by the extension class loader.

The third and most important class loader from the developer perspective is the
AppClassLoader
. The application class loader is responsible for loading all of the classes kept in the path corresponding to the
java.class.path
system
property.

"Understanding Extension Class Loading" in Sun's Java tutorial explains more on the above three class loader paths. Listed below are a few other class loaders in the JDK:

java.net.URLClassLoader

java.security.SecureClassLoader

java.rmi.server.RMIClassLoader

sun.applet.AppletClassLoader


java.lang.Thread
, contains the method
public ClassLoader getContextClassLoader()
, which returns the context class loader for a particular thread. The context class loader is provided by the creator of the thread for use by code
running in this thread when loading classes and resources. If it is not set, the default is the class loader context of the parent thread. The context class loader of the primordial thread is typically set to the class loader used to load the application.

How Class Loaders Work

All class loaders except the bootstrap class loader have a parent class loader. Moreover, all class loaders are of the type
java.lang.ClassLoader
. The above two statements are different, and very important for the correct working of any class
loaders written by developers. The most important aspect is to correctly set the parent class loader. The parent class loader for any class loader is the class loader instance that loaded that class loader. (Remember, a class loader is itself a class!)

A class is requested out of a class loader using the
loadClass()
method. The internal working of this method can be seen from the source code for
java.lang.ClassLoader
, given below:


protected synchronized Class<?> loadClass
(String name, boolean resolve)
throws ClassNotFoundException{

// First check if the class is already loadedClass 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;
}

To set the parent class loader, we have two ways to do so in the
ClassLoader
constructor:

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 preferred because calling the method
getClass()
from within the constructor should be discouraged, since the object initialization will be complete only at the exit of the constructor code. Thus, if the parent class loader
is correctly set, whenever a class is requested out of a
ClassLoader
instance, if it cannot find the class, it should ask the parent first. If the parent cannot find it (which again means that its parent also cannot find the class, and so on),
and if the
findBootstrapClass0()
method also fails, the
findClass()
method is invoked. The default implementation of
findClass()
will throw
ClassNotFoundException
and developers are expected to implement this method when they subclass
java.lang.ClassLoader
to make custom class loaders. The default implementation of
findClass()
is shown below.

protected Class<?> findClass(String name)
throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}

Inside of the
findClass()
method, the class loader needs to fetch the byte codes from some arbitrary source. The source can be the file system, a network URL, a database, another application that can spit out byte codes on the fly, or any similar
source that is capable of generating byte code compliant with the Java byte code specification. You could even useBCEL (Byte Code Engineering Library), which provides convenient methods to create
classes from scratch at runtime. BCEL is being used successfully in several projects such as compilers, optimizers, obsfuscators, code generators, and analysis tools. Once the byte code is retrieved, the method should call the
defineClass()
method,
and the runtime is very particular about which
ClassLoader
instance calls this method. Thus, if two
ClassLoader
instances define byte codes from the same or different sources, the defined classes are different.

The
Java language specification gives a detailed explanation on the process of
loading,
linking, and the
initialization of classes and interfaces in the Java Execution Engine.

Figure 1 shows an application with a main class called
MyMainClass
. As explained earlier,
MyMainClass.class
will be loaded by the
AppClassLoader
.
MyMainClass
creates instances of two class loaders,
CustomClassLoader1
and
CustomClassLoader2
, which are capable of finding the byte codes of a fourth class called
Target
from some source (say, from a network path). This means the class definition of the
Target

class is not in the application class path or extension class path. In such a scenario, if
MyMainClass
asks the custom class loaders to load the
Target
class,
Target
will be loaded and
Target.class
will be defined independently by both
CustomClassLoader1
and
CustomClassLoader2
. This has serious implications in Java. If some static initialization code is put in the
Target
class, and if we want this code to be executed one and only once in a JVM, in our current setup the code will be executed
twice in the JVM: once each when the class is loaded separately by both
CustomClassLoader
s. If the
Target
class is instantiated in both the
CustomClassLoader
s to have the instances
target1
and
target2
as shown in Figure 1, then
target1
and
target2
are not type-compatible. In other words, the JVM cannot execute the code:

Target target3 = (Target) target2;

The above code will throw a
ClassCastException
. This is because the JVM sees these two as separate, distinct class types, since they are defined by different
ClassLoader
instances. The above explanation holds true even if
MyMainClass
doesn't use two separate class loader classes like
CustomClassLoader1
and
CustomClassLoader2
, and instead uses two separate instances of a single
CustomClassLoader
class. This is demonstrated later in the article with code examples.

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: