GC performance hit for inner class vs. static nested class
Asked Answered
P

2

25

I just came across a weird effect and while tracking it down, I noticed that there seems to be a substantial performance difference for collecting inner vs. static nested classes. Consider this code fragment:

public class Test {
    private class Pointer {
        long data;
        Pointer next;
    }
    private Pointer first;

    public static void main(String[] args) {
        Test t = null;
        for (int i = 0; i < 500; i++) {
            t = new Test();
            for (int j = 0; j < 1000000; j++) {
                Pointer p = t.new Pointer();
                p.data = i*j;
                p.next = t.first;
                t.first = p;
            }
        }
    }
}

So what the code does is create a linked list using an inner class. The process is repeated 500 times (for testing purposes), discarding the objects used in the last run (which become subject to GC).

When run with a tight memory limit (like 100 MB), this code takes about 20 minutes to execute on my machine. Now, by simply replacing the inner class with a static nested class, I can reduce the runtime to less than 6 minutes. Here are the changes:

    private static class Pointer {

and

                Pointer p = new Pointer();

Now my conclusions from this little experiment are that using inner classes makes it much more difficult for the GC to figure out if the objects can be collected, making static nested classes more than 3x faster in this case.

My question is if this conclusion is correct; if yes what is the reason, and if no why are inner classes so much slower here?

Plop answered 4/12, 2013 at 16:25 Comment(5)
What version/build of Java were you using and did you try it in others?Holarctic
static nested classes do not have a reference to their parent class. Maybe this is making the GCs job easier?Armipotent
Good point, I tried it both with 1.6 (1.6.0_22-b04) and 1.7 (1.7.0_21-b11), both 64 bit Linux. The numbers above are for 1.6; the difference is not as big for 1.7 (about 1.2 - 1.4 times faster).Plop
Yep, a non-static inner class has significant overhead vs the identical static inner class.Denver
Best Points can be found here #70824Threat
H
19

I would imagine this is due to 2 factors. The first one you already touched upon. The second is using non-static inner classes results in more memory usage. Why you ask? Because non-static inner classes also have access to their containing classes data members and methods, which means you are allocating a Pointer instance that basically extends the superclass. In the case of non-static inner classes you are not extending the containing class. Here is an example of what I'm talking about

Test.java (non-static inner class)

public class Test {
    private Pointer first;

    private class Pointer {
        public Pointer next;
        public Pointer() {
            next = null;
        }
    }

    public static void main(String[] args) {
        Test test = new Test();
        Pointer[] p = new Pointer[1000];
        for ( int i = 0; i < p.length; ++i ) {
            p[i] = test.new Pointer();
        }

        while (true) {
            try {Thread.sleep(100);}
            catch(Throwable t) {}
        }
    }
}

Test2.java (static inner class)

public class Test2 {
    private Pointer first;

    private static class Pointer {
        public Pointer next;
        public Pointer() {
            next = null;
        }
    }

    public static void main(String[] args) {
        Test test = new Test();
        Pointer[] p = new Pointer[1000];
        for ( int i = 0; i < p.length; ++i ) {
            p[i] = new Pointer();
        }

        while (true) {
            try {Thread.sleep(100);}
            catch(Throwable t) {}
        }
    }
}

When both are run you can see the non-static taking up more heap space than the static. Specifically, the non-static version used 2,279,624 B and the static version used 10,485,760 1,800,000 B.

So, what it comes down to is the non-static inner class uses more memory because it contains a reference (at the very least) to the containing class. The static inner class doesn't contain this reference so the memory is never allocated for it. By setting your heap size so low you were actually thrashing your heap, which resulted in the 3x performance difference.

Holarctic answered 4/12, 2013 at 17:32 Comment(3)
I used Java 1.7.0_45 (x64) on Mac OSXHolarctic
I'm not sure how 2,279,624 is greater than 10,485,760. Did you mix those up?Longanimity
Whoops, I copied the wrong value. That should be on the order of 1,800,000 B. I'll update it.Holarctic
F
8

The cost of garbage-collection goes up very non-linearly when you near the max heap size (-Xmx), with a near-infinite artificial limit where the JVM finally gives up and throws an OutOfMemoryError. In this particular case you are seeing that the steep part of that curve is between the inner class being static or non-static. The non-static inner class is not really the cause, other than in using more memory and having more links. I've seen many other code changes "cause" GC thrashing, where they just happened to be the hapless sap that pushed it over the edge, and the heap limit should simply be set higher. This non-linear behavior shouldn't usually be considered a problem with the code - it's intrinsic to the JVM.

Of course, on the other hand, bloat is bloat. In the current case, a good habit is to make inner classes static "by default" unless access to the outer instance is useful.

Fishbowl answered 22/12, 2013 at 18:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.