The following is a diagram which represents the run-time stack:
The big box is called the frame and information inside it corresponds to the method that's currently running or executing. In the top left corner, we have the name and line number of the currently running method, and the line number that is currently being executed. In the top right, we have the name of the class to which this method belongs. Inside the frame is the storage for local variables and/or values of parameters.
Local variables are declared by local variable declaration statements. What are these? These are statements that occur within methods which allocate memory for a primitive data type or a reference. Well we've seen a few already:
int age = 25; Student s1;
When statements such as these occur within a method (remember that main is a method as well!) they allocate memory in the frame of the method which is on the run-time stack:
Notice that s1 contains the value null. The word null is a keyword of the Java language, and is the default value of any reference. It means that the reference doesn't actually refer to anything yet. If we were then to execute the statement s1 = new Student(); or if we had originally written Student s1 = new Student(): then the (local) reference variable would contain a reference to the new Student object.
Now this construction is called a stack for a good reason: when we call a method, a new frame appears on top of the old frame; in this way a 'stack' of frames is built. The new frame will contain the name, currently-executing line number and so on of the method that has just been called. When the newly-called method ends, its frame is deleted. After execution of the method, the stack is the same as it was before the method call, except we may be at the next line number. In this manner, if we call a method which calls a method which calls a method, the stack grows 'up' as we put new frames on, and as we complete each of these methods, the stack grows down as the frames are deleted. /* getRevenue() returns the total dollars of all tickets sold */
As an example, consider the following program, which is similar to ones we've seen before:
public class Student { public void PayFees() { feesOwing = 0; } private String name; private int age; private String courses; private double feesOwing = 5000.00; private boolean scholarship; } public class SecondProg { public static void main(String[] args) { Student s1 = new Student(); s1.PayFees(); } }
As a convention, the line number in the upper left hand box of a frame refers to the line that's about to be executed. Therefore, the run-time stack looks like this before any line is executed:
Note that the method that is currently executing is indeed main and the line about to be executed is indeed the first line of the program. /* getRevenue() returns the total dollars of all tickets sold */
After the first line has been executed, the run-time stack looks like this:
Note that we have declared a local variable here (s1) which contains a reference to the newly created object. The value of s1 (i.e. the reference to the object) is stored in the run-time stack. More specifically, it is stored in the frame of the run-time stack of the method in which the object was instantiated. (main). Note also that the line about to be executed has been incremented to 2.
Now in the second line we are calling or invoking a method: s1.PayFees();. While the method is executing, we'll have another frame on the stack for the method. Immediately after starting to execute the method and before any line in the method has been executed, the run-time stack will look like this:
First of all, since we've started to execute a new method, the stack has grown -- a new frame has been added 'on top of' the old frame. Since we're about to execute line 1 of PayFees(), the upper left hand box shows this information. Finally, since this method is from the class Student, this class name appears in the upper right corner of the new frame.
Just to refresh your memory, the method PayFees() is defined as follows
public void PayFees() { feesOwing = 0; }
After the first line of PayFees() has been executed, we have:
The only change here is that the line about to be executed has been incremented to 2. But we must have changed memory! Didn't we do an assignment: feesOwing=0; which should write the value 0 into the memory location associated with the instance variable feesOwing? Yes we did, but since this is an instance variable it has memory allocated in object space; we're just looking at the run-time stack in this example. For a complete example of what happens to all bits of memory, see below.
The next thing that happens is that we finish executing the method PayFees:
Note that the frame for the PayFees method has been deleted, and the line about to be executed is now line 3 of the method main. Since line 3 of main is the end of the method main, the frame for main is therefore deleted and the program ends.
This is roughly how the run-time stack works.