Although the Java system has many nice features, it also has its share of blemishes. Some of these blemishes make the Java system hard to implement, while some violate the all-important Principle of Least Astonishment. This principle states that:
A system and its commands should behave the way most people would predict, that is, the system should operate with "least astonishment."
Finalizers in Java may be run in any thread at any time. For example, it is legal for the VM to run a finalizer in a thread that already holds locks. This can easily happen if a call to new causes a garbage collection. This may cause problems with Java's otherwise intuitive mutual exclusion model. Consider:
At this point there are two logically different threads of control (the original thread of control and the thread of control in the finalizer - two unrelated activities) in the same monitor. This behavior is allowed by the language spec, and Sun's reference VM (JDK 1.0.2) demonstrates this behavior! (Note: If monitors were not recursive there would still be a problem. The finalizer would block and the thread would be deadlocked.)
for(;;) { try { return 1; } finally { break; } } return -1;What value does this snippet return? -1. Surprised? How about this little gem:
while (true) { try { return; } finally { continue; } } /* NOT REACHED */
This snippet is an infinite loop! Both of these are clear violations of the Principle of Least Astonishment.
The language spec says that monitors should be released if a synchronized block is left through an exception. However, the bytecodes generated by javac to ensure this contain a race condition that a simple example demonstrates.
Sun's Java compiler sometimes optimizes away if statements when the compiler determines that the condition will always evaluate to true. javac fails to take into account the fact that subroutines called during evaluation of the conditional may have side effects such as throwing exceptions or assigning to class variables. This causes javac to incorrectly optimize away calls to these subroutines. A simple example demonstrates this.
Several API calls need information about the call stack. For example, java.lang.Class.newInstance() needs to know the package of the calling method to properly do security checks. This information may not readily available in non-interpreted implementations.
The API spec allows users to specify that all finalizers will be run before the system exits. This leads to problems about knowing when to exit. For example:
Because it is possible, and in fact necessary (because of subclassing) to refer to classes by name, the Java API should specify what happens when a program attempts to load multiple definitions of the same class. It doesn't.
What happens in the current system? JDK 1.0.2 allows a program to load multiple definitions of the same class. Calls to Class.forName() return the definition that was loaded first. Surprising? You bet.
This means an implementer of the Java VM must use some magic glue to associate an instance of java.lang.Class with a class (rather than simply using a field in the instance). Since java.lang.Class is declared final, it is not even possible to create a subclass that has the necessary additional fields.
BitSet.set(bitnum) will throw an ArrayIndexOutOfBoundsException if (bitnum % 64 == 0) and the BitSet has to grow to set the appropriate bit. Calling (new BitSet()).set(384) will repeat this behavior.
The Java Virtual Machine specification allows monitorenter and monitorexit to execute in arbitrary order, but monitors are supposed to properly nest. For example:
aload_1 monitorenter aload_2 monitorenter aload_1 monitorexitis a legal, verifiable bytecode sequence, even though it is inconsistent with the traditional notion of monitors. The monitorenter and monitorexit bytecodes implement a flavor of recursive locks, not monitors.
(long)(double)(0.0/0.0) produces MaxLong instead of 0.0
Although Java provides a way to send exceptions to other threads via the Thread.stop() in a way analogous to UNIX signals, Sun's interpreter will only deliver one such exception to a given thread. For example, in:
Thread t = new myThread(); t.start(); t.stop(new NullPointerException()); t.stop(new myException());the thread t will be sent a NullPointerException but not a myException. This behavior is observed in Sun's Java interpreter but not documented (as far as we know).