What's the Difference of Proceeding Non-strong References Between ZGC and G1

Per Liden per.liden at oracle.com
Tue Aug 3 10:20:11 UTC 2021


On 7/9/21 6:11 AM, Jack Ling wrote:
> Dear ZGC experts,
> Recently we compared ZGC and G1 on JDK 11 for our application and
> found one big difference for reference proceeding between the two
> difference GC types.
> We captured the JFR (Java Flight Recorder) during the AB test, and
> observed from JDK reference statistics that using ZGC would proceed
> much more Non-strong references than G1 during each GC phase.
> (Thousands of weak references and hundreds of soft references were
> proceeded in ZGC but only a few in G1). Any difference between ZGC and
> G1 to handle those non-strong references?

I suspect that's just an effect of G1 being generational and ZGC is not. 
In other words, when G1 is collecting it's young generation it only 
finds a few of the References since most of them live in the old generation.

> Another question is that from GC log, the heap size after GC using ZGC
> was much higher than G1 which was not reasonable to me, as far as I
> know, ZGC does not have old (survivor) space so it should collect more
> garbage and the heap size after GC should be lower than G1 GC. Any
> explanation why we observed the opposite GC logs like below. (The GC
> logs captured under the exactly same workload for each application
> instance).
> ZGC: GC(1721) Garbage Collection (Allocation Rate) 2066M(42%)->834M(17%)
> G1: GC(864) Pause Young (Normal) (G1 Evacuation Pause) 3286M->342M(4916M)

One difference between ZGC and G1 is that with ZGC the application 
continues to run and can allocate new objects while the GC is running. I 
suspect that's what you are seeing here. If you enable more detailed 
logging (-Xlog:gc*) you will see this in more details. For example:

[6.583s][info ][gc,heap     ] GC(2)                Mark Start 
Mark End        Relocate Start      Relocate End           High 
[6.583s][info ][gc,heap     ] GC(2)  Capacity:     2014M (39%) 
2014M (39%)        2014M (39%)        2014M (39%)        2014M (39%) 
     2014M (39%)
[6.583s][info ][gc,heap     ] GC(2)      Free:     3826M (75%) 
3310M (65%)        3172M (62%)        3218M (63%)        3826M (75%) 
     3124M (61%)
[6.583s][info ][gc,heap     ] GC(2)      Used:     1294M (25%) 
1810M (35%)        1948M (38%)        1902M (37%)        1996M (39%) 
     1294M (25%)
[6.583s][info ][gc,heap     ] GC(2)      Live:         - 
960M (19%)         960M (19%)         960M (19%)            - 
[6.583s][info ][gc,heap     ] GC(2) Allocated:         - 
516M (10%)         654M (13%)         771M (15%)            - 
[6.583s][info ][gc,heap     ] GC(2)   Garbage:         - 
333M (7%)          333M (7%)          169M (3%)             - 
[6.583s][info ][gc,heap     ] GC(2) Reclaimed:         - 
   -                 0M (0%)          163M (3%)             - 

See the "Allocated:" line. This shows you how much memory the 
application has allocated since the GC started. In this example, it 
allocated an additional 771M while it GC was running. Anything allocated 
while the GC cycle is in progress will be considered live and will be 
eligible for garbage collection in the next cycle.


> JVM parameters:
> ZGC, -XX:+UnlockExperimentalVMOptions -XX:+UseZGC
> -XX:ZAllocationSpikeTolerance=5 (We set this parameter for more
> aggressive GC operations to reserve memory for upcoming peak workload
> in production, not sure if it's recommended)
> G1, -XX:+UseG1GC -XX:MaxGCPauseMillis=100
> Thank you for the help!
> Best Regards!
> Jack

More information about the zgc-dev mailing list