Interviewer: Please explain in detail about the class loading process, the class loading mechanism and a custom class loader

I. Introduction

When a program uses a class, if the class has not yet been loaded into memory , the JVM will by loading, link initialization three steps that type of conduct class loading.

Second, the type of load, link initialization

1, Load

Class load refers to the class of the class file is read into memory, and whom to create a java.lang.Class object. Class loading process is accomplished by a class loader, class loader provided by the JVM. We Developers can also implement your own class loader through inheritance ClassLoader.

1.1, loaded class source

  • Load class files from the local file system
  • Load class files from the package JAR
  • Load class files over a network
  • Put a dynamically compiled java source files, and perform the load.

2, class link

By loading the class, memory has created a Class object. Link is responsible for merging binary data into the JRE. Links need to verify, ready, to resolve three stages.

2.1, verify

Verify that the stage is used to check the loaded class has the correct internal structure and coherence, and other classes. That is, whether to meet the constraints java virtual machine.

2.2, ready

Class preparation phase is responsible for the kind of class variables allocate memory and set the default initial value.

2.3, parsing

We know, in fact, correspond to reference memory address. Thinking about this problem, write code, using the reference method, class know the memory address reference method do? Obviously do not know, because the class has not been loaded into the virtual machine, you can not get these addresses. For example, for a method call, the compiler will generate a class where the method comprises the target, the target method name, return type, and receives the parameter value type of symbol reference, refers to a method to be called generations.

The purpose of the resolution phase, is to resolve these symbolic references to the actual reference. If a symbolic reference point of the class is not loaded or not loaded class field or method, then the resolution will trigger the class is loaded (but not necessarily trigger analysis and initialization).

3, an initiating

Class initialization phase, the virtual machine main class variables are initialized. Virtual Machine calls <clinit> method, initialize class variables.

java class class variables initialized in two ways:

  1. When you define initialization
  2. Initializing a static initialization block

3.1, <clinit> RELATED

  • And collecting virtual machine based parent class variables and class methods for combination <clinit> method, according to the order defined initialized. Virtual opportunity to ensure that a subclass of <clinit> before executing, <clinit> parent class finished first. Therefore, the first virtual machine has been carried out in <clinit> method must be java.lang.Object method.
public class Test {
 static int A = 10;
 static {
 A = 20;
 }
}
class Test1 extends Test {
 private static int B = A;
 public static void main(String[] args) {
 System.out.println(Test1.B);
 }
}
// output
//20

From the output, the parent class static initialization block initialized before subclass static initialization, the output result is 20, not 10.

  • If you do not have static variables and methods, the virtual machine will not be generated for <clinit> Method in class or parent class.
  • Various interfaces and classes that implements the interface <clinit> method does not require <clinit> method to execute the parent interface. Only when the parent variables defined in the interface using parent interface will be initialized. Further, the interface implementation class in the initialization was not executed <clinit> method interface.
public interface InterfaceInitTest {
 long A = CurrentTime.getTime();
}
interface InterfaceInitTest1 extends InterfaceInitTest {
 int B = 100;
}
class InterfaceInitTestImpl implements InterfaceInitTest1 {
 public static void main(String[] args) {
 System.out.println(InterfaceInitTestImpl.B);
 System.out.println("---------------------------");
 System.out.println ( "Current Time:" + InterfaceInitTestImpl.A);
 }
}
class CurrentTime {
 static long getTime() {
 System.out.println ( "loaded InterfaceInitTest Interface");
 return System.currentTimeMillis();
 }
}
// output
//100
//---------------------------
// Load the InterfaceInitTest Interface
// Current Time: 1560158880660

Output from verified: For the interface, the interface only really use the parent class variable will really load parent interface. This is not the same as an ordinary class load.

  • Virtual opportunity to ensure that a class <clinit> method is correctly locked in a multithreaded environment, and synchronization, if multiple threads to initialize a class, so only one thread to perform this type of <clinit> method, other threads are need to block until an active thread execution <clinit> method is completed.
public class MultiThreadInitTest {
 static int A = 10;
 static {
 System.out.println(Thread.currentThread()+"init MultiThreadInitTest");
 try {
 TimeUnit.SECONDS.sleep(10);
 } catch (InterruptedException e) {
 e.printStackTrace ();
 }
 }
 public static void main(String[] args) {
 Runnable runnable = () -> {
 System.out.println(Thread.currentThread() + "start");
 System.out.println(MultiThreadInitTest.A);
 System.out.println(Thread.currentThread() + "run over");
 };
 Thread thread1 = new Thread(runnable);
 Thread thread2 = new Thread(runnable);
 thread1.start();
 thread2.start();
 }
}
// output
//Thread[main,5,main]init MultiThreadInitTest
//Thread[Thread-0,5,main]start
//10
//Thread[Thread-0,5,main]run over
//Thread[Thread-1,5,main]start
//10
//Thread[Thread-1,5,main]run over

See from the output verified: only the first thread to initialize MultiThreadInitTest conducted a second thread is blocked waiting rankings has been a thread initialized.

3.2, class initialization timing

  1. When a virtual machine starts, initializing the main class specified by the user;
  2. When faced with new instructions for the new instance of the target class, the target class to initialize the new instruction;
  3. When faced with the static method call or using a static variable or static variable initialization method where the class;
  4. Subclass initialization process will trigger the parent class initialization;
  5. If the interface defines a default method, then directly or indirectly implement this interface, class initialization, the interface initialization is triggered;
  6. When a class is reflected calls using reflection API, initialize this class;
  7. Class.forName () will trigger initialization class

3.3, final defined initialization

Note: For a definition of constant final use, if at compile time value has been determined, in reference to not trigger initialization, because at compile time has been determined, is the "macro variables." If you can not determine at compile time, it will lead to the first use initialization.

public class StaticInnerSingleton {
 /**
 * Using a static inner class singleton:
 * 1: Thread safety
 * 2: Lazy Load
 * 3: Non deserialized security, the object that is deserialized get the time sequence of the same object is not a single case of a violation of the principle of single cases
 */
 private static class LazyHolder {
 private static final StaticInnerSingleton INNER_SINGLETON = new StaticInnerSingleton();
 }
 private StaticInnerSingleton() {
 }
 public static StaticInnerSingleton getInstance() {
 return LazyHolder.INNER_SINGLETON;
 }
}

See example, Singleton static inner class implementation. We can see the singleton instance final definition, but can not be determined at compile time down, so the first time you use StaticInnerSingleton.getInstance () method to trigger load static inner classes, that is lazy loading. Here I would point out that if the final definition of variables can not be determined at compile time, or when you use the class will be initialized.

3.4, ClassLoader class will be loaded and will not initialize

public class Tester {
 static {
 System.out.println ( "Tester static class initialization block");
 }
}
class ClassLoaderTest {
 public static void main(String[] args) throws ClassNotFoundException {
 ClassLoader classLoader = ClassLoader.getSystemClassLoader();
 // The following statement is just loaded Tester class
 classLoader.loadClass("loader.Tester");
 System.out.println ( "Tester class loading system");
 // the following statement will initialize class Tester
 Class.forName("loader.Tester");
 }
}
// output
// Load Tester class system
// Tester static class initialization block

: Output proved ClassLoader class will be loaded, do not initialize; use Class.forName () forces cause class initialization.

Third, the class loader

Class loader is responsible for the .class file (whether jar, or a local disk, or network acquisition, etc.) is loaded into memory for, and generate the corresponding java.lang.Class objects. A class is loaded into the JVM will not load the second time.

How to judge that it is the same class?

Each class using the fully qualified class name (package name + class name) and class loader in the JVM joint is a unique ID, so that if a different class loader same class, may be loaded into the virtual machine, but not to one another compatible.

1, JVM class loader classification

1.1、Bootstrap ClassLoader

Bootstrap ClassLoader is the root class loader is responsible for loading the java core libraries. Subclass ClassLoader loader is not the root, there is a C ++ implementation.

public class BootstrapTest {
 public static void main(String[] args) {
 // Get an array of all the root URL class loader loaded
 URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
 Arrays.stream(urLs).forEach(System.out::println);
 }
}
// output
//file:/C:/SorftwareInstall/java/jdk/jre/lib/resources.jar
//file:/C:/SorftwareInstall/java/jdk/jre/lib/rt.jar
//file:/C:/SorftwareInstall/java/jdk/jre/lib/sunrsasign.jar
//file:/C:/SorftwareInstall/java/jdk/jre/lib/jsse.jar
//file:/C:/SorftwareInstall/java/jdk/jre/lib/jce.jar
//file:/C:/SorftwareInstall/java/jdk/jre/lib/charsets.jar
//file:/C:/SorftwareInstall/java/jdk/jre/lib/jfr.jar
//file:/C:/SorftwareInstall/java/jdk/jre/classes

Root class loader is responsible for loading the next pack jar% JAVA_HOME% / jre / lib (-Xbootclasspath parameters by the virtual machine, and the class specified).

Interview: class loading process, the class loading mechanism and a custom class loader Detailed

 

We will rt.jar decompression, the library can see we often use in this jar package.

1.2 、Extension ClassLoader

Extension ClassLoader for the extension class loader is responsible for loading the familiar specified directory jar package% JAVA_HOME% / jre / ext or java.ext.dirs system. You can write your own kit together under this directory, you can easily own use.

1.3、 System ClassLoader

System ClassLoader system (application) class loader is responsible for loading from the loading -classpath java command option, the java.class.path system property or the CLASSPATH environment variable and a JAR file specified class path. Programs may be acquired through the system class loader ClassLoader.getSystemClassLoader (). If not specified, the user-defined class loader will default to the system as the parent class loader loader.

Fourth, the class loading mechanism

1.1, JVM major class loading mechanism.

  1. Overall responsibility : when a class loader is responsible for loading a Class, and the Class depends cited also responsible for other Class loaded by the class loader, unless the display with another class loader to load.
  2. Parent trust (parents delegate) : let the parent loader attempts to load the Class, only when the parent class loader can not load the loader will attempt to load the class from their own class path.
  3. Cache mechanism : when caching mechanisms already loaded class will be cached, when the program requires the use of a Class, class loader first from the buffer zone in search of the Class, Class only when the cache does not exist, the system will read take such binary data, and convert it to a Class object, stored in the cache. That is why change a class, you need to restart the cause of the JVM to take effect.

Note: The parent-child relationship between the class loader on the parent-child relationship is not a class inheritance, but the parent-child relationship between the instances.

Interview: class loading process, the class loading mechanism and a custom class loader Detailed

 

public class ClassloaderPropTest {
 public static void main(String[] args) throws IOException {
 // Get the system class loader
 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
 System.out.println ( "class loader system:" + systemClassLoader);
 /*
 Acquisition system class loader load path - typically specified by the CLASSPATH environment variable, if the operating system is not specified
 CLASSPATH environment variable, the default path to the current path as a loading system class loader
 */
 Enumeration<URL> eml = systemClassLoader.getResources("");
 while (eml.hasMoreElements()) {
 System.out.println(eml.nextElement());
 }
 // Get system class loader parent class loader, class loader expanded
 ClassLoader extensionLoader = systemClassLoader.getParent();
 System.out.println("系统类的父加载器是扩展类加载器:" + extensionLoader);
 System.out.println("扩展类加载器的加载路径:" + System.getProperty("java.ext.dirs"));
 System.out.println("扩展类加载器的parant:" + extensionLoader.getParent());
 }
}
//输出结果
//系统类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
//file:/C:/ProjectTest/FengKuang/out/production/FengKuang/
//系统类的父加载器是扩展类加载器:sun.misc.Launcher$ExtClassLoader@1540e19d
//扩展类加载器的加载路径:C:\SorftwareInstall\java\jdk\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
//扩展类加载器的parant:null

从输出中验证了:系统类加载器的父加载器是扩展类加载器。但输出中扩展类加载器的父加载器是null,这是因为父加载器不是java实现的,是C++实现的,所以获取不到。但扩展类加载器的父加载器是根加载器。

1.2、类加载流程图

Interview: class loading process, the class loading mechanism and a custom class loader Detailed

 

图中红色部分,可以是我们自定义实现的类加载器来进行加载。

五、创建并使用自定义类加载器

1、自定义类加载分析

除了根类加载器,所有类加载器都是ClassLoader的子类。所以我们可以通过继承ClassLoader来实现自己的类加载器。

ClassLoader类有两个关键的方法:

  1. protected Class loadClass(String name, boolean resolve):name为类名,resove如果为true,在加载时解析该类。
  2. protected Class findClass(String name) :根据指定类名来查找类。

所以,如果要实现自定义类,可以重写这两个方法来实现。但推荐重写findClass方法,而不是重写loadClass方法,因为loadClass方法内部回调用findClass方法。

我们来看一下loadClass的源码

protected Class<?> loadClass(String name, boolean resolve)
 throws ClassNotFoundException
 {
 synchronized (getClassLoadingLock(name)) {
 //第一步,先从缓存里查看是否已经加载
 Class<?> c = findLoadedClass(name);
 if (c == null) {
 long t0 = System.nanoTime();
 try {
 //第二步,判断父加载器是否为null
 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) {
 //第三步,如果前面都没有找到,就会调用findClass方法
 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;
 }
 }

loadClass加载方法流程:

  1. 判断此类是否已经加载;
  2. 如果父加载器不为null,则使用父加载器进行加载;反之,使用跟加载器进行加载;
  3. 如果前面都没加载成功,则使用findClass方法进行加载。

所以,为了不影响类的加载过程,我们重写findClass方法即可简单方便的实现自定义类加载。

2、实现自定义类加载器

基于以上分析,我们简单重写findClass方法进行自定义类加载。

public class Hello {
 public void test(String str){
 System.out.println(str);
 }
}
public class MyClassloader extends ClassLoader {
 /**
 * 读取文件内容
 *
 * @param fileName 文件名
 * @return
 */
 private byte[] getBytes(String fileName) throws IOException {
 File file = new File(fileName);
 long len = file.length();
 byte[] raw = new byte[(int) len];
 try (FileInputStream fin = new FileInputStream(file)) {
 //一次性读取Class文件的全部二进制数据
 int read = fin.read(raw);
 if (read != len) {
 throw new IOException("无法读取全部文件");
 }
 return raw;
 }
 }
 @Override
 protected Class<?> findClass(String name) throws ClassNotFoundException {
 Class clazz = null;
 //将包路径的(.)替换为斜线(/)
 String fileStub = name.replace(".", "/");
 String classFileName = fileStub + ".class";
 File classFile = new File(classFileName);
 //如果Class文件存在,系统负责将该文件转换为Class对象
 if (classFile.exists()) {
 try {
 //将Class文件的二进制数据读入数组
 byte[] raw = getBytes(classFileName);
 //调用ClassLoader的defineClass方法将二进制数据转换为Class对象
 clazz = defineClass(name, raw, 0, raw.length);
 } catch (IOException e) {
 e.printStackTrace();
 }
 }
 //如果clazz为null,表明加载失败,抛出异常
 if (null == clazz) {
 throw new ClassNotFoundException(name);
 }
 return clazz;
 }
 public static void main(String[] args) throws Exception {
 String classPath = "loader.Hello";
 MyClassloader myClassloader = new MyClassloader();
 Class<?> aClass = myClassloader.loadClass(classPath);
 Method main = aClass.getMethod("test", String.class);
 System.out.println(main);
 main.invoke(aClass.newInstance(), "Hello World");
 }
}
//输出结果
//Hello World

ClassLoader还有一个重要的方法defineClass(String name, byte[] b, int off, int len)。此方法的作用是将class的二进制数组转换为Calss对象。

此例子很简单,我写了一个Hello测试类,并且编译过后放在了当前路径下(大家可以在findClass中加入判断,如果没有此文件,可以尝试查找.java文件,并进行编译得到.class文件;或者判断.java文件的最后更新时间大于.class文件最后更新时间,再进行重新编译等逻辑)。

发布了159 篇原创文章 · 获赞 45 · 访问量 1万+

Guess you like

Origin blog.csdn.net/qq_45401061/article/details/104225789