Skip to content

Commit 7bee3d1

Browse files
committed
Optimize Jackson resource management in codecs
Prior to this commit, references to `JsonGenerator` and `ByteArrayBuilder` were not closed/released within codecs calls. This prevents Jackson from reusing more efficiently shared memory resources. This commit properly closes/releases Jackson resources in Spring MVC, Spring WebFlux and Spring Messaging codecs. A benchmark on WebFlux codecs (in both single value/streaming mode) shows significant throughput and allocation improvements for small payloads. Closes gh-25910
1 parent db3d537 commit 7bee3d1

File tree

5 files changed

+67
-52
lines changed

5 files changed

+67
-52
lines changed

spring-messaging/src/main/java/org/springframework/messaging/converter/MappingJackson2MessageConverter.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -263,14 +263,15 @@ protected Object convertToInternal(Object payload, @Nullable MessageHeaders head
263263
if (byte[].class == getSerializedPayloadClass()) {
264264
ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
265265
JsonEncoding encoding = getJsonEncoding(getMimeType(headers));
266-
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(out, encoding);
267-
if (view != null) {
268-
this.objectMapper.writerWithView(view).writeValue(generator, payload);
269-
}
270-
else {
271-
this.objectMapper.writeValue(generator, payload);
266+
try (JsonGenerator generator = this.objectMapper.getFactory().createGenerator(out, encoding)) {
267+
if (view != null) {
268+
this.objectMapper.writerWithView(view).writeValue(generator, payload);
269+
}
270+
else {
271+
this.objectMapper.writeValue(generator, payload);
272+
}
273+
payload = out.toByteArray();
272274
}
273-
payload = out.toByteArray();
274275
}
275276
else {
276277
// Assuming a text-based target payload

spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Encoder.java

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,16 @@ public Flux<DataBuffer> encode(Publisher<?> inputStream, DataBufferFactory buffe
149149

150150
return Flux.from(inputStream)
151151
.map(value -> encodeStreamingValue(value, bufferFactory, hints, sequenceWriter, byteBuilder,
152-
separator));
152+
separator))
153+
.doAfterTerminate(() -> {
154+
try {
155+
byteBuilder.release();
156+
generator.close();
157+
}
158+
catch (IOException ex) {
159+
logger.error("Could not close Encoder resources", ex);
160+
}
161+
});
153162
}
154163
catch (IOException ex) {
155164
return Flux.error(ex);
@@ -172,30 +181,34 @@ public DataBuffer encodeValue(Object value, DataBufferFactory bufferFactory,
172181

173182
ObjectWriter writer = createObjectWriter(valueType, mimeType, hints);
174183
ByteArrayBuilder byteBuilder = new ByteArrayBuilder(writer.getFactory()._getBufferRecycler());
175-
JsonEncoding encoding = getJsonEncoding(mimeType);
184+
try {
185+
JsonEncoding encoding = getJsonEncoding(mimeType);
176186

177-
logValue(hints, value);
187+
logValue(hints, value);
178188

179-
try {
180-
JsonGenerator generator = getObjectMapper().getFactory().createGenerator(byteBuilder, encoding);
181-
writer.writeValue(generator, value);
182-
generator.flush();
183-
}
184-
catch (InvalidDefinitionException ex) {
185-
throw new CodecException("Type definition error: " + ex.getType(), ex);
186-
}
187-
catch (JsonProcessingException ex) {
188-
throw new EncodingException("JSON encoding error: " + ex.getOriginalMessage(), ex);
189-
}
190-
catch (IOException ex) {
191-
throw new IllegalStateException("Unexpected I/O error while writing to byte array builder", ex);
192-
}
189+
try (JsonGenerator generator = getObjectMapper().getFactory().createGenerator(byteBuilder, encoding)) {
190+
writer.writeValue(generator, value);
191+
generator.flush();
192+
}
193+
catch (InvalidDefinitionException ex) {
194+
throw new CodecException("Type definition error: " + ex.getType(), ex);
195+
}
196+
catch (JsonProcessingException ex) {
197+
throw new EncodingException("JSON encoding error: " + ex.getOriginalMessage(), ex);
198+
}
199+
catch (IOException ex) {
200+
throw new IllegalStateException("Unexpected I/O error while writing to byte array builder", ex);
201+
}
193202

194-
byte[] bytes = byteBuilder.toByteArray();
195-
DataBuffer buffer = bufferFactory.allocateBuffer(bytes.length);
196-
buffer.write(bytes);
203+
byte[] bytes = byteBuilder.toByteArray();
204+
DataBuffer buffer = bufferFactory.allocateBuffer(bytes.length);
205+
buffer.write(bytes);
197206

198-
return buffer;
207+
return buffer;
208+
}
209+
finally {
210+
byteBuilder.release();
211+
}
199212
}
200213

201214
private DataBuffer encodeStreamingValue(Object value, DataBufferFactory bufferFactory, @Nullable Map<String, Object> hints,

spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,8 +307,8 @@ protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessa
307307

308308
MediaType contentType = outputMessage.getHeaders().getContentType();
309309
JsonEncoding encoding = getJsonEncoding(contentType);
310-
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);
311-
try {
310+
311+
try (JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding)) {
312312
writePrefix(generator, object);
313313

314314
Object value = object;

spring-web/src/test/resources/log4j2-test.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
</Console>
77
</Appenders>
88
<Loggers>
9-
<Logger name="org.springframework.web" level="debug" />
9+
<Logger name="org.springframework.web" level="warn" />
1010
<Logger name="org.springframework.beans" level="warn" />
1111
<Logger name="org.springframework.binding" level="warn" />
1212
<Logger name="org.springframework.http" level="warn" />

spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/AbstractJackson2View.java

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -206,29 +206,30 @@ protected Object filterAndWrapModel(Map<String, Object> model, HttpServletReques
206206
* @throws IOException if writing failed
207207
*/
208208
protected void writeContent(OutputStream stream, Object object) throws IOException {
209-
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(stream, this.encoding);
210-
writePrefix(generator, object);
211-
212-
Object value = object;
213-
Class<?> serializationView = null;
214-
FilterProvider filters = null;
215-
216-
if (value instanceof MappingJacksonValue) {
217-
MappingJacksonValue container = (MappingJacksonValue) value;
218-
value = container.getValue();
219-
serializationView = container.getSerializationView();
220-
filters = container.getFilters();
221-
}
209+
try (JsonGenerator generator = this.objectMapper.getFactory().createGenerator(stream, this.encoding)) {
210+
writePrefix(generator, object);
211+
212+
Object value = object;
213+
Class<?> serializationView = null;
214+
FilterProvider filters = null;
215+
216+
if (value instanceof MappingJacksonValue) {
217+
MappingJacksonValue container = (MappingJacksonValue) value;
218+
value = container.getValue();
219+
serializationView = container.getSerializationView();
220+
filters = container.getFilters();
221+
}
222222

223-
ObjectWriter objectWriter = (serializationView != null ?
224-
this.objectMapper.writerWithView(serializationView) : this.objectMapper.writer());
225-
if (filters != null) {
226-
objectWriter = objectWriter.with(filters);
227-
}
228-
objectWriter.writeValue(generator, value);
223+
ObjectWriter objectWriter = (serializationView != null ?
224+
this.objectMapper.writerWithView(serializationView) : this.objectMapper.writer());
225+
if (filters != null) {
226+
objectWriter = objectWriter.with(filters);
227+
}
228+
objectWriter.writeValue(generator, value);
229229

230-
writeSuffix(generator, object);
231-
generator.flush();
230+
writeSuffix(generator, object);
231+
generator.flush();
232+
}
232233
}
233234

234235

0 commit comments

Comments
 (0)