Are invisible links still relevant in recent JVMs?

I read Java Platform Performance (unfortunately, the link seems to have disappeared from the Internet since I originally asked this question) and section A.3.3 bothers me.

I worked on the assumption that a variable that fell out of scope would no longer be considered a GC root, but this article seems to contradict this.

Do recent JVMs, in particular Sun version 1.6.0_07, still have this limitation? If so, then I have a lot of code to analyze ...

I ask a question because the article is from 1999 - sometimes things change, especially in the GC world.




Since the document is no longer available, I would like to rephrase the problem. The document implied that the variables that were defined inside the method would be considered the root of the GC until the method completed, and only until the block of code ended. Therefore, setting the variable to null was necessary so that the referenced object was garbage collected.

This meant that a local variable defined in a conditional block in the main () method (or a similar method containing an infinite loop) would cause a one-time memory leak if you did not change the variable just before it dropped out of scope.

The code from the selected answer illustrates the problem well. In the JVM version specified in the document, the foo object cannot be garbage collected when it falls out of scope at the end of the try block. Instead, the JVM will hold the link until the end of the main () method, even if it is not possible to use that link.

This, apparently, is the source of the idea that nulling the variable reference would help the garbage collector, even if the variable was supposed to go out of scope.

+23
java garbage-collection memory-leaks jvm sun
Nov 07 '08 at 9:25
source share
4 answers

This code should clear it:

public class TestInvisibleObject{ public static class PrintWhenFinalized{ private String s; public PrintWhenFinalized(String s){ System.out.println("Constructing from "+s); this.s = s; } protected void finalize() throws Throwable { System.out.println("Finalizing from "+s); } } public static void main(String[] args) { try { PrintWhenFinalized foo = new PrintWhenFinalized("main"); } catch (Exception e) { // whatever } while (true) { // Provoke garbage-collection by allocating lots of memory byte[] o = new byte[1024]; } } } 

On my machine (jdk1.6.0_05) it prints:

Building from the core

Finalization from the main

So it looks like the problems are fixed.

Note that using System.gc () instead of a loop does not cause the object to be collected for any reason.

+6
Nov 07 '08 at 11:33
source share
— -

The article states that:

... an efficient JVM implementation is unlikely to result in null links when it goes beyond

I think this is due to such situations:

 public void doSomething() { for(int i = 0; i < 10 ; i++) { String s = new String("boo"); System.out.println(s); } } 

Here the same link is used by the "effective JVM" in every String s declaration, but there will be 10 new lines in the heap if the GC doesn't hit.

In the example article, I think that the link to foo is stored on the stack, because the "efficient JVM" believes that it is very likely that another foo object will be created, and if so, then it will use the same link. Thoughts ???

 public void run() { try { Object foo = new Object(); foo.doSomething(); } catch (Exception e) { // whatever } while (true) { // do stuff } // loop forever } 

I also performed the following profiling test:

 public class A { public static void main(String[] args) { A a = new A(); a.test4(); } public void test1() { for(int i = 0; i < 10 ; i++) { B b = new B(); System.out.println(b.toString()); } System.out.println("b is collected"); } public void test2() { try { B b = new B(); System.out.println(b.toString()); } catch (Exception e) { } System.out.println("b is invisible"); } public void test3() { if (true) { B b = new B(); System.out.println(b.toString()); } System.out.println("b is invisible"); } public void test4() { int i = 0; while (i < 10) { B b = new B(); System.out.println(b.toString()); i++; } System.out.println("b is collected"); } public A() { } class B { public B() { } @Override public String toString() { return "I'm B."; } } } 

and come to the conclusions:

teste1 → b is going

teste2 → b is invisible

teste3 → b is invisible

teste4 → b assembled

... therefore, I think that in cycles the JVM does not create invisible variables when the cycle ends, because it is unlikely that they will be declared again outside the cycle.

Any thoughts

+2
Nov 07 '08 at 12:34
source share

Do you really have code like this for analysis? Basically, I can only see that this is a serious problem for very long methods, which are usually only the tops of each thread stack.

I would not be surprised if it had not been fixed at the moment, but I do not think that it can be as significant as you seem to be afraid.

+1
Nov 07 '08 at 9:42
source share

The problem still exists. I tested it with Java 8 and could prove it.

You should note the following things:

  • The only way to force guaranteed garbage collection is to try out a selection that ends in OutOfMemoryError, since the JVM is required to try to free unused objects before throwing. However, this does not happen if the requested amount is too large to succeed, that is, exceeds the address space. Trying to boost the selection to get OOME is a good strategy.

  • The guaranteed GC described in clause 1 does not guarantee completion. The time when the finalize () method is called is not specified; they could not be called at all. Therefore, adding the finalize () method to a class may prevent its instances from being assembled, so finalization is not a good choice for analyzing GC behavior.

  • Creating another new local variable after the local variable leaves the scope reuses its place in the stack frame. In the following example, the object a will be assembled, since its place in the stack frame is taken by the local variable b. But b lasts until the end of the main method, since there is no other local variable to take its place.

     import java.lang.ref.*; public class Test { static final ReferenceQueue<Object> RQ=new ReferenceQueue<>(); static Reference<Object> A, B; public static void main(String[] s) { { Object a=new Object(); A=new PhantomReference<>(a, RQ); } { Object b=new Object(); B=new PhantomReference<>(b, RQ); } forceGC(); checkGC(); } private static void forceGC() { try { for(int i=100000;;i+=i) { byte[] b=new byte[i]; } } catch(OutOfMemoryError err){ err.printStackTrace();} } private static void checkGC() { for(;;) { Reference<?> r=RQ.poll(); if(r==null) break; if(r==A) System.out.println("Object a collected"); if(r==B) System.out.println("Object b collected"); } } } 
+1
Aug 23 '13 at 15:01
source share



All Articles