如何打印整个字符串池?

15

我希望打印整个字符串池,其中包含使用 intern() 添加的字面值和 String 对象,以便在垃圾收集之前检查它们。

JDK 中是否有隐式方法可执行此操作?我们如何检查字符串池?


3
重写 String 并在使用 intern() 方法之前对它们进行存储 ;) - NeplatnyUdaj
6
"String" 是 "final" 的。 - Jeroen Vannevel
1
但我们无法覆盖字符串? - Prashant
3
那么创建一个新的String实现,并通过Xbootclasspath提供它怎么样?我不知道是否可以加载不同的String... - NeplatnyUdaj
1
垃圾回收器绝对不会在程序退出之前运行——那有什么意义呢? - Marko Topolnik
显示剩余4条评论
2个回答

15
编辑:评论表明可能存在对此“黑客”所做的事情的误解。它打印通过(直接或间接)调用 intern() 进行内部化的字符串,如问题所述。它不会打印“整个字符串池”,因为字符串池仅驻留在JVM中,在类加载和初始化期间填充符号和字符串,并且无法从Java侧访问。

NeplatnyUdaj 在评论中提到可能可以定义一个新的 java.lang.String 类并在启动时将其潜入JVM中。我很好奇,试了一下。结果怎么样呢?它起作用了!

1. 创建一个包含包 java.lang 的新项目

2. 将像这样的类插入到此包中

package java.lang;

import java.util.LinkedHashSet;
import java.util.Set;

public class StringPool {

    private static Set<String> pool = null;
    public static synchronized void store(String string)
    {
        try
        {
            if (pool == null)
            {
                pool = new LinkedHashSet<String>();
            }
            pool.add(string);
        }
        catch (Exception e)
        {
            // Ignore
        }
    }

    public static synchronized Set<String> getPool()
    {
        return new LinkedHashSet<String>(pool);
    }

}

3. 复制并粘贴原始的 java.lang.String 类到这个包中。出人意料的是,这个操作可以在不引起太多问题的情况下工作。它只会抱怨一个函数,也就是对一个函数的调用。

    h = sun.misc.Hashing.murmur3_32(HASHING_SEED, value, 0, value.length);

可以安全替换为
    h = 0;

4. 更改新的String类的String#intern()方法。原本,它是一个native方法。可以用类似以下的内容进行替换:

public String intern()
{
    StringPool.store(this);
    return this;
}

5. 从该项目创建一个.JAR文件,并将其存储为例如newString.jar

6. 创建另一个项目,其中包含生成/包含/使用某些字符串的测试类。(这应该很容易),并编译此类,该类可以命名为NewStringTest

7. 使用修改后的字符串类启动测试程序:

java -Xbootclasspath:newString.jar;C:\jre\lib\rt.jar NewStringTest

StringPool#getPool() 方法可以用来获取包含已池化字符串的池。



我刚刚使用了以下类进行了测试,该类手动创建了一些字符串和一些 Swing 组件(可以预期其中包含一些字符串):

import java.lang.reflect.InvocationTargetException;

import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.SwingUtilities;


public class NewStringTest 
{
    public static void main(String[] args) 
    {
        generateSomeStrings();
        System.out.println(StringPool.getPool());
    }

    private static void generateSomeStrings()
    {
        String s = "This is some test string";
        for (int i=0; i<10; i++)
        {
            String t = s + i;
            t.intern();
        }
        try 
        {
            SwingUtilities.invokeAndWait(new Runnable() 
            {
                @Override
                public void run() {
                    JFrame frame = new JFrame();
                    JTable table = new JTable();
                }
            });
        } 
        catch (InvocationTargetException e) 
        {
            e.printStackTrace();
        } 
        catch (InterruptedException e) 
        {
            e.printStackTrace();
        }
    }
}

输出结果为

[hashSeed, value, buf, J, D, Z, seed, segmentShift, segmentMask, segments, state, head, tail, waitStatus, next, Ljava/lang/String;, I, [C, [J, Ljava/util/Hashtable;, Ljava/security/PermissionCollection;, Ljava/util/Vector;, Ljava/lang/Class;, main, 这是一些测试字符串0, 这是一些测试字符串1, 这是一些测试字符串2, 这是一些测试字符串3, 这是一些测试字符串4, 这是一些测试字符串5, 这是一些测试字符串6, 这是一些测试字符串7, 这是一些测试字符串8, 这是一些测试字符串9, INSTANCE, es, , ES, sv, SE, values, Ljava/lang/Object;, [Ljava/awt/Component;, Ljava/awt/LayoutManager;, Ljava/awt/LightweightDispatcher;, Ljava/awt/Dimension;, createUI, invoke, VK_F10, VK_CONTEXT_MENU, VK_SPACE, VK_LEFT, VK_KP_LEFT, VK_RIGHT, VK_KP_RIGHT, VK_ESCAPE, VK_C, VK_V, VK_X, VK_COPY, VK_PASTE, VK_CUT, VK_INSERT, VK_DELETE, VK_DOWN, VK_KP_DOWN, VK_UP, VK_KP_UP, VK_HOME, VK_END, VK_PAGE_UP, VK_PAGE_DOWN, VK_TAB, VK_ENTER, VK_A, VK_SLASH, VK_BACK_SLASH, VK_F2, VK_F8]


2
你的intern()替代方案有问题,因为它无条件地返回this,而不考虑映射中已存在的实例。更糟糕的是,这个hack似乎只能捕获对intern()的显式调用——请注意,你的输出中并没有程序中的字面量"This is some test string",而只有你手动intern的编号版本。当我在未修改的环境下使用-XX:+PrintStringTableStatistics运行你的程序时,JVM说有大约四千个interned字符串... - Holger
@Holger 尽管标题更为一般,但问题是关于 intern() 方法的。事实上,字符串池也会在类加载和 VM 启动级别上进行 interning,绕过了 intern() 调用,这使得事情变得困难(当将其应用于 此问题 时可以看到,这可能也是你来到这里的原因)。我没有看到任何进一步访问字符串池的选项(除了手工定制的 JVM hack)。但请随意提供其他建议作为答案。 - Marco13
2
不仅仅是标题,问题的正文也说“包含字面量和使用intern()添加的字符串对象的整个字符串池”。 - Holger
@Holger 那么,答案是:“这是不可能的”。 - Marco13
我想是这样的。JVMTI允许获取所有现有的String实例,但据我所知,没有办法检查特定实例是否已池化... - Holger
厉害的黑客! - chaws

-1

http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#finalize%28%29,因此在清理任何对象之前,GC会调用finalize方法。

因此,String中的finalize方法也会被调用。但可悲的是,String是一个final类,您无法覆盖它。(为什么Java中声明String类为final?

但是,如果您真的想让这个东西起作用,那么您需要创建自己的字符串对象,命名为其他名称,但内部行为将保留所有字符串函数。

对于保证的GC,请尝试:http://code.google.com/p/jlibs/wiki/GarbageCollection


finalize() 方法与此无关,创建新的 String 类也无法帮助您查看池化字面量。 - user207421

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接