The Java Execution Model

In order to understand the behavior of a Java program, we must have some model of how such a program is executed. Here we present the Java Execution Model, which explains the step-by-step execution of a Java program.

1. A Simple Example: Drawing a Hole

We will introduce the Java Execution Model in the context of the following simple Hole applet:

import java.awt.*;
import java.applet.Applet;
 
public class Hole1 extends Applet {
 
  public void paint(Graphics g) {
    this.drawHole(g, 50);
    this.drawHole(g, 25);
  }
 
  public void drawHole(Graphics g, int rad) {
    g.setColor(Color.blue);
    g.fillRect(60 - rad, 80 - rad, 2*rad, 2*rad);
    g.setColor(Color.yellow);
    g.fillOval(60 - rad, 80 - rad, 2*rad, 2*rad); 
  }
 }

In addition to responding to the paint message, an instance of the Hole1 applet class also responds to a drawHole message, which includes a graphics object g and an integer radius rad as parameters. The drawHole message causes the graphics object g to draw the following pattern in its associated window:

The pattern consists of a yellow circle of radius rad embedded in a blue square of side length that is twice rad. Both the circle and square are centered at the point, (60, 80). We will call this pattern a "hole" of radius rad.

 

The paint method of the Hole class draws a hole of radius 25 embedded in a hole of radius 50. The resulting pattern should look like:

2. The Java Execution Model: An Example

Let's suppose that we have an object H that is an instance of the Hole class and an object G that is an instance of the Graphics class. Up until this point, we have been depicting objects like H and G as stick figures, to emphasize the perspective that they are agents that can respond to messages:

Since such depictions are somewhat large, we will abbreviate them via labelled colored disks, like so: . We will use the red dot to represent the "spirit" that animates an object while it executes a method. This spirit is often called "control", and its movement through a program is called "control flow".

Let's suppose that H receives a paint message with argument G. (For now, we won't worry about who is sending H such a message. It turns out that this message is generated as part of the process of invoking the Hole applet from the Applet Viewer or from Netscape.) We will depict this situation as follows:

H is said to be the receiver of the paint message, and G is the argument (or actual parameter) of the paint message. The placement of the spirit at the end of the paint method invocation statement indicates the sending of the paint message to the receiver. Upon receipt of the message, H creates an execution frame for executing the method named by the message:

An execution frame is a box with two components:

  1. A collection of variables. A variable is a named box that holds an object or primitive value (number, character, etc.). Initially, there is one variable for every parameter of the method that contains the corresponding argument value. In this case, there is a single parameter named g that is filled with the graphics object G. Additionally, an execution frame always contains a special variable named this that holds the receiver of the message (in this case, the Hole object H).
  2. A copy of the statements in the method body. These statements are executed in sequential order as the red dot visits them from top down.

In the above diagram, the shaded red rectangle around H.paint (G) indicates that this statement is in the process of being executed. The execution frame to the right is the "exploded view" that shows the details of the execution. The placement of the red dot before this.drawHole(g,50) indicates the statement to be executed next.

  1. Execution of a method invocation statement involves the following steps:
  2. Evaluate the receiver expression to yield the receiver of the message.
  3. Evaluate the argument expressions to yield the argument values of the message.
  4. Send the message to the receiver with the given arguments. An object responds to a message by executing the associated method in an execution frame as described above.

In this case, the statement to be executed is the method invocaton this.drawHole(g,50).

Evaluating the receiver expression this yields the Hole object H:

In the next two steps, the two argument expressions g and 50 yield G and 50 as their values:

Finally, we reach the point where the drawHole message is sent to H with the arguments G and 50.

A second execution frame is created to execute H's drawHole method:

This execution frame contains three variables: the parameters g and rad plus the special variable this. The statements in this execution frame are the body of H's drawHole method. The execution of the first statement, g.setColor(Color.blue) proceeds by first evaluating g and then evaluating Color.blue:

The value of g is the graphics object G. The value of Color.blue is the blue color object, here represented as a blue rectangle. Next, the setColor message is sent to G with the blue color as its argument value.

G responds to the message by executing its setColor method. This execution will be handled in yet another execution frame. However, since we do not know the implementation details of setColor, we will treat the execution details of setColor as a black box that are not depicted in the model. Instead, we will simply assume that the execution of this method changes G's current pen color to blue.

 

After the successful execution of setColor, the red dot returns to evaluate the second statement in the body of drawHole. (The first statement is outlined in gray to indicate that it has already been executed.)

The receiver expression and argument expressions of the second method invocation are evaluated in turn:

 

After the receiver and argument values have been obtained, the fillRect message is sent to G with the arguments 10, 30, 100, and 100.

We treat fillRect as a black box whose execution fills the following rectangle on the screen using the current pen color (blue):

Next is the execution of the method invocation g.setColor(Color.yellow):

Evalution of the receiver and argument expressions yields:

Sending this setColor message to G causes it to change its current pen color to yellow.

The final statement in this frame is the invocation of a fillOval method. Evaluating the receiver and argument expressions yields:

We treat fillOval as a primitive that fills the following disk in the current pen color (which is now yellow):

At this point, the first drawHole method invocation within the second execution frame has been completely executed. We indicate this in the following diagram via gray shading:

The red dot returns to the second statement of the first execution frame. The receiver and argument expressions are evaluated to yield:

At this point, a second drawHole message is send to H, this time with arguments G and 25. A third execution frame is created for executed the method associated with this message. The steps of this execution are highlighted in the following sequence of diagrams:

In the final diagram, all shading is gray to indicate the fact that the execution of H's paint method is complete. The red dot is nowhere to be found because it has returned to the invoker of H's paint method.

The final diagram of the execution model is an execution tree of method invocations, where each invocation branches out into the invocations of the statements in its body. The initial invocation H.paint(G) is the "root" of the tree and the invocations of the black-box graphics routines are the "leaves" of the tree.

3. Capturing Patterns: Treating Methods as Primitives

Note how all the drawing in the above example is actually performed by the eight invocations at the leaves. In the execution model, we chose to treat these operations as black boxes. We call such operations "primitives" because they are the primitive building blocks out of which the final solution is ultimately composed.

If all the operations are ultimately performed by the primitives, why not program directly in terms of the primitives? That is, why not dispense with the drawHole method and implement the paint method very concretely in terms of eight primitive invocations, as follows:

public void paint(Graphics g) {
  g.setColor(Color.blue);
  g.fillRect(10,30,100,100);
  g.setColor(Color.yellow);
  g.fillOval(10,30,100,100);
  g.setColor(Color.blue);
  g.fillRect(35,55,50,50);
  g.setColor(Color.yellow);
  g.fillOval(35,55,50,50);
}

The problem with the strategy of writing all programs directly in terms of primitives is that such programs are difficult to read, write and modify. For instance, it is not at all apparent from the concrete code that all the figures are centered at the point (60,80). And changing the figures to be centered instead at (70,50) requires serious thinking. So programs written in this concrete style are unlikely to ever be updated. In fact, since they are so difficult to debug, it is unlikely that they will every work correctly to begin with.

These issues may not seem like such a big deal with only 8 primitive invocations. But programs of reasonable complexity perform thousands, millions, and even billions of primitive operations. Such large numbers of operations can quickly overwhelm even the best human minds.

The main problem with the concrete programming strategy is its inability to capture common patterns. When we see the picture

we naturally decompose it into two instances of a common pattern:

Suppose that the Java Graphics agent were already equipped with a drawHole primitive for drawing such a pattern. Then the entire execution tree for the nested pattern would be something like

Such program is extremely easy to understand!

Of course, the designers of Java cannot anticipate our every whim, so they have not provided us with such a primitive. But we can create one of our own! That is the point of the drawHouse method in the Hole1 class:

public void drawHole(Graphics g, int rad) {
  g.setColor(Color.blue);
  g.fillRect(60 - rad, 80 - rad, 2*rad, 2*rad);
  g.setColor(Color.yellow);
  g.fillOval(60 - rad, 80 - rad, 2*rad, 2*rad); 
}

The purpose of this method is to make up for what we perceive to be as a deficiency in the Java primitives. Whenever we define a new method, we are effectively extending the Java language for our own personal purposes. Whenever we use abstraction to capture patterns in our code, we are programming language designers!

In fact, when reasoning about the original paint method, we can even treat our own drawHole message as a primitive so that we don't have to delve into the details. That is, we can consider the execution tree of paint to be:

This means of simplifying reasoning about programs is the key technique that allows programmers to build complex programs without getting swamped by a plethora of details. At every level of programming, it is possible to use methods, like drawHole, that we treat as black-box building blocks that are are assumed to work correctly. But a black box is for all intents and purposes just another primitive! Of course, it is also necessary to reason that drawHole actually does what it is supposed to do. But that can be determined independently of how paint uses drawHole.

4. Execution Trees as Nested Boxes

Earlier in the semester, we noticed the correspondence between trees and nested boxes. We can use this correspondence to represent execution trees in terms of nested boxes. Since nested boxes are easy to represent as nested tables within HTML, we can use HTML tables to represent the content of execution trees. Click here to continue.