docs: protected void stopThreads(ClassLoaderLeakPreventor preventor) {
      final Class<?> workerClass = preventor.findClass("java.util.concurrent.ThreadPoolExecutor$Worker");
  
      final boolean waitForThreads = threadWaitMs > 0;
      for(Thread thread : preventor.getAllThreads()) {
        final Runnable runnable = getRunnable(preventor, thread);
  
        final boolean threadLoadedByClassLoader = preventor.isLoadedInClassLoader(thread);
        final boolean threadGroupLoadedByClassLoader = preventor.isLoadedInClassLoader(thread.getThreadGroup());
        final boolean runnableLoadedByClassLoader = preventor.isLoadedInClassLoader(runnable);
        final boolean hasContextClassLoader = preventor.isClassLoaderOrChild(thread.getContextClassLoader());
        if(thread != Thread.currentThread() && // Ignore current thread
           (threadLoadedByClassLoader || threadGroupLoadedByClassLoader || hasContextClassLoader || // = preventor.isThreadInClassLoader(thread) 
            runnableLoadedByClassLoader)) {
  
          if (thread.getClass().getName().startsWith(StopThreadsCleanUp.JURT_ASYNCHRONOUS_FINALIZER)) {
            // Note, the thread group of this thread may be "system" if it is triggered by the Garbage Collector
            // however if triggered by us in forceStartOpenOfficeJurtCleanup() it may depend on the application server
            if(stopThreads) {
              preventor.info("Found JURT thread " + thread.getName() + "; starting " + JURTKiller.class.getSimpleName());
              new JURTKiller(preventor, thread).start();
            }
            else
              preventor.warn("JURT thread " + thread.getName() + " is still running in protected ClassLoader");
          }
          else if(thread.getThreadGroup() != null && 
             ("system".equals(thread.getThreadGroup().getName()) ||  // System thread
              "RMI Runtime".equals(thread.getThreadGroup().getName()))) { // RMI thread (honestly, just copied from Tomcat)
            
            if("Keep-Alive-Timer".equals(thread.getName())) {
              thread.setContextClassLoader(preventor.getLeakSafeClassLoader());
              preventor.debug("Changed contextClassLoader of HTTP keep alive thread");
            }
          }
          else if(thread.isAlive()) { // Non-system, running in protected ClassLoader
  
            if(thread.getClass().getName().startsWith("java.util.Timer")) { // Sun/Oracle = "java.util.TimerThread"; IBM = "java.util.Timer$TimerImpl"
              if(thread.getName() != null && thread.getName().startsWith("PostgreSQL-JDBC-SharedTimer-")) { // Postgresql JDBC timer thread
                // Replace contextClassLoader, if needed
                if(hasContextClassLoader) {
                  final Class<?> postgresqlDriver = preventor.findClass("org.postgresql.Driver");
                  final ClassLoader postgresqlCL = (postgresqlDriver != null && ! preventor.isLoadedByClassLoader(postgresqlDriver)) ?
                      postgresqlDriver.getClassLoader() : // Postgresql driver loaded by other classloader than we want to protect
                      preventor.getLeakSafeClassLoader();
                  thread.setContextClassLoader(postgresqlCL);
                  preventor.warn("Changing contextClassLoader of " + thread + " to " + postgresqlCL);
                }
  
                // Replace AccessControlContext
                final Field inheritedAccessControlContext = preventor.findField(Thread.class, "inheritedAccessControlContext");
                if(inheritedAccessControlContext != null) {
                  try {
                    final AccessControlContext acc = preventor.createAccessControlContext();
                    inheritedAccessControlContext.set(thread, acc);
                    preventor.removeDomainCombiner("thread " + thread, acc);
                  }
                  catch (Exception e) {
                    preventor.error(e);
                  }
                }
              }
              else if(stopTimerThreads) {
                preventor.warn("Stopping Timer thread '" + thread.getName() + "' running in protected ClassLoader. " +
                    preventor.getStackTrace(thread));
                stopTimerThread(preventor, thread);
              }
              else {
                preventor.info("Timer thread is running in protected ClassLoader, but will not be stopped. " + 
                    preventor.getStackTrace(thread));
              }
            }
            else {
              final String displayString = "Thread '" + thread + "'" + 
                  (threadLoadedByClassLoader ? " of type " + thread.getClass().getName() + " loaded by protected ClassLoader" : "") +
                  (runnableLoadedByClassLoader ? " with Runnable of type " + runnable.getClass().getName() + " loaded by protected ClassLoader" : "") +
                  (threadGroupLoadedByClassLoader ? " with ThreadGroup of type " + thread.getThreadGroup().getClass().getName() + " loaded by protected ClassLoader" : "") +
                  (hasContextClassLoader ? " with contextClassLoader = protected ClassLoader or child" : "");
  
              // If threads is running an java.util.concurrent.ThreadPoolExecutor.Worker try shutting down the executor
              if(workerClass != null && workerClass.isInstance(runnable)) {
                try {
                  // java.util.concurrent.ThreadPoolExecutor, introduced in Java 1.5
                  final Field workerExecutor = preventor.findField(workerClass, "this$0");
                  final ThreadPoolExecutor executor = preventor.getFieldValue(workerExecutor, runnable);
                  if(executor != null) {
                    if("org.apache.tomcat.util.threads.ThreadPoolExecutor".equals(executor.getClass().getName())) {
                      // Tomcat pooled thread
                      preventor.debug(displayString + " is worker of " + executor.getClass().getName());
                    }
                    else if(preventor.isLoadedInClassLoader(executor) || preventor.isLoadedInClassLoader(executor.getThreadFactory())) {
                      if(stopThreads) {
                        preventor.warn("Shutting down ThreadPoolExecutor of type " + executor.getClass().getName());
                        executor.shutdownNow();
                      }
                      else {
                        preventor.warn("ThreadPoolExecutor of type " + executor.getClass().getName() +
                            " should be shut down.");
                      }
                    }
                    else {
                      preventor.info(displayString + " is a ThreadPoolExecutor.Worker of " + executor.getClass().getName() +
                          " but found no reason to shut down ThreadPoolExecutor.");
                    }
                  }
                }
                catch (Exception ex) {
                  preventor.error(ex);
                }
              }
  
              if(! threadLoadedByClassLoader && ! runnableLoadedByClassLoader && ! threadGroupLoadedByClassLoader) { // Not loaded in protected ClassLoader - just running there
                // This would for example be the case with org.apache.tomcat.util.threads.TaskThread
                if(waitForThreads) {
                  preventor.warn(displayString + "; waiting " + threadWaitMs + 
                      " ms. " + preventor.getStackTrace(thread));
                  preventor.waitForThread(thread, threadWaitMs, false /* No interrupt */);
                }
                
                if(thread.isAlive() && preventor.isClassLoaderOrChild(thread.getContextClassLoader())) { // Still running in ClassLoader
                  preventor.warn(displayString + (waitForThreads ? " still" : "") + 
                      " alive; changing context ClassLoader to leak safe (" + 
                      preventor.getLeakSafeClassLoader() + "). " + preventor.getStackTrace(thread));
                  thread.setContextClassLoader(preventor.getLeakSafeClassLoader());
                }
              }
              else if(stopThreads) { // Thread/Runnable/ThreadGroup loaded by protected ClassLoader
                if(waitForThreads) {
                  preventor.warn("Waiting for " + displayString + " for " + threadWaitMs + " ms. " +
                      preventor.getStackTrace(thread));
  
                  preventor.waitForThread(thread, threadWaitMs, true /* Interrupt if needed */);
                }
  
                // Normally threads should not be stopped (method is deprecated), since it may cause an inconsistent state.
                // In this case however, the alternative is a classloader leak, which may or may not be considered worse.
                if(thread.isAlive()) {
                  preventor.warn("Stopping " + displayString + ". " + preventor.getStackTrace(thread));
                  //noinspection deprecation
                  thread.stop();
                }
                else {
                  preventor.info(displayString + " no longer alive - no action needed.");
                }
              }
              else {
                preventor.warn(displayString + " would cause leak. " + preventor.getStackTrace(thread));
              }
                
            }
          }
        }
      }
    }