Sponsored Link •
Phase one: Internal checks
During phase one, the class-file verifier checks everything that's possible to check in a class file by looking at only the class file itself (without examining any other classes or interfaces). Phase one of the class-file verifier makes sure the imported class file is properly formed, internally consistent, adheres to the constraints of the Java programming language, and contains bytecodes that will be safe for the Java virtual machine to execute. If the class-file verifier finds that any of these are not true, it throws an error, and the class file is never used by the program.
Checking format and internal consistency
Besides verifying the integrity of the bytecodes, the verifier performs many checks for proper class file format and internal consistency during phase one. For example, every class file must start with the same four bytes, the magic number:
0xCAFEBABE. The purpose
of magic numbers is to make it easy for file parsers to recognize a
certain type of file. Thus, the first thing a class-file verifier
likely checks is that the imported file does indeed begin with
The class-file verifier also checks to make sure the class file is neither truncated nor enhanced with extra trailing bytes. Although different class files can be different lengths, each individual component contained inside a class file indicates its length as well as its type. The verifier can use the component types and lengths to determine the correct total length for each individual class file. In this way, it can verify that the imported file has a length consistent with its internal contents.
The verifier also looks at individual components to make sure they are well-formed instances of their type of component. For example, a method descriptor (the method's return type and the number and types of its parameters) is stored in the class file as a string that must adhere to a certain context-free grammar. One of the checks the verifier performs on individual components is to make sure each method descriptor is a well-formed string of the appropriate grammar.
In addition, the class-file verifier checks that the class itself
adheres to certain constraints placed on it by the specification of the
Java programming language. For example, the verifier enforces the rule
that all classes, except class
Object, must have a
superclass. Thus, the class-file verifier checks at runtime some of the
Java language rules that should have been enforced at compile-time.
Because the verifier has no way of knowing if the class file was
generated by a benevolent, bug-free compiler, it checks each class file
to make sure the rules are followed.
The bytecode verifier
Once the class-file verifier has successfully completed the checks for proper format and internal consistency, it turns its attention to the bytecodes. During this part of phase one, which is commonly called the "bytecode verifier," the Java virtual machine performs a data-flow analysis on the streams of bytecodes that represent the methods of the class. To understand the bytecode verifier, you need to understand a bit about bytecodes and frames.
The bytecode streams that represent Java methods are a series of one-byte instructions, called opcodes, each of which may be followed by one or more operands. The operands supply extra data needed by the Java virtual machine to execute the opcode instruction. The activity of executing bytecodes, one opcode after another, constitutes a thread of execution inside the Java virtual machine. Each thread is awarded its own Java stack, which is made up of discrete frames. Each method invocation gets its own frame, a section of memory where it stores, among other things, local variables and intermediate results of computation. The part of the frame in which a method stores intermediate results is called the method's operand stack. An opcode and its (optional) operands may refer to the data stored on the operand stack or in the local variables of the method's frame. Thus, the virtual machine may use data on the operand stack, in the local variables, or both, in addition to any data stored as operands following an opcode when it executes the opcode.
The bytecode verifier does a great deal of checking. It checks to make sure that no matter what path of execution is taken to get to a certain opcode in the bytecode stream, the operand stack always contains the same number and types of items. It checks to make sure no local variable is accessed before it is known to contain a proper value. It checks that fields of the class are always assigned values of the proper type, and that methods of the class are always invoked with the correct number and types of arguments. The bytecode verifier also checks to make sure that each opcode is valid, that each opcode has valid operands, and that for each opcode, values of the proper type are in the local variables and on the operand stack. These are just a few of the many checks performed by the bytecode verifier, which is able, through all its checking, to verify that a stream of bytecodes is safe for the JVM to execute.