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; }
The Java String toCharArray() method converts the string to a char array and returns it. The syntax of the toCharArray() method is: string.toCharArray() Here, string is an object of the String class.
The java string toCharArray() method converts the given string into a sequence of characters. The returned array length is equal to the length of the string. Syntax : public char[] toCharArray() Return : It returns a newly allocated character array.
The toCharArray() method converts a string to a char array in Java. This method lets you manipulate individual characters in a string as list items. Spaces, numbers, letters, and other characters are all added to the char array.
I did a test about this. Following is the works I've done:
String.java
from JDK.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); }
compile this modified String
, and save it back into JRE's rt.jar.
Write a simple HelloWorld Java code.
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:
System.initProperties
needs to handle some Strings while it initializes system properties.String.toCharArray
to get char arrays from those strings.Arrays.copyOf
, but at this point & this time, Arrays
has not been loaded / initialized.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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With