You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在运行时内存编译Java类,避免文件系统写入操作?

内存中编译Java源码并获取字节码(无磁盘IO)

完全可以通过自定义javax.tools相关类实现纯内存编译,无需任何磁盘IO操作。核心是替换默认的文件管理逻辑,用内存对象替代磁盘文件作为输入输出载体。

核心思路

  1. 自定义JavaFileObject实现:
    • 一个用于提供内存中的源码(输入)
    • 另一个用于存储编译后的字节码(输出)
  2. 自定义JavaFileManager:覆盖文件获取逻辑,返回上述内存对象,避免磁盘交互
  3. 使用JavaCompilerAPI执行编译,最终从内存对象中提取字节码

完整代码示例

import javax.tools.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

// 内存源码文件对象:提供String形式的源码
class MemorySourceFileObject extends SimpleJavaFileObject {
    private final String sourceCode;

    public MemorySourceFileObject(String className, String sourceCode) {
        super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
        this.sourceCode = sourceCode;
    }

    @Override
    public CharSequence getCharContent(boolean ignoreEncodingErrors) {
        return sourceCode;
    }
}

// 内存字节码文件对象:存储编译后的字节码
class MemoryByteCodeFileObject extends SimpleJavaFileObject {
    private final ByteArrayOutputStream byteCodeOutputStream = new ByteArrayOutputStream();

    public MemoryByteCodeFileObject(String className) {
        super(URI.create("byte:///" + className.replace('.', '/') + Kind.CLASS.extension), Kind.CLASS);
    }

    @Override
    public OutputStream openOutputStream() {
        return byteCodeOutputStream;
    }

    // 获取编译后的字节码数组
    public byte[] getByteCode() {
        return byteCodeOutputStream.toByteArray();
    }
}

// 自定义文件管理器:完全基于内存处理输入输出
class MemoryJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
    private final Map<String, MemoryByteCodeFileObject> byteCodeFileObjects = new HashMap<>();

    protected MemoryJavaFileManager(JavaFileManager fileManager) {
        super(fileManager);
    }

    // 覆盖获取输出文件的逻辑,返回内存字节码对象
    @Override
    public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
        MemoryByteCodeFileObject byteCodeFileObject = new MemoryByteCodeFileObject(className);
        byteCodeFileObjects.put(className, byteCodeFileObject);
        return byteCodeFileObject;
    }

    // 根据类名获取编译后的字节码对象
    public MemoryByteCodeFileObject getByteCodeFileObject(String className) {
        return byteCodeFileObjects.get(className);
    }
}

public class InMemoryCompiler {
    public static byte[] compile(String className, String sourceCode) throws IOException {
        // 获取系统默认Java编译器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        if (compiler == null) {
            throw new IllegalStateException("无法获取Java编译器,请确保运行环境包含JDK(而非JRE)");
        }

        // 创建诊断监听器,用于捕获编译错误
        DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();

        // 创建自定义内存文件管理器
        try (MemoryJavaFileManager fileManager = new MemoryJavaFileManager(compiler.getStandardFileManager(diagnosticCollector, null, null))) {
            // 准备内存源码对象
            MemorySourceFileObject sourceFileObject = new MemorySourceFileObject(className, sourceCode);

            // 创建编译任务
            JavaCompiler.CompilationTask task = compiler.getTask(
                    null,
                    fileManager,
                    diagnosticCollector,
                    null, // 编译选项,比如"-target" "11"等,按需添加
                    null,
                    Set.of(sourceFileObject)
            );

            // 执行编译
            boolean success = task.call();
            if (!success) {
                // 打印编译错误
                for (Diagnostic<? extends JavaFileObject> diagnostic : diagnosticCollector.getDiagnostics()) {
                    System.err.printf("编译错误:%s%n行号:%d%n信息:%s%n",
                            diagnostic.getSource().toUri(),
                            diagnostic.getLineNumber(),
                            diagnostic.getMessage(null));
                }
                throw new RuntimeException("编译失败");
            }

            // 返回编译后的字节码
            return fileManager.getByteCodeFileObject(className).getByteCode();
        }
    }

    // 测试示例
    public static void main(String[] args) throws IOException {
        String className = "com.example.HelloWorld";
        String sourceCode = """
                package com.example;
                public class HelloWorld {
                    public void sayHello() {
                        System.out.println("Hello from in-memory compiled class!");
                    }
                }
                """;

        byte[] byteCode = compile(className, sourceCode);
        System.out.println("编译成功,字节码长度:" + byteCode.length + " bytes");
    }
}

关键说明

  • 依赖环境:必须使用JDK运行(而非JRE),因为JRE不包含JavaCompiler组件
  • 编译选项:可以在compiler.getTask的第三个参数添加编译选项,比如List.of("-target", "17", "-source", "17")来指定Java版本
  • 错误处理:代码中通过DiagnosticCollector捕获编译错误,方便调试语法或语义问题
  • 字节码使用:编译得到的byte[]可以直接用于后续操作(比如手动加载到类加载器、存储到数据库等),完全不需要写入磁盘

内容的提问来源于stack exchange,提问作者Volodya Lombrozo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 22:13:23