RFR(M): 8211425: Allocation of old generation of java heap on alternate memory devices - G1 GC

Stefan Johansson stefan.johansson at oracle.com
Fri Oct 26 14:32:02 UTC 2018


Hi Kishor,

I'll start with a few general observations and then there are some 
specific code comments below.

I think the general design looks promising, not having to set a fixed 
limit for what can end up on NV-dimm is great. Having a separate 
HeapRegionManager looks like a good abstraction and when adding larger 
features like this that is really important.

I also agree with Sangheon that you should do more testing, both with 
and without the featured turned on. I also recommend you to build with 
pre-compiled headers disabled, to find places where includes have been 
forgotten. To configure such build add --disable-precompiled-headers to 
your configure call.

On 2018-10-04 04:08, Kharbas, Kishor wrote:
> Hi all,
> 
> Requesting review of the patch for allocating old generation of g1 gc on 
> alternate memory devices such nv-dimm.
> 
> The design of this implementation is explained on the bug page - 
> https://bugs.openjdk.java.net/browse/JDK-8211425
> 
> Please follow the parent issue here : 
> https://bugs.openjdk.java.net/browse/JDK-8202286.
> 
> Webrev: http://cr.openjdk.java.net/~kkharbas/8211425/webrev.00 
> <http://cr.openjdk.java.net/%7Ekkharbas/8211425/webrev.00>
src/hotspot/share/gc/g1/g1Allocator.inline.hpp
----------------------------------------------
100   size_t length = static_cast 
<G1CollectedHeap*>(Universe::heap())->max_reserved_capacity();

You can avoid the cast by using G1CollectedHeap::heap() instead of 
Universe::heap().
---

src/hotspot/share/gc/g1/g1CollectedHeap.cpp
-------------------------------------------
1642   if (AllocateOldGenAt != NULL) {
1643     _is_hetero_heap = true;
1644     max_byte_size *= 2;
1645   }

I would like this to be abstracted out of G1CollectedHeap, not entirely 
sure how but I'm thinking something like adding a 
G1HeteroCollectorPolicy which answers correctly on how much memory needs 
to be reserved and how big the heap actually is.

1668     G1RegionToSpaceMapper::create_heap_mapper(g1_rs,
1669                                          g1_rs.size(),
1670                                          page_size,
1671                                          HeapRegion::GrainBytes,
1672                                          1,
1673                                          mtJavaHeap);

This function could then also make use of the new policy, something like:
create_heap_mapper(g1_rs, _collector_policy, page_size);

1704   if (is_hetero_heap()) {
1705     _hrm = new HeapRegionManagerForHeteroHeap((uint)((max_byte_size 
/ 2) / HeapRegion::GrainBytes /*heap size as num of regions*/));
1706   }
1707   else {
1708     _hrm = new HeapRegionManager();
1709   }

This code could then become something like:
_hrm = HeapRegionManager::create_manager(_collector_policy)

3925         if(g1h->is_hetero_heap()) {
3926           if(!r->is_old()) {
....
3929             r->set_premature_old();
3930           }
3931         } else {
3932           r->set_old();
3933         }

So if I understand this correctly, premature_old is used when we get 
evac failures and we use it to force these regions to be included in the 
next Mixed collection. I'm not sure this is needed, in fact I think one 
of the cool things with nv-dimm is that we can avoid evac failures. G1 
normally needs to stop handing out regions to promote to when there are 
no regions left, but when using nv-dimm the old regions come from 
another pool and we should be able to avoid this case.
---

src/hotspot/share/gc/g1/g1FullCollector.cpp
-------------------------------------------
  169   if (_heap->is_hetero_heap()) {
  170     static_cast 
<HeapRegionManagerForHeteroHeap*>(_heap->_hrm)->prepare_for_full_collection_start();
  171   }

Move this to:
G1CollectedHeap::prepare_heap_for_full_collection()

  185   if (_heap->is_hetero_heap()) {
  186     static_cast 
<HeapRegionManagerForHeteroHeap*>(_heap->_hrm)->prepare_for_full_collection_end();
  187   }

Move this to:
G1CollectedHeap::prepare_heap_for_mutators()

And if you make the prepare-functions virtual and not doing anything on 
HeapRegionManager we can avoid the checks.
---

src/hotspot/share/gc/g1/g1Policy.cpp
------------------------------------
  223   if(_g1h->is_hetero_heap() && (Thread::current()->is_VM_thread() 
|| Heap_lock->owned_by_self())) {
  224     static_cast 
<HeapRegionManagerForHeteroHeap*>(_g1h->hrm())->resize_dram_regions(_young_list_target_length, 
_g1h->workers());
  225   }

Feels like this code should be part of the 
prepare_for_full_collection_end() above, but the 
_young_list_target_length isn't updated until right after 
prepare_heap_for_mutators() so you might need to restructure the code a 
bit more to make it fit.
---

src/hotspot/share/gc/g1/g1RegionToSpaceMapper.cpp
-------------------------------------------------
Had to add these two includes to make it compile.
#include "runtime/java.hpp"
#include "utilities/formatBuffer.hpp"

Please also clean out all code commented out.
---

src/hotspot/share/gc/g1/heapRegionManager.hpp
---------------------------------------------
I agree with Sangheon, please group the members that should be protected 
and remember to update the init-list for the constructor.

Also agree that the #else on #ifdef ASSERT should be removed.
---

src/hotspot/share/gc/g1/heapRegionSet.cpp
-----------------------------------------
  240   bool started = false;
  241   while (cur != NULL && cur->hrm_index() <= end) {
  242     if (!started && cur->hrm_index() >= start) {
  243       started = true;
  244     }
  245     if(started) {
  246       num++;
  247     }
  248     cur = cur->next();
  249   }

To me this looks more complex than it is because of the multiple 
conditions, what do you think about:
while (cur != NULL) {
   uint index = cur->hrm_index();
   if (index > end) {
     break;
   } else if (index >= start) {
     num++;
   }
   cur = cur->next();
}
---

src/hotspot/share/runtime/arguments.cpp
---------------------------------------
2072   if(!FLAG_IS_DEFAULT(AllocateHeapAt) && 
!FLAG_IS_DEFAULT(AllocateOldGenAt)) {
2073     jio_fprintf(defaultStream::error_stream(),
2074                 "AllocateHeapAt and AllocateOldGenAt cannot be used 
together.\n");
2075     status = false;
2076   }
2077
2078   if (!FLAG_IS_DEFAULT(AllocateOldGenAt) && (UseSerialGC || 
UseConcMarkSweepGC || UseEpsilonGC || UseZGC)) {
2079     jio_fprintf(defaultStream::error_stream(),
2080       "AllocateOldGenAt not supported for selected GC.\n");
2081     status = false;
2082   }

This code would fit much better in GCArguments. There is currently no 
method handling this case but please add check_args_consistency(). You 
can look at CompilerConfig::check_args_consistency for inspiration.
---

src/hotspot/share/runtime/globals.hpp
-------------------------------------
2612   experimental(uintx, G1YoungExpansionBufferPerc, 10,

Move to g1_globals.hpp and call it G1YoungExpansionBufferPercent.
---

src/hotspot/share/gc/g1/heapRegionManagerForHeteroHeap.*pp
----------------------------------------------------------

Haven't reviewed this code in detail yet but will do in a later review. 
One thought I have already is about the name, what do you think about:
HeterogeneousHeapRegionManager
---

Thanks,
Stefan

> 
> Testing : Passed tier1_gc and tier1_runtime jtret tests.
> 
>                   I added extra options 
> "-XX:+UnlockExperimentalVMOptions -XX:AllocateOldGenAt=/home/Kishor" to 
> each test. There are some tests which I had to exclude since they won't 
> work with this flag. Example: tests in ConcMarkSweepGC which does not 
> support this flag, tests requiring CompressedOops to be enabled, etc.
> 
> Thanks
> 
> Kishor
> 


More information about the hotspot-gc-dev mailing list