I have an application that processes text files and stores them and to save some space it gzips the files.
So I have some chained OutputStreams and one of them is java.util.zip.GZIPOutputStream that manages the compression.
To make sure I was not wasting memory somewhere, I profiled my process with the async profiler/intellij with a file that had around 6MB of random data in a small loop for some amount of times. For reference I'm using Temurin JDK18.
I was surprised to see a lot of memory allocations with the GZIPOutputStream (via the parent method):
1,601,318,568 samples
That's a bit strange. I know GZIPOutputStream/DeflaterOutputStream uses a buffer, but why is it doing so many allocations?
I look deeper in the code. I notice the parent method in java.util.zip.DeflaterOutputStream does this when it writes a byte:
public void write(int b) throws IOException {
byte[] buf = new byte[1];
buf[0] = (byte)(b & 0xff);
write(buf, 0, 1);
}
So, it makes a new single byte array for every single byte? That definitely seems like it would be a lot of allocations?
To see if it makes a difference, I extend GZIPOutputStream with a new class I called LowAllocGzipOutputStream with an override method like this:
private final byte[] singleByteBuff = new byte[1];
@Override
public void write(int b) throws IOException {
singleByteBuff[0] = (byte)(b & 0xff);
write(singleByteBuff, 0, 1);
}
I then profiled it again with my test case to see what might happen. The data was quite different:
162,262,880 samples
That is a pretty big reduction of allocations, -1,439,055,688 samples.
So I'm left with a few questions that I haven't found answers for:
- Why does
GZIPOutputStream/DeflaterOutputStreamallocatebyte[]s like this? This is a class that comes with the JDK, so I'm sure it's been profiled and scrutinized heavily, but with my naive understanding it appears to be unnecessarily wasteful? Does the single byte array get optimized away by hotspot or something eventually? Does it not really add pressure to the garbage collector? - Is there a negative consequence to my cached
singleByteBuffmethod? I can't seem to think of any issue it would cause so far. The benefit that I find with it is that my app's memory profile is no longer dominated byDeflaterOutputStreambyte[]allocations.