我希望打印整个字符串池,其中包含使用 intern()
添加的字面值和 String
对象,以便在垃圾收集之前检查它们。
JDK 中是否有隐式方法可执行此操作?我们如何检查字符串池?
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]
intern()
替代方案有问题,因为它无条件地返回this
,而不考虑映射中已存在的实例。更糟糕的是,这个hack似乎只能捕获对intern()
的显式调用——请注意,你的输出中并没有程序中的字面量"This is some test string"
,而只有你手动intern的编号版本。当我在未修改的环境下使用-XX:+PrintStringTableStatistics
运行你的程序时,JVM说有大约四千个interned字符串... - Holgerintern()
方法的。事实上,字符串池也会在类加载和 VM 启动级别上进行 interning,绕过了 intern()
调用,这使得事情变得困难(当将其应用于 此问题 时可以看到,这可能也是你来到这里的原因)。我没有看到任何进一步访问字符串池的选项(除了手工定制的 JVM hack)。但请随意提供其他建议作为答案。 - Marco13intern()
添加的字符串对象的整个字符串池”。 - HolgerString
实例,但据我所知,没有办法检查特定实例是否已池化... - Holgerhttp://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
String
并在使用intern()
方法之前对它们进行存储 ;) - NeplatnyUdajXbootclasspath
提供它怎么样?我不知道是否可以加载不同的String... - NeplatnyUdaj