Java metaspace source parsing

本文基于openjdk11及hotspot

From the start Java8, JVM in order to be replaced with permanent generation of metaspace, herein JVM source according to the initialization of the metaspace, memory allocation, freeing memory three main processes for resolution.

1. Data Structure

Following some concepts, metaspace, classLoaderMetaspace, virtualSpace, metachunk, chunkManager, spaceManager, metablock in the metaspace. First, let's look at the contents of each data structure,

1.1 metaspace

// hotspot/share/memory/metaspace.hpp
class Metaspace : public AllStatic {
  static metaspace::VirtualSpaceList* _space_list;
  static metaspace::VirtualSpaceList* _class_space_list;

  static metaspace::ChunkManager* _chunk_manager_metadata;
  static metaspace::ChunkManager* _chunk_manager_class;
}
复制代码

Metaspace is a class that contains only static properties and static methods, looks more like a utility class. Inside it contains important VirtualSpaceList and ChunkManager, and ChunkManager is not difficult to see VirtualSpaceList globally shared.

The difference space_list and class_space_list

Both correspond to the area of ​​memory, the name can be seen in class_space_list java class is used to store data. But in fact, not entirely correct, only if compression pointers in force, class_space_list will exist, or class data will also be stored in space_list in. That is in fact the JVM metaspace area is actually divided into two --Class area and NonClass area.

Similarly, chunk_manager_metadata corresponds NonClass, chunk_manager_class corresponds Class.

1.2 classLoaderMetaspace

In Java, each ClassLoader instance (including bootstrapClassLoader) will have a separate area in the metaspace called classLoaderMetaspace. classLoaderMetaspace data structure as follows:

class ClassLoaderMetaspace : public CHeapObj<mtClass> {
  metaspace::SpaceManager* _vsm;
  metaspace::SpaceManager* _class_vsm;
}
复制代码

Each instance will have a ClassLoaderMetaspace SpaceManager (possibly a classSpaceManager), for processing ClassLoaderMetaspace memory allocation.

classLoaderMetaspace type

Some classLoaderMetaspace plurality of types, respectively, corresponding to different ClassLoader

name The corresponding ClassLoader
StandardMetaspace Ordinary ClassLoader
BootMetaspace BootstrapClassLoader
AnonymousMetaspace Anonymous ClassLoader
ReflectionMetaspace Reflection ClassLoader

Between different types of metaspace not very different, the main difference lies in the size of the chunk they create.

1.3 virtualSpace

virtualSpace composed metaspace space allocated to the shared ClassLoaderMetaspace used as a list. Data is structured as follows:

class VirtualSpace {
  // Reserved area
  char* _low_boundary;
  char* _high_boundary;

  // Committed area
  char* _low;
  char* _high;
  
  // MPSS Support
  char* _lower_high;
  char* _middle_high;
  char* _upper_high;

  char* _lower_high_boundary;
  char* _middle_high_boundary;
  char* _upper_high_boundary;
}
复制代码

In order to divide the three regions under the virtualSpace, as shown in FIG.

-----------------  upper_high_boundary / high_boundary
| unused |      |
|--------|  上  |- upper_high
|  used  |      |
-----------------  middle_high_boundary
| unused |      |
|--------|  中  |- middle_high
|  used  |      |
-----------------  lower_high_boundary
| unused |      |
|--------|  下  |- lower_high
|  used  |      |
-----------------  low_boundary
复制代码

The difference between the three regions, not careful study herein.

1.4 metachunk

metachunk is ClassLoaderMetaspace dispensed from VirtualSpace area of ​​memory, each ClassLoaderMetaspace by spaceManager will hold a metachunk list, indicating that all it holds metaspace memory, all memory of the same application classLoader also be carried out in all the chunk.

In the JVM from small to large chunk into four types, and its corresponding chunk size in the following table,

chunk type Class (unit: word) NonClass (unit: word)
specialized 128 128
small 256 512
medium 4K 8K
humongous No fixed size No fixed size

1.5 chunkManager

chunkManager that has been released for the chunk, for repeated use, the following data structure:

class ChunkManager : public CHeapObj<mtInternal> {
  ChunkList _free_chunks[NumberOfFreeLists];
  ChunkTreeDictionary _humongous_dictionary;
}
复制代码

Which free_chunks[]is used to store special, small, medium three types of chunk, and humongous_dictionaryfor storing the type of humongous chunk. The first three are of fixed size, thus directly using the storage arrays, and humongous is no fixed size, the use of ordered binary tree is stored.

1.6 spaceManager

Each corresponds to a NonClassSpaceManager classLoaderMetaspace and a ClassSpaceManager, SpaceManager list stored in the metablock classLoaderMetaspace currently used chunk information, and a release for re-use. Meanwhile classLoaderMetaspace final memory allocation is handled by the spaceManager. Main data structure is as follows:

class SpaceManager : public CHeapObj<mtClass> {
  Metachunk* _chunk_list;
  Metachunk* _current_chunk;
  BlockFreelist* _block_freelists;
}
复制代码

1.7 metablock

metablock is allocated by the memory metachunk out for final use. Memory block after the release of those reusable in BlockFreeList spaceManager's.

Figure 1.8 Total

image-20190809164533218

2. initialization process

JVM metaspace initialization into the initialization metaspace and classLoaderMetaspace. We turn to see both initialization,

2.1 metaspace initialization

Called when initialization metaspace divided into three steps, first Arguments :: apply_ergo () Metaspace :: ergo_initialize (), then call Metaspace :: global_initialize (when) in universe_init (), the last call Metaspace :: post_initialize (). These three steps are executed during JVM initialization. We turn to see this three-step initialization process,

ergo_initialize

void Metaspace::ergo_initialize() {
  if (DumpSharedSpaces) {
    FLAG_SET_ERGO(bool, UseLargePagesInMetaspace, false);
  }

  size_t page_size = os::vm_page_size();
  if (UseLargePages && UseLargePagesInMetaspace) {
    page_size = os::large_page_size();
  }

  _commit_alignment  = page_size;
  _reserve_alignment = MAX2(page_size, (size_t)os::vm_allocation_granularity());
  
  MaxMetaspaceSize = align_down_bounded(MaxMetaspaceSize, _reserve_alignment);

  if (MetaspaceSize > MaxMetaspaceSize) {
    MetaspaceSize = MaxMetaspaceSize;
  }

  MetaspaceSize = align_down_bounded(MetaspaceSize, _commit_alignment);

  assert(MetaspaceSize <= MaxMetaspaceSize, "MetaspaceSize should be limited by MaxMetaspaceSize");

  MinMetaspaceExpansion = align_down_bounded(MinMetaspaceExpansion, _commit_alignment);
  MaxMetaspaceExpansion = align_down_bounded(MaxMetaspaceExpansion, _commit_alignment);

  CompressedClassSpaceSize = align_down_bounded(CompressedClassSpaceSize, _reserve_alignment);

  size_t min_metaspace_sz =
      VIRTUALSPACEMULTIPLIER * InitialBootClassLoaderMetaspaceSize;
  if (UseCompressedClassPointers) {
    if ((min_metaspace_sz + CompressedClassSpaceSize) >  MaxMetaspaceSize) {
      if (min_metaspace_sz >= MaxMetaspaceSize) {
        vm_exit_during_initialization("MaxMetaspaceSize is too small.");
      } else {
        FLAG_SET_ERGO(size_t, CompressedClassSpaceSize,
                      MaxMetaspaceSize - min_metaspace_sz);
      }
    }
  } else if (min_metaspace_sz >= MaxMetaspaceSize) {
    FLAG_SET_ERGO(size_t, InitialBootClassLoaderMetaspaceSize,
                  min_metaspace_sz);
  }

  set_compressed_class_space_size(CompressedClassSpaceSize);
}
复制代码

Ergo the initialization process is mainly provided some global variables, e.g. MaxMetaspaceSize, MinMetaspaceExpansion, MaxMetaspaceExpansion and CompressedClassSpaceSize. The more important thing is MaxMetaspaceSize and CompressedClassSpaceSize, CompressedClassSpaceSize the default size is 1G (meet globals.hpp).

global_initialize

Global initialization is primarily used to initialize VirtualSpaceList and ChunkManager. Wherein the first node ClassVirtualSpaceList size is directly allocated CompressedClassSpaceSize (without considering UseSharedSpaces open mode). The size of the head node is assigned NonClassVirtualSpaceList 4M * 8/2 (64-bit machines) or 2200K / 4 * 2 (32-bit machines). Source code had a lot of computing aligned source, the more long-winded, not here show.

post_initialize

void Metaspace::post_initialize() {
  MetaspaceGC::post_initialize();
}
复制代码

post initialization is mainly used for initialization MetaspaceGC, this article is not concerned Metaspace the GC, so this section is not discussed.

2.2 classLoaderMetaspace initialization

ClassLoaderMetaspace different initialization and the initialization of the metaspace, metaspace is started when the JVM has been initialized, the initialization is classLoaderMetaspace when its corresponding classLoader will need to initialize the metaspace, as follows:

ClassLoaderMetaspace* ClassLoaderData::metaspace_non_null() {
  ClassLoaderMetaspace* metaspace = OrderAccess::load_acquire(&_metaspace);
  if (metaspace == NULL) {
    MutexLockerEx ml(_metaspace_lock,  Mutex::_no_safepoint_check_flag);
    // Check if _metaspace got allocated while we were waiting for this lock.
    if ((metaspace = _metaspace) == NULL) {
      if (this == the_null_class_loader_data()) {
        assert (class_loader() == NULL, "Must be");
        metaspace = new ClassLoaderMetaspace(_metaspace_lock, Metaspace::BootMetaspaceType);
      } else if (is_anonymous()) {
        metaspace = new ClassLoaderMetaspace(_metaspace_lock, Metaspace::AnonymousMetaspaceType);
      } else if (class_loader()->is_a(SystemDictionary::reflect_DelegatingClassLoader_klass())) {
        metaspace = new ClassLoaderMetaspace(_metaspace_lock, Metaspace::ReflectionMetaspaceType);
      } else {
        metaspace = new ClassLoaderMetaspace(_metaspace_lock, Metaspace::StandardMetaspaceType);
      }
      OrderAccess::release_store(&_metaspace, metaspace);
    }
  }
  return metaspace;
}
复制代码

In this code, we can see four kinds ClassLoaderMetaspace type ClassLoader correspond respectively to four.


Next is the initialization process classLoaderMetaspace,

void ClassLoaderMetaspace::initialize(Mutex* lock, Metaspace::MetaspaceType type) {
  Metaspace::verify_global_initialization();

  DEBUG_ONLY(Atomic::inc(&g_internal_statistics.num_metaspace_births));

  _vsm = new SpaceManager(Metaspace::NonClassType, type, lock)
  if (Metaspace::using_class_space()) {
    _class_vsm = new SpaceManager(Metaspace::ClassType, type, lock);
  }

  MutexLockerEx cl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag);

  initialize_first_chunk(type, Metaspace::NonClassType);
  if (Metaspace::using_class_space()) {
    initialize_first_chunk(type, Metaspace::ClassType);
  }
}
复制代码

In this code, we can see that the initialization process consists of two main steps,

  1. Creating NonClassSpaceManger (_vsm) and ClassSpaceManager (_class_vsm)
  2. Initialize the first NonClassChunk and a ClassChunk

We then focus on it during initialization of a Chunk of (simple period, we are only concerned NonClass type of initialization, in fact, the two are basically the same).

// 代码已经过简单整理
void ClassLoaderMetaspace::initialize_first_chunk(Metaspace::MetaspaceType type, Metaspace::MetadataType mdtype) {
  size_t chunk_word_size = get_space_manager(mdtype)->get_initial_chunk_size(type);
  
  Metachunk* chunk = Metaspace::get_chunk_manager(mdtype)->chunk_freelist_allocate(chunk_word_size);

  if (chunk == NULL) {
    chunk = Metaspace::get_space_list(mdtype)->get_new_chunk(chunk_word_size,
                                                  get_space_manager(mdtype)->medium_chunk_bunch());
  }
  
  if (chunk != NULL) {
    get_space_manager(mdtype)->add_chunk(chunk, true);
  }
}
复制代码

Overall, initialize the first chunk is divided into three steps:

  1. Try to allocate a chunk from the global chunk_freelist
  2. Create a new chunk from the global virtualSpaceList
  3. Add a new chunk to manage spaceManager

But before exploring these three steps, we take a look at the first sentence of the code to calculate the size of the chunk, we take a look at how chunk size calculation,

enum ChunkSizes {    // in words.
  ClassSpecializedChunk = 128,
  SpecializedChunk = 128,
  ClassSmallChunk = 256,
  SmallChunk = 512,
  ClassMediumChunk = 4 * K,
  MediumChunk = 8 * K
};

size_t SpaceManager::adjust_initial_chunk_size(size_t requested, bool is_class_space) {
  size_t chunk_sizes[] = {
      specialized_chunk_size(is_class_space),
      small_chunk_size(is_class_space),
      medium_chunk_size(is_class_space)
  };
  for (size_t i = 0; i < ARRAY_SIZE(chunk_sizes); i++) {
    if (requested <= chunk_sizes[i]) {
      return chunk_sizes[i];
    }
  }
  return requested;
}

size_t SpaceManager::get_initial_chunk_size(Metaspace::MetaspaceType type) const {
  size_t requested;

  if (is_class()) {
    switch (type) {
      case Metaspace::BootMetaspaceType:       requested = Metaspace::first_class_chunk_word_size(); break;
      case Metaspace::AnonymousMetaspaceType:  requested = ClassSpecializedChunk; break;
      case Metaspace::ReflectionMetaspaceType: requested = ClassSpecializedChunk; break;
      default:                                 requested = ClassSmallChunk; break;
    }
  } else {
    switch (type) {
      case Metaspace::BootMetaspaceType:       requested = Metaspace::first_chunk_word_size(); break;
      case Metaspace::AnonymousMetaspaceType:  requested = SpecializedChunk; break;
      case Metaspace::ReflectionMetaspaceType: requested = SpecializedChunk; break;
      default:                                 requested = SmallChunk; break;
    }
  }

  const size_t adjusted = adjust_initial_chunk_size(requested);

  assert(adjusted != 0, "Incorrect initial chunk size. Requested: "
         SIZE_FORMAT " adjusted: " SIZE_FORMAT, requested, adjusted);

  return adjusted;
}
复制代码

Here we can see the difference between different types of classLoaderMetaspace, their initial chunk size is not the same. Meanwhile, for the Class class and type of the Chunk NonClass, their specialized, small, medium size value of the third gear is completely different.


Next, we focus remains back into the first chunk of the initialization process, the former here focus on two steps, first the first step - try to allocate a chunk from the global chunk_freelist in. ChunkManager::chunk_freelist_allocate(size_t word_size)The main call ChunkManager::free_chunks_getmethod, we look at the specific source,

// 去除了校验代码&日志代码
Metachunk* ChunkManager::free_chunks_get(size_t word_size) {
  slow_locked_verify();

  Metachunk* chunk = NULL;
  bool we_did_split_a_chunk = false;

  if (list_index(word_size) != HumongousIndex) {

    ChunkList* free_list = find_free_chunks_list(word_size);

    chunk = free_list->head();

    if (chunk == NULL) {
      ChunkIndex target_chunk_index = get_chunk_type_by_size(word_size, is_class());
      Metachunk* larger_chunk = NULL;
      ChunkIndex larger_chunk_index = next_chunk_index(target_chunk_index);
      while (larger_chunk == NULL && larger_chunk_index < NumberOfFreeLists) {
        larger_chunk = free_chunks(larger_chunk_index)->head();
        if (larger_chunk == NULL) {
          larger_chunk_index = next_chunk_index(larger_chunk_index);
        }
      }

      if (larger_chunk != NULL) {
        chunk = split_chunk(word_size, larger_chunk);
        we_did_split_a_chunk = true;
      }
    }

    if (chunk == NULL) {
      return NULL;
    }

    free_list->remove_chunk(chunk)
  } else {
    chunk = humongous_dictionary()->get_chunk(word_size);

    if (chunk == NULL) {
      return NULL;
    }
  }
  chunk->set_next(NULL);
  chunk->set_prev(NULL);
  return chunk;
}
复制代码

Briefly explain the code, memory allocation is divided into two cases

  • specialized, small, medium three sizes chunk
  • humongous chunk type

Wherein specialized, small, medium respectively corresponding to the three types of freeChunk the three lists, the humongou type freeChunk due to their size is not fixed, is used to store a binary tree sorting.

Non-humongou type of chunk in the allocation process if it fails, will try to split a larger chunk.


Next, look to create a new chunk of the process from the global virtualSpaceList,

Metachunk* VirtualSpaceList::get_new_chunk(size_t chunk_word_size, size_t suggested_commit_granularity) {
  
  Metachunk* next = current_virtual_space()->get_chunk_vs(chunk_word_size);
  if (next != NULL) {
    return next;
  }

  const size_t size_for_padding = largest_possible_padding_size_for_chunk(chunk_word_size, this->is_class());

  size_t min_word_size       = align_up(chunk_word_size + size_for_padding, Metaspace::commit_alignment_words());
  size_t preferred_word_size = align_up(suggested_commit_granularity, Metaspace::commit_alignment_words());
  if (min_word_size >= preferred_word_size) {
    preferred_word_size = min_word_size;
  }

  bool expanded = expand_by(min_word_size, preferred_word_size);
  if (expanded) {
    next = current_virtual_space()->get_chunk_vs(chunk_word_size);
  }

   return next;
}
复制代码

The whole code can be organized into three steps:

  1. Attempts to allocate chunk from current virtualSpace
  2. Extended virtualSpace
  3. Attempts to allocate chunk from current virtualSpace again

Compare feel curious is the second step, expansion virtualSpace,

bool VirtualSpaceList::expand_by(size_t min_words, size_t preferred_words) {

  if (!MetaspaceGC::can_expand(min_words, this->is_class())) {
    return false;
  }

  size_t allowed_expansion_words = MetaspaceGC::allowed_expansion();
  if (allowed_expansion_words < min_words) {
    return false;
  }

  size_t max_expansion_words = MIN2(preferred_words, allowed_expansion_words);

  bool vs_expanded = expand_node_by(current_virtual_space(), min_words, max_expansion_words);
  if (vs_expanded) {
     return true;
  }
  
  retire_current_virtual_space();

  size_t grow_vs_words = MAX2((size_t)VirtualSpaceSize, preferred_words);
  grow_vs_words = align_up(grow_vs_words, Metaspace::reserve_alignment_words());

  if (create_new_virtual_space(grow_vs_words)) {
    if (current_virtual_space()->is_pre_committed()) {
      return true;
    }
    return expand_node_by(current_virtual_space(), min_words, max_expansion_words);
  }

  return false;
}
复制代码

This step mainly consists of several core steps:

  • Try to extend the current virtualSpace
  • Current virtualSpace not meet the requirements, the current virtualSpace retirement. This step sets the current recovery of the remaining space virtualSpace to chunk_freelist
  • Create a new virtualSpace

to sum up

ClassLoaderMetaspace entire initialization process may be summarized in the following steps:

  1. Creating SpaceManager
  2. Initialization ClassLoaderMetaspace first Chunk
    1. Try to allocate a chunk from the global chunk_freelist
    2. Create a new chunk from the global virtualSpaceList
      1. Tries to allocate from the current virtualSpace
      2. VirtualSpace recovery current remaining space, and a new attempt to allocate a virtualSpace
    3. Initialization is complete, add a chunk to manage spaceManager

3. Assign memory

For metaspace terms, in addition to the initialization, there are two most important functions - allocate memory and free memory. Let's look at allocating memory,

static bool is_class_space_allocation(MetadataType mdType) {
  return mdType == ClassType && using_class_space();
}

MetaWord* ClassLoaderMetaspace::allocate(size_t word_size, Metaspace::MetadataType mdtype) {
  if (Metaspace::is_class_space_allocation(mdtype)) {
    return  class_vsm()->allocate(word_size);
  } else {
    return  vsm()->allocate(word_size);
  }
}
复制代码

In this code, we can see that only the metadata type Class types and when to use compression pointers will use Class space, or are using NonClass space.

Next, we continue to explore vsm()->allocate(word_size)methods,

MetaWord* SpaceManager::allocate(size_t word_size) {
  MutexLockerEx cl(lock(), Mutex::_no_safepoint_check_flag);
  size_t raw_word_size = get_allocation_word_size(word_size);
  BlockFreelist* fl =  block_freelists();
  MetaWord* p = NULL;

  if (fl != NULL && fl->total_size() > allocation_from_dictionary_limit) {
    p = fl->get_block(raw_word_size);
  }
  if (p == NULL) {
    p = allocate_work(raw_word_size);
  }

  return p;
}
复制代码

At metaspace initialization chunk somewhat the same purpose before this step, here is the first attempt to allocate from block_freelists, the allocation failed attempt to re-assigned from the chunk, chunk initialization logic almost exactly the same as above.

block_freelists is also divided into small and large, the data structure is as follows:

class BlockFreelist : public CHeapObj<mtClass> {
  BlockTreeDictionary* const _dictionary;
  SmallBlocks* _small_blocks;
}

class SmallBlocks : public CHeapObj<mtClass> {
  FreeList<Metablock> _small_lists[_small_block_max_size - _small_block_min_size];
}
复制代码

In small_block_max_size small_block_min_size range block into the linked list is stored, a larger block is used to implement a binary tree sorting.

As chunk allocates memory are exactly the same, to try to allocate from the current chunk, chunk allocation fails then the new allocation.

4. Release Memory

Code release memory is relatively simple, that is, direct memory will need to release back into the block_freelist re-use.

void SpaceManager::deallocate(MetaWord* p, size_t word_size) {
  size_t raw_word_size = get_allocation_word_size(word_size);
  if (block_freelists() == NULL) {
    _block_freelists = new BlockFreelist();
  }
  block_freelists()->return_block(p, raw_word_size);
}

void BlockFreelist::return_block(MetaWord* p, size_t word_size) {
  Metablock* free_chunk = ::new (p) Metablock(word_size);
  if (word_size < SmallBlocks::small_block_max_size()) {
    small_blocks()->return_block(free_chunk, word_size);
  } else {
  	dictionary()->return_chunk(free_chunk);
	}
}
复制代码

Thus, initialization, memory allocation, memory release metaspace part already ended.

Guess you like

Origin juejin.im/post/5d4d4bcff265da03eb13b7ac