How to print the whole String pool?
Asked Answered
Y

2

16

I wanted to print the whole string pool which contains literals and String objects added using intern() just before garbage collection.

Is there a method implicit to JDK for such operation? How can we inspect the string pool?

Yearn answered 28/2, 2014 at 11:37 Comment(9)
Hmmm, why do you want to do that?Undercast
Override String and store them before interning ;)Thumbnail
@NeplatnyUdaj: String is final.Barbabra
But we cannot override string?Yearn
I thought that interned strings are never GCed...Napoleonnapoleonic
Then what about creating a new implementation of String and provide it via Xbootclasspath. I don't know if it's possible to load different String...Thumbnail
but i thought Garbage Collector runs just before program exits in most cases so we can read pool atleast.Yearn
GC definitely does not run before program exit---what purpose would that serve?Paranoid
@MarkoTopolnik is right. GC is called whenever needed. :)Showiness
M
15

EDIT: The comment suggests that there may be a misunderstanding regarding what this "hack" does. It prints the strings that have been interned by (directly or indirectly) calling intern(), as described in the question. It will not print the "whole string pool", as the string pool only resides in the JVM, is filled with symbols and strings that appear during classloading and initialization, and not accessible from Java side.


NeplatnyUdaj mentioned in a comment that it might be possible to define a new java.lang.String class and sneak this into the JVM at startup. I was curious, and tried it out. And what should I say: It works!

1. Create a new project that contains the package java.lang

2. Insert a class like this into this package

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. Copy & Paste the original java.lang.String class into this package. Surprisingly, this works without many problems. It will complain about a single function, namely a call to

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

that can safely be replaced with

    h = 0;

4. Change the String#intern() method of the new String class. Originally, this is a native method. It can be replaced with something like

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

5. Create a .JAR file from this project, and store it, for example, as newString.jar

6. Create another project with a test class that generates/contains/uses some strings. (that should be easy) and compile this class, which may be named NewStringTest

7. Launch the test program with the modified string class:

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

The StringPool#getPool() method can then be used to obtain the pool containing the interned strings.



I just tested this with the following class, which manually creates some strings, and some Swing components (which can be expected to contain some strings):

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();
        }
    }
}

And the output is

[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, This is some test string0, This is some test string1, This is some test string2, This is some test string3, This is some test string4, This is some test string5, This is some test string6, This is some test string7, This is some test string8, This is some test string9, 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]

Mancino answered 28/2, 2014 at 15:12 Comment(6)
Your intern() replacement is broken as it unconditionally returns this, not considering existing instances in the map. But even worse, this hack apparently only catches explicit invocations of intern()—note that your output doesn’t contain the literal "This is some test string" of your program, but only the numbered versions you have interned manually. When I run your program in an unmodified environment using -XX:+PrintStringTableStatistics, the JVM says that there were roughly four thousand interned strings…Jere
@Jere Although the title is more general, the question referred to the intern() method. The fact that interning also takes place at class loading and VM startup level, bypassing the intern() calls, makes things difficult (as can be seen when applying this to this question, which likely led you here as well). I don't see any further options to access the string pool, though (except for a hand-tailored JVM hack). But feel free to provide other suggestions as an answer.Mancino
It’s not only the title; the body of the question also says “the whole string pool which contains literals and String objects added using intern()Jere
@Jere So then, the answer is: "This is not possible".Mancino
I guess so. JVMTI allows to get all existing String instances, but afaik there is no way of checking whether a particular instance is pooled…Jere
Awesome hacking!Twoseater
S
-1

http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#finalize%28%29 , So the GC calls finalize method before clean-up any of the objects.

So the finalize method in String is also getting called. But sadly String is a final class and you cannot override it. (Why is String class declared final in Java?)

But If you really want to get this thing to be worked, then you need to create your own string object named something else, but inner behaviour will keep all the strings functions.

And for a guaranteed GC try this : http://code.google.com/p/jlibs/wiki/GarbageCollection

Sebi answered 28/2, 2014 at 12:2 Comment(1)
The finalize() method has nothing to do with it, and creating a new String class won't help you see pooled literals.Dugaid

© 2022 - 2024 — McMap. All rights reserved.