После того, как написанная java-программа будет скомпилирована, она будет помещена в следующий.classСреди файлов байткода в конце эти файлы байткода помещаются на диск.Нет никаких сомнений в том, что jvm необходимо читать соответствующие файлы байткода с диска при запуске.Как выглядит процесс?
формат файла класса
Формат файла класса использует структуру, аналогичную C, для хранения данных.
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
формат файла классаИнформация хранится в области метода:
- u4 Эта структура указывает, что магическая информация занимает 4 байта, аналогично базовым типам в формате класса
- cp_info — это составной тип данных, состоящий из базовых типов в нескольких форматах классов. Конечно, он также может содержать другие определенные составные типы. Подобные структуры являются составными типами.
Все байты в файле класса расположены близко друг к другу в соответствии с этим соглашением, и никаких изменений произойти не может.
Constant_pool указан в файле класса
В Constant_pool есть два основных типа констант: литералы и символические ссылки. Типы различаются байтом
-
Литералы типа CONSTANT_Integer_info
CONSTANT_Integer_info { u1 tag; u4 bytes; }Если тег чтения равен 3, это указывает на то, что структура является CONSTANT_Integer_info, а следующие 4 байта указывают значение этого int.
-
Символические ссылки, такие как CONSTANT_Class_info
CONSTANT_Class_info { u1 tag; u2 name_index; }Если тег чтения равен 7, это означает, что структура является CONSTANT_Class_info, а следующие 2 байта должны быть допустимой позицией индекса в пуле констант этого класса. Например, возьмите байт-код класса
07-》class 00 16->class完,07 указывает, что константная структура является CONSTANT_Class, а 0016 указывает, что соответствующее значение хранится в 22-м нижнем индексе пула констант.Constant pool: #3 = Class #22 // main/domain/A ... #22 = Utf8 main/domain/A- javap -v A.class, чтобы увидеть
- Символическая ссылка: вы не должны знать конкретное расположение кода в памяти при компиляции, вы можете использовать только символы, такие как
main/domain/AЧтобы определить адрес памяти заранее, прямая ссылка должна быть загружена в память.Разница между символической ссылкой и прямой ссылкой
Сам jvm будет содержать все пулы констант.При создании класса или интерфейса он будет использоваться для создания пула констант времени выполнения.
Постоянный_пул в файле класса, обозначаемый термином постоянный пул времени выполнения во время выполнения jvm.
classFileParser
Возьмем, к примеру, виртуальную машину точки доступа.classFileParser.cpp的instanceKlassHandle ClassFileParser::parseClassFileВы можете увидеть файл класса синтаксического анализа в
instanceKlassHandle ClassFileParser::parseClassFile(Symbol* name,
Handle class_loader,
Handle protection_domain,
KlassHandle host_klass,
GrowableArray<Handle>* cp_patches,
TempNewSymbol& parsed_name,
bool verify,
TRAPS) {
...
ClassFileStream* cfs = stream();
...
// Magic value
u4 magic = cfs->get_u4_fast();
...
// Version numbers
u2 minor_version = cfs->get_u2_fast();
u2 major_version = cfs->get_u2_fast();
if (!is_supported_version(major_version, minor_version)){
...
throw..."Unsupported major.minor version %u.%u"
...
}
...
// Constant pool
constantPoolHandle cp = parse_constant_pool(CHECK_(nullHandle));
...
}
Анализ постоянного пула выглядит следующим образом
constantPoolHandle ClassFileParser::parse_constant_pool(TRAPS) {
ClassFileStream* cfs = stream();
constantPoolHandle nullHandle;
...
u2 length = cfs->get_u2_fast();
...
constantPoolOop constant_pool =
oopFactory::new_constantPool(length,
oopDesc::IsSafeConc,
CHECK_(nullHandle));
constantPoolHandle cp (THREAD, constant_pool);
cp->set_partially_loaded(); // Enables heap verify to work on partial constantPoolOops
ConstantPoolCleaner cp_in_error(cp); // set constant pool to be cleaned up.
// parsing constant pool entries
parse_constant_pool_entries(cp, length, CHECK_(nullHandle));
...
}
parse_constant_pool_entries анализирует пул констант следующим образом
void ClassFileParser::parse_constant_pool_entries(constantPoolHandle cp, int length, TRAPS) {
...
// Used for batching symbol allocations.
const char* names[SymbolTable::symbol_alloc_batch_size];
int lengths[SymbolTable::symbol_alloc_batch_size];
int indices[SymbolTable::symbol_alloc_batch_size];
unsigned int hashValues[SymbolTable::symbol_alloc_batch_size];
int names_count = 0;
// parsing Index 0 is unused
for (int index = 1; index < length; index++) {
// Each of the following case guarantees one more byte in the stream
// for the following tag or the access_flags following constant pool,
// so we do not need bounds-check for reading tag.
//读取常量池中的第一个字节
u1 tag = cfs->get_u1_fast();
//判断类型做不同的处理
switch (tag) {
case JVM_CONSTANT_Class :
{
cfs->guarantee_more(3, CHECK); // name_index, tag/access_flags
u2 name_index = cfs->get_u2_fast();
cp->klass_index_at_put(index, name_index);
}
break;
...
case JVM_CONSTANT_Utf8 :
{
...
unsigned int hash;
//查找符号链表中是否已经存在这个符号
Symbol* result = SymbolTable::lookup_only((char*)utf8_buffer, utf8_length, hash);
if (result == NULL) {
names[names_count] = (char*)utf8_buffer;
lengths[names_count] = utf8_length;
indices[names_count] = index;
hashValues[names_count++] = hash;
if (names_count == SymbolTable::symbol_alloc_batch_size) {
//如果已经达到一定的符号量,取值是8个,就一次性存起来,放入SymbolTable中
SymbolTable::new_symbols(cp, names_count, names, lengths, indices, hashValues, CHECK);
names_count = 0;
}
} else {
cp->symbol_at_put(index, result);
}
}
...
}
if (names_count > 0) {
SymbolTable::new_symbols(cp, names_count, names, lengths, indices, hashValues, CHECK);
}
}
Создание класса или интерфейса
Время создания триггерного класса или интерфейса C включает
- Пул констант времени выполнения другого класса или интерфейса D содержит ссылку на текущий класс или интерфейс C.
- Другой класс или интерфейс D вызывает некоторые определенные методы библиотеки классов, такие как отражение
Дано имя N, представляющее создаваемый класс или интерфейс C.
- Если N не является массивом, будет использоваться один из следующих двух методов: Если D, запускающий создание C, загружается загрузчиком классов Bootstrap, то загрузчик классов Bootstrap загружает C. Если D, запускающий создание C, загружается определяемым пользователем загрузчиком, то начальная загрузка C также будет загружена тем же определяемым пользователем загрузчиком.
- N — это массив, он создается непосредственно JVM.
загрузка класса без массива
Использование загрузчика классов Bootstrap имеет следующие шаги.
- Во-первых, JVM увидит, был ли класс или интерфейс, указанный N, записан как загрузчик классов загрузчиком классов Bootstrap.Если это так, класс не создается.
- Если записи нет, N будет передано в качестве параметра загрузчику классов BootStrap, и будет выполнен процесс извлечения класса из файла классов, если он не будет найден, будет выброшено исключение ClassNotFoundException.
- Найден, извлеките объект класса из файла класса.
Загрузка кода для todo bootstrap
Используйте определяемый пользователем загрузчик классов L. Шаги следующие:
- Во-первых, посмотрите, является ли L уже загрузчиком N, если это так, он больше не будет создан
- Нет, JVM вызовет метод loadClass(N) L, и если выполнение пройдет успешно, L будет помечен как начальный загрузчик C.
Примечание для пользовательских загрузчиков классов: Пользовательский L должен реализовать один из следующих двух способов:
- После того, как L прочитает массив байтов сам по себе, он передает массив байтов методу defineClass в ClassLoader, и defineClass выполняет процесс извлечения объекта класса из массива байтов класса.
- L действует только как прокси и передает детали загрузки C другому загрузчику класса L', который обычно соответствует методу loadClass, а возвращаемый результат является объектом класса C.
В соответствии с этим соглашением загрузчик классов можно настроить следующим образом.
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try{
String fName=name.substring(name.lastIndexOf("."))+".class";
//todo 补充这里的含义
InputStream is=getClass().getResourceAsStream(fName);
if (is == null){
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name,b,0,b.length);
}catch (Exception e){
throw new ClassNotFoundException(name);
}
}
};
Загрузка класса массива
Тип компонента массива по-прежнему будет загружаться загрузчиком классов L, где L может быть BootstrapClassLoader или определяемым пользователем. Этот процесс аналогичен загрузке класса без массива, за исключением того, что когда компонент необходимо загрузить, JVM сама создаст соответствующий массив.
Способ извлечения класса из файла класса
Предположим, что загружаемый класс или интерфейс C однозначно идентифицируется N, а загрузчик — L. Извлеките или выполните следующие шаги
- JVM ищет, был ли L помечен как начальный загрузчик N. Да, этот шаг недопустим, и выдается исключение LinkageError.
- В противном случае JVM попытается выполнить синтаксический анализ, и во время этого процесса может возникнуть следующее исключение: если анализируемый файл не соответствует спецификации ClassFile, выдается ClassFormatError; если основная или младшая версия ClassFile не поддерживается, выдается UnsupportedClassVersionError; кроме того, если предоставленные данные Если не C, бросить NoClassDefFoundError
- Если C имеет родительский класс, разрешите ссылку на символ его родительского класса, если полученный родительский класс является не классом, а интерфейсом, выдайте IncompiledClassChangeError, если он сам, выдайте ClassCircularityError
- Если C реализует интерфейс, обрабатывать аналогично родительскому классу.
- JVM помечает L как определяющий его загрузчик классов и записывает, что L является начальным загрузчиком для C.
Загрузчики общих классов
С точки зрения JVM существует только два разных загрузчика классов: Bootstrap ClassLoader и другие загрузчики, унаследованные от ClassLoader. Загрузчики классов, используемые большинством программ Java, следующие:
- Bootstrap ClassLoader: отвечает за загрузку библиотеки классов в каталог java_home\lib или -Xbootclasspath и распознается виртуальной машиной в JVM, на него не может напрямую ссылаться программа Java.
Это означает, что даже если написанная вами библиотека будет помещена в java_home\lib, она не будет загружена.
- Расширение ClassLoader: отвечает за загрузку всех библиотек классов в каталог java_home\lib\ext или по пути, указанному в java.ext.dirs, разработчики могут использовать
- Application ClassLoader: отвечает за загрузку библиотеки классов, указанной в пути к классам. Разработчики могут использовать
Application ClassLoader по умолчанию возвращается через метод getSystemClassLoader класса ClassLoader.
Приложение ClassLoader и Extension ClassLoader реализованы вsun.misc.Launcher, сам класс Launcher является «системой», используемой для запуска основного приложения при его инициализации.
public Launcher() {
// Create the extension class loader
ClassLoader extcl;
try {
//它的parent字段设置为null
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader");
}
// Now create the class loader to use to launch the application
try {
//将extClassLoader作为AppClassLoader的parent字段存储
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader");
}
// Also set the context class loader for the primordial thread.
Thread.currentThread().setContextClassLoader(loader);
...
}
Видно, что AppClassLoader и ExtClassLoader связаны комбинацией. С точки зрения JVM AppClassLoader и ExtClassLoader являются пользовательскими загрузчиками классов. Когда они используются для загрузки классов, выполняется метод loadClass. Основная реализация ClassLoader выглядит следующим образом.
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先查看类是否已经加载了,如果加载了什么都不做
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//没有加载过,以AppClassLoader为例,则是先使用ExtClassLoader去执行加载
c = parent.loadClass(name, false);
} else {
//没有加载过,以ExtClassLoader为例,先使用BootstrapClassLoader来加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//“parent”加载失败,则自己加载
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
Как видно из логики загрузки, этот метод использует сначала BootstrapClassLoader, затем ExtClassLoader и, наконец, ApplicationClassLoader, Этот режим загрузки классов также называется双亲委派模型.
Модель родительского делегирования
Когда класс загружается, ему отдается приоритет для его «восходящего» («родительского») загрузчика для выполнения, и они сами загрузят его, когда не смогут его найти. Преимущество:
- Например, для загрузки java.lang.Object, независимо от того, какой загрузчик используется, в этом режиме загружается один и тот же класс. В то же время, с точки зрения режима загрузки loadClass, даже если будет написан идентичный класс, он не будет загружен, что позволит избежать путаницы.
Из реализации loadClass, если
要利用双亲委派模型的优势,则自定义类加载器实现findClass是最佳的选择
Конечно, некоторые сценарии необходимо загружать из родительского загрузчика с помощью дочернего загрузчика, например JNDI.Сама служба JNDI загружается с помощью BootstrapClassLoader, но то, что делает JNDI, — это вызывать поставщика интерфейса JNDI в соответствии с ClassPath, что не может быть загружается с помощью BootstrapClassLoader. Загружается аналогично JDBC, это осуществляется черезThread.currentThread().setContextClassLoaderреализовать родительский загрузчик использовать дочерний загрузчик для реализации загрузки
Существует также OSGI, которая выполняется в плоском загрузчике классов.
Механизм загрузки BootstrapClassLoader
ClassLoader в конечном итоге будет загружен первым в BootstrapClassLoader. Это нативная реализация, и в конечном итоге она будет загружена самой JVM. Соответствующая реализация метода находится в коде JVM. Взяв в качестве примера точку доступа, место загрузкиjvm.cpp中的JVM_FindClassFromBootLoader
JVM_ENTRY(jclass, JVM_FindClassFromBootLoader(JNIEnv* env,
const char* name))
...
//首先从符号表里面去查找,看看能不能找到
TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL);
//根据查找的结果去解析符号链接
klassOop k = SystemDictionary::resolve_or_null(h_name, CHECK_NULL);
if (k == NULL) {
return NULL;
}
...
//转成java对象
return (jclass) JNIHandles::make_local(env, Klass::cast(k)->java_mirror());
JVM_END
Этот метод вызывается через динамическое соединение, например
dlsymфункция
SymbolTable
существуетsymbolTable.hppможно увидеть вSymbolTableОпределение
class SymbolTable : public Hashtable<Symbol*>
На самом деле это хеш-таблица.Из процесса разбора файла класса видно, что он на самом деле загружается информацией о классе, которая уже была загружена.
SystemDictionary
Используется для записи всех загруженных (имя типа, загрузчик классов) -> сопоставления типов
// Forwards to resolve_instance_class_or_null
klassOop SystemDictionary::resolve_or_null(Symbol* class_name, Handle class_loader, Handle protection_domain, TRAPS) {
assert(!THREAD->is_Compiler_thread(), "Can not load classes with the Compiler thread");
if (FieldType::is_array(class_name)) {
//数组类型
return resolve_array_class_or_null(class_name, class_loader, protection_domain, CHECK_NULL);
} else if (FieldType::is_obj(class_name)) {
//对象类型
ResourceMark rm(THREAD);
// Ignore wrapping L and ;.
TempNewSymbol name = SymbolTable::new_symbol(class_name->as_C_string() + 1,
class_name->utf8_length() - 2, CHECK_NULL);
return resolve_instance_class_or_null(name, class_loader, protection_domain, CHECK_NULL);
} else {
return resolve_instance_class_or_null(class_name, class_loader, protection_domain, CHECK_NULL);
}
}
Обработка экземпляровresolve_instance_class_or_null:
klassOop SystemDictionary::resolve_instance_class_or_null(Symbol* name, Handle class_loader, Handle protection_domain, TRAPS) {
...
klassOop check = find_class(d_index, d_hash, name, class_loader);
if (check != NULL) {
// Klass is already loaded, so just return it
class_has_been_loaded = true;
k = instanceKlassHandle(THREAD, check);
} else {
//没有加载过则在PlaceholderTable中查找,如果目标的类加载器和类名一样,就找到
placeholder = placeholders()->get_entry(p_index, p_hash, name, class_loader);
if (placeholder && placeholder->super_load_in_progress()) {
super_load_in_progress = true;
if (placeholder->havesupername() == true) {
superclassname = placeholder->supername();
havesupername = true;
}
}
}
...
return k();
}
Конечно вresolve_instance_class_or_nullЕсли файл класса с таким именем не загружен, он будет заменен наclassLoader.cpp中的load_classfileвыполнять загрузку
stringStream st;
// st.print() uses too much stack space while handling a StackOverflowError
// st.print("%s.class", h_name->as_utf8());
st.print_raw(h_name->as_utf8());
st.print_raw(".class");
char* name = st.as_string();
// Lookup stream for parsing .class file
ClassFileStream* stream = NULL;
int classpath_index = 0;
{
PerfClassTraceTime vmtimer(perf_sys_class_lookup_time(),
((JavaThread*) THREAD)->get_thread_stat()->perf_timers_addr(),
PerfClassTraceTime::CLASS_LOAD);
ClassPathEntry* e = _first_entry;
while (e != NULL) {
//根据名字找到文件流
stream = e->open_stream(name);
if (stream != NULL) {
break;
}
e = e->next();
++classpath_index;
}
}
instanceKlassHandle h(THREAD, klassOop(NULL));
if (stream != NULL) {
// 找到类文件开始执行解析等等
ClassFileParser parser(stream);
...
}
Наконец-то начал искать файлы здесь
ClassFileStream* ClassPathDirEntry::open_stream(const char* name) {
// construct full path name
char path[JVM_MAXPATHLEN];
//将名字和默认的目录匹配到路径
if (jio_snprintf(path, sizeof(path), "%s%s%s", _dir, os::file_separator(), name) == -1) {
return NULL;
}
// check if file exists
struct stat st;
if (os::stat(path, &st) == 0) {
// 调用操作系统的方法来找
int file_handle = os::open(path, 0, 0);
...
return new ClassFileStream(buffer, st.st_size, _dir); // Resource allocated
}
}
}
return NULL;
}
Единственная неопределенность здесь заключается в том, где _dir — это расположение каталога, то есть в какой каталог загружается bootstrapLoader,_dirЭто сам объект каталога, он в конечном итоге устанавливается системой, и трассировку всегда можно увидеть в os.cpp.
const char* home = Arguments::get_java_home();
...
// Any modification to the JAR-file list, for the boot classpath must be
// aligned with install/install/make/common/Pack.gmk. Note: boot class
// path class JARs, are stripped for StackMapTable to reduce download size.
static const char classpath_format[] =
"%/lib/resources.jar:"
"%/lib/rt.jar:"
"%/lib/sunrsasign.jar:"
"%/lib/jsse.jar:"
"%/lib/jce.jar:"
"%/lib/charsets.jar:"
"%/lib/jfr.jar:"
#ifdef __APPLE__
"%/lib/JObjC.jar:"
#endif
"%/classes";
char* sysclasspath = format_boot_path(classpath_format, home, home_len, fileSep, pathSep);
if (sysclasspath == NULL) return false;
Arguments::set_sysclasspath(sysclasspath);
Это то, что загружает bootstrapclassloader