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
/DeflaterOutputStream
allocatebyte[]
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
singleByteBuff
method? 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 byDeflaterOutputStream
byte[]
allocations.