0

We are running a multi classloaders java application using Hibernate:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.4.16.Final</version>
</dependency>
<dependency>
    <groupId>com.oracle.database.jdbc</groupId>
    <artifactId>ojdbc10-production</artifactId>
    <version>19.7.0.0</version>
    <type>pom</type>
</dependency>

We noticed that even if close all off Hibernate and artifacts and unregister the driver we still see threads left alive that hold the classloader in the contextclassloader. Is there a way to shutdown those threads Timer & OracleTimeoutPollingThread?

 public void close() throws IOException {
    sessionFactory.close();
    session.close();
    factory.close();

    try {
      Enumeration<Driver> de = DriverManager.getDrivers();
      while(de.hasMoreElements()) {
        Driver d = de.nextElement();
        if(d.getClass().getClassLoader() == RGHibernate.class.getClassLoader()) {
          DriverManager.deregisterDriver(d);
        }

      }
    } catch (SQLException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}

Heap dump analysis

Guy Korland
  • 9,139
  • 14
  • 59
  • 106

1 Answers1

0
    protected void cancelTimers() {
        try {
            for (Thread thread : Thread.getAllStackTraces().keySet())
                if (thread.getClass().getSimpleName().equals("TimerThread"))
                    cancelTimer(thread);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    private void cancelTimer(Thread thread) throws Exception {
        // Timer::cancel
        Object queue = ReflectionUtils.getFieldValue(thread, "queue");
        Method m = queue.getClass().getDeclaredMethod("isEmpty");
        m.setAccessible(true);
        if ((boolean) m.invoke(queue)) {
            synchronized (queue) {
                ReflectionUtils.setFieldValue(thread, "newTasksMayBeScheduled", false);
                m = queue.getClass().getDeclaredMethod("clear");
                m.setAccessible(true);
                m.invoke(queue);
                queue.notify();
            }
        }
    }
quaff
  • 51
  • 7
  • Does it also relate to the OralceTImeoutPollingThread? – Guy Korland Jul 01 '20 at 06:59
  • `OracleTimeoutPollingThread` will be stopped when `OracleDriver` deregiestered by `DriverManager`, or you can call `OracleTimeoutThreadPerVM.stopWatchdog()` directly. – quaff Jul 02 '20 at 08:21
  • As you can see in the question we do call DriverManager.deregisterDriver(d); and OracleTimeoutPollingThread indeed stops but Timer is still alive and holds OracleTimeoutPollingThread reference – Guy Korland Jul 02 '20 at 09:40
  • It‘s a bug of oracle jdbc driver, It should call `timer.cancel()` in `HAManager.stop()` – quaff Jul 04 '20 at 08:19
  • ``` className = "oracle.jdbc.driver.NoSupportHAManager"; Object noSupportHAManager = Class.forName(className).getMethod("getInstance").invoke(null); if (noSupportHAManager != null) { Field f = Class.forName("oracle.jdbc.driver.HAManager").getDeclaredField("timer"); f.setAccessible(true); timer = (Timer) f.get(noSupportHAManager); if (timer != null) timer.cancel(); } ``` – quaff Jul 06 '20 at 00:36
  • thanks, but how is it better than the solution above? – Guy Korland Jul 06 '20 at 05:42
  • The above solution will cancel all timers not only oracle's, the under solution need extra dependency `com.oracle.ojdbc:simplefan`, otherwise `NoClassDefFoundError` will be thrown. – quaff Jul 07 '20 at 08:06