8220312: Implementation: NUMA-Aware Memory Allocation for G1, Logging (3/3)
authorsangheki
Wed, 13 Nov 2019 10:51:41 -0800
changeset 59062 6530de931b8e
parent 59061 df6f2350edfa
child 59063 058d299b22b6
8220312: Implementation: NUMA-Aware Memory Allocation for G1, Logging (3/3) Reviewed-by: kbarrett, sjohanss, tschatzl
src/hotspot/os/linux/os_linux.cpp
src/hotspot/os/linux/os_linux.hpp
src/hotspot/share/gc/g1/g1CollectedHeap.cpp
src/hotspot/share/gc/g1/g1CollectedHeap.hpp
src/hotspot/share/gc/g1/g1EdenRegions.hpp
src/hotspot/share/gc/g1/g1HeapTransition.cpp
src/hotspot/share/gc/g1/g1HeapTransition.hpp
src/hotspot/share/gc/g1/g1NUMA.cpp
src/hotspot/share/gc/g1/g1NUMA.hpp
src/hotspot/share/gc/g1/g1NUMAStats.cpp
src/hotspot/share/gc/g1/g1NUMAStats.hpp
src/hotspot/share/gc/g1/g1ParScanThreadState.cpp
src/hotspot/share/gc/g1/g1ParScanThreadState.hpp
src/hotspot/share/gc/g1/g1ParScanThreadState.inline.hpp
src/hotspot/share/gc/g1/g1RegionsOnNodes.cpp
src/hotspot/share/gc/g1/g1RegionsOnNodes.hpp
src/hotspot/share/gc/g1/g1SurvivorRegions.cpp
src/hotspot/share/gc/g1/g1SurvivorRegions.hpp
src/hotspot/share/gc/g1/heapRegion.cpp
src/hotspot/share/gc/g1/heapRegionManager.cpp
src/hotspot/share/gc/g1/heapRegionManager.hpp
src/hotspot/share/gc/g1/heapRegionSet.cpp
src/hotspot/share/gc/g1/heapRegionSet.hpp
src/hotspot/share/gc/g1/heapRegionSet.inline.hpp
test/hotspot/jtreg/gc/g1/numa/TestG1NUMATouchRegions.java
--- a/src/hotspot/os/linux/os_linux.cpp	Wed Nov 13 10:49:32 2019 -0800
+++ b/src/hotspot/os/linux/os_linux.cpp	Wed Nov 13 10:51:41 2019 -0800
@@ -3008,17 +3008,13 @@
 }
 
 int os::numa_get_group_id_for_address(const void* address) {
-#ifndef MPOL_F_NODE
-#define MPOL_F_NODE     (1<<0)  // Return next IL mode instead of node mask
-#endif
-
-#ifndef MPOL_F_ADDR
-#define MPOL_F_ADDR     (1<<1)  // Look up VMA using address
-#endif
-
-  int id = 0;
-
-  if (syscall(SYS_get_mempolicy, &id, NULL, 0, const_cast<void*>(address), MPOL_F_NODE | MPOL_F_ADDR) == -1) {
+  void** pages = const_cast<void**>(&address);
+  int id = -1;
+
+  if (os::Linux::numa_move_pages(0, 1, pages, NULL, &id, 0) == -1) {
+    return -1;
+  }
+  if (id < 0) {
     return -1;
   }
   return id;
@@ -3152,6 +3148,8 @@
                                           libnuma_v2_dlsym(handle, "numa_get_membind")));
       set_numa_get_interleave_mask(CAST_TO_FN_PTR(numa_get_interleave_mask_func_t,
                                                   libnuma_v2_dlsym(handle, "numa_get_interleave_mask")));
+      set_numa_move_pages(CAST_TO_FN_PTR(numa_move_pages_func_t,
+                                         libnuma_dlsym(handle, "numa_move_pages")));
 
       if (numa_available() != -1) {
         set_numa_all_nodes((unsigned long*)libnuma_dlsym(handle, "numa_all_nodes"));
@@ -3286,6 +3284,7 @@
 os::Linux::numa_distance_func_t os::Linux::_numa_distance;
 os::Linux::numa_get_membind_func_t os::Linux::_numa_get_membind;
 os::Linux::numa_get_interleave_mask_func_t os::Linux::_numa_get_interleave_mask;
+os::Linux::numa_move_pages_func_t os::Linux::_numa_move_pages;
 os::Linux::NumaAllocationPolicy os::Linux::_current_numa_policy;
 unsigned long* os::Linux::_numa_all_nodes;
 struct bitmask* os::Linux::_numa_all_nodes_ptr;
--- a/src/hotspot/os/linux/os_linux.hpp	Wed Nov 13 10:49:32 2019 -0800
+++ b/src/hotspot/os/linux/os_linux.hpp	Wed Nov 13 10:51:41 2019 -0800
@@ -216,6 +216,7 @@
   typedef void (*numa_interleave_memory_v2_func_t)(void *start, size_t size, struct bitmask* mask);
   typedef struct bitmask* (*numa_get_membind_func_t)(void);
   typedef struct bitmask* (*numa_get_interleave_mask_func_t)(void);
+  typedef long (*numa_move_pages_func_t)(int pid, unsigned long count, void **pages, const int *nodes, int *status, int flags);
 
   typedef void (*numa_set_bind_policy_func_t)(int policy);
   typedef int (*numa_bitmask_isbitset_func_t)(struct bitmask *bmp, unsigned int n);
@@ -234,6 +235,7 @@
   static numa_distance_func_t _numa_distance;
   static numa_get_membind_func_t _numa_get_membind;
   static numa_get_interleave_mask_func_t _numa_get_interleave_mask;
+  static numa_move_pages_func_t _numa_move_pages;
   static unsigned long* _numa_all_nodes;
   static struct bitmask* _numa_all_nodes_ptr;
   static struct bitmask* _numa_nodes_ptr;
@@ -253,6 +255,7 @@
   static void set_numa_distance(numa_distance_func_t func) { _numa_distance = func; }
   static void set_numa_get_membind(numa_get_membind_func_t func) { _numa_get_membind = func; }
   static void set_numa_get_interleave_mask(numa_get_interleave_mask_func_t func) { _numa_get_interleave_mask = func; }
+  static void set_numa_move_pages(numa_move_pages_func_t func) { _numa_move_pages = func; }
   static void set_numa_all_nodes(unsigned long* ptr) { _numa_all_nodes = ptr; }
   static void set_numa_all_nodes_ptr(struct bitmask **ptr) { _numa_all_nodes_ptr = (ptr == NULL ? NULL : *ptr); }
   static void set_numa_nodes_ptr(struct bitmask **ptr) { _numa_nodes_ptr = (ptr == NULL ? NULL : *ptr); }
@@ -318,6 +321,9 @@
   static int numa_distance(int node1, int node2) {
     return _numa_distance != NULL ? _numa_distance(node1, node2) : -1;
   }
+  static long numa_move_pages(int pid, unsigned long count, void **pages, const int *nodes, int *status, int flags) {
+    return _numa_move_pages != NULL ? _numa_move_pages(pid, count, pages, nodes, status, flags) : -1;
+  }
   static int get_node_by_cpu(int cpu_id);
   static int get_existing_num_nodes();
   // Check if numa node is configured (non-zero memory node).
--- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp	Wed Nov 13 10:49:32 2019 -0800
+++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp	Wed Nov 13 10:51:41 2019 -0800
@@ -2389,6 +2389,15 @@
   st->print("%u survivors (" SIZE_FORMAT "K)", survivor_regions,
             (size_t) survivor_regions * HeapRegion::GrainBytes / K);
   st->cr();
+  if (_numa->is_enabled()) {
+    uint num_nodes = _numa->num_active_nodes();
+    st->print("  remaining free region(s) on each NUMA node: ");
+    const int* node_ids = _numa->node_ids();
+    for (uint node_index = 0; node_index < num_nodes; node_index++) {
+      st->print("%d=%u ", node_ids[node_index], _hrm->num_free_regions(node_index));
+    }
+    st->cr();
+  }
   MetaspaceUtils::print_on(st);
 }
 
@@ -2578,6 +2587,20 @@
   // We have just completed a GC. Update the soft reference
   // policy with the new heap occupancy
   Universe::update_heap_info_at_gc();
+
+  // Print NUMA statistics.
+  _numa->print_statistics();
+}
+
+void G1CollectedHeap::verify_numa_regions(const char* desc) {
+  LogTarget(Trace, gc, heap, verify) lt;
+
+  if (lt.is_enabled()) {
+    LogStream ls(lt);
+    // Iterate all heap regions to print matching between preferred numa id and actual numa id.
+    G1NodeIndexCheckClosure cl(desc, _numa, &ls);
+    heap_region_iterate(&cl);
+  }
 }
 
 HeapWord* G1CollectedHeap::do_collection_pause(size_t word_size,
@@ -2887,6 +2910,7 @@
   }
   _verifier->verify_before_gc(type);
   _verifier->check_bitmaps("GC Start");
+  verify_numa_regions("GC Start");
 }
 
 void G1CollectedHeap::verify_after_young_collection(G1HeapVerifier::G1VerifyType type) {
@@ -2897,6 +2921,7 @@
   }
   _verifier->verify_after_gc(type);
   _verifier->check_bitmaps("GC End");
+  verify_numa_regions("GC End");
 }
 
 void G1CollectedHeap::expand_heap_after_young_collection(){
--- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp	Wed Nov 13 10:49:32 2019 -0800
+++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp	Wed Nov 13 10:51:41 2019 -0800
@@ -530,6 +530,9 @@
   // Merges the information gathered on a per-thread basis for all worker threads
   // during GC into global variables.
   void merge_per_thread_state_info(G1ParScanThreadStateSet* per_thread_states);
+
+  void verify_numa_regions(const char* desc);
+
 public:
   G1YoungRemSetSamplingThread* sampling_thread() const { return _young_gen_sampling_thread; }
 
@@ -1284,7 +1287,9 @@
   const G1SurvivorRegions* survivor() const { return &_survivor; }
 
   uint eden_regions_count() const { return _eden.length(); }
+  uint eden_regions_count(uint node_index) const { return _eden.regions_on_node(node_index); }
   uint survivor_regions_count() const { return _survivor.length(); }
+  uint survivor_regions_count(uint node_index) const { return _survivor.regions_on_node(node_index); }
   size_t eden_regions_used_bytes() const { return _eden.used_bytes(); }
   size_t survivor_regions_used_bytes() const { return _survivor.used_bytes(); }
   uint young_regions_count() const { return _eden.length() + _survivor.length(); }
--- a/src/hotspot/share/gc/g1/g1EdenRegions.hpp	Wed Nov 13 10:49:32 2019 -0800
+++ b/src/hotspot/share/gc/g1/g1EdenRegions.hpp	Wed Nov 13 10:51:41 2019 -0800
@@ -25,6 +25,7 @@
 #ifndef SHARE_GC_G1_G1EDENREGIONS_HPP
 #define SHARE_GC_G1_G1EDENREGIONS_HPP
 
+#include "gc/g1/g1RegionsOnNodes.hpp"
 #include "gc/g1/heapRegion.hpp"
 #include "runtime/globals.hpp"
 #include "utilities/debug.hpp"
@@ -35,18 +36,25 @@
   // Sum of used bytes from all retired eden regions.
   // I.e. updated when mutator regions are retired.
   volatile size_t _used_bytes;
+  G1RegionsOnNodes  _regions_on_node;
 
 public:
-  G1EdenRegions() : _length(0), _used_bytes(0) { }
+  G1EdenRegions() : _length(0), _used_bytes(0), _regions_on_node() { }
 
-  void add(HeapRegion* hr) {
+  virtual uint add(HeapRegion* hr) {
     assert(!hr->is_eden(), "should not already be set");
     _length++;
+    return _regions_on_node.add(hr);
   }
 
-  void clear() { _length = 0; _used_bytes = 0; }
+  void clear() {
+    _length = 0;
+    _used_bytes = 0;
+    _regions_on_node.clear();
+  }
 
   uint length() const { return _length; }
+  uint regions_on_node(uint node_index) const { return _regions_on_node.count(node_index); }
 
   size_t used_bytes() const { return _used_bytes; }
 
--- a/src/hotspot/share/gc/g1/g1HeapTransition.cpp	Wed Nov 13 10:49:32 2019 -0800
+++ b/src/hotspot/share/gc/g1/g1HeapTransition.cpp	Wed Nov 13 10:51:41 2019 -0800
@@ -26,15 +26,38 @@
 #include "gc/g1/g1CollectedHeap.hpp"
 #include "gc/g1/g1HeapTransition.hpp"
 #include "gc/g1/g1Policy.hpp"
-#include "logging/log.hpp"
+#include "logging/logStream.hpp"
 #include "memory/metaspace.hpp"
 
-G1HeapTransition::Data::Data(G1CollectedHeap* g1_heap) {
-  _eden_length = g1_heap->eden_regions_count();
-  _survivor_length = g1_heap->survivor_regions_count();
-  _old_length = g1_heap->old_regions_count();
-  _archive_length = g1_heap->archive_regions_count();
-  _humongous_length = g1_heap->humongous_regions_count();
+G1HeapTransition::Data::Data(G1CollectedHeap* g1_heap) :
+  _eden_length(g1_heap->eden_regions_count()),
+  _survivor_length(g1_heap->survivor_regions_count()),
+  _old_length(g1_heap->old_regions_count()),
+  _archive_length(g1_heap->archive_regions_count()),
+  _humongous_length(g1_heap->humongous_regions_count()),
+  _eden_length_per_node(NULL),
+  _survivor_length_per_node(NULL) {
+
+  uint node_count = G1NUMA::numa()->num_active_nodes();
+
+  if (node_count > 1) {
+    LogTarget(Debug, gc, heap, numa) lt;
+
+    if (lt.is_enabled()) {
+      _eden_length_per_node = NEW_C_HEAP_ARRAY(uint, node_count, mtGC);
+      _survivor_length_per_node = NEW_C_HEAP_ARRAY(uint, node_count, mtGC);
+
+      for (uint i = 0; i < node_count; i++) {
+        _eden_length_per_node[i] = g1_heap->eden_regions_count(i);
+        _survivor_length_per_node[i] = g1_heap->survivor_regions_count(i);
+      }
+    }
+  }
+}
+
+G1HeapTransition::Data::~Data() {
+  FREE_C_HEAP_ARRAY(uint, _eden_length_per_node);
+  FREE_C_HEAP_ARRAY(uint, _survivor_length_per_node);
 }
 
 G1HeapTransition::G1HeapTransition(G1CollectedHeap* g1_heap) : _g1_heap(g1_heap), _before(g1_heap) { }
@@ -84,6 +107,34 @@
   }
 };
 
+static void log_regions(const char* msg, size_t before_length, size_t after_length, size_t capacity,
+                        uint* before_per_node_length, uint* after_per_node_length) {
+  LogTarget(Info, gc, heap) lt;
+
+  if (lt.is_enabled()) {
+    LogStream ls(lt);
+
+    ls.print("%s regions: " SIZE_FORMAT "->" SIZE_FORMAT "("  SIZE_FORMAT ")",
+             msg, before_length, after_length, capacity);
+    // Not NULL only if gc+heap+numa at Debug level is enabled.
+    if (before_per_node_length != NULL && after_per_node_length != NULL) {
+      G1NUMA* numa = G1NUMA::numa();
+      uint num_nodes = numa->num_active_nodes();
+      const int* node_ids = numa->node_ids();
+      ls.print(" (");
+      for (uint i = 0; i < num_nodes; i++) {
+        ls.print("%d: %u->%u", node_ids[i], before_per_node_length[i], after_per_node_length[i]);
+        // Skip adding below if it is the last one.
+        if (i != num_nodes - 1) {
+          ls.print(", ");
+        }
+      }
+      ls.print(")");
+    }
+    ls.print_cr("");
+  }
+}
+
 void G1HeapTransition::print() {
   Data after(_g1_heap);
 
@@ -106,12 +157,12 @@
         after._humongous_length, usage._humongous_region_count);
   }
 
-  log_info(gc, heap)("Eden regions: " SIZE_FORMAT "->" SIZE_FORMAT "("  SIZE_FORMAT ")",
-                     _before._eden_length, after._eden_length, eden_capacity_length_after_gc);
+  log_regions("Eden", _before._eden_length, after._eden_length, eden_capacity_length_after_gc,
+              _before._eden_length_per_node, after._eden_length_per_node);
   log_trace(gc, heap)(" Used: 0K, Waste: 0K");
 
-  log_info(gc, heap)("Survivor regions: " SIZE_FORMAT "->" SIZE_FORMAT "("  SIZE_FORMAT ")",
-                     _before._survivor_length, after._survivor_length, survivor_capacity_length_before_gc);
+  log_regions("Survivor", _before._survivor_length, after._survivor_length, survivor_capacity_length_before_gc,
+              _before._survivor_length_per_node, after._survivor_length_per_node);
   log_trace(gc, heap)(" Used: " SIZE_FORMAT "K, Waste: " SIZE_FORMAT "K",
       usage._survivor_used / K, ((after._survivor_length * HeapRegion::GrainBytes) - usage._survivor_used) / K);
 
--- a/src/hotspot/share/gc/g1/g1HeapTransition.hpp	Wed Nov 13 10:49:32 2019 -0800
+++ b/src/hotspot/share/gc/g1/g1HeapTransition.hpp	Wed Nov 13 10:51:41 2019 -0800
@@ -39,7 +39,13 @@
     size_t _humongous_length;
     const metaspace::MetaspaceSizesSnapshot _meta_sizes;
 
+    // Only includes current eden regions.
+    uint* _eden_length_per_node;
+    // Only includes current survivor regions.
+    uint* _survivor_length_per_node;
+
     Data(G1CollectedHeap* g1_heap);
+    ~Data();
   };
 
   G1CollectedHeap* _g1_heap;
--- a/src/hotspot/share/gc/g1/g1NUMA.cpp	Wed Nov 13 10:49:32 2019 -0800
+++ b/src/hotspot/share/gc/g1/g1NUMA.cpp	Wed Nov 13 10:51:41 2019 -0800
@@ -24,8 +24,7 @@
 
 #include "precompiled.hpp"
 #include "gc/g1/g1NUMA.hpp"
-#include "gc/g1/heapRegion.hpp"
-#include "logging/log.hpp"
+#include "logging/logStream.hpp"
 #include "runtime/globals.hpp"
 #include "runtime/os.hpp"
 
@@ -74,7 +73,7 @@
 G1NUMA::G1NUMA() :
   _node_id_to_index_map(NULL), _len_node_id_to_index_map(0),
   _node_ids(NULL), _num_active_node_ids(0),
-  _region_size(0), _page_size(0) {
+  _region_size(0), _page_size(0), _stats(NULL) {
 }
 
 void G1NUMA::initialize_without_numa() {
@@ -119,9 +118,12 @@
   for (uint i = 0; i < _num_active_node_ids; i++) {
     _node_id_to_index_map[_node_ids[i]] = i;
   }
+
+  _stats = new G1NUMAStats(_node_ids, _num_active_node_ids);
 }
 
 G1NUMA::~G1NUMA() {
+  delete _stats;
   FREE_C_HEAP_ARRAY(int, _node_id_to_index_map);
   FREE_C_HEAP_ARRAY(int, _node_ids);
 }
@@ -215,7 +217,7 @@
   assert(is_aligned(aligned_address, page_size()), "Given address (" PTR_FORMAT ") should be aligned.", p2i(aligned_address));
   assert(is_aligned(size_in_bytes, page_size()), "Given size (" SIZE_FORMAT ") should be aligned.", size_in_bytes);
 
-  log_debug(gc, heap, numa)("Request memory [" PTR_FORMAT ", " PTR_FORMAT ") to be numa id (%d).",
+  log_trace(gc, heap, numa)("Request memory [" PTR_FORMAT ", " PTR_FORMAT ") to be NUMA id (%d)",
                             p2i(aligned_address), p2i((char*)aligned_address + size_in_bytes), _node_ids[node_index]);
   os::numa_make_local((char*)aligned_address, size_in_bytes, _node_ids[node_index]);
 }
@@ -225,3 +227,79 @@
   // There would be some cases that 1 page may be consisted of multiple HeapRegions.
   return 3 * MAX2((uint)(page_size() / region_size()), (uint)1) * num_active_nodes();
 }
+
+void G1NUMA::update_statistics(G1NUMAStats::NodeDataItems phase,
+                               uint requested_node_index,
+                               uint allocated_node_index) {
+  if (_stats == NULL) {
+    return;
+  }
+
+  uint converted_req_index;
+  if(requested_node_index < _num_active_node_ids) {
+    converted_req_index = requested_node_index;
+  } else {
+    assert(requested_node_index == AnyNodeIndex,
+           "Requested node index %u should be AnyNodeIndex.", requested_node_index);
+    converted_req_index = _num_active_node_ids;
+  }
+  _stats->update(phase, converted_req_index, allocated_node_index);
+}
+
+void G1NUMA::copy_statistics(G1NUMAStats::NodeDataItems phase,
+                             uint requested_node_index,
+                             size_t* allocated_stat) {
+  if (_stats == NULL) {
+    return;
+  }
+
+  _stats->copy(phase, requested_node_index, allocated_stat);
+}
+
+void G1NUMA::print_statistics() const {
+  if (_stats == NULL) {
+    return;
+  }
+
+  _stats->print_statistics();
+}
+
+G1NodeIndexCheckClosure::G1NodeIndexCheckClosure(const char* desc, G1NUMA* numa, LogStream* ls) :
+  _desc(desc), _numa(numa), _ls(ls) {
+
+  uint num_nodes = _numa->num_active_nodes();
+  _matched = NEW_C_HEAP_ARRAY(uint, num_nodes, mtGC);
+  _mismatched = NEW_C_HEAP_ARRAY(uint, num_nodes, mtGC);
+  _total = NEW_C_HEAP_ARRAY(uint, num_nodes, mtGC);
+  memset(_matched, 0, sizeof(uint) * num_nodes);
+  memset(_mismatched, 0, sizeof(uint) * num_nodes);
+  memset(_total, 0, sizeof(uint) * num_nodes);
+}
+
+G1NodeIndexCheckClosure::~G1NodeIndexCheckClosure() {
+  _ls->print("%s: NUMA region verification (id: matched/mismatched/total): ", _desc);
+  const int* numa_ids = _numa->node_ids();
+  for (uint i = 0; i < _numa->num_active_nodes(); i++) {
+    _ls->print("%d: %u/%u/%u ", numa_ids[i], _matched[i], _mismatched[i], _total[i]);
+  }
+
+  FREE_C_HEAP_ARRAY(uint, _matched);
+  FREE_C_HEAP_ARRAY(uint, _mismatched);
+  FREE_C_HEAP_ARRAY(uint, _total);
+}
+
+bool G1NodeIndexCheckClosure::do_heap_region(HeapRegion* hr) {
+  // Preferred node index will only have valid node index.
+  uint preferred_node_index = _numa->preferred_node_index_for_index(hr->hrm_index());
+  // Active node index may have UnknownNodeIndex.
+  uint active_node_index = _numa->index_of_address(hr->bottom());
+
+  if (preferred_node_index == active_node_index) {
+    _matched[preferred_node_index]++;
+  } else if (active_node_index != G1NUMA::UnknownNodeIndex) {
+    _mismatched[preferred_node_index]++;
+  }
+  _total[preferred_node_index]++;
+
+  return false;
+}
--- a/src/hotspot/share/gc/g1/g1NUMA.hpp	Wed Nov 13 10:49:32 2019 -0800
+++ b/src/hotspot/share/gc/g1/g1NUMA.hpp	Wed Nov 13 10:51:41 2019 -0800
@@ -25,10 +25,12 @@
 #ifndef SHARE_VM_GC_G1_NUMA_HPP
 #define SHARE_VM_GC_G1_NUMA_HPP
 
+#include "gc/g1/g1NUMAStats.hpp"
+#include "gc/g1/heapRegion.hpp"
 #include "memory/allocation.hpp"
 #include "runtime/os.hpp"
 
-class HeapRegion;
+class LogStream;
 
 class G1NUMA: public CHeapObj<mtGC> {
   // Mapping of available node ids to  0-based index which can be used for
@@ -49,6 +51,9 @@
   // Necessary when touching memory.
   size_t _page_size;
 
+  // Stores statistic data.
+  G1NUMAStats* _stats;
+
   size_t region_size() const;
   size_t page_size() const;
 
@@ -113,6 +118,35 @@
   // Returns maximum search depth which is used to limit heap region search iterations.
   // The number of active nodes, page size and heap region size are considered.
   uint max_search_depth() const;
+
+  // Update the given phase of requested and allocated node index.
+  void update_statistics(G1NUMAStats::NodeDataItems phase, uint requested_node_index, uint allocated_node_index);
+
+  // Copy all allocated statistics of the given phase and requested node.
+  // Precondition: allocated_stat should have same length of active nodes.
+  void copy_statistics(G1NUMAStats::NodeDataItems phase, uint requested_node_index, size_t* allocated_stat);
+
+  // Print all statistics.
+  void print_statistics() const;
+};
+
+class G1NodeIndexCheckClosure : public HeapRegionClosure {
+  const char* _desc;
+  G1NUMA* _numa;
+  // Records matched count of each node.
+  uint* _matched;
+  // Records mismatched count of each node.
+  uint* _mismatched;
+  // Records total count of each node.
+  // Total = matched + mismatched + unknown.
+  uint* _total;
+  LogStream* _ls;
+
+public:
+  G1NodeIndexCheckClosure(const char* desc, G1NUMA* numa, LogStream* ls);
+  ~G1NodeIndexCheckClosure();
+
+  bool do_heap_region(HeapRegion* hr);
 };
 
 #endif // SHARE_VM_GC_G1_NUMA_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/share/gc/g1/g1NUMAStats.cpp	Wed Nov 13 10:51:41 2019 -0800
@@ -0,0 +1,232 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#include "precompiled.hpp"
+#include "gc/g1/g1NUMAStats.hpp"
+#include "logging/logStream.hpp"
+
+double G1NUMAStats::Stat::rate() const {
+  return _requested == 0 ? 0 : (double)_hit / _requested * 100;
+}
+
+G1NUMAStats::NodeDataArray::NodeDataArray(uint num_nodes) {
+  guarantee(num_nodes > 1, "Number of nodes (%u) should be set", num_nodes);
+
+  // The row represents the number of nodes.
+  _num_column = num_nodes;
+  // +1 for G1MemoryNodeManager::AnyNodeIndex.
+  _num_row = num_nodes + 1;
+
+  _data = NEW_C_HEAP_ARRAY(size_t*, _num_row, mtGC);
+  for (uint row = 0; row < _num_row; row++) {
+    _data[row] = NEW_C_HEAP_ARRAY(size_t, _num_column, mtGC);
+  }
+
+  clear();
+}
+
+G1NUMAStats::NodeDataArray::~NodeDataArray() {
+  for (uint row = 0; row < _num_row; row++) {
+    FREE_C_HEAP_ARRAY(size_t, _data[row]);
+  }
+  FREE_C_HEAP_ARRAY(size_t*, _data);
+}
+
+void G1NUMAStats::NodeDataArray::create_hit_rate(Stat* result) const {
+  size_t requested = 0;
+  size_t hit = 0;
+
+  for (size_t row = 0; row < _num_row; row++) {
+    for (size_t column = 0; column < _num_column; column++) {
+      requested += _data[row][column];
+      if (row == column) {
+        hit += _data[row][column];
+      }
+    }
+  }
+
+  assert(result != NULL, "Invariant");
+  result->_hit = hit;
+  result->_requested = requested;
+}
+
+void G1NUMAStats::NodeDataArray::create_hit_rate(Stat* result, uint req_index) const {
+  size_t requested = 0;
+  size_t hit = _data[req_index][req_index];
+
+  for (size_t column = 0; column < _num_column; column++) {
+    requested += _data[req_index][column];
+  }
+
+  assert(result != NULL, "Invariant");
+  result->_hit = hit;
+  result->_requested = requested;
+}
+
+size_t G1NUMAStats::NodeDataArray::sum(uint req_index) const {
+  size_t sum = 0;
+  for (size_t column = 0; column < _num_column; column++) {
+    sum += _data[req_index][column];
+  }
+
+  return sum;
+}
+
+void G1NUMAStats::NodeDataArray::increase(uint req_index, uint alloc_index) {
+  assert(req_index < _num_row,
+         "Requested index %u should be less than the row size %u",
+         req_index, _num_row);
+  assert(alloc_index < _num_column,
+         "Allocated index %u should be less than the column size %u",
+         alloc_index, _num_column);
+  _data[req_index][alloc_index] += 1;
+}
+
+void G1NUMAStats::NodeDataArray::clear() {
+  for (uint row = 0; row < _num_row; row++) {
+    memset((void*)_data[row], 0, sizeof(size_t) * _num_column);
+  }
+}
+
+size_t G1NUMAStats::NodeDataArray::get(uint req_index, uint alloc_index) {
+  return _data[req_index][alloc_index];
+}
+
+void G1NUMAStats::NodeDataArray::copy(uint req_index, size_t* stat) {
+  assert(stat != NULL, "Invariant");
+
+  for (uint column = 0; column < _num_column; column++) {
+    _data[req_index][column] += stat[column];
+  }
+}
+
+G1NUMAStats::G1NUMAStats(const int* node_ids, uint num_node_ids) :
+  _node_ids(node_ids), _num_node_ids(num_node_ids), _node_data() {
+
+  assert(_num_node_ids  > 1, "Should have more than one active memory nodes %u", _num_node_ids);
+
+  for (int i = 0; i < NodeDataItemsSentinel; i++) {
+    _node_data[i] = new NodeDataArray(_num_node_ids);
+  }
+}
+
+G1NUMAStats::~G1NUMAStats() {
+  for (int i = 0; i < NodeDataItemsSentinel; i++) {
+    delete _node_data[i];
+  }
+}
+
+void G1NUMAStats::clear(G1NUMAStats::NodeDataItems phase) {
+  _node_data[phase]->clear();
+}
+
+void G1NUMAStats::update(G1NUMAStats::NodeDataItems phase,
+                         uint requested_node_index,
+                         uint allocated_node_index) {
+  _node_data[phase]->increase(requested_node_index, allocated_node_index);
+}
+
+void G1NUMAStats::copy(G1NUMAStats::NodeDataItems phase,
+                       uint requested_node_index,
+                       size_t* allocated_stat) {
+  _node_data[phase]->copy(requested_node_index, allocated_stat);
+}
+
+static const char* phase_to_explanatory_string(G1NUMAStats::NodeDataItems phase) {
+  switch(phase) {
+    case G1NUMAStats::NewRegionAlloc:
+      return "Placement match ratio";
+    case G1NUMAStats::LocalObjProcessAtCopyToSurv:
+      return "Worker task locality match ratio";
+    default:
+      return "";
+  }
+}
+
+#define RATE_TOTAL_FORMAT "%0.0f%% " SIZE_FORMAT "/" SIZE_FORMAT
+
+void G1NUMAStats::print_info(G1NUMAStats::NodeDataItems phase) {
+  LogTarget(Info, gc, heap, numa) lt;
+
+  if (lt.is_enabled()) {
+    LogStream ls(lt);
+    Stat result;
+    size_t array_width = _num_node_ids;
+
+    _node_data[phase]->create_hit_rate(&result);
+
+    ls.print("%s: " RATE_TOTAL_FORMAT " (",
+             phase_to_explanatory_string(phase), result.rate(), result._hit, result._requested);
+
+    for (uint i = 0; i < array_width; i++) {
+      if (i != 0) {
+        ls.print(", ");
+      }
+      _node_data[phase]->create_hit_rate(&result, i);
+      ls.print("%d: " RATE_TOTAL_FORMAT,
+               _node_ids[i], result.rate(), result._hit, result._requested);
+    }
+    ls.print_cr(")");
+  }
+}
+
+void G1NUMAStats::print_mutator_alloc_stat_debug() {
+  LogTarget(Debug, gc, heap, numa) lt;
+
+  if (lt.is_enabled()) {
+    LogStream ls(lt);
+    uint array_width = _num_node_ids;
+
+    ls.print("Allocated NUMA ids    ");
+    for (uint i = 0; i < array_width; i++) {
+      ls.print("%8d", _node_ids[i]);
+    }
+    ls.print_cr("   Total");
+
+    ls.print("Requested NUMA id ");
+    for (uint req = 0; req < array_width; req++) {
+      ls.print("%3d ", _node_ids[req]);
+      for (uint alloc = 0; alloc < array_width; alloc++) {
+        ls.print(SIZE_FORMAT_W(8), _node_data[NewRegionAlloc]->get(req, alloc));
+      }
+      ls.print(SIZE_FORMAT_W(8), _node_data[NewRegionAlloc]->sum(req));
+      ls.print_cr("");
+      // Add padding to align with the string 'Requested NUMA id'.
+      ls.print("                  ");
+    }
+    ls.print("Any ");
+    for (uint alloc = 0; alloc < array_width; alloc++) {
+      ls.print(SIZE_FORMAT_W(8), _node_data[NewRegionAlloc]->get(array_width, alloc));
+    }
+    ls.print(SIZE_FORMAT_W(8), _node_data[NewRegionAlloc]->sum(array_width));
+    ls.print_cr("");
+  }
+}
+
+void G1NUMAStats::print_statistics() {
+  print_info(NewRegionAlloc);
+  print_mutator_alloc_stat_debug();
+
+  print_info(LocalObjProcessAtCopyToSurv);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/share/gc/g1/g1NUMAStats.hpp	Wed Nov 13 10:51:41 2019 -0800
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#ifndef SHARE_VM_GC_G1_NODE_TIMES_HPP
+#define SHARE_VM_GC_G1_NODE_TIMES_HPP
+
+#include "memory/allocation.hpp"
+
+// Manages statistics of multi nodes.
+class G1NUMAStats : public CHeapObj<mtGC> {
+  struct Stat {
+    // Hit count: if requested id equals to returned id.
+    size_t _hit;
+    // Total request count
+    size_t _requested;
+
+    // Hit count / total request count
+    double rate() const;
+  };
+
+  // Holds data array which has a size of (node count) * (node count + 1) to
+  // represent request node * allocated node. The request node includes any node case.
+  // All operations are NOT thread-safe.
+  // The row index indicates a requested node index while the column node index
+  // indicates an allocated node index. The last row is for any node index request.
+  // E.g. (req, alloc) = (0,0) (1,0) (2,0) (0,1) (Any, 3) (0,2) (0,3) (0,3) (3,3)
+  // Allocated node index      0    1    2    3  Total
+  // Requested node index 0    1    1    1    2    5
+  //                      1    1    0    0    0    1
+  //                      2    1    0    0    0    1
+  //                      3    0    0    0    1    1
+  //                    Any    0    0    0    1    1
+  class NodeDataArray : public CHeapObj<mtGC> {
+    // The number of nodes.
+    uint _num_column;
+    // The number of nodes + 1 (for any node request)
+    uint _num_row;
+    // 2-dimension array that holds count of allocated / requested node index.
+    size_t** _data;
+
+  public:
+    NodeDataArray(uint num_nodes);
+    ~NodeDataArray();
+
+    // Create Stat result of hit count, requested count and hit rate.
+    // The result is copied to the given result parameter.
+    void create_hit_rate(Stat* result) const;
+    // Create Stat result of hit count, requested count and hit rate of the given index.
+    // The result is copied to the given result parameter.
+    void create_hit_rate(Stat* result, uint req_index) const;
+    // Return sum of the given index.
+    size_t sum(uint req_index) const;
+    // Increase at the request / allocated index.
+    void increase(uint req_index, uint alloc_index);
+    // Clear all data.
+    void clear();
+    // Return current value of the given request / allocated index.
+    size_t get(uint req_index, uint alloc_index);
+    // Copy values of the given request index.
+    void copy(uint req_index, size_t* stat);
+  };
+
+public:
+  enum NodeDataItems {
+    // Statistics of a new region allocation.
+    NewRegionAlloc,
+    // Statistics of object processing during copy to survivor region.
+    LocalObjProcessAtCopyToSurv,
+    NodeDataItemsSentinel
+  };
+
+private:
+  const int* _node_ids;
+  uint _num_node_ids;
+
+  NodeDataArray* _node_data[NodeDataItemsSentinel];
+
+  void print_info(G1NUMAStats::NodeDataItems phase);
+
+  void print_mutator_alloc_stat_debug();
+
+public:
+  G1NUMAStats(const int* node_ids, uint num_node_ids);
+  ~G1NUMAStats();
+
+  void clear(G1NUMAStats::NodeDataItems phase);
+
+  // Update the given phase of requested and allocated node index.
+  void update(G1NUMAStats::NodeDataItems phase, uint requested_node_index, uint allocated_node_index);
+
+  // Copy all allocated statistics of the given phase and requested node.
+  // Precondition: allocated_stat should have same length of active nodes.
+  void copy(G1NUMAStats::NodeDataItems phase, uint requested_node_index, size_t* allocated_stat);
+
+  void print_statistics();
+};
+
+#endif // SHARE_VM_GC_G1_NODE_TIMES_HPP
--- a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp	Wed Nov 13 10:49:32 2019 -0800
+++ b/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp	Wed Nov 13 10:51:41 2019 -0800
@@ -57,7 +57,9 @@
     _stack_trim_lower_threshold(GCDrainStackTargetSize),
     _trim_ticks(),
     _old_gen_is_full(false),
-    _num_optional_regions(optional_cset_length)
+    _num_optional_regions(optional_cset_length),
+    _numa(g1h->numa()),
+    _obj_alloc_stat(NULL)
 {
   // We allocate number of young gen regions in the collection set plus one
   // entries, since entry 0 keeps track of surviving bytes for non-young regions.
@@ -79,6 +81,8 @@
   _closures = G1EvacuationRootClosures::create_root_closures(this, _g1h);
 
   _oops_into_optional_regions = new G1OopStarChunkedList[_num_optional_regions];
+
+  initialize_numa_stats();
 }
 
 // Pass locally gathered statistics to global state.
@@ -92,6 +96,7 @@
   for (uint i = 0; i < length; i++) {
     surviving_young_words[i] += _surviving_young_words[i];
   }
+  flush_numa_stats();
 }
 
 G1ParScanThreadState::~G1ParScanThreadState() {
@@ -99,6 +104,7 @@
   delete _closures;
   FREE_C_HEAP_ARRAY(size_t, _surviving_young_words_base);
   delete[] _oops_into_optional_regions;
+  FREE_C_HEAP_ARRAY(size_t, _obj_alloc_stat);
 }
 
 size_t G1ParScanThreadState::lab_waste_words() const {
@@ -248,6 +254,8 @@
         return handle_evacuation_failure_par(old, old_mark);
       }
     }
+    update_numa_stats(node_index);
+
     if (_g1h->_gc_tracer_stw->should_report_promotion_events()) {
       // The events are checked individually as part of the actual commit
       report_promotion_event(dest_attr, old, word_sz, age, obj_ptr, node_index);
--- a/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp	Wed Nov 13 10:49:32 2019 -0800
+++ b/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp	Wed Nov 13 10:51:41 2019 -0800
@@ -95,6 +95,13 @@
   size_t _num_optional_regions;
   G1OopStarChunkedList* _oops_into_optional_regions;
 
+  G1NUMA* _numa;
+
+  // Records how many object allocations happened at each node during copy to survivor.
+  // Only starts recording when log of gc+heap+numa is enabled and its data is
+  // transferred when flushed.
+  size_t* _obj_alloc_stat;
+
 public:
   G1ParScanThreadState(G1CollectedHeap* g1h,
                        G1RedirtyCardsQueueSet* rdcqs,
@@ -207,6 +214,12 @@
   inline bool is_partially_trimmed() const;
 
   inline void trim_queue_to_threshold(uint threshold);
+
+  // NUMA statistics related methods.
+  inline void initialize_numa_stats();
+  inline void flush_numa_stats();
+  inline void update_numa_stats(uint node_index);
+
 public:
   oop copy_to_survivor_space(G1HeapRegionAttr const region_attr, oop const obj, markWord const old_mark);
 
--- a/src/hotspot/share/gc/g1/g1ParScanThreadState.inline.hpp	Wed Nov 13 10:49:32 2019 -0800
+++ b/src/hotspot/share/gc/g1/g1ParScanThreadState.inline.hpp	Wed Nov 13 10:51:41 2019 -0800
@@ -230,4 +230,30 @@
   return &_oops_into_optional_regions[hr->index_in_opt_cset()];
 }
 
+void G1ParScanThreadState::initialize_numa_stats() {
+  if (_numa->is_enabled()) {
+    LogTarget(Info, gc, heap, numa) lt;
+
+    if (lt.is_enabled()) {
+      uint num_nodes = _numa->num_active_nodes();
+      // Record only if there are multiple active nodes.
+      _obj_alloc_stat = NEW_C_HEAP_ARRAY(size_t, num_nodes, mtGC);
+      memset(_obj_alloc_stat, 0, sizeof(size_t) * num_nodes);
+    }
+  }
+}
+
+void G1ParScanThreadState::flush_numa_stats() {
+  if (_obj_alloc_stat != NULL) {
+    uint node_index = _numa->index_of_current_thread();
+    _numa->copy_statistics(G1NUMAStats::LocalObjProcessAtCopyToSurv, node_index, _obj_alloc_stat);
+  }
+}
+
+void G1ParScanThreadState::update_numa_stats(uint node_index) {
+  if (_obj_alloc_stat != NULL) {
+    _obj_alloc_stat[node_index]++;
+  }
+}
+
 #endif // SHARE_GC_G1_G1PARSCANTHREADSTATE_INLINE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/share/gc/g1/g1RegionsOnNodes.cpp	Wed Nov 13 10:51:41 2019 -0800
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#include "precompiled.hpp"
+#include "gc/g1/g1NUMA.hpp"
+#include "gc/g1/g1RegionsOnNodes.hpp"
+#include "gc/g1/heapRegion.hpp"
+
+G1RegionsOnNodes::G1RegionsOnNodes() : _count_per_node(NULL), _numa(G1NUMA::numa()) {
+  _count_per_node = NEW_C_HEAP_ARRAY(uint, _numa->num_active_nodes(), mtGC);
+  clear();
+}
+
+G1RegionsOnNodes::~G1RegionsOnNodes() {
+  FREE_C_HEAP_ARRAY(uint, _count_per_node);
+}
+
+uint G1RegionsOnNodes::add(HeapRegion* hr) {
+  uint node_index = hr->node_index();
+
+  // Update only if the node index is valid.
+  if (node_index < _numa->num_active_nodes()) {
+    *(_count_per_node + node_index) += 1;
+    return node_index;
+  }
+
+  return G1NUMA::UnknownNodeIndex;
+}
+
+void G1RegionsOnNodes::clear() {
+  for (uint i = 0; i < _numa->num_active_nodes(); i++) {
+    _count_per_node[i] = 0;
+  }
+}
+
+uint G1RegionsOnNodes::count(uint node_index) const {
+  return _count_per_node[node_index];
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/share/gc/g1/g1RegionsOnNodes.hpp	Wed Nov 13 10:51:41 2019 -0800
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#ifndef SHARE_VM_GC_G1_G1REGIONS_HPP
+#define SHARE_VM_GC_G1_G1REGIONS_HPP
+
+#include "memory/allocation.hpp"
+
+class G1NUMA;
+class HeapRegion;
+
+// Contains per node index region count
+class G1RegionsOnNodes : public StackObj {
+  volatile uint* _count_per_node;
+  G1NUMA*        _numa;
+
+public:
+  G1RegionsOnNodes();
+
+  ~G1RegionsOnNodes();
+
+  // Increase _count_per_node for the node of given heap region and returns node index.
+  uint add(HeapRegion* hr);
+
+  void clear();
+
+  uint count(uint node_index) const;
+};
+
+#endif // SHARE_VM_GC_G1_G1REGIONS_HPP
--- a/src/hotspot/share/gc/g1/g1SurvivorRegions.cpp	Wed Nov 13 10:49:32 2019 -0800
+++ b/src/hotspot/share/gc/g1/g1SurvivorRegions.cpp	Wed Nov 13 10:51:41 2019 -0800
@@ -30,17 +30,23 @@
 
 G1SurvivorRegions::G1SurvivorRegions() :
   _regions(new (ResourceObj::C_HEAP, mtGC) GrowableArray<HeapRegion*>(8, true, mtGC)),
-  _used_bytes(0) {}
+  _used_bytes(0),
+  _regions_on_node() {}
 
-void G1SurvivorRegions::add(HeapRegion* hr) {
+uint G1SurvivorRegions::add(HeapRegion* hr) {
   assert(hr->is_survivor(), "should be flagged as survivor region");
   _regions->append(hr);
+  return _regions_on_node.add(hr);
 }
 
 uint G1SurvivorRegions::length() const {
   return (uint)_regions->length();
 }
 
+uint G1SurvivorRegions::regions_on_node(uint node_index) const {
+  return _regions_on_node.count(node_index);
+}
+
 void G1SurvivorRegions::convert_to_eden() {
   for (GrowableArrayIterator<HeapRegion*> it = _regions->begin();
        it != _regions->end();
@@ -54,6 +60,7 @@
 void G1SurvivorRegions::clear() {
   _regions->clear();
   _used_bytes = 0;
+  _regions_on_node.clear();
 }
 
 void G1SurvivorRegions::add_used_bytes(size_t used_bytes) {
--- a/src/hotspot/share/gc/g1/g1SurvivorRegions.hpp	Wed Nov 13 10:49:32 2019 -0800
+++ b/src/hotspot/share/gc/g1/g1SurvivorRegions.hpp	Wed Nov 13 10:51:41 2019 -0800
@@ -25,6 +25,7 @@
 #ifndef SHARE_GC_G1_G1SURVIVORREGIONS_HPP
 #define SHARE_GC_G1_G1SURVIVORREGIONS_HPP
 
+#include "gc/g1/g1RegionsOnNodes.hpp"
 #include "runtime/globals.hpp"
 
 template <typename T>
@@ -35,17 +36,19 @@
 private:
   GrowableArray<HeapRegion*>* _regions;
   volatile size_t             _used_bytes;
+  G1RegionsOnNodes            _regions_on_node;
 
 public:
   G1SurvivorRegions();
 
-  void add(HeapRegion* hr);
+  virtual uint add(HeapRegion* hr);
 
   void convert_to_eden();
 
   void clear();
 
   uint length() const;
+  uint regions_on_node(uint node_index) const;
 
   const GrowableArray<HeapRegion*>* regions() const {
     return _regions;
--- a/src/hotspot/share/gc/g1/heapRegion.cpp	Wed Nov 13 10:49:32 2019 -0800
+++ b/src/hotspot/share/gc/g1/heapRegion.cpp	Wed Nov 13 10:51:41 2019 -0800
@@ -477,9 +477,9 @@
   if (UseNUMA) {
     G1NUMA* numa = G1NUMA::numa();
     if (node_index() < numa->num_active_nodes()) {
-      st->print("|%02d", numa->numa_id(node_index()));
+      st->print("|%d", numa->numa_id(node_index()));
     } else {
-      st->print("|--");
+      st->print("|-");
     }
   }
   st->print_cr("");
--- a/src/hotspot/share/gc/g1/heapRegionManager.cpp	Wed Nov 13 10:49:32 2019 -0800
+++ b/src/hotspot/share/gc/g1/heapRegionManager.cpp	Wed Nov 13 10:51:41 2019 -0800
@@ -26,6 +26,7 @@
 #include "gc/g1/g1Arguments.hpp"
 #include "gc/g1/g1CollectedHeap.inline.hpp"
 #include "gc/g1/g1ConcurrentRefine.hpp"
+#include "gc/g1/g1NUMAStats.hpp"
 #include "gc/g1/heapRegion.hpp"
 #include "gc/g1/heapRegionManager.inline.hpp"
 #include "gc/g1/heapRegionSet.inline.hpp"
@@ -107,10 +108,11 @@
 HeapRegion* HeapRegionManager::allocate_free_region(HeapRegionType type, uint requested_node_index) {
   HeapRegion* hr = NULL;
   bool from_head = !type.is_young();
+  G1NUMA* numa = G1NUMA::numa();
 
-  if (requested_node_index != G1NUMA::AnyNodeIndex && G1NUMA::numa()->is_enabled()) {
+  if (requested_node_index != G1NUMA::AnyNodeIndex && numa->is_enabled()) {
     // Try to allocate with requested node index.
-    hr = _free_list.remove_region_with_node_index(from_head, requested_node_index, NULL);
+    hr = _free_list.remove_region_with_node_index(from_head, requested_node_index);
   }
 
   if (hr == NULL) {
@@ -122,6 +124,10 @@
   if (hr != NULL) {
     assert(hr->next() == NULL, "Single region should not have next");
     assert(is_available(hr->hrm_index()), "Must be committed");
+
+    if (numa->is_enabled() && hr->node_index() < numa->num_active_nodes()) {
+      numa->update_statistics(G1NUMAStats::NewRegionAlloc, requested_node_index, hr->node_index());
+    }
   }
 
   return hr;
--- a/src/hotspot/share/gc/g1/heapRegionManager.hpp	Wed Nov 13 10:49:32 2019 -0800
+++ b/src/hotspot/share/gc/g1/heapRegionManager.hpp	Wed Nov 13 10:51:41 2019 -0800
@@ -192,6 +192,10 @@
     return _free_list.length();
   }
 
+  uint num_free_regions(uint node_index) const {
+    return _free_list.length(node_index);
+  }
+
   size_t total_free_bytes() const {
     return num_free_regions() * HeapRegion::GrainBytes;
   }
--- a/src/hotspot/share/gc/g1/heapRegionSet.cpp	Wed Nov 13 10:49:32 2019 -0800
+++ b/src/hotspot/share/gc/g1/heapRegionSet.cpp	Wed Nov 13 10:51:41 2019 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -24,6 +24,7 @@
 
 #include "precompiled.hpp"
 #include "gc/g1/g1CollectedHeap.inline.hpp"
+#include "gc/g1/g1NUMA.hpp"
 #include "gc/g1/heapRegionRemSet.hpp"
 #include "gc/g1/heapRegionSet.inline.hpp"
 
@@ -101,6 +102,9 @@
     curr->set_next(NULL);
     curr->set_prev(NULL);
     curr->set_containing_set(NULL);
+
+    decrease_length(curr->node_index());
+
     curr = next;
   }
   clear();
@@ -119,6 +123,10 @@
     return;
   }
 
+  if (_node_info != NULL && from_list->_node_info != NULL) {
+    _node_info->add(from_list->_node_info);
+  }
+
   #ifdef ASSERT
   FreeRegionListIterator iter(from_list);
   while (iter.more_available()) {
@@ -220,6 +228,9 @@
     remove(curr);
 
     count++;
+
+    decrease_length(curr->node_index());
+
     curr = next;
   }
 
@@ -267,6 +278,10 @@
   _head = NULL;
   _tail = NULL;
   _last = NULL;
+
+  if (_node_info!= NULL) {
+    _node_info->clear();
+  }
 }
 
 void FreeRegionList::verify_list() {
@@ -303,3 +318,41 @@
   guarantee(_tail == NULL || _tail->next() == NULL, "_tail should not have a next");
   guarantee(length() == count, "%s count mismatch. Expected %u, actual %u.", name(), length(), count);
 }
+
+
+FreeRegionList::FreeRegionList(const char* name, HeapRegionSetChecker* checker):
+  HeapRegionSetBase(name, checker),
+  _node_info(G1NUMA::numa()->is_enabled() ? new NodeInfo() : NULL) {
+
+  clear();
+}
+
+FreeRegionList::~FreeRegionList() {
+  if (_node_info != NULL) {
+    delete _node_info;
+  }
+}
+
+FreeRegionList::NodeInfo::NodeInfo() : _numa(G1NUMA::numa()), _length_of_node(NULL),
+                                       _num_nodes(_numa->num_active_nodes()) {
+  assert(UseNUMA, "Invariant");
+
+  _length_of_node = NEW_C_HEAP_ARRAY(uint, _num_nodes, mtGC);
+}
+
+FreeRegionList::NodeInfo::~NodeInfo() {
+  FREE_C_HEAP_ARRAY(uint, _length_of_node);
+}
+
+void FreeRegionList::NodeInfo::clear() {
+  for (uint i = 0; i < _num_nodes; ++i) {
+    _length_of_node[i] = 0;
+  }
+}
+
+void FreeRegionList::NodeInfo::add(NodeInfo* info) {
+  for (uint i = 0; i < _num_nodes; ++i) {
+    _length_of_node[i] += info->_length_of_node[i];
+  }
+}
+
--- a/src/hotspot/share/gc/g1/heapRegionSet.hpp	Wed Nov 13 10:49:32 2019 -0800
+++ b/src/hotspot/share/gc/g1/heapRegionSet.hpp	Wed Nov 13 10:51:41 2019 -0800
@@ -136,11 +136,33 @@
 // add / remove one region at a time or concatenate two lists.
 
 class FreeRegionListIterator;
+class G1NUMA;
 
 class FreeRegionList : public HeapRegionSetBase {
   friend class FreeRegionListIterator;
 
 private:
+
+  // This class is only initialized if there are multiple active nodes.
+  class NodeInfo : public CHeapObj<mtGC> {
+    G1NUMA* _numa;
+    uint*   _length_of_node;
+    uint    _num_nodes;
+
+  public:
+    NodeInfo();
+    ~NodeInfo();
+
+    inline void increase_length(uint node_index);
+    inline void decrease_length(uint node_index);
+
+    inline uint length(uint index) const;
+
+    void clear();
+
+    void add(NodeInfo* info);
+  };
+
   HeapRegion* _head;
   HeapRegion* _tail;
 
@@ -148,20 +170,23 @@
   // time. It helps to improve performance when adding several ordered items in a row.
   HeapRegion* _last;
 
+  NodeInfo*   _node_info;
+
   static uint _unrealistically_long_length;
 
   inline HeapRegion* remove_from_head_impl();
   inline HeapRegion* remove_from_tail_impl();
 
+  inline void increase_length(uint node_index);
+  inline void decrease_length(uint node_index);
+
 protected:
   // See the comment for HeapRegionSetBase::clear()
   virtual void clear();
 
 public:
-  FreeRegionList(const char* name, HeapRegionSetChecker* checker = NULL):
-    HeapRegionSetBase(name, checker) {
-    clear();
-  }
+  FreeRegionList(const char* name, HeapRegionSetChecker* checker = NULL);
+  ~FreeRegionList();
 
   void verify_list();
 
@@ -182,8 +207,7 @@
   HeapRegion* remove_region(bool from_head);
 
   HeapRegion* remove_region_with_node_index(bool from_head,
-                                            const uint requested_node_index,
-                                            uint* region_node_index);
+                                            uint requested_node_index);
 
   // Merge two ordered lists. The result is also ordered. The order is
   // determined by hrm_index.
@@ -200,6 +224,9 @@
   virtual void verify();
 
   uint num_of_regions_in_range(uint start, uint end) const;
+
+  using HeapRegionSetBase::length;
+  uint length(uint node_index) const;
 };
 
 // Iterator class that provides a convenient way to iterate over the
--- a/src/hotspot/share/gc/g1/heapRegionSet.inline.hpp	Wed Nov 13 10:49:32 2019 -0800
+++ b/src/hotspot/share/gc/g1/heapRegionSet.inline.hpp	Wed Nov 13 10:51:41 2019 -0800
@@ -95,6 +95,8 @@
     _head = hr;
   }
   _last = hr;
+
+  increase_length(hr->node_index());
 }
 
 inline HeapRegion* FreeRegionList::remove_from_head_impl() {
@@ -145,12 +147,14 @@
 
   // remove() will verify the region and check mt safety.
   remove(hr);
+
+  decrease_length(hr->node_index());
+
   return hr;
 }
 
 inline HeapRegion* FreeRegionList::remove_region_with_node_index(bool from_head,
-                                                                 const uint requested_node_index,
-                                                                 uint* allocated_node_index) {
+                                                                 uint requested_node_index) {
   assert(UseNUMA, "Invariant");
 
   const uint max_search_depth = G1NUMA::numa()->max_search_depth();
@@ -202,11 +206,48 @@
   }
 
   remove(cur);
-  if (allocated_node_index != NULL) {
-    *allocated_node_index = cur->node_index();
-  }
+  decrease_length(cur->node_index());
 
   return cur;
 }
 
+inline void FreeRegionList::NodeInfo::increase_length(uint node_index) {
+  if (node_index < _num_nodes) {
+    _length_of_node[node_index] += 1;
+  }
+}
+
+inline void FreeRegionList::NodeInfo::decrease_length(uint node_index) {
+  if (node_index < _num_nodes) {
+    assert(_length_of_node[node_index] > 0,
+           "Current length %u should be greater than zero for node %u",
+           _length_of_node[node_index], node_index);
+    _length_of_node[node_index] -= 1;
+  }
+}
+
+inline uint FreeRegionList::NodeInfo::length(uint node_index) const {
+  return _length_of_node[node_index];
+}
+
+inline void FreeRegionList::increase_length(uint node_index) {
+  if (_node_info != NULL) {
+    return _node_info->increase_length(node_index);
+  }
+}
+
+inline void FreeRegionList::decrease_length(uint node_index) {
+  if (_node_info != NULL) {
+    return _node_info->decrease_length(node_index);
+  }
+}
+
+inline uint FreeRegionList::length(uint node_index) const {
+  if (_node_info != NULL) {
+    return _node_info->length(node_index);
+  } else {
+    return 0;
+  }
+}
+
 #endif // SHARE_GC_G1_HEAPREGIONSET_INLINE_HPP
--- a/test/hotspot/jtreg/gc/g1/numa/TestG1NUMATouchRegions.java	Wed Nov 13 10:49:32 2019 -0800
+++ b/test/hotspot/jtreg/gc/g1/numa/TestG1NUMATouchRegions.java	Wed Nov 13 10:51:41 2019 -0800
@@ -111,8 +111,8 @@
     //    Each 'int' represents a numa id of single HeapRegion (bottom page).
     //    e.g. 1MB heap region, 2MB page size and 2 NUMA nodes system
     //         Check the first set(2 regions)
-    //         0| ...omitted..| 00
-    //         1| ...omitted..| 01
+    //         0| ...omitted..| 0
+    //         1| ...omitted..| 1
     static void checkCase1Pattern(OutputAnalyzer output, int index, long g1HeapRegionSize, long actualPageSize, int[] memoryNodeIds) throws Exception {
         StringBuilder sb = new StringBuilder();
 
@@ -121,7 +121,7 @@
         sb.append("| .* | ");
 
         // Append page node id.
-        sb.append(String.format("%02d", memoryNodeIds[index]));
+        sb.append(memoryNodeIds[index]);
 
         output.shouldMatch(sb.toString());
     }
@@ -132,10 +132,10 @@
     //    printed multiple times for same numa id.
     //    e.g. 1MB heap region, 2MB page size and 2 NUMA nodes system
     //         Check the first set(4 regions)
-    //         0| ...omitted..| 00
-    //         1| ...omitted..| 00
-    //         2| ...omitted..| 01
-    //         3| ...omitted..| 01
+    //         0| ...omitted..| 0
+    //         1| ...omitted..| 0
+    //         2| ...omitted..| 1
+    //         3| ...omitted..| 1
     static void checkCase2Pattern(OutputAnalyzer output, int index, long g1HeapRegionSize, long actualPageSize, int[] memoryNodeIds) throws Exception {
         StringBuilder sb = new StringBuilder();
 
@@ -147,7 +147,7 @@
             sb.append("| .* | ");
 
             // Append page node id.
-            sb.append(String.format("%02d", memoryNodeIds[index]));
+            sb.append(memoryNodeIds[index]);
 
             output.shouldMatch(sb.toString());
             sb.setLength(0);