Substrate VM does not support all features of Java to keep the implementation small and concise, and also to allow aggressive ahead-of-time optimizations. This page documents the limitations. If you are not sure if a feature is supported, ask [email protected] so that this list can be updated.
Loading classes with Class.forName()
or subclassing java.lang.ClassLoader
to dynamically generate new bytecodes and load them; relying on classes being unloaded at run time.
Dynamic class loading is not supported, and cannot be supported by our execution model.
During native image generation, we run an aggressive static analysis that requires a closed-world assumption. At this point, we need to know all classes and all bytecodes that are ever reachable. The static analysis finds out which parts are required by the application and performs ahead-of-time compilation for these parts.
Listing methods and fields of a class; invoking methods and accessing fields reflectively; most classes in the package java.lang.reflect
.
Specific kinds of reflection access can be enabled for individual classes, methods and fields by providing one or several configuration files via the -H:ReflectionConfigurationFiles=
option during native image generation. Elements that are not included in a configuration cannot be accessed reflectively. For more details, read our documentation on reflection.
Reflection can be used without restrictions during native image generation, for example in static initializers.
Exceptions that are thrown implicitly by bytecodes, such as: NullPointerException
, ArrayIndexOutOfBoundsException
, ClassCastException
, ArrayStoreException
, ArithmeticException
(division by 0), ...
The only restriction is that implicit exceptions cannot be caught in the same method that they are thrown in, because that would make the control flow within a method too complicated.
Printing Java stack traces via the Throwable.printStackTrace
methods.
The invokedynamic
bytecode and the method handle classes introduced with Java 7.
(Except for special cases like Lambda expressions)
The static analysis and our closed-world assumption require that we know all methods that are called and their call sites, while invokedynamic
can introduce calls at runtime.
The Lambda expressions introduced with Java 8.
Lambda expressions use the invokedynamic
bytecode, but only for the bootstrapping process (which dynamically creates new classes). All the bootstrapping runs during native image generation, so that no reflective or dynamic method invocation is necessary at run time.
The Java Native Interface (JNI) enables Java code to interact with native code and vice versa.
JNI integration must be enabled using -H:+JNI
during native image generation. Individual classes, methods and fields that should be accessible via JNI must be specified in a configuration via the -H:JNIConfigurationFiles=
option. Alternatively, a Feature
can be used to register accessible program elements during the native image build. For more details, read our JNI implementation documentation.
The Substrate VM defines its own native interface, which is much simpler than JNI. It allows calls between Java and C, and access of C data structures from Java code. However, it does not allow access of Java data structures from C code.
Java offers the synchronized
keyword for methods and blocks. Every object and every class has an intrinsic lock. The base class java.lang.Object
provides wait and notify methods for conditional waiting.
Synchronization on objects is generally supported with the exception of arrays of any type. When using java.lang.Class
objects for synchronization, the class object must always be specified as compile-time constant synchronization target, such as in synchronized(Vector.class)
, or implicitly such as for static synchronized
methods. The method Thread.holdsLock()
does not work on class objects.
The implementation of synchronization uses java.util.concurrent.locks
. There are no optimizations such as biased locking that reduce the overhead of synchronized, so code that uses unnecessary synchronization is significantly slower on Substrate VM compared to the Java HotSpot VM.
The Java base class java.lang.Object
defines the method finalize()
. It is called by the garbage collector on an object when garbage collection determines that there are no more references to the object. A subclass overrides the finalize()
method to dispose of system resources or to perform other cleanup.
Finalizers are not supported at all, and there are no plans to ever support it. This means that no finalize()
method will ever be called.
Finalizers are an ancient relict of the early days of Java that are complicated to implement, and have very badly designed semantics. For example, the finalizer can make the object reachable again by storing it in a static field.
Weak references and reference queues
The package java.lang.ref
defines the base class Reference
, as well as subclasses for weak, soft, and phantom references. The object that the reference refers to can be deallocated, in which case the reference is updated to contain the value null. With the help of a ReferenceQueue
, user code can be executed when a reference gets deallocated.
We have our own Feeble References (exposed as java.lang.ref.Reference
) similar to Java's weak references. However, we do not distinguish between weak, soft, and phantom references.
Starting new threads; Support for java.lang.Thread
Nearly full support for java.lang.Thread
. Only long deprecated methods, such as Thread.stop()
, will not be supported. However, we discourage explicit usage of threads. Use higher level constructs from java.util.concurrent
instead.
java.lang.Object.hashCode()
and java.lang.System.identityHashCode()
return a random but fixed-per-object integer value. Successive calls to identityHashCode()
for the same object yield the same result.
Identity hash codes are fully supported. The hash code is stored in an injected int field in all classes that might access it. The identity hash code of hosted objects during native image generation is the same as the identity hash code at run time, so hash maps that are built during native image generation can be used at run time.
Annotations of classes, methods, fields, ... that can be queried at run time.
Annotations on classes, i.e., Class.getAnnotation()
, is fully supported. Annotations are objects in the native image heap, and annotation attributes are fields of these objects. To use annotations on methods and fields, make them accessible (as java.lang.reflect.Method
and java.lang.reflect.Field
) via reflection configuration file (see Reflection above).
Static class initialization blocks, pre-initialized static variables.
All static class initialization is done during native image construction. This has the advantage that possibly expensive initializations do not slow down the startup, and large static data structures are pre-allocated. However, it also means that instance-specific initializations (such as opening and initializing native libraries, opening files or socket connections, ...) cannot be done in static initializers.
Static initializers run in the host VM during native image generation, and it is not possible to prevent or intercept that.
Write your own initialization methods and call them explicitly from your main entry point.
Management and debugging interfaces that Java offers.
The JVM shutdown hooks are implemented as threads that are invoked on the VM shutdown.
We use the Runnable
interface and invoke all shutdown hooks in the main thread. Errors during shutdown are reported similarly to the multi-threaded implementation.