< prev index next >

src/hotspot/share/memory/metaspace/metachunk.hpp

Print this page
rev 60538 : imported patch jep387-all.patch

@@ -1,7 +1,8 @@
 /*
- * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2020 SAP SE. 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.

@@ -19,155 +20,302 @@
  * 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_MEMORY_METASPACE_METACHUNK_HPP
 #define SHARE_MEMORY_METASPACE_METACHUNK_HPP
 
-#include "memory/metaspace/metabase.hpp"
-#include "memory/metaspace/metaspaceCommon.hpp"
+
+#include "memory/metaspace/counter.hpp"
+#include "memory/metaspace/chunkLevel.hpp"
 #include "utilities/debug.hpp"
 #include "utilities/globalDefinitions.hpp"
 
-class MetachunkTest;
+
+class outputStream;
 
 namespace metaspace {
 
 class VirtualSpaceNode;
 
-//  Metachunk - Quantum of allocation from a Virtualspace
-//    Metachunks are reused (when freed are put on a global freelist) and
-//    have no permanent association to a SpaceManager.
+// A Metachunk is a contiguous metaspace memory region. It is part of
+// a MetaspaceArena, which keeps a list of MetaChunk and allocates via
+// pointer bump from the top element in the list.
+//
+// The Metachunk object itself (the "chunk header") is separated from
+//  the memory region (the chunk payload) it describes. It also can have
+//  no payload (a "dead" chunk). In itself it lives in C-heap, managed
+//  as part of a pool of Metachunk headers (ChunkHeaderPool).
+//
+// -- Metachunk state --
+//
+// A Metachunk is "in-use" if it is part of a MetaspaceArena. That means
+//  its memory is used - or will be used shortly - to hold VM metadata
+//  on behalf of a class loader.
+//
+// A Metachunk is "free" if its payload is currently unused. In that
+//  case it is managed by a chunk freelist (the ChunkManager).
+// 
+// A Metachunk is "dead" if it does not have a corresponding payload.
+//  In that case it lives as part of a freelist-of-dead-chunk-headers
+//  in the ChunkHeaderPool.
+//
+// -- Level --
+//
+// Metachunks are managed as part of a buddy style allocation scheme.
+// Sized always in steps of power-of-2, ranging from the smallest chunk size
+// (1Kb) to the largest (4Mb) (see chunklevel.hpp).
+// Its size is encoded as level, with level 0 being the largest chunk
+// size ("root chunk").
+//
+// -- Payload commit state --
+//
+// A Metachunk payload may be committed, partly committed or completely
+// uncommitted. Technically, a payload may be committed "checkered" -
+// i.e. committed and uncommitted parts may interleave - but the
+// important part is how much contiguous space is committed starting
+// at the base of the payload (since that's where we allocate). 
+// 
+// The Metachunk keeps track of how much space is committed starting
+//  at the base of the payload - which is a performace optimization - 
+//  while underlying layers (VirtualSpaceNode->commitmask) keep track
+//  of the "real" commit state, aka which granules are committed,
+//  independent on what chunks reside above those granules.
+
 
-//            +--------------+ <- end    --+       --+
+//            +--------------+ <- end    -----------+ ----------+
 //            |              |             |         |
-//            |              |             | free    |
 //            |              |             |         |
-//            |              |             |         | size | capacity
 //            |              |             |         |
-//            |              | <- top   -- +         |
 //            |              |             |         |
-//            |              |             | used    |
 //            |              |             |         |
+//            | -----------  | <- committed_top  -- +           |
 //            |              |             |         |
-//            +--------------+ <- bottom --+       --+
-
-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
-};
+//            |              |                      | "free"    |
+//            |              |                      |           | size 
+//            |              |     "free_below_     |           |
+//            |              |        committed"    |           |
+//            |              |                      |           |
+//            |              |                      |           |
+//            | -----------  | <- top     --------- + --------  |
+//            |              |                      |           |
+//            |              |     "used"           |           |
+//            |              |                      |           |
+//            +--------------+ <- start   ----------+ ----------+
 
-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> {
-
-  friend class ::MetachunkTest;
-
-  // The VirtualSpaceNode containing this chunk.
-  VirtualSpaceNode* const _container;
-
-  // Current allocation top.
-  MetaWord* _top;
-
-  // A 32bit sentinel for debugging purposes.
-  enum { CHUNK_SENTINEL = 0x4d4554EF,  // "MET"
-         CHUNK_SENTINEL_INVALID = 0xFEEEEEEF
+// Note: this is a chunk **descriptor**. The real Payload area lives in metaspace,
+// this class lives somewhere else.
+class Metachunk {
+
+  // start of chunk memory; NULL if dead.
+  MetaWord* _base;
+
+  // Used words.
+  size_t _used_words;
+
+  // Size of the region, starting from base, which is guaranteed to be committed. In words.
+  //  The actual size of committed regions may actually be larger.
+  //
+  //  (This is a performance optimization. The underlying VirtualSpaceNode knows
+  //   which granules are committed; but we want to avoid having to ask.)
+  size_t _committed_words;
+
+  chunklevel_t _level; // aka size.
+
+  // state_free:    free, owned by a ChunkManager
+  // state_in_use:  in-use, owned by a MetaspaceArena
+  // dead:          just a hollow chunk header without associated memory, owned
+  //                 by chunk header pool.
+  enum state_t {
+    state_free = 0,
+    state_in_use = 1,
+    state_dead = 2
   };
+  state_t _state;
 
-  uint32_t _sentinel;
-
-  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;
-
-  ChunkOrigin _origin;
-  int _use_count;
-
-  MetaWord* initial_top() const { return (MetaWord*)this + overhead(); }
-  MetaWord* top() const         { return _top; }
-
- 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.
+  // We need unfortunately a back link to the virtual space node
+  // for splitting and merging nodes.
+  VirtualSpaceNode* _vsnode;
+
+
+  // A chunk header is kept in a list:
+  // 1 in the list of used chunks inside a MetaspaceArena, if it is in use
+  // 2 in the list of free chunks inside a ChunkManager, if it is free
+  // 3 in the freelist of unused headers inside the ChunkHeaderPool,
+  //   if it is unused (e.g. result of chunk merging) and has no associated
+  //   memory area.
+  Metachunk* _prev;
+  Metachunk* _next;
+
+  // Furthermore, we keep, per chunk, information about the neighboring chunks.
+  // This is needed to split and merge chunks.
+  //
+  // Note: These members can be modified concurrently while a chunk is alive and in use.
+  // This can happen if a neighboring chunk is added or removed.
+  // This means only read or modify these members under expand lock protection.
+  Metachunk* _prev_in_vs;
+  Metachunk* _next_in_vs;
+
+  // Commit uncommitted section of the chunk.
+  // Fails if we hit a commit limit.
+  bool commit_up_to(size_t new_committed_words);
+
+  DEBUG_ONLY(static void assert_have_expand_lock();)
+
+public:
+
+  Metachunk()
+    : _base(NULL),
+      _used_words(0),
+      _committed_words(0),
+      _level(chunklevel::ROOT_CHUNK_LEVEL),
+      _state(state_free),
+      _vsnode(NULL),
+      _prev(NULL), _next(NULL),
+      _prev_in_vs(NULL), _next_in_vs(NULL)
+  {}
+
+ void clear() {
+   _base = NULL;
+   _used_words = 0;
+   _committed_words = 0;
+   _level = chunklevel::ROOT_CHUNK_LEVEL;
+   _state = state_free;
+   _vsnode = NULL;
+   _prev = NULL;
+   _next = NULL;
+   _prev_in_vs = NULL;
+   _next_in_vs = NULL;
+  }
+
+
+  size_t word_size() const        { return chunklevel::word_size_for_level(_level); }
+
+  MetaWord* base() const          { return _base; }
+  MetaWord* top() const           { return base() + _used_words; }
+  MetaWord* committed_top() const { return base() + _committed_words; }
+  MetaWord* end() const           { return base() + word_size(); }
+
+  // Chunk list wiring
+  void set_prev(Metachunk* c)     { _prev = c; }
+  Metachunk* prev() const         { return _prev; }
+  void set_next(Metachunk* c)     { _next = c; }
+  Metachunk* next() const         { return _next; }
+
+  DEBUG_ONLY(bool in_list() const { return _prev != NULL || _next != NULL; })
+
+  // Physical neighbors wiring
+  void set_prev_in_vs(Metachunk* c) { DEBUG_ONLY(assert_have_expand_lock()); _prev_in_vs = c; }
+  Metachunk* prev_in_vs() const     { DEBUG_ONLY(assert_have_expand_lock()); return _prev_in_vs; }
+  void set_next_in_vs(Metachunk* c) { DEBUG_ONLY(assert_have_expand_lock()); _next_in_vs = c; }
+  Metachunk* next_in_vs() const     { DEBUG_ONLY(assert_have_expand_lock()); return _next_in_vs; }
+
+  bool is_free() const            { return _state == state_free; }
+  bool is_in_use() const          { return _state == state_in_use; }
+  bool is_dead() const            { return _state == state_dead; }
+  void set_free()                 { _state = state_free; }
+  void set_in_use()               { _state = state_in_use; }
+  void set_dead()                 { _state = state_dead; }
+
+  // Return a single char presentation of the state ('f', 'u', 'd')
+  char get_state_char() const;
+
+  void inc_level()                { _level ++; DEBUG_ONLY(chunklevel::is_valid_level(_level);) }
+  void dec_level()                { _level --; DEBUG_ONLY(chunklevel::is_valid_level(_level);) }
+  chunklevel_t level() const          { return _level; }
+
+  // Convenience functions for extreme levels.
+  bool is_root_chunk() const      { return chunklevel::ROOT_CHUNK_LEVEL == _level; }
+  bool is_leaf_chunk() const      { return chunklevel::HIGHEST_CHUNK_LEVEL == _level; }
+
+  VirtualSpaceNode* vsnode() const        { return _vsnode; }
+
+  size_t used_words() const                   { return _used_words; }
+  size_t free_words() const                   { return word_size() - used_words(); }
+  size_t free_below_committed_words() const   { return committed_words() - used_words(); }
+  void reset_used_words()                     { _used_words = 0; }
+
+  size_t committed_words() const      { return _committed_words; }
+  void set_committed_words(size_t v);
+  bool is_fully_committed() const     { return committed_words() == word_size(); }
+  bool is_fully_uncommitted() const   { return committed_words() == 0; }
+
+  // Ensure that chunk is committed up to at least new_committed_words words.
+  // Fails if we hit a commit limit.
+  bool ensure_committed(size_t new_committed_words);
+  bool ensure_committed_locked(size_t new_committed_words);
+
+  bool ensure_fully_committed()           { return ensure_committed(word_size()); }
+  bool ensure_fully_committed_locked()    { return ensure_committed_locked(word_size()); }
+
+  // Ensure that the chunk is committed far enough to serve an additional allocation of word_size.
+  bool ensure_committed_additional(size_t additional_word_size)   {
+    return ensure_committed(used_words() + additional_word_size);
+  }
+
+  // Uncommit chunk area. The area must be a common multiple of the
+  // commit granule size (in other words, we cannot uncommit chunks smaller than
+  // a commit granule size).
+  void uncommit();
+  void uncommit_locked();
+
+  // Allocation from a chunk
+
+  // Allocate word_size words from this chunk (word_size must be aligned to
+  //  allocation_alignment_words).
+  //
+  // Caller must make sure the chunk is both large enough and committed far enough
+  // to hold the allocation. Will always work.
+  //
+  MetaWord* allocate(size_t request_word_size);
+
+  // Initialize structure for reuse.
+  void initialize(VirtualSpaceNode* node, MetaWord* base, chunklevel_t lvl) {
+    _vsnode = node; _base = base; _level = lvl;
+    _used_words = _committed_words = 0; _state = state_free;
+    _next = _prev = _next_in_vs = _prev_in_vs = NULL;
+  }
+
+  // Returns true if this chunk is the leader in its buddy pair, false if not.
+  // Do not call for root chunks.
+  bool is_leader() const {
+    assert(!is_root_chunk(), "Root chunks have no buddy."); // Bit harsh?
+    return is_aligned(base(), chunklevel::word_size_for_level(level() - 1) * BytesPerWord);
+  }
+
+  //// Debug stuff ////
+#ifdef ASSERT
+  void verify(bool slow) const;
+  // Verifies linking with neighbors in virtual space. Needs expand lock protection.
+  void verify_neighborhood() const;
+  void zap_header(uint8_t c = 0x17);
+  void fill_with_pattern(MetaWord pattern, size_t word_size);
+  void check_pattern(MetaWord pattern, size_t word_size);
+
+  // Returns true if given pointer points into the payload area of this chunk.
+  bool is_valid_pointer(const MetaWord* p) const {
+    return base() <= p && p < top();
+  }
+
+  // Returns true if given pointer points into the commmitted payload area of this chunk.
+  bool is_valid_committed_pointer(const MetaWord* p) const {
+    return base() <= p && p < committed_top();
+  }
 
-  // Alignment of each allocation in the chunks.
-  static size_t object_alignment();
-
-  // 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);
-
-  VirtualSpaceNode* container() const { return _container; }
-
-  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(); }
-
-  // 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;
-
-  bool is_tagged_free() { return _is_tagged_free; }
-  void set_is_tagged_free(bool v) { _is_tagged_free = v; }
-
-  bool contains(const void* ptr) { return bottom() <= ptr && ptr < _top; }
+#endif // ASSERT
 
   void print_on(outputStream* st) const;
 
-  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; }
-
-  DEBUG_ONLY(void mangle(juint word_value);)
-  DEBUG_ONLY(void verify() const;)
-
 };
 
+// Little print helpers: since we often print out chunks, here some convenience macros
+#define METACHUNK_FORMAT                "@" PTR_FORMAT ", %c, base " PTR_FORMAT ", level " CHKLVL_FORMAT
+#define METACHUNK_FORMAT_ARGS(chunk)    p2i(chunk), chunk->get_state_char(), p2i(chunk->base()), chunk->level()
 
-// 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);
+#define METACHUNK_FULL_FORMAT                "@" PTR_FORMAT ", %c, base " PTR_FORMAT ", level " CHKLVL_FORMAT " (" SIZE_FORMAT "), used: " SIZE_FORMAT ", committed: " SIZE_FORMAT ", committed-free: " SIZE_FORMAT
+#define METACHUNK_FULL_FORMAT_ARGS(chunk)    p2i(chunk), chunk->get_state_char(), p2i(chunk->base()), chunk->level(), chunk->word_size(), chunk->used_words(), chunk->committed_words(), chunk->free_below_committed_words()
 
 } // namespace metaspace
 
 #endif // SHARE_MEMORY_METASPACE_METACHUNK_HPP
< prev index next >