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

咨询:运行仅存于内存且超Java字节数组容量的Jar文件方案

内存加载超大Jar(无磁盘落地)的稳定实现方案需求

我开发了一款可向Java进程注入DLL的应用,目标是加载并运行远程主机的Jar文件,且Jar全程不得落地磁盘。由于Jar对应的本地缓冲区大小超过Integer.MAX_VALUE,无法直接用byte[]传递,因此通过JNI从C++代码传入缓冲区的起始地址和长度,自定义了MemoryClassLoader类加载器和UnsafeBufferInputStream输入流来读取内存中的Jar数据。

目前该实现已能加载包含入口点的类,但实际运行Jar时遇到诸多问题,且担心此类自定义实现会引发难以排查的运行时问题,现寻求成熟稳定的内存Jar运行预实现方案,或更优的替代思路。


现有实现代码

MemoryClassLoader 实现

public class MemoryClassLoader extends ClassLoader {
    private final Map<String, byte[]> classes = new HashMap<>();
    private final Map<String, byte[]> resources = new HashMap<>();

    public MemoryClassLoader() {

    }
    
    // Called from my native c++ code, providing the start address and length of the buffer
    public void load(long memAddress, long bufSize) {
        try {
            String tempFolderPath = System.getProperty("java.io.tmpdir"); // Get the system's temporary folder
            String className = "net.sxlver.UnsafeBufferInputStream"; // Adjust with your class name

            Class<?> unsafeStream = null;
            try {
                // Read the class file bytes from the temp folder
                Path filePath = Paths.get(tempFolderPath, "UnsafeBufferInputStream.class");
                byte[] classBytes = Files.readAllBytes(filePath);
                unsafeStream = defineClass(className, classBytes, 0, classBytes.length);
            } catch (IOException e) {
                e.printStackTrace();
            }


            //UnsafeBufferInputStream stream = (UnsafeBufferInputStream) unsafeStream.getDeclaredConstructor(long.class, long.class).newInstance(memAddress, bufSize);
            UnsafeBufferInputStream stream = new UnsafeBufferInputStream(memAddress, bufSize);

            loadClassesAndResourcesFromJar(stream);
        }catch(final Exception exception) {
            JOptionPane.showMessageDialog(null, "");
        }

    }

    private static Unsafe getUnsafe() {
        try {
            java.lang.reflect.Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            return (Unsafe) field.get(null);
        } catch (Exception e) {
            throw new RuntimeException("Unsafe access error: " + e.getMessage());
        }
    }

    private void loadClassesAndResourcesFromJar(UnsafeBufferInputStream stream) throws IOException {
        JarInputStream jarStream = new JarInputStream(stream);
        JarEntry entry;
        while ((entry = jarStream.getNextJarEntry()) != null) {
            if (!entry.isDirectory()) {
                byte[] buffer = new byte[1024];
                int bytesRead;
                ByteArrayOutputStream baos = new ByteArrayOutputStream();

                while ((bytesRead = jarStream.read(buffer)) != -1) {
                    baos.write(buffer, 0, bytesRead);
                }

                if (entry.getName().endsWith(".class")) {
                    classes.put(entry.getName().replace(".class", "").replace("/", "."), baos.toByteArray());

                } else {
                    resources.put(entry.getName(), baos.toByteArray());
                }
            }
        }
    }

    // Override findClass to load classes from memory
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classBytes = classes.get(name);
        if (classBytes == null) {
            throw new ClassNotFoundException(name);
        }
        return defineClass(name, classBytes, 0, classBytes.length);
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            Class<?> loadedClass = findLoadedClass(name);
            if (loadedClass == null) {
                try {
                    loadedClass = findClass(name);
                } catch (ClassNotFoundException e) {
                    loadedClass = super.loadClass(name);
                }
            }
            return loadedClass;
        }
    }

    @Override
    public InputStream getResourceAsStream(String name) {
        byte[] resourceBytes = resources.get(name);
        if (resourceBytes != null) {
            return new ByteArrayInputStream(resourceBytes);
        }
        return super.getResourceAsStream(name);
    }
}

UnsafeBufferInputStream 实现

public class UnsafeBufferInputStream extends InputStream {
    private Unsafe unsafe;
    private long address;
    private long remainingBytes;
    private static final int BUFFER_SIZE = 1024; // Adjust buffer size as needed

    public UnsafeBufferInputStream(long bufferAddress, long size) {
        this.unsafe = getUnsafe();
        this.address = bufferAddress;
        this.remainingBytes = size;
    }

    private Unsafe getUnsafe() {
        try {
            java.lang.reflect.Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            return (Unsafe) field.get(null);
        } catch (Exception e) {
            throw new RuntimeException("Unsafe access error: " + e.getMessage());
        }
    }

    @Override
    public int read() throws IOException {
        if (remainingBytes <= 0) {
            return -1;
        }
        byte value = unsafe.getByte(address++);
        remainingBytes--;
        return value & 0xFF;
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        if (remainingBytes <= 0) {
            return -1;
        }
        int bytesRead = (int) Math.min(len, remainingBytes);
        for (int i = 0; i < bytesRead; i++) {
            b[off + i] = unsafe.getByte(address++);
        }
        remainingBytes -= bytesRead;
        return bytesRead;
    }

    @Override
    public void close() {
    }
}

成熟稳定的替代方案与优化思路

1. 替换Unsafe为NIO MappedByteBuffer(推荐)

Unsafe操作存在兼容性风险(不同JDK版本实现可能变化),且手动字节循环读取效率低下。可以在JNI层将C++分配的内存包装为FileDescriptor,然后通过FileChannel.map将其映射为MappedByteBuffer,再基于该ByteBuffer实现标准InputStream:

  • 避免直接使用Unsafe,依赖Java标准API,稳定性更高
  • MappedByteBuffer支持批量内存拷贝,读取效率远高于单字节循环

2. 优化ClassLoader的按需加载逻辑

现有实现会一次性将Jar中所有类和资源加载到HashMap,对于超大Jar会直接导致OOM。改为按需加载:

  • 保留内存映射的Jar流引用,当findClass或getResourceAsStream被调用时,才定位到对应的JarEntry读取内容
  • 避免预加载所有资源,大幅降低内存占用

3. 复用成熟的类加载器实现

直接使用经过生产环境验证的开源类加载器:

  • Apache Commons Lang的ByteArrayClassLoader:支持从字节数组或流加载类,可扩展为支持内存映射流
  • Spring Framework的Resource体系:自定义Resource实现读取内存中的Jar,配合DefaultResourceLoader实现类加载,自动处理类依赖和资源查找

4. JNI层拆分大缓冲区

如果无法使用MappedByteBuffer,可以在JNI层将超过Integer.MAX_VALUE的缓冲区拆分为多个int大小的块,分批次传递到Java层,用SequenceInputStream拼接成完整的Jar流,兼容Java标准API的int长度限制。

5. 移除临时文件依赖

现有代码中读取临时目录的UnsafeBufferInputStream.class存在安全风险和依赖问题,改为:

  • 将UnsafeBufferInputStream的字节码直接硬编码到MemoryClassLoader中,通过defineClass加载
  • 或者将该类打包到注入DLL的辅助资源中,通过JNI直接传递其字节码

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 19:45:26