Technical Article
New JDK 7 Feature: Support for Dynamically Typed Languages in the Java Virtual Machine
By Ed Ort, July 2009
DaVinci Helicopter - This article describes a new feature provided in JDK 7: support for dynamically typed languages in the Java virtual machine (JVM). This feature, which implements JSR 292: Supporting Dynamically Typed Languages on the Java Platform, is the logical follow-on to JSR 223: Scripting for the Java Platform. Support for JSR 223 was provided as part of Java SE 6 and implemented in JDK 6.
With the addition of support for JSR 292 in JDK 7, dynamically typed languages should run faster in the JVM than they do today. A key part of this support is the addition of a new Java bytecode, invokedynamic
, for method invocation, and an accompanying linkage mechanism that involves a new construct called a method handle. These features enable implementers of compilers for dynamically typed languages, that is, the people who develop compilers for languages such as JRuby and Jython, to generate bytecode that runs extremely fast in the JVM.
This should increase the variety and quality of dynamically typed languages that run in the JVM. Application developers should see more of their favorite dynamically typed languages available in the Java ecosystem. Also, these features should boost the performance of code generated by dynamically typed language compilers that already run in the JVM. For example, the JRuby compiler generates bytecode that performs well in the JVM, but the JRuby bytecode will run even faster when the JRuby compiler is modified to use the invokedynamic
bytecode and method handles.
Support for JSR 292 in JDK 7 will enable developers of compilers for dynamically typed languages to generate bytecode that runs extremely fast in the JVM.
The JSR 292 Expert Group is also investigating support for interface injection, the ability to modify classes at runtime so that they can implement new interfaces -- this is a feature that is common in dynamically typed languages.
Another stimulus for new languages on the JVM is the Da Vinci Machine Project, an OpenJDK community effort. The project's mission is to extend the JVM so that it supports languages other than Java, especially dynamic languages. The project hosts a number of subprojects. One of these is implementing the invokedynamic
bytecode. Other Da Vinci Machine subprojects are implementing MethodHandle
support and interface injection. Some other extensions to the JVM developed in Da Vinci Machine subprojects might be incorporated into future versions of the JDK.
Contents
Dynamically Typed Languages and the JVM
Ask a developer what the JVM is and he or she is likely to say that it's a program that executes Java programs translated into machine-independent bytecode. That answer is true but not complete. The JVM does execute Java programs translated into bytecode. In fact, the execution of machine-independent bytecode makes the JVM the cornerstone of the Java platform. The Java Virtual Machine Specification , the bible of the JVM, underscores the JVM's importance as follows:
It is the component of the technology responsible for its hardware- and operating-system independence, the small size of its compiled code, and its ability to protect users from malicious programs.
However, the JVM is not limited to handling translated Java programs. The Java Virtual Machine Specification also states:
The Java virtual machine knows nothing of the Java programming language, only of a particular binary format, theclass
file format. Aclass
file contains Java virtual machine instructions (or bytecodes) and a symbol table, as well as other ancillary information. For the sake of security, the Java virtual machine imposes strong format and structural constraints on the code in aclass
file. However, any language with functionality that can be expressed in terms of a validclass
file can be hosted by the Java virtual machine. Attracted by a generally available, machine-independent platform, implementers of other languages are turning to the Java virtual machine as a delivery vehicle for their languages.
The JVM has been host to a growing number of languages. Increasingly, JVM implementations of dynamic languages are becoming available. |
Over the years, the JVM has been host to a growing number of languages, anywhere from Armed Bear Common Lisp, an implementation of the Common LISP language, to Yoix, a general purpose scripting language. Increasingly, JVM implementations of dynamic languages are becoming available, such as JRuby, an implementation of the Ruby programming language, Jython, an implementation of the Python programming language, and the Groovy scripting language.
For many application developers, the growing list of JVM-hosted dynamic languages is great news. The flexibility that dynamic languages offer, especially scripting languages, makes these languages particularly attractive for application prototyping and experimentation as well as for applications that evolve rapidly. This flexibility stems from dynamic typing. A language that is dynamically typed verifies at runtime that the values in an application conform to expected types. By comparison, a statically typed language such as the Java programming language does most of its type checking at compile time, checking the types of variables, not values. The Java language also allows some dynamic type checking of values, especially receivers of virtual or interface method calls. But even these calls require a static type for the receiver.
Dynamic typing is often more flexible than static typing because it allows programs to generate or configure types based on runtime data. In addition, dynamically typed languages have more permissive type matching rules, and can perform many type conversions automatically. These behaviors tend to help developers create applications more quickly than if they code in a statically typed language. However, programs written in languages that are statically typed often execute more efficiently. In addition, static typing can eliminate many errors at compile time. The downside of static typing is that it could reject programs at compile time that otherwise might execute correctly.
Couple the flexibility inherent in dynamic typing with the execution efficiency of the JVM — an efficiency gained from the contributions of many leading-edge engineers with years of experience — and it's easy to understand why JVM support for dynamically typed languages is attractive to creators of dynamic programming languages as well as to application developers who build applications in these languages.
JSR 223 — A First Step in Dynamic Language Support
The first step in bringing dynamic languages to the JVM was JSR 223: Scripting for the Java Platform, a specification that defined an API for accessing Java code from code written in a dynamic scripting language. JSR 223 also specified a framework for hosting scripting engines in Java applications. A scripting engine is a program that compiles or interprets scripting code and then executes it. This specification and its implementation made it much easier to create applications that include both Java code and scripting code.
JSR 223 was included in Java SE 6 and implemented in JDK 6. In addition, JDK 6 included the Rhino scripting engine, an implementation of the JavaScript scripting language. Sun also initiated a scripting project, whose primary goal is to stimulate the community to build additional scripting engines for use in Java applications. More than 20 scripting engines have already been created in this project, any of which can run in the JVM.
The Problem for Dynamically Typed Languages
Although support for JSR 223 has stimulated the development of scripting engines for the JVM, developers of these scripting engines have faced a troublesome obstacle. When developers write engines for dynamically typed languages that run in the JVM, they have to satisfy the requirements of the Java bytecode that the JVM executes. Until now, that bytecode has been designed exclusively for statically typed languages. This design has been a pain point for script engine developers when generating bytecodes for method invocations.
Meeting JVM requirements has been a pain point for dynamic language implementers when generating bytecodes for method invocations. |
Bytecode Requirements for Method Invocation
Recall that a statically typed language does its type checking at compile time. What this means for a method invocation is that the compiler, and the bytecode it generates, needs to know the type of the value returned by the method as well as the types of any receiver or parameters specified in the call.
Consider the following Java code snippet:
String s = "Hello World";
System.out.println(s);
Notice that the type of parameter to the method is known. It's a String
. The println()
method doesn't return a value, but if the code example did call a method that does return a value, the type of the return value would need to be specified. The receiver of the call, System.out
, also has a statically known type, which happens to be java.io.PrintStream
.
Armed with the needed type information, the javac compiler can generate the appropriate bytecode instructions:
ldc #2
astore_1
getstatic #3
aload_1
invokevirtual #4
If you're not familiar with bytecode, the important thing to understand is that a lot of the bytecode execution in the JVM involves operations on values in an operand stack. The operand stack is the virtual machine equivalent of the hardware registers in a real machine. Most of the bytecode instructs the JVM to push a value in a local value onto the operand stack, pop a value off the stack into a local variable, duplicate a value in the stack, swap values in the stack, or execute operations that produce or consume values.
For example, here's a commented version of the bytecode:
ldc #2 // Push the
String "Hello World" onto the stack
astore_1 // Pop the value "Hello World" from the stack and store it in local variable #1
getstatic #3 // Get the static field
java/lang/System.out:Ljava/io/PrintStream from the class
aload_1 // Load the
String referenced from local variable #1
invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
Clearly the invokevirtual
bytecode instruction is different than the other bytecode instructions in the example. It invokes a method rather than manipulating the operand stack. The comment for the invokevirtual
bytecode instruction might look odd too. It identifies the following:
- The receiver class providing the method:
java.io.PrintStream
- The method name:
println
- The type of the method argument:
(Ljava/lang/String;)
forString
- The method's return type:
V
forvoid
This information provides a signature for the method invocation. In response to the invokevirtual
bytecode instruction, the JVM looks for a method with the supplied signature, in this case, println:(Ljava/lang/String;)V
in the java.io.PrintStream
class. If the method isn't in that class, the JVM searches up the chain of the class's superclasses.
To learn more about invokevirtual
and other "invoke" type bytecode instructions, see JSR 292 — The Next Step in Dynamic Language Support.
Because dynamically typed languages don't provide type information until runtime, implementers must try various approaches to meet the bytecode requirements for method invocation, none of which is optimal. For example, suppose the following code snippet exists in a hypothetical dynamically typed language:
function max (x,y) {
if x.lessThan(y) then y else x
}
Notice that no type is specified for the receiver or the arguments — remember that dynamically typed languages don't provide type information until runtime. As a result, the code does not meet the know-in-advance type requirements for method invocation. The code can't be successfully compiled into bytecode on the Java platform.
One common approach to solving this problem is to create language-specific Java types for the return value and method arguments, especially those which act as method call receivers. For example, a dynamically typed language implementation might change the previous code snippet to the following:
MyObject function max (MyObject x,MyObject y) {
if x.lessThan(y) then y else x
}
The language-specific base type MyObject
contains the method isLessThan
and any other methods that a dynamic language might want to use, in order to meet the bytecode requirements for the method invocation.
Another technique to solve the problem of receiver typing is called reflected invocation. This approach uses a java.lang.reflect.Method
object to invoke the method. Using a Method
object to do the method invocation circumvents having to directly invoke the method, and as a result, bypasses the requirement of having to specify the type of the returned value or the types of any parameters.
Implementers of dynamically typed languages have tried various approaches to meet the bytecode requirements for method invocation, none of which are optimal. |
A third approach that implementers of dynamic languages use is to create a language-specific interpreter for method invocation to run on top of the JVM.
Creating a language-specific base type meets the requirements of the Java bytecode, but it is limited by the need to statically type each receiver to a prespecified base type. One problem with this technique is that a language implementer must think up a list of all methods in advance to put into the base type. Future methods, such as those defined by an end user, must be simulated less directly with a catch-all method — often called invoke
or apply
. Also, unless interface injection is an option, JVM system types such as String
and Integer
cannot serve directly as receivers of language-specific method calls.
The reflected invocation approach has some of its own restrictions. For example, a java.lang.reflect.Method
object provides access to a method on a class or interface, just what an implementer of a dynamic language needs, but the object must come from a specific Java type available at runtime. Although dynamic languages can provide type information at runtime, not all of them can provide normal Java types for reflection. This is particularly true for dynamic languages such as JRuby or Rhino, which have interpreters.
Running an interpreter on top of the JVM to handle method invocations is relatively slow, certainly slower than having the JVM directly handle the processing.
JSR 292 — The Next Step in Dynamic Language Support
JSR 292 aims to solve the problems inherent in trying to fit a square peg — method invocation in a dynamically typed language — into a round hole — the statically-based Java bytecode requirements. It does this by introducing a new Java bytecode instruction for the JVM, invokedynamic
, and a new method linkage mechanism.
JSR 292 introduces a new Java bytecode instruction for the JVM, invokedynamic , and a new method linkage mechanism. |
Bytecode Instructions for Method Invocation
Since its inception, the Java Virtual Machine Specification has specified four bytecodes for method invocation:
invokevirtual
- Invokes a method on a class. This is the typical type of method invocation.invokeinterface
- Invokes a method on an interface.invokestatic
- Invokes a static method on a class. This is the only kind of invocation that lacks a receiver argument.invokespecial
- Invokes a method without reference to the type of the receiver. Methods called this way can be constructors, superclass methods, or private methods.
Let's examine two of these bytecodes: invokevirtual
, because it's the most typical type of method invocation, and invokeinterface
, because it's similar in format to the new invokedynamic
instruction.
invokevirtual
Instruction Recall the invokevirtual
bytecode instruction that was shown in the earlier bytecode example.
invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
The invokevirtual
part of the instruction is the one-byte operation code. The remainder of the instruction, #4
, is the two-byte operand, which provides information about the method call in an abstract way. The operand refers to an entry in a pool of constants. The entry contains a symbolic reference that bundles together information pertinent to the method invocation. The information includes the receiver class that contains the method, the method name, and the method descriptor. The method descriptor specifies the method's return type and the types of its arguments.
As mentioned previously, the receiver in the method invocation is java.io.PrintStream
. The method name is println
. The method's return type is void
— represented by the V
. And the method has one String
argument — represented by (Ljava/lang/String;)
.
The syntax of the invokevirtual
bytecode instruction is as follows:
invokevirtual <method-specification>
where <method-specification>
is the constant pool index as described above.
invokeinterface
Instruction The invokeinterface
bytecode instruction for a Java program might look similar to the following:
invokeinterface #9, 2; //InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
The syntax for the invokeinterface
bytecode instruction is as follows:
invokeinterface <method-specification> <n>
where <method-specification>
is a two-byte constant pool index specifying an interface name, a method name, and a descriptor, and <n>
is a two-byte operand that specifies the number of arguments.
As is the case for invokevirtual
, the descriptor specifies the method's argument types as well as the method's return type. In this example, the <method-specification>
specifies that the interface is java.util.List
and the method is add
. The type descriptor specifies that the method takes arguments of type java.lang.Object
, represented by Ljava/lang/Object
, and returns a boolean result, represented by the Z
code.
The <n>
value in the example is 2
, indicating that the method takes 2 arguments, including the receiver.
invokedynamic
Instruction The syntax of the new invokedynamic
bytecode instruction is similar to that of the invokeinterface
instruction.
invokedynamic <method-specification> <n>
However, unlike the invokeinterface
instruction, the <method-specification>
only needs to specify a method name and descriptor. In this case, the two-byte number <n>
must be zero. Some JVMs may use these extra bytes internally to link the instruction to runtime structures.
An invokeinterface
bytecode instruction might look similar to the following:
Invokedynamic #10; //NameAndTypelessThan:(Ljava/lang/Object;Ljava/lang/Object;)
Significantly, the invokedynamic
bytecode instruction enables an implementer of a dynamic language to translate a method invocation into bytecode without having to specify a target type that contains the method. The method specification in this case is a simplified constant pool reference, which specifies only a method name and type descriptor. The descriptor specifies the return type of the call and the type of method arguments. As with invokestatic
, none of the arguments is singled out as a receiver.
But wait. How does the JVM find the method if the receiver type isn't supplied? After all, the JVM needs to link to and invoke a real method on a real type. The answer to that question is that JSR 292 also includes a new linkage mechanism for dynamically typed languages. When the JVM sees an invokedynamic
bytecode, it uses the new linkage mechanism to get to the method it needs.
A New Dynamic Linkage Mechanism: Method Handles
Method handles enable the JVM to invoke the correct method in response to an invokedynamic bytecode instruction. |
The new linkage mechanism for dynamically typed languages involves a new structure called method handles. JDK 7 includes a new package, java.dyn
, that contains the classes associated with dynamic language support in the Java platform. One of the classes in the package is MethodHandle
. A method handle is a simple object of type java.dyn.MethodHandle
that contains an anonymous reference to a JVM method. A method handle is callable just like a named reference to a method. What makes it unique, however, is that it is accessed through a pointer structure, as opposed to a linked name.
As with the other invoke instructions, the first time an invokedynamic
instruction is executed, it is linked. When this linking happens, a method handle is assigned to the individual invokedynamic
instruction as its particular target. Each time that particular instruction is executed, the JVM invokes the target method handle. Interestingly, the target of any given invokedynamic
instruction can change over time in response to changes in the dynamic language program.
Another part of this new linkage mechanism is the bootstrap method. A bootstrap method is a method handle that is called once for each invokedynamic
instruction, when the instruction is linked. The bootstrap method determines which target method to assign initially to the instruction. Each class that contains at least one invokedynamic
instruction must also specify a bootstrap method.
The bootstrap mechanism provides a way for a language runtime to participate in method dispatching at runtime, but the approach also enables the JVM to bypass the language runtime if the method dispatch decision doesn't change. The first time the JVM sees an invokedynamic
bytecode with a receiver and argument, it calls a bootstrap method. Calling into a language-supplied method like this is termed an up-call.
The bootstrap method, in turn, creates a call site object and chooses an appropriate target method handle. The JVM then associates the method referenced by the method handle with this invokedynamic
bytecode. The next time the JVM executes an invokedynamic
bytecode, it immediately invokes the previously chosen method. The idea here is for the JVM to make an up-call into the runtime of the dynamically typed language only for an unlinked dynamic call site, that is, for an invokedynamic
instruction that hasn't yet been linked to a target method. However, once the dynamic call site is linked, the JVM directly calls the target method without the need for doing an up-call.
The bootstrap method is also responsible for creating a call site object of type java.dyn.CallSite
that is permanently associated with the linked invokedynamic
instruction. The call site object provides an API to get and set the target method. The call site object can also be subclassed to contain language-specific logic for linking the instruction. In particular, it can be used by the language runtime to change the target method of the instruction over time.
A method handle is quite simple. All it contains is a type token of class java.dyn.MethodType
that describes a specific type. Also, a method handle implicitly has an invoke
method associated with it. To call a method handle, you call its invoke
method in much the same way as you call object methods, that is, MethodHandle.invoke(...)
. Because each method handle has its own type, it will accept only an invoke
call of that type. If the type of the call doesn't match the type of the method handle, the method handle throws an exception.
Here, for example, is a snippet of bytecode that calls a method handle:
getfield myMH
ldc #999
invokevirtual #44 //Method java/dyn/MethodHandle:invoke(I)I
istore 5
In this example, the call is to a method handle named myMH
with a single int
argument. The call also expects an int
type to be returned. The method handle has to accept that signature type. Before allowing the call, the JVM will verify (each time) that the method handle includes a type token that matches the call's descriptor — in this case, (I)I
.
In response, the method handle directs the JVM to the appropriate target function.
The method handle in the previous example provides a direct reference to a Java method — this is the simplest kind of mehod handle. There are also method handles that can adjust the argument list in the course of the call, inserting, converting, or deleting arguments or return values. There is an API on the type java.dyn.MethodHandles
for creating all these kinds of method handles and more.
When a bootstrap method is called for an unlinked dynamic call site, it is passed the following information:
- The
java.lang.Class
object for the class containing the instruction. - The method name represented as a
String
. - The resolved descriptor for the instruction, represented as a
java.dyn.MethodType
token.
The bootstrap method must then return a java.dyn.CallSite
object that reifies the call site. The term reify means turning an abstract concept into something real. So in reifying the call site, the CallSite
object makes the call site real to the Java platform. The CallSite
object includes getTarget
and setTargetmethods
to get and set the call site's target method, that is, a method handle that represents the linkage state of the invokedynamic
instruction.
The bootstrap method may also store the call site object in any language-specific runtime structure, such as a table of known call sites.
What's significant here is that the CallSite
object lets the language runtime change the target method of the reified call site. In essence, the bootstrap method acts as a messenger that relays a call for help from the invokedynamic
instruction to the language runtime. The answer to the call for help is provided by the CallSite
and its setTarget()
method. When the linkage is complete, the messenger disappears, and the call site is now capable of directly calling the target method.
Here is a source code example that includes a method handle and a bootstrap method.
import java.dyn.*;
public class Hello {
public static void main(String... av) {
if (av.length == 0) av = new String[] { "world" };
greeter(av[0] + " (from a statically linked call site)");
for (String whom : av) {
greeter.<void>invoke(whom); // strongly typed direct call
// previous line generates invokevirtual MethodHandle.invoke(String)void
Object x = whom;
InvokeDynamic.hail(x); // weakly typed invokedynamic
// previous line generates invokedynamic MethodHandle.invoke(Object)Object
}
}
static void greeter(String x) { System.out.println("Hello, "+x); }
// intentionally pun between the method and its reified handle:
static MethodHandle greeter
= MethodHandles.lookup().findStatic(Hello.class, "greeter",
MethodType.make(void.class, String.class));
// Set up a class-local bootstrap method.
static { Linkage.registerBootstrapMethod("bootstrapDynamic"); }
private static CallSite bootstrapDynamic(Class caller, String name, MethodType type) {
assert(type.parameterCount() == 1 && (Object)name == "hail"); // in lieu of MOP
System.out.println("set target to adapt "+greeter);
MethodHandle target = MethodHandles.convertArguments(greeter, type);
CallSite site = new CallSite(caller, name, type);
site.setTarget(target);
return site;
}
}
In this example, a Java class named Hello
registers a bootstrap method, bootstrapDynamic
, as follows:
// Set up a class-local bootstrap method.
static { Linkage.registerBootstrapMethod("bootstrapDynamic"); }
The bootstrap method creates a CallSite
and uses its setTarget()
method to set the target for the call site to the method handle, greeter
. The call to convertArguments
creates an adapter method handle that wraps the original greeter, so that the call site target's type will match the descriptor of the invokedynamic
instruction.
static MethodHandle greeter
= MethodHandles.lookup().findStatic(Hello.class, "greeter",
MethodType.make(void.class, String.class));
private static CallSite bootstrapDynamic(Class caller, String name, MethodType type) {
assert(type.parameterCount() == 1 && (Object)name == "hail"); // in lieu of MOP
System.out.println("set target to adapt "+greeter);
MethodHandle target = MethodHandles.convertArguments(greeter, type);
CallSite site = new CallSite(caller, name, type);
site.setTarget(target);
return site;
}
In summary, method handles provide the linkage mechanism that enables the JVM to invoke the correct method in response to an invokedynamic
bytecode instruction. When the JVM sees an invokedynamic
bytecode, it uses method handles to get to the method it needs. Note that method handles provide a better way to meet the bytecode requirements for method invocation than the reflected invocation approach. Recall that the reflected invocation approach uses java.lang.reflect.Method
objects to invoke the method. That approach requires a simulation layer that imposes extra complexity and execution overhead. By comparison, method handles provide a way to name and interconnect methods without regard to method type or placement, and with full type safety and native execution speed.
Modifying Classes at Runtime Through Interface Injection
Interface injection is the ability to modify classes at runtime so that they can implement new interfaces. It can enable a dynamically typed language in the JVM to easily integrate with other languages. |
Interface injection is the ability to modify classes at runtime so that they can implement new interfaces. Adding new methods is a common feature in dynamically typed languages, especially in scripting languages. Interface injection can provide a structured way to accomplish this in the JVM. However, it has not been part of the JVM standard. The feature is being investigated for inclusion in JSR 292.
With interface injection supported in the JVM, a language runtime could add new capabilities for its own use in a modular way. For example, suppose a class or set of classes in a language running in the JVM needs a type of serialization that is currently not implemented in the language. The language runtime could define that serialization as an injectable interface. It could also define an injector method. That method would know about the classes to which the language wants to assign the new serialization capability. The injection is done either initially as a configuration step, or lazily when the JVM first attempts to invoke an injectable method on a previously untouched class.
Interface injection has a variety of potential applications. One of them is enabling a dynamically typed language in the JVM to easily integrate with other languages in the JVM. The approach here is to inject the base or master interface of the language. For example, Jython has the base interface org.python.cor.PyObject
. Once injected, the base interface can be used as the conduit for communication between the injecting language and libraries in other languages.
Summary
Over the years, the JVM has been host to a growing number of languages, including implementations of dynamically typed languages such as Ruby and Python. Support for dynamically typed languages in the JVM is very attractive to application developers who build applications in these languages. That's because dynamic typing gives developers a lot of flexibility and the JVM delivers a lot of execution efficiency.
However, implementers of compilers for dynamically typed languages have found it difficult to meet the JVM bytecode requirements for method invocation. JSR 292 addresses that problem by providing a new bytecode, invokedynamic
, and a new linkage mechanism based on method handles. Also being investigated for inclusion in JSR 292 is interface injection, the ability to modify classes at runtime so that they can implement new interfaces.