I need some clarification about how minor gc collections behave. calling a() or calling b() in a long-lived application, if they could behave worstly when old space gets bigger
//an example instance lives all application life cycle 24x7
public class Example {
private Object longLived = new Object();
public void a(){
var shortLived = new ShortLivedObject(longLived); // longLived now is attribute
shortLived.doSomething();
}
public void b(){
new ShortLivedObject().doSomething(new Object()); // actually now is shortlived
}
}
Where does my doubt comes from? I found out that in an app in which the used tenured space gets bigger, there is an increase of minor gc pauses.
Making some tests I found out that if I force the jvm to use option a() and another jvm to use option b(), then the jvm with option b() has shorter pause duration time when the old space gets bigger but i can't figured out why.
I solved that issue in the app, using this property XX:ParGCCardsPerStrideChunk in 4096, but i want to know if situation which i described above can lead in increasing gctimes cause scanning in gccard tables is slower or something that i don't know or is not related at all.

Disclaimer: I am by far no GC expert, but lately getting into these details for fun.
As I said in the comments, you are using a collector that is deprecated, no one supports it and no one wants to use it, switch to
G1or even better IMHO switch toShenandoah: start from this simple thing first.I can only assume that you increased
ParGCCardsPerStrideChunkfrom its default value and that probably helped by a fewms(though we have no proof of that). We also have no logs from GC, CPU activity, logs, etc; thus this is pretty complicated to answer.If indeed you have a big heap (tens of GB) and a big young space and you have enough GC Threads, setting that parameter to a bigger value might help indeed and it might even have to do with
card tablethat you are mentioning. Read further why.CMSsplits the heap intoold spaceandyoung space, it could have chosen any other discriminator, but they choseage(just likeG1). Why is that needed? To be able to scan and collect only partial regions of the heap (scanning it entirely is very expensive).young spaceis collected with astop-the-worldpause, so it better be small, otherwise you will not be happy; that is why also why you usually will see many moreyoung collectionscompare toold ones.The only problem when you scan
young spaceis: what happens if there are references fromold spaceto objects fromyoung space? Collecting those is obviously wrong, but scanning the entireold spaceto find out that answer would defeat the purpose ofgenerational collectionsentirely. Thus:card table.This keeps track of reference from
old spacetoyoung spacereferences, so it knows what exactly is garbage or not.G1uses acard tabletoo, but also adds aRememberedSet(not going into the details here). In practice,RememberedSetsturned out to be HUGE, that is whyG1became generational. (FYI:Shenandoahusesmatrixinstead ofcard table- making it not generational).So this huge intro, was to show that indeed increasing
ParGCCardsPerStrideChunkmight have helped. You are giving each GC thread more space to work on. The default value is256and card table is512 bytes, that meansIf you for example have a heap of
32 GBhow many hundreds of thousands of strides is that? Probably too many.Now, why you also bring
reference countinginto the discussion here? I have no idea.The examples that you have shown have different semantics and as such are kind of difficult to reason about; I'll still try to, though. You have to understand that reachability of Objects is just a graph that starts from some roots (called
GC roots). Let's take this example first:ShortLivedObjectinstance is "forgotten" as soon asdoSomethingmethod invocation is done and its scope is within the method only, as such no one can reach it. Thus the remaining part is about the parameter ofdoSomething:new Object. IfdoSomethingdoes not do anything fishy with the parameter it got (making it reachable via aGC rootgraph), then afterdoSomethingis done, it would become eligible for GC too. But even ifdoSomethingmakesnew Objectreachable it still means thatShortLivedObjectinstance is eligible for GC.As such, even if
Exampleis reachable (means it can't be collected),ShortLivedObjectandnew Object()can potentially be collected. It can look like this:You can see that once
GCwill scanExampleinstance, it might not scanShortLivedObjectat all (that is why garbage is identified as the opposite of live objects). So a GC algorithm will simply discard the entire graph and not scan it at all.The second example is different:
The difference is that
longLivedhere is an instance field and, as such, the graph will look a bit different:It's obvious that
ShortLivedObjectcan be collected in this case, but notlongLived.What you have to understand that this does not matter at all, if
Exampleinstance can be collected; this graph will not be traversed and everything thatExampleuses can be collected.You should be able to understand now that using method
acan retain a bit more garbage and can potentially move it toold space(when they become old enough) and can potentially make youryoung pausesbe longer and indeed increasingParGCCardsPerStrideChunkmight help a bit; but this is highly speculative and you would need a pretty bad same pattern of allocations to happen for all of this to happen. Without logs, I highly doubt that.