JVM series 15 (object allocation considerations)


Reduce the distribution rate

This hardly needs to be explained. It reduces the amount of memory used, which naturally reduces the pressure on GC recovery, and reduces the memory fragmentation and CPU usage. When designing objects, you should carefully check and ask yourself:

  • Do I really need this object?
  • Is this field what I need?
  • Can I reduce the size of the array?
  • Are these objects only used in very few cases or only during initialization?
  • Do I allocate a lot of memory, but actually use only a small part of it?
  • Can I get the data from other places?

Shorten the life cycle of an object

There is a basic rule for high-performance programming with garbage collection, which is in fact a guiding rule for code design.

The objects to be collected are either in the new generation or they do not exist.
Collect objects in gen 0 or not at all.

Try to make an object have a very short life cycle, and it can be recycled immediately when it is in minor GC; or it should be quickly promoted to the old age and always keep references to long-lived objects. Usually, this also means that objects can be reused, especially in large object heaps.

The shorter the scope of an object, the less likely it will be promoted to the old age when the next GC appears.

Therefore, to ensure that the object exits the scope as early as possible, for local variables, the lifecycle can be ended after the last use, or even before the end of the method. You can use {} to include the code, which will not affect your operation, but the compiler will think that the object in this scope has completed its life cycle and is no longer used. If you need to call the method of an object, try to reduce the time interval between the first and the last time, so that GC can recycle the object as soon as possible.

If an object associates (references) some objects that have been kept for a long time, they need to be dereferenced. As a result, you may have more null checks (null judgments), which can complicate the code.

Reduce depth of object hierarchy / reduce references between objects

The JVM is supported byReachability analysis algorithmTo determine whether an object is alive or not. If the object level is very deep, or a large number of other objects are referenced, the JVM will spend a lot of time traversing the object when judging whether the object is alive. This is one of the reasons why GC makes a long time.

Another problem is that if you can’t easily determine how many references an object has, you can’t accurately predict the life cycle of an object. It is necessary to reduce this complexity, which can not only make the code more robust, but also facilitate debugging and get better performance.

In addition, it should be noted that references between different generations of objects can also lead to inefficiency of GC, especially the reference of old objects to new objects. For example, if the old generation object has a reference relationship in the new generation object, then every time a new generation GC occurs, some of the old age objects need to be scanned to see if they still remain on the reference of the new generation object. Although GC should try to avoid this situation, it should not work at once.

Avoid large objects

The processing logic of the JVM for large objects is directly allocated in the older generation. The purpose of this is to avoid a large number of memory replication between the Eden area and the two survivor regions.

In general, the common large objects in our code refer to long strings and arrays, which should be avoided when writing programs. When large objects often appear, it is easy to trigger garbage collection in advance to obtain enough memory space to “place” them.