Inefficient use of Inflaters increases GC time when using mysql protocol compression
Ameet Kotian <[email protected]> Thu, 13 Aug 2015 13:41:46 -0700
| Newsgroups | gmane.comp.db.mysql.java |
|---|---|
| Message-ID | <CA+MbMe2k-Eq50-Ryic4wx7hA9pbrzynpyeEdmPSGBFfcvVO4OA@mail.gmail.com> |
Summary:
Inflaters are not used efficiently in the CompressedInputStreams class
and this leads to lots of finalize calls to Inflater objects which
drives up the garbage collection time for application using mysql
protocol compression feature.
Details:
The issue is that the MySQL driver seems to allocate a very high
number of Inflaters and Inflaters need to be finalized irrespective of
whether they have been shut down properly or not. Even if the inflater
is shut down and its native memory released, the inflater will still
be enqueued for finalization, in the jvm, even if the finalizer will
essentially be a nop as there's nothing else to do. (This is one of
the huge downsides of finalization in Java: there's no way to tell the
JVM that an object doesn't need to be finalized any more.)
Look at the code in method CompressedInputStream.getNextPacketFromServer():
...
try {
this.inflater.reset();
} catch (NullPointerException npe) {
this.inflater = new Inflater();
}
this.inflater.setInput(compressedBuffer);
try {
this.inflater.inflate(uncompressedData);
} catch (DataFormatException dfe) {
throw new IOException(
"Error while
uncompressing packet from server.");
}
this.inflater.end();
...
It re-allocates or resets the inflater (more on this in a bit), does
the uncompression, and then calls end(). Unfortunately, inflater.end()
actually completely shuts down the inflater and reclaims its native
buffer. So, next time the getNextPacketFromServer() method is called
(And this can happen numerous times for a high throughput application
running compression), inflater.reset() will throw a
NullPointerException given the inflater had been reset and the address
to its native buffer is 0. Here's the relevant code from
Inflater.java:
public void end() {
synchronized (zsRef) {
long addr = zsRef.address();
zsRef.clear(); // This zeroes the address held by zsRef
...
}
}
public void reset() {
synchronized (zsRef) {
ensureOpen();
...
}
}
private void ensureOpen () {
assert Thread.holdsLock(zsRef);
if (zsRef.address() == 0)
throw new NullPointerException("Inflater has been closed");
}
The whole file is here if you're interested:
http://www.docjar.com/html/api/java/util/zip/Inflater.java.html
Is there a reason to call end() and effectively spin off a new
Inflater each time?
A solution could be not to call inflater.end() from
getNextPacketFromServer(). This way, reset() will actually manage to
reset the inflater and we might avoid constantly allocating new ones.
--
Thanks,
Ameet Kotian
--
MySQL Java Mailing List
For list archives: http://lists.mysql.com/java
To unsubscribe: http://lists.mysql.com/java