简体   繁体   中英

Does this Java example cause a memory leak?

I have a simple example. The example loads an ArrayList<Integer> from a file f containing 10000000 random integers.

doLog("Test 2");
{
    FileInputStream fis = new FileInputStream(f);
    ObjectInputStream ois = new ObjectInputStream(fis);
    List<Integer> l = (List<Integer>) ois.readObject();
    ois.close();
    fis.close();
    doLog("Test 2.1");
    //l = null; 
    doLog("Test 2.2");
}
doLog("Test 2.3");
System.gc();
doLog("Test 2.4");

When I have l = null , I get this log:

Test 2                          Used Mem = 492 KB   Total Mem = 123 MB
Test 2.1                        Used Mem = 44 MB    Total Mem = 123 MB
Test 2.2                        Used Mem = 44 MB    Total Mem = 123 MB
Test 2.3                        Used Mem = 44 MB    Total Mem = 123 MB
Test 2.4                        Used Mem = 493 KB   Total Mem = 123 MB

But when I remove it, I get this log instead.

Test 2                          Used Mem = 492 KB   Total Mem = 123 MB
Test 2.1                        Used Mem = 44 MB    Total Mem = 123 MB
Test 2.2                        Used Mem = 44 MB    Total Mem = 123 MB
Test 2.3                        Used Mem = 44 MB    Total Mem = 123 MB
Test 2.4                        Used Mem = 44 MB    Total Mem = 123 MB

Used Memory is calculated by: runTime.totalMemory() - runTime.freeMemory()

Question: In case where l = null; is present, is there a memory leak? l is inaccessible, so why can't it be freed?

There is no memory leak in the above code.

As soon as you leave the code block enclosed in {} , the variable l falls out of scope, and the List is a candidate for garbage collection, regardless of if you set it to null first or not.

However , after the code block and until the return of the method, the List is in a state called invisible . While this is true, the JVM is unlikely to automatically null out the reference and collect the List 's memory. Therefore, explicitly setting l = null can help the JVM collect the memory before you do your memory calculations. Otherwise, it will happen automatically when the method returns.

You will probably get different results for different runs of your code, since you never know exactly when the garbage collector will run. You can suggest that you think it should run using System.gc() (and it might even collect the invisible List even without setting l = null ), but there are no promises. It is stated in the javadoc for System.gc() :

Calling the gc method suggests that the Java Virtual Machine expend effort toward recycling unused objects in order to make the memory they currently occupy available for quick reuse. When control returns from the method call, the Java Virtual Machine has made a best effort to reclaim space from all discarded objects.

I think there's a bit of semantics issue here. "Memory leak" generally means having some data stored in memory by a program (piece of software, etc) and getting that program into a state where it can no longer access that in-memory data to clean it up, thus getting into a situation where that memory cannot be claimed for future use. This, as far as I can tell, is the general definition.

A real-world use of the term "memory leak" is usually in reference to programming languages where it's up to the developer to manually allocate memory for the data that he intends to place on the heap. Such languages are C, C++, Objective-C (*), etc. For example the "malloc" command or the "new" operator both allocate memory for an instance of a class that will be placed in the heap memory space. In such languages, a pointer needs to be kept to those thusly allocated instances, if we later-on want to clean up the memory used by them (when they're no longer needed). Continuing on the above example, a pointer referencing an instance that has been created on the heap using "new" can later on be "removed" from memory by using the "delete" command and passing it the pointer as parameter.

Thus, for such languages, a memory leak usually means having data placed on the heap and subsequentlly either:

  • arriving into a state where there's no longer a pointer to that data or
  • forgetting/ignoring to manually "de-allocate" that on-the-heap data (via it's pointer)

Now, in the context of such a definition of "memory leak" this can pretty much never happend with Java. Technically, in Java it's the Garbage Collector's task to decide when heap-allocated instances are no longer referenced or fall out of scope and clean them up. There's no such equivalent of the C++ "delete" command in Java that would even allow the developer to manually "de-allocate" instances/data from the heap. Even making all the pointers of an instance null will not immediatelly free up that instance's memory, but instead it will only make it "garbage collectable" leaving it to the Garbage Collector thread(s) to clean it up when it makes its sweeps.

Now, one other thing that can happen in Java is to never let go of pointers to certain instances, even though they will no longer be needed after a given point. Or, to give certain instance a scope that's too big for what they are used. This way, they will hang around in memory longer than needed (or forever, where forever means until the JDK process is killed) and thus not have them collected by the Garbage Collector even though from a functional stand-point they should be cleaned up. This can lead to behaviour similar to a "memory leak" in the broader sense where "memory leak" simply stands for "having stuff in memory when it's no longer needed and having no way to clean it up".

Now, as you can see, "memory leak" is somewhat vague, but from what I can see, your example doesn't contain a memory leak (even the version where you don't make l=null). All your variables are in a tight scope as delimited by the accolade block, they are used inside that block and will fall out of scope when the block ends, thus they'll be Garbage Collected "properly" (from the functional stand-point of your program). As @Keppil states: making the pointer null will give the GC a better hint as to when to clean up it's corresponding instance, but even if you never make it null, your code will not (un-necessarely) hang on to instances, so no memory leak there.

A typical example of Java memory leak is when having code deployed into a Java EE application server, such that it will spawn threads outside the control of said application server (imaging a servlet that starts a Quartz job). If the application is deployed and undeployed multiple times, it's possible that some of the threads will not be killed at undeploy time, but also (re) started at deploy time, thus leaving them and any instances they might have created hang uselessly in memory.

(*) The later versions of Objective-C also give the possibility to have heap memory managed automatically, in a fashion similar to Javas Garbage Collection mechanism.

The real answer is that unless the code is JIT'd all local variables are 'reachable' within the method body.

Morealso, curly brackets do absolutely nothing in the bytecode . They exist only in the source level - JVM is absolutely unaware of them. Setting l to null effectively frees the reference up off the stack, so it's GC'd for real. Happy stuff.

If you used another method instead of an inline block everything would have passed w/o any surprises.

If the code is JIT'd and the JVM compiler has built reaching-definitions (also this) most likely setting l=null would have no effect and memory be freed in either case.

Question: In case of removing l = null; (do not have this line of code), is this a memory leak?

No, but it facilitates the gc in claiming the memory if you do this "pattern"

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM