GraalVM - 隔离虚拟化

GraalVM 中引入了一项名为隔离的新虚拟化功能。GraalVM Isolates是一个不相交的堆,允许同一 VM 实例中的多个任务独立运行。在传统的 Java 应用程序服务器中,所有任务共享相同的内存堆,如果一个任务使用大量内存,则可能会触发垃圾收集 (GC),从而减慢共享该堆的其他任务的速度。Isolate是互不干涉的,因此每个隔离都可以独立进行垃圾收集(或在需要任何 GC 之前销毁)。Isolate在多子任务或者将单个单片应用程序分解为可管理的微服务的时候有绝佳优势。

还被用于无脑解决内存泄露,写了满天飞的内存泄露代码,在Isolate销毁的时候一键释放?(真的假的?)

Isolate还支持一项优化,即压缩指针。由于Isolate Heap通常仅供单个任务使用(而不满足 Java 应用服务器可能需要支持的大量任务),因此Isolate Heap可能很小,64 位指针来寻址管理内存有点铺张浪费了。面向对象语言中的大多数数据结构为指针占用的空间比为原始数据占用的空间要大,因此减小它们的大小会对应用程序占用空间产生很大影响。

虽然这一切听起来都很棒,但需要注意Isolate的一些限制。首先,这是一种低级功能,旨在管理任务的高级技术(如数据库或无服务器云)。例如,在数据库中,我们将使用一个Isolate来放置 Graal 编译器自己的对象,并使用另一个Isolate来放置应用程序数据。其次,Isolate还不包含快照等功能,而这些功能是人们期望从成熟的虚拟化技术中获得的。最后,Isolate和压缩指针仅在使用 Substrate VM 时可用 - 在运行嵌入在 Java HotSpot VM 中的 GraalVM 时它们不适用。

Isolate在任何版本的 GraalVM 中都可用,但压缩指针仅在企业版中可用。

下图显示了一个进程中的两个Isolate。每个Isolate(接下来称为隔离)都会从父亲处拷贝一份image heap(作为副本)(使用写时复制映射进行有效管理以减少内存占用)和自己的运行时堆,新分配的对象将放置在每个隔离自己独立的运行时堆中。

Graal提供两种 API 来创建和管理隔离区:Java API 和 C API。Java API 适用于想要使用多个隔离区的纯 Java 应用程序(或用 Scala 或 Kotlin 编写的应用程序)。C API 旨在将 Java(或 Scala、Kotlin)代码集成到现有的 C 应用程序中,并管理来自 C 代码的隔离区。我们首先了解 Java API,然后简要概述 C API。再次注意,Java 和 C API 都仅适用于本机映像,而不适用于 Java VM(例如 Java HotSpot VM)。

使用 Java 方法构建可执行应用程序时main(),在调用该方法之前会自动创建默认隔离main()。构建与 C 代码集成的共享库时,不会自动创建隔离,即必须使用 C API 创建初始隔离。

使用方法

package org.example;

import org.graalvm.nativeimage.*;
import org.graalvm.nativeimage.c.function.CEntryPoint;
import org.graalvm.nativeimage.c.type.CCharPointer;
import org.graalvm.nativeimage.c.type.CTypeConversion;

public class Main {
    public static void main(String[] args) {
        if(ImageInfo.isExecutable()) {
            System.out.println("AOT Code");
            IsolateThread renderingContext = Isolates.createIsolate(Isolates.CreateIsolateParameters.getDefault());
            IsolateThread myContext = CurrentIsolate.getCurrentThread();

            String prefix = "fuqiuluo_";
            ObjectHandle prefixHandle = copyString(renderingContext, prefix);
            ObjectHandle resultHandle = addAndOutput(renderingContext, myContext, prefixHandle, 114, 514);
            String result = ObjectHandles.getGlobal().get(resultHandle);
            ObjectHandles.getGlobal().destroy(resultHandle);
            Isolates.tearDownIsolate(renderingContext);

            System.out.println(result);
        } else {
            System.out.println("JVM");
        }
    }

    @CEntryPoint
    private static ObjectHandle addAndOutput(@CEntryPoint.IsolateThreadContext IsolateThread renderingContext, IsolateThread fromContext, ObjectHandle prefixHandle, int a, int b) {
        String prefix = ObjectHandles.getGlobal().get(prefixHandle);
        ObjectHandles.getGlobal().destroy(prefixHandle);
        int n = a + b;
        String c = prefix + n;
        return copyString(fromContext, c);
    }

    private static ObjectHandle copyString(IsolateThread targetContext, String sourceString) {
        /* Convert the source Java string to a C string. */
        try (CTypeConversion.CCharPointerHolder cStringHolder = CTypeConversion.toCString(sourceString)) {
            /* Cross the isolate boundary with a C string as the parameter. */
            return copyString(targetContext, cStringHolder.get());
        }
    }

    @CEntryPoint
    private static ObjectHandle copyString(@CEntryPoint.IsolateThreadContext IsolateThread renderingContext, CCharPointer cString) {
        /* Convert the C string to the target Java string. */
        String targetString = CTypeConversion.toJavaString(cString);
        /* Encapsulate the target string in a handle that can be returned back to the source isolate. */
        return ObjectHandles.getGlobal().create(targetString);
    }
}

我寻思直接跑吧?然后他爆炸了,

什么玩意G1GC不支持隔离!!!!

把G1GC关掉,终于正常运行了

详细的隔离介绍

Isolates and Compressed References: More Flexible and Efficient Memory Management via GraalVM