Ответ 1
Я сделал тест об этом. Ниже приведены работы, которые я сделал:
- Получите исходный код
String.java
от JDK. -
Измените его метод
toCharArray
для использованияArrays.copyOf
.как это:
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); }
-
скомпилируйте эту модифицированную
String
и сохраните ее обратно в JRE rt.jar. -
Напишите простой Java-код HelloWorld.
-
Скомпилируйте и запустите код с помощью
java
программы.
И, наконец, я понимаю:
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)
Мы видим, что действительно есть ошибка initialization
. И поскольку System.initProperties
является родным методом, я не могу проверить его код.
Однако мы можем предположить, почему это может произойти:
-
System.initProperties
должен обрабатывать некоторые строки, когда он инициализирует свойства системы. - И, выполняя инициализацию, он может вызывать
String.toCharArray
для получения массивов char из этих строк. - Строки вызывают
Arrays.copyOf
, но в этот момент и на этот разArrays
не были загружены/инициализированы. - В отличие от исполняемого кода Java, собственный код не запрашивает
class initializing request
(я не уверен в этом, сообщите мне, если я ошибаюсь !!), и это приведет кNullPointerException
иNullPointerException
VM,
2018.04.10 Обновление.
Я хотел бы поблагодарить @Radiodef за его намек. Но когда я попытался войти в коды C++, меня остановили так много путей выполнения, с которыми я не мог справиться без запуска или отладки OpenJDK JVM.
И затем я изменил свою стратегию. Я сделал еще несколько тестов на основе выше, которые я сделал в течение нескольких дней.
На этот раз я не буду использовать Arrays.copyOf
с String.toCharArray
, вместо этого String.toCharArray
выяснить, какие коды будут вызывать toCharArray
при инициализации JVM.
Поэтому я изменяю String
, добавляю к ней две статические переменные:
public static int count;
public static Throwable[] logs = new Throwable[10000];
В котором count
используется для подсчета вызова toCharArray
, logs
используются для сохранения этих стеков стека вызовов.
В методе toCharArray
:
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;
}
После этого я снова скомпилирую String
и сохраню его обратно в rt.jar.
Затем я пишу тестовую программу для печати трассировки стека count
и вызова:
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);
}
Мы не можем получить доступ к String.count & String.logs
непосредственно в наших кодах, поскольку компилятор (javac
) не распознает эти поля и вызовет ошибку компиляции. Вот почему я использую рефлекторный способ сделать это.
Запустите программу, которую мы только что написали, и результаты будут следующими:
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)
Какой длинный список вызовов. Однако это яснее предыдущего теста. Мы можем четко видеть, какие классы вызываются toCharArray
.