From 475bf924a0b2832d67a20e063a980deffff0edbd Mon Sep 17 00:00:00 2001 From: claude-bot Date: Wed, 13 May 2026 02:07:47 +0900 Subject: [PATCH] =?UTF-8?q?installer:=20JVM=20=ED=8A=9C=EB=8B=9D=20?= =?UTF-8?q?=ED=94=8C=EB=9E=98=EA=B7=B8=EB=A5=BC=20=EB=A7=88=EC=9D=B8?= =?UTF-8?q?=ED=81=AC=EB=9E=98=ED=94=84=ED=8A=B8=20=EB=9F=B0=EC=B2=98=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20javaArgs=20=EC=97=90=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 이전 커밋이 server run.bat 에 잘못 적용됐던 것을 되돌리고, 본래 의도대로 launcher_profiles.json 의 javaArgs 에 Aikar 권장 G1 GC 플래그 6종을 병합한다. - mergeRamArgs(-Xmx) 후 mergeJvmTuningFlags 로 누락 플래그만 추가 - 사용자가 같은 키를 이미 지정했으면 그 값을 존중(덮어쓰지 않음) - run.bat 의 injectJvmFlags 호출 및 함수 제거 --- src/installer/main.ts | 81 ++++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/src/installer/main.ts b/src/installer/main.ts index c45eba9..854ab3e 100644 --- a/src/installer/main.ts +++ b/src/installer/main.ts @@ -463,31 +463,6 @@ ipcMain.handle('server:install', async (_event, payload: ServerInstallPayload) = * 이 경우 라우터의 UPnP TTL 에 의해 자동 만료되며, 다음 실행 시 Add 전에 Remove 를 * 시도하므로 idempotent. */ -// Aikar 권장 페이퍼 G1 GC 튜닝 셋의 기본형. 메모리(-Xms/-Xmx) 는 사용자가 -// 이미 설치 설정에서 정하므로 여기서 건드리지 않는다. -const DEFAULT_JVM_FLAGS = [ - '-XX:+UnlockExperimentalVMOptions', - '-XX:+UseG1GC', - '-XX:G1NewSizePercent=20', - '-XX:G1ReservePercent=20', - '-XX:MaxGCPauseMillis=50', - '-XX:G1HeapRegionSize=32M' -] - -/** - * java 호출 라인에 JVM 튜닝 플래그를 끼워 넣는다. -jar 앞에 삽입(존재 시), - * 없으면 java 토큰 바로 뒤에 삽입. -XX:+UseG1GC 가 이미 들어 있으면 멱등. - */ -function injectJvmFlags(javaLine: string): string { - if (javaLine.includes('-XX:+UseG1GC')) return javaLine - const flagsStr = DEFAULT_JVM_FLAGS.join(' ') - const jarMatch = /\s-jar\b/.exec(javaLine) - if (jarMatch && jarMatch.index >= 0) { - return `${javaLine.slice(0, jarMatch.index)} ${flagsStr}${javaLine.slice(jarMatch.index)}` - } - return javaLine.replace(/^(\s*java(?:\.exe)?)(\s|$)/i, `$1 ${flagsStr}$2`) -} - async function injectUpnpToRunBat(installPath: string): Promise { const runBat = path.join(installPath, 'run.bat') if (!fs.existsSync(runBat)) { @@ -513,13 +488,6 @@ async function injectUpnpToRunBat(installPath: string): Promise { } if (pauseIdx === -1) pauseIdx = lines.length - // java 호출 라인에 JVM 튜닝 플래그(Aikar 권장 G1 설정)를 주입. - // 메모리 옵션(-Xms/-Xmx)은 기존 값을 그대로 유지하고, GC/region 관련만 -jar 앞에 끼워 넣는다. - const javaLine = lines[javaIdx] - lines[javaIdx] = injectJvmFlags(javaLine) - if (lines[javaIdx] !== javaLine) { - sendLog('run.bat 의 java 호출에 JVM 튜닝 플래그(G1 GC, region/pause 등)를 추가했습니다.') - } // PowerShell 한 줄로 처리: server.properties 의 server-port 우선, 없으면 25565. // Add 전에 같은 포트의 매핑이 남아 있으면 먼저 Remove 하여 idempotent 하게 만든다. @@ -1231,6 +1199,48 @@ function mergeRamArgs(existing: string, recommendedMb: number): string { return merged.join(' ').trim() } +// Aikar 권장 G1 GC 튜닝 셋의 기본형. 메모리(-Xms/-Xmx) 는 mergeRamArgs 에서 별도 처리. +const DEFAULT_JVM_TUNING_FLAGS = [ + '-XX:+UnlockExperimentalVMOptions', + '-XX:+UseG1GC', + '-XX:G1NewSizePercent=20', + '-XX:G1ReservePercent=20', + '-XX:MaxGCPauseMillis=50', + '-XX:G1HeapRegionSize=32M' +] + +/** + * 기존 javaArgs 에 JVM 튜닝 플래그를 병합. 사용자가 이미 동일 key 를 지정했으면 + * 그 값을 존중하고(덮어쓰지 않음), 없는 항목만 끝에 덧붙인다. + * -XX:+UseG1GC, -XX:-UseG1GC → key = "-XX:UseG1GC" + * -XX:G1NewSizePercent=20 → key = "-XX:G1NewSizePercent" + * -Xmx2G → key = "-Xmx" + */ +function mergeJvmTuningFlags(existing: string, flags: string[]): string { + function keyOf(token: string): string { + if (token.startsWith('-XX:')) { + const body = token.slice(4) + const stripped = body.startsWith('+') || body.startsWith('-') ? body.slice(1) : body + const eqIdx = stripped.indexOf('=') + return `-XX:${eqIdx >= 0 ? stripped.slice(0, eqIdx) : stripped}` + } + const eqIdx = token.indexOf('=') + if (eqIdx >= 0) return token.slice(0, eqIdx) + if (token.startsWith('-Xmx')) return '-Xmx' + if (token.startsWith('-Xms')) return '-Xms' + if (token.startsWith('-Xmn')) return '-Xmn' + return token + } + const tokens = (existing || '').split(/\s+/).filter(Boolean) + const haveKeys = new Set(tokens.map(keyOf)) + const additions: string[] = [] + for (const f of flags) { + if (!haveKeys.has(keyOf(f))) additions.push(f) + } + if (additions.length === 0) return existing + return [...tokens, ...additions].join(' ').trim() +} + /** * launcher_profiles 의 lastVersionId 를 마인크래프트 런처가 실제로 가지고 있는 폴더 이름과 맞춘다. * - vanilla: mcVersion 그대로 (예: "1.21.4") @@ -1287,9 +1297,10 @@ async function updateLauncherProfile(pack: PackDefinition, gameDir: string): Pro const profileKey = pack.name const existingProfile = json.profiles[profileKey] ?? {} const existingJavaArgs = typeof existingProfile.javaArgs === 'string' ? (existingProfile.javaArgs as string) : '' - const javaArgs = mergeRamArgs(existingJavaArgs, pack.serverMaxRam) - if (existingJavaArgs && existingJavaArgs !== javaArgs) { - sendLog(`기존 JVM 인수 유지, -Xmx 만 갱신: "${existingJavaArgs}" → "${javaArgs}"`) + const ramMerged = mergeRamArgs(existingJavaArgs, pack.serverMaxRam) + const javaArgs = mergeJvmTuningFlags(ramMerged, DEFAULT_JVM_TUNING_FLAGS) + if (existingJavaArgs !== javaArgs) { + sendLog(`JVM 인수 갱신(메모리 + G1 GC 튜닝 추가): "${existingJavaArgs}" → "${javaArgs}"`) } const lastVersionId = resolveLastVersionId(pack) sendLog(`launcher_profiles 의 lastVersionId = ${lastVersionId}`)