private void warnOnThreadsHavingClassloaderFromUninstalledBundles(Collection<Thread> threads) {
        record ThreadDetails(Thread thread, Bundle bundle) {}
        List<ThreadDetails> staleThreads = new ArrayList<>();
        for (Thread t : threads) {
            // Find threads with context classloader from an uninstalled bundle
            Bundle b = isClassloaderForUninstalledBundle(t.getContextClassLoader()).orElse(null);
            if (b != null) {
                staleThreads.add(new ThreadDetails(t, b));
                continue;
            }

            // Find threads which are sub-classes of java.lang.Thread from an uninstalled bundle
            b = hasClassloaderForUninstalledBundle(t).orElse(null);
            if (b != null) {
                staleThreads.add(new ThreadDetails(t, b));
                continue;
            }
            // Find threads which Runnable is a class from an uninstalled bundle
            // This may create false positives
            b = getTargetFieldOfThread(t).flatMap(ContainerWatchdog::hasClassloaderForUninstalledBundle).orElse(null);
            if (b != null) {
                staleThreads.add(new ThreadDetails(t, b));
            }
            // Note: no reliable mechanism for detecting threads owned by leaked executors (e.g. ScheduledThreadPoolExecutor)
        }
        if (!staleThreads.isEmpty()) {
            StringBuilder msg = new StringBuilder(
                    ("Found %d stale threads that should have been stopped during previous reconfiguration(s). " +
                            "These threads have a classloader for a bundle that has been uninstalled: \n")
                            .formatted(staleThreads.size()));
            MutableInteger i = new MutableInteger(1);
            Comparator<ThreadDetails> outputOrdering =
                    Comparator.<ThreadDetails, Long>comparing(td -> td.bundle().getBundleId())
                    .thenComparing(td -> td.thread().getName()).thenComparing(td -> td.thread().getId());
            staleThreads.stream().sorted(outputOrdering).forEach(t ->
                    msg.append("%d) Thread '%s' using bundle '%s'. \n"
                            .formatted(i.next(), t.thread().getName(), t.bundle().toString())));
            log.log(Level.INFO, msg::toString);  // Level 'info' until deemed reliable enough as 'warning'
        }
    }
Detect and warn on threads leaked by defunct application components.
These are threads launched by components of application bundles and not stopped after subsequent component deconstruction.
The below algorithm is a heuristic and will not detect all leaked threads.
jdisc_core/src/main/java/com/yahoo/jdisc/core/ContainerWatchdog.java