When the Java virtual machine needs access to a particular class, it is up to a class loader to provide the class. The class loader goes through the following steps to load and define a class:
If the class loader has already loaded this class, it should find the previously defined class object and return that object immediately.
The security manager is consulted to see if this program is allowed to access the class in question. If it is not, a security exception is thrown. This step may be considered optional.
Otherwise, an internal class loader is consulted to attempt to load the class from the CLASSPATH. If that succeeds, the class loader returns. This ensures that classes within the Java API will not be superseded by classes loaded from the network (or other location).
The way this is done varies between 1.1 and 1.2.[2] In 1.1, there is a single method (the findSystemClass() method) that handles this step. In 1.2, a class loader must delegate to another class loader to find classes that are on the CLASSPATH and call the findSystemClass() method to find classes that are in the core API.
[2]1.2 is now Java 2.
The security manager is consulted to see if this program is allowed to create the class in question. If it is not, a security exception is thrown. This step may be considered optional.
The class file is read into an array of bytes. The mechanism by which the class loader reads the file and creates the byte array will vary depending on the class loader (which, after all, is one of the points of having different class loaders).
The byte codes are run through the bytecode verifier.
A Class object is constructed from the bytecodes. In the process, the methods defining the class are created. In Java 1.1 and later, this process also ensures that the name in the class file matches the name that the class loader thought it was asked to load.
Before the class can be used, it must be resolved--which is to say that any classes that it immediately references must also be found by this class loader. The set of classes that are immediately referenced contains any classes that the class extends as well as any classes used by the static initializers of the class. Note that classes that are used only as instance variables, method parameters, or local variables are not normally loaded in this phase: they are loaded when the class actually references them (although certain compiler optimizations may require that these classes be loaded when the class is resolved).
Step 5 of this process varies depending on the policy of the particular class loader--the data for the class may be read from the network or the filesystem (or from any other location, such as a database). The other steps of this process will remain fixed for a well-defined class loader.
There are a number of class loaders that are used in Java programs, described in the following sections.
All Java programs must have the capability of loading certain classes--the Java API classes and any others located in the user's CLASSPATH. Some of these classes are bootstrapped into the virtual machine. The first thing the virtual machine typically does is load the Java API class files (the rt.jar file) for future use.
The internal class loader uses the native operating system's file access methods to open and read the class files into byte arrays. When one of these classes contains a reference to another class, the internal class loader is again consulted to load the referenced class.
Unlike other class loaders we'll explore, the internal class loader cannot be overridden. Most of the internal class loader, in fact, is written in native code so that it can be accessed directly by the virtual machine (a requirement for the virtual machine to be able to bootstrap the API classes).
The internal class loader is often referred to as the default class loader or the primordial class loader. Due to some details of the Class class, we often speak of classes that are loaded by the internal class loader as having no class loader at all (and as a result, the internal class loader is sometimes called the null class loader).
There is a significant change in the use of the primordial class loader between Java 1.1 and 1.2. In 1.1, the primordial class loader was used to load all classes on the CLASSPATH. In 1.2, the primordial class loader is used only to load the Java API class files; the virtual machine constructs an instance of the URLClassLoader class to load the classes from the CLASSPATH.
An applet needs the ability to load classes via HTTP from the network. Hence, applet class loaders typically use the URL class to read in the data for a class file from the applet's CODEBASE host.
There is no standard applet class loader in the Java API--each Java browser is responsible for implementing its own class loader. In practice, the class loaders of various browsers are indistinguishable (and are usually based on the reference class loader implemented in Sun's appletviewer), but a Java programmer cannot simply instantiate an applet class loader in a platform-independent way.[3]
[3]If you want, you can figure out which class in the JDK on your system is the applet class loader, instantiate an instance of that class, and use it, but all virtual machines will not necessarily have that class available.
Beginning with JDK 1.1, the Java API includes an RMI class loader that can be used by any application. Despite its name, the RMI class loader needn't be used in an RMI application, and it is not truly a class loader--that is, it does not extend the ClassLoader class. In function, the RMIClassLoader class (java.rmi.server.RMIClassLoader) is very similar to the applet class loader--it uses the HTTP protocol to load the desired class file from a remote machine and then defines the class from the data in that file.
The RMI class loader cannot be instantiated directly; you must use one of its static methods to load a class. Once an initial class is loaded by the RMI class loader, any classes it references will also be loaded by the RMI class loader. In addition, the RMI class loader can only load classes from the URL specified by the java.rmi.server.codebase property, so it is not a generic solution to all applications where a class loader might be used.
If you are loading individual, unsigned classes (i.e., classes that are not in a JAR file) from a single URL (i.e., a single directory, whether a file-based or an HTTP-based URL), using the RMI class loader is the simplest option for Java 1.1 applications. For Java 1.2 applications, you can use the RMI class loader for this purpose, or you can use the URL class loader; the URL class loader will offer you more flexibility.
Beginning with Java 1.2, the Java API includes a class loader in the java.security package called SecureClassLoader. This class has a protected constructor, so its real use is to provide the basis for the development of other class loaders. The distinguishing feature of the secure class loader is that it associates a protection domain with each class that it loads. Protection domains form the basis of the operation of the access controller; we'll see more about them in Chapter 5, "The Access Controller". For now, just accept the fact that if you want to use the access controller to establish your security policy, you'll need to use a class loader that extends the SecureClassLoader class.
Also beginning with Java 1.2, the Java API includes a general-purpose class loader that can load classes from a set of URLs: the URLClassLoader class (java.net.URLClassLoader). This class is public and fully implemented, so for 1.2-based applications, it provides a truly useful, general purpose class loader:
Load classes from a set of URLs. A URL in this set may be a directory-based URL, in which case the class loader will attempt to locate individual class files under that directory. A URL in this set may also be a JAR file, in which case the JAR file will be loaded, and the class loader will attempt to find a class in the JAR file.
An instance of the URLClassLoader class is created via one of these constructors:
Construct a class loader based on the given array of URLs. This class loader attempts to find a class by searching each URL in the order in which it appears in the array.
The second of these constructors constructs a URL class loader that uses the 1.2-based delegation model for loading classes (which we discuss at the end of this chapter). In that case, the parent class loader will be asked to load the class first; if it fails, this URL class loader proceeds to load the class. This is the preferred constructor to use.
An instance of the URLClassLoader class may also be obtained via one of these methods:
Create and return a URL class loader. The difference between these methods and constructing a URL class loader directly is that the class loader returned from these methods will call the security manager's checkPackageAccess() method before it attempts to define a class; this is the optional step 2 referred to earlier. In 1.2, only class loaders obtained this way will perform that optional step (unless you write your own class loader to perform that step).
We can construct a URL class loader like this:
URL urls[] = new URL[2]; urls[0] = new URL("http://piccolo.East/~sdo/"); urls[1] = new URL("file:/home/classes/LocalClasses.jar"); URLClassLoader ucl = new URLClassLoader(urls, parent);
When we use this class loader to load the class com.sdo.Car, the class loader first attempts to load it via the URL http://piccolo.East/~sdo/com/sdo/Car.class; if that fails, it looks for the class in the LocalClasses.jar file.
It should come as no surprise that this class is the basis for running the Launcher. In that case, the array of URLs is created based on the list of URLs that make up the CLASSPATH (but not including the core Java API classes).
With all these class loaders to choose from, which is the better choice: an existing class loader or your own custom class loader? The answer depends upon your needs. It is better not to write your own class loader if an existing one can fit your needs, but that's not always possible. Here are some guidelines:
Start by trying to use an instance of the URLClassLoader class. This class can load classes from multiple sites, using file-based and HTTP-based URLs. It can process individual class files and JAR files (including signed JAR files, which will become important later in our discussion). This class is the basis of the Launcher, although with the Launcher itself, you're limited to file-based URLs.
When would you not use the URL class loader? Here are some possible cases:
When you want to load classes other than via HTTP or the file system. You may have classes that are held in a database, or you may want to define the bytecodes for a class programmatically.
When you want to load classes from different hosts and you have a priori knowledge of which class is on which host. The URL class loader will search for classes in its list of URLs sequentially; prior knowledge may allow you to load classes more efficiently.[4]
[4]In 1.2, the URLClassLoader class fails to handle multiple HTTP-based URLs correctly. It is hoped that this will be fixed someday; if it is not and you need to load classes from multiple web servers, you will need to use your own class loader--see the information about the MultiLoader class in Section 3.5.1, "Loading from Multiple Sites" later in this chapter.
If you're on a 1.1-based system and only need to load classes from a single site, use the RMI class loader. Remember that you will have to define as a property the location where those classes are found.
Otherwise, you'll need to provide a custom class loader.
Copyright © 2001 O'Reilly & Associates. All rights reserved.