Skip to content

Commit 7acfdef

Browse files
committed
Memory leak when using Jersey with HK2 eclipse-ee4j#5796
Signed-off-by: Jorge Bescos Gascon <jorge.bescos.gascon@oracle.com>
1 parent 382f69e commit 7acfdef

File tree

9 files changed

+283
-102
lines changed

9 files changed

+283
-102
lines changed

core-client/src/main/java/org/glassfish/jersey/client/ClientConfig.java

+4-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2012, 2025 Oracle and/or its affiliates. All rights reserved.
33
* Copyright (c) 2018 Payara Foundation and/or its affiliates.
44
*
55
* This program and the accompanying materials are made available under the
@@ -55,6 +55,7 @@
5555
import org.glassfish.jersey.internal.util.collection.LazyValue;
5656
import org.glassfish.jersey.internal.util.collection.Value;
5757
import org.glassfish.jersey.internal.util.collection.Values;
58+
import org.glassfish.jersey.message.internal.MessageBodyFactory;
5859
import org.glassfish.jersey.model.internal.CommonConfig;
5960
import org.glassfish.jersey.model.internal.ComponentBag;
6061
import org.glassfish.jersey.model.internal.ManagedObjectsFinalizer;
@@ -416,17 +417,12 @@ private ClientRuntime initRuntime() {
416417

417418
final ClientBootstrapBag bootstrapBag = new ClientBootstrapBag();
418419
bootstrapBag.setManagedObjectsFinalizer(new ManagedObjectsFinalizer(injectionManager));
419-
420-
final ClientMessageBodyFactory.MessageBodyWorkersConfigurator messageBodyWorkersConfigurator =
421-
new ClientMessageBodyFactory.MessageBodyWorkersConfigurator();
422-
423-
List<BootstrapConfigurator> bootstrapConfigurators = Arrays.asList(
424-
new RequestScope.RequestScopeConfigurator(),
420+
List<BootstrapConfigurator> bootstrapConfigurators = Arrays.asList(new RequestScope.RequestScopeConfigurator(),
425421
new ParamConverterConfigurator(),
426422
new ParameterUpdaterConfigurator(),
427423
new RuntimeConfigConfigurator(runtimeCfgState),
428424
new ContextResolverFactory.ContextResolversConfigurator(),
429-
messageBodyWorkersConfigurator,
425+
new MessageBodyFactory.MessageBodyWorkersConfigurator(),
430426
new ExceptionMapperFactory.ExceptionMappersConfigurator(),
431427
new JaxrsProviders.ProvidersConfigurator(),
432428
new AutoDiscoverableConfigurator(RuntimeType.CLIENT),
@@ -467,8 +463,6 @@ private ClientRuntime initRuntime() {
467463
final ClientRuntime crt = new ClientRuntime(configuration, connector, injectionManager, bootstrapBag);
468464

469465
client.registerShutdownHook(crt);
470-
messageBodyWorkersConfigurator.setClientRuntime(crt);
471-
472466
return crt;
473467
}
474468

core-client/src/main/java/org/glassfish/jersey/client/ClientMessageBodyFactory.java

-87
This file was deleted.

core-client/src/main/java/org/glassfish/jersey/client/ClientRuntime.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2012, 2025 Oracle and/or its affiliates. All rights reserved.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -16,6 +16,7 @@
1616

1717
package org.glassfish.jersey.client;
1818

19+
import java.io.InputStream;
1920
import java.util.Collections;
2021
import java.util.concurrent.Callable;
2122
import java.util.concurrent.ExecutorService;
@@ -173,6 +174,8 @@ Runnable createRunnableForAsyncProcessing(ClientRequest request, final ResponseC
173174

174175
@Override
175176
public void response(final ClientResponse response) {
177+
InputStream in = response.getEntityStream();
178+
request.getClientConfig().getClient().putClientRuntimeLifeCycle(in, ClientRuntime.this);
176179
requestScope.runInScope(() -> processResponse(request, response, callback));
177180
}
178181

@@ -298,6 +301,8 @@ public ClientResponse invoke(final ClientRequest request) {
298301

299302
try {
300303
response = connector.apply(addUserAgent(Stages.process(request, requestProcessingRoot), connector.getName()));
304+
InputStream in = response.getEntityStream();
305+
request.getClientConfig().getClient().putClientRuntimeLifeCycle(in, this);
301306
} catch (final AbortException aborted) {
302307
response = aborted.getAbortResponse();
303308
}

core-client/src/main/java/org/glassfish/jersey/client/JerseyClient.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2011, 2023 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2011, 2025 Oracle and/or its affiliates. All rights reserved.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -16,11 +16,13 @@
1616

1717
package org.glassfish.jersey.client;
1818

19+
import java.io.InputStream;
1920
import java.lang.ref.Reference;
2021
import java.lang.ref.ReferenceQueue;
2122
import java.lang.ref.WeakReference;
2223
import java.net.URI;
2324
import java.util.Map;
25+
import java.util.WeakHashMap;
2426
import java.util.concurrent.ExecutorService;
2527
import java.util.concurrent.Executors;
2628
import java.util.concurrent.LinkedBlockingDeque;
@@ -69,6 +71,8 @@ public SSLContext getDefaultSslContext() {
6971
private final LinkedBlockingDeque<WeakReference<JerseyClient.ShutdownHook>> shutdownHooks =
7072
new LinkedBlockingDeque<WeakReference<JerseyClient.ShutdownHook>>();
7173
private final ReferenceQueue<JerseyClient.ShutdownHook> shReferenceQueue = new ReferenceQueue<JerseyClient.ShutdownHook>();
74+
// Keeps ClientRuntime alive till InputStream is GCed
75+
private final Map<InputStream, ClientRuntime> clientRuntimeLifeCycle = new WeakHashMap<>();
7276

7377
/**
7478
* Client instance shutdown hook.
@@ -387,4 +391,8 @@ public JerseyClient preInitialize() {
387391
config.preInitialize();
388392
return this;
389393
}
394+
395+
void putClientRuntimeLifeCycle(InputStream in, ClientRuntime cr) {
396+
clientRuntimeLifeCycle.put(in, cr);
397+
}
390398
}

tests/integration/jersey-4507/pom.xml

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<!--
33
4-
Copyright (c) 2020, 2024 Oracle and/or its affiliates. All rights reserved.
4+
Copyright (c) 2020, 2025 Oracle and/or its affiliates. All rights reserved.
55
66
This program and the accompanying materials are made available under the
77
terms of the Eclipse Public License v. 2.0, which is available at
@@ -46,4 +46,15 @@
4646
<scope>test</scope>
4747
</dependency>
4848
</dependencies>
49+
<build>
50+
<plugins>
51+
<plugin>
52+
<groupId>org.apache.maven.plugins</groupId>
53+
<artifactId>maven-surefire-plugin</artifactId>
54+
<configuration>
55+
<argLine>-XX:+UseG1GC</argLine>
56+
</configuration>
57+
</plugin>
58+
</plugins>
59+
</build>
4960
</project>

tests/integration/jersey-4507/src/test/java/org/glassfish/jersey/tests/integration/jersey4507/SSETest.java

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2020, 2025 Oracle and/or its affiliates. All rights reserved.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -19,6 +19,7 @@
1919
import org.glassfish.jersey.client.ClientConfig;
2020
import org.glassfish.jersey.client.ClientLifecycleListener;
2121
import org.glassfish.jersey.client.ClientProperties;
22+
import org.glassfish.jersey.client.JerseyClient;
2223
import org.glassfish.jersey.examples.sse.jersey.App;
2324
import org.glassfish.jersey.examples.sse.jersey.DomainResource;
2425
import org.glassfish.jersey.examples.sse.jersey.ServerSentEventsResource;
@@ -33,8 +34,13 @@
3334

3435
import javax.ws.rs.client.Entity;
3536
import javax.ws.rs.core.Application;
37+
38+
import java.io.ByteArrayInputStream;
39+
import java.io.InputStream;
40+
import java.lang.reflect.Field;
3641
import java.util.ArrayList;
3742
import java.util.List;
43+
import java.util.Map;
3844
import java.util.concurrent.Callable;
3945
import java.util.concurrent.CountDownLatch;
4046
import java.util.concurrent.ExecutorService;
@@ -44,6 +50,7 @@
4450
import java.util.concurrent.atomic.AtomicInteger;
4551

4652
import static org.hamcrest.CoreMatchers.equalTo;
53+
import static org.junit.jupiter.api.Assertions.assertEquals;
4754

4855
public class SSETest extends JerseyTest {
4956
private static final int MAX_CLIENTS = 10;
@@ -123,13 +130,29 @@ public void testInboundEventReaderMultiple() throws Exception {
123130
}
124131

125132
System.gc();
133+
triggerCleanupOfWeakHashMap();
126134
closeLatch.await(15_000, TimeUnit.MILLISECONDS);
127135
// One ClientConfig is on the Client
128136
// + COUNT of them is created by .register(SseFeature.class)
129137
Assertions.assertEquals(COUNT + 1, atomicInteger.get());
130138
Assertions.assertEquals(0, closeLatch.getCount());
131139
}
132140

141+
// WeakHashMap does not clean after GC. It cleans after some operations. This triggers it.
142+
private void triggerCleanupOfWeakHashMap() throws Exception {
143+
Field field = JerseyClient.class.getDeclaredField("clientRuntimeLifeCycle");
144+
field.setAccessible(true);
145+
Map<InputStream, Object> clientRuntimeLifeCycle = (Map<InputStream, Object>) field.get(client());
146+
assertEquals(0, clientRuntimeLifeCycle.size());
147+
clientRuntimeLifeCycle.clear();
148+
System.gc();
149+
Thread.sleep(100);
150+
// Required to invoke WeakHashMap#expungeStaleEntries
151+
ByteArrayInputStream in = new ByteArrayInputStream(new byte[0]);
152+
clientRuntimeLifeCycle.put(in, new Object());
153+
clientRuntimeLifeCycle.size();
154+
}
155+
133156

134157

135158
public static class ClientRuntimeCloseVerifier implements ClientLifecycleListener {

tests/integration/jersey-5796/pom.xml

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
4+
Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved.
5+
6+
This program and the accompanying materials are made available under the
7+
terms of the Eclipse Public License v. 2.0, which is available at
8+
http://www.eclipse.org/legal/epl-2.0.
9+
10+
This Source Code may also be made available under the following Secondary
11+
Licenses when the conditions for such availability set forth in the
12+
Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
13+
version 2 with the GNU Classpath Exception, which is available at
14+
https://www.gnu.org/software/classpath/license.html.
15+
16+
SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
17+
18+
-->
19+
20+
<project xmlns="http://maven.apache.org/POM/4.0.0"
21+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
22+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
23+
<parent>
24+
<artifactId>project</artifactId>
25+
<groupId>org.glassfish.jersey.tests.integration</groupId>
26+
<version>2.47-SNAPSHOT</version>
27+
</parent>
28+
<modelVersion>4.0.0</modelVersion>
29+
30+
<artifactId>jersey-5796</artifactId>
31+
<name>jersey-tests-integration-jersey-5796</name>
32+
33+
<dependencies>
34+
<dependency>
35+
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
36+
<artifactId>jersey-test-framework-provider-bundle</artifactId>
37+
<type>pom</type>
38+
<scope>test</scope>
39+
</dependency>
40+
</dependencies>
41+
<build>
42+
<plugins>
43+
<plugin>
44+
<groupId>org.apache.maven.plugins</groupId>
45+
<artifactId>maven-surefire-plugin</artifactId>
46+
<configuration>
47+
<argLine>-XX:+UseG1GC</argLine>
48+
</configuration>
49+
</plugin>
50+
</plugins>
51+
</build>
52+
</project>

0 commit comments

Comments
 (0)