Newer
Older
KeeperJerry_Launcher / Launcher / source / helper / JVMHelper.java
package launcher.helper;

import java.io.File;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.util.Locale;

import com.sun.management.OperatingSystemMXBean;
import launcher.LauncherAPI;
import sun.misc.URLClassPath;

public final class JVMHelper {
	// System properties
	@LauncherAPI public static final OS OS_TYPE = OS.byName(System.getProperty("os.name"));
	@LauncherAPI public static final int OS_BITS = getCorrectOSArch();
	@LauncherAPI public static final int JVM_BITS = Integer.parseInt(System.getProperty("sun.arch.data.model"));
	@LauncherAPI public static final int RAM = getRAMAmount();

	// Public static fields
	@LauncherAPI public static final Runtime RUNTIME = Runtime.getRuntime();
	@LauncherAPI public static final URLClassLoader LOADER = (URLClassLoader) ClassLoader.getSystemClassLoader();
	@LauncherAPI public static final URLClassPath UCP = getURLClassPath();

	// Useful internal fields and constants
	private static final String JAVA_LIBRARY_PATH = "java.library.path";
	private static final Field USR_PATHS_FIELD = getUsrPathsField();
	private static final Field SYS_PATHS_FIELD = getSysPathsField();

	private JVMHelper() {
	}

	@LauncherAPI
	public static void addNativePath(Path path) {
		String stringPath = path.toString();

		// Add to library path
		String libraryPath = System.getProperty(JAVA_LIBRARY_PATH);
		if (libraryPath == null || libraryPath.isEmpty()) {
			libraryPath = stringPath;
		} else {
			libraryPath += File.pathSeparatorChar + stringPath;
		}
		System.setProperty(JAVA_LIBRARY_PATH, libraryPath);

		// Reset usrPaths and sysPaths cache
		try {
			USR_PATHS_FIELD.set(null, null);
			SYS_PATHS_FIELD.set(null, null);
		} catch (IllegalAccessException e) {
			throw new InternalError(e);
		}
	}

	@LauncherAPI
	@SuppressWarnings("CallToSystemGC")
	public static void fullGC() {
		RUNTIME.gc();
		RUNTIME.runFinalization();
		LogHelper.debug("Used heap: %d MiB", RUNTIME.totalMemory() - RUNTIME.freeMemory() >> 20);
	}

	@LauncherAPI
	public static void halt0(int status) {
		try {
			getMethod(Class.forName("java.lang.Shutdown"), "halt0", int.class).invoke(null, status);
		} catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
			throw new InternalError(e);
		}
	}

	@LauncherAPI
	public static boolean isJVMMatchesSystemArch() {
		return JVM_BITS == OS_BITS;
	}

	@LauncherAPI
	public static void verifySystemProperties(Class<?> mainClass) {
		Locale.setDefault(Locale.US);

		// Verify class loader
		LogHelper.debug("Verifying class loader");
		if (!mainClass.getClassLoader().equals(LOADER)) {
			throw new SecurityException("ClassLoader should be system");
		}

		// Verify system and java architecture
		LogHelper.debug("Verifying JVM architecture");
		if (!isJVMMatchesSystemArch()) {
			LogHelper.warning("Java and OS architecture mismatch");
			LogHelper.warning("It's recommended to download %d-bit JRE", OS_BITS);
		}

		// Disable SNI extensions (fixes some sites SSL unrecognized_name)
		LogHelper.debug("Disabling SNI extensions (SSL fix)");
		System.setProperty("jsse.enableSNIExtension", Boolean.toString(false));
	}

	@SuppressWarnings("CallToSystemGetenv")
	private static int getCorrectOSArch() {
		// As always, mustdie must die
		if (OS_TYPE == OS.MUSTDIE) {
			return System.getenv("ProgramFiles(x86)") == null ? 32 : 64;
		}

		// Or trust system property (maybe incorrect)
		return System.getProperty("os.arch").contains("64") ? 64 : 32;
	}

	private static Field getField(Class<?> clazz, String name) throws NoSuchFieldException {
		Field field = clazz.getDeclaredField(name);
		field.setAccessible(true);
		return field;
	}

	private static Method getMethod(Class<?> clazz, String name, Class<?>... params) throws NoSuchMethodException {
		Method method = clazz.getDeclaredMethod(name, params);
		method.setAccessible(true);
		return method;
	}

	private static int getRAMAmount() {
		int physicalRam = (int) (((OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean()).getTotalPhysicalMemorySize() >> 20);
		return Math.min(physicalRam, OS_BITS == 32 ? 1536 : 4096); // Limit 32-bit OS to 1536 MiB, and 64-bit OS to 4096 MiB (because it's enough)
	}

	private static Field getSysPathsField() {
		try {
			return getField(ClassLoader.class, "sys_paths");
		} catch (NoSuchFieldException e) {
			throw new InternalError(e);
		}
	}

	private static URLClassPath getURLClassPath() {
		try {
			return (URLClassPath) getField(URLClassLoader.class, "ucp").get(LOADER);
		} catch (NoSuchFieldException | IllegalAccessException e) {
			throw new InternalError(e);
		}
	}

	private static Field getUsrPathsField() {
		try {
			return getField(ClassLoader.class, "usr_paths");
		} catch (NoSuchFieldException e) {
			throw new InternalError(e);
		}
	}

	@LauncherAPI
	public enum OS {
		MUSTDIE("mustdie"), LINUX("linux"), MACOSX("macosx");
		public final String name;

		OS(String name) {
			this.name = name;
		}

		public static OS byName(String name) {
			if (name.startsWith("Windows")) {
				return MUSTDIE;
			}
			if (name.startsWith("Linux")) {
				return LINUX;
			}
			if (name.startsWith("Mac OS X")) {
				return MACOSX;
			}
			throw new RuntimeException(String.format("This shit is not yet supported: '%s'", name));
		}
	}
}