What is the Java Virtual Machine?
By Ernest Rider, OCI Senior Software Engineer
February 2000
The Java Virtual Machine (JVM) is the Sun Microsystems specification of a (emulated) virtual 32-bit processor that directly supports the Java programming language.
The JVM executes operating-system and hardware-independent binary format bytecodes (virtual machine instructions). JVMs can currently execute 254 different types of bytecode, each of which is exactly one byte (8 bits). Bytecode 186 is unused.
What do bytecodes look like?
Well, they are all in binary format as we might expect. Bytecodes exists to manipulate primitive types (byte, short, int, long, char, float, double, boolean, and JVM internal return address types) and reference types (objects/arrays).
All bytecodes look like this:
mnemonic <operand1
>, <operand2
>, . . .
where mnemonic can be one of the pseudo codes in the table below:
bytecode |
bytecode (Hex) |
Pseudo Code (Sun's Format) |
bytecode |
bytecode (Hex) |
Pseudo Code (Sun's Format) |
bytecode |
bytecode (Hex) | Pseudo Code (Sun's Format) |
---|---|---|---|---|---|---|---|---|
0 |
(0x00) |
nop |
86 |
(0x56) |
sastore |
171 |
(0xab) |
lookupswitch |
1 |
(0x01) |
aconst_null |
87 |
(0x57) |
pop |
172 |
(0xac) |
ireturn |
2 |
(0x02) |
iconst_m1 |
88 |
(0x58) |
pop2 |
173 |
(0xad) |
lreturn |
3 |
(0x03) |
iconst_0 |
89 |
(0x59) |
dup |
174 |
(0xae) |
freturn |
4 |
(0x04) |
iconst_1 |
90 |
(0x5a) |
dup_x1 |
175 |
(0xaf) |
dreturn |
5 |
(0x05) |
iconst_2 |
91 |
(0x5b) |
dup_x2 |
176 |
(0xb0) |
areturn |
6 |
(0x06) |
iconst_3 |
92 |
(0x5c) |
dup2 |
177 |
(0xb1) |
return |
7 |
(0x07) |
iconst_4 |
93 |
(0x5d) |
dup2_x1 |
178 |
(0xb2) |
getstatic |
8 |
(0x08) |
iconst_5 |
94 |
(0x5e) |
dup2_x2 |
179 |
(0xb3) |
putstatic |
9 |
(0x09) |
lconst_0 |
95 |
(0x5f) |
swap |
180 |
(0xb4) |
getfield |
10 |
(0x0a) |
lconst_1 |
96 |
(0x60) |
iadd |
181 |
(0xb5) |
putfield |
11 |
(0x0b) |
fconst_0 |
97 |
(0x61) |
ladd |
182 |
(0xb6) |
invokevirtual |
12 |
(0x0c) |
fconst_1 |
98 |
(0x62) |
fadd |
183 |
(0xb7) |
invokespecial |
13 |
(0x0d) |
fconst_2 |
99 |
(0x63) |
dadd |
184 |
(0xb8) |
invokestatic |
14 |
(0x0e) |
dconst_0 |
100 |
(0x64) |
isub |
185 |
(0xb9) |
invokeinterface |
15 |
(0x0f) |
dconst_1 |
101 |
(0x65) |
lsub |
186 |
(0xba) |
xxxunusedxxx |
16 |
(0x10) |
bipush |
102 |
(0x66) |
fsub |
187 |
(0xbb) |
new |
17 |
(0x11) |
sipush |
103 |
(0x67) |
dsub |
188 |
(0xbc) |
newarray |
18 |
(0x12) |
ldc |
104 |
(0x68) |
imul |
189 |
(0xbd) |
anewarray |
19 |
(0x13) |
ldc_w |
105 |
(0x69) |
lmul |
190 |
(0xbe) |
arraylength |
20 |
(0x14) |
ldc2_w |
106 |
(0x6a) |
fmul |
191 |
(0xbf) |
athrow |
21 |
(0x15) |
iload |
107 |
(0x6b) |
dmul |
192 |
(0xc0) |
checkcast |
22 |
(0x16) |
lload |
108 |
(0x6c) |
idiv |
193 |
(0xc1) |
instanceof |
23 |
(0x17) |
fload |
109 |
(0x6d) |
ldiv |
194 |
(0xc2) |
monitorenter |
24 |
(0x18) |
dload |
100 |
(0x6e) |
fdiv |
195 |
(0xc3) |
monitorexit |
25 |
(0x19) |
aload |
111 |
(0x6f) |
ddiv |
196 |
(0xc4) |
wide |
26 |
(0x1a) |
iload_0 |
112 |
(0x70) |
irem |
197 |
(0xc5) |
multianewarray |
27 |
(0x1b) |
iload_1 |
113 |
(0x71) |
lrem |
198 |
(0xc6) |
ifnull |
28 |
(0x1c) |
iload_2 |
114 |
(0x72) |
frem |
199 |
(0xc7) |
ifnonnull |
29 |
(0x1d) |
iload_3 |
115 |
(0x73) |
drem |
200 |
(0xc8) |
goto_w |
30 |
(0x1e) |
lload_0 |
116 |
(0x74) |
ineg |
201 |
(0xc9) |
jsr_w |
31 |
(0x1f) |
lload_1 |
117 |
(0x75) |
lneg |
203 |
(0xcb) |
ldc_quick |
32 |
(0x20) |
lload_2 |
118 |
(0x76) |
fneg |
204 |
(0xcc) |
ldc_w_quick |
33 |
(0x21) |
lload_3 |
119 |
(0x77) |
dneg |
205 |
(0xcd) |
ldc2_w_quick |
34 |
(0x22) |
fload_0 |
120 |
(0x78) |
ishl |
206 |
(0xce) |
getfield_quick |
35 |
(0x23) |
fload_1 |
121 |
(0x79) |
lshl |
207 |
(0xcf) |
putfield_quick |
36 |
(0x24) |
fload_2 |
122 |
(0x7a) |
ishr |
208 |
(0xd0) |
getfield2_quick |
37 |
(0x25) |
fload_3 |
123 |
(0x7b) |
lshr |
209 |
(0xd1) |
putfield2_quick |
38 |
(0x26) |
dload_0 |
124 |
(0x7c) |
iushr |
210 |
(0xd2) |
getstatic_quick |
39 |
(0x27) |
dload_1 |
125 |
(0x7d) |
lushr |
211 |
(0xd3) |
putstatic_quick |
40 |
(0x28) |
dload_2 |
126 |
(0x7e) |
iand |
212 |
(0xd4) |
getstatic2_quick |
41 |
(0x29) |
dload_3 |
127 |
(0x7f) |
land |
213 |
(0xd5) |
putstatic2_quick |
42 |
(0x2a) |
aload_0 |
128 |
(0x80) |
ior |
214 |
(0xd6) |
invokevirtual_quick |
43 |
(0x2b) |
aload_1 |
129 |
(0x81) |
lor |
215 |
(0xd7) |
invokenonvirtual_quick |
44 |
(0x2c) |
aload_2 |
130 |
(0x82) |
ixor |
216 |
(0xd8) |
invokesuper_quick |
45 |
(0x2d) |
aload_3 |
131 |
(0x83) |
lxor |
217 |
(0xd9) |
invokestatic_quick |
46 |
(0x2e) |
iaload |
132 |
(0x84) |
iinc |
218 |
(0xda) |
invokeinterface_quick |
47 |
(0x2f) |
laload |
133 |
(0x85) |
i2l |
219 |
(0xdb) |
invokevirtualobject_quick |
48 |
(0x30) |
faload |
134 |
(0x86) |
i2f |
221 |
(0xdd) |
new_quick |
49 |
(0x31) |
daload |
135 |
(0x87) |
i2d |
222 |
(0xde) |
anewarray_quick |
50 |
(0x32) |
aaload |
136 |
(0x88) |
l2i |
223 |
(0xdf) |
multianewarray_quick |
51 |
(0x33) |
baload |
137 |
(0x89) |
l2f |
224 |
(0xe0) |
checkcast_quick |
52 |
(0x34) |
caload |
138 |
(0x8a) |
l2d |
225 |
(0xe1) |
instanceof_quick |
53 |
(0x35) |
saload |
139 |
(0x8b) |
f2i |
226 |
(0xe2) |
invokevirtual_quick_w |
54 |
(0x36) |
istore |
140 |
(0x8c) |
f2l |
227 |
(0xe3) |
getfield_quick_w |
55 |
(0x37) |
lstore |
141 |
(0x8d) |
f2d |
228 |
(0xe4) |
putfield_quick_w |
56 |
(0x38) |
fstore |
142 |
(0x8e) |
d2i |
202 |
(0xca) |
breakpoint |
57 |
(0x39) |
dstore |
143 |
(0x8f) |
d2l |
254 |
(0xfe) |
impdep1 |
58 |
(0x3a) |
astore |
144 |
(0x90) |
d2f |
255 |
(0xff) |
impdep2 |
59 |
(0x3b) |
istore_0 |
145 |
(0x91) |
i2b |
|
|
|
60 |
(0x3c) |
istore_1 |
146 |
(0x92) |
i2c |
|
|
|
61 |
(0x3d) |
istore_2 |
147 |
(0x93) |
i2s |
|
|
|
62 |
(0x3e) |
istore_3 |
148 |
(0x94) |
lcmp |
|
|
|
63 |
(0x3f) |
lstore_0 |
149 |
(0x95) |
fcmpl |
|
|
|
64 |
(0x40) |
lstore_1 |
150 |
(0x96) |
fcmpg |
|
|
|
65 |
(0x41) |
lstore_2 |
151 |
(0x97) |
dcmpl |
|
|
|
66 |
(0x42) |
lstore_3 |
152 |
(0x98) |
dcmpg |
|
|
|
67 |
(0x43) |
fstore_0 |
153 |
(0x99) |
ifeq |
|
|
|
68 |
(0x44) |
fstore_1 |
154 |
(0x9a) |
ifne |
|
|
|
69 |
(0x45) |
fstore_2 |
155 |
(0x9b) |
iflt |
|
|
|
70 |
(0x46) |
fstore_3 |
156 |
(0x9c) |
ifge |
|
|
|
71 |
(0x47) |
dstore_0 |
157 |
(0x9d) |
ifgt |
|
|
|
72 |
(0x48) |
dstore_1 |
158 |
(0x9e) |
ifle |
|
|
|
73 |
(0x49) |
dstore_2 |
159 |
(0x9f) |
if_icmpeq |
|
|
|
74 |
(0x4a) |
dstore_3 |
160 |
(0xa0) |
if_icmpne |
|
|
|
75 |
(0x4b) |
astore_0 |
161 |
(0xa1) |
if_icmplt |
|
|
|
76 |
(0x4c) |
astore_1 |
162 |
(0xa2) |
if_icmpge |
|
|
|
77 |
(0x4d) |
astore_2 |
163 |
(0xa3) |
if_icmpgt |
|
|
|
78 |
(0x4e) |
astore_3 |
164 |
(0xa4) |
if_icmple |
|
|
|
79 |
(0x4f) |
iastore |
165 |
(0xa5) |
if_acmpeq |
|
|
|
80 |
(0x50) |
lastore |
166 |
(0xa6) |
if_acmpne |
|
|
|
81 |
(0x51) |
fastore |
167 |
(0xa7) |
goto |
|
|
|
82 |
(0x52) |
dastore |
168 |
(0xa8) |
jsr |
|
|
|
83 |
(0x53) |
aastore |
169 |
(0xa9) |
ret |
|
|
|
84 |
(0x54) |
bastore |
170 |
(0xaa) |
tableswitch |
|
|
|
85 |
(0x55) |
castore |
|
|
|
|
|
|
Can other languages produce Java bytecode?
In short, yes!
-
Pascal: University. Vrije Universiteit, Amsterdam, Netherlands.
Does this mean we don't have to write in Java to get "bytecode" portability?
Well that's one for debate. All I can say is that you get the wealth of a pure object orientated language in Java, some of the best minds fixing and enhancing the technology, and some really nice frameworks that other languages are struggling to match.
In essence, the Java language embodies a design thought process that is conducive to good OOP practices.
What makes up a JVM?
1. A fetch, decode, execute module
This is really the heart of a JVM implementation. It mimics a "real" machine's fetch, decode, and execute cycles.
This module will usually consist of a 32-bit program counter (PC). All other registers are not stipulated by the JVM specification, since many different architectures have different register capabilities that can be best exploited without adhering to a strict architecture.
2. A stack per thread
The JVM stack is a last-in-first-out (LIFO) stack that stores frames. A frame is a local block workspace, usually directly related to code that appears between "{"...."}" block specifiers.
In a REAL machine, a frame is analogous to a set of local block variables from the last base pointer (BP) to the current stack pointer (SP).
During a simulated multitasking (single processor) fetch, decode, execute cycle, the JVM can switch between stacks, saving and restoring the PC and any implementation registers. In some machines, this is done preemptively (forced), and in others, each must yield to another.
3. Heap
The Java heap is shared across all threads and contains dynamic object references, including dynamically constructed objects/methods.
4. Method area
All compiled class and object methods are stored in the method area.
(Dynamic objects created at run time are stored in the heap and are exposed to the garbage collector.)
5. Constant pool
The constant pool holds all the constants referenced by the system. Some constants may be propagated through the bytecode in implementations.
6. Native method stack
This is a stack for which native methods can accept parameters from, and return parameters to, the JVM.
Some JVMs parse parameters using a 3rd party Native Language framework, such as RNI/COM in the Microsoft VM.
7. Garbage collector
This is a thread or process that determines whether heap references have lost their parent/creator object and are thus marked dirty for removal. The garbage collector is quite efficient, despite the absence of a C++-like "delete" operator.
8. bytecode verifier
The bytecode verification process is perhaps the most sophisticated part a JVM implementation. It addresses the concerns that bytecodes may do damage to a system by running amok on a given hardware architecture.
Sun's JVM verifies that bytecodes are well-formed before executing them. This involves making sure all exceptions are caught, no overflows exist, and more. Some JVMs check while executing bytecodes, which can be expensive for performance.
9. Other advanced features
As Java compiler technology improves, the separation of the JVM and the "real" machine gets narrower and narrower. Just-In-Time (JIT) compilation has become a key technology for improving JVM performance.
A typical JIT will pre-optimise method bytecodes into native blocks ready to run on demand. Some JIT's use the native method stack to invoke native methods.
Implicit parallel computing support through the Java threading model is also making positive gains.
It is the author's opinion that through such research and development, some JVMs may be able to operate as real-time systems, despite the use of bytecode binaries.
Show us a program in bytecodes!
Example:
- public class Person {
-
- String name = "Unknown";
-
- public void setName(String n) {
- name = n;
- }
-
- public String getName() {
- return(name);
- }
-
- public static void main(String args[]) {
- Person p = new Person();
- p.setName(args[0]);
- System.out.println("The name of the person entered was "+p.getName()+".");
- }
- }
-
Using the command line Java decompiler tool, javap
, we can get the bytecode in pseudo ops (-c
) and the line numbers in the heap where they are stored (-l
).
C:\>javac Person.java
C:\>javap -c -l Person
- //Compiled from Person.java
- public synchronized class Person extends java.lang.Object
- /* ACC_SUPER bit set */
- {
- java.lang.String name;
- public void setName(java.lang.String);
- public java.lang.String getName();
- public static void main(java.lang.String[]);
- public Person();
- }
-
- Method void setName(java.lang.String)
- 0 aload_0
- 1 aload_1
- 2 putfield #14 -Field java.lang.String name-
- 5 return
-
- Line numbers for method void setName(java.lang.String)
- line 6: 0
- line 5: 5
-
- Method java.lang.String getName()
- 0 aload_0
- 1 getfield #14 -Field java.lang.String name-
- 4 areturn
-
- Line numbers for method java.lang.String getName()
- line 10: 0
-
- Method void main(java.lang.String[])
- 0 new #4 -Class Person-
- 3 dup
- 4 invokespecial #9 -Method Person()-
- 7 astore_1
- 8 aload_1
- 9 aload_0
- 10 iconst_0
- 11 aaload
- 12 invokevirtual #17 -Method void setName(java.lang.String)-
- 15 getstatic #15 -Field java.io.PrintStream out-
- 18 new #7 -Class java.lang.StringBuffer-
- 21 dup
- 22 ldc #2 -String "The name of the person entered was "-
- 24 invokespecial #11 -Method java.lang.StringBuffer(java.lang.String)-
- 27 aload_1
- 28 invokevirtual #13 -Method java.lang.String getName()-
- 31 invokevirtual #12 -Method java.lang.StringBuffer append(java.lang.String)-
- 34 ldc #1 -String "."-
- 36 invokevirtual #12 -Method java.lang.StringBuffer append(java.lang.String)-
- 39 invokevirtual #18 -Method java.lang.String toString()-
- 42 invokevirtual #16 -Method void println(java.lang.String)-
- 45 return
-
- Line numbers for method void main(java.lang.String[])
- line 14: 0
- line 15: 8
- line 16: 15
- line 13: 45
-
- Method Person()
- 0 aload_0
- 1 invokespecial #10 -Method java.lang.Object()-
- 4 aload_0
- 5 ldc #3 -String "Unknown"-
- 7 putfield #14 -Field java.lang.String name-
- 10 return
-
- Line numbers for method Person()
- line 1: 0
- line 3: 4
- line 1: 10
-
How safe is my Java bytecode from Reverse Engineering?
Not very safe, out-of-the-box. However, many products exist to make decompiling exponentially harder (closer to near impossible) through obfuscation.
(obfuscate. – To make so confused or opaque as to be difficult to perceive or understand)
How do I measure the performance of a JVM?
Just like testing "real" machine performance, JVM performance is an open-ended battle.
Claim and counter claim over who has the best JVM could go on forever. This goes to show that computer people are very passionate when it comes to issues such as performance.
Performance means many things to many different applications.
So how do we meaningfully test performance?
Well, first you must accept that the 90/10 rule applies in general; i.e., 90% of your time is spent working on 10% of the code for a given unit.
Then you must construct applications that typify your purpose or goal and perform some real-time analysis. Using a Java thread to time yourself is not good enough, as JVMs are – for the most part – non-real time.
Fortunately, the confusion over testing JVM performance has been confronted by the Java community. In an effort to gain a useful comparison of JVMs, a set of application performance benchmarks have been adopted (sometimes to suit the implementer).
Some useful ones:
Name | Proprietor | Description |
CaffieneMark 3.0 | Pendragon Software | |
SpecJVM98 | Spec | http://www.spec.org/ |
JMark | ZDNET | http://www.zdnet.com/zdbop/jmark/jmark.html |
VolanoMark | volano | http://www.volano.com/benchmarks.html |
It is important that when using these tools, the results have relevance to your needs; for instance, testing thread performance may not give you great graphics.
What does the future of the JVM look like?
The future the JVM centers quite firmly around increasing its performance on any given architecture.
Researchers and developers will continue to peel away at its virtual-ness and add more and more real machine code while keeping the bytecode source intact.
Meanwhile, many different architectures and frameworks will be added to aid the industry in making open technology choices.
Finally, hardware architectures that are bytecode compatible are going to break performance ground earlier than software, but the holy grail is the hybrid interpreted/native compiler. This will continue to be developed and influence the ever-increasing demand on performance software technology.
Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.