import java.lang.reflect.Field; import org.openjdk.jol.info.ClassLayout; import org.openjdk.jol.info.GraphLayout; import org.openjdk.jol.vm.VM; /** * What one String really costs: the String object, the byte[] it points to, and the coder byte that says how to read it. * Measured with JOL (org.openjdk.jol:jol-core). Run by run.sh under several JVM flag sets. */ public class StringLayout { static byte coder(String s) throws Exception { Field f = String.class.getDeclaredField("coder"); f.setAccessible(true); // needs --add-opens java.base/java.lang=ALL-UNNAMED return f.getByte(s); } static byte[] value(String s) throws Exception { Field f = String.class.getDeclaredField("value"); f.setAccessible(true); return (byte[]) f.get(s); } static String repeat(char c, int n) { return String.valueOf(c).repeat(n); } public static void main(String[] args) throws Exception { System.out.println("java.version = " + System.getProperty("java.version")); System.out.println("vm.flags = " + String.join(" ", java.lang.management.ManagementFactory.getRuntimeMXBean().getInputArguments() .stream().filter(a -> a.startsWith("-XX")).toList())); System.out.println(VM.current().details().lines().filter(l -> l.startsWith("# Objects are 8 bytes") || l.startsWith("# Field sizes") || l.contains("Array element") || l.contains("compressed")).reduce("", (a, b) -> a + b + "\n")); System.out.println("=== java.lang.String, the class"); System.out.println(ClassLayout.parseClass(String.class).toPrintable()); System.out.println("=== one Latin-1 string and one UTF-16 string, field by field"); System.out.println(ClassLayout.parseInstance("hello").toPrintable()); System.out.println(ClassLayout.parseInstance("h\u20ACllo").toPrintable()); System.out.println("=== the byte[] behind each"); System.out.println(ClassLayout.parseInstance(value("hello")).toPrintable()); System.out.println(ClassLayout.parseInstance(value("h\u20ACllo")).toPrintable()); System.out.println("=== footprint by length (String object + its byte[], via GraphLayout)"); System.out.println("chars content coder byte[].length String byte[] total"); for (int n : new int[] {0, 1, 5, 10, 20, 40, 100, 1000}) { for (char c : new char[] {'a', '\u00E9', '\u20AC'}) { // a = ASCII, e-acute = Latin-1 supplement, euro = needs UTF-16 String s = new String(repeat(c, n).toCharArray()); // a fresh object, never the interned literal long total = GraphLayout.parseInstance(s).totalSize(); long str = ClassLayout.parseInstance(s).instanceSize(); long arr = ClassLayout.parseInstance(value(s)).instanceSize(); System.out.printf("%5d %-8s %5d %13d %6d %6d %5d%n", n, c == 'a' ? "ASCII" : c == '\u00E9' ? "U+00E9" : "U+20AC", coder(s), value(s).length, str, arr, total); } } } }