Generations in JVM

on waitingforcode.com

Generations in JVM

You're still doing Java/C#/JavaScript/Python/PHP... and need a wind of change? I was like that 4 years ago. I changed then to the data engineering field and it solved my existential problems :) If you want to follow my path, I prepared a course that will help you with that! Join the class!
Generational garbage collection is one of strategies used to automatically remove useless objects from memory. JVM has 2 categories of generations: young and old.

In this article we'll discover these 2 generations - in 2 first parts. We'll also approach one of concepts related to them, stop-the-world event.

What is young generation ?

Every initialized object goes to JVM heap, and more exactly, to eden space located in young generation. Now, the first garbage collection on this generation occurs. Some of objects aren't reachable anymore, so they are directly removed and remaining free space between them is compacted. The rest of objects, still referenced, moves to a subpart called survivor space. This kind of collection is called minor and it causes a stop-the-world event. However, it influences latency less than the same event triggered from old generation. Minor collection is triggered when, for example, eden space is full and new object can't be created (allocation failure).

The 'age' of objects surviving to collection is increased every time when GC runs. If this age reaches a specific value, related object can be promoted to old generation. Meanwhile, the object continues to be a part of young generation. However, it lives not anymore in eden space but in one of 2 survivor spaces: from or to. One of them is always empty. It means that survived objects from survivor and eden space moves together between from and to spaces. Only when one of moving objects is considered as 'old enough', it's promoted to old generation.

But why one survivor space is always empty ? The principle hidden behind this comes from Copying Garbage Collection. It helps to avoid fragmentation work because it copies all still reachable objects to an empty memory space. If it was a simple removal, GC should in additionally compact memory space, so eliminate holes caused by removed objects. By doing a copy, removal holes are automatically compacted.

What is old generation ?

Old generation is also known as tenured generation, so don't be surprise if you meet this term elsewhere. As young generation keeps young objects, old generation is supposed to keep only old ones. And it's true. But which is the criterion of age classification in Java memory ? This criterion is called tenuring threshold and means the number of times when given objects survived garbage collection. So if one object, placed initially in young generation, survived to 4 last garbage actions, its age is 4. Suppose also that we configured the minimum age of old generations objects to 5. If 4 "survivals" old object survives to another one collection, it's promoted to old generation.

After this short story we can tell that old generation stores objects which are supposed to live very long time (because they survive consecutive young generation collections). The size of old generation is usually bigger than for young generation. Logically, it involves longer collection process. This collection process is called major collection and makes an event called "stop-the-world event". Unlike the same event in minor collection, this one usually takes more time but is executed rarely (old generation size is bigger).

Stop-the-world event

Garbage collections can have small and big impact on the JVM. As you already know, collections with small impact are called minor while with the big one, major. Major collections cause an important stop-the-world (STW). Wut why it's called so ? This names from the fact that everything is interrupted. When this event occurs, all running threads are stopped. Only GC thread is running. When collection process ends, stopped threads restart.

Execution time of major collection will depend on the old generation size. The collection consists on deleting all unreachable objects and compacting space by moving still living objects at the begin of the generation. This collection doesn't occur frequently because old objects are supposed to be long-lived.

Playing with generations

After this introduction, the time is to play with old generation. In this little game, we'll try to observe the behaviour of GC when some of old generation flags are defined. First, we'll try to put all created objects to old generation every time with -XX:+AlwaysTenure parameter. Next, we'll compare execution time with the same code doesn't contain AlwaysTenure flag. That is the code used for tests:

public class OldGenerationRun {
  public static void main(String[] args) {
    long expectedEnd = System.currentTimeMillis() + 5000;
    Map<Integer, List<Integer>> container = new HashMap<>();
    for (int j = 0; j < 200_000; j++) {
      container.put(j, new ArrayList<>());
    }
    int i = 0;
    while (System.currentTimeMillis() < expectedEnd) {
      if (i == 20_000) {
        i = 0;
      }
      container.put(i, new ArrayList<>());
      for (int j = 0; j < 20_000; j++) {
        container.get(i).add(j);
      }
      i++;
    }
  }
}

Version Observations
No flags
  • number of garbage collections: 12
  • old generation size from 86016kb to 515584kb (~ 6 times growth)
  • reasons of GC: allocation failure (9), ergonomics (3)
  • allocation failure GC times (in seconds): 0.030, 0.036, 0.057, 0.036, 0.058, 0.06, 0.123, 0.138, 0.155
  • ergonomics GC times (in seconds): 1.13, 0.98, 2.63
  • young generation almost always fully used when GC (10 of 12 collections times)
  • old generation never approached to full occupancy
show GC logs
-XX:+AlwaysTenure
  • number of garbage collections: 10
  • reasons of GC: allocation failure (7), ergonomics (3)
  • old generation size growth from 86016kb up to 477184kb (5.5 times)
  • survivor's from and to spaces decreased from 5120kb to 512kb
  • total young + old generations size before and after: 123904kb and 613376kb (~ 5 times)
  • allocation failure GC times (in seconds): 0.055, 0.056, 0.092, 0.063, 0.115, 0.125, 0.113
  • ergonomics GC times (in seconds): 1.28, 1.68, 2.77
  • old generation resized after the 4th GC
  • young generation resized 5 times (every 2nd GC)
show GC logs
-XX:+NeverTenure
  • number of garbage collections: 12
  • reasons of GC: allocation failure (9), ergonomics (3)
  • old generation size growth from 86016kb up to 503808kb (5.8 times)
  • survivor's from and to spaces increased, from 5120kb to, respectively, 114688kb (from space) and 148992kb (to space)
  • total young + old generations size before and after: 123904kb and 764416kb (~ 6.1 times)
  • allocation failure GC times (in seconds): 0.033, 0.036, 0.055, 0.036, 0.060, 0.068, 0.124, 0.128, 0.169
  • ergonomics GC times (in seconds): 1.00, 1.02, 2.37
  • old generation resized 3 times
  • young generation resized 8 times
show GC logs
-XX:NewRatio=5
  • old generation configured to be 5 times bigger than young one
  • number of garbage collections: 16
  • reasons of GC: allocation failure (12), ergonomics (4)
  • old generation size growth from 107520kb up to 539648kb (~ 5 times)
  • survivor's from and to spaces increased, from 2560kb to, respectively, 107008kb (from space) and 113152kb (to space)
  • total young + old generations size before and after: 123904kb and 759808kb (~ 6.1 times)
  • allocation failure GC times (in seconds): 0.017, 0.019, 0.03, 0.03, 0.05, 0.03, 0.07, 0.09, 0.13, 0.15, 0.13, 0.17
  • ergonomics GC times (in seconds): 1,27, 0.82, 1.25, 2.28
  • young generation resized 10 times
  • old generation resized 4 times - more space allocated at the begin, so no need to reallocate it further
show GC logs
-XX:SurvivorRatio=11
  • total number of GC: 11
  • Eden space is 11 times bigger than From and To spaces
  • old generation's growth from 86016kb up to 585728kb (6 times)
  • fixed size of from and to spaces, always 62-99% occupancy (after the 1st GC) - only one time is equal to 0 because of full GC ergonomics causing heap resizing (from space passed from 3072kb to 51200kb) and promotion objects from survivor space to old generation
  • Eden is full 72% of time (8/11 collections). It's empty when all objects are promoted to old generation
  • reasons of GC: allocation failure (8 times), ergonomics (3)
  • allocation failure GC times (in seconds): 0.032, 0.037, 0.056, 0.063, 0.066, 0.075, 0.153, 0.139
  • ergonomics GC times (in seconds): 1.12, 1.12, 2.54
  • old generation resized 5 times
show GC logs

To resume the experiment, we can freely tell that JVM heap is always adapted to existent situation. For example, in the case of -XX:+AlwaysTenure flag we saw that unused survivor spaces decreased 10 times. In the other side, old generation grew 5 times because it accepted more and more objects. We can also observe that full ergonomic GC takes longer than allocation failure GC. And the time needed to execute ergonomic GC is always bigger. In the case of -XX:NewRatio=5 flag GC took approximately 6 seconds to execute ergonomic GC. Also the number of GC was the biggest in this case (16 against 12, 11 and 10).

This article introduces the idea of generations inside Java memory. The first part describes short-lived objects, placed inside young generation. The second part treats about old generation and objects supposed to live much more longer. The third part explains the meaning of stop-the-world event while the last one shows some tuning options and their influence on sizes of described generations.

Share on: