Java 杂谈 获取JVM中String Pool中的所有值

130 阅读2分钟

获取方法

<1> 使用System.gc() 触发一次垃圾回收

<2> 使用sun.misc.GC.maxObjectInspectionAge() 方法获取当前年龄最大的对象

<3> 获取String类中私有属性"INTERNED"

<4> 添加其中年龄小于之前对象的所有字符串

Java8 代码实现

由于Java在最近的版本中移除了GC类,因而对于java 8 可以使用以下方法获取 

    private static void getStringPoolBeforeJava9() {
        //Add String to String Pool
        String s1 = "test", s2 = "test2", s3 = "test";

        //trigger garbage collection
        System.gc();

        //Get the age of the oldest object in the pool
        long oldestAge = sun.misc.GC.maxObjectAge();

        //Get all the strings in the pool
        Set<String> stringSet = getAllStringsInPool(oldestAge);

        //Print all the strings in the pool
        stringSet.forEach(System.out::println);
    }
    
    @SneakyThrows
    private static Set<String> getAllStringsInPool(long maxAge) {
        Set<String> stringPool = new HashSet<>();
        //Get the internal "interned" field from the String class
        Field internedField = String.class.getDeclaredField("INTERNED");
        internedField.setAccessible(true);
        Object[] interned = (Object[]) internedField.get(null);
        
        //Add all the strings in the pool to the set
        Arrays.stream(interned)
            .filter(Objects::nonNull)
            .filter(o -> sun.misc.GC.getObjectAge(o) <= maxAge)
            .forEach(o -> stringPool.add((String)o));
        
        return stringPool;
    }

Steps:

  1. First, we add some strings to the string pool.

  2. Then, we call System.gc() to trigger a garbage collection, which helps ensure that the string pool is in a stable state.

  3. Next, we use the sun.misc.GC.maxObjectInspectionAge() method to get the age of the oldest object in the pool. This will be useful for filtering out objects that have been garbage collected.

  4. In the getAllStringsInPool() method, we use reflection to access the internal INTERNED array in the String class, which contains all the strings in the pool.

  5. We then filter the array to include only the strings that are older than the maximum age we obtained earlier, and add them to a Set to ensure uniqueness.

  6. Finally, we print out all the strings in the pool.

备注:

This code uses internal Sun JDK APIs (sun.misc.GC), which are not part of the standard Java API and may not be available or work the same way in all Java implementations.

Java17 代码实现

而在新的Java 17 中,可以考虑使用StringTable来获取

    private static void getStringPoolJava17() {
        //Add String to String Pool
        String s1 = "test", s2 = "test2", s3 = "test";

        //Get all the strings in the pool
        Set<String> stringSet = getAllStringsInPool();

        //Print all the strings in the pool
        stringSet.forEach(System.out::println);
    }

    @SneakyThrows
    private static Set<String> getAllStringsInPool() {
        var stringTableField = Class.forName("java.lang.StringTable").getDeclaredField("table");
        stringTableField.setAccessible(true);

        var stringTable = stringTableField.get(null);
        var entryField = stringTable.getClass().getDeclaredField("entries");
        entryField.setAccessible(true);

        Object[] entries = (Object[]) entryField.get(stringTable);
        Set<String> stringPool = new HashSet<>();

        for (Object entry : entries) {
            if (entry != null) {
                Field valueField = entry.getClass().getDeclaredField("value");
                valueField.setAccessible(true);
                String str = (String) valueField.get(entry);
                stringPool.add(str);
            }
        }

        return stringPool;
    }

Steps:

The getAllStringsInPool() method uses reflection to access the internal StringTable class and its table field, which holds the string pool data. It then iterates through the entries array and retrieves the value field of each entry, which contains the actual string value.

备注:

This approach relies on internal JVM implementation details and may not work in future versions of Java or on non-Oracle JVMs