commit 50a71d88baa84206060dda9de871e6f433bed9a1 Author: meowrain Date: Thu Feb 12 18:09:54 2026 +0800 xxx diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..480bdf5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ +.kotlin + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b6b1ecf --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 已忽略包含查询文件的默认文件夹 +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ diff --git a/.idea/CoolRequestSetting.xml b/.idea/CoolRequestSetting.xml new file mode 100644 index 0000000..1c4794c --- /dev/null +++ b/.idea/CoolRequestSetting.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..15a25de --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e5b69a1 --- /dev/null +++ b/pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + + cn.meowrain.aioj + codesandboxtest2 + 1.0-SNAPSHOT + + + 17 + 17 + UTF-8 + + + + org.projectlombok + lombok + 1.18.42 + + + + cn.hutool + hutool-core + 5.8.42 + + + + + com.github.docker-java + docker-java + 3.7.0 + compile + + + + + com.github.docker-java + docker-java-transport-httpclient5 + 3.7.0 + compile + + + + \ No newline at end of file diff --git a/src/main/java/cn/meowrain/aioj/CodeSandbox.java b/src/main/java/cn/meowrain/aioj/CodeSandbox.java new file mode 100644 index 0000000..bedaedb --- /dev/null +++ b/src/main/java/cn/meowrain/aioj/CodeSandbox.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2026-2026-2026 MeowRain AI Online Judge + * Author: MeowRain + * Github: https://github.com/meowrain + * Blog: https://blog.meowrain.cn + * Build With Love❤️ + */ +package cn.meowrain.aioj; + + +import cn.meowrain.aioj.dto.req.ExecuteCodeRequestDTO; +import cn.meowrain.aioj.dto.resp.ExecuteCodeResponseDTO; + +/** + * 代码沙箱接口 + */ +public interface CodeSandbox { + + /** + * 执行代码 + * @param request 执行请求 + * @return 执行响应 + */ + ExecuteCodeResponseDTO executeCode(ExecuteCodeRequestDTO request); + +} diff --git a/src/main/java/cn/meowrain/aioj/JavaDockerCodeSandBox.java b/src/main/java/cn/meowrain/aioj/JavaDockerCodeSandBox.java new file mode 100644 index 0000000..187b2ae --- /dev/null +++ b/src/main/java/cn/meowrain/aioj/JavaDockerCodeSandBox.java @@ -0,0 +1,132 @@ +package cn.meowrain.aioj; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.resource.ResourceUtil; +import cn.meowrain.aioj.dto.req.ExecuteCodeRequestDTO; +import cn.meowrain.aioj.dto.resp.ExecuteCodeResponseDTO; +import cn.meowrain.aioj.dto.resp.JudgeInfoDTO; +import cn.meowrain.aioj.utils.ProcessUtil; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +public class JavaDockerCodeSandBox implements CodeSandbox { + + private static final long DEFAULT_TIME_LIMIT = 5000L; + + public static void main(String[] args) { + JavaDockerCodeSandBox javaDockerCodeSandBox = new JavaDockerCodeSandBox(); + + String code = ResourceUtil.readStr("testCode.multiargs/Main.java", StandardCharsets.UTF_8); + + String language = "java"; + ExecuteCodeRequestDTO executeCodeRequestDTO = ExecuteCodeRequestDTO.builder() + .code(code) + .language(language) + .inputList(Arrays.asList("1,2","1,3")) + .build(); + + ExecuteCodeResponseDTO response = javaDockerCodeSandBox.executeCode(executeCodeRequestDTO); + System.out.println("状态: " + response.getStatus()); + System.out.println("消息: " + response.getMessage()); + System.out.println("输出: " + response.getOutputList()); + if (response.getJudgeInfo() != null) { + System.out.println("判题信息: " + response.getJudgeInfo().getMessage()); + System.out.println("耗时: " + response.getJudgeInfo().getTime() + "ms"); + System.out.println("内存: " + response.getJudgeInfo().getMemory() + "KB"); + } + System.exit(0); + } + + @Override + public ExecuteCodeResponseDTO executeCode(ExecuteCodeRequestDTO request) { + String code = request.getCode(); + List inputList = request.getInputList(); + long timeLimit = request.getTimeLimit() != null ? request.getTimeLimit() : DEFAULT_TIME_LIMIT; + + if (inputList == null) { + inputList = Collections.emptyList(); + } + + ExecuteCodeResponseDTO response = new ExecuteCodeResponseDTO(); + + // 1. 保存用户代码到临时目录 + String userDir = System.getProperty("user.dir"); + String globalCodePathName = userDir + File.separator + "tempcode"; + if (!FileUtil.exist(globalCodePathName)) { + FileUtil.mkdir(globalCodePathName); + } + + String userCodeParentPath = globalCodePathName + File.separator + UUID.randomUUID(); + String userCodePath = userCodeParentPath + File.separator + "Main.java"; + File userCodeFile = FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8); + + try { + // 2. 编译 + String compileError = ProcessUtil.compile(userCodeFile); + if (compileError != null) { + response.setStatus(2); + response.setMessage(compileError); + return response; + } + + // 3. 逐个用例执行 + List outputList = new ArrayList<>(); + long maxTime = 0; + long maxMemory = 0; + + for (String input : inputList.isEmpty() ? Collections.singletonList("") : inputList) { + // 构建命令:java -cp Main + // inputList 中每个元素按空格拆分为多个命令行参数 + List runCommand = new ArrayList<>(Arrays.asList("java", "-Xmx256m", "-cp", userCodeParentPath, "Main")); + if (input != null && !input.trim().isEmpty()) { + runCommand.addAll(Arrays.asList(input.trim().split("\\s+"))); + } + ProcessUtil.ExecuteResult result = ProcessUtil.run(runCommand, null, timeLimit); + + if (result.isTimeout()) { + response.setStatus(4); + response.setMessage("运行超时"); + return response; + } + + if (result.getExitCode() != 0) { + response.setStatus(3); + response.setMessage(result.getStderr().isEmpty() + ? "运行错误,退出码: " + result.getExitCode() + : result.getStderr()); + return response; + } + + outputList.add(result.getStdout()); + maxTime = Math.max(maxTime, result.getTime()); + maxMemory = Math.max(maxMemory, result.getMemory()); + } + + // 4. 全部执行成功 + response.setStatus(1); + response.setMessage("成功"); + response.setOutputList(outputList); + + JudgeInfoDTO judgeInfo = new JudgeInfoDTO(); + judgeInfo.setMessage("Accepted"); + judgeInfo.setTime(maxTime); + judgeInfo.setMemory(maxMemory); + response.setJudgeInfo(judgeInfo); + + } catch (Exception e) { + response.setStatus(3); + response.setMessage("系统错误: " + e.getMessage()); + } finally { + // 5. 清理临时文件 + FileUtil.del(userCodeParentPath); + } + + return response; + } +} diff --git a/src/main/java/cn/meowrain/aioj/JavaNativeCodeSandbox.java b/src/main/java/cn/meowrain/aioj/JavaNativeCodeSandbox.java new file mode 100644 index 0000000..ce60103 --- /dev/null +++ b/src/main/java/cn/meowrain/aioj/JavaNativeCodeSandbox.java @@ -0,0 +1,132 @@ +package cn.meowrain.aioj; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.resource.ResourceUtil; +import cn.meowrain.aioj.dto.req.ExecuteCodeRequestDTO; +import cn.meowrain.aioj.dto.resp.ExecuteCodeResponseDTO; +import cn.meowrain.aioj.dto.resp.JudgeInfoDTO; +import cn.meowrain.aioj.utils.ProcessUtil; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +public class JavaNativeCodeSandbox implements CodeSandbox { + + private static final long DEFAULT_TIME_LIMIT = 5000L; + + public static void main(String[] args) { + JavaNativeCodeSandbox javaNativeCodeSandbox = new JavaNativeCodeSandbox(); + + String code = ResourceUtil.readStr("testCode.multiargs/Main.java", StandardCharsets.UTF_8); + + String language = "java"; + ExecuteCodeRequestDTO executeCodeRequestDTO = ExecuteCodeRequestDTO.builder() + .code(code) + .language(language) + .inputList(Arrays.asList("1,2","1,3")) + .build(); + + ExecuteCodeResponseDTO response = javaNativeCodeSandbox.executeCode(executeCodeRequestDTO); + System.out.println("状态: " + response.getStatus()); + System.out.println("消息: " + response.getMessage()); + System.out.println("输出: " + response.getOutputList()); + if (response.getJudgeInfo() != null) { + System.out.println("判题信息: " + response.getJudgeInfo().getMessage()); + System.out.println("耗时: " + response.getJudgeInfo().getTime() + "ms"); + System.out.println("内存: " + response.getJudgeInfo().getMemory() + "KB"); + } + System.exit(0); + } + + @Override + public ExecuteCodeResponseDTO executeCode(ExecuteCodeRequestDTO request) { + String code = request.getCode(); + List inputList = request.getInputList(); + long timeLimit = request.getTimeLimit() != null ? request.getTimeLimit() : DEFAULT_TIME_LIMIT; + + if (inputList == null) { + inputList = Collections.emptyList(); + } + + ExecuteCodeResponseDTO response = new ExecuteCodeResponseDTO(); + + // 1. 保存用户代码到临时目录 + String userDir = System.getProperty("user.dir"); + String globalCodePathName = userDir + File.separator + "tempcode"; + if (!FileUtil.exist(globalCodePathName)) { + FileUtil.mkdir(globalCodePathName); + } + + String userCodeParentPath = globalCodePathName + File.separator + UUID.randomUUID(); + String userCodePath = userCodeParentPath + File.separator + "Main.java"; + File userCodeFile = FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8); + + try { + // 2. 编译 + String compileError = ProcessUtil.compile(userCodeFile); + if (compileError != null) { + response.setStatus(2); + response.setMessage(compileError); + return response; + } + + // 3. 逐个用例执行 + List outputList = new ArrayList<>(); + long maxTime = 0; + long maxMemory = 0; + + for (String input : inputList.isEmpty() ? Collections.singletonList("") : inputList) { + // 构建命令:java -cp Main + // inputList 中每个元素按空格拆分为多个命令行参数 + List runCommand = new ArrayList<>(Arrays.asList("java", "-Xmx256m", "-cp", userCodeParentPath, "Main")); + if (input != null && !input.trim().isEmpty()) { + runCommand.addAll(Arrays.asList(input.trim().split("\\s+"))); + } + ProcessUtil.ExecuteResult result = ProcessUtil.run(runCommand, null, timeLimit); + + if (result.isTimeout()) { + response.setStatus(4); + response.setMessage("运行超时"); + return response; + } + + if (result.getExitCode() != 0) { + response.setStatus(3); + response.setMessage(result.getStderr().isEmpty() + ? "运行错误,退出码: " + result.getExitCode() + : result.getStderr()); + return response; + } + + outputList.add(result.getStdout()); + maxTime = Math.max(maxTime, result.getTime()); + maxMemory = Math.max(maxMemory, result.getMemory()); + } + + // 4. 全部执行成功 + response.setStatus(1); + response.setMessage("成功"); + response.setOutputList(outputList); + + JudgeInfoDTO judgeInfo = new JudgeInfoDTO(); + judgeInfo.setMessage("Accepted"); + judgeInfo.setTime(maxTime); + judgeInfo.setMemory(maxMemory); + response.setJudgeInfo(judgeInfo); + + } catch (Exception e) { + response.setStatus(3); + response.setMessage("系统错误: " + e.getMessage()); + } finally { + // 5. 清理临时文件 + FileUtil.del(userCodeParentPath); + } + + return response; + } +} diff --git a/src/main/java/cn/meowrain/aioj/Main.java b/src/main/java/cn/meowrain/aioj/Main.java new file mode 100644 index 0000000..0925424 --- /dev/null +++ b/src/main/java/cn/meowrain/aioj/Main.java @@ -0,0 +1,17 @@ +package cn.meowrain.aioj; + +//TIP 要运行代码,请按 或 +// 点击装订区域中的 图标。 +public class Main { + public static void main(String[] args) { + //TIP 当文本光标位于高亮显示的文本处时按 + // 查看 IntelliJ IDEA 建议如何修正。 + System.out.printf("Hello and welcome!"); + + for (int i = 1; i <= 5; i++) { + //TIP 按 开始调试代码。我们已经设置了一个 断点 + // 但您始终可以通过按 添加更多断点。 + System.out.println("i = " + i); + } + } +} \ No newline at end of file diff --git a/src/main/java/cn/meowrain/aioj/dto/req/DoJudgeRequestDTO.java b/src/main/java/cn/meowrain/aioj/dto/req/DoJudgeRequestDTO.java new file mode 100644 index 0000000..43bc3a8 --- /dev/null +++ b/src/main/java/cn/meowrain/aioj/dto/req/DoJudgeRequestDTO.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2026-2026-2026 MeowRain AI Online Judge + * Author: MeowRain + * Github: https://github.com/meowrain + * Blog: https://blog.meowrain.cn + * Build With Love❤️ + */ +package cn.meowrain.aioj.dto.req; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 判题请求 DTO + */ +@Data +public class DoJudgeRequestDTO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 题目提交ID + */ + private Long questionSubmitId; + +} diff --git a/src/main/java/cn/meowrain/aioj/dto/req/ExecuteCodeRequestDTO.java b/src/main/java/cn/meowrain/aioj/dto/req/ExecuteCodeRequestDTO.java new file mode 100644 index 0000000..9e84c13 --- /dev/null +++ b/src/main/java/cn/meowrain/aioj/dto/req/ExecuteCodeRequestDTO.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2026-2026-2026 MeowRain AI Online Judge + * Author: MeowRain + * Github: https://github.com/meowrain + * Blog: https://blog.meowrain.cn + * Build With Love❤️ + */ +package cn.meowrain.aioj.dto.req; + +import lombok.Builder; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 代码沙箱执行请求 DTO + */ +@Data +@Builder +public class ExecuteCodeRequestDTO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 编程语言 + */ + private String language; + + /** + * 用户代码 + */ + private String code; + + /** + * 输入用例列表 + */ + private List inputList; + + /** + * 时间限制(ms) + */ + private Long timeLimit; + + /** + * 内存限制(KB) + */ + private Long memoryLimit; + +} diff --git a/src/main/java/cn/meowrain/aioj/dto/resp/ExecuteCodeResponseDTO.java b/src/main/java/cn/meowrain/aioj/dto/resp/ExecuteCodeResponseDTO.java new file mode 100644 index 0000000..37c50cf --- /dev/null +++ b/src/main/java/cn/meowrain/aioj/dto/resp/ExecuteCodeResponseDTO.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2026-2026-2026 MeowRain AI Online Judge + * Author: MeowRain + * Github: https://github.com/meowrain + * Blog: https://blog.meowrain.cn + * Build With Love❤️ + */ +package cn.meowrain.aioj.dto.resp; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 代码沙箱执行响应 DTO + */ +@Data +public class ExecuteCodeResponseDTO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 输出结果列表(与输入用例一一对应) + */ + private List outputList; + + /** + * 执行消息(如编译错误信息、运行错误信息等) + */ + private String message; + + /** + * 执行状态(1-成功,2-编译错误,3-运行错误,4-超时,5-内存超限) + */ + private Integer status; + + /** + * 判题信息 + */ + private JudgeInfoDTO judgeInfo; + +} diff --git a/src/main/java/cn/meowrain/aioj/dto/resp/JudgeInfoDTO.java b/src/main/java/cn/meowrain/aioj/dto/resp/JudgeInfoDTO.java new file mode 100644 index 0000000..cbd005c --- /dev/null +++ b/src/main/java/cn/meowrain/aioj/dto/resp/JudgeInfoDTO.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2026-2026-2026 MeowRain AI Online Judge + * Author: MeowRain + * Github: https://github.com/meowrain + * Blog: https://blog.meowrain.cn + * Build With Love❤️ + */ +package cn.meowrain.aioj.dto.resp; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 判题信息 DTO + */ +@Data +public class JudgeInfoDTO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 程序执行消息(如 Accepted、Wrong Answer 等) + */ + private String message; + + /** + * 消耗内存(KB) + */ + private Long memory; + + /** + * 消耗时间(ms) + */ + private Long time; + +} diff --git a/src/main/java/cn/meowrain/aioj/utils/ProcessUtil.java b/src/main/java/cn/meowrain/aioj/utils/ProcessUtil.java new file mode 100644 index 0000000..28ed2fc --- /dev/null +++ b/src/main/java/cn/meowrain/aioj/utils/ProcessUtil.java @@ -0,0 +1,254 @@ +package cn.meowrain.aioj.utils; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * 进程执行工具类,封装编译和运行操作 + */ +public class ProcessUtil { + + /** + * 执行结果 + */ + public static class ExecuteResult { + private final boolean timeout; + private final int exitCode; + private final String stdout; + private final String stderr; + private final long time; + private final long memory; + + public ExecuteResult(boolean timeout, int exitCode, String stdout, String stderr, long time, long memory) { + this.timeout = timeout; + this.exitCode = exitCode; + this.stdout = stdout; + this.stderr = stderr; + this.time = time; + this.memory = memory; + } + + public boolean isTimeout() { + return timeout; + } + + public int getExitCode() { + return exitCode; + } + + public String getStdout() { + return stdout; + } + + public String getStderr() { + return stderr; + } + + public long getTime() { + return time; + } + + public long getMemory() { + return memory; + } + } + + /** + * 编译 Java 代码 + * + * @param userCodeFile 源代码文件 + * @param timeoutMs 编译超时时间(毫秒) + * @return 编译成功返回 null,失败返回错误信息 + */ + public static String compile(File userCodeFile, long timeoutMs) throws IOException, InterruptedException { + ProcessBuilder pb = new ProcessBuilder("javac", "-encoding", "UTF-8", userCodeFile.getAbsolutePath()); + pb.redirectErrorStream(true); + Process process = pb.start(); + + // 异步读取输出流,避免阻塞导致 waitFor 超时失效 + StringBuilder outputBuilder = new StringBuilder(); + Thread readerThread = new Thread(() -> { + try { + outputBuilder.append(readStream(process.getInputStream())); + } catch (IOException ignored) { + } + }); + readerThread.setDaemon(true); + readerThread.start(); + + boolean finished = process.waitFor(timeoutMs, TimeUnit.MILLISECONDS); + if (!finished) { + process.destroyForcibly(); + return "编译超时"; + } + + readerThread.join(2000); + String output = outputBuilder.toString(); + + if (process.exitValue() != 0) { + return "编译失败:\n" + output; + } + return null; + } + + /** + * 编译 Java 代码,默认 10 秒超时 + */ + public static String compile(File userCodeFile) throws IOException, InterruptedException { + return compile(userCodeFile, 10_000L); + } + + /** + * 运行程序 + * + * @param command 命令及参数列表 + * @param input 标准输入内容(可为 null 或空) + * @param timeLimitMs 运行超时时间(毫秒) + * @return 执行结果 + */ + public static ExecuteResult run(List command, String input, long timeLimitMs) throws IOException, InterruptedException { + ProcessBuilder pb = new ProcessBuilder(command); + pb.redirectErrorStream(false); + Process process = pb.start(); + + // 写入标准输入 + if (input != null && !input.isEmpty()) { + try (OutputStream os = process.getOutputStream()) { + os.write(input.getBytes(StandardCharsets.UTF_8)); + os.flush(); + } + } else { + process.getOutputStream().close(); + } + + // 异步读取 stdout 和 stderr,避免管道阻塞 + StringBuilder stdoutBuilder = new StringBuilder(); + StringBuilder stderrBuilder = new StringBuilder(); + + Thread stdoutThread = new Thread(() -> { + try { + stdoutBuilder.append(readStream(process.getInputStream())); + } catch (IOException ignored) { + } + }); + stdoutThread.setDaemon(true); + Thread stderrThread = new Thread(() -> { + try { + stderrBuilder.append(readStream(process.getErrorStream())); + } catch (IOException ignored) { + } + }); + stderrThread.setDaemon(true); + + // 内存采样线程:定期采样进程 PID 对应的内存使用(KB) + long[] maxMemory = {0}; + Thread memoryThread = new Thread(() -> { + try { + long pid = process.pid(); + while (process.isAlive()) { + long mem = getProcessMemoryKB(pid); + if (mem > maxMemory[0]) { + maxMemory[0] = mem; + } + Thread.sleep(50); + } + } catch (Exception ignored) { + } + }); + memoryThread.setDaemon(true); + + stdoutThread.start(); + stderrThread.start(); + memoryThread.start(); + + long startTime = System.currentTimeMillis(); + boolean finished = process.waitFor(timeLimitMs, TimeUnit.MILLISECONDS); + long elapsed = System.currentTimeMillis() - startTime; + + if (!finished) { + + // 杀死进程 + process.destroyForcibly(); + return new ExecuteResult(true, -1, "", "", elapsed, maxMemory[0]); + } + + stdoutThread.join(2000); + stderrThread.join(2000); + memoryThread.join(1000); + + String stdout = stdoutBuilder.toString().trim(); + String stderr = stderrBuilder.toString().trim(); + + return new ExecuteResult(false, process.exitValue(), stdout, stderr, elapsed, maxMemory[0]); + } + + /** + * 获取指定 PID 进程的内存使用(KB) + * Windows 使用 tasklist,Linux/Mac 使用 /proc 或 ps + */ + private static long getProcessMemoryKB(long pid) { + Process proc = null; + try { + String os = System.getProperty("os.name").toLowerCase(); + ProcessBuilder pb; + if (os.contains("win")) { + pb = new ProcessBuilder("tasklist", "/FI", "PID eq " + pid, "/FO", "CSV", "/NH"); + } else { + pb = new ProcessBuilder("ps", "-o", "rss=", "-p", String.valueOf(pid)); + } + pb.redirectErrorStream(true); + proc = pb.start(); + + boolean finished = proc.waitFor(2, TimeUnit.SECONDS); + if (!finished) { + proc.destroyForcibly(); + return 0; + } + + String output = readStream(proc.getInputStream()); + + if (os.contains("win")) { + // tasklist CSV 格式: "java.exe","12345","Console","1","123,456 K" + String[] parts = output.split("\""); + for (int i = parts.length - 1; i >= 0; i--) { + String part = parts[i].trim(); + if (part.toUpperCase().endsWith("K")) { + String numStr = part.replace("K", "").replace(",", "").replace(" ", "").trim(); + return Long.parseLong(numStr); + } + } + } else { + // ps 输出的直接就是 KB 数值 + String trimmed = output.trim(); + if (!trimmed.isEmpty()) { + return Long.parseLong(trimmed); + } + } + } catch (Exception ignored) { + } finally { + if (proc != null) { + proc.destroyForcibly(); + } + } + return 0; + } + + /** + * 读取输入流内容为字符串 + */ + public static String readStream(InputStream is) throws IOException { + StringBuilder sb = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + if (sb.length() > 0) { + sb.append(System.lineSeparator()); + } + sb.append(line); + } + } + return sb.toString(); + } +} diff --git a/src/main/resources/testCode.multiargs/Main.class b/src/main/resources/testCode.multiargs/Main.class new file mode 100644 index 0000000..2693e3c Binary files /dev/null and b/src/main/resources/testCode.multiargs/Main.class differ diff --git a/src/main/resources/testCode.multiargs/Main.java b/src/main/resources/testCode.multiargs/Main.java new file mode 100644 index 0000000..7e2c50f --- /dev/null +++ b/src/main/resources/testCode.multiargs/Main.java @@ -0,0 +1,15 @@ +import java.util.ArrayList; +import java.util.List; + +public class Main { + public static void main(String[] args) { + List holder = new ArrayList<>(); + int mb = 1024 * 1024; + long i = 0; + while (true) { + holder.add(new byte[10 * mb]); + i++; + System.out.println("allocated ~" + (i * 10) + "MB"); + } + } +} \ No newline at end of file