44

In the JDK source of java.lang.String.toCharArray, it doesn't use Arrays.copyOf to implement this and it says:

Cannot use Arrays.copyOf because of class initialization order issues

What are the "class initialization order issues"?

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;
}
5
  • where do you initialize value? do you use copy constructor? see i-programmer.info/programmer-puzzles/168-c/… Commented Apr 8, 2018 at 7:05
  • 2
    @AsfK I guess the OP take a part of the JDK source code. (not a MCVE?) Commented Apr 8, 2018 at 7:08
  • To be honest, without significant analysis @BrianGoetz might be the only person who could answer. Commented Apr 8, 2018 at 7:09
  • @Asfk I think Arrays.copyOf means use Arrays.copyOf(value , value.length) to implement . Commented Apr 8, 2018 at 7:10
  • @shaoyihe, you're right, I got it wrong. I not familiar with this method but when I'm change value to be char[] it's working, maybe there's a type mismatch issue.. Commented Apr 8, 2018 at 7:16

1 Answer 1

44

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.

Sign up to request clarification or add additional context in comments.

1 Comment

The native code can be found here, if anyone wants to keep looking around in it. The exception at Hashtable line 491 suggests that a key which is null is being passed to the Properties.remove method somehow.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.