The garbage collectors from OpenJDK that we have seen so far (Serial and Parallel, CMS and G1, ZGC) were aimed at the fastest and most efficient garbage collection possible, for which they used techniques of varying degrees of complexity and ingenuity. This is quite expected, because, based on the name, the fight against garbage is their main duty.
But today we have an assembler under consideration, which is out of the picture. Its analysis will be short, but useful, as it will allow you to look at one aspect of the work of assemblers that has not been considered before. Let’s take a break from the complicated technical tricks and deal with Epsilon GC, the simplest builder that comes with OpenJDK.
The approach used by Epsilon GC is described briefly – it does not collect garbage at all, but simply terminates the application as soon as it tries to allocate more memory than it is allowed (greater than the Xmx value).
As can be understood from the above description, it is not very difficult to implement such a collector. Its stable version was added back in JDK 11, but it still formally has experimental status. It was decided to leave this status behind him forever, so that to enable it, it was required to specify an additional option, which once again attracts attention and reminds that the application uses something unusual.
Therefore, to enable Epsilon GC, you must specify two options at once:
Epsilon GC does not have garbage collection, but this does not mean that there is absolutely nothing to write about the principles of its operation, because collectors affect not only how garbage is removed, but also how memory is allocated for new objects. So far, in our discussion of garbage collectors, we have only scratched the surface of this issue. Let’s take a moment and look at it a little more closely.
The JVM uses TLABs (thread-local allocation buffers), that is, relatively small buffers of memory that individual threads request on the heap and then use to allocate objects created by those threads without competing for heap access until a new buffer is needed. This approach can significantly speed up the process of creating new objects. For the so-called huge objects (humongous objects) that do not fit in the buffer, memory blocks are requested in the heap specifically for them.
When a thread fails to get the next buffer from the heap due to exhaustion of its resources, other collectors can run a build cycle and try to free up the missing space, and Epsilon GC simply throws an OutOfMemoryError and terminates the process.
Since Epslion GC has only TLAB requests of the right size left of its duties, almost all settings revolve around them:
-XX:EpsilonTLABElasticity=elasticity allow you to control the elasticity mode of TLABs, that is, dynamically change their sizes separately for each thread, depending on its memory appetites.
-XX:EpsilonTLABDecayTime=decay_time (in ms) develop the idea of elasticity, allowing you to periodically reset the size of TLABs to the original one, so that, for example, the start period of an application with active memory allocation could not greatly affect subsequent allocations.
-XX:EpsilonMaxTLABSize=size you can limit the size of TLABs from above.
-XX:EpsilonMinHeapExpand=size specifies the minimum size by which the JVM increases the size of the heap when it needs to expand.
Advantages and disadvantages
At first glance, the idea of such a collector may seem strange, but it has its own use cases, because the rejection of garbage collection means the rejection of large overheads for careful accounting of objects, for their separation by region, for additional collector threads, for access barriers to objects and other mechanisms that are used by other collectors.
But the other side of this coin should also be kept in mind. Just because garbage isn’t collected doesn’t mean it doesn’t exist. It usually exists and leads to defragmentation of the memory occupied by living objects, which can potentially affect the speed of access to them.
So when does the benefit of Epsilon GC outweigh the fact that your program won’t try to collect garbage, but will just exit as soon as it creates a large enough number of objects?
Firstly, it can be used by applications that create all the objects they need at startup, and after that do not litter at all. In this case, both the initial allocation of memory and further work can be faster.
Secondly, it is suitable for short-lived applications, which themselves terminate faster than they can take up all the memory they are allowed to. For example, some console utility for which you can accurately determine the maximum amount of memory, taking into account all the garbage generated.
Thirdly, it can be used for the purpose of analyzing the overheads that other collectors bring specifically to your application. Just run your program with Epsilon GC and collect performance and resource usage metrics while it is running. And then you compare them with similar metrics when using your target collector, getting a rough idea of \u200b\u200bhow much it costs you. This use case is also suitable for the developers of the JVM and other garbage collectors.
That is, the collector is not so useless, it has its own niche and its existence is worth remembering.
← Part 4 – The ZGC Collector
← Part 3 – CMS and G1 Builders