📄 chap05.html
字号:
<P> void flow() {</P>
<P> }</P>
<P>}</P>
</FONT><FONT SIZE="2"><P> </P></P>
<P></FONT><FONT FACE="Courier New">// On CD-ROM in file jvm/ex2/Volcano.java
<P>class Volcano {</P>
<P> </P>
<P> public static void main(String[] args) {</P>
<P> Lava lava = new Lava();</P>
<P> lava.flow();</P>
<P> }</P>
<P>}</P>
</FONT><FONT SIZE="2"><P> </P></FONT><FONT FACE="Courier New">end</FONT></P></PRE>
<P>The following paragraphs describe how an implementation might execute the first instruction in the bytecodes for the <FONT FACE="Courier New">main()</FONT> method of the <FONT FACE="Courier New">Volcano</FONT> application. Different implementations of the Java Virtual Machine can operate in very different ways. The following description illustrates one way--but not the only way--a Java Virtual Machine could execute the first instruction of <FONT FACE="Courier New">Volcano</FONT>韘 <FONT FACE="Courier New">main()</FONT> method.</P>
<P>To run the <FONT FACE="Courier New">Volcano</FONT> application, you give the name "<FONT FACE="Courier New">Volcano</FONT>" to a Java Virtual Machine in an implementation-dependent manner. Given the name <FONT FACE="Courier New">Volcano</FONT>, the virtual machine finds and reads in file <FONT FACE="Courier New">Volcano.class</FONT>. It extracts the definition of class <FONT FACE="Courier New">Volcano</FONT> from the binary data in the imported class file and places the information into the method area. The virtual machine then invokes the <FONT FACE="Courier New">main()</FONT> method, by interpreting the bytecodes stored in the method area. As the virtual machine executes <FONT FACE="Courier New">main()</FONT>, it maintains a pointer to the constant pool (a data structure in the method area) for the current class (class <FONT FACE="Courier New">Volcano</FONT>).</P>
<P>Note that this Java Virtual Machine has already begun to execute the bytecodes for <FONT FACE="Courier New">main()</FONT> in class <FONT FACE="Courier New">Volcano</FONT> even though it hasn韙 yet loaded class <FONT FACE="Courier New">Lava</FONT>. Like many (probably most) implementations of the Java Virtual Machine, this implementation doesn韙 wait until all classes used by the application are loaded before it begins executing <FONT FACE="Courier New">main()</FONT>. It loads classes only as it needs them.</P>
<P><FONT FACE="Courier New">main()</FONT>韘 first instruction tells the Java Virtual Machine to allocate enough memory for the class listed in constant pool entry one. The virtual machine uses its pointer into <FONT FACE="Courier New">Volcano</FONT>韘 constant pool to look up entry one and finds a symbolic reference to class <FONT FACE="Courier New">Lava</FONT>. It checks the method area to see if <FONT FACE="Courier New">Lava</FONT> has already been loaded.</P>
<P>The symbolic reference is just a string giving the class韘 fully qualified name: <FONT FACE="Courier New">"Lava"</FONT>. Here you can see that the method area must be organized so a class can be located--as quickly as possible--given only the class韘 fully qualified name. Implementation designers can choose whatever algorithm and data structures best fit their needs--a hash table, a search tree, anything. This same mechanism can be used by the static <FONT FACE="Courier New">forName()</FONT> method of class <FONT FACE="Courier New">Class</FONT>, which returns a <FONT FACE="Courier New">Class</FONT> reference given a fully qualified name.</P>
<P>When the virtual machine discovers that it hasn韙 yet loaded a class named "<FONT FACE="Courier New">Lava</FONT>," it proceeds to find and read in file <FONT FACE="Courier New">Lava.class</FONT>. It extracts the definition of class <FONT FACE="Courier New">Lava</FONT> from the imported binary data and places the information into the method area.</P>
<P>The Java Virtual Machine then replaces the symbolic reference in <FONT FACE="Courier New">Volcano</FONT>韘 constant pool entry one, which is just the string <FONT FACE="Courier New">"Lava"</FONT>, with a pointer to the class data for <FONT FACE="Courier New">Lava</FONT>. If the virtual machine ever has to use <FONT FACE="Courier New">Volcano</FONT>韘 constant pool entry one again, it won韙 have to go through the relatively slow process of searching through the method area for class <FONT FACE="Courier New">Lava</FONT> given only a symbolic reference, the string <FONT FACE="Courier New">"Lava"</FONT>. It can just use the pointer to more quickly access the class data for <FONT FACE="Courier New">Lava</FONT>. This process of replacing symbolic references with direct references (in this case, a native pointer) is called <I>constant pool resolution</I>. The symbolic reference is <I>resolved</I> into a direct reference by searching through the method area until the referenced entity is found, loading new classes if necessary.</P>
<P>Finally, the virtual machine is ready to actually allocate memory for a new <FONT FACE="Courier New">Lava</FONT> object. Once again, the virtual machine consults the information stored in the method area. It uses the pointer (which was just put into <FONT FACE="Courier New">Volcano</FONT>韘 constant pool entry one) to the <FONT FACE="Courier New">Lava</FONT> data (which was just imported into the method area) to find out how much heap space is required by a <FONT FACE="Courier New">Lava</FONT> object.</P>
<P>A Java Virtual Machine can always determine the amount of memory required to represent an object by looking into the class data stored in the method area. The actual amount of heap space required by a particular object, however, is implementation-dependent. The internal representation of objects inside a Java Virtual Machine is another decision of implementation designers. Object representation is discussed in more detail later in this chapter.</P>
<P>Once the Java Virtual Machine has determined the amount of heap space required by a <FONT FACE="Courier New">Lava</FONT> object, it allocates that space on the heap and initializes the instance variable <FONT FACE="Courier New">speed</FONT> to zero, its default initial value. If class <FONT FACE="Courier New">Lava</FONT>韘 superclass, <FONT FACE="Courier New">Object</FONT>, has any instance variables, those are also initialized to default initial values. (The details of initialization of both classes and objects are given in Chapter 7, "The Lifetime of a Class.")</P>
<P>The first instruction of <FONT FACE="Courier New">main()</FONT> completes by pushing a reference to the new <FONT FACE="Courier New">Lava</FONT> object onto the stack. A later instruction will use the reference to invoke Java code that initializes the <FONT FACE="Courier New">speed</FONT> variable to its proper initial value, five. Another instruction will use the reference to invoke the <FONT FACE="Courier New">flow()</FONT> method on the referenced <FONT FACE="Courier New">Lava</FONT> object.</P>
<H3><P>The Heap</P>
</H3><P>Whenever a class instance or array is created in a running Java application, the memory for the new object is allocated from a single heap. As there is only one heap inside a Java Virtual Machine instance, all threads share it. Because a Java application runs inside its "own" exclusive Java Virtual Machine instance, there is a separate heap for every individual running application. There is no way two different Java applications could trample on each other韘 heap data. Two different threads of the same application, however, could trample on each other韘 heap data. This is why you must be concerned about proper synchronization of multi-threaded access to objects (heap data) in your Java programs.</P>
<P>The Java Virtual Machine has an instruction that allocates memory on the heap for a new object, but has no instruction for freeing that memory. Just as you can韙 explicitly free an object in Java source code, you can韙 explicitly free an object in Java bytecodes. The virtual machine itself is responsible for deciding whether and when to free memory occupied by objects that are no longer referenced by the running application. Usually, a Java Virtual Machine implementation uses a <I>garbage collector</I> to manage the heap.</P>
<I><P>Garbage Collection</P>
</I><P>A garbage collector韘 primary function is to automatically reclaim the memory used by objects that are no longer referenced by the running application. It may also move objects as the application runs to reduce heap fragmentation.</P>
<P>A garbage collector is not strictly required by the Java Virtual Machine specification. The specification only requires that an implementation manage its own heap <I>in some manner</I>. For example, an implementation could simply have a fixed amount of heap space available and throw an <FONT FACE="Courier New">OutOfMemory</FONT> exception when that space fills up. While this implementation may not win many prizes, it does qualify as a Java Virtual Machine. The Java Virtual Machine specification does not say how much memory an implementation must make available to running programs. It does not say how an implementation must manage its heap. It says to implementation designers only that the program will be allocating memory from the heap, but not freeing it. It is up to designers to figure out how they want to deal with that fact.</P>
<P>No garbage collection technique is dictated by the Java Virtual Machine specification. Designers can use whatever techniques seem most appropriate given their goals, constraints, and talents. Because references to objects can exist in many places--Java Stacks, the heap, the method area, native method stacks--the choice of garbage collection technique heavily influences the design of an implementation韘 runtime data areas. Various garbage collection techniques are described in Chapter 9, "Garbage Collection."</P>
<P>As with the method area, the memory that makes up the heap need not be contiguous, and may be expanded and contracted as the running program progresses. An implementation韘 method area could, in fact, be implemented on top of its heap. In other words, when a virtual machine needs memory for a freshly loaded class, it could take that memory from the same heap on which objects reside. The same garbage collector that frees memory occupied by unreferenced objects could take care of finding and freeing (unloading) unreferenced classes. Implementations may allow users or programmers to specify an initial size for the heap, as well as a maximum and minimum size.</P>
<I><P>Object Representation</P>
</I><P>The Java Virtual Machine specification is silent on how objects should be represented on the heap. Object representation--an integral aspect of the overall design of the heap and garbage collector--is a decision of implementation designers</P>
<P>The primary data that must in some way be represented for each object is the instance variables declared in the object韘 class and all its superclasses. Given an object reference, the virtual machine must be able to quickly locate the instance data for the object. In addition, there must be some way to access an object韘 class data (stored in the method area) given a reference to the object. For this reason, the memory allocated for an object usually includes some kind of pointer into the method area.</P>
<P>One possible heap design divides the heap into two parts: a handle pool and an object pool. An object reference is a native pointer to a handle pool entry. A handle pool entry has two components: a pointer to instance data in the object pool and a pointer to class data in the method area. The advantage of this scheme is that it makes it easy for the virtual machine to combat heap fragmentation. When the virtual machine moves an object in the object pool, it need only update one pointer with the object韘 new address: the relevant pointer in the handle pool. The disadvantage of this approach is that every access to an object韘 instance data requires dereferencing two pointers. This approach to object representation is shown graphically in Figure 5-5. This kind of heap is demonstrated interactively by the HeapOfFish applet, described in Chapter 9, "Garbage Collection."</P>
<P><IMG SRC="fig5-5.gif" tppabs="http://www.pbg.mcgraw-hill.com/betabooks/venners/images/fig5-5.gif" ALT="Figure 5-5"></P>
<P>Another design makes an object reference a native pointer to a bundle of data that contains the object韘 instance data and a pointer to the object韘 class data. This approach requires dereferencing only one pointer to access an object韘 instance data, but makes moving objects more complicated. When the virtual machine moves an object to combat fragmentation of this kind of heap, it must update every reference to that object anywhere in the runtime data areas. This approach to object representation is shown graphically in Figure 5-6.</P>
<P><IMG SRC="fig5-6.gif" tppabs="http://www.pbg.mcgraw-hill.com/betabooks/venners/images/fig5-6.gif" ALT="Figure 5-6"></P>
<P>The virtual machine needs to get from an object reference to that object韘 class data for several reasons. When a running program attempts to cast an object reference to another type, the virtual machine must check to see if the type being cast to is the actual class of the referenced object or one of its supertypes. . It must perform the same kind of check when a program performs an <FONT FACE="Courier New">instanceof</FONT> operation. In either case, the virtual machine must look into the class data of the referenced object. When a program invokes an instance method, the virtual machine must perform dynamic binding: it must choose the method to invoke based not on the type of the reference but on the class of the object. To do this, it must once again have access to the class data given only a reference to the object.</P>
<P>No matter what object representation an implementation uses, it is likely that a method table is close at hand for each object. Method tables, because they speed up the invocation of instance methods, can play an important role in achieving good overall performance for a virtual machine implementation. Method tables are not required by the Java Virtual Machine specification and may not exist in all implementations. Implementations that have extremely low memory requirements, for instance, may not be able to afford the extra memory space method tables occupy. If an implementation does use method tables, however, an object韘 method table will likely be quickly accessible given just a reference to the object.</P>
<P>One way an implementation could connect a method table to an object reference is shown graphically in Figure 5-7. This figure shows that the pointer kept with the instance data for each object points to a special structure. The special structure has two components:</P>
<UL><LI> A pointer to the full the class data for the object
<LI> The method table for the object
<P> The method table is an array of pointers to the data for each instance method that can be invoked on objects of that class. The method data pointed to by method table includes:
<LI> The sizes of the operand stack and local variables sections of the method韘 stack
<LI> The method韘 bytecodes
<LI> An exception table</UL>
<P>This gives the virtual machine enough information to invoke the method. The method table include pointers to data for methods declared explicitly in the object韘 class or inherited from superclasses. In other words, the pointers in the method table may point to methods defined in the object韘 class or any of its superclasses. More information on method tables is given in Chapter 8, "The Linking Model."</P>
<P><IMG SRC="fig5-7.gif" tppabs="http://www.pbg.mcgraw-hill.com/betabooks/venners/images/fig5-7.gif" ALT="Figure 5-7"></P>
<P>If you are familiar with the inner workings of C++, you may recognize the method table as similar to the VTBL or virtual table of C++ objects. In C++, objects are represented by their instance data plus an array of pointers to any virtual functions that can be invoked on the object. This approach could also be taken by a Java Virtual Machine implementation. An implementation could include a copy of the method table for a class as par
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -