Я больше не сопротивляюсь механизму загрузки классов Java

Java
Я больше не сопротивляюсь механизму загрузки классов Java

Долгое время я очень сопротивлялся механизму загрузки классов в Java, потому что считал его слишком сложным для понимания. Но чтобы стать хорошим Java-инженером, я решил стиснуть зубы и разобраться в этом.

01. Байт-код

Прежде чем говорить о механизме загрузки классов Java, вам нужно понять байт-код Java, потому что он тесно связан с механизмом загрузки классов.

Компьютеры знают только 0 и 1, поэтому программа, написанная на любом языке, должна быть скомпилирована в машинный код, чтобы компьютер мог ее понять, а затем выполнить, и Java не является исключением.

Когда зародилась Java, она выкрикивала очень сильный лозунг: «Напиши один раз, работай где угодно». Для достижения этой цели Sun выпустила множество виртуальных машин Java (JVM), которые могут работать на разных платформах (Windows, Linux). - отвечает за загрузку и выполнение скомпилированных байт-кодов Java.

Давайте посмотрим, как выглядит байт-код Java с помощью простого фрагмента кода.

Исходный код выглядит следующим образом:

package com.cmower.java_demo;

public class Test {

    public static void main(String[] args) {
        System.out.println("沉默王二");
    }

}

После того, как код скомпилирован и передан, перейдитеxxd Test.classкоманда для просмотра этого файла байт-кода.

xxd Test.class
00000000: cafe babe 0000 0034 0022 0700 0201 0019  .......4."......
00000010: 636f 6d2f 636d 6f77 6572 2f6a 6176 615f  com/cmower/java_
00000020: 6465 6d6f 2f54 6573 7407 0004 0100 106a  demo/Test......j
00000030: 6176 612f 6c61 6e67 2f4f 626a 6563 7401  ava/lang/Object.
00000040: 0006 3c69 6e69 743e 0100 0328 2956 0100  ..<init>...()V..
00000050: 0443 6f64 650a 0003 0009 0c00 0500 0601  .Code...........
00000060: 000f 4c69 6e65 4e75 6d62 6572 5461 626c  ..LineNumberTabl

Чувствуешь себя немного сбитым с толку, да?

Запутался - это правильно.

в этом байткодеcafe babeИзвестный как «магическое число», это признак того, что JVM распознает файл .class. Настройщик формата файла может свободно выбирать магический номер (пока он не используется), например, магический номер файла .png8950 4e47.

Что касается другого контента, вы можете забыть о нем.

02. Процесс загрузки класса

Разобравшись с байт-кодом Java, давайте поговорим о процессе загрузки классов Java.

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

1) Загрузка

Основная цель JVM на этом этапе — преобразовать байт-код из разных источников данных (может быть, файлов классов, пакетов jar или даже сети) в потоки двоичных байтов, загружаемых в память, и сгенерировать представление класса.java.lang.Classобъект.

2) Проверка

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

  • Убедитесь, что формат двоичного потока байтов соответствует ожидаемому (например, если он начинается сcafe beneначало).
  • На все ли методы распространяются ключевые слова управления доступом.
  • Правильно ли указано количество и тип параметров вызова метода.
  • Перед использованием убедитесь, что переменные правильно инициализированы.
  • Убедитесь, что переменной присвоено значение соответствующего типа.

3) Подготовка

Переменные класса (также известные как статические переменные,staticс измененным ключевым словом) выделяют память и инициализируют (соответствуя начальному значению по умолчанию типа данных, например 0, 0L, null, false и т. д.).

То есть, если есть такой кусок кода:

public String chenmo = "沉默";
public static String wanger = "王二";
public static final String cmower = "沉默王二";

chenmo не будет выделена память, а wanger будет; но начальное значение wanger не "Wang Er", аnull.

должны знать о том,static finalМодифицированные переменные называются константами, в отличие от переменных класса. После того, как константа назначена, она не изменится, поэтому значение cmower на этапе подготовки равно «тихий король два», а неnull.

4) Разрешение

На этом этапе символические ссылки в пуле констант преобразуются в прямые ссылки.

какие? Символическая ссылка, прямая ссылка?

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

Во время компиляции класс Java не знает фактического адреса класса, на который ссылаются, поэтому вместо этого он может использовать только символические ссылки. Напримерcom.Wangerуказанный классcom.Chenmoclass, класс Wanger не знает фактического адреса памяти класса Chenmo во время компиляции, поэтому он может использовать только символcom.Chenmo.

прямая цитатаНайдите фактический адрес памяти ссылки, разрешив символическую ссылку.

5) Инициализация (инициализация)

Эта фаза является последним шагом в процессе загрузки класса. На этапе подготовки переменной класса было присвоено начальное значение по умолчанию, а на этапе инициализации переменной класса будет присвоено значение, ожидаемое кодом. Другими словами, фаза инициализации — это процесс выполнения методов конструктора класса.

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

String cmower = new String("沉默王二");

В приведенном выше коде используетсяnewдля создания экземпляра строкового объекта, то в это время будет вызываться конструктор класса String для создания экземпляра cmower.

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

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

Как правило, Java-программистам не нужно напрямую взаимодействовать с загрузчиками того же типа. Поведение JVM по умолчанию достаточно для большинства ситуаций. Однако, если вы столкнулись с ситуацией, когда вам нужно взаимодействовать с загрузчиком классов, и вы мало знаете о механизме загрузчика классов, вам придется потратить много времени на отладкуClassNotFoundExceptionиNoClassDefFoundErrorи другие исключения.

Для любого класса его уникальность в JVM должна определяться его загрузчиком классов и самим классом. То есть, если загрузчики двух классов разные, даже если два класса исходят из одного и того же файла байт-кода, два класса не должны быть равны (например, объекты класса двух классов не равны каждому). разное).equals).

С точки зрения программиста загрузчики классов Java можно разделить на три типа.

1) Запускаем загрузчик классов (Bootstrap Class-Loader), загружаемjre/libФайл jar в пакете, например, обычный rt.jar.

2) Extension или Ext Class-Loader, загрузитьjre/lib/extjar под пакетом.

3) Загрузчик классов приложений (Application или App Clas-Loader), который загружает классы Java в соответствии с classpath программы.

Давай, давайте разберемся с этим с помощью простого фрагмента кода.

public class Test {

	public static void main(String[] args) {
		ClassLoader loader = Test.class.getClassLoader();
		while (loader != null) {
			System.out.println(loader.toString());
			loader = loader.getParent();
		}
	}

}

Каждый класс Java поддерживает ссылку на загрузчик классов, который его определяет, через类名.class.getClassLoader()Эту ссылку можно получить, затем черезloader.getParent()Можно получить загрузчик высшего класса загрузчика классов.

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

sun.misc.Launcher$AppClassLoader@73d16e93
sun.misc.Launcher$ExtClassLoader@15db9742

Первая строка выводит загрузчик классов для Test, загрузчик классов приложения, которыйsun.misc.Launcher$AppClassLoaderэкземпляр класса; вторая строка вывода — это загрузчик класса расширения, которыйsun.misc.Launcher$ExtClassLoaderэкземпляр класса. Как насчет загрузчика классов запуска?

Само собой разумеется, что загрузчик верхнего класса загрузчика класса расширения является загрузчиком запускаемого класса, но в моей версии JDK загрузчик класса расширенияgetParent()возвращениеnull. Так что выхода нет.

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

Если указанные выше три загрузчика классов не соответствуют требованиям, программисты также могут настроить загрузчики классов (наследованиеjava.lang.ClassLoaderclass), а иерархическая связь между ними показана на следующем рисунке.

Эта иерархическая связь называетсяМодель родительского делегирования: если загрузчик классов получает запрос на загрузку класса, он сначала делегирует запрос загрузчику верхнего уровня для завершения, а загрузчик верхнего уровня делегирует загрузчик верхнего уровня загрузчику классов верхнего уровня; если загрузчик верхнего уровня Когда загрузка класса не может быть завершена, текущий загрузчик класса попытается загрузить класс самостоятельно.

PS: Модель назначения родителей вдруг напомнила мне о товарище Чжу Юаньчжане, который после того, как этот товарищ стал императором, даже не хотел быть премьер-министром.

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

Как упоминалось выше, если два класса имеют разные загрузчики, даже если два класса происходят из одного и того же файла байт-кода, то два класса не должны быть равными — модель родительского делегирования может гарантировать, что в конечном итоге будет использоваться один и тот же класс. загрузчик определенного класса.

05. Наконец

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

Может быть, обучение должно быть таким: пока вы осмеливаетесь бросить себе вызов, вы можете получить знания — точно так же, как существуют горы, если вы готовы подняться, вы можете достичь вершины.