Code Patterns

Copy-paste examples for common plugin tasks. Commands, events, ECS, GUI, and more.

← Back to Patterns
command

Memory Debug Command

/memory command using CommandBase

Example Code

java
package dev.myplugin.example;

// import dev.myplugin.example.TroubleInTrorkTownPlugin;  // Anonymized
// import dev.myplugin.example.DoubleTapDetector;  // Anonymized
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.server.core.Message;
import com.hypixel.hytale.server.core.command.system.CommandContext;
import com.hypixel.hytale.server.core.command.system.basecommands.CommandBase;
import com.hypixel.hytale.server.core.universe.Universe;
import org.checkerframework.checker.nullness.compatqual.NonNullDecl;

/**
 * Debug command to force garbage collection and display memory statistics.
 * Useful for diagnosing memory leaks vs lazy GC behavior.
 */
public class ExampleCommand extends CommandBase {

	private static final HytaleLogger LOGGER = HytaleLogger.forEnclosingClass();

	public ExampleCommand() {
		super("memory", "Force GC and display memory statistics for debugging.");
	}

	@Override
	protected void executeSync(@NonNullDecl CommandContext commandContext) {
		Runtime rt = Runtime.getRuntime();

		// Memory before GC
		long usedBefore = (rt.totalMemory() - rt.freeMemory()) / 1024 / 1024;
		long totalBefore = rt.totalMemory() / 1024 / 1024;

		commandContext.sendMessage(Message.raw("=== Memory Debug ===").color("#FFAA00"));
		commandContext.sendMessage(Message.raw("Before GC: ").color("#AAAAAA")
				.insert(Message.raw(usedBefore + "MB").color("#FFFFFF"))
				.insert(Message.raw(" / ").color("#AAAAAA"))
				.insert(Message.raw(totalBefore + "MB").color("#FFFFFF")));

		// Force garbage collection
		System.gc();

		// Small delay to let GC complete
		try {
			Thread.sleep(100);
		} catch (InterruptedException ignored) {
		}

		// Memory after GC
		long usedAfter = (rt.totalMemory() - rt.freeMemory()) / 1024 / 1024;
		long totalAfter = rt.totalMemory() / 1024 / 1024;
		long freed = usedBefore - usedAfter;

		commandContext.sendMessage(Message.raw("After GC:  ").color("#AAAAAA")
				.insert(Message.raw(usedAfter + "MB").color("#FFFFFF"))
				.insert(Message.raw(" / ").color("#AAAAAA"))
				.insert(Message.raw(totalAfter + "MB").color("#FFFFFF")));
		commandContext.sendMessage(Message.raw("Freed:     ").color("#AAAAAA")
				.insert(Message.raw(freed + "MB").color("#55FF55")));

		// Additional debug info
		int worldCount = Universe.get().getWorlds().size();
		int gameStateCount = TroubleInTrorkTownPlugin.gameModeStateForWorld.size();
		int playerStateCount = DoubleTapDetector.getInstance().getPlayerStateCount();

		commandContext.sendMessage(Message.raw("=== TTT State ===").color("#FFAA00"));
		commandContext.sendMessage(Message.raw("Worlds in Universe: ").color("#AAAAAA")
				.insert(Message.raw(String.valueOf(worldCount)).color("#FFFFFF")));
		commandContext.sendMessage(Message.raw("GameModeStates: ").color("#AAAAAA")
				.insert(Message.raw(String.valueOf(gameStateCount)).color("#FFFFFF")));
		commandContext.sendMessage(Message.raw("DoubleTap PlayerStates: ").color("#AAAAAA")
				.insert(Message.raw(String.valueOf(playerStateCount)).color("#FFFFFF")));

		// Log to console as well
		LOGGER.atInfo().log("Memory Debug - Before: %dMB, After: %dMB, Freed: %dMB, Worlds: %d, GameStates: %d, PlayerStates: %d",
				usedBefore, usedAfter, freed, worldCount, gameStateCount, playerStateCount);
	}
}