2

I am testing SoftReference for a cache implementation and I found a strange behaviour :

I have a setName(String name) method which sets the name of a Graph object through a SoftReference :

public void setName(String newName) {
    getData().name = new SoftReference<String>(newName,garbagedData);
}

(garbagedData is a ReferenceQueue and doesn't seem important in this particular problem).

When I call graph.setName("name"); from main thread, and when à force a OutOfMemory error, the value pointed by the reference is not garbaged but if I call graph.setName(new String("name")) then it is.

I look the heap contents with Eclipse Memory Analyzer and in both case there is no other reference chain than the Soft one.

If anyone has an explanation for this strange behavior I am interested.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
zelus
  • 143
  • 8
  • A memory-limited cache is a really bad idea: old items will be kept long past their useful life. – kdgregory Feb 06 '14 at 23:45
  • @kdgregory: I agree that it's a bad idea, but mostly you have no control at all. But you often can't tell what's the "useful life" and there's no way how to balance different caches (example: You have to decide things like "should the cache-1 limit be 1000 entries?" rather than "let cache-1 and cache-2 use together no more than 100 MB"). – maaartinus Feb 06 '14 at 23:59
  • In my case the "useful life" is the longest one. But if I use strong references a out of memory may occur. @kdgregory: Is it a bad thing to have object kept in memory if the gc can delete them whenever it needs to ? @ maaartinus: I can always set the references manually to null and limit the cache size, am I wrong ? – zelus Feb 07 '14 at 01:01

2 Answers2

2

That's pretty simple. The string appearing in the source code is interned, i.e., kept in a special pool, and there's no chance to collect it as it's referenced by the code of your method. It's needed when setName gets called again, so it's obviously no garbage.

The situation with new String("name") is very different as this creates a copy of the original String. Actually, there's no need to use this constructor since this change to String.substring.

I guess the Eclipse Memory Analyzer doesn't show references contained in the bytecode (as nobody really needs it).

maaartinus
  • 44,714
  • 32
  • 161
  • 320
  • Thanks, I didn't know about intern Strings and this special pool. I suspected a kind of optimization like that. About the change on `String.substring`, that means I just have to call `substring(0)` instead of constructor ? It doesn't seem very explicit for me. – zelus Feb 07 '14 at 00:53
  • @zelus: No. Before this change, each String had a pointer to a possibly shared `char[]` and an `offset` and `length`. So `String s = aVeryLongString.substring(0, 1)` was a very fast operation, but you got an object pointing to the huge `char[]`. To counter this, you could use `new String(s)` which copied the needed part of the array. After the change, `substring` performs the copy, so there's no more use for the constructor. Neither you should replace it by`substring(0)` nor by anything else. No need at all. – maaartinus Feb 07 '14 at 01:56
0

You are holding a SoftReference to String object. For a String object JVM manages String literals differently from String objects.

String a = new String("test");
String b = new String("test");

In this example instances a and b are references to different objects.

String a = "test";
String b = "test";

Here both a and b are references to same String literal. When you call graph.setName("name"), it is creating a reference to string literal. String objects that refer to String literals generally are not considered for garbage collection. This prevents the String object from getting garbage collected.

When you call graph.setName(new String("name")), it is creating a SoftReference to new String object. Since the reference is to newly created object and not a string literal, it can be garbage collected.

pawinder gupta
  • 1,225
  • 16
  • 35