Closed sywong70g closed 1 year ago
Tomcat's pretty good at identifying possible memory leaks and logging about them. Is there anything in Tomcat's logs when you undeploy the app? If there isn't (and perhaps even if there is) I think we'll need a sample that reproduces the problem, ideally one that doesn't require an Oracle DB.
I just tested with a helloWorld application without DB connection and actually there is no such issue. I am wondering whether it is about the connection pool. Also I am testing on MySQL with JPA and see if the same issue occurs. If yes, I will send you the sample.
It's very likely bug of jdbc driver, you can switch to another database and test.
Also you can try to extend SpringBootServletInitializer
and override deregisterJdbcDrivers
@Override
protected void deregisterJdbcDrivers(ServletContext servletContext) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
try {
String className = "com.mysql.cj.jdbc.AbandonedConnectionCleanupThread";
String methodName = "checkedShutdown";
if (ClassUtils.isPresent(className, cl)) {
ClassUtils.forName(className, cl).getMethod(methodName).invoke(null);
}
}
catch (Throwable ex) {
ex.printStackTrace();
}
try {
String className = "com.mysql.jdbc.AbandonedConnectionCleanupThread";
String methodName = "checkedShutdown";
if (ClassUtils.isPresent(className, cl)) {
ClassUtils.forName(className, cl).getMethod(methodName).invoke(null);
}
}
catch (Throwable ex) {
ex.printStackTrace();
}
super.deregisterJdbcDrivers(servletContext);
cancelTimers();
cleanupThreadLocals();
}
protected void cancelTimers() {
try {
for (Thread thread : Thread.getAllStackTraces().keySet()) {
if (thread.getClass().getSimpleName().equals("TimerThread")) {
cancelTimer(thread);
}
}
}
catch (Throwable ex) {
ex.printStackTrace();
}
}
private void cancelTimer(Thread thread) throws Exception {
Object queue = ReflectionUtils.getFieldValue(thread, "queue");
Method m = queue.getClass().getDeclaredMethod("isEmpty");
m.setAccessible(true);
if ((boolean) m.invoke(queue)) {
// Timer::cancel
synchronized (queue) {
ReflectionUtils.setFieldValue(thread, "newTasksMayBeScheduled", false);
m = queue.getClass().getDeclaredMethod("clear");
m.setAccessible(true);
m.invoke(queue);
queue.notify();
}
}
}
protected void cleanupThreadLocals() {
try {
for (Thread thread : Thread.getAllStackTraces().keySet()) {
cleanupThreadLocals(thread);
}
}
catch (Throwable ex) {
ex.printStackTrace();
}
}
private void cleanupThreadLocals(Thread thread) throws Exception {
if ("JettyShutdownThread".equals(thread.getName())) {
return; // see https://github.com/eclipse/jetty.project/issues/5782
}
for (String name : "threadLocals,inheritableThreadLocals".split(",")) {
Field f = Thread.class.getDeclaredField(name);
f.setAccessible(true);
f.set(thread, null);
}
}
@quaff you may be right, I use the same source code but connect to MySQL instead of Oracle, there is no such issue.
Thank you for your suggestion, we are testing your code and see if we can solve the issue.
Thanks for your help @quaff. I'll close this one for now, but if it turns out to be something in Spring Boot rather than the Oracle driver we can reopen it.
Environment:
a. OS : Ubuntu 22.04.3 b. JVM : Oracle Java 11.0.18 and Oracle java 17.0.17 (tested both) c. Tomcat : 9.0.75 d. SpringBoot : 2.7.5 e. Oracle : version 19c f. Oracle jdbc : com.oracle.database.jdbc:ojdbc11:23.2.0.0
Source code:
Policy.java:
PolicyRepository.java:
PolicyServiceImpl.java:
DemoController.java:
Steps to reproduce:
The VisualVM shows the chart:
At the plateau at the right hand side, the tomcat actually already hanged.