2

Below is an example of Composition:

public class A3 {
    B b = new B();
}

What I learned is that as soon as object of A3 is GC'ed (or becomes eligible for GC), object of B will also be GC'ed (or becomes eligible for GC).

Now, let's consider below situation, so as per my understanding after d.m1();, a3 object on heap will become eligible for GC, but I don't think that b object becomes eligible for GC.

My two understandings are contracting with each other, could someone please help me identify which one is wrong.

public class Testing {

    public static void main(String[] args) {
        D d = new D();
        d.m1();
        d.m2();
        // do some more things...
    }
}

    public class A3 {
        B b = new B();
        
        public B getB() {
            return b;
        }
    }
    
    public class B{
        public void m1() {
            System.out.println("B.m1");
        }
    }
    
    public class D{
        B bd2;
        
        public void m1() {
            A3 a3 = new A3();
            bd2 = a3.getB();
        }
        
        public void m2() {
            bd2.m1();
        }
    }

UPDATE: I do understand difference between "eligible for GC" and "actually getting GC'ed", so for sake of convenience, let's say object will immediately be GC'ed once it becomes eligible.

pjj
  • 2,015
  • 1
  • 17
  • 42
  • @SergeyAfinogenov I didn't get your question. – pjj Feb 20 '21 at 13:57
  • @SergeyAfinogenov Thanks, I didn't mean them in context of inner classes, so updated the code to better reflect. – pjj Feb 20 '21 at 14:11
  • 1
    [Recommended read](https://stackoverflow.com/a/52439117/2711488), also the linked Q&As like [Can java finalize an object when it is still in scope?](https://stackoverflow.com/q/24376768/2711488) In sort, you're focusing on mostly irrelevant things. – Holger Feb 20 '21 at 19:55

4 Answers4

1

What I learned is that as soon as object of A3 is GC'ed (or becomes eligible for GC), object of B will also be GC'ed (or becomes eligible for GC).

This isn't necessarily true, as long as there is a way to reference the object B, it will not be eligible for GC.

Now, let's consider below situation, so as per my understanding after d.m1();, a3 object on heap will become eligible for GC, but I don't think that b object becomes eligible for GC.

This is mostly correct. As it is possible to refer to the object b through the object d, b would not be eligible for garbage collection.

Being eligible for GC doesn't really mean that it would be GC'ed though.

Shadab
  • 1,297
  • 6
  • 9
  • But doesn't `a3` will be GC'ed as soon as `d.m1()` is completed? – pjj Feb 20 '21 at 14:13
  • Not really "as soon as", but yes it would happen during the next GC cycle. – Shadab Feb 20 '21 at 14:14
  • I updated by question, please see end. I do not want to debate on "eligible for GC" and "actually getting GC'ed". – pjj Feb 20 '21 at 14:16
  • 1
    I think I understood your confusion. The object `a3` holds a reference to the object b. It does not "own" the object. So GC'ing `a3` will not automatically GC the object b. The object b will be only be GC'ed once there are no more references to it. – Shadab Feb 20 '21 at 14:20
  • "The object a3 holds a reference to the object b. It does not "own" the object." --> my understanding is that objects of class A3 will own object 'b'. Can you please give me example of ownership of objects, probably then I will understand why you think that there is no ownership of objects in my example. – pjj Feb 20 '21 at 14:24
  • Umm..java doesn't really any sort of object ownership. You could count thread ownership, or resource ownership, but the concept of object ownership doesn't exist. If you look at the java language docs I doubt you'll find anything related to object ownership either. – Shadab Feb 20 '21 at 14:33
  • Exactly. That's why I wanted you to put an example for it. But if you treat "ownership" as a logical concept, then I would say that objects of class A3 will own object 'b'. – pjj Feb 20 '21 at 14:42
  • 2
    @pjj you can implement conceptual ownership, like `String` owns its array by never handing out a reference to anyone. But to the garbage collector, concepts are irrelevant, only existing references matter. – Holger Feb 20 '21 at 19:59
1

Here understanding of situation 2 is correct. As soon as d.m1() is complete, a3 becomes eligible for GC (but not necessarily it will get GC'ed as GC depends on the JVM) but as B holds the reference in the object bd2 outside m1(), it will not become eligible for GC.

You can go through this wonderful article on JVM memory management to get deep understanding :

JVM memory Management

Dharman
  • 30,962
  • 25
  • 85
  • 135
Deepika
  • 41
  • 3
1

Transferring this to an answer, because the comment became too long.

What does happen?

Firstly, @Shadab is right. Object a3 will be collected while object b remains.

Intuitive explanation

Your confusion seems to stem from the object oriented view towards the problem.

Basically you view A as a car and b as one of its wheels. So when looking at the problem like this the logical question is - when I crush the car and it is then gone, how can the wheel that is part of the car still exist? That does not make sense on first glance, even if someone else was given "access" to the wheel.

This is not how it works w.r.t memory management and it is also where the metaphor will break:

  • When you create a car A it does also create a wheel B.
  • Both of these things exist independently in memory.
  • The object oriented metaphor breaks here, because the objects that make up a car and which were constructed when the car was constructed are not actually "part" of the car. Only references to those objects are part of the car, but the data itself is not.

Overstretching the metaphor

If you want to have some sort of mental image of what is really going on, you can try and look at it like this:

  • Let A be a car.
  • Let B be a wheel
  • Let C be a bolt
  • A wheel B "consists" of five bolts C, i.e. five objects C are members of an object B
  • A car A "consists" of four wheels B, i.e. four objects B are members of an object A.

When you create A

A a = new A();

You also create four instances of B and these in turn create twenty instances of C. However with regard to objects in memory, your wheels are not mounted on your car with the bolts. The car A is lying in the parking lot (which is your memory) with four postcards (references) of its wheels inside it. The four wheels are also lying somewhere else in the parking lot, each with five postcards of their respective bolts on top of them. And the twenty bolts are lying somewhere in the parking lot as well.

Maybe at this point you start to see, why this object oriented approach is not necessarily suitable to understand the way this memory management works and how the metaphor is stretched really thin.

So what happens now if you destroy the car in the parking lot? The car A is gone, the postcards of the four wheels B are gone too. But that does not mean that the wheels are destroyed as well. Only if in the entire parking lot the GC cannot find anything that has a postcard of a specific wheel instance it is allowed to destroy the wheel as well. But if there is another object that holds these postcards, then the wheels are not garbage collected. Same goes for the bolts.

Bottom line

Objects do not hold objects. Objects hold references to objects. Objects are only garbage collected if there is no further reference to them. Data wise these things are independent of each other, even if the OOP model may suggest otherwise when looking at it naively.

Koenigsberg
  • 1,726
  • 1
  • 10
  • 22
1

What @Shadab and @Koenigsberg has said is right, here is some explanation with a demo - basically what I did is I have overridden the finalize() method so as to show you how objects are getting GC'ed.

  • Key thing to note here is that any object created inside a method and not passed anywhere else from the method will be eligible for GC as soon as the method's stack frame is taken out from the stack. That is why you will see "A3 is garbage collected" as soon as d.m1(); is over.
  • And for the same reason, you see "D is garbage collected" once test1(); is over. If you comment out test2(); then "D is garbage collected" will not be printed because once stack frame of test1 method was removed, there was no (forced) GC , and as long as stack frame of test1 is present on the stack, object d anyways cannot be removed.
  • GC of object 'b' is little tricky, if you don't sleep thread for a few milli-seconds then it doesn't get GC'ed, sadly I don't know the exact explanation for this.

Another important point to note is you can check yourself on which objects can be GC'ed by starting from the root object, in this example object of class Testing will be your root object, so at each step you can try to see whether you can reach a particular object by starting from your root object or not, any object which is not reachable from your root tree becomes eligible for GC.

public class Testing {

    public static void main(String[] args) {
        test1();
        test2();
    }

    private static void test1() {
        Testing t = new Testing();
        D d = t.new D();
        d.m1();
        System.gc();
        d.m2();
        System.gc();
        // do some more things...
    }

    private static void test2() {
        System.gc();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.gc();
    }
}

public class A3 {
    B b = new B();

    public B getB() {
        return b;
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("A3 is garbage collected");
    }
}

public class B {
    public void m1() {
        System.out.println("B.m1");
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("B is garbage collected");
    }
}

public class D {
    B bd2;

    public void m1() {
        A3 a3 = new A3();
        bd2 = a3.getB();
    }

    public void m2() {
        bd2.m1();
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("D is garbage collected");
    }
}
hagrawal7777
  • 14,103
  • 5
  • 40
  • 70
  • 2
    You are confusing garbage collection and finalization. Even if `System.gc()` collects an object immediately (which is not guaranteed), the finalization happens an arbitrarily long time *after* garbage collection determined that an object is eligible to finalization. It may even happen never at all. That’s the reason you sometimes need a `sleep` call to see the *finalizer’s* effect, the promptness of the gc itself is not observable. Besides that, the behavior of the program is as you expect, because it runs mostly interpreted. Optimized execution can exhibit an entirely different behavior. – Holger Feb 22 '21 at 10:41
  • 2
    To see contradicting behavior, try the program of [this answer](https://stackoverflow.com/a/24380219/2711488) or the extended variant found [here](https://stackoverflow.com/a/54942944/2711488). – Holger Feb 22 '21 at 10:44
  • @Holger Ok thanks, I will definitely check. – hagrawal7777 Feb 22 '21 at 17:25