зачем писать эту статью
Читатели всегда спрашивали меня, как отлаживать исходный код javac.Я прочитал много исходного кода javac в процессе написания буклета JVM Nuggets, а статей в Интернете относительно мало, поэтому я напишу статью о javac отладка исходного кода.Ну и как начало серии статей по javac.
Процесс отладки исходного кода javac относительно прост, он написан на языке Java, удобном для понимания внутренней логики.
Процесс построения среды
Примечания по окружающей среде: Intellij, JDK8
1. Первый шаг — загрузить и импортировать исходный код javac
Если вы не хотите скачивать с openjdk, вы можете пропустить шаг 1 и скачать прямо с моего github:GitHub.com/Артур-Станция…
Способ загрузки OpenJDK: ОткрытымKorea.open JDK.java.net/JDK8/JDK8/com…, щелкните zip или gz слева, чтобы загрузить.
Создайте новый проект javac-source-code-reading в Intellij, скопируйте весь каталог src/share/classes/com исходного каталога в каталог src проекта и удалите бесполезный каталог javadoc.
2. Найдите запись основной функции javac
код находится вsrc/com/sun/tools/javac/Main.java
Запустите эту основную функцию, поскольку нет пути к исходному коду, который необходимо скомпилировать, он должен выводить следующий контент в консоль без каких-либо аварий.
создать новыйHelloWorld.java
файл, содержимое случайное, в стартовой конфигурацииProgram arguments
Добавьте к нему абсолютный путь HelloWorld.java.
Запустите Main.java еще раз, и файл HelloWorld.class будет создан в том же каталоге, что и HelloWorld.java.
3. Добавьте точки останова
Поставьте точку останова в Main.java, и после того, как вы начнете отладку, вы обнаружите, что независимо от того, как вы ее установили, отладка войдетtool.jar
, не беря только что импортированный исходный код.
Intellij показывает исходный код, полученный путем декомпиляции tools.jar, и его читабельность не так хороша, как у исходного кода.
Откройте страницу Структура проекта (Файл->Структура проекта), выберите вкладку Зависимости на рисунке и установите<Moudle source>
Порядок приведен в начало проекта JDK:
После повторной отладки можно ввести точку останова в исходном коде проекта.
javac см. случай байт-кода 1: стратегия, выбранная tableswitch и lookupswitch
Читатель спрашивает, почему оператор switch-case, скомпилированный следующим кодом, использует lookupswitch вместо tableswitch.Означает ли это, что «если значение case относительно компактно и в середине есть несколько ошибок или их нет, будет использоваться tableswitch? реализовать switch-case"?
public static void foo() {
int a = 0;
switch (a) {
case 0:
System.out.println("#0");
break;
case 1:
System.out.println("#1");
break;
default:
System.out.println("default");
break;
}
}
Соответствующий байт-код
public static void foo();
0: iconst_0
1: istore_0
2: iload_0
3: lookupswitch { // 2
0: 28
1: 39
default: 50
}
Этот вопрос более интересен, в основном оценка стоимости tableswitch и lookupswitch, код находится вsrc/com/sun/tools/javac/jvm/Gen.java
середина
В случае, когда значения case только 0 и 1
hi=1
lo=0
nlabels = 2
// table_space_cost = 4 + (1 - 0 + 1) = 6
long table_space_cost = 4 + ((long) hi - lo + 1); // words
// table_time_cost = 3
long table_time_cost = 3; // comparisons
// lookup_space_cost = 3 + 2 * 2 = 7
long lookup_space_cost = 3 + 2 * (long) nlabels;
// lookup_time_cost = 2
long lookup_time_cost = nlabels;
// table_space_cost + 3 * table_time_cost = 6 + 3 * 3 = 15
// lookup_space_cost + 3 * lookup_time_cost = 7 + 3 * 2 = 13
// opcode = 15 <= 13 ? tableswitch : lookupswich
int opcode = nlabels > 0 &&
table_space_cost + 3 * table_time_cost <=
lookup_space_cost + 3 * lookup_time_cost
? tableswitch : lookupswitch;
Таким образом, в случае, когда значения случая равны только 0 и 1, расчет стоимости table_space_cost + 3 * table_time_cost > lookup_space_cost + 3 * lookup_time_cost, lookupswich дешевле выбрать lookupswich
Что, если есть три 0, 1 и 2?
hi=2
lo=0
nlabels = 3
// table_space_cost = 4 + (2 - 0 + 1) = 7
long table_space_cost = 4 + ((long) hi - lo + 1); // words
// table_time_cost = 3
long table_time_cost = 3; // comparisons
// lookup_space_cost = 3 + 2 * 3 = 9
long lookup_space_cost = 3 + 2 * (long) nlabels;
// lookup_time_cost = 3
long lookup_time_cost = nlabels;
// table_space_cost + 3 * table_time_cost = 7 + 3 * 3 = 16
// lookup_space_cost + 3 * lookup_time_cost = 9 + 3 * 3 = 18
// opcode = 16 <= 18 ? tableswitch : lookupswich
int opcode = nlabels > 0 &&
table_space_cost + 3 * table_time_cost <=
lookup_space_cost + 3 * lookup_time_cost
? tableswitch : lookupswitch;
Таким образом, в случае, когда значения случая равны только 0, 1 и 2, расчет стоимости table_space_cost + 3 * table_time_cost
На самом деле, в случае очень маленького числа разница между ними невелика, но алгоритм здесь в javac приводит к выбору lookupswitch
javac см. случай байт-кода 2: загрузить целые числа в выбор инструкции байт-кода в стеке
Мы знаем, что существует множество инструкций, которые могут загружать в стек целые числа, например,iconst_0
,bipush
,sipush
,ldc
, как они выбираются?
public static void foo() {
int a = 0;
int b = 6;
int c = 130;
int d = 33000;
}
对应部分字节码
0: iconst_0
1: istore_0
2: bipush 6
4: istore_1
5: sipush 130
8: istore_2
9: ldc #2 // int 33000
11: istore_3
существуетcom/sun/tools/javac/jvm/Items.java
Функция load() плюс точка останова
Вы можете видеть, что выбранные стратегии выглядят следующим образом:
- Способ выбора iconst_n между -1~5
- Выберите bipush между -128~127
- -Выберите между 32768 ~ 32767 sipush
- Другие опционы на большие целые ldc
Это согласуется с документацией по инструкциям байт-кода в Спецификации виртуальной машины Java.
постскриптум
Используйте javac, чтобы узнать много интересного, я надеюсь, что вы можете оставить сообщение, чтобы найти больше интересных вещей.