can run multiple operating systems at the same time, since the operating system now runs on
a virtual rather than physical machine. These virtual machines are really virtual hardware en-
vironments. We programmers interact with these virtual machines in the same way that we
interact with all pure hardware environments. Which is to say, not at all, except through the
mediation of an operating system, which really provides us with a set of abstractions that we
can use directly.
The Java virtual machine is a similar sort of abstraction, but done at a much higher level.
Rather than presenting a single set of interfaces and abstractions representing hardware to an
operating system, the JVM presents an abstraction of both the hardware and the operating sys-
tem to the programmer. This means that the virtual machine presents more than an abstract
view of some piece of hardware. All of the functions of the computer, from the filesystem
to networking to threads, are presented as part of the programming environment. This makes
the JVM and its associated environment much more complex than the virtual machines that
are mimicking hardware, but also means that the programmer deals directly with one set of
abstractions on all of the implementations of the virtual machine. A JVM on one operating
system should look to the programmer just like a JVM on a completely different operating
system. If it doesn't, it isn't because your code isn't using the operating system correctly; it is
because of a flaw in the JVM. It's someone else's problem.
The difference in the abstraction level presented by the JVM and that presented by the low-
level VMs is in part a function of the intended use for those virtual machines. The Java virtual
machine was always meant to be a development and runtime platform for new code, written
in the Java language. Virtual machines such as Xen or VMWare are meant to be platforms that
allow system administrators to run existing code, written to existing operating systems, in a
way that lets the system administrator leverage the hardware in a machine room in an efficient
manner. Running those existing applications requires running the operating system on which
those existing applications were originally targeted, so a low-level abstraction below those op-
erating systems makes sense. But for the production of new code, the JVM makes sense, since
it papers over the differences between the various operating systems.
The mechanism for this splits the work of generating object code between the compiler and
the JVM. The Java compiler translates the source code into a file of bytecodes, which are then
run within the JVM. Each source file will generate at least a single bytecode file. [ 18 ] The JVM
is then responsible for loading that file, and the files that it references, into the virtual ma-
chine, which will then interpret and run the bytecodes. Early JVMs acted as an interpreter for
the bytecodes, translating those codes into whatever underlying instructions were required by
the basic hardware and operating system.