Last review
1. Are the seven processes of class loading clear?
2. Six conditions related to class initialization
3. What are the differences between array loading and reference type loading
I don’t know about that. The portal
This chapter content
Without further discussion, I’ll pick up where I left off. This chapter focuses on class loaders, the parental delegation model, and “breaking” the parental delegation model, and I’ll try to impress you by overwriting loadClss () and findClass ().
Class loader
The “get the binary stream describing a class by its fully qualified name” action is implemented outside the Java virtual machine so that the application can decide how to get the required classes. The code that does this is called a Class Loader.
A classloader does more than just fetch the binary stream describing a class by its fully qualified name. It is also a sign of class uniqueness. Two classes are equal only if they have the same classloader. Otherwise there would be no point in discussing it.
1. Parental delegation model
I’m sure you’ve all seen this relationship picture
This is the classic parent delegate model of the three-tier model, except the top start class loader is JVM through c++ language to implement, the other class loaders are all implemented in Java language, and is independent of the virtual machine, so it provides us a lot of room to operate.
Logically these three layers are a top-down inheritance of a parent class, but in fact they are a composite reuse relationship, which needs to be distinguished.
This sentence is best understood by the way he works: When a classloader receives a classload request, it first does not attempt to load the class itself. 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.
In other words, when a class needs to be loaded, our class loader will start from the bottom up and then call the current class loader to load the class when the parent says it can’t load the class. So all classloaders are cooperative combinations, not strictly parent-child relationships.
Here is a brief description of the responsibilities of each loader
1. Start the class loader
This class loader is responsible for loading files stored in <JAVA_HOME>\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.
Java.lang. ClassLoader does not inherit the abstract java.lang.ClassLoader class, which is native to the JVM and cannot be directly referenced in Java code.
2. Extend the class loader
The classloader is implemented as Java code in the sun.misc.Launcher$ExtClassLoader class. It is responsible for loading all libraries in the <JAVA_HOME>\lib\ext directory, or in the path specified by the java.ext.dirs system variable.
Inherits the abstract java.lang.ClassLoader class and can load class files directly in Java code using the extended ClassLoader.
3. Application class loader
The classloader is implemented by sun.misc.Launcher$AppClassLoader. Because the application ClassLoader is the return value of the getSystemClassLoader() 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. If the application does not have its own custom class loader, this is generally the default class loader in the application.
Two, parent delegate model source code
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
Remember the method they gave you in the high concurrency series? Look at the source code first look at the method annotations
* <ol> * * <li><p> Invoke {@link #findLoadedClass(String)} to check if the class * has already been loaded. </p></li> * * <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method * on the parent class loader. If the parent is <tt>null</tt> the class * loader built-in to the virtual machine is used, instead. </p></li> * * <li><p> Invoke the {@link #findClass(String)} method to find the * class. </p></li> * * </ol>Copy the code
So let’s focus on this
-
The main logic of this method is described here
1. Call findLoadedClass(String) to determine whether the class has already been loaded.
2. When the parent is not empty, the loadClass(String) of the parent is called. If the parent is empty, the virtual machine has its own loader, that is, the startup class loader.
3. If all of the above loaders fail, call findClass(String) to load the class using a user-defined lookup.
The parent delegate model is not mandatory. If the parent delegate model fails to load, we are provided with a findClass(String) method to load the class.
Three, the destruction of parental delegation
The loadClass () source code also shows that the parent delegate model is not a constraint model. When the parent delegate model fails to load, we can customize findClass () to find classes.
In some specific scenarios, we need to break this model to achieve our purpose, which in turn proves that the relationship between the three layers of the parent delegation model is only a seemingly parent-child inheritance relationship, but actually a combinatorial reuse relationship.
There were three cases in which the parental delegation model was broken
It was destroyed for the first time
Since the parent delegate model was introduced after JDK 1.2, but the concept of class loaders and the abstract java.lang.ClassLoader existed in the first version of Java, faced with the existing code for user-defined classloaders, Java designers had to make some compromises when introducing the parental delegation model. To accommodate the existing code, they could no longer technically avoid the possibility of loadClass() being overridden by subclasses. You can only add a new protected method, findClass(), to java.lang.classloader after JDK 1.2 and override it as much as possible when instructing user-written classloading logic instead of writing code in loadClass().
It was destroyed a second time
It’s all about loops, and no matter what architecture you design, no matter how perfect it is, there are always some requirements and some people that will do something magical about loops. A depends on B, and B depends on A.
But the truth is, programs can’t do everything, but people can. It’s always people, not programs, who make compromises.
Tell a story
The second “break” of the parent delegate model is caused by the defects of the model itself. Parent delegate is a good solution to the problem of consistency of the base type when class loaders cooperate (the more basic classes are loaded by the higher loaders). The base type is called “base”. Because they always exist as apis that are inherited and called by user code, but there are often no perfect rules of programming that are immutable. What if you have an underlying type that calls back to the user’s code?
A good example of this is JNDI services, which are now standard in Java and whose code is loaded by the bootstrap class loader (added to rt.jar in JDK 1.3), and are certainly of a very basic type in Java. But THE purpose of JNDI is to lookup and centrally manage resources. It calls code that is implemented by other vendors and deployed in the application’s ClassPath on the JNDI Service Provider Interface (SPI). There is no way that the bootstrap class loader will recognize and load this code, so what happens?
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.
With thread-context classloaders, programs can do “dirty” things. JNDI service USES the thread context class loader to load the required SPI service code, this is a parent class loader to request a subclass loader finish class loading behavior, this behavior is, in fact, get through the parents delegation model hierarchy to reverse the use of class loaders, has violated the parents delegation model of general principles of but also helpless. Loading in Java that involves SPIs, such as JNDI, JDBC, JCE, JAXB, and JBI, is basically done this way. However, when SPI has more than one service provider, the code must be hardcoded according to the type of the specific provider. To eliminate this unelegant implementation, in JDK 6 the java.util.Serviceloader class was provided. The configuration information in META-INF/ Services, coupled with the chain of responsibility pattern, provides a reasonable solution for SPI loading.
Destroyed for the third time
The third “break” of the parental delegation model is due to the user’s desire for dynamic application, and by “dynamic” I mean some very “Hot” terms: Hot Swap, Hot Deployment, etc. To put it simply, we want Java applications to be like our computer peripherals, connected to a mouse, usb flash drive, immediately usable without rebooting the machine, a problem with the mouse or to upgrade the mouse to change the mouse, no need to shut down or restart. For a PC, a restart is not a big deal, but for some production systems, a shutdown and restart may be classified as a production incident, in which case hot deployment is very attractive to software developers, especially large systems or enterprise software developers.
Overwrite loadClass and findClass
1. Override localClass to load it through a custom loader
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException { ClassLoader myLoader = new ClassLoader() { @Override public Class<? > loadClass(String name) throws ClassNotFoundException { try { String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class"; InputStream is = getClass().getResourceasStream (fileName); if (is == null) { return super.loadClass(name); } byte[] b = new byte[is.available()]; is.read(b); Return defineClass(name, b, 0, b.length); } catch (IOException e) { throw new ClassNotFoundException(name); }}}; Object obj = myLoader.loadClass("day6.ExtendTestClass").newInstance(); ExtendTestClass = new ExtendTestClass(); System.out.println(" create object by custom loader myLoader, class loader: "+ obj.getClass().getClassLoader()); System.out.println(" create object by new, classloader: "+ extendTestClass.getClass().getClassLoader()); System.out.println(obj instanceof ExtendTestClass); System.out.println(extendTestClass instanceof ExtendTestClass); }Copy the code
The output
ClassLoaders: day6.classloaders $1@60e53b93; new: myLoader: day6.classloaders $1@60e53b93 sun.misc.Launcher$AppClassLoader@18b4aac2 false trueCopy the code
Here we customize a classloader by overwriting loadClass, and the effect is already obvious by comparing the traditional new() keyword to create objects and custom classloaders. There is also a conclusion that the same class can be loaded multiple times through different loaders.
We would only consider doing this in special cases, because overwriting loadClass breaks the parent-delegate model
2. Findclass implements the hot replacement function of a class file
/** * Finds the class with the specified <a href="#name">binary name</a>. * This method should be overridden by class loader implementations that * follow the delegation model for loading classes, and will be invoked by * the {@link #loadClass <tt>loadClass</tt>} method after checking the * parent class loader for the requested class. The default implementation * throws a <tt>ClassNotFoundException</tt>. * * @param name * The <a href="#name">binary name</a> of the class * * @return The resulting <tt>Class</tt> object * * @throws ClassNotFoundException * If the class could not be found * * @since 1.2 */ protected class <? > findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }Copy the code
Findclass () {return ClassNotFoundException (); This means that in general, classes are loaded in accordance with the parent delegate model, but there are classes that the parent delegate model cannot load, or we can override findClass to load a specified class in a specified path.
Since findClass normally calls a method only when both parent delegate models fail to load, we will break the parent delegate and override loadClass to call findClass directly, implementing our own logic.
1. Specify a MyClassLoader
public class MyClassLoader extends ClassLoader { private final static Path DEFAULT_PATH = Paths.get("/Users/doudou/workspace", "jvm/jvm/src"); private final Path classdir; public MyClassLoader() { super(); classdir = DEFAULT_PATH; } public MyClassLoader(String classPath) { super(); this.classdir = Paths.get(classPath); } public MyClassLoader(ClassLoader parent, String classPath) throws ClassNotFoundException { super(parent); classdir = Paths.get(classPath); } @Override protected Class<? > findClass(String name) throws ClassNotFoundException { byte[] bytes = this.readClassBytes(name); if (null == bytes || bytes.length == 0) { throw new ClassNotFoundException("Can not load the class" + name); } return this.defineClass(name, bytes, 0, bytes.length); } private byte[] readClassBytes(String name) throws ClassNotFoundException { String classPath = name.replace(".", "/"); Path classPullPath = classdir.resolve(Paths.get(classPath) + ".class"); if (! classPullPath.toFile().exists()) { throw new ClassNotFoundException("The class" + name + "not found."); } try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { Files.copy(classPullPath, byteArrayOutputStream); return byteArrayOutputStream.toByteArray(); } catch (IOException e) { throw new ClassNotFoundException("load the class" + name + "occur error." + e); } } @Override public String toString() { return "My ClassLoader"; }}Copy the code
Here are two main methods, divided into three steps to complete
1. For the first step we override findClass () inherited from ClassLoaer.
2. Call readClassBytes(String Name) inside findClass to customize how the file is loaded. Here we wrap our logic with try-with-resource
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
Files.copy(classPullPath, byteArrayOutputStream);
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
throw new ClassNotFoundException("load the class" + name + "occur error." + e);
}
Copy the code
Start a byte stream, copy the file inside the byte stream, and return it to FindClass
3. Call this.defineclass (name, bytes, 0, bytes.length); To convert a byte stream into an object.
2. In order to prevent our class from being preempted by the application class loader, we override loadClass to simulate a parent delegate load failure.
/** * Select * from 'loadClass'; /** * select * from 'loadClass'; If it starts with Java or Javax, use the system class loader directly, otherwise use the custom class loader first. If it doesn't load, check if there is a parent, if there is, use the parent, if there is not, use the system loader * and then break the loading order. Public class BrockdelegateClassLoader extends MyClassLoader {@override protected class <? > loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class<? > loadedClass = findLoadedClass(name); if (loadedClass == null) { if (name.startsWith("java.") || name.startsWith("javax.")) { try { loadedClass = getSystemClassLoader().loadClass(name); } catch (Exception e) { System.out.println(e.getMessage()); } } else { loadedClass = this.findClass(name); if (loadedClass == null) { if (getParent() ! = null) { loadedClass = getParent().loadClass(name); } else { loadedClass = getSystemClassLoader().loadClass(name); }}}} else {system.out.println (" class obtained in cache "); return loadedClass; } if (loadedClass == null) { throw new ClassNotFoundException("the class " + name + " not found"); } if (resolve) { resolveClass(loadedClass); } return loadedClass; } } public BrockdelegateClassLoader(String name) { super(name); } public BrockdelegateClassLoader() { super(); }}Copy the code
Unless the system classloader is called by a Java or Javax-starting file on the classpath, our custom findClass () logic is executed directly. This is why we overwrite the loadClass to break the parent delegate model.
3. As we have said before, a class can only be judged to be the same if the class loaders are the same. In this case, the class cannot be loaded repeatedly only if the class loaders are the same. So our classloader needs to be new every time we load it. This should be easy for you to understand.
public class Test { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException NoSuchMethodException, InvocationTargetException InterruptedException {/ / / / if the object of the class loader in circulation outside, If you load the same class loader every time, you don't get the hot replacement effect of class files, // BrockdelegateClassLoader myClassLoader = new BrockdelegateClassLoader(); while (true) { BrockdelegateClassLoader myClassLoader = new BrockdelegateClassLoader(); // // if you open it, you'll see that our class loader is Sun.misc.Launcher$AppClassLoader, so it only loads once, // MyClassLoader MyClassLoader = new MyClassLoader(); Class<? > aClass1 = myClassLoader.loadClass("day3.HelloWorld"); System.out.println(aClass1.getClassLoader()); Object o = aClass1.newInstance(); Method say = aClass1.getMethod("say"); Object invoke = say.invoke(o); System.out.println(invoke); TimeUnit.SECONDS.sleep(3); }}}Copy the code
Through our test class debugging can easily achieve the hot replacement of class files. The procedure is to run the main function, and then regenerate the class file using the Javac instruction by modifying the return value of say(). Since the interval of class loading is set to be every 3 seconds, the next output after the substitution will see the effect.
The specific effects are as follows:
My ClassLoader helloworld initialization hello world333 My ClassLoader helloworld initialization hello world333 My ClassLoader HelloWorld Initialization Hello World initialization My ClassLoader HelloWorld Initialization Hello world is initializedCopy the code
How to break the parent delegate model by overwriting loadClass and findClass.
So far we have covered the structure parsing of class files, the loading process of class files, and the loader of class files in four chapters. The next one will start with a brief introduction to gc for the JVM.
Finally, let me see which big smart has not yet praised 👍!