Machine code isn't just low-level, it is the native language of
the machine; the only language the machine actually understands.
When we say low-level or high-level we are referring to the amount
of abstraction between the source code and the machine code. The
source code must be translated into machine code and the more
closely that code resembles the machine code the more low-level it
is. Assembly is extremely low-level because there is very little in
the way of abstraction; the assembly instructions map 1:1 with the
machine code. C is also low-level because C statements map very
closely to the machine code, but offer a higher level of
abstraction than assembly. Assembly language is machine-dependant
because every type of machine requires its own distinct version of
assembly language whereas C's slightly higher level of abstraction
means it can be used to write non-machine-dependant code, more
portable code. However, the source code must be recompiled for each
machine.
Java, on the other hand, is extremely high-level because the
source code bears no resemblance whatsoever to the machine code.
Indeed, the source code does not even compile to machine code it
compiles to Java byte code suitable for interpretation by the Java
virtual machine. In other words, the source code is compiled
against a non-existent machine, but one that has common
architecture across all platforms that support Java (which is
pretty much everything today). This makes the code extremely
portable because the Java virtual machine handles the low-level
conversion from the Java byte code to the physical machine's native
code; the same byte code can be executed upon any platform without
the need to recompile.