Files
pzmanager/internal/pzconfig/java.go
T
mrleo1nidandClaude Opus 5 e78a72d516 Память JVM задаётся раздельно, автоперезапуск можно отменить
Панель принудительно ставила -Xms равным -Xmx: куча не растёт на ходу, но
JVM обязана занять весь объём при старте. На машине с 5 ГБ ОЗУ сервер с
4g так вообще не поднимался — «Failed to allocate initial Java heap».

Теперь -Xmx задаётся как раньше, а -Xms стал отдельным полем профиля и по
умолчанию пуст: JVM берёт память по мере надобности. Пара значений
проверяется при сохранении профиля, а не при запуске сервера, — про
опечатку лучше узнать из формы, чем из лога упавшего сервера.

Там же в супервизоре: ожидание автоподъёма стало отдельным состоянием.
Раньше упавший сервер уходил в цикл «падение — пауза 10 секунд — падение»,
причём во время паузы он считался остановленным, и прервать это было
нечем. Теперь паузу видно в статусе с обратным отсчётом и её можно
отменить, а после трёх падений сразу после запуска подряд панель
перестаёт поднимать сервер сама: так проявляется нехватка памяти или
битый конфиг, и перезапуск тут не помогает.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 02:25:16 +03:00

113 lines
3.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package pzconfig
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
)
// javaLauncherFile — файл, из которого сервер берёт аргументы JVM.
const javaLauncherFile = "ProjectZomboid64.json"
var (
heapArgRe = regexp.MustCompile(`^-Xm[sx]`)
memoryRe = regexp.MustCompile(`^([0-9]+)([kKmMgG])$`)
)
// ParseMemory переводит запись вида "4g" в байты. Возвращает 0, если строка
// пустая, и ошибку, если формат непонятен.
func ParseMemory(value string) (int64, error) {
value = strings.TrimSpace(value)
if value == "" {
return 0, nil
}
match := memoryRe.FindStringSubmatch(value)
if match == nil {
return 0, fmt.Errorf("объём памяти должен выглядеть как 4g или 8192m, получено %q", value)
}
amount, err := strconv.ParseInt(match[1], 10, 64)
if err != nil {
return 0, fmt.Errorf("не разобран объём памяти %q", value)
}
switch strings.ToLower(match[2]) {
case "k":
return amount * 1024, nil
case "m":
return amount * 1024 * 1024, nil
default:
return amount * 1024 * 1024 * 1024, nil
}
}
// SetJavaMemory прописывает границы кучи JVM в ProjectZomboid64.json. Именно
// оттуда start-server.sh берёт -Xmx и -Xms, поэтому менять размер памяти в
// аргументах запуска бесполезно.
//
// maxMemory задаёт -Xmx (верхнюю границу), minMemory — -Xms. Пустой minMemory
// означает, что -Xms не выставляется вовсе: JVM тогда занимает память по мере
// надобности. Равные значения удобны тем, что куча не растёт на ходу, но JVM
// обязана занять весь объём при старте — на машине с небольшой ОЗУ сервер
// просто не поднимется.
func SetJavaMemory(serverDir, maxMemory, minMemory string) error {
maxMemory = strings.TrimSpace(maxMemory)
minMemory = strings.TrimSpace(minMemory)
if maxMemory == "" && minMemory == "" {
return nil
}
maxBytes, err := ParseMemory(maxMemory)
if err != nil {
return err
}
minBytes, err := ParseMemory(minMemory)
if err != nil {
return err
}
// JVM с -Xms больше -Xmx не стартует вообще, поэтому ловим это здесь, а не
// в логе упавшего сервера.
if maxBytes > 0 && minBytes > maxBytes {
return fmt.Errorf("начальный размер кучи (%s) больше максимального (%s)", minMemory, maxMemory)
}
path := filepath.Join(serverDir, javaLauncherFile)
raw, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("прочитать %s: %w", path, err)
}
// Разбираем в map, чтобы не потерять поля, о которых мы не знаем.
var doc map[string]any
if err := json.Unmarshal(raw, &doc); err != nil {
return fmt.Errorf("разобрать %s: %w", path, err)
}
args, _ := doc["vmArgs"].([]any)
updated := make([]any, 0, len(args)+2)
for _, a := range args {
if str, ok := a.(string); ok && heapArgRe.MatchString(str) {
continue
}
updated = append(updated, a)
}
if minMemory != "" {
updated = append(updated, "-Xms"+minMemory)
}
if maxMemory != "" {
updated = append(updated, "-Xmx"+maxMemory)
}
doc["vmArgs"] = updated
out, err := json.MarshalIndent(doc, "", " ")
if err != nil {
return err
}
if err := backupFile(path); err != nil {
return err
}
return writeAtomic(path, out, 0o644)
}