As many of you may already know, garbage collection policies affect system stability and throughput. From Java5 and above, "generational garbage collection" policy was introduced with the motivation that most objects are temporary and die young, and need to be collected more frequently than long lived objects. However, simply setting JVM gcpolicy argument to gencon may not be sufficient. Recently I was investigating policy settings for some apps where the heap usage graph was a nice ramp, growing up to 900MB and scaling down to 300 MB; no issues related to performance, stability, etc., but the graph was little concerning. The clip off/shrinkage always kicked at around 900MB (red plot) on a 1GB heap max; what if a large request was received at that time; will the system crash or is this an expected gencon behavior. All these intriguing questions made me look deep into this generational collection policy.
Gencon policy, splits your VM in two regions,
- Nursery (aka young region) where new and ALL objects are created (except when new object cannot find an allocation in this region)
- Nursery is again split into 2 spaces, allocate space and survivor space. The allocation space being larger than survivor space.
- Tenure (aka old generation) where little older objects that are still referenced by your application and have survived from nursery, gets moved to.
Garbage collection is more frequent in young region and are of short duration. Most objects do not survive and if they do, they get moved into survivor space and subsequently into tenure space after they survived multiple collections. So far so good, but then the following questions come across. How large should nursery be? What should be the ratio between allocation, and survivor space? Will the tenure size be fixed or vary? What will be the GC overhead? Without thinking through these, setting gcpolicy to gencon may produce undesirable effect, especially if your application attracts high volume.
Back to our chart above, it is clear the gc policy was set without taking other factors into consideration. For a 1GB heap, by default young generation is sized at 64M and old generation to 960M and as a result large objects that cannot get into young region were allocated to old generation and this region has less frequent garbage collection explaining the slow ramp growth upto 900+MB. But this still does not answer the clip off exactly at 950MB each time. Looking into further, let us check heap expansion and shrinkage, a normal JVM occurrence, except that under this gc policy they need to meet some conditions. To shrink heap space, occupancy should be under 40% and heap has not been recently expanded and the object exists in the area being shrunk. The only explanation that fits the above chart is our usage level is less than 40% of total heap (evident from lower trough at 380MB).
To conclude, gencon could handle a higher "rate of garbage collection", provide a shorter longest garbage collection cycle, and less frequent long garbage collection cycle. On the downside, It also means a net increase in memory usage due to multiple regions and if your GC overhead is > 10%, you have likely chosen the wrong one.
No comments:
Post a Comment