I did a test about this. Following is the works I've done:
- Get the source code of
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.
- And while doing initialize, it might invoke
String.toCharArray to get char arrays from those strings.
- Strings invoke
Arrays.copyOf, but at this point & this time, Arrays has not been loaded / initialized.
- 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.
value? do you use copy constructor? see i-programmer.info/programmer-puzzles/168-c/…Arrays.copyOfmeans useArrays.copyOf(value , value.length)to implement .