Java code does not declare any local variables, but we have one here in the bytecode: the hint why is given
by the name, args . The arguments for a method also count as local variables: they just happen to be local
variables that are set when the method is invoked.
The last section is the “Code” section. This is where the body of the method is defined. The numbers
are offsets, which tells you how long your method is within bytecode. In our case, the only thing that the
method is going to do is return, so there is only the single instruction at index 0: return . The locals value is
the number of local variables used by the method, and the args_size is the size of the arguments. This just
leaves stack=0 to explain.
Java bytecode is a stack-based language. There are more sophisticated stack-based languages (such as
Factor), so do not base your opinion of all stack-based languages on your experience with Java bytecode.
Nonetheless, the fact that Java bytecode is a stack-based language means that each instruction acts on a
stack. Everything you do in bytecode is either a non-operation (“nop”), pushing elements on a stack, or
popping elements off. Therefore, every behavior in Java (e.g., referring to a variable) corresponds to some
sequence of stack operations (e.g., pushing the variable's value onto the stack). There can be side effects,
most notably in flow control: for instance, when you invoke a method, you are popping elements off the
stack to invoke the method, and when the method returns, it may push an element back onto the stack.
In bytecode, each method has to declare the maximum depth of its particular stack. This allows the
runtime to optimize allocation for the stack: it can allocate a single block of memory once when it enters the
method, instead of potentially needing to allocate more when elements are pushed onto the stack. In the
case of our main method, we do not need any space on the stack since we are simply going to return void.
Therefore, our bytecode tells the runtime that we need a stack size of 0 by setting stack=0 . And that is it for
our main method. The constructor (lines 37-49), however, is somewhat more complicated:
line 1: 0
Start Length Slot Name Signature
0 5 0 this LListing1;
stack=1, locals=1, args_size=1
1: invokespecial #1 // Method java/lang/Object."<init>":()V
The first thing to note is that the constructor is not a static method: there is no ACC_STATIC flag set. The
constructor is an instance method that is invoked on an instance of the class. Since it is an instance method,
it has access to the variable, which is declared in the LocalVariableTable . Its type is LListing1; —the two
“L” characters at the front is not a typo. Again, the type name is declared between “L” and “;”, which gives our
initial duplication. The existence of this again gives us our values locals=1 and args_size=1 , even though
we have no arguments declared in our descriptor . It is also interesting to note that Java does not want you
to think of constructors as having a return type, but as far as the bytecode method descriptor is concerned,
the constructor returns void.
The maximum stack size for our constructor is 1, and that is because the constructor is going to do
something interesting. In the Java language, the first line of the constructor may be a call to this() or
super() (possibly with arguments). Those calls delegate to another constructor. In bytecode, the first
line of a constructor must be a delegation to another constructor: it is optional in the Java language, but
required in the bytecode. Our parent class for Listing1 is the Object class, so the bytecode delegates our
constructor to the Object class super constructor. The bytecode accomplishes this by loading the this local