From 56de7e7177f8ba34281e6a03d90953cae7fa05b4 Mon Sep 17 00:00:00 2001 From: Armin Samii Date: Thu, 18 Dec 2025 15:46:56 -0500 Subject: [PATCH 1/5] Menu option to restart with increased memory --- src/main/java/module-info.java | 2 + .../brightspots/rcv/ApplicationRestarter.java | 173 ++++++++++++++++++ .../brightspots/rcv/GuiConfigController.java | 118 ++++++++++++ .../java/network/brightspots/rcv/Main.java | 1 + .../brightspots/rcv/MemoryManager.java | 99 ++++++++++ .../brightspots/rcv/GuiConfigLayout.fxml | 4 +- 6 files changed, 396 insertions(+), 1 deletion(-) create mode 100644 src/main/java/network/brightspots/rcv/ApplicationRestarter.java create mode 100644 src/main/java/network/brightspots/rcv/MemoryManager.java diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index b8534928..c6e0c9db 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -4,6 +4,8 @@ requires com.fasterxml.jackson.databind; requires com.fasterxml.jackson.dataformat.xml; requires java.logging; + requires java.management; + requires jdk.management; requires javafx.base; requires javafx.controls; requires javafx.fxml; diff --git a/src/main/java/network/brightspots/rcv/ApplicationRestarter.java b/src/main/java/network/brightspots/rcv/ApplicationRestarter.java new file mode 100644 index 00000000..af8a6013 --- /dev/null +++ b/src/main/java/network/brightspots/rcv/ApplicationRestarter.java @@ -0,0 +1,173 @@ +/* + * RCTab + * Copyright (c) 2017-2023 Bright Spots Developers. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +/* + * Purpose: Utility class for restarting the RCTab application with new JVM arguments. + * Design: Uses ProcessBuilder to launch a new instance with updated memory settings. + * Conditions: Always. + * Version history: see https://github.com/BrightSpots/rcv. + */ + +package network.brightspots.rcv; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javafx.application.Platform; + +/** + * Utility class for restarting the RCTab application with new JVM arguments. + * Handles cross-platform restart mechanisms for Windows, macOS, and Linux. + */ +class ApplicationRestarter { + + /** + * Restart the application with specified max heap size. + * Launches a new process with the updated -Xmx parameter and exits the current instance. + * + * @param maxHeapMB maximum heap size in megabytes + * @return true if restart initiated successfully, false otherwise + */ + static boolean restartWithMemory(long maxHeapMB) { + try { + // Get current java executable path + String javaPath = getJavaExecutablePath(); + Logger.info("Java executable path: %s", javaPath); + + // Build command to restart application + List command = buildRestartCommand(javaPath, maxHeapMB); + Logger.info("Restart command: %s", String.join(" ", command)); + + // Start new process + ProcessBuilder builder = new ProcessBuilder(command); + // Inherit the working directory from current process + builder.directory(new File(System.getProperty("user.dir"))); + // Redirect error stream to output for debugging + builder.redirectErrorStream(true); + + Process process = builder.start(); + Logger.info("New RCTab process started with PID (if available)"); + + // Give the new process a moment to start before we exit + Thread.sleep(500); + + // Exit current instance + Logger.info("Restarting RCTab with %d MB heap. Shutting down current instance...", + maxHeapMB); + Platform.exit(); + System.exit(0); + + return true; + } catch (IOException e) { + Logger.severe("Failed to restart application (IO error): %s", e.getMessage()); + return false; + } catch (InterruptedException e) { + Logger.severe("Restart interrupted: %s", e.getMessage()); + Thread.currentThread().interrupt(); + return false; + } catch (Exception e) { + Logger.severe("Failed to restart application: %s", e.getMessage()); + return false; + } + } + + /** + * Get path to java executable. + * Checks JAVA_HOME first, then uses 'java' from PATH as fallback. + * + * @return path to java executable + */ + private static String getJavaExecutablePath() { + String javaHome = System.getProperty("java.home"); + if (javaHome != null && !javaHome.isEmpty()) { + String javaBin = javaHome + File.separator + "bin" + File.separator + "java"; + if (isWindows()) { + javaBin += ".exe"; + } + File javaFile = new File(javaBin); + if (javaFile.exists()) { + return javaBin; + } + Logger.warning("Java executable not found at %s, falling back to 'java' from PATH", + javaBin); + } + // Fallback to PATH + return isWindows() ? "java.exe" : "java"; + } + + /** + * Build command line for restarting the application. + * Reconstructs: java -Xmx{mem}m --module-path {path} --module {module} + * + * @param javaPath path to java executable + * @param maxHeapMB maximum heap size in MB + * @return command as list of strings for ProcessBuilder + */ + private static List buildRestartCommand(String javaPath, long maxHeapMB) { + List command = new ArrayList<>(); + + // Java executable + command.add(javaPath); + + // Memory parameter + command.add("-Xmx" + maxHeapMB + "m"); + + // Get module path from current runtime + String modulePath = System.getProperty("jdk.module.path"); + if (modulePath != null && !modulePath.isEmpty()) { + command.add("--module-path"); + command.add(modulePath); + Logger.info("Using module path: %s", modulePath); + } else { + Logger.warning("jdk.module.path not set. Restart may not work correctly."); + Logger.warning("This is expected when running from an IDE during development."); + + // Try to use classpath as fallback + String classPath = System.getProperty("java.class.path"); + if (classPath != null && !classPath.isEmpty()) { + command.add("-cp"); + command.add(classPath); + Logger.info("Using classpath fallback: %s", classPath); + } + } + + // Add module and main class + if (modulePath != null && !modulePath.isEmpty()) { + command.add("--module"); + command.add("network.brightspots.rcv/network.brightspots.rcv.Main"); + } else { + // If no module path, use direct class launch + command.add("network.brightspots.rcv.Main"); + } + + return command; + } + + /** + * Check if running on Windows. + * + * @return true if OS is Windows + */ + private static boolean isWindows() { + String osName = System.getProperty("os.name"); + return osName != null && osName.toLowerCase().contains("win"); + } + + /** + * Check if running on macOS. + * + * @return true if OS is macOS + */ + @SuppressWarnings("unused") + private static boolean isMac() { + String osName = System.getProperty("os.name"); + return osName != null && osName.toLowerCase().contains("mac"); + } +} diff --git a/src/main/java/network/brightspots/rcv/GuiConfigController.java b/src/main/java/network/brightspots/rcv/GuiConfigController.java index 7cac988d..ab260b7c 100644 --- a/src/main/java/network/brightspots/rcv/GuiConfigController.java +++ b/src/main/java/network/brightspots/rcv/GuiConfigController.java @@ -591,6 +591,124 @@ public void menuItemConvertToCdfClicked() { } } + /** + * Action when "Increase RCTab Memory (Requires Restart)" menu item is clicked. + * Calculates optimal memory, shows confirmation dialog, and restarts the app with new -Xmx + * setting. + */ + public void menuItemIncreaseMemoryClicked() { + if (guiIsBusy) { + Logger.warning("Cannot change memory while an operation is in progress."); + showInfoDialog( + "Operation in Progress", + "Please wait for the current operation to complete before changing memory settings."); + return; + } + + // Calculate recommended memory + long recommendedMB = MemoryManager.calculateRecommendedMemoryMB(); + long currentMB = MemoryManager.getCurrentMaxHeapMB(); + + if (recommendedMB <= 0) { + showErrorDialog( + "Unable to Determine Memory", + "Could not determine your system's total RAM. Please restart RCTab manually " + + "with the -Xmx parameter to increase memory.\n\n" + + "Example: java -Xmx12800m -p app -m network.brightspots.rcv/network.brightspots.rcv.Main"); + return; + } + + // Check if recommended is less than or equal to current + if (recommendedMB <= currentMB) { + showInfoDialog( + "Memory Already Optimized", + String.format( + "RCTab is already running with %s of memory.\n\n" + + "Recommended memory based on your system (80%% of total RAM): %s\n\n" + + "No restart needed.", + MemoryManager.formatMemorySize(currentMB), + MemoryManager.formatMemorySize(recommendedMB))); + return; + } + + // Show confirmation dialog + boolean confirmed = showMemoryIncreaseConfirmation(currentMB, recommendedMB); + if (confirmed) { + // Check for unsaved changes + if (!checkForSaveAndContinue()) { + return; // User cancelled + } + + // Attempt restart + boolean success = ApplicationRestarter.restartWithMemory(recommendedMB); + if (!success) { + showErrorDialog( + "Restart Failed", + String.format( + "Unable to restart RCTab automatically. Please restart manually with:\n\n" + + "java -Xmx%dm -p app -m network.brightspots.rcv/network.brightspots.rcv.Main", + recommendedMB)); + } + } + } + + /** + * Show confirmation dialog for memory increase. + * + * @param currentMB current max heap in MB + * @param recommendedMB recommended max heap in MB + * @return true if user confirmed, false otherwise + */ + private boolean showMemoryIncreaseConfirmation(long currentMB, long recommendedMB) { + ButtonType restartButton = new ButtonType("Restart Now", ButtonBar.ButtonData.YES); + ButtonType cancelButton = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE); + + Alert alert = + new Alert( + AlertType.CONFIRMATION, + String.format( + "RCTab will restart with increased memory.\n\n" + + "Current memory: %s\n" + + "New memory: %s\n\n" + + "This will close RCTab and reopen it with the new memory settings.\n" + + "Any unsaved changes will be lost if not saved.", + MemoryManager.formatMemorySize(currentMB), + MemoryManager.formatMemorySize(recommendedMB)), + restartButton, + cancelButton); + alert.setTitle("Increase RCTab Memory"); + alert.setHeaderText("Confirm Application Restart"); + + Optional result = alert.showAndWait(); + return result.isPresent() && result.get() == restartButton; + } + + /** + * Show error dialog. + * + * @param title dialog title + * @param message dialog message + */ + private void showErrorDialog(String title, String message) { + Alert alert = new Alert(AlertType.ERROR, message, ButtonType.OK); + alert.setTitle(title); + alert.setHeaderText(null); + alert.showAndWait(); + } + + /** + * Show info dialog. + * + * @param title dialog title + * @param message dialog message + */ + private void showInfoDialog(String title, String message) { + Alert alert = new Alert(AlertType.INFORMATION, message, ButtonType.OK); + alert.setTitle(title); + alert.setHeaderText(null); + alert.showAndWait(); + } + private void sessionDone(GenericService service) { setGuiIsBusy(false); diff --git a/src/main/java/network/brightspots/rcv/Main.java b/src/main/java/network/brightspots/rcv/Main.java index b59cc509..3bde7d33 100644 --- a/src/main/java/network/brightspots/rcv/Main.java +++ b/src/main/java/network/brightspots/rcv/Main.java @@ -135,5 +135,6 @@ private static void logSystemInfo() { Logger.info( "Host system: %s version %s", System.getProperty("os.name"), System.getProperty("os.version")); + Logger.info("Max heap size: %d MB", Runtime.getRuntime().maxMemory() / (1024 * 1024)); } } diff --git a/src/main/java/network/brightspots/rcv/MemoryManager.java b/src/main/java/network/brightspots/rcv/MemoryManager.java new file mode 100644 index 00000000..ea937f95 --- /dev/null +++ b/src/main/java/network/brightspots/rcv/MemoryManager.java @@ -0,0 +1,99 @@ +/* + * RCTab + * Copyright (c) 2017-2023 Bright Spots Developers. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +/* + * Purpose: Utility class for calculating system memory and determining optimal heap size. + * Design: Uses JMX to query system memory, applies 80% rule with 512MB rounding. + * Conditions: Always. + * Version history: see https://github.com/BrightSpots/rcv. + */ + +package network.brightspots.rcv; + +import com.sun.management.OperatingSystemMXBean; +import java.lang.management.ManagementFactory; + +/** + * Utility class for managing memory-related calculations and system queries. + * Provides methods to determine system RAM, current JVM heap size, and calculate + * optimal heap allocation based on available system resources. + */ +class MemoryManager { + + private static final long MEGABYTE = 1024L * 1024L; + private static final long CHUNK_SIZE_MB = 512L; + private static final double PERCENTAGE = 0.80; // 80% of total RAM + + /** + * Get total physical memory in MB. + * Uses com.sun.management.OperatingSystemMXBean for cross-platform compatibility. + * + * @return total physical RAM in MB, or -1 if cannot determine + */ + static long getTotalPhysicalMemoryMB() { + try { + OperatingSystemMXBean osBean = + (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); + long totalMemoryBytes = osBean.getTotalPhysicalMemorySize(); + if (totalMemoryBytes > 0) { + return totalMemoryBytes / MEGABYTE; + } + } catch (Exception e) { + Logger.warning("Unable to determine total physical memory: %s", e.getMessage()); + } + return -1; + } + + /** + * Get current max heap size the JVM is running with. + * + * @return current max heap in MB + */ + static long getCurrentMaxHeapMB() { + return Runtime.getRuntime().maxMemory() / MEGABYTE; + } + + /** + * Calculate recommended memory: 80% of RAM, rounded down to nearest 512MB. + * Examples: + * - 8GB (8192MB) RAM → 6144MB (6GB) + * - 16GB (16384MB) RAM → 12800MB (12.5GB) + * - 32GB (32768MB) RAM → 26112MB (25.5GB) + * + * @return recommended heap size in MB, or -1 if unable to determine + */ + static long calculateRecommendedMemoryMB() { + long totalMB = getTotalPhysicalMemoryMB(); + if (totalMB <= 0) { + Logger.warning("Cannot calculate recommended memory: total physical memory unknown"); + return -1; + } + + long eightyPercent = (long) (totalMB * PERCENTAGE); + // Round down to nearest 512MB chunk + long recommended = (eightyPercent / CHUNK_SIZE_MB) * CHUNK_SIZE_MB; + + Logger.info( + "Memory calculation: Total RAM = %d MB, 80%% = %d MB, Rounded = %d MB", + totalMB, eightyPercent, recommended); + + return recommended; + } + + /** + * Format memory size for display. + * + * @param memoryMB memory size in megabytes + * @return formatted string like "6144 MB (6.0 GB)" + */ + static String formatMemorySize(long memoryMB) { + double gb = memoryMB / 1024.0; + return String.format("%d MB (%.1f GB)", memoryMB, gb); + } +} diff --git a/src/main/resources/network/brightspots/rcv/GuiConfigLayout.fxml b/src/main/resources/network/brightspots/rcv/GuiConfigLayout.fxml index 6c6c1d0d..0e7dacf2 100644 --- a/src/main/resources/network/brightspots/rcv/GuiConfigLayout.fxml +++ b/src/main/resources/network/brightspots/rcv/GuiConfigLayout.fxml @@ -34,7 +34,9 @@ - + + From 9bce024e136b4be6eb28bdd0a34e27d1fc35b92d Mon Sep 17 00:00:00 2001 From: Armin Samii Date: Thu, 18 Dec 2025 16:41:36 -0500 Subject: [PATCH 2/5] lint --- .../brightspots/rcv/ApplicationRestarter.java | 19 ++-- .../brightspots/rcv/GuiConfigController.java | 93 ++++++++----------- .../brightspots/rcv/MemoryManager.java | 22 ++--- 3 files changed, 60 insertions(+), 74 deletions(-) diff --git a/src/main/java/network/brightspots/rcv/ApplicationRestarter.java b/src/main/java/network/brightspots/rcv/ApplicationRestarter.java index af8a6013..f76b96df 100644 --- a/src/main/java/network/brightspots/rcv/ApplicationRestarter.java +++ b/src/main/java/network/brightspots/rcv/ApplicationRestarter.java @@ -1,6 +1,6 @@ /* * RCTab - * Copyright (c) 2017-2023 Bright Spots Developers. + * Copyright (c) 2017-2025 Bright Spots Developers. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -32,17 +32,17 @@ class ApplicationRestarter { * Restart the application with specified max heap size. * Launches a new process with the updated -Xmx parameter and exits the current instance. * - * @param maxHeapMB maximum heap size in megabytes + * @param maxHeapMb maximum heap size in megabytes * @return true if restart initiated successfully, false otherwise */ - static boolean restartWithMemory(long maxHeapMB) { + static boolean restartWithMemory(long maxHeapMb) { try { // Get current java executable path String javaPath = getJavaExecutablePath(); Logger.info("Java executable path: %s", javaPath); // Build command to restart application - List command = buildRestartCommand(javaPath, maxHeapMB); + List command = buildRestartCommand(javaPath, maxHeapMb + "m"); Logger.info("Restart command: %s", String.join(" ", command)); // Start new process @@ -53,14 +53,14 @@ static boolean restartWithMemory(long maxHeapMB) { builder.redirectErrorStream(true); Process process = builder.start(); - Logger.info("New RCTab process started with PID (if available)"); + Logger.info("New RCTab process started with PID " + process.pid()); // Give the new process a moment to start before we exit Thread.sleep(500); // Exit current instance Logger.info("Restarting RCTab with %d MB heap. Shutting down current instance...", - maxHeapMB); + maxHeapMb); Platform.exit(); System.exit(0); @@ -107,17 +107,17 @@ private static String getJavaExecutablePath() { * Reconstructs: java -Xmx{mem}m --module-path {path} --module {module} * * @param javaPath path to java executable - * @param maxHeapMB maximum heap size in MB + * @param maxHeapString maximum heap size string, e.g. "2048m" * @return command as list of strings for ProcessBuilder */ - private static List buildRestartCommand(String javaPath, long maxHeapMB) { + public static List buildRestartCommand(String javaPath, String maxHeapString) { List command = new ArrayList<>(); // Java executable command.add(javaPath); // Memory parameter - command.add("-Xmx" + maxHeapMB + "m"); + command.add("-Xmx" + maxHeapString); // Get module path from current runtime String modulePath = System.getProperty("jdk.module.path"); @@ -127,7 +127,6 @@ private static List buildRestartCommand(String javaPath, long maxHeapMB) Logger.info("Using module path: %s", modulePath); } else { Logger.warning("jdk.module.path not set. Restart may not work correctly."); - Logger.warning("This is expected when running from an IDE during development."); // Try to use classpath as fallback String classPath = System.getProperty("java.class.path"); diff --git a/src/main/java/network/brightspots/rcv/GuiConfigController.java b/src/main/java/network/brightspots/rcv/GuiConfigController.java index ab260b7c..9f92b242 100644 --- a/src/main/java/network/brightspots/rcv/GuiConfigController.java +++ b/src/main/java/network/brightspots/rcv/GuiConfigController.java @@ -606,60 +606,59 @@ public void menuItemIncreaseMemoryClicked() { } // Calculate recommended memory - long recommendedMB = MemoryManager.calculateRecommendedMemoryMB(); - long currentMB = MemoryManager.getCurrentMaxHeapMB(); + long recommendedMb = MemoryManager.calculateRecommendedMemoryMb(); + long currentMb = MemoryManager.getCurrentMaxHeapMb(); - if (recommendedMB <= 0) { + if (recommendedMb <= 0) { + String restartCommand = String.join(" ", + ApplicationRestarter.buildRestartCommand("java", "12800m")); showErrorDialog( "Unable to Determine Memory", "Could not determine your system's total RAM. Please restart RCTab manually " - + "with the -Xmx parameter to increase memory.\n\n" - + "Example: java -Xmx12800m -p app -m network.brightspots.rcv/network.brightspots.rcv.Main"); + + "with the -Xmx parameter to increase memory. Example: " + + restartCommand + + "\n\n"); return; } // Check if recommended is less than or equal to current - if (recommendedMB <= currentMB) { + if (recommendedMb <= currentMb) { showInfoDialog( "Memory Already Optimized", String.format( - "RCTab is already running with %s of memory.\n\n" - + "Recommended memory based on your system (80%% of total RAM): %s\n\n" + "RCTab is already running with %s of memory.%n%n" + + "Recommended memory based on your system (80%% of total RAM): %s%n%n" + "No restart needed.", - MemoryManager.formatMemorySize(currentMB), - MemoryManager.formatMemorySize(recommendedMB))); + MemoryManager.formatMemorySize(currentMb), + MemoryManager.formatMemorySize(recommendedMb))); return; } // Show confirmation dialog - boolean confirmed = showMemoryIncreaseConfirmation(currentMB, recommendedMB); - if (confirmed) { - // Check for unsaved changes - if (!checkForSaveAndContinue()) { - return; // User cancelled - } + boolean confirmed = showMemoryIncreaseConfirmation(currentMb, recommendedMb); + if (!confirmed) { + return; + } - // Attempt restart - boolean success = ApplicationRestarter.restartWithMemory(recommendedMB); - if (!success) { - showErrorDialog( - "Restart Failed", - String.format( - "Unable to restart RCTab automatically. Please restart manually with:\n\n" - + "java -Xmx%dm -p app -m network.brightspots.rcv/network.brightspots.rcv.Main", - recommendedMB)); - } + // Check for unsaved changes + if (!checkForSaveAndContinue()) { + return; + } + + // Attempt restart + boolean success = ApplicationRestarter.restartWithMemory(recommendedMb); + if (!success) { + String restartCommand = String.join(" ", + ApplicationRestarter.buildRestartCommand("java", recommendedMb + "m")); + showErrorDialog( + "Restart Failed", + String.format( + "Unable to restart RCTab automatically. Please restart manually with: %s%n%n", + restartCommand)); } } - /** - * Show confirmation dialog for memory increase. - * - * @param currentMB current max heap in MB - * @param recommendedMB recommended max heap in MB - * @return true if user confirmed, false otherwise - */ - private boolean showMemoryIncreaseConfirmation(long currentMB, long recommendedMB) { + private boolean showMemoryIncreaseConfirmation(long currentMb, long recommendedMb) { ButtonType restartButton = new ButtonType("Restart Now", ButtonBar.ButtonData.YES); ButtonType cancelButton = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE); @@ -667,13 +666,13 @@ private boolean showMemoryIncreaseConfirmation(long currentMB, long recommendedM new Alert( AlertType.CONFIRMATION, String.format( - "RCTab will restart with increased memory.\n\n" - + "Current memory: %s\n" - + "New memory: %s\n\n" - + "This will close RCTab and reopen it with the new memory settings.\n" - + "Any unsaved changes will be lost if not saved.", - MemoryManager.formatMemorySize(currentMB), - MemoryManager.formatMemorySize(recommendedMB)), + "RCTab will restart with increased memory.%n%n" + + "Current memory: %s%n" + + "New memory: %s%n%n" + + "This will close RCTab and reopen it with the new memory settings.%n" + + "Any unsaved changes will be lost.", + MemoryManager.formatMemorySize(currentMb), + MemoryManager.formatMemorySize(recommendedMb)), restartButton, cancelButton); alert.setTitle("Increase RCTab Memory"); @@ -683,12 +682,6 @@ private boolean showMemoryIncreaseConfirmation(long currentMB, long recommendedM return result.isPresent() && result.get() == restartButton; } - /** - * Show error dialog. - * - * @param title dialog title - * @param message dialog message - */ private void showErrorDialog(String title, String message) { Alert alert = new Alert(AlertType.ERROR, message, ButtonType.OK); alert.setTitle(title); @@ -696,12 +689,6 @@ private void showErrorDialog(String title, String message) { alert.showAndWait(); } - /** - * Show info dialog. - * - * @param title dialog title - * @param message dialog message - */ private void showInfoDialog(String title, String message) { Alert alert = new Alert(AlertType.INFORMATION, message, ButtonType.OK); alert.setTitle(title); diff --git a/src/main/java/network/brightspots/rcv/MemoryManager.java b/src/main/java/network/brightspots/rcv/MemoryManager.java index ea937f95..8ac33be5 100644 --- a/src/main/java/network/brightspots/rcv/MemoryManager.java +++ b/src/main/java/network/brightspots/rcv/MemoryManager.java @@ -36,7 +36,7 @@ class MemoryManager { * * @return total physical RAM in MB, or -1 if cannot determine */ - static long getTotalPhysicalMemoryMB() { + static long getTotalPhysicalMemoryMb() { try { OperatingSystemMXBean osBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); @@ -55,7 +55,7 @@ static long getTotalPhysicalMemoryMB() { * * @return current max heap in MB */ - static long getCurrentMaxHeapMB() { + static long getCurrentMaxHeapMb() { return Runtime.getRuntime().maxMemory() / MEGABYTE; } @@ -68,20 +68,20 @@ static long getCurrentMaxHeapMB() { * * @return recommended heap size in MB, or -1 if unable to determine */ - static long calculateRecommendedMemoryMB() { - long totalMB = getTotalPhysicalMemoryMB(); - if (totalMB <= 0) { + static long calculateRecommendedMemoryMb() { + long totalMb = getTotalPhysicalMemoryMb(); + if (totalMb <= 0) { Logger.warning("Cannot calculate recommended memory: total physical memory unknown"); return -1; } - long eightyPercent = (long) (totalMB * PERCENTAGE); + long eightyPercent = (long) (totalMb * PERCENTAGE); // Round down to nearest 512MB chunk long recommended = (eightyPercent / CHUNK_SIZE_MB) * CHUNK_SIZE_MB; Logger.info( "Memory calculation: Total RAM = %d MB, 80%% = %d MB, Rounded = %d MB", - totalMB, eightyPercent, recommended); + totalMb, eightyPercent, recommended); return recommended; } @@ -89,11 +89,11 @@ static long calculateRecommendedMemoryMB() { /** * Format memory size for display. * - * @param memoryMB memory size in megabytes + * @param memoryMb memory size in megabytes * @return formatted string like "6144 MB (6.0 GB)" */ - static String formatMemorySize(long memoryMB) { - double gb = memoryMB / 1024.0; - return String.format("%d MB (%.1f GB)", memoryMB, gb); + static String formatMemorySize(long memoryMb) { + double gb = memoryMb / 1024.0; + return String.format("%d MB (%.1f GB)", memoryMb, gb); } } From 0502fed33412bc4851d6d5c4cf1f4b37a71ecc62 Mon Sep 17 00:00:00 2001 From: Armin Samii Date: Mon, 29 Dec 2025 10:37:16 -0500 Subject: [PATCH 3/5] clean up & lint --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 215e5496..558631e2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,7 +48,7 @@ jobs: needs: deleteRelease strategy: matrix: - os: [ ubuntu-latest, windows-latest, macos-15-intel, macos-latest ] # -latest is ARM + os: [ ubuntu-latest, windows-latest, macos-13, macos-latest ] # -13 is intel; -latest is ARM steps: - uses: actions/checkout@v3 @@ -165,7 +165,7 @@ jobs: shaA: 512 - name: "Prepare keychain" - if: matrix.os == 'macOS-latest' || matrix.os == 'macOS-15-intel' + if: matrix.os == 'macOS-latest' || matrix.os == 'macOS-13' env: MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }} @@ -191,7 +191,7 @@ jobs: final-filepath: ${{ steps.exefn.outputs.FILEPATH }} - name: "Notarize app bundle" - if: matrix.os == 'macOS-latest' || matrix.os == 'macOS-15-intel' + if: matrix.os == 'macOS-latest' || matrix.os == 'macOS-13' env: MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }} MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }} From 0d44f80a67d519ead663539f750b74242007c9a7 Mon Sep 17 00:00:00 2001 From: Armin Samii Date: Tue, 6 Jan 2026 15:16:08 -0500 Subject: [PATCH 4/5] clean up & lint --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 558631e2..215e5496 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,7 +48,7 @@ jobs: needs: deleteRelease strategy: matrix: - os: [ ubuntu-latest, windows-latest, macos-13, macos-latest ] # -13 is intel; -latest is ARM + os: [ ubuntu-latest, windows-latest, macos-15-intel, macos-latest ] # -latest is ARM steps: - uses: actions/checkout@v3 @@ -165,7 +165,7 @@ jobs: shaA: 512 - name: "Prepare keychain" - if: matrix.os == 'macOS-latest' || matrix.os == 'macOS-13' + if: matrix.os == 'macOS-latest' || matrix.os == 'macOS-15-intel' env: MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }} @@ -191,7 +191,7 @@ jobs: final-filepath: ${{ steps.exefn.outputs.FILEPATH }} - name: "Notarize app bundle" - if: matrix.os == 'macOS-latest' || matrix.os == 'macOS-13' + if: matrix.os == 'macOS-latest' || matrix.os == 'macOS-15-intel' env: MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }} MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }} From bdf0e4ccf1ae40cc561cac954ec3ecd2adceca73 Mon Sep 17 00:00:00 2001 From: Armin Samii Date: Tue, 13 Jan 2026 16:29:26 -0500 Subject: [PATCH 5/5] error messages show java path too --- .../network/brightspots/rcv/ApplicationRestarter.java | 10 +++------- .../network/brightspots/rcv/GuiConfigController.java | 4 ++-- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/main/java/network/brightspots/rcv/ApplicationRestarter.java b/src/main/java/network/brightspots/rcv/ApplicationRestarter.java index f76b96df..0dae8b0d 100644 --- a/src/main/java/network/brightspots/rcv/ApplicationRestarter.java +++ b/src/main/java/network/brightspots/rcv/ApplicationRestarter.java @@ -37,12 +37,8 @@ class ApplicationRestarter { */ static boolean restartWithMemory(long maxHeapMb) { try { - // Get current java executable path - String javaPath = getJavaExecutablePath(); - Logger.info("Java executable path: %s", javaPath); - // Build command to restart application - List command = buildRestartCommand(javaPath, maxHeapMb + "m"); + List command = buildRestartCommand(maxHeapMb + "m"); Logger.info("Restart command: %s", String.join(" ", command)); // Start new process @@ -106,11 +102,11 @@ private static String getJavaExecutablePath() { * Build command line for restarting the application. * Reconstructs: java -Xmx{mem}m --module-path {path} --module {module} * - * @param javaPath path to java executable * @param maxHeapString maximum heap size string, e.g. "2048m" * @return command as list of strings for ProcessBuilder */ - public static List buildRestartCommand(String javaPath, String maxHeapString) { + public static List buildRestartCommand(String maxHeapString) { + String javaPath = getJavaExecutablePath(); List command = new ArrayList<>(); // Java executable diff --git a/src/main/java/network/brightspots/rcv/GuiConfigController.java b/src/main/java/network/brightspots/rcv/GuiConfigController.java index 9f92b242..c3998773 100644 --- a/src/main/java/network/brightspots/rcv/GuiConfigController.java +++ b/src/main/java/network/brightspots/rcv/GuiConfigController.java @@ -611,7 +611,7 @@ public void menuItemIncreaseMemoryClicked() { if (recommendedMb <= 0) { String restartCommand = String.join(" ", - ApplicationRestarter.buildRestartCommand("java", "12800m")); + ApplicationRestarter.buildRestartCommand("12800m")); showErrorDialog( "Unable to Determine Memory", "Could not determine your system's total RAM. Please restart RCTab manually " @@ -649,7 +649,7 @@ public void menuItemIncreaseMemoryClicked() { boolean success = ApplicationRestarter.restartWithMemory(recommendedMb); if (!success) { String restartCommand = String.join(" ", - ApplicationRestarter.buildRestartCommand("java", recommendedMb + "m")); + ApplicationRestarter.buildRestartCommand(recommendedMb + "m")); showErrorDialog( "Restart Failed", String.format(