Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DriverManagerCleanUp can't really work for many scenarios #80

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 62 additions & 62 deletions classloader-leak-prevention/classloader-leak-prevention-core/README.md
Original file line number Diff line number Diff line change
@@ -1,63 +1,63 @@
# Classloader Leak Prevention library integration

_This document is about using the Classloader Leak Prevention library in
a non-servlet environment. For general information and use in servlet
environments, please see the [root README.md](../../README.md)_

Version 2.x of the Classloader Leak Prevention library has been refactored
to allow for use outside a servlet environment, or by all means in a
servlet container (Java EE application server).

# Setting up
What you will want to do is first create a [ClassLoaderLeakPreventorFactory](src/main/java/se/jiderhamn/classloader/leak/prevention/ClassLoaderLeakPreventorFactory.java)
instance, either by using the default constructor that will configure the system
`ClassLoader` (`ClassLoader.getSystemClassLoader()`) to be used for pre-inits,
or provide your own leak safe `ClassLoader` to the constructor.

Make any configurations on the factory instance, i.e. add or remove any
[cleanup](src/main/java/se/jiderhamn/classloader/leak/prevention/ClassLoaderPreMortemCleanUp.java)
or [pre-init](https://github.com/mjiderhamn/classloader-leak-prevention/blob/master/classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/PreClassLoaderInitiator.java)
plugins, or change parameters of any of the default plugins.

# Protect ClassLoader
Then for every `ClassLoader` that needs leak protection, create a new
[ClassLoaderLeakPreventor](src/main/java/se/jiderhamn/classloader/leak/prevention/ClassLoaderLeakPreventor.java)
using
```java
classLoaderLeakPreventor = classLoaderLeakPreventorFactory.newLeakPreventor(classLoader);
```

Before letting any code execute inside the `ClassLoader` (or at least as
soon as possible), invoke
```java
classLoaderLeakPreventor.runPreClassLoaderInitiators();
```

You can reuse the same `ClassLoaderLeakPreventorFactory` for multiple
`ClassLoaders`, but please be aware that any configuration changes made
to plugins of the factory will affect all `ClassLoaderLeakPreventor`s
created by the factory - both future and existing. If however you add
or remove plugins, that will only affect new `ClassLoaderLeakPreventor`s.

# Shutting down
When you believe the `ClassLoader` should no longer be used, but be ready
for Garbage Collection, invoke
```java
classLoaderLeakPreventor.runCleanUps();
```
on the `ClassLoaderLeakPreventor` that corresponds to the `ClassLoader`.

# Example
For an example how to use the framework, feel free to study the
[ClassLoaderLeakPreventorListener](../classloader-leak-prevention-servlet/src/main/java/se/jiderhamn/classloader/leak/prevention/ClassLoaderLeakPreventorListener.java)
in the `classloader-leak-prevention-servlet` module.

# Maven
The module is available in Maven as
```xml
<dependency>
<groupId>se.jiderhamn.classloader-leak-prevention</groupId>
<artifactId>classloader-leak-prevention-core</artifactId>
<version>2.6.1</version>
</dependency>
# Classloader Leak Prevention library integration
_This document is about using the Classloader Leak Prevention library in
a non-servlet environment. For general information and use in servlet
environments, please see the [root README.md](../../README.md)_
Version 2.x of the Classloader Leak Prevention library has been refactored
to allow for use outside a servlet environment, or by all means in a
servlet container (Java EE application server).
# Setting up
What you will want to do is first create a [ClassLoaderLeakPreventorFactory](src/main/java/se/jiderhamn/classloader/leak/prevention/ClassLoaderLeakPreventorFactory.java)
instance, either by using the default constructor that will configure the system
`ClassLoader` (`ClassLoader.getSystemClassLoader()`) to be used for pre-inits,
or provide your own leak safe `ClassLoader` to the constructor.
Make any configurations on the factory instance, i.e. add or remove any
[cleanup](src/main/java/se/jiderhamn/classloader/leak/prevention/ClassLoaderPreMortemCleanUp.java)
or [pre-init](https://github.com/mjiderhamn/classloader-leak-prevention/blob/master/classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/PreClassLoaderInitiator.java)
plugins, or change parameters of any of the default plugins.
# Protect ClassLoader
Then for every `ClassLoader` that needs leak protection, create a new
[ClassLoaderLeakPreventor](src/main/java/se/jiderhamn/classloader/leak/prevention/ClassLoaderLeakPreventor.java)
using
```java
classLoaderLeakPreventor = classLoaderLeakPreventorFactory.newLeakPreventor(classLoader);
```
Before letting any code execute inside the `ClassLoader` (or at least as
soon as possible), invoke
```java
classLoaderLeakPreventor.runPreClassLoaderInitiators();
```
You can reuse the same `ClassLoaderLeakPreventorFactory` for multiple
`ClassLoaders`, but please be aware that any configuration changes made
to plugins of the factory will affect all `ClassLoaderLeakPreventor`s
created by the factory - both future and existing. If however you add
or remove plugins, that will only affect new `ClassLoaderLeakPreventor`s.
# Shutting down
When you believe the `ClassLoader` should no longer be used, but be ready
for Garbage Collection, invoke
```java
classLoaderLeakPreventor.runCleanUps();
```
on the `ClassLoaderLeakPreventor` that corresponds to the `ClassLoader`.
# Example
For an example how to use the framework, feel free to study the
[ClassLoaderLeakPreventorListener](../classloader-leak-prevention-servlet/src/main/java/se/jiderhamn/classloader/leak/prevention/ClassLoaderLeakPreventorListener.java)
in the `classloader-leak-prevention-servlet` module.
# Maven
The module is available in Maven as
```xml
<dependency>
<groupId>se.jiderhamn.classloader-leak-prevention</groupId>
<artifactId>classloader-leak-prevention-core</artifactId>
<version>2.6.1</version>
</dependency>
```
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package se.jiderhamn.classloader.leak.prevention.cleanup;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;

Expand All @@ -12,27 +14,96 @@

/**
* Deregister JDBC drivers loaded by classloader
*
* @author Mattias Jiderhamn
* @author SONGQQ 2018-4-19
*/
public class DriverManagerCleanUp implements ClassLoaderPreMortemCleanUp {
@Override
public void cleanUp(ClassLoaderLeakPreventor preventor) {
final List<Driver> driversToDeregister = new ArrayList<Driver>();
final Enumeration<Driver> allDrivers = DriverManager.getDrivers();
while(allDrivers.hasMoreElements()) {
final Driver driver = allDrivers.nextElement();
if(preventor.isLoadedInClassLoader(driver)) // Should be true for all returned by DriverManager.getDrivers()
driversToDeregister.add(driver);
}

for(Driver driver : driversToDeregister) {
try {
preventor.warn("JDBC driver loaded by protected ClassLoader deregistered: " + driver.getClass());
DriverManager.deregisterDriver(driver);
}
catch (SQLException e) {
preventor.error(e);
}
}
final Enumeration<Driver> allDrivers = getAllDrivers(preventor);
while (allDrivers.hasMoreElements()) {
final Driver driver = allDrivers.nextElement();
if (preventor.isLoadedInClassLoader(driver)) {
try {
preventor.warn("JDBC driver loaded by protected ClassLoader deregistered: " + driver.getClass());
deregisterDriver(preventor, driver);
} catch (Exception e) {
preventor.error(e);
}
}
}
}
}

/**
* DriverManager.getDrivers() only returns the drivers that are loaded by
* the classloader of the caller. For many scenarios the callers classloader is not the
* same classloader which load the jdbc drivers.
* @return All drivers in DriverManager's registeredDrivers field,or
* DriverManager.getDrivers() if exception occurred
*/
public Enumeration<Driver> getAllDrivers(ClassLoaderLeakPreventor preventor) {
List<Driver> result = new ArrayList<Driver>();
try {
List<?> driverinfos = preventor.getStaticFieldValue(DriverManager.class, "registeredDrivers");
for (Object driverinfo : driverinfos) {
Driver driver = (Driver) preventor.getFieldValue(driverinfo, "driver");
if (driver == null)
throw new NullPointerException("Driver missing in DriverInfo!");
else
result.add(driver);
}
} catch (Exception e) {
preventor.error("Error getting registered drivers from DriverManager");
return DriverManager.getDrivers();
}
return Collections.enumeration(result);
}

/**
* Do the work as DriverManager.deregisterDriver,but this method don't check the
* Security of the caller's Classloader.If Exception occur, invoke DriverManager.deregisterDriver(driver)
*/
private void deregisterDriver(ClassLoaderLeakPreventor preventor, final Driver driver) throws Exception {
synchronized (DriverManager.class) {
if (driver == null) {
return;
}
try {
List<?> registeredDrivers = preventor.getStaticFieldValue(DriverManager.class, "registeredDrivers");
Class<?> innerClass = Class.forName("java.sql.DriverInfo");
Method actionMethod = null;
Object innerInstance = null;
// DriverInfo is Changed in JDK8. So the Code must be backward compatible
try {
actionMethod = innerClass.getDeclaredMethod("action", new Class[0]);
} catch (NoSuchMethodException e) {
// No DriverInfo.action() means we're running JDK prior to 8
}
Constructor<?> ctor = null;
if (actionMethod != null) {
ctor = innerClass.getDeclaredConstructor(Driver.class, actionMethod.getReturnType());
ctor.setAccessible(true);
innerInstance = ctor.newInstance(driver, null);
// Find DriverInfo with DriverAction in list by leveraging DriverInfo.equals()
Object driverInfo = registeredDrivers.get(registeredDrivers.indexOf(innerInstance));
actionMethod.setAccessible(true);
Object driverAction = actionMethod.invoke(driverInfo);
if (driverAction != null) {
Method deregisterMethod = driverAction.getClass().getDeclaredMethod("deregister", new Class[0]);
if (deregisterMethod != null)
deregisterMethod.invoke(driverAction);
}
} else {
ctor = innerClass.getDeclaredConstructor(Driver.class);
ctor.setAccessible(true);
innerInstance = ctor.newInstance(driver);
}
registeredDrivers.remove(innerInstance);
} catch (Exception e) {
preventor.error("reflection to deregisterDriver error,invoke the orgin DriverManager.deregisterDriver()");
DriverManager.deregisterDriver(driver);
}
}
}
}