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

Possible memory leak when using CompressionCodecs.DEFLATE #392

Closed
zumoris opened this issue Aug 16, 2018 · 2 comments
Closed

Possible memory leak when using CompressionCodecs.DEFLATE #392

zumoris opened this issue Aug 16, 2018 · 2 comments
Labels
Milestone

Comments

@zumoris
Copy link

zumoris commented Aug 16, 2018

Prehistory

We are using jjwt 0.9.0 inside a Spring Boot application in a production environment, which is running in a Docker container. Here is some information about our environment:

  • openjdk version "1.8.0_171"
  • OpenJDK Runtime Environment (IcedTea 3.8.0) (Alpine 8.171.11-r0)
  • OpenJDK 64-Bit Server VM (build 25.171-b11, mixed mode)

Every second we create approximately 10-30 JWTs. Our web application crashes approximately every 24 hours because of OOM and Docker restarts it.

Problem description

After investigating a heap dump with Eclipse Memory Analyzer I found out that 28.71% of all memory is taken by 159,914 instances of java.lang.ref.Finalizer. After grouping all instances in heap dump by class and sorting by retained heap in descending order, I noticed that almost all instances of java.lang.ref.Finalizer refer to java.util.zip.Deflater and java.util.zip.Inflater objects.
screen shot 2018-08-16 at 19 46 33

Evidence

We don't use Deflater in our project, but we do use compressWith(CompressionCodecs.DEFLATE), so I decided to disable JWT compression and see what will happen. After redeploying a fixed application version, memory consumption became pretty low and stable: it doesn't increase and even change during the application work, at least during several hours.

Cause Investigation

I decided to investigate the JwtBuilder class and especially it's compact() method code. After some investigation, I've reached DeflateCompressionCodec and found that inside the byte[] doCompress(byte[] payload) method deflater's end method was not called. Moreover, DeflaterOutputStream which is used to wrap deflater also doesn't call deflater's end method. Let me explain why.

As far as I can see, DeflaterOutputStream close method closes the deflater if usesDefaultDeflater flag is true. But this flag is false by default and it becomes true only if you create a DeflaterOutputStream object using one of these constructors:

  1. DeflaterOutputStream(OutputStream out)
  2. DeflaterOutputStream(OutputStream out, boolean syncFlush)

In our case DeflaterOutputStream(OutputStream out, Deflater def, boolean syncFlush) constructor is being used, so usesDefaultDeflater remains false and the deflater remains open, which seems to lead to a memory leak. At least Oracle advices to call end method in the Deflater documentation.

Resolution

I suppose that you should either call deflater's end method inside the doCompress method or use
DeflaterOutputStream with a default deflater.

Thank you for your attention.
P.S. Seems like Inflater has the same problem.

@lhazlewood
Copy link
Contributor

Thank you for this - fantastic analysis! This should be an easy fix.

FWIW, the DeflateCompressionCodec class's doDecompress implementation does not appear to have the same problem - the default constructor is used. Here's the code:

https://github.com/jwtk/jjwt/blob/master/impl/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java#L64-L72

Line 71 will close the streams, and the InflaterOutputStream implementation's close method calls finish which in turn calls inflator.end()

@lhazlewood lhazlewood added the bug label Aug 16, 2018
@zumoris
Copy link
Author

zumoris commented Aug 16, 2018

I am happy to help. :)

And yes, I agree: doDecompress do use InflaterOutputStream with a default Inflater, so it should work fine.

@lhazlewood lhazlewood added this to the 0.10.6 milestone Sep 18, 2018
lhazlewood added a commit that referenced this issue Oct 13, 2018
…ed GzipCompressionCodec and AbstractCompressionCodec to utilize consistent logic across compression algorithms. Resolves #392.
lhazlewood added a commit that referenced this issue Oct 13, 2018
…ed GzipCompressionCodec and AbstractCompressionCodec to utilize consistent logic across compression algorithms. Resolves #392.
lhazlewood added a commit that referenced this issue Oct 13, 2018
…ed GzipCompressionCodec and AbstractCompressionCodec to utilize consistent logic across compression algorithms. Resolves #392.
lhazlewood added a commit that referenced this issue Mar 9, 2019
…ed GzipCompressionCodec and AbstractCompressionCodec to utilize consistent logic across compression algorithms. Resolves #392.
@lhazlewood lhazlewood modified the milestones: 0.10.6, 0.10.7 Mar 9, 2019
lhazlewood added a commit that referenced this issue Mar 10, 2019
…ed GzipCompressionCodec and AbstractCompressionCodec to utilize consistent logic across compression algorithms. Resolves #392.
lhazlewood added a commit that referenced this issue Mar 10, 2019
…ed GzipCompressionCodec and AbstractCompressionCodec to utilize consistent logic across compression algorithms. Resolves #392.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants