Руководство по отладке исходного кода Javac

Java JVM

зачем писать эту статью

Читатели всегда спрашивали меня, как отлаживать исходный код 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 &lt;= 13 ? tableswitch : lookupswich

int opcode = nlabels &gt; 0 &&

table_space_cost + 3 * table_time_cost &lt;=
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 &lt;= 18 ? tableswitch : lookupswich

int opcode = nlabels &gt; 0 &&

table_space_cost + 3 * table_time_cost &lt;=
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, чтобы узнать много интересного, я надеюсь, что вы можете оставить сообщение, чтобы найти больше интересных вещей.