Why doesn’t String toCharArray use Arrays.copyOf?

I did a test about this. Following is the works I’ve done:

  1. Get the source code of String.java from JDK.
  2. Modify its toCharArray method to use Arrays.copyOf.

    like this:

    public char[] toCharArray() {
        // Cannot use Arrays.copyOf because of class initialization order issues
        /*char result[] = new char[value.length];
        System.arraycopy(value, 0, result, 0, value.length);
        return result;*/
        return Arrays.copyOf(value, value.length);
    }
    
  3. compile this modified String, and save it back into JRE’s rt.jar.

  4. Write a simple HelloWorld Java code.

  5. Compile & run the code using java program.

And finally, I get this:

PS D:\> & 'C:\Program Files (x86)\Java\jdk1.8.0_121\jre\bin\java.exe' StringTest
Error occurred during initialization of VM
java.lang.NullPointerException
    at java.util.Hashtable.remove(Hashtable.java:491)
    at java.lang.System.initProperties(Native Method)
    at java.lang.System.initializeSystemClass(System.java:1166)

We can see there is truly an initialization error. And because System.initProperties is a native method, I can not check its code.

However, We can make a guess why this could happen:

  1. System.initProperties needs to handle some Strings while it initializes system properties.
  2. And while doing initialize, it might invoke String.toCharArray to get char arrays from those strings.
  3. Strings invoke Arrays.copyOf, but at this point & this time, Arrays has not been loaded / initialized.
  4. Differing from running Java code, the native code would not ask for a class initializing request (I’m not sure about this, please let me know if I’m wrong!!), and which will lead to a NullPointerException and make VM exit.

2018.04.10 Update.

I’d like to Thank @Radiodef for his hint. But when I tried going into the C++ codes, I was stopped by so many execution paths which I could not handle without running or debugging the OpenJDK’s JVM.

And then, I changed my strategy. I did some more test based on above which I had done for a few days.

This time, I’m not going to use Arrays.copyOf with String.toCharArray, instead, I attempt to find out which codes will invoke toCharArray method while JVM initializing.

So I modify String, add two static variables to it:

public static int count;
public static Throwable[] logs = new Throwable[10000];

In which count is used to count the invocation of toCharArray, logs is used to keep those invocation’s stack traces.

In toCharArray method:

public char[] toCharArray() {
    if (count < logs.length) {
        try {
            throw new RuntimeException();
        } catch (Throwable e) {
            logs[count] = e;
        }
    }
    count++;

    // Cannot use Arrays.copyOf because of class initialization order issues
    char result[] = new char[value.length];
    System.arraycopy(value, 0, result, 0, value.length);
    return result;
}

After done those, I compile String again and save it back into rt.jar.

Then, I write a test program to print count and invocation stack traces out:

Class<String> clazz = String.class;
Field count = clazz.getDeclaredField("count");
System.out.println(count.getInt(null));
Field logs = clazz.getDeclaredField("logs");
Throwable[] arr = (Throwable[]) logs.get(null);
for (Throwable e : arr) {
    if (e != null)
        e.printStackTrace(System.out);
}

We can not access String.count & String.logs directly in our codes, as compiler (javac) does not recognize these fields and will cause a compile error. That’s why I’m using reflect-way to do this.

Run the program we’ve just written, and the results will be:

525
java.lang.RuntimeException
    at java.lang.String.toCharArray(String.java:2889)
    at sun.nio.cs.ext.GBK.initb2c(Unknown Source)
    at sun.nio.cs.ext.GBK.newDecoder(Unknown Source)
    at java.lang.StringCoding$StringDecoder.<init>(Unknown Source)
    at java.lang.StringCoding$StringDecoder.<init>(Unknown Source)
    at java.lang.StringCoding.decode(Unknown Source)
    at java.lang.String.<init>(String.java:414)
    at java.lang.String.<init>(String.java:479)
    at java.lang.System.initProperties(Native Method)
    at java.lang.System.initializeSystemClass(Unknown Source)

......

java.lang.RuntimeException
    at java.lang.String.toCharArray(String.java:2889)
    at sun.net.www.ParseUtil.encodePath(Unknown Source)
    at sun.misc.URLClassPath$FileLoader.getResource(Unknown Source)
    at sun.misc.URLClassPath.getResource(Unknown Source)
    at java.net.URLClassLoader$1.run(Unknown Source)
    at java.net.URLClassLoader$1.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at sun.launcher.LauncherHelper.checkAndLoadMain(Unknown Source)

What a long invocation list. However it is clearer than the previous test. We can clearly see which classes invoke toCharArray.

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)