Загрузчика классов достаточно, чтобы прочитать это

Java

Интервью о загрузчике классов? Достаточно

считать

Как работает код или программа, которую мы обычно пишем? Например, я использую язык java для разработки, а исходный код представляет собой файл .java, но запустить их невозможно. Обычно мы упаковываем его в пакет jar, а затем развертываем на сервере.На самом деле под упаковкой мы подразумеваем компиляцию, то есть компиляцию java-файлов в файлы байт-кода .class.Как выполнить эти файлы байт-кода .class? Выполните эти файлы .class с помощью команды java -jar. На самом деле команда java -jar запускает процесс jvm, а процесс jvm запускает эти файлы байт-кода

概述

Как jvm загружает эти файлы классов?

Выше мы сказали, что jvm будет запускать эти файлы байт-кода .class, но как они загружаются?

Конечно череззагрузчик классовТеперь процесс загрузки файла .class загрузчиком классов выглядит следующим образом:

Загрузить->Проверить->Подготовить->Разобрать->Инициализировать

类加载过程

Ниже мы разберем общий процесс загрузки, но прежде чем анализировать весь процесс, сначала введем условия загрузки класса

состояние загрузки класса

Как правило, в одной из наших программ будет много файлов классов, будет ли jvm безоговорочно загружать эти файлы?

Конечно нет, на самом деле jvm только "использует"Файл класса будет загружен только тогда, когда"использовать"Ссылаться наАктивное использование **, активное использование только в следующих ситуациях:

1. При создании экземпляра класса, например, с использованием ключевого слова new или отражения, клонирования, десериализации

2. При вызове статического метода класса используйте инструкцию байт-кода invokestatic

3. При использовании статических полей класса или интерфейса (кроме финальных констант), например, при использовании директив getstatic или putstatic.

4. При использовании методов пакета java.lang.reflect для отражения методов класса

5. При инициализации подкласса необходимо сначала инициализировать родительский класс

6. В качестве стартовой виртуальной машины класс, содержащий метод main()

За исключением 6 пунктов, перечисленных выше, которые относятся к активному использованию, остальные относятся к пассивному использованию.

Примеры активного использования

public class Parent {
    static {
        System.out.println("Parent init");
    }
}

public class Child extends Parent {
    static {
        System.out.println("Child init");
    }
}

public class Main {
    public static void main(String[] args) {
        Child child = new Child();
    }
}

Если родительский класс инициализирован, будет напечатано "Parent init". Если инициализирован дочерний класс, будет напечатано "Child init". При выполнении основного метода в основном классе инициализируется дочерний класс, и печать следующее:

Parent init Child init

Распечатав результаты, мы можем убедиться, что два условия для активного использования файла класса, 1 и 5, верны.

Примеров других активных вариантов использования нет, рассмотрим примеры пассивного использования.

Примеры пассивного использования

public class Parent {
    public static int v = 60;
    static {
        System.out.println("Parent init");
    }
}

public class Child extends Parent {
    static {
        System.out.println("Child init");
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(Child.v);
    }
}

На этот раз статическая переменная v добавляется в родительский класс, но не в дочерний класс, а затем осуществляется доступ к Child.v в основном классе. Будет ли эта ситуация загружать родительский класс? Будет ли загружен класс Child?

Результат выглядит следующим образом:

Parent init 60

Видно, что загружается только класс Parent, а класс Child не загружается.Стоит отметить, что под «загрузкой» здесь понимается завершение всего процесса загрузки.На самом деле в этот момент загружается и класс Child. время (под загрузкой здесь понимается первый шаг всего процесса загрузки. load, что можно проверить, добавив параметр -XX:TraceClassLoading), но без инициализации.

Вывод после добавления -XX:TraceClassLoading

[Loaded jvm.loadclass.Parent from file:/D:/workspace/study/study_demo/target/classes/] [Loaded jvm.loadclass.Child from file:/D:/workspace/study/study_demo/target/classes/] Parent init 60

Поэтому при использовании поля будет инициализирован только класс, который определяет поле напрямую.

В третьем пункте активного использования явно указано, что конечная константа класса активно не используется, и соответствующий класс не будет загружаться, проверяем это через код.

public class ConstantClass {
    public static final String CONSTANT = "constant";
    static {
        System.out.println("ConstantClass init");
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(ConstantClass.CONSTANT);
    }
}

Результат выглядит следующим образом:

[Loaded jvm.loadclass.Main from file:/D:/workspace/study/study_demo/target/classes/]

constant

По результатам действительно подтверждается, что конечная константа не вызовет инициализацию класса, потому что константа оптимизируется на этапе компиляции (научное название «оптимизация распространения константы»), а значение константы «константа» равно непосредственно хранится в пуле констант основного класса, поэтому класс ConstantClass не будет загружен

нагрузка

Загрузка — это первый этап процесса загрузки класса.На этапе загрузки jvm необходимо выполнить следующие задачи:

1. Получите поток двоичных данных класса через полное имя класса класса.

2. Проанализируйте двоичный поток данных класса как структуру данных в области методов.

3. Создайте экземпляр класса java.lang.Class для представления типа

Существует много способов получить поток двоичных данных класса, например, непосредственное чтение файла .class или извлечение файла .class из пакета архивных данных, такого как jar, zip, war и т. д., а затем процессы jvm. эти потоки двоичных данных и генерирует java.lang Экземпляр класса, который является интерфейсом для доступа к метаданным типа и ключевым данным для реализации отражения

проверять

Фаза проверки заключается в том, чтобы убедиться, что загруженный байт-код соответствует спецификации jvm, которая примерно делится на проверку формата, семантическую проверку, проверку проверки байт-кода и проверку ссылки на символ следующим образом:

验证

Подготовить

Этап подготовки в основном заключается в выделении соответствующего пространства памяти для класса и установке начального значения.Часто используемые начальные значения показаны в следующей таблице:

тип данных начальное значение по умолчанию
int 0
long 0L
short (short)0
char '\u0000'
boolean fasle
float 0.0f
double 0.0d
reference null

Если класс определяет константы, такие как:

public static final String CONSTANT = "constant";

Эта константа (см. файл байт-кода, содержит свойство ConstantValue) будет храниться непосредственно в пуле констант на этапе подготовки.

 public static final java.lang.String CONSTANT;
    descriptor: Ljava/lang/String;
    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: String constant

Разобрать

Фаза синтаксического анализа в основном преобразует символические ссылки классов, интерфейсов, полей и методов в прямые ссылки.

Символическая ссылка: Символическая ссылка представляет собой набор символов для описания цели, на которую ссылаются.Символ может быть любой формой литерала, если его можно использовать для определения местоположения цели без двусмысленности.

Прямая ссылка: Прямая ссылка — это указатель, который может напрямую указывать на цель, относительное смещение или дескриптор, который может быть расположен косвенно на цель.

Этап синтаксического анализа в основном предназначен для классов или интерфейсов, полей, методов классов, методов интерфейса, типов методов, дескрипторов методов и квалификаторов сайта вызова.

Кратко объясним на примере

public class Demo {
    public static void main(String[] args) {
        System.out.println();
    }
}

Просмотрите байт-код, соответствующий методу System.out.println() в основном методе.

3: invokevirtual #3                  // Method java/io/PrintStream.println:()V

Используется третий элемент постоянного пула, затем давайте посмотрим на содержимое третьего элемента в постоянном пуле следующим образом:

#3 = Methodref          #17.#18        // java/io/PrintStream.println:()V

Похоже, что эталонные отношения, пункты 17 и 18, продолжают находить следующим образом:

#17 = Class              #24            // java/io/PrintStream
#18 = NameAndType        #25:#7         // println:()V

Пункт 17 ссылается на пункт 24, а пункт 18 ссылается на пункты 25 и 7 следующим образом:

#24 = Utf8               java/io/PrintStream
#25 = Utf8               println
#7 = Utf8               ()V

Мы представляем приведенную выше эталонную зависимость на графике следующим образом:

符号引用

На самом деле приведенное выше эталонное соотношениеСимволическая ссылка

Но когда программа работает, недостаточно иметь символическую ссылку, система должна четко знать расположение метода, поэтому jvm готовит таблицу методов для каждого класса и перечисляет все его методы в таблице методов. Когда вам нужно вызвать метод класса, вы можете вызвать его напрямую, если знаете смещение метода в таблице методов. С помощью операции разрешения символическая ссылка может быть преобразована в позицию целевого метода в таблице методов класса, чтобы метод был успешно вызван.

инициализация

Инициализация — это последний этап загрузки класса, и если на предыдущих этапах нет проблем, он войдет в стадию инициализации. Что делает фаза инициализации?

В основном это выполнение метода инициализации класса (метод инициализации автоматически генерируется компилятором), который генерируется оператором присваивания статической переменной-члена класса и блоком статических операторов. На этом этапе выполняется настоящая операция присваивания. На этапе подготовки просто выделяется соответствующее пространство памяти и устанавливается начальное значение.

Давайте проверим это на небольшом примере

public class StaticParent {
    public static int id = 1;
    public static int num ;
    static {
        num = 4;
    }
}

Соответствующая часть файла байт-кода выглядит следующим образом:

#13 = Utf8               <clinit>
static {};
    descriptor: ()V
    flags: (0x0008) ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: iconst_1
         1: putstatic     #2                  // Field id:I
         4: iconst_4
         5: putstatic     #3                  // Field num:I
         8: return

Вы можете видеть, что в методе выполняются присвоения статической переменной id в классе и num в блоке статических операторов.

Будет ли компилятор генерировать методы для всех классов? Ответ — нет, если в классе нет ни оператора присваивания, ни блока статических операторов, то даже если метод сгенерирован, делать нечего, поэтому компилятор его не вставит. Давайте посмотрим на соответствующий байт-код на примере

public class StaticFinalParent {
    public static final int a = 1;
    public static final int b = 2;
}
public jvm.loadclass.StaticFinalParent();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return

Из байт-кода метод не находится, потому что, как мы уже говорили, константа типа final инициализируется на этапе подготовки, поэтому нет необходимости инициализировать ее снова на этапе инициализации.

будь осторожен

Здесь следует отметить, что jvm обеспечит безопасность метода, потому что может быть несколько потоков для инициализации класса одновременно, чтобы гарантировать, что только один поток выполняет метод, а другие потоки должны ждать , пока один поток успешно инициализирует класс, другие потоки не нуждаются в повторной инициализации потока.

краткое содержание

Я думаю, что благодаря приведенному выше введению каждый должен понимать код, который мы обычно пишем, и то, как он работает в конце.. Одним словом, файл Java, который мы пишем, будет скомпилирован в файл байт-кода класса, а затем с помощью jvm Load активно используемые классы в память и начать выполнение этих программ. Очень важным этапом является загрузка класса, то есть получение бинарного потока файла класса из внешней системы, и то, что играет решающую роль на этом этапе, будет представлено ниже.загрузчик классов

загрузчик классов

ClassLoader означает загрузчик классов и является основным компонентом java.Можно сказать, что все файлы классов считываются в систему загрузчиком классов извне, а затем передаются jvm для последующего подключения, инициализации и других операций.

Классификация

JVM создаст три загрузчика классов, а именно загрузчик класса запуска, загрузчик класса расширения и загрузчик класса приложения.Давайте кратко представим каждый загрузчик класса.

Запустите загрузчик классов

Bootstrap ClassLoader в основном отвечает за загрузку основных классов системы, таких как классы java в rt.jar.Когда мы используем java в системе Linux или системе Windows, будет установлен jdk.На самом деле, эти основные классы находятся в каталоге lib .

загрузчик класса расширения

Расширение ClassLoader в основном используется для загрузки java классов в lib\ext, эти классы будут поддерживать работу системы

Загрузчик классов приложений

Application ClassLoader в основном загружает пользовательские классы, то есть загружает библиотеку классов, указанную в пути к пользовательскому классу (ClassPath), который обычно представляет собой код, написанный нами

Модель родительского делегирования

Когда класс загружается, система определяет, был ли загружен текущий класс.Если он был загружен, он напрямую вернет доступный класс, в противном случае он попытается загрузить класс. При попытке загрузить класс он будет делегирован родительскому загрузчику для загрузки и, наконец, передан загрузчику верхнего уровня для загрузки. Если загрузчик родительского класса не находит класс в пределах своей сферы ответственности, он будет передан загрузчику дочернего класса для загрузки. Ситуация с загрузкой выглядит так:

双亲委派模型

Видно, что процесс делегирования проверки загруженности класса односторонний. Базовый загрузчик классов спрашивает долго и, наконец, сам загружает класс. Разве это не пустая трата усилий? Конечно, в этом есть свои преимущества,Таким образом, структура становится более понятной, и самое главное — избежать многократной загрузки определенных классов многоуровневыми загрузчиками.

Недостатки модели родительского делегирования

Модель родительского делегирования проверяет, является ли загрузка классов односторонней, но у этого также есть недостаток, заключающийся в том, что загрузчик верхнего класса не может получить доступ к классам, загруженным загрузчиком нижнего класса. Затем, если системный класс, загруженный загрузчиком классов запуска, предоставляет интерфейс, интерфейс должен быть реализован в приложении, и фабричный метод также связан для создания экземпляра интерфейса. А интерфейс и фабричный метод находятся в загрузчике классов запуска. В этот момент возникает проблема, что фабрика не может создать экземпляр приложения, загружаемый загрузчиком класса приложения. Например, JDBC, XML Parser и т. д.

JVM настолько мощная, что определенно будет способ решить проблему такого рода, да, в java решение механизма SPI (интерфейс поставщика услуг) используется для решения такой проблемы.

Суммировать

В этой статье в основном представлен механизм загрузки классов jvm, включая весь процесс загрузки классов и некоторые действия, выполняемые на каждом этапе. Затем он знакомит с рабочим механизмом загрузчика классов и моделью родительского делегирования. Я надеюсь, что для получения дополнительных знаний вы сможете продолжить самостоятельное изучение, например, механизм OSGI, как реализовать горячую замену и горячее развертывание и т. д.

использованная литература

1. «Практическая виртуальная машина Java»

2. «Углубленное понимание виртуальной машины Java»

3. «От 0 до того, как вы станете мастером боя JVM», официальный аккаунт отвечает «jvm» для просмотра информации.

Добро пожаловать в официальный аккаунт [Белые зубы каждый день], получайте свежие статьи, давайте общаться и добиваться успехов вместе!