2

I wrote a code to check if ImageIO.read would take much memory and then cause high memory usage.(there was a production issue before)

    import javax.imageio.ImageIO;

    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;


    public class ImageIOTest2 {

        public static void main(String[] args) {
                for (int i = 0; i < 20; i++) {
                    BufferedImage image;
                    try {
                        image = ImageIO.read(new File("test.jpg"));
                        System.out.println(image);
                        Thread.sleep(100);
                    } catch (IOException) {
                        e.printStackTrace();
                    }
                }
        }
    }

test.jpg is about 4.3MB (I guessed maybe extra memory 4.3 * 20 * (3 or 4) exclude JVM).

I use jemalloc to track memory mallocation.
LD_PRELOAD=/usr/local/jemalloc/lib/libjemalloc.so MALLOC_CONF=prof:true,lg_prof_sample:17,lg_prof_interval:25,prof_prefix:/root/output/je java -Xmx64m -Xms64m -XX:NativeMemoryTracking=summary ImageIOTest2

The RSS in htop was about 160M and I printed the NMT:

Native Memory Tracking:

Total: reserved=1377MB, committed=109MB
-                 Java Heap (reserved=64MB, committed=64MB)
                            (mmap: reserved=64MB, committed=64MB)

-                     Class (reserved=1037MB, committed=10MB)
                            (classes #827)
                            (malloc=5MB #716)
                            (mmap: reserved=1032MB, committed=5MB)

-                    Thread (reserved=16MB, committed=16MB)
                            (thread #17)
                            (stack: reserved=16MB, committed=16MB)

-                      Code (reserved=244MB, committed=3MB)
                            (mmap: reserved=244MB, committed=2MB)

-                        GC (reserved=8MB, committed=8MB)
                            (malloc=6MB #118)
                            (mmap: reserved=2MB, committed=2MB)

-                  Internal (reserved=5MB, committed=5MB)
                            (malloc=5MB #2080)

-                    Symbol (reserved=2MB, committed=2MB)
                            (malloc=1MB #200)
                            (arena=1MB #1)

committed is about 109MB.

But the total memory from jeprof is 1055.7M: enter image description here

If I do nothing in code just print something.
It will malloc 200MB.(Using the code before comment out ImageIO and same options).
So almost 800MB for reading a 4.3MB jpeg for 20 times.
(Ps:Just read the jpeg file using Files.readAllBytes just malloc 256MB using the same options)

Is this normal? And how to optimize memory using ImageIO?

fairjm
  • 1,115
  • 12
  • 26
  • 3
    [JPEG](https://en.wikipedia.org/wiki/JPEG) is compressed (actually **it is** the compression method), most certainly Java is saving the image without compression, that is, each pixel... so the pixel count is important not the disk space to determine how much memory is used - related: https://stackoverflow.com/q/12489009/85421 – user85421 Sep 26 '18 at 13:56
  • 1
    jeprof - shows you that 77.9% of memory (about 770 mb) used for the decoded image pixels map. I.e. array of [ARGB](https://en.wikipedia.org/wiki/RGBA_color_space) pixel color components (4 bytes per pixel) which is array of int type. – Victor Gubin Sep 26 '18 at 14:32
  • @CarlosHeuberger thx~ – fairjm Sep 27 '18 at 03:02
  • @VictorGubin since the extra memory not used by heap(size fixed).So lots of memory is used by native lib? anyway thx~ – fairjm Sep 27 '18 at 03:04
  • 1
    As far as I know, BufferedImage default raster may allocate memory for image pixmap directly in the [video-ram](https://www.oracle.com/technetwork/java/perf-graphics-135933.html) since java 1.4. On handles mode it should be a call to default system VirtualAlloc/mmap etc. – Victor Gubin Sep 27 '18 at 09:42

2 Answers2

2

Sounds just about right? Let's just count the bytes and remember that garbage collection works in increments, not instantly.

The absolutely minimal number of memory used by the loop will be:

image width * image height * 3 (byte per RGB; in practice more likely 4) * 20 = MEMORY.

The number above is not dependent on technology. It's just the amount of data processed. In Java, this memory will accumulate as long as there's a free memory available (there's no way of instantly freeing objects).

Since you claim that it takes 800 MB on your system, then, working backwards, I guess that:

width * height = 800 000 000 / 20 / 4 = 40 000 000 / 4 = 10 000 000.

This all means that if your source image is around 3500 x 2500 pixels, then your code is pretty much expected to use 800 MB. The only way around it would be to somehow invoke GC after discarding each image. For example by simply limiting the amount of available memory.

fdreger
  • 12,264
  • 1
  • 36
  • 42
  • I think it's not GC.Because Heap size is fixed,this extra memory is native memory malloc,looks like native lib using so much? – fairjm Sep 27 '18 at 03:02
  • Frankly, I still don't really understand the problem: everything you describe is 100% expected: processing 800 MB of data uses 800 MB of memory. What did you expect? What is your question? – fdreger Sep 27 '18 at 09:44
  • I mean the 800M is not used in head but off-heap(and not direct memory but native memory). GC does not happen int these area.And my problem is how to optimize memory(May change some native lib like using jemalloc to take place of original malloc). :) – fairjm Sep 27 '18 at 11:14
  • Either I don't understand the question or you are overthinking and looking for a problem where there is none. GC of native memory is driven by GC of Java objects - there is little difference if the memory is native or not. JVM by default optimizes by preferring bigger memory usage over more frequent collections - if you don't like it, you might want to invoke GC after discarding each image. You could also try calling flush on images after using them (this might reclaim some native resources a bit sooner). This is only likely to affect your microbenchmark - in real world 800MB is 800MB. – fdreger Sep 28 '18 at 12:01
  • It was a production problem.I found the memory was not released immediately by GC or FGC(I did it manually using jcmd),but some time later(one night maybe, this really strange).And the code reads image paralleling(My workmate has changed it to using work queue).I want to find anyway to reduce native memory usage reading image(like I have said before,change native image lib).I'am sorry not saying it clearly and thx for your help~ :) – fairjm Sep 28 '18 at 12:23
  • GC in Java NEVER releases memory immediately. This is NOT an error, nor is it strange - it's a feature. Objects are always collected in batches, because it gives better performance. What's more, in usual JVM setups, big objects might be allocated straight in old gen space - the "cold" part of heap, rarely collected - this could be once an hour, several hours, even once a week. Again: what is the problem? JVM is using the memory you gave it in a way it considers best. What you describe is a healthy JVM trying to do its job. – fdreger Sep 28 '18 at 13:18
  • I have said before ...It's not a heap problem,It's a native memory usage problem...Heap is ok and not increased(This is why I print NMT) and problem is too much native memory(This is why I print jeprof result) allocated... I just don't understand I said times it's not about heap why you still focused it.More clearly,give a example, I found there is 64M memory gap so I change default malloc to jemalloc.Now I want to figure out is there a better image lib than default to save memory.Not heap but native lib You can just think JNI or C lib. – fairjm Sep 28 '18 at 13:28
  • Could you try the solution I gave you (plainly: add `System.gc()` before `sleep` in your loop) and then come back with the results? – fdreger Sep 28 '18 at 13:59
  • Not much different I tried.Because jemalloc heap profiling don't record native memory release and this situation not changed any native library the malloc behavior would not changed too much.It's a low level memory allocation optimization out of the JVM.Image you find your game not worked well in new graphic driver version so you downgrade.. – fairjm Sep 28 '18 at 14:17
1

I faced the same problem when using ImageIO.read(). This caused the physical memory usage on the production server increased and the OS killed 3 over 4 tomcat instances running on that server.

Solution: using apache-commons-imaging to read image sizes.

mobject
  • 179
  • 5