
How to unload dll from Java machine
Java interacts with the operating system through methods marked with the native keyword, using the system libraries loaded by the System.loadLibrary () procedure.
Downloading the system library is very simple, but to unload it, as it turned out, you need to make a lot of effort. How exactly the system libraries are unloaded, and why I need it, I will try to tell.
Suppose we want to make a small utility that users will run on their computers on the local network. We would like to save users from problems with installing and configuring the program, but there are no resources to deploy and maintain a centralized infrastructure. In such cases, they usually assemble the program along with all the dependencies into a single jar file. This is easy to do with the maven-assembly-plugin or just export from the IDE Runnable jar. The program will be launched by the command:
Unfortunately, this does not work if one of the libraries requires a system dynamic library for its work, in other words, dll. Usually, in one of the classes of such a library, a call to System.loadLibrary () is made in the static initializer. In order for the dll to load, you need to put it in a directory accessible through the JVM system property java.library.path. How can this circumvention be circumvented?
We pack the dll inside the jar file. Before you start using classes that require dll loading, create a temporary directory, extract the library there and add the directory to java.library.path. It will look something like this:
Unfortunately, you have to chemicalize with reflection, because Java does not provide standard methods to extend java.library.path .
Now loading the library is transparent for the user, and he should not worry about copying files or setting environment variables. To work, it’s still enough just to run a regular script. However, after each start of the program, a temporary directory with files remains. This is not very good, so the output must be cleaned.
But on Windows this does not work. The library loaded in the JVM locks the dll file and the directory in which it lies. Thus, in order to solve the problem of accurately terminating the program, you need to unload the system dynamic library from the JVM.
First of all, it’s wise to add diagnostics to the code. If the files were deleted, for example. when the library was not used, then nothing needs to be done, and if the files are locked, then take additional measures.
As a quick, but not the most beautiful solution, I used a scheduler. At the output, I create an xml-file with a task to execute the cmd / c rd / s / q temp-dir command after 1 minute and load the task into the scheduler with the command schtasks -create taskName -xml taskFile.xml. By the time the task is completed, the program has already been completed, and no one is holding the files.
The most correct solution is to ensure that the library is unloaded with the Java machine. The documentation says that the system library will be unloaded when the class is deleted, and the class will be deleted by the garbage collector along with the classloader when there is not a single instance left of its classes. In my opinion, it is better to always write such code that completely cleans all memory and other resources after itself. Because if the code does something useful, sooner or later you will want to reuse it and deploy it to some server where other components are installed. So I decided to take the time to figure out how to correctly unload the dll programmatically.
In my program, the problems came from the JDBC driver, so I will continue to look at the JDBC example. But you can work with other libraries in a similar way.
If the dll is loaded from the system class loader, then it will not be possible to unload it, so you need to create your classloader so that the class pulling up the library is loaded from it. The new classloader must be connected to the system classloader via the parent property, otherwise the String, Object and other things necessary in the economy will not be available in it.
Let's try:
Does not work. When loading a class, an attempt is first made to lift it from the parent loader, so our driver did not load as we needed. To use the new classloader, you need to remove the JDBC driver from the program jar file so that it is not accessible to the system loader. So, we pack the library in the form of an embedded jar-file, and before using it, we expand it in a temporary directory (in the same place where the dll lies with us).
We got the object loaded from our new bootloader, at the end of work we need to close everything that we opened, clean all our variables, and, apparently, call System.gc (), after which we already try to clean the files. At this point, it makes sense to encapsulate the entire logic of working with class loaders in a separate class with explicit initialization methods.
Despite the fact that everything seems to be formally necessary for unloading the library has been done, in fact, unloading does not occur. Reading the sources from the java.lang package allowed us to determine that the removal of native libraries is done in the finalize () method in one of the inner classes. This is distressing and alarming because the documentation does not provide any precise definition of when this method will be executed and whether it will be executed at all. That is, success depends on some factors that may vary in different environments, in different versions of the JVM, or in different garbage collectors. However, there is a System.runFinalization () method that gives some hope.
We try:
Does not work. The directory is locked by the java process. From now on I used this technique:
There were 5 sources of leakage:
After clearing all the discovered references to the paged classes, the situation is a bit paradoxical. Judging by the memory dump, there are no objects in the memory, the number of instances in all classes is 0. But the classes themselves and their loader have not disappeared, and accordingly the native library has not been deleted.
It turned out to fix the problem with this technique:
Probably, in Java 1.7, which I used, there was some peculiarity of cleaning objects that lie in PermGen. I did not experiment with garbage collection settings because I tried to write code that would work equally in different environments, including application servers.
After the specified reception, the code worked properly, the library was unloaded, directories were deleted. However, after switching to Java 8, the problem returned. There was no time to figure out what was the matter, but apparently, something in the behavior of the garbage collector changed.
Therefore, it was necessary to use heavy artillery, namely JMX:
Through HotSpotDiagnosticMXBean, we call the storage dump. We specify nul as the file name, which on Windows means the same as / dev / null on Unix. The second parameter indicates that only live objects should be dumped into the dump. It is this parameter that causes the JVM to complete the full garbage collection.
After this life hack, the problem of deleting the library from the temporary directory no longer arose. The final file cleanup code looks like this:
To check the quality of the code, I wrote my JDBC driver, which completely cleans up after itself. It works like a wrapper around any other driver loaded from a separate classpath.
I inserted this driver into the OSGI service on Apache Felix.
When starting the module through the Apache Felix system console running on Java 1.8.0_102, a temporary directory with a dll-file appears. File locked by java process. As soon as the module stops, the directory is deleted automatically. If instead of using UnloadableDriver you use DriverManager and a regular library from Embedded-Artifacts, then after updating the module, the error java.lang.UnsatisfiedLinkError: Native Library is already loaded in another classloader.
There is no universal way to unload the system dynamic library from the Java machine, but this problem is solved.
There are many places in Java where you can accidentally leave references to your classes, and this is a prerequisite for memory leaks.
Even if your code does everything correctly, the leak may be introduced by some library that you use.
Particular attention should be paid to cases when a program loads something using a new class loader that is created at runtime. If at least one link to one of the loaded classes remains, then the classloader and all its classes will remain in memory.
To detect a memory leak, you need to dump and analyze using special tools such as Eclipse MAT.
If a memory leak is detected in a third-party library, you can try to eliminate it using one of the recipes described in the article.
Downloading the system library is very simple, but to unload it, as it turned out, you need to make a lot of effort. How exactly the system libraries are unloaded, and why I need it, I will try to tell.
Suppose we want to make a small utility that users will run on their computers on the local network. We would like to save users from problems with installing and configuring the program, but there are no resources to deploy and maintain a centralized infrastructure. In such cases, they usually assemble the program along with all the dependencies into a single jar file. This is easy to do with the maven-assembly-plugin or just export from the IDE Runnable jar. The program will be launched by the command:
java -jar my-program.jar
Unfortunately, this does not work if one of the libraries requires a system dynamic library for its work, in other words, dll. Usually, in one of the classes of such a library, a call to System.loadLibrary () is made in the static initializer. In order for the dll to load, you need to put it in a directory accessible through the JVM system property java.library.path. How can this circumvention be circumvented?
We pack the dll inside the jar file. Before you start using classes that require dll loading, create a temporary directory, extract the library there and add the directory to java.library.path. It will look something like this:
prepareLibrary
private void addLibraryPath(String pathToAdd) throws ReflectiveOperationException {
Field usrPathsField = ClassLoader.class.getDeclaredField("usr_paths");
usrPathsField.setAccessible(true);
String[] paths = (String[]) usrPathsField.get(null);
String[] newPaths = Arrays.copyOf(paths, paths.length + 1);
newPaths[newPaths.length - 1] = pathToAdd;
usrPathsField.set(null, newPaths);
}
private Path prepareLibrary() throws IOException, ReflectiveOperationException {
Path dir = Files.createTempDirectory("lib");
try (InputStream input = ExampleClass.class.getResourceAsStream("custom.dll")) {
if (input == null) {
throw new FileNotFoundException("Can't load resource custom.dll");
}
Files.copy(input, dir.resolve("custom.dll"));
}
addLibraryPath(dir.toAbsolutePath().toString());
return dir;
}
Unfortunately, you have to chemicalize with reflection, because Java does not provide standard methods to extend java.library.path .
Now loading the library is transparent for the user, and he should not worry about copying files or setting environment variables. To work, it’s still enough just to run a regular script. However, after each start of the program, a temporary directory with files remains. This is not very good, so the output must be cleaned.
try {
...
} finally {
delete(dir);
}
But on Windows this does not work. The library loaded in the JVM locks the dll file and the directory in which it lies. Thus, in order to solve the problem of accurately terminating the program, you need to unload the system dynamic library from the JVM.
Attempt to solve
First of all, it’s wise to add diagnostics to the code. If the files were deleted, for example. when the library was not used, then nothing needs to be done, and if the files are locked, then take additional measures.
if (!delete(dir)) {
forceDelete(dir);
}
As a quick, but not the most beautiful solution, I used a scheduler. At the output, I create an xml-file with a task to execute the cmd / c rd / s / q temp-dir command after 1 minute and load the task into the scheduler with the command schtasks -create taskName -xml taskFile.xml. By the time the task is completed, the program has already been completed, and no one is holding the files.
The most correct solution is to ensure that the library is unloaded with the Java machine. The documentation says that the system library will be unloaded when the class is deleted, and the class will be deleted by the garbage collector along with the classloader when there is not a single instance left of its classes. In my opinion, it is better to always write such code that completely cleans all memory and other resources after itself. Because if the code does something useful, sooner or later you will want to reuse it and deploy it to some server where other components are installed. So I decided to take the time to figure out how to correctly unload the dll programmatically.
Using classloader
In my program, the problems came from the JDBC driver, so I will continue to look at the JDBC example. But you can work with other libraries in a similar way.
If the dll is loaded from the system class loader, then it will not be possible to unload it, so you need to create your classloader so that the class pulling up the library is loaded from it. The new classloader must be connected to the system classloader via the parent property, otherwise the String, Object and other things necessary in the economy will not be available in it.
Let's try:
Loading a class from a new bootloader (1)
ClassLoader parentCl = ExampleClass.class.getClassLoader();
classLoader = new URLClassLoader(new URL[0], parentCl);
Class.forName("org.jdbc.CustomDriver", classLoader, true);
try (Connection connection = DriverManager.getConnection(dbUrl, dbProperties)) {
if (connection.getClass().getClassLoader() != classLoader) {
System.out.printf("Что-то пошло не так%n");
}
...
}
Does not work. When loading a class, an attempt is first made to lift it from the parent loader, so our driver did not load as we needed. To use the new classloader, you need to remove the JDBC driver from the program jar file so that it is not accessible to the system loader. So, we pack the library in the form of an embedded jar-file, and before using it, we expand it in a temporary directory (in the same place where the dll lies with us).
Class loading from the new bootloader (2)
ClassLoader cl = ExampleClass.class.getClassLoader();
URL url = UnloadableDriver.class.getResource("CustomJDBCDriver.jar");
if (url == null) {
throw new FileNotFoundException("Can't load resource CustomJDBCDriver.jar");
}
Path dir = prepareLibrary();
try (InputStream stream = url.openStream()) {
Path target = dir.resolve("CustromJDBCDriver.jar");
Files.copy(stream, target);
url = target.toUri().toURL();
}
ClassLoader classLoader = new URLClassLoader(new URL[] {url}, cl);
Class.forName("org.jdbc.CustomDriver", true, classLoader);
try (Connection connection = DriverManager.getConnection(dbUrl, dbProperties)) {
if (connection.getClass().getClassLoader() != classLoader) {
System.out.printf("Что-то пошло не так%n");
} else {
System.out.printf("Получилось, можно идти дальше%n");
}
...
}
We got the object loaded from our new bootloader, at the end of work we need to close everything that we opened, clean all our variables, and, apparently, call System.gc (), after which we already try to clean the files. At this point, it makes sense to encapsulate the entire logic of working with class loaders in a separate class with explicit initialization methods.
Skeleton of the main class
public class ExampleClass implements AutoCloseable {
private final Path dir;
private URLClassLoader classLoader;
public ExampleClass() {
...
}
public void doWork() {
...
}
@Override
public void close() {
...
this.classLoader.close();
this.classloader = null;
System.gc(); // где-то здесь должна выгрузиться dll
if (!delete(this.dir)) {
scheduleRemovalToTaskschd(this.dir);
}
}
}
public class Main {
public static void main(String args[]) {
try (ExampleClass example = new ExampleClass()) {
example.doWork();
} catch (Throwable e) {
e.printStackTrace();
}
}
}
Garbage Collector Experiments
Despite the fact that everything seems to be formally necessary for unloading the library has been done, in fact, unloading does not occur. Reading the sources from the java.lang package allowed us to determine that the removal of native libraries is done in the finalize () method in one of the inner classes. This is distressing and alarming because the documentation does not provide any precise definition of when this method will be executed and whether it will be executed at all. That is, success depends on some factors that may vary in different environments, in different versions of the JVM, or in different garbage collectors. However, there is a System.runFinalization () method that gives some hope.
We try:
Run finalization ...
@Override
public void close() {
...
this.classLoader.close();
this.classloader = null;
System.gc();
System.runFinalization(); // где-то здесь должна выгрузиться dll
if (!delete(this.dir)) {
scheduleRemovalToTaskschd(this.dir);
}
}
Does not work. The directory is locked by the java process. From now on I used this technique:
- I put System.in.read () on the output
- When the program stops in this place, I make a memory dump from jvisualvm
- Watching a dump using the Eclipse Memory Analysis Tool or jhat
- I am looking for instances of objects whose classes were loaded by my loader
There were 5 sources of leakage:
- Local variables
- Drivermanager
- Resourcebundle
- Threadlocals
- Exceptions
Local variables
It turned out that the garbage collector does not consider the local variable unreachable until the function containing this variable is completed, even if the variable has gone out of scope.
Therefore, to solve the classloader unloading problem, it is necessary to exit all functions that use the unloaded classes before calling gc.
Local variables
It turned out that the garbage collector does not consider the local variable unreachable until the function containing this variable is completed, even if the variable has gone out of scope.
if (needConnection) {
try (Connection connection = DriverManager.connect()) {
...
}
}
// Вот здесь переменна connection еще считается живой.
Therefore, to solve the classloader unloading problem, it is necessary to exit all functions that use the unloaded classes before calling gc.
Drivermanager
When loading their class, JDBC drivers are registered in the DriverManager class by the registerDriver () method. Apparently, before unloading, you must call the deregisterDriver () method. We try.
Does not work. Heapdump has not changed. We look at the source code of the DriverManager class and find that the deregisterDriver () method checks that the call must be from a class that belongs to the same classloader as the class that called registerDriver () earlier. And registerDriver () is called by the driver itself from a static initializer. Unexpected turn.
It turns out that we cannot directly unregister the driver. Instead, we should ask some class from the new classloader to do it on his own behalf. The way out is to create a special DriverManagerProxy class, more precisely even two, a class and an interface.
The interface will be in the main classpath, and the implementation will be loaded by the new loader from the auxiliary jar-file together with the JDBC driver. Theoretically, an interface could be dispensed with, but then reflection would have to be used to call the function. The proxy is used as follows:
Drivermanager
When loading their class, JDBC drivers are registered in the DriverManager class by the registerDriver () method. Apparently, before unloading, you must call the deregisterDriver () method. We try.
Enumeration drivers = driverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
if (driver.getClass().getClassLoader() == classLoader) {
DriverManager.deregisterDriver(driver);
break;
}
}
Does not work. Heapdump has not changed. We look at the source code of the DriverManager class and find that the deregisterDriver () method checks that the call must be from a class that belongs to the same classloader as the class that called registerDriver () earlier. And registerDriver () is called by the driver itself from a static initializer. Unexpected turn.
It turns out that we cannot directly unregister the driver. Instead, we should ask some class from the new classloader to do it on his own behalf. The way out is to create a special DriverManagerProxy class, more precisely even two, a class and an interface.
public interface DriverManagerProxy {
void deregisterDriver(Driver driver) throws SQLException;
}
public class DriverManagerProxyImpl implements DriverManagerProxy {
@Override
public void deregisterDriver(Driver driver) throws SQLException {
DriverManager.deregisterDriver(driver);
}
}
The interface will be in the main classpath, and the implementation will be loaded by the new loader from the auxiliary jar-file together with the JDBC driver. Theoretically, an interface could be dispensed with, but then reflection would have to be used to call the function. The proxy is used as follows:
Using DriverManagerProxy
public class ExampleClass implements AutoCloseable {
private final Path dir;
private URLClassLoader classLoader;
private DriverManagerProxy driverManager;
public ExampleClass() {
...
this.classLoader = ...;
Class.forName("org.jdbc.CustomDriver", true, classLoader);
Class dmClass = Class.forName("ru.example.DriverManagerProxyImpl",
true,
classLoader);
this.driverManager = (DriverManagerProxy) dmClass.newInstance();
}
public void doWork() {
...
}
@Override
public void close() {
...
Enumeration drivers = driverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
if (driver.getClass().getClassLoader() == classLoader) {
driverManager.deregisterDriver(driver);
break;
}
}
this.driverManager = null;
this.classLoader.close();
this.classloader = null;
System.gc();
System.runFinalization(); // где-то здесь должна выгрузиться dll
if (!delete(this.dir)) {
scheduleRemovalToTaskschd(this.dir);
}
}
}
Resourcebundle
The next clasloader clue that I was trying to unload was found in the bowels of the ResourceBundle class. Fortunately, unlike DriverManager, ResourceBundle provides a special function clearCache (), to which the classloader is passed as a parameter.
It should be noted that, judging by the sources, the ResourceBundle uses weak links that should not interfere with garbage collection. Perhaps if you clear all other links to our objects, then there is no need to clear this cache.
Resourcebundle
The next clasloader clue that I was trying to unload was found in the bowels of the ResourceBundle class. Fortunately, unlike DriverManager, ResourceBundle provides a special function clearCache (), to which the classloader is passed as a parameter.
ResourceBundle.clearCache(classLoader);
It should be noted that, judging by the sources, the ResourceBundle uses weak links that should not interfere with garbage collection. Perhaps if you clear all other links to our objects, then there is no need to clear this cache.
Threadlocals
The last place where the tails of an unused driver showed up was ThreadLocals. After the story with the DriverManager, clearing local stream variables seems like a couple of trivial things. Although it was not possible to do without reflection.
Threadlocals
The last place where the tails of an unused driver showed up was ThreadLocals. After the story with the DriverManager, clearing local stream variables seems like a couple of trivial things. Although it was not possible to do without reflection.
private static void cleanupThreadLocals(ClassLoader cl)
throws ReflectiveOperationException {
int length = 1;
Thread threads[] = new Thread[length];
int cnt = Thread.enumerate(threads);
while (cnt >= length) {
length *= 2;
threads = new Thread[length];
cnt = Thread.enumerate(threads);
}
for (int i = 0; i < cnt; i++) {
Thread thread = threads[i];
if (thread == null) {
continue;
}
cleanupThreadLocals(thread, cl);
}
}
private static void cleanupThreadLocals(Thread thread, ClassLoader cl)
throws ReflectiveOperationException {
Field threadLocalsField = Thread.class.getDeclaredField("threadLocals");
threadLocalsField.setAccessible(true);
Object threadLocals = threadLocalsField.get(thread);
if (threadLocals == null) {
return;
}
Class threadLocalsClass = threadLocals.getClass();
Field tableField = threadLocalsClass.getDeclaredField("table");
tableField.setAccessible(true);
Object table = tableField.get(threadLocals);
Object entries[] = (Object[]) table;
Class entryClass = table.getClass().getComponentType();
Field valueField = entryClass.getDeclaredField("value");
valueField.setAccessible(true);
Method expungeStaleEntry = threadLocalsClass.getDeclaredMethod("expungeStaleEntry", Integer.TYPE);
expungeStaleEntry.setAccessible(true);
for (int i = 0; i < entries.length; i++) {
Object entry = entries[i];
if (entry == null) {
continue;
}
Object value = valueField.get(entry);
if (value != null) {
ClassLoader valueClassLoader = value.getClass().getClassLoader();
if (valueClassLoader == cl) {
((java.lang.ref.Reference) entry).clear();
expungeStaleEntry.invoke(threadLocals, i);
}
}
}
}
Exceptions
We hope that the cleanup code can be placed in the finally block. At the entrance to this block, we should already have everything closed automatically using the try-with-resources mechanism. However, our classloader will still not be deleted from this memory if an exception is thrown from the try block whose class is loaded by this classloader.
To remove an unwanted exception from memory, it must be caught and processed, and if you still need to throw the error up, copy exception to another class. Here's how I did it in my program:
Exceptions
We hope that the cleanup code can be placed in the finally block. At the entrance to this block, we should already have everything closed automatically using the try-with-resources mechanism. However, our classloader will still not be deleted from this memory if an exception is thrown from the try block whose class is loaded by this classloader.
To remove an unwanted exception from memory, it must be caught and processed, and if you still need to throw the error up, copy exception to another class. Here's how I did it in my program:
try {
...
} catch (RuntimeException e) {
if (e.getClass().getClassLoader() == this.getClass().getClassLoader()) {
throw e;
}
RuntimeException exception = new RuntimeException(String.format("%s: %s", e.getClass(), e.getMessage()));
exception.setStackTrace(e.getStackTrace());
throw exception;
} catch (SQLException e) {
if (e.getClass().getClassLoader() == this.getClass().getClassLoader()) {
throw e;
}
SQLException exception = new SQLException(String.format("%s: %s", e.getClass(), e.getMessage()));
exception.setStackTrace(e.getStackTrace());
throw exception;
}
Java strikes back
After clearing all the discovered references to the paged classes, the situation is a bit paradoxical. Judging by the memory dump, there are no objects in the memory, the number of instances in all classes is 0. But the classes themselves and their loader have not disappeared, and accordingly the native library has not been deleted.
It turned out to fix the problem with this technique:
System.gc();
System.runFinalization();
System.gc();
System.runFinalization();
Probably, in Java 1.7, which I used, there was some peculiarity of cleaning objects that lie in PermGen. I did not experiment with garbage collection settings because I tried to write code that would work equally in different environments, including application servers.
After the specified reception, the code worked properly, the library was unloaded, directories were deleted. However, after switching to Java 8, the problem returned. There was no time to figure out what was the matter, but apparently, something in the behavior of the garbage collector changed.
Therefore, it was necessary to use heavy artillery, namely JMX:
How to get Java to collect garbage
private static void dumpHeap() {
try {
Class clazz = Class.forName("com.sun.management.HotSpotDiagnosticMXBean");
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
Object hotspotMBean =
ManagementFactory.newPlatformMXBeanProxy(
server, "com.sun.management:type=HotSpotDiagnostic", clazz);
Method m = clazz.getMethod("dumpHeap", String.class, boolean.class);
m.invoke(hotspotMBean, "nul", true);
} catch (@SuppressWarnings("unused") RuntimeException e) {
return;
} catch (@SuppressWarnings("unused") ReflectiveOperationException e) {
return;
} catch (@SuppressWarnings("unused") IOException e) {
return;
}
}
Through HotSpotDiagnosticMXBean, we call the storage dump. We specify nul as the file name, which on Windows means the same as / dev / null on Unix. The second parameter indicates that only live objects should be dumped into the dump. It is this parameter that causes the JVM to complete the full garbage collection.
After this life hack, the problem of deleting the library from the temporary directory no longer arose. The final file cleanup code looks like this:
this.classLoader = null;
System.gc();
System.runFinalization();
System.gc();
System.runFinalization();
if (!delete(this.dir)) {
dumpHeap();
if (!delete(this.dir)) {
scheduleRemovalToTaskschd(this.dir);
}
}
Verification with OSGI
To check the quality of the code, I wrote my JDBC driver, which completely cleans up after itself. It works like a wrapper around any other driver loaded from a separate classpath.
UnloadableDriver
public class UnloadableDriver implements Driver, AutoCloseable {
private final Path dir; // временный каталог, подлежащий удалению
private URLClassLoader classLoader;
private DriverManagerProxy driverManager;
private Driver driver;
public UnloadableDriver() throws SQLException {
...
}
@Override
public void close() {
...
}
...
}
I inserted this driver into the OSGI service on Apache Felix.
JDBCService
public interface JDBCService {
Connection getConnection(String url, Properties properties) throws SQLException;
}
@Service(JDBCService.class)
public class JDBCServiceImpl implements JDBCService {
private UnloadableDriver driver;
@Activate
public void activate(ComponentContext ctx) throws SQLException {
this.driver = new UnloadableDriver();
}
@Deactivate
public void deactivate() {
this.driver.close();
this.driver = null;
}
@Override
public Connection getConnection(String url, Properties info) throws SQLException {
return this.driver.connect(url, properties);
}
}
When starting the module through the Apache Felix system console running on Java 1.8.0_102, a temporary directory with a dll-file appears. File locked by java process. As soon as the module stops, the directory is deleted automatically. If instead of using UnloadableDriver you use DriverManager and a regular library from Embedded-Artifacts, then after updating the module, the error java.lang.UnsatisfiedLinkError: Native Library is already loaded in another classloader.
conclusions
There is no universal way to unload the system dynamic library from the Java machine, but this problem is solved.
There are many places in Java where you can accidentally leave references to your classes, and this is a prerequisite for memory leaks.
Even if your code does everything correctly, the leak may be introduced by some library that you use.
Particular attention should be paid to cases when a program loads something using a new class loader that is created at runtime. If at least one link to one of the loaded classes remains, then the classloader and all its classes will remain in memory.
To detect a memory leak, you need to dump and analyze using special tools such as Eclipse MAT.
If a memory leak is detected in a third-party library, you can try to eliminate it using one of the recipes described in the article.