Three
basic operations performed by garbage collectors are - finding unused objects,
making their memory available, and compacting the heap. Different collectors
take different approaches to these operations, which is why they yield
different performance characteristics.
It
is simpler to perform these operations if no application threads are running
while the garbage collector is running. Java programs are typically heavily
multithreaded, and the garbage collector itself often runs multiple threads. So
there are two logical groups of threads: those performing application logic,
and those performing GC.
The
pauses when all application threads are stopped are called stop-the-world
pauses. These pauses generally have the greatest impact on the performance of
an application, and minimizing those pauses is the key consideration when
tuning GC.
With
little bit of difference in their implementation almost all garbage collectors
work by splitting the heap into different generations. They are broadly divided
into two generations – old and young generation. The young generation is
further divided into eden and the survivor spaces. Java garbage collector is
designed to take advantage of the fact that most of the objects in the memory
have very short life. This is where the generational design comes in. Objects
are first created in the young generation which is some subset of the entire
heap. When the young generation fills up, the garbage collector will stop the application
threads and empty out the young generation by either discarding or moving the
objects elsewhere. This operation of cleaning up the young generation is called
minor GC.
There
are two advantages of this design. First, as the young generation is only a
portion of the entire heap, processing it is faster than processing the entire
heap. This results in shorter pause of the application thread but the downside
is that the application threads will be stopped more frequently. But it is
always a big advantage to have shorter pauses even though they will be more
frequent. The second advantage is that when the young generation is cleared
during the GC all objects in eden are either moved or discarded; all live
objects are moved to either one of the survivor spaces or to the old
generation. Since all objects are moved, the young generation is automatically
compacted when it is collected. All GC algorithms have stop the world pauses
during the collection of young generation.
As
objects are moved from young generation to old generation eventually it will
also fill up and the JVM will need to find the objects that are no longer in
use and discard them from the old generation. This is where the GC algorithms
have their biggest differences. The simpler algorithms stop all
application threads, find the unused objects and free their memory, and then
compact the heap. This process is called a full GC, and it generally causes a long
pause for the application threads.
It is also possible to find unused objects while application
threads are running; CMS and G1 both take that approach. Because the phase
where they scan for unused objects can occur without stopping application
threads, CMS and G1 are called concurrent collectors. They are also called
low-pause (and sometimes—incorrectly—pauseless)
collectors, since they minimize the need to stop all the application threads.
Concurrent collectors also take different approaches to compacting the old
generation. With CMS or G1 collector, an application will typically experience
fewer and much shorter pauses. The trade-off is that the application will use
more CPU overall.
Different
GCs suit for different application needs. There are trade-offs in every
situation. In an application such as a Java EE server measuring the response
time of individual requests, consider these points:
The individual requests will be impacted by
pause times—and more importantly by long pause times
for full GCs. If minimizing the effect of pauses on response times is the goal,
a concurrent collector will be more appropriate. If the average response time
is more important than the outliers (i.e., the 90th% response time), the
throughput collector will usually yield better results. The benefit of avoiding
long pause times with a concurrent collector comes at the expense of extra CPU
usage. Similarly, the choice of garbage collector in a batch application is
guided by the following trade-off: If enough CPU is available, using the concurrent
collector to avoid full GC pauses will allow the job to finish faster. If CPU
is limited, then the extra CPU consumption of the concurrent collector will cause
the batch job to take more time.
No comments:
Post a Comment