-
-
Notifications
You must be signed in to change notification settings - Fork 60
Description
As background, I investigated a periodical OutOfMemoryError encountered in a server application I am working on. This used REST-API-Client built from the 'master' branch, for JDK 17 support. The main symptoms were:
Symptom 1:
Lots of messages like:
org.glassfish.jersey.client.innate.inject.NonInjectionManager.<init> Falling back to injection-less client.
Symptom 2:
Periodical crashes even when the server application saw no usage, apart from health checks that performed a request using org.igniterealtime.restclient.RestApiClient.
A heap dump showed a lot of org.glassfish.jersey.client.innate.inject.NonInjectionManagers having piled up:
This is from a server application that has not seen much use apart from a health check every few seconds for roughly half a day. Each health check created a new RestApiClient and performed a single request with it.
After analyzing these symptoms, I found various problems and solutions to many of them. This approach uses jersey-injectless-client. I also tried using jersey-hk2, but it suffered from piling up of various hk2-related classes, caused OutOfMemoryErrors in more active usage and was more difficult to analyze, so I abandoned that research.
Problem 1: MessageBodyWriter error messages
I also found messages like in the logs of the server application I am developing:
MessageBodyWriter not found for media type=application/xml, type=class org.igniterealtime.restclient.entity.UserEntity, genericType=class org.igniterealtime.restclient.entity.UserEntity.
This is fixed by adding jersey-media-jaxb as a dependency.
Problem 2: Warnings about NonInjectionManager
Every time a request is made with RestApiClient, the following message appears in the logs:
org.glassfish.jersey.client.innate.inject.NonInjectionManager.<init> Falling back to injection-less client.
These disappear after adding either jersey-hk2 or jersey-injectless-client as a dependency. I tried both and ended up with jersey-injectless-client due to the other problems being simpler to analyze with it.
Problem 3: New NonInjectionManager is created for each request
I noticed NonInjectionManagers piling up in the heap dump.
I set a debugger breakpoint at NonInjectionManager constructors, and performed requests with the RestApiClient. A new NonInjectionManager is created every time.
To solve this problem, I changed RestClient to store the created Jersey client and return it for future use:
- private Client createRestClient() throws KeyManagementException, NoSuchAlgorithmException {
+ private Client getOrCreateRestClient() throws KeyManagementException, NoSuchAlgorithmException {
+ if(this.client != null)
+ return this.client;
+
ClientConfig clientConfig = new ClientConfig();
// Set connection timeout
if (this.connectionTimeout != 0) {
@@ -296,6 +301,8 @@ public final class RestClient {
client = ClientBuilder.newClient(clientConfig);
}
+ this.client = client;
+
return client;
}
This solves NonInjectionManagers piling up. This requires also that the application using the RestApiClient does not create multiple RestApiClients.
This code is not as thread-safe as the previous code, but I am unsure if the RestApiClient is aiming for thread-safety.
Problem 4: Allocated resources are not closed
The clients allocated by createRestClient are never explicitly closed. Some of the calls that end up calling it return closeable resources, but not all of them do. I am also unsure if the returned closeable resources free the resources allocated by the client created by createRestClient or not.
A partial fix would be to implement AutoCloseable interface along with the above Problem 3 fix, with the close method calling this.client.close(). However, this did not seem to be sufficient to clean everything based on my tests. When I analyzed this approach, heap dumps still showed a mixture of NonInjectionManagers with 'shutdown' values of both false and true.
Problem 5: NonInjectionManager has a bug that has been patched in a newer Jersey version
Jersey version 3.1.8 should contain a patch to fix this issue: eclipse-ee4j/jersey#5710
This issue may have prevented the piled-up NonInjectionManagers from being garbage collected properly, which would make this the cause of the OutOfMemoryErrors I have observed. I have not been able to validate this, and the approach I eventually picked for the server application I am developing ensures that no excessive amounts of NonInjectionManagers are created in the first place.
Problem 6: NonInjectionManagers still keep piling up over time
Even with the fixes above, I did not manage to find a way to make NonInjectionManagers not keep piling up in the JVM heap. I am unsure if they would now get collected by the GC more properly with the problem 5 fix.
As a workaround, I ended up making the application I am working on create RestApiClients under a ThreadLocal, which limits the number of created instances to the application's thread pool size. This avoids potential concurrency issues.
Results
By patching org.igniterealtime.restclient.RestClient.createRestClient() and updating the dependencies as above, and by ensuring my own application does not create too many RestApiClients, I managed to solve the problem with heap getting filled with NonInjectionManagers. This seems sufficient for my own use case, but solving the resource buildup and cleanup problems even if lots of RestApiClients are created and discarded would be better.
