preface

This article mainly introduces the process of Java class loading, and the principle and function of classloader. The author will combine “In-depth understanding of Java virtual machine” and the blog on the Internet to summarize. This post is both a blog post and a personal note.

The purpose of this article is to solve the author of a puzzle, for the same class, different ClassLoader to load the object is the same?

This article will not discuss Java’s memory model concepts, and you are welcome to correct any errors or inaccuracies.

Much of this article is excerpted from understanding the Java Virtual Machine, which I recommend reading for myself if I have the time.

Class file structure

concept

Before we can start, we need to distinguish between classes and objects.

Difference: A class is a file or binary stream (which enables remote class loading) that loads a class into a virtual machine, allocates memory space, and generates an object associated with it (either directly by reference or indirectly through a handle pool).

You create a Peron file, which is the class, and then create a Person, which is the object, by new.

To discuss the loading process of a class, it is important to understand the structure of the class file.

structure

Have you ever wondered why Java is still compatible with historical versions despite more than a dozen major releases and countless minor updates?

Because of the specification, as early as JDK1.2, has determined the format of the class file, the later “Virtual Machine specification” although there are changes, but only new, did not change the historical format. Class files are not the only ones supported by Java’s first definition specification. For example, the @Resource and @PostConstruct annotations introduced by the JSR-250 specification are not implemented in Java, but are officially supported by Spring.

The Class file format uses a pseudo-structure similar to the C-language structure to store data with only two data types: unsigned numbers and tables.

Unsigned numbers are basic data types. U1, U2, U4, and U8 represent unsigned numbers of 1 byte, 2 byte, 4 byte, and 8 byte respectively. Unsigned numbers can be used to describe numbers, index references, quantity values, or string values encoded in UTF-8

A table is a composite data type consisting of multiple unsigned numbers or other tables as data items. To facilitate differentiation, all table names are customarily terminated with “_info”. Tables are used to describe hierarchical composite structures of data, and the entire Class file can essentially be thought of as a table consisting of data items in a strict order as shown in the following figure.

Whether it is unsigned number or table, when it is necessary to describe multiple data of the same type but an indefinite number, it is often used in the form of a front capacity counter plus a number of consecutive data items. At this time, this series of consecutive data of a certain type is called a “set” of a certain type.

It is not necessary to memorize, but to know the concepts of roughly unsigned numbers and tables.

The following describes the structure of class files one by one.

Magic number and file version

The first four bytes of each Class file are called the Magic Number, whose sole purpose is to determine whether the file is an acceptable Class file for the virtual machine.

Many file format standards, not just Class files, use magic numbers for identification. Image formats, such as GIF or JPEG, have magic numbers in their headers. The use of magic numbers rather than extensions for identification is primarily for security reasons, since file extensions can be changed at will.

The next four bytes of the magic number store the Version number of the Class file: bytes 5 and 6 are Minor versions, and bytes 7 and 8 are Major versions.

Constant pool

A constant pool can be likened to a resource repository in a Class file. It is the data item that is most associated with other items in a Class file structure, and is usually one of the data items that occupy the largest space in a Class file. It is also the first table type data item that appears in a Class file.

Since the number of constants in a constant pool is not fixed, you need to place an entry of type U2 in the constant pool, representing the constant pool capacity count (constant_pool_count).

There are two main types of constants in the constant pool: Literal and Symbolic References. Literals are close to the Java language level concepts of constants, such as text strings, constant values declared final, and so on.

Symbolic references are the concept of compilation principle and mainly include the following types of constants:

  • Packages exported or exposed by modules
  • Fully Qualified Name of class and interface
  • Field name and Descriptor
  • The name and descriptor of the method
  • Method handles and Method types (Method Handle, Method Type, Invoke Dynamic)
  • Dynamic Call Points and Dynamic constants (Dynamically-Computed Call Site, Dynamically-Computed Constant)
Access tokens

Access_flags. This flag identifies some Class or interface level access information, including whether the Class is a Class or an interface; Whether it is defined as public; Whether to define an abstract type; If it is a class, whether it is declared final; , etc.

A collection of class indexes, parent indexes, and interface indexes

This_class and super_class indexes are a U2-type data, while interfaces are a set of U2-type data, which are used to determine the inheritance relationship of this type in the Class file.

The class index is used to determine the fully qualified name of the class, and the superclass index is used to determine the fully qualified name of the class’s parent. Because the Java language does not allow multiple inheritance, there is only one parent class index. All Java classes except java.lang.Object have a parent class, so none of the Java classes except java.lang.Object have a parent class index of zero. The interface index collection is used to describe the interfaces implemented by the Class. The implemented interfaces are arranged from left to right after the implements keyword (or extends keyword if the Class file represents an interface).

Set of field tables

Field tables (field_info) are used to describe interfaces or classes. A variable declared in. Fields in the Java language include class-level variables as well as instance-level variables, but not local variables declared inside methods.

A field can include modifiers such as the field’s scope (public, private, and protected modifiers), whether it is an instance variable or a class variable (static modifiers), variability (final), and concurrency visibility (volatile modifiers, Whether to force reads and writes from main memory), whether to serialize (transient modifier), field data type (base type, object, array), field name. Each of the modifiers in the above information is a Boolean value that either has a modifier or does not have one, which is a good place to use flag bits. What is the name of the field, what is the data type of the field defined, these are not fixed, can only refer to constants in the constant pool to describe

Extend two concepts: fully qualified names and simple names.

“Org/fenixsoft/clazz/TestClass” is the fully qualified name TestClass, merely is in the class name “.” Instead of “/”, a “; “is usually added at the end of the usage to avoid confusion between consecutive fully qualified names. A sign indicates the end of a fully qualified name.

A simple name is a method or field name that has no type or parameter modification. In this case, the inc() method and m field are simply “inc” and “m”, respectively.

Method table collection

The Class file storage format describes methods in almost exactly the same way that fields are described. The method table is structured like a field table. This includes access_flags, name_index, descriptor_index, and Attributes.

The meaning of these data items is also very similar to that in the field table, with the only differences in the options for accessing the flags and property table collections. This is mainly reflected in that volatile keyword and transient keyword can not modify methods. In contrast, synchronized, native, StrictFP and abstract keyword can modify methods.

The Java Code in the method, compiled into bytecode instructions by the Javac compiler, is stored in a property called “Code” in the method property sheet, which is the most extensible data item in the Class file format.

In the Java language, to Overload a method, in addition to having the same simple name as the original method, you must also have a signature that is different from the original method. A signature is a collection of field symbol references for each parameter in a method in the constant pool. Because the return value is not included in the signature, it is impossible to override an existing method in the Java language only by the difference of the return value. But in the Class file format, the scope of signature is significantly larger, and two methods can coexist as long as the descriptors are not identical. That is, if two methods have the same name and signature but return different values, they can legally coexist in the same Class file.

Class loading process

The Java VIRTUAL machine loads the data describing the Class from the Class file to the memory, verifies, transforms, and initializes the data, and finally forms Java types that can be directly used by the VIRTUAL machine. This process is called the virtual machine Class loading mechanism. Unlike languages that need to be wired at compile time, the Java language loads, joins, and initializes types while the program is running. This strategy makes early compilation difficult for the Java language and adds a slight performance overhead to class loading. However, it provides extremely high extensibility and flexibility for Java applications. Java naturally can be dynamically extended language features are based on the runtime dynamic loading and dynamic connection characteristics.

For example, Java can wait until the program runs to specify the implementation class of the interface, which is common in reflection. Or at run time, a binary stream can be retrieved from the network or other sources as part of the program.

As mentioned earlier, a class file is a file structure based on specifications and conventions, meaning that it has the properties of a file and can be transferred or loaded as a binary stream. Therefore, “class files” should not be considered only files, but also file streams that conform to the specification.

In addition, this article follows the description of “type” from the book “Understanding the Java Virtual Machine,” which stands for a class or interface unless specifically said.

Class Loading cycle: Loading, Verification, Preparation, Resolution, Initialization, Using, and Unloading.

This diagram is familiar to most of you, and you’ve seen it on many classloading blogs, but few people will tell you that it’s from Understanding the Java Virtual Machine.

Load, validation, preparation, initialization and unload the order of the five stages is certain, the type of loading process must, in accordance with the order, step by step while parsing stage does not necessarily: in some cases it can start again after the initialization phase, this is in order to support the Java language runtime binding features (also called dynamic binding or late binding).

The Java Virtual Machine Specification specifies that there are only six cases in which classes must be “initialized” immediately (and loading, validation, and preparation naturally need to begin before then).

  1. When you encounter four bytecode instructions — New, getstatic, putstatic, or Invokestatic — if the type has not been initialized, you need to trigger its initialization phase first. Typical Java code scenarios that can generate these four instructions are:

    • When an object is instantiated using the new keyword.

    • Read or set a static field of a type (except for static fields that are modified by final and have been put into the constant pool at compile time).

    • When a static method of a type is called.

  2. When a reflection call is made to a type using the java.lang.Reflect package’s methods, initialization needs to be triggered if the type has not already been initialized.

  3. When initializing a class, if the parent class has not been initialized, the initialization of the parent class must be triggered first.

  4. When the virtual machine starts, the user needs to specify a primary class (the one containing the main() method) to execute, and the virtual machine initializes this primary class first.

  5. When using the new dynamic language support in JDK 7, If a Java lang. Invoke. The final analytical results for MethodHandle instance REF_getStatic, REF_putStatic, REF_invokeStatic, REF_newInvokeSpecial handle four kinds of methods, If the class corresponding to the method handle has not been initialized, it needs to be initialized first.

  6. When an interface defines a new JDK 8 default method (an interface method decorated with the default keyword), if any of the interface’s implementation classes are initialized, the interface should be initialized before it.

The Java Virtual Machine Specification uses a very strong qualifier “have and only” for each of the six scenarios that trigger the initialization of a type, and the action in these six scenarios is called an active reference to a type.

None of these reference types trigger initialization, called passive references. For example, subclasses refer to static variables of their parent class, construct an array of objects ClassA[10], and refer directly to static variables in types, etc.

You may feel that these are concepts and have little practical use. Is it really so? Do a simple thought question.

public class ClassA extends ClassB { static { System.out.println("class A static code area"); } { System.out.println("class A code area"); } public ClassA() { System.out.println("class A constructor"); } } public class ClassB { public final static ClassC c = new ClassC(); static { System.out.println("class B static code area"); } { System.out.println("class B code area"); } public ClassB() { System.out.println("class B constructor"); } } public class ClassC { static { System.out.println("class C static code area"); } { System.out.println("class C code area"); } public ClassC() { System.out.println("class C constructor"); }}Copy the code

Consider the following two cases, in order of output:

ClassA a = new ClassA(); ClassC c = classa.c;Copy the code

You may have seen this in some of the interview questions, so if you’re interested, try it out for yourself. What if ClassA also implements an interface ClassD, and D has a default method inside it?

Create classAS by proxy, such as class.forname (), or classa.class.class.newinstance (). Would it be different?

Class loading

The title “class load” refers to the entire loading process, not the “load” part of it.

The individual sections are analyzed below.

loading

During the load phase, the Java virtual machine needs to do three things:

  1. Gets the binary byte stream that defines a class by its fully qualified name.
  2. Transform the static storage structure represented by this byte stream into the runtime data structure of the method area.
  3. Generate a java.lang.Class object representing the Class in memory as an access point to the Class’s various data (Class information, constants, static variables, compiled code, etc.) in the method area.

The Java Virtual Machine Specification is not particularly specific about these three requirements, leaving much flexibility for virtual machine implementation and Java applications. For example, the rule “get the binary byte stream that defines a Class by its fully qualified name” does not specify that the binary byte stream must be obtained from a Class file, or indeed, it does not specify where or how to get it at all. For example, it can be woven from the network, compressed packages, runtime, etc.

In understanding the JVM also introduces the Java memory model, in this three rules also has a memory allocation, withdrawal method after reading streams of bytes stored in the area (note that the method is not equal to the permanent generation, but for the memory management method, to expand a generational design to methods), then can produce an object in the heap, as with the method’s association.

In the case of an array class, the array class itself is not created by the class loader, but is dynamically constructed directly in memory by the Java virtual machine. But array classes are still closely related to class loaders, because the Element Type of an array (i.e., the Type of the array with all dimensions removed) is ultimately loaded by the class loader.

Stages of loading and the connection part of the action, such as part of the bytecode file format validation action) was performed by cross loading phase is not yet complete, may have begun connection phase, but the clip in the middle of the stage of loading action, still belongs to the part of connection, the two stages of start time remains fixed order.

validation

The purpose is to ensure that the information contained in the byte stream of a Class file complies with all constraints of the Java Virtual Machine Specification and does not compromise the security of the virtual machine when it is run as code.

Verification phase:

  1. File format verification verifies that the byte stream complies with the Class file format specification and can be processed by the current version of the VM. Here is to combine with the previous class file structure, the content of the verification part is as follows:

    1. Does it start with the magic number 0xCAFEBABE?

    2. Check whether the major and minor versions are acceptable for the current Java VM.

    3. If there are unsupported constant types in the constant pool (check the constant tag flag).

    4. Is there any index value that points to a constant that does not exist or that does not conform to a type?

    5. Whether any other information has been deleted or added to parts of the Class file and the file itself.

      .

  2. Metadata verification performs semantic analysis on the information described by bytecode to ensure that the information described conforms to the requirements of Java Language Specification. Mainly grammar check, including the following parts:

    1. Whether this class has a parent (all classes except java.lang.Object should have a parent).
    2. Whether the parent of this class inherits classes that are not allowed to be inherited (classes modified by final).
    3. If the class is not abstract, does it implement all the methods required by its parent or interface? .
  3. Bytecode verification is the most complicated stage in the whole verification process. The main purpose of bytecode verification is to confirm that the program semantics are legal and logical through data flow analysis and control flow analysis. Validates a method body or code fragment inside a class, including the following sections:

    1. Ensure that the data type of the operand stack and the sequence of instruction codes work together at any time. For example, there is no such thing as “putting an int on the operand stack and loading it into the local variable table as long”.
    2. Ensure that no jump instruction jumps to a bytecode instruction outside the method body.
    3. Ensure the method body type conversion is always effective, for example, can put a subclass object assignment to the parent class data type, which is safe, but the parent class object is assigned to a subclass data types, even the object assignment give it no inheritance relationships, and completely irrelevant to a data type, is dangerous and illegal. .
  4. Symbolic reference validation occurs when the virtual machine converts a symbolic reference to a direct reference. This conversion occurs during the parse phase, the third phase of the connection. Symbolic reference verification can be regarded as the matching verification of all kinds of information outside the class itself (various symbolic references in the constant pool). In plain English, that is, whether the class is missing or denied access to some external classes, methods, fields and other resources on which it depends.

    1. Whether a class can be found for a fully qualified name described by a string in a symbol reference.

    2. Whether a field descriptor for a method and methods and fields described by a simple name exist in the specified class

      .

    The verification phase is not necessary. If the code has been repeatedly verified to be ok, you can skip most of the verification process by using ** -xverify: None ** to speed up loading.

To prepare

Stage is formally defined as a class variable (namely static variables, by static modified variable) allocates memory and set up the class variables in the stage of initial value, conceptually, the memory used by these variables should be distributed in the method, but must pay attention to the method of area itself is a logical area, before the JDK 7 and, When HotSpot uses persistent generation to implement a method area, the implementation is entirely logical; In JDK 8 and later, Class variables are stored in the Java heap along with Class objects, so “Class variables in the method area” is simply a logical statement.

At this point, the variables that allocate memory are only “class variables”, not instance variables (that is, instance objects), which will be allocated in the Java heap along with the object when it is instantiated.

And in general, the initialization values for the preparation phase are not given in the code, but are different types of defaults, such as 0 for int, 0L for long, and so on.

Exceptions:

public static final int value = 123;
Copy the code

It’s going to be initialized directly to 123.

parsing

The Java virtual machine replaces symbolic references in the constant pool with direct references.

Symbolic References: Symbolic References describe the referenced target as a set of symbols, which can be any literal, as long as they are used to unambiguously locate the target.

Direct References: A Direct reference is a pointer that can point directly to the target, a relative offset, or a handle that can be indirectly located to the target.

The Java Virtual Machine Specification does not specify when parsing occurs, Only ane-warray, checkcast, getField, getStatic, Instanceof, InvokeDynamic, InvokeInterface, invoke-Special, InvokeStatic, InvokeV are required Irtual, LDC, LDC_w, LDC2_W, multianewarray, new, putfield and putstatic are before the 17 commands. So the virtual machine implementation can decide for itself whether to resolve symbolic references in the constant pool as soon as the class is loaded by the loader, or to wait until a symbolic reference is ready to be used.

Access to methods or fields is also checked for accessibility (public, protected, private, etc.) during the parsing phase.

The parse action is mainly for class or interface, field, class method, interface method, method type, method handle, and call point qualifier. CONSTANT_Class_info, con-stant_fieldref_info, CONSTANT_Methodref_info, CONSTANT_InterfaceMethodref_info, and CONSTANT_M correspond to the constant pool EthodType_info, CONSTANT_MethodHandle_info, constant_dyna-mic_info and CONSTANT_InvokeDynamic_info are 8 constant types.

The last four require dynamic language support and an understanding of the Invokedynamic command. Java itself is a statically typed language. Dynamic syntax (Java’s dynamic proxy) is supported, so this article will only cover the first four for now.

Parsing is divided into several dimensions:

  1. Class or interface parsing assumes that the current code is of class D, if you want to resolve a symbolic reference N that has never been resolved into a direct reference to class or interface C.

    class D { private C N = new C(); Private C[] N = new C[]{}; //N can also be array}Copy the code

    The code above is one possibility where symbolic references may identify an object or an array of objects.

    As you can see from the class file structure section, arrays are a special existence for virtual machines, and the loading and parsing process is not quite the same as classes.

    • If C is not an array type, the virtual machine will pass the fully qualified name representing N to D’s classloader to load C. In the loading process, due to the requirements of metadata verification and bytecode verification, loading actions of other related classes may be triggered, such as loading the parent class or the interface implemented by this class. An error terminates parsing.

      If object A is referenced by C and D at the same time, it may be loaded by different class loaders.

    • If C is an array type and the element type of the array is an object, the descriptor of N will be of the form “[Ljava/lang/Integer”, then the array element type will be loaded according to the rules in point 1. If the descriptor of N is of the form assumed earlier, The element type to load is “java.lang.Integer”, and the virtual machine generates an array object representing the dimensions and elements of the array.

    • If nothing happens in the previous two steps, then C is actually a valid class or interface in the virtual machine, but symbolic reference validation is performed to verify that D has access to C before parsing is complete. If it is found that do not have access, will throw Java. Lang. IllegalAccessError anomalies.

      Access rights are not just the usual modifiers: public, private, protected, default. It also includes module concepts introduced in JDK 9.

      The access restriction modifier rule and the module access rule must be verified before the access class D and C can be resolved. For example, the accessed class C is public and in the same module as the accessed class D.

  2. Field resolution The process of field resolution is linear. If the class or interface resolution fails, no field resolution is performed. In addition, the process of parsing is similar to coding. First look in its own class, if not, try to find the parent (non-object class). If none exists, an exception is thrown.

  3. Method resolution is similar to field resolution in that you first make sure that your class or interface is properly resolved. Secondly, from their own upward search, after finding the confirmation method is not abstract.

  4. Interface method resolution is similar to class method resolution. But interfaces allow multiple inheritance, so be careful.

Initialize the

Until now, the virtual machine has taken the lead, mainly to ensure that the loaded class files are correctly executed by the program and then handed over to the application.

In the preparation phase, attributes inside the class are initialized once, but they are given a zero value (not necessarily 0, but the default value for the type, such as Boolean false and int 0). Instead, the class constructor () method will be executed. This is not an application-level init method, but an automatic artifact of the Javac compiler.

This method can be seen simply through IDEA. In this case, view-> Byte code of idea can be viewed.

() method is by the compiler automatically collect all class variable assignment in class action and static blocks (static {} block) of the statement in merger, the compiler collection order is decided by the order of the statement in the source file, static block can only access to the definition in the static block variables before and after its variables, The previous static block can be assigned, but not accessed.

Such as:

public class Test { static { i = 0; Print (I); print(I); print(I); } static int I = 1; }Copy the code

Unlike class constructors (that is, instance constructor () methods in the virtual machine perspective), the () method does not require an explicit call to the superclass constructor, and the Java virtual machine guarantees that the () method of the superclass is executed before the () method of the subclass is executed. So the first () method to be executed in the Java virtual machine must be of type java.lang.object.

Since the () method of the parent class executes first, it means that static blocks defined in the parent class take precedence over assignment operations in the child class.

The () method is not required for a class or interface, and the compiler may not generate () methods for a class that has no static blocks and no assignment to variables.

Static blocks cannot be used in interfaces, but there are still assignment operations that initialize variables, so interfaces generate () methods just like classes. Interfaces differ from classes in that the () method of the interface does not need to execute the () method of the parent interface first, because the parent interface is initialized only when a variable defined in the parent interface is used. In addition, the implementation class of the interface does not execute the interface’s () method when initialized.

The Java virtual machine must ensure that a class’s () methods are locked and synchronized correctly in a multithreaded environment. If multiple threads initialize a class at the same time, only one of them will execute the class’s () methods, and all the other threads will block and wait until the active thread finishes executing the () method. If you have a long operation in a class’s () method, multiple processes can block.

Assignment of static code blocks or attributes in a class should not be too complicated or it will block the initialization process.

Class loader

The Java Virtual Machine design team intentionally implemented the class-loading action of “getting the binary stream describing a class by its fully qualified name” outside the Java Virtual machine so that the application could decide how to get the required classes. The code that does this is called a Class Loader.

Although the class loader is only used to implement the loading action of the class, it plays a far more important role in Java programs than the class loading phase. For any class, the uniqueness of the Java virtual machine must be established by both the classloader that loads it and the class itself. Each classloader has a separate class namespace. This sentence can express more popular: compare whether the two classes “equal”, only in these two classes are from the same Class loader under the premise of load to be meaningful, otherwise, even if these two classes derived from the same Class files, by the same Java virtual machine loading, as long as the load they’re different Class loaders, these two classes are not necessarily equal.

Equality includes returns from equals(), isAssignableFrom(), isInstance(), and the instanceof keyword.

This also explains the above question, class loaders in different cases, classes are not the same.

The project rarely goes back to custom classloaders, nor does it know much about them, usually using class.getClassloader directly to call them, but if you look at the source of the SpringBoot boot process. You will find that a class loader is acquired when the runtime environment is initialized before the bean is loaded, and this class loader is accompanied by the entire startup process and subsequent bean loading.

Parental delegation model

concept

When it comes to class loading, perhaps the most familiar is the parent delegate principle, which is used everywhere class loading is mentioned. The principle requires that all class loaders have their own parent class loaders, except for the top start class loaders.

“Parents” is a translation of “parents”, not both parents, which is essentially what we understand as “parent”, but not an inheritance relationship, but a combination of modes used, parent as a classloader property. The assignment can be made through the constructor, or if the assignment is null, the Bootstrap ClassLoader is used.

From the perspective of virtual machines, it can be divided into Bootstrap ClassLoader and other classloaders.

From a development point of view, there are three layers of class loaders, and the parent delegate principle of class loading architecture. This implementation has been maintained from JDK 1.2 through JDK 8, with some changes since modularity in JDK 9, but the body remains the same.

Let’s start with a class loading structure:

Bootstrap Class Loader: This class loader is responsible for loading files stored in

\lib, or in the path specified by the -xbootclasspath parameter, that are recognized by the Java virtual machine (by filename, e.g. Rt.jar, tools.jar, Libraries with incorrect names will not be loaded even if placed in the lib directory.) The libraries are loaded into the virtual machine memory. The boot class loader cannot be directly referenced by Java programs. If you need to delegate the loading request to the boot class loader when writing a custom class loader, you can use NULL instead.

Extension ClassLoader: This ClassLoader is implemented as Java code in the sun.misc.Launcher$ExtClassLoader Class. It is responsible for loading all libraries in the

\lib\ext directory, or in the path specified by the java.ext.dirs system variable.

Application ClassLoader: This ClassLoader is implemented by sun.misc.Launcher$AppClassLoader. Because the application ClassLoader is the return value of the getSystem-classLoader () method in the ClassLoader class, it is also called the “system ClassLoader” in some cases. It is responsible for loading all libraries on the user’s ClassPath, and developers can use the class loader directly in their code.

The working process of the

If a classloader receives a classload request, it does not try to load the class itself at first. Instead, it delegates the request to the parent classloader. This is true at every level of classloaders, so all load requests should eventually be sent to the top level of the starting classloader. Only when the parent loader reports that it cannot complete the load request (it did not find the desired class in its search scope) will the child loader attempt to complete the load itself.

role

There is a hierarchy of priorities, such as java.lang.Object, which is stored in rt.jar. Any class loader that loads this class is ultimately delegated to the boot class loader at the top of the model, so that the Object class is guaranteed to be the same class in each class loader environment of the program. On the other hand, instead of using the parent delegate model and having each class loader load it, if the user wrote a class named java.lang.Object and put it in the program’s ClassPath, the system would have multiple Object classes. The most basic behavior in the Java type system is not guaranteed, and the application becomes chaotic.

The parent delegate principle is broken

The parental delegation principle is not mandatory, but rather recommended by Java designers for developers to implement classloaders. This article introduces three cases of damage.

  1. In the ancient days before JDK1.2, before the principle of parental delegation was formulated. After 1.2, the findClass method was introduced in order to be compatible with existing custom loaders, and subsequent developers were instructed to override this method instead of overwriting loadClass directly. So if the parent class call fails, it will call itself to load.

  2. Model defects caused by itself, parents delegate well solved the various class loader collaborate basis types of consistency issues (the base classes conducted by the upper level of the loader loads), foundation type are called the “foundation”, because they were always as user code inheritance, call API, but the program design often no absolute constant perfect rules, What if you have an underlying type and want to call back to the user’s code? To solve this dilemma, the Java design team introduced a less elegant design: the Thread Context ClassLoader. This ClassLoader can be set using the setContext-classloader () method of the java.lang.Thread class. If it has not been set when the Thread is created, it will inherit one from the parent Thread, if it has not been set at the global level of the application. This class loader is the application class loader by default. Extension:

    Speaking of this, THE author thought that before writing springBoot source code found that Springboot as early as in the early stage of startup, build the running environment object has early initialization of a class loader.

    To get a classloader:

    Public ClassLoader getClassLoader() {// where resourceLoader is null return this.resourceloader! = null ? this.resourceLoader.getClassLoader() : ClassUtils.getDefaultClassLoader(); } public static ClassLoader getDefaultClassLoader() {ClassLoader cl = null; try { cl = Thread.currentThread().getContextClassLoader(); } catch (Throwable var3) { } if (cl == null) { cl = ClassUtils.class.getClassLoader(); if (cl == null) { try { cl = ClassLoader.getSystemClassLoader(); } catch (Throwable var2) { } } } return cl; }Copy the code
  3. This is caused by the user’s pursuit of dynamic application, and by “dynamic” I mean some very “Hot” terms: Hot Swap, Hot Deployment of modules, etc. In short, to achieve “hot deployment.”

These are just three cases where we are forced to violate the “parent delegate principle”. We do not need to break it and should not actively break it. If asked how to break it, the parent delegate principle is implemented in the basic loadClass method and the code is very simple:

protected Class<? > loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<? > c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent ! = null) { c = parent.loadClass(name, false); } else { 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(); 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; }}Copy the code

If you want to break the “parent delegate principle”, just override the loadClass method and remove the parent judgment. You won’t be asked this question except in an interview.

summary

Going back to the original question, do different classloaders load the same object for the same class?

Answer: No. Different class loaders load different objects. And in order to avoid repeated loading resulting in different underlying classes, the “parent delegate principle” is used, as embodied in the ClassLoader#loadClass method.

In addition, also introduced the structure of the class file, divided into: magic number, version number, constant pool, access identification, index set (class, parent class, interface), field table, method table. Java’s stability and good compatibility benefits from the relative stability of the file structure. All file data is composed of unsigned numbers and tables (collections of multiple signed numbers).

The loading process of a class is divided into five main stages: loading, verification, preparation, parsing and initialization. The whole process is not necessarily linear, for example, the load may not be complete, the validation may have been done, and the parsing may occur after initialization.

Load: Reads binary byte streams into the virtual machine, not necessarily real class files.

Verification: file format verification, metadata verification, bytecode verification, symbol reference verification. The main purpose is to ensure that the loaded classes are compliant with the virtual machine specification and can be executed.

Prepare: Initializes the static values of the class. This initializes the default values of various types, such as 0 for int and false for Boolean.

Parsing: The process of replacing symbolic references in a constant pool with direct references.

Initialization: Information that is left to the application to initialize an object, such as static code blocks and assignments to properties within constructors.