How does Tomcat break the parental delegation mechanism

What is the parent delegation mechanism

Java class loading is the process of loading a bytecode “.class” file into the JVM’s method area and creating an instance of a Java.lang. class object in the JVM’s heap area to encapsulate the data and methods associated with Java classes. JVM class loading is done by class loaders, and the JDK provides an abstract class ClassLoader.


public abstract class ClassLoader {

    // Every class loader has a parent loader
    private final ClassLoader parent;
    
    publicClass<? > loadClass(String name) {// Check to see if the class is already loadedClass<? > c = findLoadedClass(name);// If not loaded
        if( c == null) {// Delegate the load to the parent loader. Note that this is a recursive call
          if(parent ! =null) {
              c = parent.loadClass(name);
          }else {
              // If the parent loader is empty, check to see if the Bootstrap loader has been loadedc = findBootstrapClassOrNull(name); }}// If the parent loader fails, call findClass to load it
        if (c == null) {
            c = findClass(name);
        }
        
        returnC; }protectedClass<? > findClass(String name){//1. Read the.class file into memory in the specified directory according to the class name.//2. Call defineClass to convert the byte array into a Class object
       returnDefineClass (buf, off, len); }// Parse the bytecode array into a Class object using native methods
    protected finalClass<? > defineClass(byte[] b, int off, intlen){ ... }}Copy the code

First check whether the class has been loaded. If it has been loaded, it will return directly. Otherwise, it will be handed over to the parent loader. This process is called the parent delegate mechanism. This mechanism ensures that a class is unique within the JVM. **

Class loader for the JDK

  • BootstrapClassLoader BootstrapClassLoader BootstrapClassLoader BootstrapClassLoader BootstrapClassLoader BootstrapClassLoader BootstrapClassLoader BootstrapClassLoader BootstrapClassLoader BootstrapClassLoader BootstrapClassLoader BootstrapClassLoader BootstrapClassLoader BootstrapClassLoader BootstrapClassLoader BootstrapClassLoader BootstrapClassLoader
  • ExtClassLoader is an extension class loader used to load JAR packages in the JRE /lib/ext directory
  • AppClassLoader is the system class loader used to load classes in the classpath. Applications use it by default to load classes
  • Custom class loader, used to load classes in a custom path

How does Tomcat break the parental delegation mechanism

This is done by customizing a class loader, WebappClassLoader. WebappClassLoader inherits from ClassLoader. The logic for loading the class:

  • Check whether the WebappClassLoader has loaded the class. If yes, return
  • Check the system loader to see if it has been loaded, and return if it has
  • Attempts to load using the ExtClassLoader class loader, returns if any
  • Load from a local directory
  • Try loading using the system class loader (i.e. AppClassLoader)

Before loading the local directory, the ExtClassLoader is used to avoid overwriting the classes under JRE /lib/ext and the customized loading logic of the core class WebappClassLoader to achieve: Classes in the Web application directory are loaded first, and then classes in other directories.

How does Tomcat customize thread pools

The JDK thread pool

To distinguish the differences between JDK native thread pools and Tomcat custom thread pools, here is a brief description of how JDK thread pools work. Example of thread pool usage

ThreadPoolExecutor executor = new ThreadPoolExecutor(10.20.60, 
                                                     TimeUnit.SECONDS, new LinkedBlockingQueue<>());

executor.execute(() -> {
    System.out.println("Tasks completed by thread pool");
});
Copy the code

The process of submitting tasks

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();

    if (workerCountOf(c) < corePoolSize) {
        // If the core threads are not all started, start the core threads and perform the submission task
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    
    // The core thread is full
    // If the thread pool is not closed, the task is added to the task queue
    if (isRunning(c) && workQueue.offer(command)) {
        // Double check, if the thread pool is closed, then the task is removed from the queue.
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            // The task is removed from the queue successfully
            reject(command);
        else if (workerCountOf(recheck) == 0)
            // If there are no runnable threads, start a non-core thread to execute the task
            addWorker(null.false);
    }
    // Can not add to the task queue, then start non-core threads
    else if(! addWorker(command,false))
        // The number of threads is full, execute the reject policy
        reject(command);
}
Copy the code

The execution behavior of thread pool is controlled by the number of core threads, the maximum number of threads and the task queue.

  1. Preferentially start threads with the size of core threads.
  2. If the number of core threads is reached, the task is put into the task queue.
  3. When the task queue is full, new threads are started until the maximum number of threads is reached.

Tomcat’s revamp of thread pools

Thread pool By default, the maximum number of threads is not started, but submitted tasks are put into a task queue. To maximize the performance of the server, Tomcat preferentially starts the threads specified by the maximum number of threads for processing requests. This is done by passing in a custom queue: TaskQueue.

public class TaskQueue extends LinkedBlockingQueue<Runnable> {
    
    // Omit some code
    
    @Override
    public boolean offer(Runnable o) {
      //we can't do any checks
        if (parent==null) return super.offer(o);
        //we are maxed out on threads, simply queue the object
        // If the number of threads reaches the maximum limit, the task is queued
        if (parent.getPoolSize() == parent.getMaximumPoolSize()) return super.offer(o);
        //we have idle threads, just add it to the queue
        // If the number of submitted tasks is smaller than the number of threads in the thread pool, it indicates that there are free threads
        if (parent.getSubmittedCount()<=(parent.getPoolSize())) return super.offer(o);
        //if we have less threads than maximum force creation of a new thread
        // If the number of threads in the thread pool is less than the maximum number of threads, the thread is started
        if (parent.getPoolSize()<parent.getMaximumPoolSize()) return false;
        //if we reached here, we need to add it to the queue
        // 
        return super.offer(o); }}Copy the code
public void execute(Runnable command, long timeout, TimeUnit unit) {
    // Record the number of submitted tasks
    submittedCount.incrementAndGet();
    try {
        // Execute the logic of the default commit task
        super.execute(command);
    } catch (RejectedExecutionException rx) {
        // When a rejection policy occurs, if it is a customized task queue
        if (super.getQueue() instanceof TaskQueue) {
            final TaskQueue queue = (TaskQueue)super.getQueue();
            try {
                // Put the task into a queue with a timeout mechanism
                if(! queue.force(command, timeout, unit)) { submittedCount.decrementAndGet();throw new RejectedExecutionException(sm.getString("threadPoolExecutor.queueFull")); }}catch (InterruptedException x) {
                submittedCount.decrementAndGet();
                throw newRejectedExecutionException(x); }}else {
            submittedCount.decrementAndGet();
            throwrx; }}}Copy the code

Through the above modification, Tomcat preferentially starts the maximum number of threads to execute the task, and puts the task into the queue by using the queuing policy with timeout mechanism when the rejection policy occurs.