4 Replies Latest reply on Nov 9, 2011 5:28 PM by 898939

    Memory leak or what ?

      Hi all,

      Is there anybody to explain me why the method below is seriously draining memory (It sucks my 16Gb within seconds).
      It is a simple rotate method, after the first rotation operator, it is searching the unpainted bounding box to be able to trim/crop the image (the rotated image has an alpha layer and after rotation there is a need to trim borders). It finally does a translation necessary for some processing after exiting this method.
      The method yields expected result.

      It's easy to reproduce what I would call memory leak - however I'am not sure, there may be some mistake in my code :

      image = ImageUtils.rotate(image, angle);

      hundreds of time, with a big image, say 2048x2048 or more. Calling gc() in the loop won't help.

      I am running JDK1.6 last verison on Mac OS X Lion. Same problem with JDK1.6 64bits linux ubuntu.

      Any hint ?
      Thank you,

      public static PlanarImage rotate(PlanarImage image, int degAngle) {
              float angle = (float) Math.toRadians(degAngle);
              float centerX = 0; //  (float) 1+(diag - image.getWidth()) + (image.getWidth() / 2f);
              float centerY = 0; //  (float) 1+(diag - image.getHeight()) + (image.getHeight() / 2f);
              // Rotates the original image.
              ParameterBlock pb = new ParameterBlock();
              pb.addSource(image );
              RenderedOp rotatedImage = JAI.create("rotate", pb, rh); // ROTATION
              Rectangle bounds = rotatedImage.getBounds();
              pb = new ParameterBlock();
              pb.add((float) (bounds.getX() > 0 ? -bounds.getX() : Math.abs(bounds.getX())));
              pb.add((float) (bounds.getY() > 0 ? -bounds.getY() : Math.abs(bounds.getY())));
              RenderedOp rotatedImage2 = JAI.create("translate", pb, rh);
              // Determine la zone peinte (pour supprimer les bords non peints).
              int x1 = Integer.MAX_VALUE, y1 = Integer.MAX_VALUE, x2 = 0, y2 = 0;
              Raster raster = rotatedImage2.getData();
              for (int yi = raster.getMinY(); yi < raster.getHeight(); yi++) {
                  for (int xi = raster.getMinX(); xi < raster.getWidth(); xi++) {
                      if (raster.getSample(xi, yi, 3) != 0) {
                          x1 = Math.min(x1, xi);
                          y1 = Math.min(y1, yi);
                          x2 = Math.max(x2, xi);
                          y2 = Math.max(y2, yi);
              pb = new ParameterBlock();
              pb.add((float) x1);
              pb.add((float) y1);
              pb.add((float) (x2 - x1));
              pb.add((float) (y2 - y1));
              RenderedOp rotatedImage3 = JAI.create("crop", pb, rh);
              bounds = rotatedImage3.getBounds();
              pb = new ParameterBlock();
              pb.add((float) (bounds.getX() > 0 ? -bounds.getX() : Math.abs(bounds.getX())));
              pb.add((float) (bounds.getY() > 0 ? -bounds.getY() : Math.abs(bounds.getY())));
              return JAI.create("translate", pb, rh);
        • 1. Re: Memory leak or what ?
          It's not entirely clear to me where you're allocating memory and where you're looping, however, consider the following:
          void foo() {
            for (a large number of times) {
              ReallyBigObject rbo = new ReallyBigObject();
              someOtherMethod(rbo); // e.g., PlanarImage.rotate(rbo, angle);
          In the above, although at any given time there's only one reachable RBO, and therefore looping should not cause heap memory usage to accumulate, my understanding is that typical JVM behavior is to NOT GC objects that are created/released locally until after the method exits. The result is that even though you only really have one "live" RBO at any given time, they accumulate as if they are all reachable until the method ends.

          I don't know if this is in fact true, or if it even represents what you're doing. It should be easy to test, however, by simply removing the loop body, including the RBO creation, into a separate method.

          Barring that, you're best bet is to throw a profiler on to see what's being held onto, and by whom. Maybe you've got something in a collection somewhere that you're done with but not removing from the collection.
          • 2. Re: Memory leak or what ?
            I can't reproduce the memory leak since each successive loop of your code takes exponentially longer then the previous loop. So I can't loop it hundreds of times.

            I can kind of see why this might be. PlanarImages, unlike BufferedImages, holds the source of the image in memory (be it from a file or from a previous RenderedOp). So internally you get translated images refering to cropped images, referring to rotated images, and so on all the way back to the initial creation of the PlanarImage. In my particular case, when the PlanarImage's data is needed it goes through all the calculations starting from loop #1; hence the slow down. In your case, buffer data must be storing at each level in PlanarImage's hierarchy and not getting disposed of properly.

            The solution I would recommend is to do the rotation with the Java2D api.
            public static PlanarImage getRotatedImage(PlanarImage img,int degrees){
                BufferedImage src = img.getAsBufferedImage();
                AffineTransform xForm = AffineTransform.
                java.awt.Point p = xForm
                double[] mtrx = new double[6];
                mtrx[4] = -p.x; mtrx[5] = -p.y;
                xForm = new AffineTransform(mtrx);
                return PlanarImage.wrapRenderedImage(
                        new AffineTransformOp(xForm,null).filter(src,null));
            • 3. Re: Memory leak or what ?

              Yes I see what you mean.

              The resulting rotated image is painted and then disposed anyway.
              So to my understanding the "memory leak" is rather caused by JAI...
              • 4. Re: Memory leak or what ?
                Well I thought RenderedOp could be chained without taking care of memory management !

                I have tried with your replacement method based on BufferedImage, yes indeed, no more memory leak ! Thank you !
                The angle is interpreted differently (this can be fixed easily).
                My concern is about antialiasing : the resulting rotated image is quite poor in term of antialiasing !

                I was using the following to get acceptable results with JAI :
                static HashMap map = new HashMap();
                    static RenderingHints rh = new RenderingHints(map);
                    static InterpolationBicubic interpol = new InterpolationBicubic(32);
                    static {
                        map.put(JAI.KEY_BORDER_EXTENDER,  BorderExtender.createInstance(BorderExtender.BORDER_ZERO));
                        map.put(JAI.KEY_INTERPOLATION, Interpolation.getInstance(Interpolation.INTERP_BICUBIC));
                        map.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                        map.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
                So I transmitted the same rh to :
                PlanarImage.wrapRenderedImage(new AffineTransformOp(xForm,rh).filter(src,null));

                Strangely enough, the quality of the result is very poor, not exploitable, nothing comparable to JAI.

                Solved !
                Transmitting Rendering Hints has absolutely no effect, rather, one must transmit the BICUBIC constant and then it works as expected!
                PlanarImage.wrapRenderedImage(new AffineTransformOp(xForm, AffineTransformOp.TYPE_BICUBIC ).filter(src,null));

                Thank you very much!
                I will be now suspicions about JAI.

                Edited by: semiosys on 9 nov. 2011 09:23