Garbage Collection on a Local Variable

Garbage collection on a local variable

It will be garbage collected at some point after it's no longer used. I believe in current implementations of Java it will actually persist until the end of the method, whereas the garbage collector in .NET is more aggressive. (I don't know whether there are any guarantees even in Java. Normally you'd only want the local variable to persist beyond its last possible read when you're debugging.)

But no, you don't need to set the variable to null, and doing so would harm readability.

It's unlikely that the object will garbage collected immediately after the method exits; it's up to when the GC runs... and of course if anything else holds onto a reference to the object, it may not be eligible for garbage collection anyway. Don't forget that the value of the variable is just a reference, not the object itself. (That may take a while to get used to coming from C++.)

What destroys the local variable in java?

The garbage collector - the Reaper, as it's sometimes known - runs on its own schedule, and collects objects which are out of reference.
The local variables, of course, cannot be referenced after the method exits, because they are out of scope, so to your program they are dead*, but they still exist on the heap until the GC runs.

Under normal circumstances (and most abnormal ones) you do not need to tell the Reaper when to do its work. It will come, silently, when it is needed, and carry away those things which are no longer needed. This is one of the major advantages of working in a high-level language: it's safe to assume that you never need to think about things like managing deallocation of dead objects. You can just toss them over your shoulder and know that they will never bother you. I suppose there are some high-performance, high-demand applications that need to fiddle with the GC, but that's an optimization which should always be presumed premature unless you have really good evidence to the contrary.

*except, of course, for local variables which are returned to the calling function, which may become local variables in that scope and gain a little more lease on life. In general, the rule is: if some part of your code still cares about the variable, it will not be garbage collected, and if no part of your program cares about it, then you don't need to think about it.

Is Method local variable will garbage collected if stored in static storage?

As long as any references exist pointing to these objects, they won't be garbage collected. In your case you have static variable connectionPool pointing to map, holding these objects. If you remove these objects from the above mentioned map or assign another map to connectionPool without these objects and will not have any other references to these objects, than yes, they will be collected.

Does JVM garbage collect objects being referenced by local variables which are no longer used?

As elaborated in Can java finalize an object when it is still in scope?, local variables do not prevent the garbage collection of referenced objects. Or, as this answer puts it, scope is a only a language concept, irrelevant to the garbage collector.

I’ll cite the relevant part of the specification, JLS §12.6.1 again:

A reachable object is any object that can be accessed in any potential continuing computation from any live thread.

Further, I extended the answer’s example to

class A {
static volatile boolean finalized;

Object b = new Object() {
@Override protected void finalize() {
System.out.println(this + " was finalized!");
finalized = true;
}
@Override public String toString() {
return "B@"+Integer.toHexString(hashCode());
}
};
@Override protected void finalize() {
System.out.println(this + " was finalized!");
}

@Override public String toString() {
return super.toString() + " with "+b;
}

public static void main(String[] args) {
A a = new A();
System.out.println("Created " + a);
for(int i = 0; !finalized; i++) {
if (i % 1_000_000 == 0)
System.gc();
}
System.out.println("finalized");
}
}
Created A@59a6e353 with B@6aaa5eb0
B@6aaa5eb0 was finalized!
finalized
A@59a6e353 with B@6aaa5eb0 was finalized!

which demonstrates that even the method with the variable in scope may detect the finalization of the referenced object. Further, being referenced from a heap variable doesn’t necessarily prevent the garbage collection either, as the B object is unreachable, as no continuing computation can access it when the object containing the reference is unreachable too.


It’s worth emphasizing that even using the object does not always prevent its garbage collection. What matters, is whether the object’s memory is needed for the ongoing operation(s) and not every access to an object’s field in source code has to lead to an actual memory access at runtime. The specification states:

Optimizing transformations of a program can be designed that reduce the number of objects that are reachable to be less than those which would naively be considered reachable. […]

Another example of this occurs if the values in an object's fields are stored in registers. The program may then access the registers instead of the object, and never access the object again. This would imply that the object is garbage.

This is not only a theoretical option. As discussed in finalize() called on strongly reachable object in Java 8, it may even happen to objects while a method is invoked on them, or in other words, the this reference may get garbage collected while an instance method is still executing.

The only ways to prevent an objects garbage collection for sure, are synchronization on the object if the finalizer also does synchronization on the object or calling Reference.reachabilityFence(object), a method added in Java 9. The late addition of the fence method demonstrates the impact of the optimizers getting better from version to version on the issue of earlier-than-wanted garbage collection. Of course, the preferred solution is to write code that does not depend on the time of garbage collection at all.

local variables in an event Handling method and garbage collection

It does get released, just not right away. Garbage collection occurs when

The system has low physical memory. This is detected by either the low memory notification from the OS or low memory as indicated by the host.

The memory that is used by allocated objects on the managed heap surpasses an acceptable threshold. This threshold is continuously adjusted as the process runs.

The GC.Collect method is called. In almost all cases, you do not have to call this method, because the garbage collector runs continuously. This method is primarily used for unique situations and testing.

This means that you can't be certain when SomeClass is getting freed unless you call for the collection yourself.

Source: https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals

Garbage collection when using multiple local variables with the same name

Normally, I'd expect that hello would be marked for garbage collection
as soon as it is overwritten by world.

hello is not overwritten by world. It is just no longer referenced by v

a = nil will mark the function value for garbage collection as it removes the only reference to it.

With that function value gone nothing refers to "hello" anymore, so it will also be collected.

Java garbage collection in multithreaded application for local variable

tl;dr

You asked:

is it possible that old object can be garbage collected before Thread-2 finish searching in it?

No, the old Set object does not become garbage while some thread is still using it.

An object only becomes a candidate for garbage-collection after each and every reference to said object (a) goes out of scope, (b) is set to null, or (c) is a weak reference.

No, an object in use within a method will not be garbage-collected

In Java, an object reference assignment is atomic, as discussed in another Question. When this.accountIds is directed to point to a new Set object, that happens in one logical operation. That means that any other code in any other thread accessing the accountIds member field will always successfully access either the old Set object or the new Set object, always one or the other.

If during that re-assignment another thread accessed the old Set object, that other thread's code is working with a copy of the object reference. You can think of your doesAccountExist method:

 public boolean doesAccountExist(String accountId) {
return accountIds.contains(accountId);
}

…as having a local variable with a copy of the object reference, as if written like this:

 public boolean doesAccountExist(String accountId) {
Set<String> set = this.accountIds ;
return set.contains(accountId);
}

While one thread is replacing the reference to a new Set on the member field accountIds, the doesAccountExist method already has a copy of the reference to the old Set. At that moment, while one thread is changing the member field reference, and another thread has a local reference, the garbage collector sees both the new and old Set objects as having (at least) one reference each. So neither is a candidate for being garbage-collected.

Actually, more technically correct would be explaining that at the point in your line return accountIds.contains(accountId); where execution reaches the accountIds portion, the current (old) Set will be accessed. A moment later the contains method begins its work, during which re-assigning a new Set to that member field has no effect on this method's work-in-progress already using the old Set.

This means that even after the new Set has been assigned in one thread, the other thread may still be continuing its work of searching the old Set. This may or may not be a problem depending on the business context of your app. But your Question did not address this stale-data transactional aspect.

So regarding your question:

is it possible that old object can be garbage collected before Thread-2 finish searching in it?

No, the old Set object does not become garbage while some thread is still using it.

Other issues

Your code does have other issues.

Visibility

You declared your member field as private Set<String> accountIds;. If you access that member field across threads on a host machine with multiple cores, then you have a visibility problem. The caches on each core may not be refreshed immediately when you assign a different object to that member field. As currently written, it is entirely possible that one thread accessing this.accountIds will gain access to the old Set object even after that variable was assigned the new Set object.

If you do not already know about the issues I mention, study up on concurrency. There is more involved than I can cover here. Learn about the Java Memory Model. And I strongly recommend reading and re-reading the classic book, Java Concurrency in Practice by Brian Goetz, et al.

volatile

One solution is to mark the member field as volatile. So, this:

private Set<String> accountIds;

…becomes this:

volatile private Set<String> accountIds;

Marking as volatile avoids a stale cache on a CPU core pointing to the old object reference rather than the new object reference.

AtomicReference

Another solution is using an object of AtomicReference class as the member field. I would mark it final so that one and only one such object is ever assigned to that member field, so the field is a constant rather than a variable. Then assign each new Set object as the payload contained within that AtomicReference object. Code wanting the current Set object calls a getter method on that AtomicReference object. This call is guaranteed to be thread-safe, eliminating the need for volatile.

Concurrent access to existing Set

Another possible problem with your code might be concurrent access to an existing Set. If you have more than one thread accessing the existing Set, then you must protect that resource.

One way to protect access to that Set is to use a thread-safe implementation of Set such as ConcurrentSkipListSet.

From what you have showed in the Question, the only access to the existing Set that I noticed is calling contains. If you are never modifying the existing Set, then merely calling contains across multiple threads may be safe — I just don't know, you'd have to research it.

If you intend to never modify an existing Set, then you can enforce that by using an unmodifiable set. One way to produce an unmodifiable set is to construct and populate a regular set. Then feed that regular set to the method Set.copyOf. So your getAccountIds method would look like this:

 private Set<String> getAccountIds() {
Set<String> accounts = new HashSet<String>();
// calls Database and put list of accountIds into above set
return Set.copyOf( accounts );
}

Return a copy rather a reference

There are two easy ways to avoid dealing with concurrency:

  • Make the object immutable
  • Provide a copy of the object

As for the first way, immutability, the Java Collections Framework is generally very good but unfortunately lacks explicit mutability & immutability in its type system. The Set.of methods and Collections.unmodifiableSet both provide a Set that cannot be modified. But the type itself does not proclaim that fact. So we cannot ask the compiler to enforce a rule such as our AtomicReference only storing an immutable set. As an alternative, consider using a third-party collections with immutability as part of its type. Perhaps Eclipse Collections or Google Guava.

As for the second way, we can make a copy of our Set of account IDs whenever needing access. So we need a getCurrentAccountIds method that goes into the AtomicReference, retrieves the Set stored there, and called Set.copyOf to produce a new set of the same contained objects. This copy operation is not documented as being thread-safe. So we should mark the method synchronized to allow only one copy operation at a time. Bonus: We can mark this method public to give any calling programmer access to the Set of account IDs for their own perusal.

    synchronized public Set < UUID > getCurrentAccountIds ( )
{
return Set.copyOf( this.accountIdsRef.get() ); // Safest approach is to return a copy rather than original set.
}

Our convenience method doesAccountExist should call that same getCurrentAccountIds to obtain a copy of the set before doing its "contains" logic. This way we do not care whether or not the "contains" work is thread-safe.

Caveat: I am not satisfied with using Set.copyOf to as means to avoid any possible thread-safety issues. That method notes that if the passed collection being copied is already an unmodifiable set, then a copy may not be made. In real work I would use a Set implementation that guarantees thread-safety whether found bundled with Java or by adding a third-party library.

Do not use object within constructor

I do not like seeing the scheduled executor service appearing within your constructor. I see two issues there: (a) app lifecycle and (b) using an object within a constructor.

Creating the executor service, scheduling tasks on that service, and shutting down that service are all related to the lifecycle of the app. An object generally should not be aware of its lifecycle within the greater app. This account IDs provider object should know only how to do its job (provide IDs) but should not be responsible for putting itself to work. So your code is mixing responsibilities, which is generally a poor practice.

Another problem is that the executor service is being scheduled to immediately start using the very object that we are still constructing. Generally, the best practice is to not use an object while still under construction. You may get away with such use, but doing so is risky and is prone to leading to bugs. A constructor should be short and sweet, used just to validate inputs, establish required resources, and ensure the integrity of the object being birthed.

I did not pull the service out of your constructor only because I did not want to this Answer to go too far out into the weeds. However, I did make two adjustments. (a) I changed the initial delay on the scheduleWithFixedDelay call from zero to one second. This is a hack to give the constructor time to finish birthing the object before its first use. (b) I added the tearDown method to properly shutdown the executor service so its backing thread-pool does not continue running indefinitely in zombie fashion.

Tips

I suggest renaming your getAccountIds() method. The get wording in Java is usually associated with the JavaBeans convention of accessing an existing property. In your case you are generating an entirely new replacement set of values. So I would change that name to something like fetchFreshAccountIds.

Consider wrapping your scheduled task with a try-catch. Any Exception or Error bubbling up to reach the ScheduledExecutorService brings a silent halt to any further scheduling. See ScheduledExecutorService Exception handling.

Example code.

Here is a complete example of my take on your code.

Caveat: Use at your own risk. I am not a concurrency expert. This is meant as food-for-thought, not production use.

I used UUID as the data type of the account IDs to be more realistic and clear.

I changed some of your class & method names for clarity.

Notice which methods are private and which are public.

package work.basil.example;

import java.time.Duration;
import java.time.Instant;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

public class AccountIdsProvider
{
// Member fields
private AtomicReference < Set < UUID > > accountIdsRef;
private ScheduledExecutorService scheduledExecutorService;

// Constructor
public AccountIdsProvider ( )
{
this.accountIdsRef = new AtomicReference <>();
this.accountIdsRef.set( Set.of() ); // Initialize to empty set.
this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
scheduledExecutorService.scheduleWithFixedDelay( this :: replaceAccountIds , 1 , 1 , TimeUnit.SECONDS ); // I strongly suggest you move the executor service and the scheduling work to be outside this class, to be a different class’ responsibility.
}

// Performs database query to find currently relevant account IDs.
private void replaceAccountIds ( )
{
// Beware: Any uncaught Exception or Error bubbling up to the scheduled executor services halts the scheduler immediately and silently.
try
{
System.out.println( "Running replaceAccountIds. " + Instant.now() );
Set < UUID > freshAccountIds = this.fetchFreshAccountIds();
this.accountIdsRef.set( freshAccountIds );
System.out.println( "freshAccountIds = " + freshAccountIds + " at " + Instant.now() );
}
catch ( Throwable t )
{
t.printStackTrace();
}
}

// Task to be run by scheduled executor service.
private Set < UUID > fetchFreshAccountIds ( )
{
int limit = ThreadLocalRandom.current().nextInt( 0 , 4 );
HashSet < UUID > uuids = new HashSet <>();
for ( int i = 1 ; i <= limit ; i++ )
{
uuids.add( UUID.randomUUID() );
}
return Set.copyOf( uuids ); // Return unmodifiable set.
}

// Calling programmers can get a copy of the set of account IDs for their own perusal.
// Pass a copy rather than a reference for thread-safety.
// Synchronized in case copying the set is not thread-safe.
synchronized public Set < UUID > getCurrentAccountIds ( )
{
return Set.copyOf( this.accountIdsRef.get() ); // Safest approach is to return a copy rather than original set.
}

// Convenience method for calling programmers.
public boolean doesAccountExist ( UUID accountId )
{
return this.getCurrentAccountIds().contains( accountId );
}

// Destructor
public void tearDown ( )
{
// IMPORTANT: Always shut down your executor service. Otherwise the backing pool of threads may run indefinitely, like a zombie ‍.
if ( Objects.nonNull( this.scheduledExecutorService ) )
{
System.out.println( "INFO - Shutting down the scheduled executor service. " + Instant.now() );
this.scheduledExecutorService.shutdown(); // I strongly suggest you move the executor service and the scheduling work to be outside this class, to be a different class’ responsibility.
}
}

public static void main ( String[] args )
{
System.out.println( "INFO - Starting app. " + Instant.now() );
AccountIdsProvider app = new AccountIdsProvider();
try { Thread.sleep( Duration.ofSeconds( 10 ).toMillis() ); } catch ( InterruptedException e ) { e.printStackTrace(); }
app.tearDown();
System.out.println( "INFO - Ending app. " + Instant.now() );
}
}

Local variables and garbage generation

Consider this very simple example (where SomeObject is a class, not a struct):

class C
{
void MethodCalledMillionsOfTimes()
{
var so = new SomeObject();
// some use of so
}
}

Each time the method is called, one new object is created on the heap. It needs to be garbage collected some time after the use of it has ended. That is one object to collect per call of the method.

Then suppose it is changed into:

class C
{
SomeObject soField;

void MethodCalledMillionsOfTimes()
{
soField = new SomeObject();
// some use of soField
}
}

This changes nothing! You still create one new instance for each method call. The garbage collector has to do the same amount of work.

But what if you did:

class C
{
SomeObject soField = new SomeObject();

void MethodCalledMillionsOfTimes()
{
// some use of soField
}
}

This time the same object is re-used each time the method is called again on the same instance. So fewer objects need to be garbage collected (assuming the method is actually called many times on the same C object). Note that this can only work if the SomeObject instance can be used many times, and does not have its state "ruined" by each use.

If many threads call the method at once, be sure the object soField can handle that.

If you go further and say:

class C
{
static SomeObject soStaticField = new SomeObject();

void MethodCalledMillionsOfTimes()
{
// some use of soStaticField
}
}

then there's only one object shared between all instances of C. No SomeObject will have to be collected by the GC.

However, this GC activity is very often not important for your performance. So please measure if it is important or not in your case.



Related Topics



Leave a reply



Submit