Embedding Languages

The GraalVM Polyglot API lets you embed and run code from guest languages in JVM-based host applications.

Throughout this section, you will learn how to create a host application in Java that runs on GraalVM and directly calls a guest language. You can use the tabs beneath each code example to choose between JavaScript, R, Ruby, and Python.

Ensure you set up GraalVM before you begin.

Compile and Run a Polyglot Application #

GraalVM can run polyglot applications written in any language implemented with the Truffle language implementation framework. These languages are henceforth referenced as guest languages.

Complete the steps in this section to create a sample polyglot application that runs on GraalVM and demonstrates programming language interoperability.

1. Create a hello-polyglot project directory.

2. In your project directory, add a HelloPolyglot.java file that includes the following code:

  // COMPILE-CMD: javac {file}
// RUN-CMD: java {file}
// BEGIN-SNIPPET
import org.graalvm.polyglot.*;
import org.graalvm.polyglot.proxy.*;
// END-SNIPPET

public class hello_polyglot_js {

static
// BEGIN-SNIPPET
public class HelloPolyglot {
    public static void main(String[] args) {
        System.out.println("Hello Java!");
        try (Context context = Context.create()) {
            context.eval("js", "print('Hello JavaScript!');");
        }
    }
}
// END-SNIPPET

    public static void main(String[] args) {
        HelloPolyglot.main(null);
    }
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java {file}
// BEGIN-SNIPPET
import org.graalvm.polyglot.*;
import org.graalvm.polyglot.proxy.*;
// END-SNIPPET

public class hello_polyglot_R {

static
// BEGIN-SNIPPET
public class HelloPolyglot {
    public static void main(String[] args) {
        System.out.println("Hello Java!");
        try (Context context = Context.newBuilder()
                                   .allowAllAccess(true)
                               .build()) {
            context.eval("R", "print('Hello R!');");
        }
    }
}
// END-SNIPPET

    public static void main(String[] args) {
        HelloPolyglot.main(null);
    }
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java {file}
// BEGIN-SNIPPET
import org.graalvm.polyglot.*;
import org.graalvm.polyglot.proxy.*;
// END-SNIPPET

public class hello_polyglot_ruby {

static
// BEGIN-SNIPPET
public class HelloPolyglot {
    public static void main(String[] args) {
        System.out.println("Hello Java!");
        try (Context context = Context.create()) {
            context.eval("ruby", "puts 'Hello Ruby!'");
        }
    }
}
// END-SNIPPET

    public static void main(String[] args) {
        HelloPolyglot.main(null);
    }
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java {file}
// BEGIN-SNIPPET
import org.graalvm.polyglot.*;
import org.graalvm.polyglot.proxy.*;
// END-SNIPPET

public class hello_polyglot_python {

static
// BEGIN-SNIPPET
public class HelloPolyglot {
    public static void main(String[] args) {
        System.out.println("Hello Java!");
        try (Context context = Context.create()) {
            context.eval("python", "print('Hello Python!')");
        }
    }
}
// END-SNIPPET

    public static void main(String[] args) {
        HelloPolyglot.main(null);
    }
}

  

 In this code:

  • import org.graalvm.polyglot.* imports the base API for the Polyglot API.
  • import org.graalvm.polyglot.proxy.* imports the proxy classes of the Polyglot API, needed in later examples.
  • Context provides an execution environment for guest languages. R currently requires the allowAllAccess flag to be set to true to run the example.
  • eval evaluates the specified snippet of guest language code.
  • The try with resource statement initializes the Context and ensures that it is closed after use. Closing the context ensures that all resources including potential native resources are freed eagerly. Closing a context is optional but recommended. Even if a context is not closed and no longer referenced it will be freed by the garbage collector automatically.

3. Run javac HelloPolyglot.java to compile HelloPolyglot.java with GraalVM.

4. Run java HelloPolyglot to run the application on GraalVM.

You now have a polyglot application that consists of a Java host application and guest language code that run on GraalVM. You can use this application with other code examples to demonstrate more advanced capabilities of the Polyglot API.

To use other code examples in this section, you simply need to do the following:

1. Add the code snippet to the main method of HelloPolyglot.java.

2. Compile and run your polyglot application.

Define Guest Language Functions as Java Values #

Polyglot applications let you take values from one programming language and use them with other languages.

Use the code example in this section with your polyglot application to show how the Polyglot API can return JavaScript, R, Ruby, or Python functions as Java values.

  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;

public class function_js {
    public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.create()) {
    Value function = context.eval("js", "x => x+1");
    assert function.canExecute();
    int x = function.execute(41).asInt();
    assert x == 42;
}
// END-SNIPPET
    }
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;

public class function_R {
    public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.newBuilder()
                           .allowAllAccess(true)
                       .build()) {
    Value function = context.eval("R", "function(x) x + 1");
    assert function.canExecute();
    int x = function.execute(41).asInt();
    assert x == 42;
}
// END-SNIPPET
    }
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;

public class function_ruby {
    public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.create()) {
    Value function = context.eval("ruby", "proc { |x| x + 1 }");
    assert function.canExecute();
    int x = function.execute(41).asInt();
    assert x == 42;
}
 // END-SNIPPET
    }
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;

public class function_python {
    public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.create()) {
    Value function = context.eval("python", "lambda x: x + 1");
    assert function.canExecute();
    int x = function.execute(41).asInt();
    assert x == 42;
}
 // END-SNIPPET
    }
}

  

 In this code:

  • Value function is a Java value that refers to a function.
  • The eval call parses the script and returns the guest language function.
  • The first assertion checks that the value returned by the code snippet can be executed.
  • The execute call executes the function with the argument 41.
  • The asInt call converts the result to a Java int.
  • The second assertion verifies that the result was incremented by one as expected.

Access Guest Languages Directly from Java #

Polyglot applications can readily access most language types and are not limited to functions. Host languages, such as Java, can directly access guest language values embedded in the polyglot application.

Use the code example in this section with your polyglot application to show how the Polyglot API can access objects, numbers, strings, and arrays.

  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;

public class access_js_from_java {
    public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.create()) {
    Value result = context.eval("js", 
                    "({ "                   +
                        "id   : 42, "       +
                        "text : '42', "     +
                        "arr  : [1,42,3] "  +
                    "})");
    assert result.hasMembers();

    int id = result.getMember("id").asInt();
    assert id == 42;

    String text = result.getMember("text").asString();
    assert text.equals("42");

    Value array = result.getMember("arr");
    assert array.hasArrayElements();
    assert array.getArraySize() == 3;
    assert array.getArrayElement(1).asInt() == 42;
}
// END-SNIPPET
    }
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;

public class access_R_from_java {
    public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.newBuilder()
                           .allowAllAccess(true)
                       .build()) {
    Value result = context.eval("R", 
                    "list("                +
                        "id   = 42, "      +
                        "text = '42', "    +
                        "arr  = c(1,42,3)" +
                    ")");
    assert result.hasMembers();
    
    int id = result.getMember("id").asInt();
    assert id == 42;
    
    String text = result.getMember("text").asString();
    assert text.equals("42");
    
    Value array = result.getMember("arr");
    assert array.hasArrayElements();
    assert array.getArraySize() == 3;
    assert array.getArrayElement(1).asInt() == 42;
}
// END-SNIPPET
    }
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;

public class access_ruby_from_java {
    public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.create()) {
    Value result = context.eval("ruby", 
                    "o = Struct.new(:id, :text, :arr).new(" +
                        "42, "       +
                        "'42', "     +
                        "[1,42,3] "  +
                    ")");
    assert result.hasMembers();
    
    int id = result.getMember("id").asInt();
    assert id == 42;
    
    String text = result.getMember("text").asString();
    assert text.equals("42");
    
    Value array = result.getMember("arr");
    assert array.hasArrayElements();
    assert array.getArraySize() == 3;
    assert array.getArrayElement(1).asInt() == 42;
}
// END-SNIPPET
    }
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;

public class access_python_from_java {
    public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.create()) {
    Value result = context.eval("python", 
                    "type('obj', (object,), {" +
                        "'id'  : 42, "         +
                        "'text': '42', "       +
                        "'arr' : [1,42,3]"     +
                    "})()");
    assert result.hasMembers();
    
    int id = result.getMember("id").asInt();
    assert id == 42;
    
    String text = result.getMember("text").asString();
    assert text.equals("42");
    
    Value array = result.getMember("arr");
    assert array.hasArrayElements();
    assert array.getArraySize() == 3;
    assert array.getArrayElement(1).asInt() == 42;
}
// END-SNIPPET
    }
}

  

 In this code:

  • Value result is an Object that contains three members: a number named id, a string named text, and an array named arr.
  • The first assertion verifies that the return value can contain members, which indicates that the value is an object-like structure.
  • The id variable is initialized by reading the member with the name id from the resulting object. The result is then converted to a Java int using asInt().
  • The next assert verifies that result has a value of 42.
  • The text variable is initialized using the value of the member text, which is also converted to a Java String using asString().
  • The following assertion verifies the result value is equal to the Java String "42".
  • Next the arr member that holds an array is read.
  • Arrays return true for hasArrayElements. R array instances can have members and array elements at the same time.
  • The next assertion verifies that the size of the array equals three. The Polyglot API supports big arrays, so the array length is of type long.
  • Finally we verify that the array element at index 1 equals 42. Array indexing with polyglot values is always zero-based, even for languages such as R where indices start with one.

Access Java from Guest Languages #

Polyglot applications offer bi-directional access between guest languages and host languages. As a result, you can pass Java objects to guest languages.

Use the code example in this section with your polyglot application to show how guest languages can access primitive Java values, objects, arrays, and functional interfaces.

To permit guest languages to access any public method or field of a Java object, set allowAllAccess(true) when the context is built. In this mode, the guest language code must be fully trusted, as it can access other not explicitly exported Java methods using reflection.

  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import java.util.concurrent.Callable;
import org.graalvm.polyglot.*;

public class access_java_from_js {

// BEGIN-SNIPPET
public static class MyClass {
    public int               id    = 42;
    public String            text  = "42";
    public int[]             arr   = new int[]{1, 42, 3};
    public Callable<Integer> ret42 = () -> 42;
}

public static void main(String[] args) {
    try (Context context = Context.newBuilder()
                               .allowAllAccess(true)
                           .build()) {
        context.getBindings("js").putMember("javaObj", new MyClass());
        boolean valid = context.eval("js",
               "    javaObj.id         == 42"          +
               " && javaObj.text       == '42'"        +
               " && javaObj.arr[1]     == 42"          +
               " && javaObj.ret42()    == 42")
           .asBoolean();
        assert valid == true;
    }
}
// END-SNIPPET
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import java.util.concurrent.Callable;
import org.graalvm.polyglot.*;

public class access_java_from_R {

// BEGIN-SNIPPET
public static class MyClass {
    public int               id    = 42;
    public String            text  = "42";
    public int[]             arr   = new int[]{1, 42, 3};
    public Callable<Integer> ret42 = () -> 42;
}

public static void main(String[] args) {
    try (Context context = Context.newBuilder()
                               .allowAllAccess(true)
                           .build()) {
        context.getBindings("R").putMember("javaObj", new MyClass());
        boolean valid = context.eval("R",
               "    javaObj$id         == 42"   +
               " && javaObj$text       == '42'" +
               " && javaObj$arr[[2]]   == 42"   +
               " && javaObj$ret42()    == 42")
           .asBoolean();
        assert valid == true;
    }
}
// END-SNIPPET
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import java.util.concurrent.Callable;
import org.graalvm.polyglot.*;

public class access_java_from_ruby {

// BEGIN-SNIPPET
public static class MyClass {
    public int               id    = 42;
    public String            text  = "42";
    public int[]             arr   = new int[]{1, 42, 3};
    public Callable<Integer> ret42 = () -> 42;
}

public static void main(String[] args) {
    try (Context context = Context.newBuilder()
                               .allowAllAccess(true)
                           .build()) {
        context.getPolyglotBindings().putMember("javaObj", new MyClass());
        boolean valid = context.eval("ruby",
               "javaObj = Polyglot.import('javaObj')\n" +
               "    javaObj[:id]         == 42"         +
               " && javaObj[:text]       == '42'"       +
               " && javaObj[:arr][1]     == 42"         +
               " && javaObj[:ret42].call == 42")
           .asBoolean();
        assert valid == true;
    }
}
// END-SNIPPET
}
 
  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import java.util.concurrent.Callable;
import org.graalvm.polyglot.*;

public class access_java_from_python {

// BEGIN-SNIPPET
public static class MyClass {
    public int               id    = 42;
    public String            text  = "42";
    public int[]             arr   = new int[]{1, 42, 3};
    public Callable<Integer> ret42 = () -> 42;
}

public static void main(String[] args) {
    try (Context context = Context.newBuilder()
                               .allowAllAccess(true)
                           .build()) {
        context.getPolyglotBindings().putMember("javaObj", new MyClass());
        boolean valid = context.eval("python",
               "import polyglot \n"                            +
               "javaObj =  polyglot.import_value('javaObj')\n" +
               "javaObj.id                   == 42"            +
               " and javaObj.text            == '42'"          +
               " and javaObj.arr[1]          == 42"            +
               " and javaObj.ret42() == 42")
           .asBoolean();
        assert valid == true;
    }
}
// END-SNIPPET
}

  

 In this code:

  • The Java class MyClass has four public fields id, text, arr, and ret42. The fields are initialized with 42, "42", new int[]{1, 42, 3}, and lambda () -> 42 that always returns an int value of 42.
  • The Java class MyClass is instantiated and exported with the name javaObj into the polyglot scope, which allows the host and guest languages to exchange symbols.
  • A guest language script is evaluated that imports the javaObj symbol and assigns it to the local variable which is also named javaObj. To avoid conflicts with variables, every value in the polyglot scope must be explicitly imported and exported in the top-most scope of the language.
  • The next two lines verify the contents of the Java object by comparing it to the number 42 and the string '42'.
  • The third verification reads from the second array position and compares it to the number 42. Whether arrays are accessed using 0-based or 1-based indices depends on the guest language. Independently of the language, the Java array stored in the arr field is always accessed using translated 0-based indices. For example, in the R language, arrays are 1-based so the second array element is accessible using index 2. In the JavaScript and Ruby languages, the second array element is at index 1. In all language examples, the Java array is read from using the same index 1.
  • The last line invokes the Java lambda that is contained in the field ret42 and compares the result to the number value 42.
  • After the guest language script executes, validation takes place to ensure that the script returns a boolean value of true as a result.

Lookup Java Types from Guest Languages #

In addition to passing Java objects to the guest language, it is possible to allow the lookup of Java types in the guest language.

Use the code example in this section with your polyglot application to show how guest languages lookup Java types and instantiate them.

  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;

public class lookup_java_from_js {


public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.newBuilder()
                           .allowAllAccess(true)
                       .build()) {
    java.math.BigDecimal v = context.eval("js",
            "var BigDecimal = Java.type('java.math.BigDecimal');" +
            "BigDecimal.valueOf(10).pow(20)")
        .asHostObject();
    assert v.toString().equals("100000000000000000000");
}
// END-SNIPPET
}
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;

public class lookup_java_from_R {


public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.newBuilder()
                           .allowAllAccess(true)
                       .build()) {
    java.math.BigDecimal v = context.eval("R",
            "BigDecimal = java.type('java.math.BigDecimal');\n" + 
            "BigDecimal$valueOf(10)$pow(20)")
        .asHostObject();
    assert v.toString().equals("100000000000000000000");
}
// END-SNIPPET
}
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;

public class lookup_java_from_ruby {

public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.newBuilder()
                           .allowAllAccess(true)
                       .build()) {
    java.math.BigDecimal v = context.eval("ruby",
            "BigDecimal = Java.type('java.math.BigDecimal')\n" + 
            "BigDecimal.valueOf(10).pow(20)")
        .asHostObject();
    assert v.toString().equals("100000000000000000000");
}
// END-SNIPPET
}
}
 
  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;

public class lookup_java_from_python {


public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.newBuilder()
                           .allowAllAccess(true)
                       .build()) {
    java.math.BigDecimal v = context.eval("python",
            "import java\n" +
            "BigDecimal = java.type('java.math.BigDecimal')\n" + 
            "BigDecimal.valueOf(10).pow(20)")
        .asHostObject();
    assert v.toString().equals("100000000000000000000");
}
// END-SNIPPET
}
}

  

 In this code:

  • A new context is created with all access enabled (allowAllAccess(true)).
  • A guest language script is evaluated.
  • The script looks up the Java type java.math.BigDecimal and stores it in a variable named BigDecimal.
  • The static method BigDecimal.valueOf(long) is invoked to create new BigDecimals with value 10. In addition to looking up static Java methods, it is also possible to directly instantiate the returned Java type., e.g., in JavaScript using the new keyword.
  • The new decimal is used to invoke the pow instance method with 20 which calculates 10^20.
  • The result of the script is converted to a host object by calling asHostObject(). The return value is automatically cast to the BigDecimal type.
  • The result decimal string is asserted to equal to "100000000000000000000".

Computed Arrays Using Polyglot Proxies #

The Polyglot API includes polyglot proxy interfaces that let you customize Java interoperability by mimicking guest language types, such as objects, arrays, native objects, or primitives.

Use the code example in this section with your polyglot application to see how you can implement arrays that compute their values lazily.

Note: The Polyglot API supports polyglot proxies either on the JVM or in Native Image.

  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;
import org.graalvm.polyglot.proxy.*;

public class proxy_js {

// BEGIN-SNIPPET
static class ComputedArray implements ProxyArray {
    public Object get(long index) {
        return index * 2;
    }
    public void set(long index, Value value) {
        throw new UnsupportedOperationException();
    }
    public long getSize() {
        return Long.MAX_VALUE;
    }
}

public static void main(String[] args) {
    try (Context context = Context.create()) {
        ComputedArray arr = new ComputedArray();
        context.getBindings("js").putMember("arr", arr);
        long result = context.eval("js",
                    "arr[1] + arr[1000000000]")
                .asLong();
        assert result == 2000000002L;
    }
}
// END-SNIPPET
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.proxy.ProxyArray;

public class proxy_R {

// BEGIN-SNIPPET
static class ComputedArray implements ProxyArray {
    public Object get(long index) {
        return index * 2;
    }
    public void set(long index, Value value) {
        throw new UnsupportedOperationException();
    }
    public long getSize() {
        return Long.MAX_VALUE;
    }
}

public static void main(String[] args) {
    try (Context context = Context.newBuilder()
                               .allowAllAccess(true)
                           .build()) {
        ComputedArray arr = new ComputedArray();
        context.getPolyglotBindings().putMember("arr", arr);
        long result = context.eval("R",
               "arr <- import('arr');" +
               "arr[2] + arr[1000000001]")
           .asLong();
        assert result == 2000000002L;
    }
}
// END-SNIPPET
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.proxy.ProxyArray;

public class proxy_ruby {

// BEGIN-SNIPPET
static class ComputedArray implements ProxyArray {
    public Object get(long index) {
        return index * 2;
    }
    public void set(long index, Value value) {
        throw new UnsupportedOperationException();
    }
    public long getSize() {
        return Long.MAX_VALUE;
    }
}

public static void main(String[] args) {
    try (Context context = Context.newBuilder()
                               .allowAllAccess(true)
                           .build()) {
        ComputedArray arr = new ComputedArray();
        context.getPolyglotBindings().putMember("arr", arr);
        long result = context.eval("ruby",
               "arr = Polyglot.import('arr') \n" +
               "arr[1] + arr[1000000000]")
           .asLong();
        assert result == 2000000002L;
    }
}
// END-SNIPPET
}

  
  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.proxy.ProxyArray;

public class proxy_python {

// BEGIN-SNIPPET
static class ComputedArray implements ProxyArray {
    public Object get(long index) {
        return index * 2;
    }
    public void set(long index, Value value) {
        throw new UnsupportedOperationException();
    }
    public long getSize() {
        return Long.MAX_VALUE;
    }
}

public static void main(String[] args) {
    try (Context context = Context.newBuilder()
                               .allowAllAccess(true)
                           .build()) {
        ComputedArray arr = new ComputedArray();
        context.getPolyglotBindings().putMember("arr", arr);
        long result = context.eval("python",
               "import polyglot\n" +
               "arr = polyglot.import_value('arr') \n" +
               "arr[1] + arr[1000000000]")
           .asLong();
        assert result == 2000000002L;
    }
}
// END-SNIPPET
}

  

 In this code:

  • The Java class ComputedArray implements the proxy interface ProxyArray so that guest languages treat instances of the Java class like arrays.
  • ComputedArray array overrides the method get and computes the value using an arithmetic expression.
  • The array proxy does not support write access. For this reason, it throws an UnsupportedOperationException in the implementation of set.
  • The implementation for getSize returns Long.MAX_VALUE for its length.
  • The main method creates a new polyglot execution context.
  • A new instance of the ComputedArray class is then exported using the name arr.
  • The guest language script imports the arr symbol, which returns the exported proxy.
  • The second element and the 1000000000th element is accessed, summed up, and then returned. Note that array indices from 1-based languages such as R are converted to 0-based indices for proxy arrays.
  • The result of the language script is returned as a long value and verified.

For more information about the polyglot proxy interfaces, see the Polyglot API JavaDoc.

Access Restrictions #

The Polyglot API by default restricts access to certain critical functionality, such as file I/O. These restrictions can be lifted entirely by setting allowAllAccess to true.

Note: The access restrictions are currently only supported with JavaScript.

Configuring Host Access #

It might be desirable to limit the access of guest applications to the host. For example, if a Java method is exposed that calls System.exit then the guest application will be able to exit the host process. In order to avoid accidentally exposed methods, no host access is allowed by default and every public method or field needs to be annotated with @HostAccess.Export explicitly.

  // COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.HostAccess;
import org.graalvm.polyglot.PolyglotException;

public class explicit_access_java_from_js {

static
// BEGIN-SNIPPET
public class Employee {
    private final String name;
    Employee(String name) {this.name = name;}

    @HostAccess.Export
    public String getName() {
        return name;
    }
}//END-SNIPPET
static//BEGIN-SNIPPET
public class Services {
    @HostAccess.Export
    public Employee createEmployee(String name) {
        return new Employee(name);
    }
    
    public void exitVM() {
        System.exit(1);
    }
}

public static void main(String[] args) {
    try (Context context = Context.create()) {
        Services services = new Services();
        context.getBindings("js").putMember("services", services);
        String name = context.eval("js",
                "let emp = services.createEmployee('John Doe');" + 
                "emp.getName()").asString();
        assert name.equals("John Doe");
        
        try {
            context.eval("js", "services.exitVM()");
            assert false;
        } catch (PolyglotException e) {
            assert e.getMessage().endsWith(
                    "Unknown identifier: exitVM");
        }
    }
}
// END-SNIPPET
}

  

 In this code:

  • The class Employee is declared with a field name of type String. Access to the getName method is explicitly allowed by annotating the method with @HostAccess.Export.
  • The Services class exposes two methods, createEmployee and exitVM. The createEmployee method takes the name of the employee as an argument and creates a new Employee instance. The createEmployee method is annotated with @HostAccess.Export and therefore accessible to the guest application. The exitVM method is not explicitly exported and therefore not accessible.
  • The main method first creates a new polyglot context in the default configuration, disallowing host access except for methods annotated with @HostAccess.Export.
  • A new Services instance is created and put into the context as global variable services.
  • The first evaluated script creates a new employee using the services object and returns its name.
  • The returned name is asserted to equal the expected name John Doe.
  • A second script is evaluated that calls the exitVM method on the services object. This fails with a PolyglotException as the exitVM method is not exposed to the guest application.

Host access is fully customizable by creating a custom HostAccess policy.

Access Privilege Configuration #

It is possible to configure fine-grained access privileges for guest applications. The configuration can be provided using the Context.Builder class when constructing a new context. The following access parameters may be configured:

  • Allow access to other languages using allowPolyglotAccess.
  • Allow and customize access to host objects using allowHostAccess.
  • Allow and customize host lookup to host types using allowHostClassLookup. Allows the guest application to look up the host application classes permitted by the lookup predicate. For example, a Javascript context can create a Java ArrayList, provided that ArrayList is allowlisted by the classFilter and access is permitted by the host access policy: context.eval("js", "var array = Java.type('java.util.ArrayList')")
  • Allow host class loading using allowHostClassLoading. Classes are only accessible if access to them is granted by the host access policy.
  • Allow the creation of threads using allowCreateThread.
  • Allow access to native APIs using allowNativeAccess.
  • Allow access to IO using allowIO and proxy file accesses using fileSystem.

Note: Granting access to class loading, native APIs, or host I/O effectively grants all access, as these privileges can be used to bypass other access restrictions.

Build Native Images from Polyglot Applications #

Polyglot embeddings can also be compiled ahead-of-time using Native Image. By default, no language is included if the Polyglot API is used. To enable guest languages, the --language:<languageId> (e.g., --language:js) option needs to be specified. Currently, it is required to set the --initialize-at-build-time option when building a polyglot native image. All examples on this page can be converted to native executables with the native-image builder.

The following example shows how a simple HelloWorld JavaScript application can be built using native-image:

javac HelloPolyglot.java
native-image --language:js --initialize-at-build-time -cp . HelloPolyglot
./HelloPolyglot

It should be mentioned that you can also include a guest language into the native image, but exclude the JIT compiler by passing the -Dtruffle.TruffleRuntime=com.oracle.truffle.api.impl.DefaultTruffleRuntime option to the builder. Be aware, the flag -Dtruffle.TruffleRuntime=com.oracle.truffle.api.impl.DefaultTruffleRuntime has to placed after all the Truffle language/tool options, so that it will override the default settings.

You can build the above example again but this time the created image will only contain the Truffle language interpreter (the GraalVM compiler will not be included in the image) by running:

native-image --language:js -Dtruffle.TruffleRuntime=com.oracle.truffle.api.impl.DefaultTruffleRuntime --initialize-at-build-time -cp . HelloPolyglotInterpreter

Configuring Native Host Reflection #

Accessing host Java code from the guest application requires Java reflection in order to work. When reflection is used within a native image, the reflection configuration file is required.

For this example we use JavaScript to show host access with native executables. Copy the following code in a new file named AccessJavaFromJS.java.

import org.graalvm.polyglot.*;
import org.graalvm.polyglot.proxy.*;
import java.util.concurrent.*;

public class AccessJavaFromJS {

    public static class MyClass {
        public int               id    = 42;
        public String            text  = "42";
        public int[]             arr   = new int[]{1, 42, 3};
        public Callable<Integer> ret42 = () -> 42;
    }

    public static void main(String[] args) {
        try (Context context = Context.newBuilder()
                                   .allowAllAccess(true)
                               .build()) {
            context.getBindings("js").putMember("javaObj", new MyClass());
            boolean valid = context.eval("js",
                   "    javaObj.id         == 42"          +
                   " && javaObj.text       == '42'"        +
                   " && javaObj.arr[1]     == 42"          +
                   " && javaObj.ret42()    == 42")
               .asBoolean();
            System.out.println("Valid " + valid);
        }
    }
}

Copy the following code into reflect.json:

[
  { "name": "AccessJavaFromJS$MyClass", "allPublicFields": true },
  { "name": "java.util.concurrent.Callable", "allPublicMethods": true }
]

Now you can create a native executable that supports host access:

javac AccessJavaFromJS.java
native-image --language:js --initialize-at-build-time -H:ReflectionConfigurationFiles=reflect.json -cp . AccessJavaFromJS
./accessjavafromjs

Note that in case assertions are needed in the image, the -H:+RuntimeAssertions option can be passed to native-image. For production deployments, this option should be omitted.

Code Caching Across Multiple Contexts #

The GraalVM Polyglot API allows code caching across multiple contexts. Code caching allows compiled code to be reused and allows sources to be parsed only once. Code caching can often reduce memory consumption and warm-up time of the application.

By default, code is cached within a single context instance only. To enable code caching between multiple contexts, an explicit engine needs to be specified. The engine is specified when creating the context using the context builder. The scope of code sharing is determined by the engine instance. Code is only shared between contexts associated with one engine instance.

All sources are cached by default. Caching may be disabled explicitly by setting cached(boolean cached) to false. Disabling caching may be useful in case the source is known to only be evaluated once.

Consider the following code snippet as an example:

public class Main {
    public static void main(String[] args) {
        try (Engine engine = Engine.create()) {
            Source source = Source.create("js", "21 + 21");
            try (Context context = Context.newBuilder()
                .engine(engine)
                .build()) {
                    int v = context.eval(source).asInt();
                    assert v == 42;
            }
            try (Context context = Context.newBuilder()
                .engine(engine)
                .build()) {
                    int v = context.eval(source).asInt();
                    assert v == 42;
            }
        }
    }
}

In this code:


  • import org.graalvm.polyglot.* imports the base API for the Polyglot API.
  • Engine.create() creates a new engine instance with the default configuration.
  • Source.create() creates a source object for the expression “21 + 21” with “js” language, which is the language identifier for JavaScript.
  • Context.newBuilder().engine(engine).build() builds a new context with an explicit engine assigned to it. All contexts associated with an engine share the code.
  • context.eval(source).asInt() evaluates the source and returns the result as Value instance.

Embed Guest languages in Guest Languages #

The GraalVM Polyglot API can be used from within a guest language using Java interoperability. This can be useful if a script needs to run isolated from the parent context. In Java as a host language a call to Context.eval(Source) returns an instance of Value, but since we executing this code as part of a guest language we can use the language-specific interoperability API instead. It is therefore possible to use values returned by contexts created inside of a language, like regular values of the language. In the example below we can conveniently write value.data instead of value.getMember("data"). Please refer to the individual language documentation for details on how to interoperate with foreign values. More information on value sharing between multiple contexts can be found here.

Consider the following code snippet as an example:

import org.graalvm.polyglot.*;

public class Main {
    public static void main(String[] args) {
        try (Context outer = Context.newBuilder()
                                   .allowAllAccess(true)
                               .build()) {            
            outer.eval("js", "inner = Java.type('org.graalvm.polyglot.Context').create()");
            outer.eval("js", "value = inner.eval('js', '({data:42})')");
            int result = outer.eval("js", "value.data").asInt();
            outer.eval("js", "inner.close()");
            
            System.out.println("Valid " + (result == 42));
        }
    }
}

In this code:


  • Context.newBuilder().allowAllAccess(true).build() builds a new outer context with all privileges.
  • outer.eval evaluates a JavaScript snippet in the outer context.
  • inner = Java.type('org.graalvm.polyglot.Context').create() the first JS script line looks up the Java host type Context and creates a new inner context instance with no privileges (default).
  • inner.eval('js', '({data:42})'); evaluates the JavaScript code ({data:42}) in the inner context and returns stores the result.
  • "value.data" this line reads the member data from the result of the inner context. Note that this result can only be read as long as the inner context is not yet closed.
  • context.eval("js", "c.close()") this snippet closes the inner context. Inner contexts need to be closed manually and are not automatically closed with the parent context.
  • Finally the example is expected to print Valid true to the console.

Build a Shell for Many Languages #

With just a few lines of code, the GraalVM Polyglot API lets you build applications that integrate with any guest language supported by GraalVM.

This shell implementation is agnostic to any particular guest language.

BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
PrintStream output = System.out;
Context context = Context.newBuilder().allowAllAccess(true).build();
Set<String> languages = context.getEngine().getLanguages().keySet();
output.println("Shell for " + languages + ":");
String language = languages.iterator().next();
for (;;) {
    try {
        output.print(language + "> ");
        String line = input.readLine();
        if (line == null) {
            break;
        } else if (languages.contains(line)) {
            language = line;
            continue;
        }
        Source source = Source.newBuilder(language, line, "<shell>")
                        .interactive(true).buildLiteral();
        context.eval(source);
    } catch (PolyglotException t) {
        if(t.isExit()) {
            break;
        }
        t.printStackTrace();
    }
}

Step Through with Execution Listeners #

The GraalVM Polyglot API allows users to instrument the execution of guest languages through ExecutionListener class. For example, it lets you attach an execution listener that is invoked for every statement of the guest language program. Execution listeners are designed as simple API for polyglot embedders and may become handy in, e.g., single-stepping through the program.

import org.graalvm.polyglot.*;
import org.graalvm.polyglot.management.*;

public class ExecutionListenerTest {
    public static void main(String[] args) {
        try (Context context = Context.create("js")) {
            ExecutionListener listener = ExecutionListener.newBuilder()
                      .onEnter((e) -> System.out.println(
                              e.getLocation().getCharacters()))
                      .statements(true)
                      .attach(context.getEngine());
            context.eval("js", "for (var i = 0; i < 2; i++);");
            listener.close();
        }
    }
}

In this code:

  • The Context.create() call creates a new context for the guest language.
  • Create an execution listener builder by invoking ExecutionListeners.newBuilder().
  • Set onEnter event to notify when element’s execution is entered and consumed. At least one event consumer and one filtered source element needs to be enabled.
  • To complete the listener attachment, attach() needs to be invoked.
  • The statements(true) filters execution listeners to statements only.
  • The context.eval() call evaluates a specified snippet of guest language code.
  • The listener.close() closes a listener earlier, however execution listeners are automatically closed with the engine.

layout: docs toc_group: reference-manual link_title: Enterprise Sandbox Resource Limits permalink: /reference-manual/embed-languages/sandbox/ —

Enterprise Sandbox Resource Limits

The 20.3 release of GraalVM introduced the experimental Sandbox Resource Limits feature that allows for the limiting of resources used by guest applications. These resource limits are not available in the community edition of GraalVM. The following document describes how to configure sandbox resource limits using options in the polyglot API

In general all resource limit options are prefixed with sandbox option group and they can be listed using the help of any language launcher provided in GraalVM e.g. js --help:tools. Polyglot options can be provided through the language launcher, using the polyglot embedding API of the Graal SDK, or on the JVM using a system property. For better understanding of the examples it is recommended to read the polyglot embedding guide of the reference manual first.

Currently all sandbox options are experimental therefore in these examples it is assumed that experimental options are enabled (e.g. with --experimental-options). The options are a best effort approach to limiting resource usage of guest applications.

The resource limits may be configured using the following options:

  • --sandbox.MaxStatements=<long> Limit the maximum number of guest language statements.
  • --sandbox.MaxStatementsIncludeInternal=<boolean> Whether to include internal sources in the max statements computation.
  • --sandbox.MaxCPUTime=<duration> Limit the total maximum CPU time that was spent running the application.
  • --sandbox.MaxCPUTimeCheckInterval=<duration> Time interval to check the active CPU time for a context.
  • --sandbox.MaxStackFrames=<int> Limits the maximum number of guest stack frames.
  • --sandbox.MaxThreads=<int> Limit the number of threads that can be concurrently used by a context.
  • --sandbox.MaxASTDepth=<int> Limit the maximum depth of AST nodes for a guest language function.
  • --sandbox.MaxHeapMemory=<size> Specifies the maximum heap memory that can be retained by the application during its run.
  • --sandbox.AllocatedBytesCheckInterval=<duration> Time interval to check allocated bytes for an execution context. Exceeding certain number of allocated bytes triggers computation of bytes retained in the heap by the context.
  • --sandbox.AllocatedBytesCheckEnabled=<boolean> Specifies whether checking of allocated bytes for an execution context is enabled. If disabled, retained size checking for the context can be triggered only by the low memory trigger.
  • --sandbox.AllocatedBytesCheckFactor=<factor> Specifies a factor of MaxHeapMemory the allocation of which triggers retained heap memory computation. When allocated bytes for an execution context reach the specified factor, computation of bytes retained in the heap by the context is initiated.
  • --sandbox.RetainedBytesCheckInterval=<duration> Specifies the minimum time interval between two computations of retained bytes in the heap for a single execution context.
  • --sandbox.RetainedBytesCheckFactor=<factor> Specifies a factor of total heap memory of the host VM the exceeding of which stops the world. When the total number of bytes allocated in the heap for the whole host VM exceeds the factor, the following process is initiated. Execution for all engines with at least one memory-limited execution context (one with sandbox.MaxHeapMemory set) is paused. Retained bytes in the heap for each memory-limited context are computed. Contexts exceeding their limits are cancelled. The execution is resumed. All contexts using the sandbox.MaxHeapMemory option must use the same value for sandbox.RetainedBytesCheckFactor.
  • --sandbox.UseLowMemoryTrigger=<boolean> Specifies whether stopping the world is enabled. If enabled, engines with at least one memory-limited execution context are paused when the total number of bytes allocated in the heap for the whole host VM exceeds the specified factor of total heap memory of the host VM. If disabled, retained size checking for memory-limited execution contexts can be triggered only by the allocated bytes checker. All contexts using the sandbox.MaxHeapMemory option must use the same value for sandbox.UseLowMemoryTrigger.

Different configurations may be provided for each polyglot embedding Context instance. In addition to that the limits may be reset at any point of time during the execution. Resetting is only aplicable to sandbox.MaxStatements and sandbox.MaxCPUTime.

A guest language might choose to create an inner context within the outer execution context. The limits are applied to the outer context and all inner contexts it spawns. It is not possible to specify a separate limit for inner contexts and it is also not possible to escape any limit by creating an inner context.

Limiting the active CPU time #

The sandbox.MaxCPUTime option allows you to specify the maximum CPU time spent running the application. The maximum CPU time specifies how long a context can be active until it is automatically cancelled and the context is closed. By default the time limit is checked every 10 milliseconds. This can be customized using the sandbox.MaxCPUTimeCheckInterval option. Both maximum CPU time limit and check interval must be positive. By default no CPU time limit is enforced. If the time limit is exceeded then the polyglot context is cancelled and the execution stops by throwing a PolyglotException which returns true for isResourceExhausted(). As soon as the time limit is triggered, no further application code can be executed with this context. It will continuously throw a PolyglotException for any method of the polyglot context that will be invoked.

The used CPU time of a context typically does not include time spent waiting for synchronization or IO. The CPU time of all threads will be added and checked against the CPU time limit. This can mean that if two threads execute the same context then the time limit will be exceeded twice as fast.

The time limit is enforced by a separate high-priority thread that will be woken regularly. There is no guarantee that the context will be cancelled within the accuracy specified. The accuracy may be significantly missed, e.g. if the host VM causes a full garbage collection. If the time limit is never exceeded then the throughput of the guest context is not affected. If the time limit is exceeded for one context then it may slow down the throughput for other contexts with the same explicit engine temporarily.

Available units to specify time durations are ms for milliseconds, s for seconds, m for minutes, h for hours and d for days. It is not allowed specify negative values or no time unit with CPU time limit options.

Example Usage #

try (Context context = Context.newBuilder("js")
                           .experimentalOptions(true)
                           .option("sandbox.MaxCPUTime", "500ms")
                           .option("sandbox.MaxCPUTimeCheckInterval", "5ms")
                       .build();) {
    try {
        context.eval("js", "while(true);");
        assert false;
    } catch (PolyglotException e) {
        // triggered after 500ms;
        // context is closed and can no longer be used
        // error message: Maximum CPU time limit of 500ms exceeded.
        assert e.isCancelled();
        assert e.isResourceExhausted();
    }
}

Limiting the number of executed statements #

Specifies the maximum number of statements a context may execute until the the context will be cancelled. After the statement limit was triggered for a context, it is no longer usable and every use of the context will throw a PolyglotException that returns true for PolyglotException.isCancelled(). The statement limit is independent of the number of threads executing and is applied per context. It is also possible to specify this limit using the ResourceLimits API of the polyglot embedding API.

By default there is no statement limit applied. The limit may be set to a negative number to disable it. Whether this limit is applied internal sources only can be configured using sandbox.MaxStatementsIncludeInternal. By default the limit does not include statements of sources that are marked internal. If a shared engine is used then the same internal configuration must be used for all contexts of an engine. The maximum statement limit can be configured for each context of an engine separately.

Attaching a statement limit to a context reduces the throughput of all guest applications with the same engine. The statement counter needs to be updated with every statement that is executed. It is recommended to benchmark the use of the statement limit before it is used in production.

The complexity of a single statement may not be constant time depending on the guest language. For example, statements that execute JavaScript builtins, like Array.sort, may account for a single statement, but its execution time is dependent on the size of the array. The statement count limit is therefore not suitable to perform time boxing and must be combined with other more reliable measures like the CPU time limit.

try (Context context = Context.newBuilder("js")
                           .experimentalOptions(true)
                           .option("sandbox.MaxStatements", "2")
                           .option("sandbox.MaxStatementsIncludeInternal", "false")
                       .build();) {
    try {
        context.eval("js", "purpose = 41");
        context.eval("js", "purpose++");
        context.eval("js", "purpose++"); // triggers max statements
        assert false;
    } catch (PolyglotException e) {
        // context is closed and can no longer be used
        // error message: Maximum statements limit of 2 exceeded.
        assert e.isCancelled();
        assert e.isResourceExhausted();
    }
}

Limiting the AST depth of functions #

A limit on the maximum expression depth of a guest language function. Only instrumentable nodes count towards the limit. If the limit is exceeded, evaluation of the code fails and the context is canceled.

The AST depth can give an estimate of the complexity of a function as well as its stack frame size. Limiting the AST depth can serve as a safeguard against arbitrary stack space usage by a single function.

Limiting the number of stack frames #

Specifies the maximum number of frames a context can push on the stack. Exceeding the limit results in cancellation of the context. A thread-local stack frame counter is incremented on function enter and decremented on function return. Resetting resource limits does not affect the stack frame counter.

The stack frame limit in itself can serve as a safeguard against infinite recursion. If used together with the AST depth limit it can be used to estimate total stack space usage.

Limiting the number of active threads #

Limits the number of threads that can be used by a context at the same point in time. By default, an arbitary number of threads can be used. If a set limit is exceeded, entering the context fails with a PolyglotException and the polyglot context is canceled. Resetting resource limits does not affect thread limits.

Limiting the maximum heap memory #

The sandbox.MaxHeapMemory option allows you to specify the maximum heap memory the application is allowed to retain during its run. sandbox.MaxHeapMemory must be positive. This option is only supported on a HotSpot-based VM. Enabling this option in AOT mode will result in PolyglotException. When exceeding of the limit is detected, the corresponding context is automatically cancelled and then closed.

The efficacy of this option (also) depends on the garbage collector used.

Example Usage

try (Context context = Context.newBuilder("js")
                           .experimentalOptions(true)
                           .option("sandbox.MaxHeapMemory", "100MB")
                       .build()) {
    try {
        context.eval("js", "var r = {}; var o = r; while(true) { o.o = {}; o = o.o; };");
        assert false;
    } catch (PolyglotException e) {
        // triggered after the retained size is greater than 100MB;
        // context is closed and can no longer be used
        // error message: Maximum heap memory limit of 104857600 bytes exceeded. Current memory at least...
        assert e.isCancelled();
        assert e.isResourceExhausted();
    }
}

Implementation details and expert options

The limit is checked by retained size computation triggered either based on allocated bytes or on low memory notification.

The allocated bytes are checked by a separate high-priority thread that will be woken regularly. There is one such thread for each memory-limited context (one with sandbox.MaxHeapMemory set). The retained bytes computation is done by yet another high-priority thread that is started from the allocated bytes checking thread as needed. The retained bytes computation thread also cancels the context if the heap memory limit is exeeded. Additionaly, when low memory trigger is invoked, all contexts on engines with at least one memory-limited context are paused together with their allocation checkers. All individual retained size computations are cancelled. Retained bytes in the heap for each memory-limited context are computed by a single high-priority thread. Contexts exceeding their limits are cancelled, and then the execution is resumed.

The main goal of the heap memory limits is to prevent heap memory depletion related errors in most cases and thus enable the host VM to run smoothly even in the presence of misbehaving contexts. The implementation is best effort. This means that there is no guarantee on the accuracy of the heap memory limit. There is also no guarantee that setting a heap memory limit will prevent the context from causing OutOfMemory errors. Guest applications that allocate many objects in quick succession have a lower accuracy than applications which allocate objects rarely. The guest code execution will only be paused if the host heap memory is exhausted and a low memory trigger of the host VM is invoked. Note that the scope of the pause is an engine, so a context without the sandbox.MaxHeapMemory option set is also paused in case it shares the engine with other context that is memory-limited. Also note that if one context is cancelled other contexts with the same explicit engine may be slowed down. How the size retained by a context is computed can be customized using the expert options sandbox.AllocatedBytesCheckInterval, sandbox.AllocatedBytesCheckEnabled, sandbox.AllocatedBytesCheckFactor, sandbox.RetainedBytesCheckInterval, sandbox.RetainedBytesCheckFactor, and sandbox.UseLowMemoryTrigger described below.

Retained size computation for a context is triggered when a retained bytes estimate exceeds a certain factor of specified sandbox.MaxHeapMemory. The estimate is based on heap memory allocated by threads where the context has been active. More precisely, the estimate is the result of previous retained bytes computation, if available, plus bytes allocated since the start of the previous computation. By default the factor of sandbox.MaxHeapMemory is 1.0 and it can be customized by the sandbox.AllocatedBytesCheckFactor option. The factor must be positive. For example, let sandbox.MaxHeapMemory be 100MB and sandbox.AllocatedBytesCheckFactor be 0.5. The retained size computation is first triggered when allocated bytes reach 50MB. Let the computed retained size be 25MB, then the next retained size computation is triggered when additional 25MB is allocated, etc.

By default, allocated bytes are checked every 10 milliseconds. This can be configured by sandbox.AllocatedBytesCheckInterval. The smallest possible interval is 1ms. Any smaller value is interpreted as 1ms.

The beginnings of two retained size computations of the same context must be by default at least 10 milliseconds apart. This can be configured by the sandbox.RetainedBytesCheckInterval option. The interval must be positive.

The allocated bytes checking for a context can be disabled by the sandbox.AllocatedBytesCheckEnabled option. By default it is enabled (“true”). If disabled (“false”), retained size checking for the context can be triggered only by the low memory trigger.

When the total number of bytes allocated in the heap for the whole host VM exceeds a certain factor of the total heap memory of the VM, low memory notification is invoked and initiates the following process. The execution for all engines with at least one execution context which has the sandbox.MaxHeapMemory option set is paused, retained bytes in the heap for each memory-limited context are computed, contexts exceeding their limits are cancelled, and then the execution is resumed. The default factor is 0.7. This can be configuted by the sandbox.RetainedBytesCheckFactor option. The factor must be between 0.0 and 1.0. All contexts using the sandbox.MaxHeapMemory option must use the same value for sandbox.RetainedBytesCheckFactor.

The described low memory trigger can be disabled by the sandbox.UseLowMemoryTrigger option. By default it is enabled (“true”). If disabled (“false”), retained size checking for the execution context can be triggered only by the allocated bytes checker. All contexts using the sandbox.MaxHeapMemory option must use the same value for sandbox.UseLowMemoryTrigger.

If exceeding of the heap memory limit is detected then the polyglot context is cancelled and the execution stops by throwing a PolyglotException which returns true for isResourceExhausted(). As soon as the memory limit is triggered, no further application code can be executed with this context. It will continuously throw a PolyglotException for any method of the polyglot context that will be invoked.

Available units to specify time durations are ms for milliseconds, s for seconds, m for minutes, h for hours and d for days. It is not allowed to specify negative values or no time unit with max heap memory options.

Available units to specify sizes are B for bytes, KB for kilobytes, MB for megabytes, and GB for gigabytes. It is not allowed to specify negative values or no size unit with max heap memory options.

Resetting resource limits using Context.resetLimits does not affect the heap memory limit.

Resetting Resource Limits #

With the polyglot embedding API it is possible to reset the limits at any point in time using the Context.resetLimits method. This can be useful if a known and trusted initialization script should be excluded from limit. Resetting the limits is not applicable to all limits.

Example Usage #

try (Context context = Context.newBuilder("js")
                           .experimentalOptions(true)
                           .option("sandbox.MaxCPUTime", "500ms")
                       .build();) {
    try {
        context.eval("js", /*... initialization script ...*/);
        context.resetLimits();
        context.eval("js", /*... user script ...*/);
        assert false;
    } catch (PolyglotException e) {
        assert e.isCancelled();
        assert e.isResourceExhausted();
    }
}