src/hotspot/share/memory/metaspace/metachunk.hpp
branchstuefe-improved-metaspace
changeset 57464 32e61f51ee09
parent 53244 9807daeb47c4
--- a/src/hotspot/share/memory/metaspace/metachunk.hpp	Wed Jul 10 05:12:23 2019 +0100
+++ b/src/hotspot/share/memory/metaspace/metachunk.hpp	Mon Jul 08 22:30:19 2019 +0200
@@ -26,6 +26,7 @@
 
 #include "memory/metaspace/metabase.hpp"
 #include "memory/metaspace/metaspaceCommon.hpp"
+#include "memory/metaspace/chunkLevel.hpp"
 #include "utilities/debug.hpp"
 #include "utilities/globalDefinitions.hpp"
 
@@ -39,78 +40,74 @@
 //    Metachunks are reused (when freed are put on a global freelist) and
 //    have no permanent association to a SpaceManager.
 
-//            +--------------+ <- end    --+       --+
-//            |              |             |         |
-//            |              |             | free    |
-//            |              |             |         |
-//            |              |             |         | size | capacity
-//            |              |             |         |
-//            |              | <- top   -- +         |
-//            |              |             |         |
-//            |              |             | used    |
-//            |              |             |         |
-//            |              |             |         |
-//            +--------------+ <- bottom --+       --+
+//            +--------------+ <- end    ----+         --+
+//            |              |               |           |
+//            |              |               | free      |
+//            | -----------  | <- commit_top |           |
+//            |              |               |           | size (aka capacity)
+//            |              |               |           |
+//            | -----------  | <- top     -- +           |
+//            |              |               |           |
+//            |              |               | used      |
+//            +--------------+ <- start   -- +           |
+//            |   header     |               | overhead  |
+//            +--------------+ <- base   ----+         --+
 
-enum ChunkOrigin {
-  // Chunk normally born (via take_from_committed)
-  origin_normal = 1,
-  // Chunk was born as padding chunk
-  origin_pad = 2,
-  // Chunk was born as leftover chunk in VirtualSpaceNode::retire
-  origin_leftover = 3,
-  // Chunk was born as result of a merge of smaller chunks
-  origin_merge = 4,
-  // Chunk was born as result of a split of a larger chunk
-  origin_split = 5,
 
-  origin_minimum = origin_normal,
-  origin_maximum = origin_split,
-  origins_count = origin_maximum + 1
-};
-
-inline bool is_valid_chunkorigin(ChunkOrigin origin) {
-  return origin == origin_normal ||
-    origin == origin_pad ||
-    origin == origin_leftover ||
-    origin == origin_merge ||
-    origin == origin_split;
-}
-
-class Metachunk : public Metabase<Metachunk> {
+class Metachunk {
 
   friend class ::MetachunkTest;
 
-  // The VirtualSpaceNode containing this chunk.
-  VirtualSpaceNode* const _container;
+  // A metachunk is kept in a list
+  Metachunk* _prev;
+  Metachunk* _next;
 
-  // Current allocation top.
-  MetaWord* _top;
+  chklvl_t _level; // aka size.
+
+  bool _is_free;
 
-  // A 32bit sentinel for debugging purposes.
-  enum { CHUNK_SENTINEL = 0x4d4554EF,  // "MET"
-         CHUNK_SENTINEL_INVALID = 0xFEEEEEEF
-  };
+  // Committed words, including header.
+  size_t _committed_words;
+
+  // Used words, including header.
+  size_t _used_words;
 
-  uint32_t _sentinel;
+  // "abandoned" committed words
+  // Number of words committed beyond the commit_top(). This
+  // can be the result of merging two half-committed chunks.
+  // We need to track the number, but not the exact location
+  // (see merging/splitting)
+  size_t _abandoned_committed_words;
 
-  const ChunkIndex _chunk_type;
-  const bool _is_class;
-  // Whether the chunk is free (in freelist) or in use by some class loader.
-  bool _is_tagged_free;
+#ifdef ASSERT
+  // A 32bit sentinel for debugging purposes.
+  static const uint32_t CHUNK_SENTINEL = 0x4d4554EF;  // "MET"
+  static const uint32_t CHUNK_SENTINEL_INVALID = 0xFEEEEEEF;
+  uint32_t _sentinel;
+#endif
 
-  ChunkOrigin _origin;
-  int _use_count;
+  MetaWord* base() const        { return (MetaWord*)this; }
+  MetaWord* start() const       { return (MetaWord*)this + overhead(); }
+  MetaWord* top() const         { return base() + _used_words; }
+  MetaWord* commit_top() const  { return base() + _committed_words; }
+  MetaWord* end() const         { return (MetaWord*)this + word_size(); }
 
-  MetaWord* initial_top() const { return (MetaWord*)this + overhead(); }
-  MetaWord* top() const         { return _top; }
+  // A "root chunk" is a chunk of the highest level. It cannot coalesce further
+  // and has no buddy.
+  bool is_root_chunk() const    { return _level == HIGHEST_CHUNK_LEVEL; }
+
+  // expand the committed range of this chunk to hold at least word_size additional words;
+  // Returns false if failed, which may be e.g. due to hitting a limit.
+  bool expand_committed(size_t requested_word_size, bool& did_hit_commit_limit);
 
- public:
-  // Metachunks are allocated out of a MetadataVirtualSpace and
-  // and use some of its space to describe itself (plus alignment
-  // considerations).  Metadata is allocated in the rest of the chunk.
-  // This size is the overhead of maintaining the Metachunk within
-  // the space.
+  // Returns the location of the buddy, or NULL if this is a root chunk which
+  // has no buddy.
+  Metachunk* get_buddy_address() const;
+
+public:
+
+  // Create a chunk with a given level and a given commit size.
+  Metachunk(chklvl_t level, size_t committed_words);
 
   // Alignment of each allocation in the chunks.
   static size_t object_alignment();
@@ -118,56 +115,145 @@
   // Size of the Metachunk header, in words, including alignment.
   static size_t overhead();
 
-  Metachunk(ChunkIndex chunktype, bool is_class, size_t word_size, VirtualSpaceNode* container);
-
-  MetaWord* allocate(size_t word_size);
+  chklvl_t level() const              { return _level; }
 
-  VirtualSpaceNode* container() const { return _container; }
+  // Returns size, in bytes, of this chunk including the header.
+  size_t size() const {
+    return MIN_CHUNK_BYTE_SIZE << _level;
+  }
 
-  MetaWord* bottom() const { return (MetaWord*) this; }
 
   // Reset top to bottom so chunk can be reused.
-  void reset_empty() { _top = initial_top(); clear_next(); clear_prev(); }
-  bool is_empty() { return _top == initial_top(); }
+  void reset()    { _used_words = 0; }
+
+
+  // Returns the total word size of this chunk, including header.
+  size_t word_size() const                { return size() / sizeof(MetaWord); }
+
+  // Returns the used words in this chunk, including header.
+  size_t used_word_size() const           { return _used_words; }
+
+  // Returns the number of free words below the commit top
+  // (how much can be allocated from this chunk without commit).
+  size_t free_word_size_no_commit() const { return _committed_words - _used_words; }
+
+  // Returns the number of free words in total, including uncommitted area.
+  size_t free_word_size_total() const     { return word_size() - _used_words; }
+
+  bool is_empty() const             { return _used_words == 0; }
+  bool is_fully_committed() const   { return _committed_words == word_size(); }
+
+  /////////////
+  // Allocation, commit, uncommit
+
+  // Allocate from chunk.
+  // This may fail and return NULL due to the following reasons:
+  // - the chunk may be too small to hold the allocation (did_hit_commit_limit will be false).
+  // - chunk needed to expand his commit top to hold the allocation, but that failed because we hit a
+  //   limit (GC threshold or metaspace limit)  (did_hit_commit_limit will be true).
+  // Returns pointer to allocation, or NULL.
+  MetaWord* allocate(size_t requested_word_size, bool& did_hit_commit_limit);
+
+  /////////////
+  // Splitting
+
+  // Given a chunk c, split it two chunks.
+  // - chunk must be free
+  // - chunk must be committed far enough to include the header of the new
+  //   buddy chunk
+  // - if chunk is of the smallest size already and cannot split anymore,
+  //   false is ret
+  // Returns true if success.
+  // This operation succeeds unless the chunk is of the smallest level already
+  //  in which case NULL is returned.
+  static bool split(Metachunk* c, Metachunk** p_leader, Metachunk** p_follower);
+
+  /////////////
+  // Merging
 
-  // used (has been allocated)
-  // free (available for future allocations)
-  size_t word_size() const { return size(); }
-  size_t used_word_size() const;
-  size_t free_word_size() const;
+  // Chunk merging means a chunk is merged with its buddy. The resulting
+  //  chunk occupies the area of both former chunks. Its level will be increased by 1.
+  //  (x marks the header):
+  //
+  // before:  |x      |x      |
+  //
+  // after:   |x              |
+  //
+  // The result chunk will be committed according to following rules:
+  //  1) if the first chunk was completely committed, result chunk will be as far committed
+  //     as the second chunk was committed (dashes mark the committed area):
+  //
+  // before:  |-------|----   |
+  //
+  // after:   |------------   |
+  //
+  //    Obviously, this means that if the second chunk was completely committed, the
+  //    result chunk will be completely committed too:
+  //
+  // before:  |-------|-------|
+  //
+  // after:   |---------------|
+  //
+  //  2) if the first chunk was not completely committed, result chunk will be as far committed
+  //     as the first chunk, and will carry over the size of the committed area of the second chunk
+  //     in the "_abandoned_committed_words" member:
+  //
+  // before:  |---    |------ |  _abandoned_committed_words = 0
+  //
+  // after:   |---            |  _abandoned_committed_words = sizeof(------)
+  //
+  // _abandoned_committed_words accumulates with each subsequent merge. After a sequence of merges,
+  // it carries over the sum of all committed "abandoned" regions in the follower chunks.
+  //
+  //
 
-  bool is_tagged_free() { return _is_tagged_free; }
-  void set_is_tagged_free(bool v) { _is_tagged_free = v; }
+  // Attempt to merge this chunk with its buddy.
+  // This succeeds if:
+  // - the chunk is not of the highest level (root chunks have no buddies).
+  // - the buddy is free
+  // If successful, a pointer to the new chunk is returned.
+  //   !! In that case, the original "this" pointer will be invalid; do not access it anymore !!
+  // If failed, will return NULL.
+  Metachunk* try_merge();
+
+  /////////////
+  // Free/in use
+
+  bool is_free() const  { return _is_free; }
+  void set_free()       { _is_free = true; }
+  void set_in_use()     { _is_free = false; }
+
 
-  bool contains(const void* ptr) { return bottom() <= ptr && ptr < _top; }
+  /////////////
+  // List stuff
+
+  void set_prev(Metachunk* c)   { _prev = c; }
+  void set_next(Metachunk* c)   { _next = c; }
+  Metachunk* prev() const       { return _prev; }
+  Metachunk* next() const       { return _next; }
+  // Remove chunk from whatever list it lives in by wiring next with previous.
+  // In debug case, zeros out _next, _prev.
+  void remove_from_list();
+
+  //////////////
+  // Debugging aids
+
+  bool contains(const void* ptr) const {
+    return base() <= ptr && ptr < top();
+  }
 
   void print_on(outputStream* st) const;
 
+#ifdef ASSERT
   bool is_valid_sentinel() const        { return _sentinel == CHUNK_SENTINEL; }
   void remove_sentinel()                { _sentinel = CHUNK_SENTINEL_INVALID; }
-
-  int get_use_count() const             { return _use_count; }
-  void inc_use_count()                  { _use_count ++; }
-
-  ChunkOrigin get_origin() const        { return _origin; }
-  void set_origin(ChunkOrigin orig)     { _origin = orig; }
-
-  ChunkIndex get_chunk_type() const     { return _chunk_type; }
-  bool is_class() const                 { return _is_class; }
+#endif
 
   DEBUG_ONLY(void mangle(juint word_value);)
   DEBUG_ONLY(void verify() const;)
 
 };
 
-
-// Helper function that does a bunch of checks for a chunk.
-DEBUG_ONLY(void do_verify_chunk(Metachunk* chunk);)
-
-// Given a Metachunk, update its in-use information (both in the
-// chunk and the occupancy map).
-void do_update_in_use_info_for_chunk(Metachunk* chunk, bool inuse);
-
 } // namespace metaspace
 
 #endif // SHARE_MEMORY_METASPACE_METACHUNK_HPP