The Java compiler does not generate a big executable file at compile time. Instead the compiler generates many small class files, one for each class, that will be interpreted during execution. This means that the compiler needs access to the class files involved at compile time and also that the JVM needs access to the same class files during execution. In this chapter we will describe the different stages in execution, namely start-up, loading ,linking and initialization.
The JVM will start executing an application
by invoking the main method in the class file
specified. This means that the class file that is retrieved from the
command line needs to contain one and only one main method. For
example, JAVAX can be invoked on a UNIX machine by typing:
javax MakeEyesPop RightNowThe file MakeEyesPop.class is then loaded and the main method is executed. An array containing the string ``RightNow'' is passed as an argument. If more arguments are specified, they can be reached through the same array. The execution involves linking and creation of a frame to send to the Interpreter.
Loading refers to the process of finding the compiled form of a class (i.e. a class file) and constructing a Class object to represent the class. The first task is done by the ClassLoader who will search the current directory as well as the default Java directory. The ClassLoader will then store the name of the class file in an internal list to prevent from reloading the same file twice. The second task is performed by the Parser which will read and store the class file in a format understood by the rest of the JVM (i.e a ClassFile struct). This dynamic loading makes sure that only the class files that are really used will be loaded.
Linking is the process of taking a binary form of a class or interface type and combining it into the run-time state of the JVM, so that it can be executed. A class or interface is always loaded before it is linked. Linking involves: verification, preparation and optionally resolution.
The verification process ensures that the binary representation of a class is structurally correct. A lot of checks have to be done at run-time since one cannot be sure that the class file has been generated by a trustworthy compiler like the one from Sun. Other compilers may be used as long as they generate bytecode that are within the limits of the class file format. The verification is also necessary since things might have happened to the class files used since the compilation. Consider the following:
Class A creates an object of class B and invokes one of its methods. After class A is compiled, the invoked method in class B is deleted for some reason. This does not change the fact that class A is already compiled and a class file is already generated. When the JVM tries to invoke the deleted method, in class B, it will throw a NoSuchMethodError and execution will be interrupted.
The verification process can be divided into four passes:
Preparation involves creating static fields for a class and initializing them to their default values, namely zero, null etc. No Java code is executed since the Java initialization methods are not run until later, in the initialization phase described below.
In our implementation we use a pointer static_p in each field_info which is set to null by the Parser. The struct field_info is part of our ClassFile struct and it contains information about a field. A field is essentially a variable so field_info includes descriptor as well as access flags.
The pointer static_p is used to access variables that are declared static. This is done since there may be only one copy of a static variable. The memory that the pointer refers to will be allocated at initialization time.
Initialization of a class consists of executing its static initializers as well as the initializers for the variables declared static. A static initializer is a block of code that is declared static. Before a class is initialized its super class must be initialized and so on. This procedure ends when class Object, or a class that are already initialized, is reached.
A class gets initialized by running the <clinit> method which is found in the class file providing that the class file contains static declarations. This is done in our implementation by a function called initialize_class which will parse every field_info and compute the object size. The object size is required in order to allocate an object. If a field is declared static we will allocate the memory in this function and set the pointer static_p to refer to it. Finally initialize_class will try to create a <clinit> frame and send it to the Interpreter if it was successful.
Not every class file contains a <clinit> method but every class file does contain an <init> method. These differ in the respect that the <init> method is always invoked when a new instance of a class is created. The <clinit> method is only invoked once. They unite in the respect that they are generated by the compiler and may not be designed by the programmer, hence the enclosing (<>).
The JVM will only load class files when they are needed and will only initialize a class file if necessary. This means that a Java program will hopefully be more efficient in an interactive application since it is not clear at the beginning of the execution what will be needed. In conventional linking one will have to cover all the possibilities which leads to a very large executable file. Probably larger than necessary.
Another advantage becomes evident when class files are loaded over a network. A Java application is able to start executing when the class file containing the main method is loaded, hence start-up is fast. Conventional loading would require that every file that the program needs has to be loaded initially.
From Axis' point of view it is very attractive that Java is memory efficient. The memory in Etrax is limited and it would be nice if the JVM would eject class files that have not been used in awhile. JAVAX does not have this feature.
There are many similarities between the run-time system in Java and the one in Simula. The main features in an object-oriented run-time system are the descriptions of objects and methods (the frame and the object_info respectively). These help the run-time system to remember the overall state of the objects, as well as the methods. The internal design of these two posts are similar in Java and Simula.
The differences lie in the language specification which is reflected in the run-time system. There are three main differences:
Simula has a similar object initiation procedure but with an extra feature, called inner. The object initiation procedure in Simula consists of setting variables to default or to declared values. Secondly every code segment declared before inner is executed. The inner-execution begins with the most specific class to the most abstract class. The last step is to execute every statement after the inner declaration, going from the most abstract class to the specific class in the class hierarchy. Java object initiation doesn't have an inner equivalent.