diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/classfile/classLoaderData.cpp --- a/src/hotspot/share/classfile/classLoaderData.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/classfile/classLoaderData.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -59,6 +59,8 @@ #include "logging/logStream.hpp" #include "memory/allocation.inline.hpp" #include "memory/metadataFactory.hpp" +#include "memory/metaspace/classLoaderMetaspace.hpp" +#include "memory/metaspace/metaspaceEnums.hpp" #include "memory/resourceArea.hpp" #include "oops/access.inline.hpp" #include "oops/oop.inline.hpp" @@ -73,6 +75,8 @@ #include "utilities/macros.hpp" #include "utilities/ostream.hpp" +using metaspace::ClassLoaderMetaspace; + ClassLoaderData * ClassLoaderData::_the_null_class_loader_data = NULL; void ClassLoaderData::init_null_class_loader_data() { @@ -759,13 +763,13 @@ if ((metaspace = _metaspace) == NULL) { if (this == the_null_class_loader_data()) { assert (class_loader() == NULL, "Must be"); - metaspace = new ClassLoaderMetaspace(_metaspace_lock, Metaspace::BootMetaspaceType); + metaspace = new ClassLoaderMetaspace(_metaspace_lock, metaspace::BootMetaspaceType); } else if (is_unsafe_anonymous()) { - metaspace = new ClassLoaderMetaspace(_metaspace_lock, Metaspace::UnsafeAnonymousMetaspaceType); + metaspace = new ClassLoaderMetaspace(_metaspace_lock, metaspace::UnsafeAnonymousMetaspaceType); } else if (class_loader()->is_a(SystemDictionary::reflect_DelegatingClassLoader_klass())) { - metaspace = new ClassLoaderMetaspace(_metaspace_lock, Metaspace::ReflectionMetaspaceType); + metaspace = new ClassLoaderMetaspace(_metaspace_lock, metaspace::ReflectionMetaspaceType); } else { - metaspace = new ClassLoaderMetaspace(_metaspace_lock, Metaspace::StandardMetaspaceType); + metaspace = new ClassLoaderMetaspace(_metaspace_lock, metaspace::StandardMetaspaceType); } // Ensure _metaspace is stable, since it is examined without a lock OrderAccess::release_store(&_metaspace, metaspace); @@ -956,9 +960,11 @@ guarantee(cl != NULL || this == ClassLoaderData::the_null_class_loader_data() || is_unsafe_anonymous(), "must be"); // Verify the integrity of the allocated space. +#ifdef ASSERT if (metaspace_or_null() != NULL) { - metaspace_or_null()->verify(); + metaspace_or_null()->verify(false); } +#endif for (Klass* k = _klasses; k != NULL; k = k->next_link()) { guarantee(k->class_loader_data() == this, "Must be the same"); diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/classfile/classLoaderData.hpp --- a/src/hotspot/share/classfile/classLoaderData.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/classfile/classLoaderData.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -27,7 +27,6 @@ #include "memory/allocation.hpp" #include "memory/memRegion.hpp" -#include "memory/metaspace.hpp" #include "oops/oopHandle.hpp" #include "oops/weakHandle.hpp" #include "runtime/atomic.hpp" @@ -63,6 +62,10 @@ class DictionaryEntry; class Dictionary; +namespace metaspace { + class ClassLoaderMetaspace; +} + // ClassLoaderData class class ClassLoaderData : public CHeapObj { @@ -113,7 +116,7 @@ OopHandle _class_loader; // The instance of java/lang/ClassLoader associated with // this ClassLoaderData - ClassLoaderMetaspace * volatile _metaspace; // Meta-space where meta-data defined by the + metaspace::ClassLoaderMetaspace* volatile _metaspace; // Meta-space where meta-data defined by the // classes in the class loader are allocated. Mutex* _metaspace_lock; // Locks the metaspace for allocations and setup. bool _unloading; // true if this class loader goes away @@ -223,7 +226,7 @@ bool is_alive() const; // Accessors - ClassLoaderMetaspace* metaspace_or_null() const { return _metaspace; } + metaspace::ClassLoaderMetaspace* metaspace_or_null() const { return _metaspace; } static ClassLoaderData* the_null_class_loader_data() { return _the_null_class_loader_data; @@ -256,7 +259,7 @@ // The Metaspace is created lazily so may be NULL. This // method will allocate a Metaspace if needed. - ClassLoaderMetaspace* metaspace_non_null(); + metaspace::ClassLoaderMetaspace* metaspace_non_null(); inline oop class_loader() const; diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/classfile/classLoaderDataGraph.cpp --- a/src/hotspot/share/classfile/classLoaderDataGraph.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/classfile/classLoaderDataGraph.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -680,24 +680,6 @@ return NULL; } -ClassLoaderDataGraphMetaspaceIterator::ClassLoaderDataGraphMetaspaceIterator() { - assert(SafepointSynchronize::is_at_safepoint(), "must be at safepoint!"); - _data = ClassLoaderDataGraph::_head; -} - -ClassLoaderDataGraphMetaspaceIterator::~ClassLoaderDataGraphMetaspaceIterator() {} - -ClassLoaderMetaspace* ClassLoaderDataGraphMetaspaceIterator::get_next() { - assert(_data != NULL, "Should not be NULL in call to the iterator"); - ClassLoaderMetaspace* result = _data->metaspace_or_null(); - _data = _data->next(); - // This result might be NULL for class loaders without metaspace - // yet. It would be nice to return only non-null results but - // there is no guarantee that there will be a non-null result - // down the list so the caller is going to have to check. - return result; -} - #ifndef PRODUCT // callable from debugger extern "C" int print_loader_data_graph() { diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/classfile/classLoaderDataGraph.hpp --- a/src/hotspot/share/classfile/classLoaderDataGraph.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/classfile/classLoaderDataGraph.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -176,12 +176,4 @@ static Klass* next_klass_in_cldg(Klass* klass); }; -class ClassLoaderDataGraphMetaspaceIterator : public StackObj { - ClassLoaderData* _data; - public: - ClassLoaderDataGraphMetaspaceIterator(); - ~ClassLoaderDataGraphMetaspaceIterator(); - bool repeat() { return _data != NULL; } - ClassLoaderMetaspace* get_next(); -}; #endif // SHARE_CLASSFILE_CLASSLOADERDATAGRAPH_HPP diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/classfile/classLoaderStats.cpp --- a/src/hotspot/share/classfile/classLoaderStats.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/classfile/classLoaderStats.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -23,6 +23,7 @@ */ #include "precompiled.hpp" +#include "memory/metaspace/classLoaderMetaspace.hpp" #include "classfile/classLoaderData.inline.hpp" #include "classfile/classLoaderDataGraph.hpp" #include "classfile/classLoaderStats.hpp" @@ -78,7 +79,7 @@ } _total_classes += csc._num_classes; - ClassLoaderMetaspace* ms = cld->metaspace_or_null(); + metaspace::ClassLoaderMetaspace* ms = cld->metaspace_or_null(); if (ms != NULL) { if(cld->is_unsafe_anonymous()) { cls->_anon_chunk_sz += ms->allocated_chunks_bytes(); diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/gc/g1/g1CollectedHeap.cpp --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -1049,7 +1049,7 @@ // Delete metaspaces for unloaded class loaders and clean up loader_data graph ClassLoaderDataGraph::purge(); - MetaspaceUtils::verify_metrics(); + DEBUG_ONLY(MetaspaceUtils::verify(false);) // Prepare heap for normal collections. assert(num_free_regions() == 0, "we should not have added any free regions"); diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/gc/parallel/psMarkSweep.cpp --- a/src/hotspot/share/gc/parallel/psMarkSweep.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/gc/parallel/psMarkSweep.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -251,7 +251,7 @@ // Delete metaspaces for unloaded class loaders and clean up loader_data graph ClassLoaderDataGraph::purge(); - MetaspaceUtils::verify_metrics(); + DEBUG_ONLY(MetaspaceUtils::verify(false);) BiasedLocking::restore_marks(); heap->prune_scavengable_nmethods(); diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/gc/parallel/psParallelCompact.cpp --- a/src/hotspot/share/gc/parallel/psParallelCompact.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/gc/parallel/psParallelCompact.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -1059,7 +1059,7 @@ // Delete metaspaces for unloaded class loaders and clean up loader_data graph ClassLoaderDataGraph::purge(); - MetaspaceUtils::verify_metrics(); + DEBUG_ONLY(MetaspaceUtils::verify(false);) heap->prune_scavengable_nmethods(); diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/gc/shared/collectedHeap.cpp --- a/src/hotspot/share/gc/shared/collectedHeap.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/gc/shared/collectedHeap.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -37,6 +37,7 @@ #include "gc/shared/memAllocator.hpp" #include "logging/log.hpp" #include "memory/metaspace.hpp" +#include "memory/metaspace/classLoaderMetaspace.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "oops/instanceMirrorKlass.hpp" @@ -105,18 +106,18 @@ MetaspaceUtils::used_bytes(), MetaspaceUtils::reserved_bytes()); const MetaspaceSizes data_space( - MetaspaceUtils::committed_bytes(Metaspace::NonClassType), - MetaspaceUtils::used_bytes(Metaspace::NonClassType), - MetaspaceUtils::reserved_bytes(Metaspace::NonClassType)); + MetaspaceUtils::committed_bytes(metaspace::NonClassType), + MetaspaceUtils::used_bytes(metaspace::NonClassType), + MetaspaceUtils::reserved_bytes(metaspace::NonClassType)); const MetaspaceSizes class_space( - MetaspaceUtils::committed_bytes(Metaspace::ClassType), - MetaspaceUtils::used_bytes(Metaspace::ClassType), - MetaspaceUtils::reserved_bytes(Metaspace::ClassType)); + MetaspaceUtils::committed_bytes(metaspace::ClassType), + MetaspaceUtils::used_bytes(metaspace::ClassType), + MetaspaceUtils::reserved_bytes(metaspace::ClassType)); const MetaspaceChunkFreeListSummary& ms_chunk_free_list_summary = - MetaspaceUtils::chunk_free_list_summary(Metaspace::NonClassType); + MetaspaceUtils::chunk_free_list_summary(metaspace::NonClassType); const MetaspaceChunkFreeListSummary& class_chunk_free_list_summary = - MetaspaceUtils::chunk_free_list_summary(Metaspace::ClassType); + MetaspaceUtils::chunk_free_list_summary(metaspace::ClassType); return MetaspaceSummary(MetaspaceGC::capacity_until_GC(), meta_space, data_space, class_space, ms_chunk_free_list_summary, class_chunk_free_list_summary); @@ -255,7 +256,7 @@ MetaWord* CollectedHeap::satisfy_failed_metadata_allocation(ClassLoaderData* loader_data, size_t word_size, - Metaspace::MetadataType mdtype) { + metaspace::MetadataType mdtype) { uint loop_count = 0; uint gc_count = 0; uint full_gc_count = 0; diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/gc/shared/collectedHeap.hpp --- a/src/hotspot/share/gc/shared/collectedHeap.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/gc/shared/collectedHeap.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -29,6 +29,7 @@ #include "gc/shared/gcWhen.hpp" #include "gc/shared/verifyOption.hpp" #include "memory/allocation.hpp" +#include "memory/metaspace/metaspaceEnums.hpp" #include "runtime/handles.hpp" #include "runtime/perfData.hpp" #include "runtime/safepoint.hpp" @@ -362,7 +363,7 @@ virtual MetaWord* satisfy_failed_metadata_allocation(ClassLoaderData* loader_data, size_t size, - Metaspace::MetadataType mdtype); + metaspace::MetadataType mdtype); // Returns "true" iff there is a stop-world GC in progress. (I assume // that it should answer "false" for the concurrent part of a concurrent diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/gc/shared/gcTrace.cpp --- a/src/hotspot/share/gc/shared/gcTrace.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/gc/shared/gcTrace.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -30,6 +30,7 @@ #include "gc/shared/gcTrace.hpp" #include "gc/shared/objectCountEventSender.hpp" #include "gc/shared/referenceProcessorStats.hpp" +#include "memory/metaspace/metaspaceEnums.hpp" #include "memory/heapInspection.hpp" #include "memory/resourceArea.hpp" #include "runtime/os.hpp" @@ -116,9 +117,9 @@ void GCTracer::report_metaspace_summary(GCWhen::Type when, const MetaspaceSummary& summary) const { send_meta_space_summary_event(when, summary); - send_metaspace_chunk_free_list_summary(when, Metaspace::NonClassType, summary.metaspace_chunk_free_list_summary()); + send_metaspace_chunk_free_list_summary(when, metaspace::NonClassType, summary.metaspace_chunk_free_list_summary()); if (UseCompressedClassPointers) { - send_metaspace_chunk_free_list_summary(when, Metaspace::ClassType, summary.class_chunk_free_list_summary()); + send_metaspace_chunk_free_list_summary(when, metaspace::ClassType, summary.class_chunk_free_list_summary()); } } diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/gc/shared/gcTrace.hpp --- a/src/hotspot/share/gc/shared/gcTrace.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/gc/shared/gcTrace.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -30,7 +30,7 @@ #include "gc/shared/gcId.hpp" #include "gc/shared/gcName.hpp" #include "gc/shared/gcWhen.hpp" -#include "memory/metaspace.hpp" +#include "memory/metaspace/metaspaceEnums.hpp" #include "memory/referenceType.hpp" #include "utilities/macros.hpp" #include "utilities/ticks.hpp" @@ -112,7 +112,7 @@ void send_garbage_collection_event() const; void send_gc_heap_summary_event(GCWhen::Type when, const GCHeapSummary& heap_summary) const; void send_meta_space_summary_event(GCWhen::Type when, const MetaspaceSummary& meta_space_summary) const; - void send_metaspace_chunk_free_list_summary(GCWhen::Type when, Metaspace::MetadataType mdtype, const MetaspaceChunkFreeListSummary& summary) const; + void send_metaspace_chunk_free_list_summary(GCWhen::Type when, metaspace::MetadataType mdtype, const MetaspaceChunkFreeListSummary& summary) const; void send_reference_stats_event(ReferenceType type, size_t count) const; void send_phase_events(TimePartitions* time_partitions) const; }; diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/gc/shared/gcTraceSend.cpp --- a/src/hotspot/share/gc/shared/gcTraceSend.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/gc/shared/gcTraceSend.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -60,7 +60,7 @@ } } -void GCTracer::send_metaspace_chunk_free_list_summary(GCWhen::Type when, Metaspace::MetadataType mdtype, +void GCTracer::send_metaspace_chunk_free_list_summary(GCWhen::Type when, metaspace::MetadataType mdtype, const MetaspaceChunkFreeListSummary& summary) const { EventMetaspaceChunkFreeListSummary e; if (e.should_commit()) { diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/gc/shared/gcVMOperations.cpp --- a/src/hotspot/share/gc/shared/gcVMOperations.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/gc/shared/gcVMOperations.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -32,6 +32,8 @@ #include "gc/shared/genCollectedHeap.hpp" #include "interpreter/oopMapCache.hpp" #include "logging/log.hpp" +#include "memory/metaspace/classLoaderMetaspace.hpp" +#include "memory/metaspace/metaspaceEnums.hpp" #include "memory/oopFactory.hpp" #include "memory/universe.hpp" #include "runtime/handles.inline.hpp" @@ -180,7 +182,7 @@ VM_CollectForMetadataAllocation::VM_CollectForMetadataAllocation(ClassLoaderData* loader_data, size_t size, - Metaspace::MetadataType mdtype, + metaspace::MetadataType mdtype, uint gc_count_before, uint full_gc_count_before, GCCause::Cause gc_cause) diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/gc/shared/gcVMOperations.hpp --- a/src/hotspot/share/gc/shared/gcVMOperations.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/gc/shared/gcVMOperations.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -28,6 +28,7 @@ #include "gc/shared/collectedHeap.hpp" #include "gc/shared/genCollectedHeap.hpp" #include "memory/heapInspection.hpp" +#include "memory/metaspace/metaspaceEnums.hpp" #include "prims/jvmtiExport.hpp" #include "runtime/handles.hpp" #include "runtime/jniHandles.hpp" @@ -206,13 +207,13 @@ private: MetaWord* _result; size_t _size; // size of object to be allocated - Metaspace::MetadataType _mdtype; + metaspace::MetadataType _mdtype; ClassLoaderData* _loader_data; public: VM_CollectForMetadataAllocation(ClassLoaderData* loader_data, size_t size, - Metaspace::MetadataType mdtype, + metaspace::MetadataType mdtype, uint gc_count_before, uint full_gc_count_before, GCCause::Cause gc_cause); diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/gc/shared/genCollectedHeap.cpp --- a/src/hotspot/share/gc/shared/genCollectedHeap.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/gc/shared/genCollectedHeap.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -56,6 +56,7 @@ #include "gc/shared/workgroup.hpp" #include "memory/filemap.hpp" #include "memory/metaspaceCounters.hpp" +#include "memory/metaspace/metaspaceSizesSnapshot.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "oops/oop.inline.hpp" @@ -686,7 +687,7 @@ // Delete metaspaces for unloaded class loaders and clean up loader_data graph ClassLoaderDataGraph::purge(); - MetaspaceUtils::verify_metrics(); + DEBUG_ONLY(MetaspaceUtils::verify(false);) // Resize the metaspace capacity after full collections MetaspaceGC::compute_new_size(); update_full_collections_completed(); diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -69,7 +69,9 @@ #include "gc/shenandoah/shenandoahJfrSupport.hpp" #endif -#include "memory/metaspace.hpp" + +#include "memory/metaspace/classLoaderMetaspace.hpp" +#include "memory/metaspace/metaspaceEnums.hpp" #include "oops/compressedOops.inline.hpp" #include "runtime/globals.hpp" #include "runtime/interfaceSupport.inline.hpp" @@ -876,7 +878,7 @@ MetaWord* ShenandoahHeap::satisfy_failed_metadata_allocation(ClassLoaderData* loader_data, size_t size, - Metaspace::MetadataType mdtype) { + metaspace::MetadataType mdtype) { MetaWord* result; // Inform metaspace OOM to GC heuristics if class unloading is possible. @@ -1994,7 +1996,6 @@ } // Resize and verify metaspace MetaspaceGC::compute_new_size(); - MetaspaceUtils::verify_metrics(); } // Process leftover weak oops: update them, if needed or assert they do not diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -32,6 +32,7 @@ #include "gc/shenandoah/shenandoahLock.hpp" #include "gc/shenandoah/shenandoahEvacOOMHandler.hpp" #include "gc/shenandoah/shenandoahSharedVariables.hpp" +#include "memory/metaspace/metaspaceEnums.hpp" #include "services/memoryManager.hpp" class ConcurrentGCTimer; @@ -588,7 +589,7 @@ HeapWord* mem_allocate(size_t size, bool* what); MetaWord* satisfy_failed_metadata_allocation(ClassLoaderData* loader_data, size_t size, - Metaspace::MetadataType mdtype); + metaspace::MetadataType mdtype); void notify_mutator_alloc_words(size_t words, bool waste); diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/gc/z/zCollectedHeap.cpp --- a/src/hotspot/share/gc/z/zCollectedHeap.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/gc/z/zCollectedHeap.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -33,6 +33,8 @@ #include "gc/z/zServiceability.hpp" #include "gc/z/zStat.hpp" #include "gc/z/zUtils.inline.hpp" +#include "memory/metaspace/classLoaderMetaspace.hpp" +#include "memory/metaspace/metaspaceEnums.hpp" #include "memory/universe.hpp" #include "runtime/mutexLocker.hpp" #include "utilities/align.hpp" @@ -146,7 +148,7 @@ MetaWord* ZCollectedHeap::satisfy_failed_metadata_allocation(ClassLoaderData* loader_data, size_t size, - Metaspace::MetadataType mdtype) { + metaspace::MetadataType mdtype) { MetaWord* result; // Start asynchronous GC diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/gc/z/zCollectedHeap.hpp --- a/src/hotspot/share/gc/z/zCollectedHeap.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/gc/z/zCollectedHeap.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -34,6 +34,7 @@ #include "gc/z/zRuntimeWorkers.hpp" #include "gc/z/zStat.hpp" #include "gc/z/zUncommitter.hpp" +#include "memory/metaspace/metaspaceEnums.hpp" class ZCollectedHeap : public CollectedHeap { friend class VMStructs; @@ -79,7 +80,7 @@ virtual HeapWord* mem_allocate(size_t size, bool* gc_overhead_limit_was_exceeded); virtual MetaWord* satisfy_failed_metadata_allocation(ClassLoaderData* loader_data, size_t size, - Metaspace::MetadataType mdtype); + metaspace::MetadataType mdtype); virtual void collect(GCCause::Cause cause); virtual void collect_as_vm_thread(GCCause::Cause cause); virtual void do_full_collection(bool clear_all_soft_refs); diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/gc/z/zUnload.cpp --- a/src/hotspot/share/gc/z/zUnload.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/gc/z/zUnload.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -177,5 +177,5 @@ void ZUnload::finish() { // Resize and verify metaspace MetaspaceGC::compute_new_size(); - MetaspaceUtils::verify_metrics(); + DEBUG_ONLY(MetaspaceUtils::verify(false);) } diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.cpp --- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -39,7 +39,8 @@ #include "jfr/recorder/checkpoint/types/jfrTypeSet.hpp" #include "jfr/support/jfrThreadLocal.hpp" #include "jfr/writers/jfrJavaEventWriter.hpp" -#include "memory/metaspaceGCThresholdUpdater.hpp" +#include "memory/metaspace.hpp" +#include "memory/metaspace/metaspaceEnums.hpp" #include "memory/referenceType.hpp" #include "memory/universe.hpp" #include "oops/compressedOops.hpp" @@ -193,11 +194,11 @@ } void MetadataTypeConstant::serialize(JfrCheckpointWriter& writer) { - static const u4 nof_entries = Metaspace::MetadataTypeCount; + static const u4 nof_entries = metaspace::MetadataTypeCount; writer.write_count(nof_entries); for (u4 i = 0; i < nof_entries; ++i) { writer.write_key(i); - writer.write(Metaspace::metadata_type_name((Metaspace::MetadataType)i)); + writer.write(metaspace::describe_mdtype((metaspace::MetadataType)i)); } } diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/filemap.cpp --- a/src/hotspot/share/memory/filemap.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/filemap.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -39,6 +39,7 @@ #include "memory/heapShared.inline.hpp" #include "memory/iterator.inline.hpp" #include "memory/metadataFactory.hpp" +#include "memory/metaspace.hpp" #include "memory/metaspaceClosure.hpp" #include "memory/metaspaceShared.hpp" #include "memory/oopFactory.hpp" @@ -1380,7 +1381,7 @@ assert(!HeapShared::is_heap_region(i), "sanity"); FileMapRegion* si = space_at(i); size_t used = si->used(); - size_t alignment = os::vm_allocation_granularity(); + size_t alignment = Metaspace::reserve_alignment(); size_t size = align_up(used, alignment); char *requested_addr = region_addr(i); diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metadataFactory.hpp --- a/src/hotspot/share/memory/metadataFactory.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metadataFactory.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -26,6 +26,8 @@ #define SHARE_MEMORY_METADATAFACTORY_HPP #include "classfile/classLoaderData.hpp" +#include "memory/metaspace/classLoaderMetaspace.hpp" +#include "memory/metaspace/metaspaceEnums.hpp" #include "oops/array.hpp" #include "utilities/exceptions.hpp" #include "utilities/globalDefinitions.hpp" diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace.cpp --- a/src/hotspot/share/memory/metaspace.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -22,65 +22,204 @@ * */ +#include #include "precompiled.hpp" + #include "aot/aotLoader.hpp" -#include "classfile/classLoaderDataGraph.hpp" #include "gc/shared/collectedHeap.hpp" #include "logging/log.hpp" #include "logging/logStream.hpp" #include "memory/filemap.hpp" #include "memory/metaspace.hpp" -#include "memory/metaspace/chunkManager.hpp" -#include "memory/metaspace/metachunk.hpp" -#include "memory/metaspace/metaspaceCommon.hpp" -#include "memory/metaspace/printCLDMetaspaceInfoClosure.hpp" -#include "memory/metaspace/spaceManager.hpp" -#include "memory/metaspace/virtualSpaceList.hpp" #include "memory/metaspaceShared.hpp" #include "memory/metaspaceTracer.hpp" +#include "memory/metaspace/chunkManager.hpp" +#include "memory/metaspace/classLoaderMetaspace.hpp" +#include "memory/metaspace/commitLimiter.hpp" +#include "memory/metaspace/metaspaceCommon.hpp" +#include "memory/metaspace/metaspaceEnums.hpp" +#include "memory/metaspace/metaspaceReport.hpp" +#include "memory/metaspace/metaspaceSizesSnapshot.hpp" +#include "memory/metaspace/runningCounters.hpp" +#include "memory/metaspace/virtualSpaceList.hpp" #include "memory/universe.hpp" #include "oops/compressedOops.hpp" #include "runtime/init.hpp" +#include "runtime/java.hpp" #include "runtime/orderAccess.hpp" #include "services/memTracker.hpp" #include "utilities/copy.hpp" #include "utilities/debug.hpp" #include "utilities/formatBuffer.hpp" #include "utilities/globalDefinitions.hpp" -#include "utilities/vmError.hpp" -using namespace metaspace; +using metaspace::ChunkManager; +using metaspace::ClassLoaderMetaspace; +using metaspace::CommitLimiter; +using metaspace::MetaspaceType; +using metaspace::MetadataType; +using metaspace::MetaspaceReporter; +using metaspace::RunningCounters; +using metaspace::VirtualSpaceList; -MetaWord* last_allocated = 0; -size_t Metaspace::_compressed_class_space_size; -const MetaspaceTracer* Metaspace::_tracer = NULL; +// Used by MetaspaceCounters +size_t MetaspaceUtils::free_chunks_total_words(MetadataType mdtype) { + return is_class(mdtype) ? RunningCounters::free_chunks_words_class() : RunningCounters::free_chunks_words_nonclass(); +} -DEBUG_ONLY(bool Metaspace::_frozen = false;) +size_t MetaspaceUtils::used_words() { + return RunningCounters::used_words(); +} -static const char* space_type_name(Metaspace::MetaspaceType t) { - const char* s = NULL; - switch (t) { - case Metaspace::StandardMetaspaceType: s = "Standard"; break; - case Metaspace::BootMetaspaceType: s = "Boot"; break; - case Metaspace::UnsafeAnonymousMetaspaceType: s = "UnsafeAnonymous"; break; - case Metaspace::ReflectionMetaspaceType: s = "Reflection"; break; - default: ShouldNotReachHere(); +size_t MetaspaceUtils::used_words(MetadataType mdtype) { + return is_class(mdtype) ? RunningCounters::used_words_class() : RunningCounters::used_words_nonclass(); +} + +size_t MetaspaceUtils::reserved_words() { + return RunningCounters::reserved_words(); +} + +size_t MetaspaceUtils::reserved_words(MetadataType mdtype) { + return is_class(mdtype) ? RunningCounters::reserved_words_class() : RunningCounters::reserved_words_nonclass(); +} + +size_t MetaspaceUtils::committed_words() { + return RunningCounters::committed_words(); +} + +size_t MetaspaceUtils::committed_words(MetadataType mdtype) { + return is_class(mdtype) ? RunningCounters::committed_words_class() : RunningCounters::committed_words_nonclass(); +} + + + +void MetaspaceUtils::print_metaspace_change(const metaspace::MetaspaceSizesSnapshot& pre_meta_values) { + const metaspace::MetaspaceSizesSnapshot meta_values; + + // We print used and committed since these are the most useful at-a-glance vitals for Metaspace: + // - used tells you how much memory is actually used for metadata + // - committed tells you how much memory is committed for the purpose of metadata + // The difference between those two would be waste, which can have various forms (freelists, + // unused parts of committed chunks etc) + // + // Left out is reserved, since this is not as exciting as the first two values: for class space, + // it is a constant (to uninformed users, often confusingly large). For non-class space, it would + // be interesting since free chunks can be uncommitted, but for now it is left out. + + if (Metaspace::using_class_space()) { + log_info(gc, metaspace)(HEAP_CHANGE_FORMAT" " + HEAP_CHANGE_FORMAT" " + HEAP_CHANGE_FORMAT, + HEAP_CHANGE_FORMAT_ARGS("Metaspace", + pre_meta_values.used(), + pre_meta_values.committed(), + meta_values.used(), + meta_values.committed()), + HEAP_CHANGE_FORMAT_ARGS("NonClass", + pre_meta_values.non_class_used(), + pre_meta_values.non_class_committed(), + meta_values.non_class_used(), + meta_values.non_class_committed()), + HEAP_CHANGE_FORMAT_ARGS("Class", + pre_meta_values.class_used(), + pre_meta_values.class_committed(), + meta_values.class_used(), + meta_values.class_committed())); + } else { + log_info(gc, metaspace)(HEAP_CHANGE_FORMAT, + HEAP_CHANGE_FORMAT_ARGS("Metaspace", + pre_meta_values.used(), + pre_meta_values.committed(), + meta_values.used(), + meta_values.committed())); } - return s; } + +// Prints an ASCII representation of the given space. +void MetaspaceUtils::print_metaspace_map(outputStream* out, MetadataType mdtype) { + out->print_cr("-- not yet implemented ---"); +} + +// This will print out a basic metaspace usage report but +// unlike print_report() is guaranteed not to lock or to walk the CLDG. +void MetaspaceUtils::print_basic_report(outputStream* out, size_t scale) { + MetaspaceReporter::print_basic_report(out, scale); +} + +// Prints a report about the current metaspace state. +// Optional parts can be enabled via flags. +// Function will walk the CLDG and will lock the expand lock; if that is not +// convenient, use print_basic_report() instead. +void MetaspaceUtils::print_full_report(outputStream* out, size_t scale) { + const int flags = + MetaspaceReporter::rf_show_loaders | + MetaspaceReporter::rf_break_down_by_chunktype | + MetaspaceReporter::rf_show_classes; + MetaspaceReporter::print_report(out, scale, flags); +} + +void MetaspaceUtils::print_on(outputStream* out) { + + // Used from all GCs. It first prints out totals, then, separately, the class space portion. + + out->print_cr(" Metaspace " + "used " SIZE_FORMAT "K, " + "committed " SIZE_FORMAT "K, " + "reserved " SIZE_FORMAT "K", + used_bytes()/K, + committed_bytes()/K, + reserved_bytes()/K); + + if (Metaspace::using_class_space()) { + const MetadataType ct = metaspace::ClassType; + out->print_cr(" class space " + "used " SIZE_FORMAT "K, " + "committed " SIZE_FORMAT "K, " + "reserved " SIZE_FORMAT "K", + used_bytes(ct)/K, + committed_bytes(ct)/K, + reserved_bytes(ct)/K); + } +} + +#ifdef ASSERT +void MetaspaceUtils::verify(bool slow) { + if (Metaspace::initialized()) { + + // Verify non-class chunkmanager... + ChunkManager* cm = ChunkManager::chunkmanager_nonclass(); + cm->verify(slow); + + // ... and space list. + VirtualSpaceList* vsl = VirtualSpaceList::vslist_nonclass(); + vsl->verify(slow); + + if (Metaspace::using_class_space()) { + // If we use compressed class pointers, verify class chunkmanager... + cm = ChunkManager::chunkmanager_class(); + assert(cm != NULL, "Sanity"); + cm->verify(slow); + + // ... and class spacelist. + VirtualSpaceList* vsl = VirtualSpaceList::vslist_nonclass(); + assert(vsl != NULL, "Sanity"); + vsl->verify(slow); + } + + } +} +#endif + +////////////////////////////////7 +// MetaspaceGC methods + volatile size_t MetaspaceGC::_capacity_until_GC = 0; uint MetaspaceGC::_shrink_factor = 0; bool MetaspaceGC::_should_concurrent_collect = false; -// BlockFreelist methods - -// VirtualSpaceNode methods - -// MetaspaceGC methods - // VM_CollectForMetadataAllocation is the vm operation used to GC. // Within the VM operation after the GC the attempt to allocate the metadata // should succeed. If the GC did not free enough space for the metaspace @@ -198,7 +337,7 @@ bool MetaspaceGC::can_expand(size_t word_size, bool is_class) { // Check if the compressed class space is full. if (is_class && Metaspace::using_class_space()) { - size_t class_committed = MetaspaceUtils::committed_bytes(Metaspace::ClassType); + size_t class_committed = MetaspaceUtils::committed_bytes(metaspace::ClassType); if (class_committed + word_size * BytesPerWord > CompressedClassSpaceSize) { log_trace(gc, metaspace, freelist)("Cannot expand %s metaspace by " SIZE_FORMAT " words (CompressedClassSpaceSize = " SIZE_FORMAT " words)", (is_class ? "class" : "non-class"), word_size, CompressedClassSpaceSize / sizeof(MetaWord)); @@ -352,626 +491,21 @@ } } -// MetaspaceUtils -size_t MetaspaceUtils::_capacity_words [Metaspace:: MetadataTypeCount] = {0, 0}; -size_t MetaspaceUtils::_overhead_words [Metaspace:: MetadataTypeCount] = {0, 0}; -volatile size_t MetaspaceUtils::_used_words [Metaspace:: MetadataTypeCount] = {0, 0}; -// Collect used metaspace statistics. This involves walking the CLDG. The resulting -// output will be the accumulated values for all live metaspaces. -// Note: method does not do any locking. -void MetaspaceUtils::collect_statistics(ClassLoaderMetaspaceStatistics* out) { - out->reset(); - ClassLoaderDataGraphMetaspaceIterator iter; - while (iter.repeat()) { - ClassLoaderMetaspace* msp = iter.get_next(); - if (msp != NULL) { - msp->add_to_statistics(out); - } - } -} -size_t MetaspaceUtils::free_in_vs_bytes(Metaspace::MetadataType mdtype) { - VirtualSpaceList* list = Metaspace::get_space_list(mdtype); - return list == NULL ? 0 : list->free_bytes(); -} +////// Metaspace methods ///// -size_t MetaspaceUtils::free_in_vs_bytes() { - return free_in_vs_bytes(Metaspace::ClassType) + free_in_vs_bytes(Metaspace::NonClassType); -} -static void inc_stat_nonatomically(size_t* pstat, size_t words) { - assert_lock_strong(MetaspaceExpand_lock); - (*pstat) += words; -} -static void dec_stat_nonatomically(size_t* pstat, size_t words) { - assert_lock_strong(MetaspaceExpand_lock); - const size_t size_now = *pstat; - assert(size_now >= words, "About to decrement counter below zero " - "(current value: " SIZE_FORMAT ", decrement value: " SIZE_FORMAT ".", - size_now, words); - *pstat = size_now - words; -} - -static void inc_stat_atomically(volatile size_t* pstat, size_t words) { - Atomic::add(words, pstat); -} - -static void dec_stat_atomically(volatile size_t* pstat, size_t words) { - const size_t size_now = *pstat; - assert(size_now >= words, "About to decrement counter below zero " - "(current value: " SIZE_FORMAT ", decrement value: " SIZE_FORMAT ".", - size_now, words); - Atomic::sub(words, pstat); -} - -void MetaspaceUtils::dec_capacity(Metaspace::MetadataType mdtype, size_t words) { - dec_stat_nonatomically(&_capacity_words[mdtype], words); -} -void MetaspaceUtils::inc_capacity(Metaspace::MetadataType mdtype, size_t words) { - inc_stat_nonatomically(&_capacity_words[mdtype], words); -} -void MetaspaceUtils::dec_used(Metaspace::MetadataType mdtype, size_t words) { - dec_stat_atomically(&_used_words[mdtype], words); -} -void MetaspaceUtils::inc_used(Metaspace::MetadataType mdtype, size_t words) { - inc_stat_atomically(&_used_words[mdtype], words); -} -void MetaspaceUtils::dec_overhead(Metaspace::MetadataType mdtype, size_t words) { - dec_stat_nonatomically(&_overhead_words[mdtype], words); -} -void MetaspaceUtils::inc_overhead(Metaspace::MetadataType mdtype, size_t words) { - inc_stat_nonatomically(&_overhead_words[mdtype], words); -} - -size_t MetaspaceUtils::reserved_bytes(Metaspace::MetadataType mdtype) { - VirtualSpaceList* list = Metaspace::get_space_list(mdtype); - return list == NULL ? 0 : list->reserved_bytes(); -} - -size_t MetaspaceUtils::committed_bytes(Metaspace::MetadataType mdtype) { - VirtualSpaceList* list = Metaspace::get_space_list(mdtype); - return list == NULL ? 0 : list->committed_bytes(); -} - -size_t MetaspaceUtils::min_chunk_size_words() { return Metaspace::first_chunk_word_size(); } - -size_t MetaspaceUtils::free_chunks_total_words(Metaspace::MetadataType mdtype) { - ChunkManager* chunk_manager = Metaspace::get_chunk_manager(mdtype); - if (chunk_manager == NULL) { - return 0; - } - return chunk_manager->free_chunks_total_words(); -} - -size_t MetaspaceUtils::free_chunks_total_bytes(Metaspace::MetadataType mdtype) { - return free_chunks_total_words(mdtype) * BytesPerWord; -} - -size_t MetaspaceUtils::free_chunks_total_words() { - return free_chunks_total_words(Metaspace::ClassType) + - free_chunks_total_words(Metaspace::NonClassType); -} - -size_t MetaspaceUtils::free_chunks_total_bytes() { - return free_chunks_total_words() * BytesPerWord; -} - -bool MetaspaceUtils::has_chunk_free_list(Metaspace::MetadataType mdtype) { - return Metaspace::get_chunk_manager(mdtype) != NULL; -} - -MetaspaceChunkFreeListSummary MetaspaceUtils::chunk_free_list_summary(Metaspace::MetadataType mdtype) { - if (!has_chunk_free_list(mdtype)) { - return MetaspaceChunkFreeListSummary(); - } - - const ChunkManager* cm = Metaspace::get_chunk_manager(mdtype); - return cm->chunk_free_list_summary(); -} - -void MetaspaceUtils::print_metaspace_change(const metaspace::MetaspaceSizesSnapshot& pre_meta_values) { - const metaspace::MetaspaceSizesSnapshot meta_values; - - if (Metaspace::using_class_space()) { - log_info(gc, metaspace)(HEAP_CHANGE_FORMAT" " - HEAP_CHANGE_FORMAT" " - HEAP_CHANGE_FORMAT, - HEAP_CHANGE_FORMAT_ARGS("Metaspace", - pre_meta_values.used(), - pre_meta_values.committed(), - meta_values.used(), - meta_values.committed()), - HEAP_CHANGE_FORMAT_ARGS("NonClass", - pre_meta_values.non_class_used(), - pre_meta_values.non_class_committed(), - meta_values.non_class_used(), - meta_values.non_class_committed()), - HEAP_CHANGE_FORMAT_ARGS("Class", - pre_meta_values.class_used(), - pre_meta_values.class_committed(), - meta_values.class_used(), - meta_values.class_committed())); - } else { - log_info(gc, metaspace)(HEAP_CHANGE_FORMAT, - HEAP_CHANGE_FORMAT_ARGS("Metaspace", - pre_meta_values.used(), - pre_meta_values.committed(), - meta_values.used(), - meta_values.committed())); - } -} - -void MetaspaceUtils::print_on(outputStream* out) { - Metaspace::MetadataType nct = Metaspace::NonClassType; - - out->print_cr(" Metaspace " - "used " SIZE_FORMAT "K, " - "capacity " SIZE_FORMAT "K, " - "committed " SIZE_FORMAT "K, " - "reserved " SIZE_FORMAT "K", - used_bytes()/K, - capacity_bytes()/K, - committed_bytes()/K, - reserved_bytes()/K); - - if (Metaspace::using_class_space()) { - Metaspace::MetadataType ct = Metaspace::ClassType; - out->print_cr(" class space " - "used " SIZE_FORMAT "K, " - "capacity " SIZE_FORMAT "K, " - "committed " SIZE_FORMAT "K, " - "reserved " SIZE_FORMAT "K", - used_bytes(ct)/K, - capacity_bytes(ct)/K, - committed_bytes(ct)/K, - reserved_bytes(ct)/K); - } -} - - -void MetaspaceUtils::print_vs(outputStream* out, size_t scale) { - const size_t reserved_nonclass_words = reserved_bytes(Metaspace::NonClassType) / sizeof(MetaWord); - const size_t committed_nonclass_words = committed_bytes(Metaspace::NonClassType) / sizeof(MetaWord); - { - if (Metaspace::using_class_space()) { - out->print(" Non-class space: "); - } - print_scaled_words(out, reserved_nonclass_words, scale, 7); - out->print(" reserved, "); - print_scaled_words_and_percentage(out, committed_nonclass_words, reserved_nonclass_words, scale, 7); - out->print_cr(" committed "); - - if (Metaspace::using_class_space()) { - const size_t reserved_class_words = reserved_bytes(Metaspace::ClassType) / sizeof(MetaWord); - const size_t committed_class_words = committed_bytes(Metaspace::ClassType) / sizeof(MetaWord); - out->print(" Class space: "); - print_scaled_words(out, reserved_class_words, scale, 7); - out->print(" reserved, "); - print_scaled_words_and_percentage(out, committed_class_words, reserved_class_words, scale, 7); - out->print_cr(" committed "); - - const size_t reserved_words = reserved_nonclass_words + reserved_class_words; - const size_t committed_words = committed_nonclass_words + committed_class_words; - out->print(" Both: "); - print_scaled_words(out, reserved_words, scale, 7); - out->print(" reserved, "); - print_scaled_words_and_percentage(out, committed_words, reserved_words, scale, 7); - out->print_cr(" committed "); - } - } -} - -static void print_basic_switches(outputStream* out, size_t scale) { - out->print("MaxMetaspaceSize: "); - if (MaxMetaspaceSize >= (max_uintx) - (2 * os::vm_page_size())) { - // aka "very big". Default is max_uintx, but due to rounding in arg parsing the real - // value is smaller. - out->print("unlimited"); - } else { - print_human_readable_size(out, MaxMetaspaceSize, scale); - } - out->cr(); - if (Metaspace::using_class_space()) { - out->print("CompressedClassSpaceSize: "); - print_human_readable_size(out, CompressedClassSpaceSize, scale); - } - out->cr(); -} - -// This will print out a basic metaspace usage report but -// unlike print_report() is guaranteed not to lock or to walk the CLDG. -void MetaspaceUtils::print_basic_report(outputStream* out, size_t scale) { - - if (!Metaspace::initialized()) { - out->print_cr("Metaspace not yet initialized."); - return; - } - - out->cr(); - out->print_cr("Usage:"); - - if (Metaspace::using_class_space()) { - out->print(" Non-class: "); - } - - // In its most basic form, we do not require walking the CLDG. Instead, just print the running totals from - // MetaspaceUtils. - const size_t cap_nc = MetaspaceUtils::capacity_words(Metaspace::NonClassType); - const size_t overhead_nc = MetaspaceUtils::overhead_words(Metaspace::NonClassType); - const size_t used_nc = MetaspaceUtils::used_words(Metaspace::NonClassType); - const size_t free_and_waste_nc = cap_nc - overhead_nc - used_nc; - - print_scaled_words(out, cap_nc, scale, 5); - out->print(" capacity, "); - print_scaled_words_and_percentage(out, used_nc, cap_nc, scale, 5); - out->print(" used, "); - print_scaled_words_and_percentage(out, free_and_waste_nc, cap_nc, scale, 5); - out->print(" free+waste, "); - print_scaled_words_and_percentage(out, overhead_nc, cap_nc, scale, 5); - out->print(" overhead. "); - out->cr(); - - if (Metaspace::using_class_space()) { - const size_t cap_c = MetaspaceUtils::capacity_words(Metaspace::ClassType); - const size_t overhead_c = MetaspaceUtils::overhead_words(Metaspace::ClassType); - const size_t used_c = MetaspaceUtils::used_words(Metaspace::ClassType); - const size_t free_and_waste_c = cap_c - overhead_c - used_c; - out->print(" Class: "); - print_scaled_words(out, cap_c, scale, 5); - out->print(" capacity, "); - print_scaled_words_and_percentage(out, used_c, cap_c, scale, 5); - out->print(" used, "); - print_scaled_words_and_percentage(out, free_and_waste_c, cap_c, scale, 5); - out->print(" free+waste, "); - print_scaled_words_and_percentage(out, overhead_c, cap_c, scale, 5); - out->print(" overhead. "); - out->cr(); - - out->print(" Both: "); - const size_t cap = cap_nc + cap_c; - - print_scaled_words(out, cap, scale, 5); - out->print(" capacity, "); - print_scaled_words_and_percentage(out, used_nc + used_c, cap, scale, 5); - out->print(" used, "); - print_scaled_words_and_percentage(out, free_and_waste_nc + free_and_waste_c, cap, scale, 5); - out->print(" free+waste, "); - print_scaled_words_and_percentage(out, overhead_nc + overhead_c, cap, scale, 5); - out->print(" overhead. "); - out->cr(); - } - - out->cr(); - out->print_cr("Virtual space:"); - - print_vs(out, scale); - - out->cr(); - out->print_cr("Chunk freelists:"); - - if (Metaspace::using_class_space()) { - out->print(" Non-Class: "); - } - print_human_readable_size(out, Metaspace::chunk_manager_metadata()->free_chunks_total_bytes(), scale); - out->cr(); - if (Metaspace::using_class_space()) { - out->print(" Class: "); - print_human_readable_size(out, Metaspace::chunk_manager_class()->free_chunks_total_bytes(), scale); - out->cr(); - out->print(" Both: "); - print_human_readable_size(out, Metaspace::chunk_manager_class()->free_chunks_total_bytes() + - Metaspace::chunk_manager_metadata()->free_chunks_total_bytes(), scale); - out->cr(); - } - - out->cr(); - - // Print basic settings - print_basic_switches(out, scale); - - out->cr(); - -} - -void MetaspaceUtils::print_report(outputStream* out, size_t scale, int flags) { - - if (!Metaspace::initialized()) { - out->print_cr("Metaspace not yet initialized."); - return; - } - - const bool print_loaders = (flags & rf_show_loaders) > 0; - const bool print_classes = (flags & rf_show_classes) > 0; - const bool print_by_chunktype = (flags & rf_break_down_by_chunktype) > 0; - const bool print_by_spacetype = (flags & rf_break_down_by_spacetype) > 0; - - // Some report options require walking the class loader data graph. - PrintCLDMetaspaceInfoClosure cl(out, scale, print_loaders, print_classes, print_by_chunktype); - if (print_loaders) { - out->cr(); - out->print_cr("Usage per loader:"); - out->cr(); - } - - ClassLoaderDataGraph::loaded_cld_do(&cl); // collect data and optionally print - - // Print totals, broken up by space type. - if (print_by_spacetype) { - out->cr(); - out->print_cr("Usage per space type:"); - out->cr(); - for (int space_type = (int)Metaspace::ZeroMetaspaceType; - space_type < (int)Metaspace::MetaspaceTypeCount; space_type ++) - { - uintx num_loaders = cl._num_loaders_by_spacetype[space_type]; - uintx num_classes = cl._num_classes_by_spacetype[space_type]; - out->print("%s - " UINTX_FORMAT " %s", - space_type_name((Metaspace::MetaspaceType)space_type), - num_loaders, loaders_plural(num_loaders)); - if (num_classes > 0) { - out->print(", "); - print_number_of_classes(out, num_classes, cl._num_classes_shared_by_spacetype[space_type]); - out->print(":"); - cl._stats_by_spacetype[space_type].print_on(out, scale, print_by_chunktype); - } else { - out->print("."); - out->cr(); - } - out->cr(); - } - } - - // Print totals for in-use data: - out->cr(); - { - uintx num_loaders = cl._num_loaders; - out->print("Total Usage - " UINTX_FORMAT " %s, ", - num_loaders, loaders_plural(num_loaders)); - print_number_of_classes(out, cl._num_classes, cl._num_classes_shared); - out->print(":"); - cl._stats_total.print_on(out, scale, print_by_chunktype); - out->cr(); - } - - // -- Print Virtual space. - out->cr(); - out->print_cr("Virtual space:"); - - print_vs(out, scale); - - // -- Print VirtualSpaceList details. - if ((flags & rf_show_vslist) > 0) { - out->cr(); - out->print_cr("Virtual space list%s:", Metaspace::using_class_space() ? "s" : ""); - - if (Metaspace::using_class_space()) { - out->print_cr(" Non-Class:"); - } - Metaspace::space_list()->print_on(out, scale); - if (Metaspace::using_class_space()) { - out->print_cr(" Class:"); - Metaspace::class_space_list()->print_on(out, scale); - } - } - out->cr(); - - // -- Print VirtualSpaceList map. - if ((flags & rf_show_vsmap) > 0) { - out->cr(); - out->print_cr("Virtual space map:"); - - if (Metaspace::using_class_space()) { - out->print_cr(" Non-Class:"); - } - Metaspace::space_list()->print_map(out); - if (Metaspace::using_class_space()) { - out->print_cr(" Class:"); - Metaspace::class_space_list()->print_map(out); - } - } - out->cr(); - - // -- Print Freelists (ChunkManager) details - out->cr(); - out->print_cr("Chunk freelist%s:", Metaspace::using_class_space() ? "s" : ""); - - ChunkManagerStatistics non_class_cm_stat; - Metaspace::chunk_manager_metadata()->collect_statistics(&non_class_cm_stat); - - if (Metaspace::using_class_space()) { - out->print_cr(" Non-Class:"); - } - non_class_cm_stat.print_on(out, scale); - - if (Metaspace::using_class_space()) { - ChunkManagerStatistics class_cm_stat; - Metaspace::chunk_manager_class()->collect_statistics(&class_cm_stat); - out->print_cr(" Class:"); - class_cm_stat.print_on(out, scale); - } - - // As a convenience, print a summary of common waste. - out->cr(); - out->print("Waste "); - // For all wastages, print percentages from total. As total use the total size of memory committed for metaspace. - const size_t committed_words = committed_bytes() / BytesPerWord; - - out->print("(percentages refer to total committed size "); - print_scaled_words(out, committed_words, scale); - out->print_cr("):"); - - // Print space committed but not yet used by any class loader - const size_t unused_words_in_vs = MetaspaceUtils::free_in_vs_bytes() / BytesPerWord; - out->print(" Committed unused: "); - print_scaled_words_and_percentage(out, unused_words_in_vs, committed_words, scale, 6); - out->cr(); - - // Print waste for in-use chunks. - UsedChunksStatistics ucs_nonclass = cl._stats_total.nonclass_sm_stats().totals(); - UsedChunksStatistics ucs_class = cl._stats_total.class_sm_stats().totals(); - UsedChunksStatistics ucs_all; - ucs_all.add(ucs_nonclass); - ucs_all.add(ucs_class); - - out->print(" Waste in chunks in use: "); - print_scaled_words_and_percentage(out, ucs_all.waste(), committed_words, scale, 6); - out->cr(); - out->print(" Free in chunks in use: "); - print_scaled_words_and_percentage(out, ucs_all.free(), committed_words, scale, 6); - out->cr(); - out->print(" Overhead in chunks in use: "); - print_scaled_words_and_percentage(out, ucs_all.overhead(), committed_words, scale, 6); - out->cr(); - - // Print waste in free chunks. - const size_t total_capacity_in_free_chunks = - Metaspace::chunk_manager_metadata()->free_chunks_total_words() + - (Metaspace::using_class_space() ? Metaspace::chunk_manager_class()->free_chunks_total_words() : 0); - out->print(" In free chunks: "); - print_scaled_words_and_percentage(out, total_capacity_in_free_chunks, committed_words, scale, 6); - out->cr(); - - // Print waste in deallocated blocks. - const uintx free_blocks_num = - cl._stats_total.nonclass_sm_stats().free_blocks_num() + - cl._stats_total.class_sm_stats().free_blocks_num(); - const size_t free_blocks_cap_words = - cl._stats_total.nonclass_sm_stats().free_blocks_cap_words() + - cl._stats_total.class_sm_stats().free_blocks_cap_words(); - out->print("Deallocated from chunks in use: "); - print_scaled_words_and_percentage(out, free_blocks_cap_words, committed_words, scale, 6); - out->print(" (" UINTX_FORMAT " blocks)", free_blocks_num); - out->cr(); - - // Print total waste. - const size_t total_waste = ucs_all.waste() + ucs_all.free() + ucs_all.overhead() + total_capacity_in_free_chunks - + free_blocks_cap_words + unused_words_in_vs; - out->print(" -total-: "); - print_scaled_words_and_percentage(out, total_waste, committed_words, scale, 6); - out->cr(); - - // Print internal statistics -#ifdef ASSERT - out->cr(); - out->cr(); - out->print_cr("Internal statistics:"); - out->cr(); - out->print_cr("Number of allocations: " UINTX_FORMAT ".", g_internal_statistics.num_allocs); - out->print_cr("Number of space births: " UINTX_FORMAT ".", g_internal_statistics.num_metaspace_births); - out->print_cr("Number of space deaths: " UINTX_FORMAT ".", g_internal_statistics.num_metaspace_deaths); - out->print_cr("Number of virtual space node births: " UINTX_FORMAT ".", g_internal_statistics.num_vsnodes_created); - out->print_cr("Number of virtual space node deaths: " UINTX_FORMAT ".", g_internal_statistics.num_vsnodes_purged); - out->print_cr("Number of times virtual space nodes were expanded: " UINTX_FORMAT ".", g_internal_statistics.num_committed_space_expanded); - out->print_cr("Number of deallocations: " UINTX_FORMAT " (" UINTX_FORMAT " external).", g_internal_statistics.num_deallocs, g_internal_statistics.num_external_deallocs); - out->print_cr("Allocations from deallocated blocks: " UINTX_FORMAT ".", g_internal_statistics.num_allocs_from_deallocated_blocks); - out->print_cr("Number of chunks added to freelist: " UINTX_FORMAT ".", - g_internal_statistics.num_chunks_added_to_freelist); - out->print_cr("Number of chunks removed from freelist: " UINTX_FORMAT ".", - g_internal_statistics.num_chunks_removed_from_freelist); - out->print_cr("Number of chunk merges: " UINTX_FORMAT ", split-ups: " UINTX_FORMAT ".", - g_internal_statistics.num_chunk_merges, g_internal_statistics.num_chunk_splits); - - out->cr(); -#endif - - // Print some interesting settings - out->cr(); - out->cr(); - print_basic_switches(out, scale); - - out->cr(); - out->print("InitialBootClassLoaderMetaspaceSize: "); - print_human_readable_size(out, InitialBootClassLoaderMetaspaceSize, scale); - - out->cr(); - out->cr(); - -} // MetaspaceUtils::print_report() - -// Prints an ASCII representation of the given space. -void MetaspaceUtils::print_metaspace_map(outputStream* out, Metaspace::MetadataType mdtype) { - MutexLocker cl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); - const bool for_class = mdtype == Metaspace::ClassType ? true : false; - VirtualSpaceList* const vsl = for_class ? Metaspace::class_space_list() : Metaspace::space_list(); - if (vsl != NULL) { - if (for_class) { - if (!Metaspace::using_class_space()) { - out->print_cr("No Class Space."); - return; - } - out->print_raw("---- Metaspace Map (Class Space) ----"); - } else { - out->print_raw("---- Metaspace Map (Non-Class Space) ----"); - } - // Print legend: - out->cr(); - out->print_cr("Chunk Types (uppercase chunks are in use): x-specialized, s-small, m-medium, h-humongous."); - out->cr(); - VirtualSpaceList* const vsl = for_class ? Metaspace::class_space_list() : Metaspace::space_list(); - vsl->print_map(out); - out->cr(); - } -} - -void MetaspaceUtils::verify_free_chunks() { -#ifdef ASSERT - Metaspace::chunk_manager_metadata()->verify(false); - if (Metaspace::using_class_space()) { - Metaspace::chunk_manager_class()->verify(false); - } -#endif -} - -void MetaspaceUtils::verify_metrics() { -#ifdef ASSERT - // Please note: there are time windows where the internal counters are out of sync with - // reality. For example, when a newly created ClassLoaderMetaspace creates its first chunk - - // the ClassLoaderMetaspace is not yet attached to its ClassLoaderData object and hence will - // not be counted when iterating the CLDG. So be careful when you call this method. - ClassLoaderMetaspaceStatistics total_stat; - collect_statistics(&total_stat); - UsedChunksStatistics nonclass_chunk_stat = total_stat.nonclass_sm_stats().totals(); - UsedChunksStatistics class_chunk_stat = total_stat.class_sm_stats().totals(); - - bool mismatch = false; - for (int i = 0; i < Metaspace::MetadataTypeCount; i ++) { - Metaspace::MetadataType mdtype = (Metaspace::MetadataType)i; - UsedChunksStatistics chunk_stat = total_stat.sm_stats(mdtype).totals(); - if (capacity_words(mdtype) != chunk_stat.cap() || - used_words(mdtype) != chunk_stat.used() || - overhead_words(mdtype) != chunk_stat.overhead()) { - mismatch = true; - tty->print_cr("MetaspaceUtils::verify_metrics: counter mismatch for mdtype=%u:", mdtype); - tty->print_cr("Expected cap " SIZE_FORMAT ", used " SIZE_FORMAT ", overhead " SIZE_FORMAT ".", - capacity_words(mdtype), used_words(mdtype), overhead_words(mdtype)); - tty->print_cr("Got cap " SIZE_FORMAT ", used " SIZE_FORMAT ", overhead " SIZE_FORMAT ".", - chunk_stat.cap(), chunk_stat.used(), chunk_stat.overhead()); - tty->flush(); - } - } - assert(mismatch == false, "MetaspaceUtils::verify_metrics: counter mismatch."); -#endif -} - -// Metaspace methods - -size_t Metaspace::_first_chunk_word_size = 0; -size_t Metaspace::_first_class_chunk_word_size = 0; - +MetaWord* Metaspace::_compressed_class_space_base = NULL; +size_t Metaspace::_compressed_class_space_size = 0; +const MetaspaceTracer* Metaspace::_tracer = NULL; +bool Metaspace::_initialized = false; size_t Metaspace::_commit_alignment = 0; size_t Metaspace::_reserve_alignment = 0; -VirtualSpaceList* Metaspace::_space_list = NULL; -VirtualSpaceList* Metaspace::_class_space_list = NULL; +DEBUG_ONLY(bool Metaspace::_frozen = false;) -ChunkManager* Metaspace::_chunk_manager_metadata = NULL; -ChunkManager* Metaspace::_chunk_manager_class = NULL; - -bool Metaspace::_initialized = false; - -#define VIRTUALSPACEMULTIPLIER 2 #ifdef _LP64 static const uint64_t UnscaledClassSpaceMax = (uint64_t(max_juint) + 1); @@ -1002,6 +536,15 @@ } } + // We must prevent any metaspace object from being allocated directly at + // CompressedKlassPointers::base() - that would translate to a narrow Klass + // pointer of 0, which has a special meaning (invalid) (Note: that was + // never a problem in old metaspace, since every chunk was prefixed by its + // header, so allocation at position 0 in a chunk was never possible). + if (lower_base == metaspace_base) { + lower_base -= os::vm_page_size(); + } + CompressedKlassPointers::set_base(lower_base); // CDS uses LogKlassAlignmentInBytes for narrow_klass_shift. See @@ -1047,23 +590,23 @@ bool large_pages = false; #if !(defined(AARCH64) || defined(AIX)) - ReservedSpace metaspace_rs = ReservedSpace(compressed_class_space_size(), + ReservedSpace rs = ReservedSpace(compressed_class_space_size(), _reserve_alignment, large_pages, requested_addr); #else // AARCH64 - ReservedSpace metaspace_rs; + ReservedSpace rs; // Our compressed klass pointers may fit nicely into the lower 32 // bits. if ((uint64_t)requested_addr + compressed_class_space_size() < 4*G) { - metaspace_rs = ReservedSpace(compressed_class_space_size(), + rs = ReservedSpace(compressed_class_space_size(), _reserve_alignment, large_pages, requested_addr); } - if (! metaspace_rs.is_reserved()) { + if (! rs.is_reserved()) { // Aarch64: Try to align metaspace so that we can decode a compressed // klass with a single MOVK instruction. We can do this iff the // compressed class base is a multiple of 4G. @@ -1083,7 +626,7 @@ && ! can_use_cds_with_metaspace_addr(a, cds_base)) { // We failed to find an aligned base that will reach. Fall // back to using our requested addr. - metaspace_rs = ReservedSpace(compressed_class_space_size(), + rs = ReservedSpace(compressed_class_space_size(), _reserve_alignment, large_pages, requested_addr); @@ -1091,18 +634,18 @@ } #endif - metaspace_rs = ReservedSpace(compressed_class_space_size(), + rs = ReservedSpace(compressed_class_space_size(), _reserve_alignment, large_pages, a); - if (metaspace_rs.is_reserved()) + if (rs.is_reserved()) break; } } #endif // AARCH64 - if (!metaspace_rs.is_reserved()) { + if (!rs.is_reserved()) { #if INCLUDE_CDS if (UseSharedSpaces) { size_t increment = align_up(1*G, _reserve_alignment); @@ -1111,10 +654,10 @@ // by 1GB each time, until we reach an address that will no longer allow // use of CDS with compressed klass pointers. char *addr = requested_addr; - while (!metaspace_rs.is_reserved() && (addr + increment > addr) && + while (!rs.is_reserved() && (addr + increment > addr) && can_use_cds_with_metaspace_addr(addr + increment, cds_base)) { addr = addr + increment; - metaspace_rs = ReservedSpace(compressed_class_space_size(), + rs = ReservedSpace(compressed_class_space_size(), _reserve_alignment, large_pages, addr); } } @@ -1124,10 +667,10 @@ // metaspace as if UseCompressedClassPointers is off because too much // initialization has happened that depends on UseCompressedClassPointers. // So, UseCompressedClassPointers cannot be turned off at this point. - if (!metaspace_rs.is_reserved()) { - metaspace_rs = ReservedSpace(compressed_class_space_size(), + if (!rs.is_reserved()) { + rs = ReservedSpace(compressed_class_space_size(), _reserve_alignment, large_pages); - if (!metaspace_rs.is_reserved()) { + if (!rs.is_reserved()) { vm_exit_during_initialization(err_msg("Could not allocate metaspace: " SIZE_FORMAT " bytes", compressed_class_space_size())); } @@ -1135,19 +678,21 @@ } // If we got here then the metaspace got allocated. - MemTracker::record_virtual_memory_type((address)metaspace_rs.base(), mtClass); + MemTracker::record_virtual_memory_type((address)rs.base(), mtClass); + + _compressed_class_space_base = (MetaWord*)rs.base(); #if INCLUDE_CDS // Verify that we can use shared spaces. Otherwise, turn off CDS. - if (UseSharedSpaces && !can_use_cds_with_metaspace_addr(metaspace_rs.base(), cds_base)) { + if (UseSharedSpaces && !can_use_cds_with_metaspace_addr(rs.base(), cds_base)) { FileMapInfo::stop_sharing_and_unmap( "Could not allocate metaspace at a compatible address"); } #endif - set_narrow_klass_base_and_shift((address)metaspace_rs.base(), + set_narrow_klass_base_and_shift((address)rs.base(), UseSharedSpaces ? (address)cds_base : 0); - initialize_class_space(metaspace_rs); + initialize_class_space(rs); LogTarget(Trace, gc, metaspace) lt; if (lt.is_enabled()) { @@ -1157,13 +702,29 @@ } } +// For UseCompressedClassPointers the class space is reserved above the top of +// the Java heap. The argument passed in is at the base of the compressed space. +void Metaspace::initialize_class_space(ReservedSpace rs) { + + // The reserved space size may be bigger because of alignment, esp with UseLargePages + assert(rs.size() >= CompressedClassSpaceSize, + SIZE_FORMAT " != " SIZE_FORMAT, rs.size(), CompressedClassSpaceSize); + assert(using_class_space(), "Must be using class space"); + + VirtualSpaceList* vsl = new VirtualSpaceList("class space list", rs, CommitLimiter::globalLimiter()); + VirtualSpaceList::set_vslist_class(vsl); + ChunkManager* cm = new ChunkManager("class space chunk manager", vsl); + ChunkManager::set_chunkmanager_class(cm); + +} + + void Metaspace::print_compressed_class_space(outputStream* st, const char* requested_addr) { st->print_cr("Narrow klass base: " PTR_FORMAT ", Narrow klass shift: %d", p2i(CompressedKlassPointers::base()), CompressedKlassPointers::shift()); - if (_class_space_list != NULL) { - address base = (address)_class_space_list->current_virtual_space()->bottom(); + if (Metaspace::using_class_space()) { st->print("Compressed class space size: " SIZE_FORMAT " Address: " PTR_FORMAT, - compressed_class_space_size(), p2i(base)); + compressed_class_space_size(), p2i(compressed_class_space_base())); if (requested_addr != 0) { st->print(" Req Addr: " PTR_FORMAT, p2i(requested_addr)); } @@ -1171,24 +732,23 @@ } } -// For UseCompressedClassPointers the class space is reserved above the top of -// the Java heap. The argument passed in is at the base of the compressed space. -void Metaspace::initialize_class_space(ReservedSpace rs) { - // The reserved space size may be bigger because of alignment, esp with UseLargePages - assert(rs.size() >= CompressedClassSpaceSize, - SIZE_FORMAT " != " SIZE_FORMAT, rs.size(), CompressedClassSpaceSize); - assert(using_class_space(), "Must be using class space"); - _class_space_list = new VirtualSpaceList(rs); - _chunk_manager_class = new ChunkManager(true/*is_class*/); - - if (!_class_space_list->initialization_succeeded()) { - vm_exit_during_initialization("Failed to setup compressed class space virtual space list."); - } -} - #endif void Metaspace::ergo_initialize() { + + // Must happen before using any setting from Settings::--- + metaspace::Settings::strategy_t strat = metaspace::Settings::strategy_balanced_reclaim; + if (strcmp(MetaspaceReclaimStrategy, "balanced") == 0) { + strat = metaspace::Settings::strategy_balanced_reclaim; + } else if (strcmp(MetaspaceReclaimStrategy, "aggressive") == 0) { + strat = metaspace::Settings::strategy_aggressive_reclaim; + } else if (strcmp(MetaspaceReclaimStrategy, "none") == 0) { + strat = metaspace::Settings::strategy_no_reclaim; + } else { + vm_exit_during_initialization("Invalid value for MetaspaceReclaimStrategy: \"%s\".", MetaspaceReclaimStrategy); + } + metaspace::Settings::initialize(strat); + if (DumpSharedSpaces) { // Using large pages when dumping the shared archive is currently not implemented. FLAG_SET_ERGO(UseLargePagesInMetaspace, false); @@ -1199,8 +759,15 @@ page_size = os::large_page_size(); } - _commit_alignment = page_size; - _reserve_alignment = MAX2(page_size, (size_t)os::vm_allocation_granularity()); + // Commit alignment: (I would rather hide this since this is an implementation detail but we need it + // when calculating the gc threshold). + _commit_alignment = metaspace::Settings::commit_granule_bytes(); + + // Reserve alignment: all Metaspace memory mappings are to be aligned to the size of a root chunk. + _reserve_alignment = MAX2(page_size, (size_t)metaspace::chklvl::MAX_CHUNK_BYTE_SIZE); + + assert(is_aligned(_reserve_alignment, os::vm_allocation_granularity()), + "root chunk size must be a multiple of alloc granularity"); // Do not use FLAG_SET_ERGO to update MaxMetaspaceSize, since this will // override if MaxMetaspaceSize was set on the command line or not. @@ -1225,28 +792,26 @@ CompressedClassSpaceSize = align_down_bounded(CompressedClassSpaceSize, _reserve_alignment); - // Initial virtual space size will be calculated at global_initialize() - size_t min_metaspace_sz = - VIRTUALSPACEMULTIPLIER * InitialBootClassLoaderMetaspaceSize; + // Note: InitialBootClassLoaderMetaspaceSize is an old parameter which is used to determine the chunk size + // of the first non-class chunk handed to the boot class loader. See metaspace/chunkAllocSequence.hpp. + size_t min_metaspace_sz = align_up(InitialBootClassLoaderMetaspaceSize, _reserve_alignment); 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(CompressedClassSpaceSize, - MaxMetaspaceSize - min_metaspace_sz); - } + if (min_metaspace_sz >= MaxMetaspaceSize) { + vm_exit_during_initialization("MaxMetaspaceSize is too small."); + } else if ((min_metaspace_sz + CompressedClassSpaceSize) > MaxMetaspaceSize) { + FLAG_SET_ERGO(CompressedClassSpaceSize, MaxMetaspaceSize - min_metaspace_sz); } } else if (min_metaspace_sz >= MaxMetaspaceSize) { FLAG_SET_ERGO(InitialBootClassLoaderMetaspaceSize, min_metaspace_sz); } - set_compressed_class_space_size(CompressedClassSpaceSize); + _compressed_class_space_size = CompressedClassSpaceSize; + } void Metaspace::global_initialize() { - MetaspaceGC::initialize(); + MetaspaceGC::initialize(); // <- since we do not prealloc init chunks anymore is this still needed? #if INCLUDE_CDS if (DumpSharedSpaces) { @@ -1262,10 +827,10 @@ if (DynamicDumpSharedSpaces && !UseSharedSpaces) { vm_exit_during_initialization("DynamicDumpSharedSpaces is unsupported when base CDS archive is not loaded", NULL); } +#endif // INCLUDE_CDS - if (!DumpSharedSpaces && !UseSharedSpaces) -#endif // INCLUDE_CDS - { + // Initialize class space: + if (CDS_ONLY(!DumpSharedSpaces && !UseSharedSpaces) NOT_CDS(true)) { #ifdef _LP64 if (using_class_space()) { char* base = (char*)align_up(CompressedOops::end(), _reserve_alignment); @@ -1274,27 +839,11 @@ #endif // _LP64 } - // Initialize these before initializing the VirtualSpaceList - _first_chunk_word_size = InitialBootClassLoaderMetaspaceSize / BytesPerWord; - _first_chunk_word_size = align_word_size_up(_first_chunk_word_size); - // Make the first class chunk bigger than a medium chunk so it's not put - // on the medium chunk list. The next chunk will be small and progress - // from there. This size calculated by -version. - _first_class_chunk_word_size = MIN2((size_t)MediumChunk*6, - (CompressedClassSpaceSize/BytesPerWord)*2); - _first_class_chunk_word_size = align_word_size_up(_first_class_chunk_word_size); - // Arbitrarily set the initial virtual space to a multiple - // of the boot class loader size. - size_t word_size = VIRTUALSPACEMULTIPLIER * _first_chunk_word_size; - word_size = align_up(word_size, Metaspace::reserve_alignment_words()); - - // Initialize the list of virtual spaces. - _space_list = new VirtualSpaceList(word_size); - _chunk_manager_metadata = new ChunkManager(false/*metaspace*/); - - if (!_space_list->initialization_succeeded()) { - vm_exit_during_initialization("Unable to setup metadata virtual space list.", NULL); - } + // Initialize non-class virtual space list, and its chunk manager: + VirtualSpaceList* vsl = new VirtualSpaceList("Non-Class VirtualSpaceList", CommitLimiter::globalLimiter()); + VirtualSpaceList::set_vslist_nonclass(vsl); + ChunkManager* cm = new ChunkManager("Non-Class ChunkManager", vsl); + ChunkManager::set_chunkmanager_nonclass(cm); _tracer = new MetaspaceTracer(); @@ -1306,21 +855,6 @@ MetaspaceGC::post_initialize(); } -void Metaspace::verify_global_initialization() { - assert(space_list() != NULL, "Metadata VirtualSpaceList has not been initialized"); - assert(chunk_manager_metadata() != NULL, "Metadata ChunkManager has not been initialized"); - - if (using_class_space()) { - assert(class_space_list() != NULL, "Class VirtualSpaceList has not been initialized"); - assert(chunk_manager_class() != NULL, "Class ChunkManager has not been initialized"); - } -} - -size_t Metaspace::align_word_size_up(size_t word_size) { - size_t byte_size = word_size * wordSize; - return ReservedSpace::allocation_align_size_up(byte_size) / wordSize; -} - MetaWord* Metaspace::allocate(ClassLoaderData* loader_data, size_t word_size, MetaspaceObj::Type type, TRAPS) { assert(!_frozen, "sanity"); @@ -1334,7 +868,7 @@ assert(loader_data != NULL, "Should never pass around a NULL loader_data. " "ClassLoaderData::the_null_class_loader_data() should have been used."); - MetadataType mdtype = (type == MetaspaceObj::ClassType) ? ClassType : NonClassType; + MetadataType mdtype = (type == MetaspaceObj::ClassType) ? metaspace::ClassType : metaspace::NonClassType; // Try to allocate metadata. MetaWord* result = loader_data->metaspace_non_null()->allocate(word_size, mdtype); @@ -1367,6 +901,8 @@ // Zero initialize. Copy::fill_to_words((HeapWord*)result, word_size, 0); + log_trace(metaspace)("Metaspace::allocate: type %d return " PTR_FORMAT ".", (int)type, p2i(result)); + return result; } @@ -1377,7 +913,7 @@ Log(gc, metaspace, freelist, oom) log; if (log.is_info()) { log.info("Metaspace (%s) allocation failed for size " SIZE_FORMAT, - is_class_space_allocation(mdtype) ? "class" : "data", word_size); + is_class(mdtype) ? "class" : "data", word_size); ResourceMark rm; if (log.is_debug()) { if (loader_data->metaspace_or_null() != NULL) { @@ -1390,12 +926,17 @@ MetaspaceUtils::print_basic_report(&ls, 0); } + // Which limit did we hit? CompressedClassSpaceSize or MaxMetaspaceSize? bool out_of_compressed_class_space = false; - if (is_class_space_allocation(mdtype)) { + if (is_class(mdtype)) { ClassLoaderMetaspace* metaspace = loader_data->metaspace_non_null(); out_of_compressed_class_space = - MetaspaceUtils::committed_bytes(Metaspace::ClassType) + - (metaspace->class_chunk_size(word_size) * BytesPerWord) > + MetaspaceUtils::committed_bytes(metaspace::ClassType) + + // TODO: Okay this is just cheesy. + // Of course this may fail and return incorrect results. + // Think this over - we need some clean way to remember which limit + // exactly we hit during an allocation. Some sort of allocation context structure? + align_up(word_size * BytesPerWord, 4 * M) > CompressedClassSpaceSize; } @@ -1422,26 +963,16 @@ } } -const char* Metaspace::metadata_type_name(Metaspace::MetadataType mdtype) { - switch (mdtype) { - case Metaspace::ClassType: return "Class"; - case Metaspace::NonClassType: return "Metadata"; - default: - assert(false, "Got bad mdtype: %d", (int) mdtype); - return NULL; +void Metaspace::purge() { + ChunkManager* cm = ChunkManager::chunkmanager_nonclass(); + if (cm != NULL) { + cm->wholesale_reclaim(); } -} - -void Metaspace::purge(MetadataType mdtype) { - get_space_list(mdtype)->purge(get_chunk_manager(mdtype)); -} - -void Metaspace::purge() { - MutexLocker cl(MetaspaceExpand_lock, - Mutex::_no_safepoint_check_flag); - purge(NonClassType); if (using_class_space()) { - purge(ClassType); + cm = ChunkManager::chunkmanager_class(); + if (cm != NULL) { + cm->wholesale_reclaim(); + } } } @@ -1453,214 +984,9 @@ } bool Metaspace::contains_non_shared(const void* ptr) { - if (using_class_space() && get_space_list(ClassType)->contains(ptr)) { + if (using_class_space() && VirtualSpaceList::vslist_class()->contains((MetaWord*)ptr)) { return true; } - return get_space_list(NonClassType)->contains(ptr); + return VirtualSpaceList::vslist_nonclass()->contains((MetaWord*)ptr); } - -// ClassLoaderMetaspace - -ClassLoaderMetaspace::ClassLoaderMetaspace(Mutex* lock, Metaspace::MetaspaceType type) - : _space_type(type) - , _lock(lock) - , _vsm(NULL) - , _class_vsm(NULL) -{ - initialize(lock, type); -} - -ClassLoaderMetaspace::~ClassLoaderMetaspace() { - Metaspace::assert_not_frozen(); - DEBUG_ONLY(Atomic::inc(&g_internal_statistics.num_metaspace_deaths)); - delete _vsm; - if (Metaspace::using_class_space()) { - delete _class_vsm; - } -} - -void ClassLoaderMetaspace::initialize_first_chunk(Metaspace::MetaspaceType type, Metaspace::MetadataType mdtype) { - Metachunk* chunk = get_initialization_chunk(type, mdtype); - if (chunk != NULL) { - // Add to this manager's list of chunks in use and make it the current_chunk(). - get_space_manager(mdtype)->add_chunk(chunk, true); - } -} - -Metachunk* ClassLoaderMetaspace::get_initialization_chunk(Metaspace::MetaspaceType type, Metaspace::MetadataType mdtype) { - size_t chunk_word_size = get_space_manager(mdtype)->get_initial_chunk_size(type); - - // Get a chunk from the chunk freelist - 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()); - } - - return chunk; -} - -void ClassLoaderMetaspace::initialize(Mutex* lock, Metaspace::MetaspaceType type) { - Metaspace::verify_global_initialization(); - - DEBUG_ONLY(Atomic::inc(&g_internal_statistics.num_metaspace_births)); - - // Allocate SpaceManager for metadata objects. - _vsm = new SpaceManager(Metaspace::NonClassType, type, lock); - - if (Metaspace::using_class_space()) { - // Allocate SpaceManager for classes. - _class_vsm = new SpaceManager(Metaspace::ClassType, type, lock); - } - - MutexLocker cl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); - - // Allocate chunk for metadata objects - initialize_first_chunk(type, Metaspace::NonClassType); - - // Allocate chunk for class metadata objects - if (Metaspace::using_class_space()) { - initialize_first_chunk(type, Metaspace::ClassType); - } -} - -MetaWord* ClassLoaderMetaspace::allocate(size_t word_size, Metaspace::MetadataType mdtype) { - Metaspace::assert_not_frozen(); - - DEBUG_ONLY(Atomic::inc(&g_internal_statistics.num_allocs)); - - // Don't use class_vsm() unless UseCompressedClassPointers is true. - if (Metaspace::is_class_space_allocation(mdtype)) { - return class_vsm()->allocate(word_size); - } else { - return vsm()->allocate(word_size); - } -} - -MetaWord* ClassLoaderMetaspace::expand_and_allocate(size_t word_size, Metaspace::MetadataType mdtype) { - Metaspace::assert_not_frozen(); - size_t delta_bytes = MetaspaceGC::delta_capacity_until_GC(word_size * BytesPerWord); - assert(delta_bytes > 0, "Must be"); - - size_t before = 0; - size_t after = 0; - bool can_retry = true; - MetaWord* res; - bool incremented; - - // Each thread increments the HWM at most once. Even if the thread fails to increment - // the HWM, an allocation is still attempted. This is because another thread must then - // have incremented the HWM and therefore the allocation might still succeed. - do { - incremented = MetaspaceGC::inc_capacity_until_GC(delta_bytes, &after, &before, &can_retry); - res = allocate(word_size, mdtype); - } while (!incremented && res == NULL && can_retry); - - if (incremented) { - Metaspace::tracer()->report_gc_threshold(before, after, - MetaspaceGCThresholdUpdater::ExpandAndAllocate); - log_trace(gc, metaspace)("Increase capacity to GC from " SIZE_FORMAT " to " SIZE_FORMAT, before, after); - } - - return res; -} - -size_t ClassLoaderMetaspace::allocated_blocks_bytes() const { - return (vsm()->used_words() + - (Metaspace::using_class_space() ? class_vsm()->used_words() : 0)) * BytesPerWord; -} - -size_t ClassLoaderMetaspace::allocated_chunks_bytes() const { - return (vsm()->capacity_words() + - (Metaspace::using_class_space() ? class_vsm()->capacity_words() : 0)) * BytesPerWord; -} - -void ClassLoaderMetaspace::deallocate(MetaWord* ptr, size_t word_size, bool is_class) { - Metaspace::assert_not_frozen(); - assert(!SafepointSynchronize::is_at_safepoint() - || Thread::current()->is_VM_thread(), "should be the VM thread"); - - DEBUG_ONLY(Atomic::inc(&g_internal_statistics.num_external_deallocs)); - - MutexLocker ml(vsm()->lock(), Mutex::_no_safepoint_check_flag); - - if (is_class && Metaspace::using_class_space()) { - class_vsm()->deallocate(ptr, word_size); - } else { - vsm()->deallocate(ptr, word_size); - } -} - -size_t ClassLoaderMetaspace::class_chunk_size(size_t word_size) { - assert(Metaspace::using_class_space(), "Has to use class space"); - return class_vsm()->calc_chunk_size(word_size); -} - -void ClassLoaderMetaspace::print_on(outputStream* out) const { - // Print both class virtual space counts and metaspace. - if (Verbose) { - vsm()->print_on(out); - if (Metaspace::using_class_space()) { - class_vsm()->print_on(out); - } - } -} - -void ClassLoaderMetaspace::verify() { - vsm()->verify(); - if (Metaspace::using_class_space()) { - class_vsm()->verify(); - } -} - -void ClassLoaderMetaspace::add_to_statistics_locked(ClassLoaderMetaspaceStatistics* out) const { - assert_lock_strong(lock()); - vsm()->add_to_statistics_locked(&out->nonclass_sm_stats()); - if (Metaspace::using_class_space()) { - class_vsm()->add_to_statistics_locked(&out->class_sm_stats()); - } -} - -void ClassLoaderMetaspace::add_to_statistics(ClassLoaderMetaspaceStatistics* out) const { - MutexLocker cl(lock(), Mutex::_no_safepoint_check_flag); - add_to_statistics_locked(out); -} - -/////////////// Unit tests /////////////// - -struct chunkmanager_statistics_t { - int num_specialized_chunks; - int num_small_chunks; - int num_medium_chunks; - int num_humongous_chunks; -}; - -extern void test_metaspace_retrieve_chunkmanager_statistics(Metaspace::MetadataType mdType, chunkmanager_statistics_t* out) { - ChunkManager* const chunk_manager = Metaspace::get_chunk_manager(mdType); - ChunkManagerStatistics stat; - chunk_manager->collect_statistics(&stat); - out->num_specialized_chunks = (int)stat.chunk_stats(SpecializedIndex).num(); - out->num_small_chunks = (int)stat.chunk_stats(SmallIndex).num(); - out->num_medium_chunks = (int)stat.chunk_stats(MediumIndex).num(); - out->num_humongous_chunks = (int)stat.chunk_stats(HumongousIndex).num(); -} - -struct chunk_geometry_t { - size_t specialized_chunk_word_size; - size_t small_chunk_word_size; - size_t medium_chunk_word_size; -}; - -extern void test_metaspace_retrieve_chunk_geometry(Metaspace::MetadataType mdType, chunk_geometry_t* out) { - if (mdType == Metaspace::NonClassType) { - out->specialized_chunk_word_size = SpecializedChunk; - out->small_chunk_word_size = SmallChunk; - out->medium_chunk_word_size = MediumChunk; - } else { - out->specialized_chunk_word_size = ClassSpecializedChunk; - out->small_chunk_word_size = ClassSmallChunk; - out->medium_chunk_word_size = ClassMediumChunk; - } -} diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace.hpp --- a/src/hotspot/share/memory/metaspace.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -26,11 +26,12 @@ #include "memory/allocation.hpp" #include "memory/memRegion.hpp" +#include "memory/metaspace/metaspaceEnums.hpp" #include "memory/metaspaceChunkFreeListSummary.hpp" #include "memory/virtualspace.hpp" -#include "memory/metaspace/metaspaceSizesSnapshot.hpp" #include "runtime/globals.hpp" #include "utilities/exceptions.hpp" +#include "utilities/globalDefinitions.hpp" // Metaspace // @@ -58,23 +59,17 @@ // class ClassLoaderData; +class MetaspaceShared; class MetaspaceTracer; -class Mutex; class outputStream; -class CollectedHeap; namespace metaspace { - class ChunkManager; - class ClassLoaderMetaspaceStatistics; - class Metablock; - class Metachunk; - class PrintCLDMetaspaceInfoClosure; - class SpaceManager; - class VirtualSpaceList; - class VirtualSpaceNode; +class MetaspaceSizesSnapshot; } +////////////////// Metaspace /////////////////////// + // Metaspaces each have a SpaceManager and allocations // are done by the SpaceManager. Allocations are done // out of the current Metachunk. When the current Metachunk @@ -94,74 +89,22 @@ friend class MetaspaceShared; - public: - enum MetadataType { - ClassType, - NonClassType, - MetadataTypeCount - }; - enum MetaspaceType { - ZeroMetaspaceType = 0, - StandardMetaspaceType = ZeroMetaspaceType, - BootMetaspaceType = StandardMetaspaceType + 1, - UnsafeAnonymousMetaspaceType = BootMetaspaceType + 1, - ReflectionMetaspaceType = UnsafeAnonymousMetaspaceType + 1, - MetaspaceTypeCount - }; - - private: - - // Align up the word size to the allocation word size - static size_t align_word_size_up(size_t); - - // Aligned size of the metaspace. + // Base and size of the compressed class space. + static MetaWord* _compressed_class_space_base; static size_t _compressed_class_space_size; - static size_t compressed_class_space_size() { - return _compressed_class_space_size; - } - - static void set_compressed_class_space_size(size_t size) { - _compressed_class_space_size = size; - } - - static size_t _first_chunk_word_size; - static size_t _first_class_chunk_word_size; - static size_t _commit_alignment; static size_t _reserve_alignment; DEBUG_ONLY(static bool _frozen;) - // Virtual Space lists for both classes and other metadata - static metaspace::VirtualSpaceList* _space_list; - static metaspace::VirtualSpaceList* _class_space_list; - - static metaspace::ChunkManager* _chunk_manager_metadata; - static metaspace::ChunkManager* _chunk_manager_class; - static const MetaspaceTracer* _tracer; static bool _initialized; - public: - static metaspace::VirtualSpaceList* space_list() { return _space_list; } - static metaspace::VirtualSpaceList* class_space_list() { return _class_space_list; } - static metaspace::VirtualSpaceList* get_space_list(MetadataType mdtype) { - assert(mdtype != MetadataTypeCount, "MetadaTypeCount can't be used as mdtype"); - return mdtype == ClassType ? class_space_list() : space_list(); - } + static MetaWord* compressed_class_space_base() { return _compressed_class_space_base; } + static size_t compressed_class_space_size() { return _compressed_class_space_size; } - static metaspace::ChunkManager* chunk_manager_metadata() { return _chunk_manager_metadata; } - static metaspace::ChunkManager* chunk_manager_class() { return _chunk_manager_class; } - static metaspace::ChunkManager* get_chunk_manager(MetadataType mdtype) { - assert(mdtype != MetadataTypeCount, "MetadaTypeCount can't be used as mdtype"); - return mdtype == ClassType ? chunk_manager_class() : chunk_manager_metadata(); - } - - // convenience function - static metaspace::ChunkManager* get_chunk_manager(bool is_class) { - return is_class ? chunk_manager_class() : chunk_manager_metadata(); - } +public: static const MetaspaceTracer* tracer() { return _tracer; } static void freeze() { @@ -192,15 +135,13 @@ static void global_initialize(); static void post_initialize(); - static void verify_global_initialization(); - - static size_t first_chunk_word_size() { return _first_chunk_word_size; } - static size_t first_class_chunk_word_size() { return _first_class_chunk_word_size; } - + // The alignment at which Metaspace mappings are reserved. static size_t reserve_alignment() { return _reserve_alignment; } static size_t reserve_alignment_words() { return _reserve_alignment / BytesPerWord; } + + // The granularity at which Metaspace is committed and uncommitted. static size_t commit_alignment() { return _commit_alignment; } - static size_t commit_alignment_words() { return _commit_alignment / BytesPerWord; } + static size_t commit_words() { return _commit_alignment / BytesPerWord; } static MetaWord* allocate(ClassLoaderData* loader_data, size_t word_size, MetaspaceObj::Type type, TRAPS); @@ -209,13 +150,10 @@ static bool contains_non_shared(const void* ptr); // Free empty virtualspaces - static void purge(MetadataType mdtype); static void purge(); static void report_metadata_oome(ClassLoaderData* loader_data, size_t word_size, - MetaspaceObj::Type type, MetadataType mdtype, TRAPS); - - static const char* metadata_type_name(Metaspace::MetadataType mdtype); + MetaspaceObj::Type type, metaspace::MetadataType mdtype, TRAPS); static void print_compressed_class_space(outputStream* st, const char* requested_addr = 0) NOT_LP64({}); @@ -224,216 +162,38 @@ return NOT_LP64(false) LP64_ONLY(UseCompressedClassPointers); } - static bool is_class_space_allocation(MetadataType mdType) { - return mdType == ClassType && using_class_space(); - } - static bool initialized() { return _initialized; } }; -// Manages the metaspace portion belonging to a class loader -class ClassLoaderMetaspace : public CHeapObj { - friend class CollectedHeap; // For expand_and_allocate() - friend class ZCollectedHeap; // For expand_and_allocate() - friend class ShenandoahHeap; // For expand_and_allocate() - friend class Metaspace; - friend class MetaspaceUtils; - friend class metaspace::PrintCLDMetaspaceInfoClosure; - friend class VM_CollectForMetadataAllocation; // For expand_and_allocate() - - private: - - void initialize(Mutex* lock, Metaspace::MetaspaceType type); - - // Initialize the first chunk for a Metaspace. Used for - // special cases such as the boot class loader, reflection - // class loader and anonymous class loader. - void initialize_first_chunk(Metaspace::MetaspaceType type, Metaspace::MetadataType mdtype); - metaspace::Metachunk* get_initialization_chunk(Metaspace::MetaspaceType type, Metaspace::MetadataType mdtype); - - const Metaspace::MetaspaceType _space_type; - Mutex* const _lock; - metaspace::SpaceManager* _vsm; - metaspace::SpaceManager* _class_vsm; - - metaspace::SpaceManager* vsm() const { return _vsm; } - metaspace::SpaceManager* class_vsm() const { return _class_vsm; } - metaspace::SpaceManager* get_space_manager(Metaspace::MetadataType mdtype) { - assert(mdtype != Metaspace::MetadataTypeCount, "MetadaTypeCount can't be used as mdtype"); - return mdtype == Metaspace::ClassType ? class_vsm() : vsm(); - } - - Mutex* lock() const { return _lock; } - - MetaWord* expand_and_allocate(size_t size, Metaspace::MetadataType mdtype); - - size_t class_chunk_size(size_t word_size); - - // Adds to the given statistic object. Must be locked with CLD metaspace lock. - void add_to_statistics_locked(metaspace::ClassLoaderMetaspaceStatistics* out) const; - - Metaspace::MetaspaceType space_type() const { return _space_type; } - - public: - - ClassLoaderMetaspace(Mutex* lock, Metaspace::MetaspaceType type); - ~ClassLoaderMetaspace(); - - // Allocate space for metadata of type mdtype. This is space - // within a Metachunk and is used by - // allocate(ClassLoaderData*, size_t, bool, MetadataType, TRAPS) - MetaWord* allocate(size_t word_size, Metaspace::MetadataType mdtype); - - size_t allocated_blocks_bytes() const; - size_t allocated_chunks_bytes() const; - - void deallocate(MetaWord* ptr, size_t byte_size, bool is_class); - - void print_on(outputStream* st) const; - // Debugging support - void verify(); - - // Adds to the given statistic object. Will lock with CLD metaspace lock. - void add_to_statistics(metaspace::ClassLoaderMetaspaceStatistics* out) const; - -}; // ClassLoaderMetaspace - -class MetaspaceUtils : AllStatic { - - // Spacemanager updates running counters. - friend class metaspace::SpaceManager; - - // Special access for error reporting (checks without locks). - friend class oopDesc; - friend class Klass; - - // Running counters for statistics concerning in-use chunks. - // Note: capacity = used + free + waste + overhead. Note that we do not - // count free and waste. Their sum can be deduces from the three other values. - // For more details, one should call print_report() from within a safe point. - static size_t _capacity_words [Metaspace:: MetadataTypeCount]; - static size_t _overhead_words [Metaspace:: MetadataTypeCount]; - static volatile size_t _used_words [Metaspace:: MetadataTypeCount]; - - // Atomically decrement or increment in-use statistic counters - static void dec_capacity(Metaspace::MetadataType mdtype, size_t words); - static void inc_capacity(Metaspace::MetadataType mdtype, size_t words); - static void dec_used(Metaspace::MetadataType mdtype, size_t words); - static void inc_used(Metaspace::MetadataType mdtype, size_t words); - static void dec_overhead(Metaspace::MetadataType mdtype, size_t words); - static void inc_overhead(Metaspace::MetadataType mdtype, size_t words); - - - // Getters for the in-use counters. - static size_t capacity_words(Metaspace::MetadataType mdtype) { return _capacity_words[mdtype]; } - static size_t used_words(Metaspace::MetadataType mdtype) { return _used_words[mdtype]; } - static size_t overhead_words(Metaspace::MetadataType mdtype) { return _overhead_words[mdtype]; } - - static size_t free_chunks_total_words(Metaspace::MetadataType mdtype); - - // Helper for print_xx_report. - static void print_vs(outputStream* out, size_t scale); - -public: - - // Collect used metaspace statistics. This involves walking the CLDG. The resulting - // output will be the accumulated values for all live metaspaces. - // Note: method does not do any locking. - static void collect_statistics(metaspace::ClassLoaderMetaspaceStatistics* out); - - // Used by MetaspaceCounters - static size_t free_chunks_total_words(); - static size_t free_chunks_total_bytes(); - static size_t free_chunks_total_bytes(Metaspace::MetadataType mdtype); - - static size_t capacity_words() { - return capacity_words(Metaspace::NonClassType) + - capacity_words(Metaspace::ClassType); - } - static size_t capacity_bytes(Metaspace::MetadataType mdtype) { - return capacity_words(mdtype) * BytesPerWord; - } - static size_t capacity_bytes() { - return capacity_words() * BytesPerWord; - } - - static size_t used_words() { - return used_words(Metaspace::NonClassType) + - used_words(Metaspace::ClassType); - } - static size_t used_bytes(Metaspace::MetadataType mdtype) { - return used_words(mdtype) * BytesPerWord; - } - static size_t used_bytes() { - return used_words() * BytesPerWord; - } - - // Space committed but yet unclaimed by any class loader. - static size_t free_in_vs_bytes(); - static size_t free_in_vs_bytes(Metaspace::MetadataType mdtype); - - static size_t reserved_bytes(Metaspace::MetadataType mdtype); - static size_t reserved_bytes() { - return reserved_bytes(Metaspace::ClassType) + - reserved_bytes(Metaspace::NonClassType); - } - - static size_t committed_bytes(Metaspace::MetadataType mdtype); - static size_t committed_bytes() { - return committed_bytes(Metaspace::ClassType) + - committed_bytes(Metaspace::NonClassType); - } - - static size_t min_chunk_size_words(); - - // Flags for print_report(). - enum ReportFlag { - // Show usage by class loader. - rf_show_loaders = (1 << 0), - // Breaks report down by chunk type (small, medium, ...). - rf_break_down_by_chunktype = (1 << 1), - // Breaks report down by space type (anonymous, reflection, ...). - rf_break_down_by_spacetype = (1 << 2), - // Print details about the underlying virtual spaces. - rf_show_vslist = (1 << 3), - // Print metaspace map. - rf_show_vsmap = (1 << 4), - // If show_loaders: show loaded classes for each loader. - rf_show_classes = (1 << 5) - }; - - // This will print out a basic metaspace usage report but - // unlike print_report() is guaranteed not to lock or to walk the CLDG. - static void print_basic_report(outputStream* st, size_t scale); - - // Prints a report about the current metaspace state. - // Optional parts can be enabled via flags. - // Function will walk the CLDG and will lock the expand lock; if that is not - // convenient, use print_basic_report() instead. - static void print_report(outputStream* out, size_t scale = 0, int flags = 0); - - static bool has_chunk_free_list(Metaspace::MetadataType mdtype); - static MetaspaceChunkFreeListSummary chunk_free_list_summary(Metaspace::MetadataType mdtype); - - // Log change in used metadata. - static void print_metaspace_change(const metaspace::MetaspaceSizesSnapshot& pre_meta_values); - static void print_on(outputStream * out); - - // Prints an ASCII representation of the given space. - static void print_metaspace_map(outputStream* out, Metaspace::MetadataType mdtype); - - static void dump(outputStream* out); - static void verify_free_chunks(); - // Check internal counters (capacity, used). - static void verify_metrics(); -}; +////////////////// MetaspaceGC /////////////////////// // Metaspace are deallocated when their class loader are GC'ed. // This class implements a policy for inducing GC's to recover // Metaspaces. -class MetaspaceGC : AllStatic { +class MetaspaceGCThresholdUpdater : public AllStatic { + public: + enum Type { + ComputeNewSize, + ExpandAndAllocate, + Last + }; + + static const char* to_string(MetaspaceGCThresholdUpdater::Type updater) { + switch (updater) { + case ComputeNewSize: + return "compute_new_size"; + case ExpandAndAllocate: + return "expand_and_allocate"; + default: + assert(false, "Got bad updater: %d", (int) updater); + return NULL; + }; + } +}; + +class MetaspaceGC : public AllStatic { // The current high-water-mark for inducing a GC. // When committed memory of all metaspaces reaches this value, @@ -481,4 +241,64 @@ static void compute_new_size(); }; + + + +class MetaspaceUtils : AllStatic { +public: + + // Committed space actually in use by Metadata + static size_t used_words(); + static size_t used_words(metaspace::MetadataType mdtype); + + // Space committed for Metaspace + static size_t committed_words(); + static size_t committed_words(metaspace::MetadataType mdtype); + + // Space reserved for Metaspace + static size_t reserved_words(); + static size_t reserved_words(metaspace::MetadataType mdtype); + + // _bytes() variants for convenience... + static size_t used_bytes() { return used_words() * BytesPerWord; } + static size_t used_bytes(metaspace::MetadataType mdtype) { return used_words(mdtype) * BytesPerWord; } + static size_t committed_bytes() { return committed_words() * BytesPerWord; } + static size_t committed_bytes(metaspace::MetadataType mdtype) { return committed_words(mdtype) * BytesPerWord; } + static size_t reserved_bytes() { return reserved_words() * BytesPerWord; } + static size_t reserved_bytes(metaspace::MetadataType mdtype) { return reserved_words(mdtype) * BytesPerWord; } + + // TODO. Do we need this really? This number is kind of uninformative. + static size_t capacity_bytes() { return 0; } + static size_t capacity_bytes(metaspace::MetadataType mdtype) { return 0; } + + // Todo. Consolidate. + // Committed space in freelists + static size_t free_chunks_total_words(metaspace::MetadataType mdtype); + + // Todo. Implement or Consolidate. + static MetaspaceChunkFreeListSummary chunk_free_list_summary(metaspace::MetadataType mdtype) { + return MetaspaceChunkFreeListSummary(0,0,0,0,0,0,0,0); + } + + // Log change in used metadata. + static void print_metaspace_change(const metaspace::MetaspaceSizesSnapshot& pre_meta_values); + + // Prints an ASCII representation of the given space. + static void print_metaspace_map(outputStream* out, metaspace::MetadataType mdtype); + + // This will print out a basic metaspace usage report but + // unlike print_report() is guaranteed not to lock or to walk the CLDG. + static void print_basic_report(outputStream* st, size_t scale = 0); + + // Prints a report about the current metaspace state. + // Function will walk the CLDG and will lock the expand lock; if that is not + // convenient, use print_basic_report() instead. + static void print_full_report(outputStream* out, size_t scale = 0); + + static void print_on(outputStream * out); + + DEBUG_ONLY(static void verify(bool slow);) + +}; + #endif // SHARE_MEMORY_METASPACE_HPP diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/blockFreelist.cpp --- a/src/hotspot/share/memory/metaspace/blockFreelist.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace/blockFreelist.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -43,10 +43,11 @@ } void BlockFreelist::return_block(MetaWord* p, size_t word_size) { - assert(word_size >= SmallBlocks::small_block_min_size(), "never return dark matter"); + assert(word_size >= SmallBlocks::small_block_min_word_size(), + "attempting to return dark matter (" SIZE_FORMAT ").", word_size); Metablock* free_chunk = ::new (p) Metablock(word_size); - if (word_size < SmallBlocks::small_block_max_size()) { + if (word_size < SmallBlocks::small_block_max_word_size()) { small_blocks()->return_block(free_chunk, word_size); } else { dictionary()->return_chunk(free_chunk); @@ -56,10 +57,10 @@ } MetaWord* BlockFreelist::get_block(size_t word_size) { - assert(word_size >= SmallBlocks::small_block_min_size(), "never get dark matter"); + assert(word_size >= SmallBlocks::small_block_min_word_size(), "never get dark matter"); // Try small_blocks first. - if (word_size < SmallBlocks::small_block_max_size()) { + if (word_size < SmallBlocks::small_block_max_word_size()) { // Don't create small_blocks() until needed. small_blocks() allocates the small block list for // this space manager. MetaWord* new_block = (MetaWord*) small_blocks()->get_block(word_size); @@ -89,7 +90,7 @@ MetaWord* new_block = (MetaWord*)free_block; assert(block_size >= word_size, "Incorrect size of block from freelist"); const size_t unused = block_size - word_size; - if (unused >= SmallBlocks::small_block_min_size()) { + if (unused >= SmallBlocks::small_block_min_word_size()) { return_block(new_block + word_size, unused); } diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/chunkAllocSequence.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/chunkAllocSequence.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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 "memory/metaspace/chunkAllocSequence.hpp" +#include "memory/metaspace/chunkLevel.hpp" +#include "utilities/globalDefinitions.hpp" + +namespace metaspace { + +// A chunk allocation sequence which can be encoded with a simple const array. +class ConstantChunkAllocSequence : public ChunkAllocSequence { + + // integer array specifying chunk level allocation progression. + // Last chunk is to be an endlessly repeated allocation. + const chklvl_t* const _entries; + const int _num_entries; + +public: + + ConstantChunkAllocSequence(const chklvl_t* array, int num_entries) + : _entries(array) + , _num_entries(num_entries) + { + assert(_num_entries > 0, "must not be empty."); + } + + chklvl_t get_next_chunk_level(int num_allocated) const { + if (num_allocated >= _num_entries) { + // Caller shall repeat last allocation + return _entries[_num_entries - 1]; + } + return _entries[_num_entries]; + } + +}; + +// hard-coded chunk allocation sequences for various space types + +/////////////////////////// +// chunk allocation sequences for normal loaders: +static const chklvl_t g_sequ_standard_nonclass[] = { + chklvl::CHUNK_LEVEL_4K, + chklvl::CHUNK_LEVEL_4K, + chklvl::CHUNK_LEVEL_4K, + chklvl::CHUNK_LEVEL_4K, + chklvl::CHUNK_LEVEL_64K + -1 // .. repeat last +}; + +static const chklvl_t g_sequ_standard_class[] = { + chklvl::CHUNK_LEVEL_4K, + chklvl::CHUNK_LEVEL_4K, + chklvl::CHUNK_LEVEL_4K, + chklvl::CHUNK_LEVEL_4K, + chklvl::CHUNK_LEVEL_32K, + -1 // .. repeat last +}; + +/////////////////////////// +// chunk allocation sequences for reflection/anonymous loaders: +// We allocate four smallish chunks before progressing to bigger chunks. +static const chklvl_t g_sequ_anon_nonclass[] = { + chklvl::CHUNK_LEVEL_1K, + chklvl::CHUNK_LEVEL_1K, + chklvl::CHUNK_LEVEL_1K, + chklvl::CHUNK_LEVEL_1K, + chklvl::CHUNK_LEVEL_4K, + -1 // .. repeat last +}; + +static const chklvl_t g_sequ_anon_class[] = { + chklvl::CHUNK_LEVEL_1K, + chklvl::CHUNK_LEVEL_1K, + chklvl::CHUNK_LEVEL_1K, + chklvl::CHUNK_LEVEL_1K, + chklvl::CHUNK_LEVEL_4K, + -1 // .. repeat last +}; + +#define DEFINE_CLASS_FOR_ARRAY(what) \ + static ConstantChunkAllocSequence g_chunk_alloc_sequence_##what (g_sequ_##what, sizeof(g_sequ_##what)/sizeof(int)); + +DEFINE_CLASS_FOR_ARRAY(standard_nonclass) +DEFINE_CLASS_FOR_ARRAY(standard_class) +DEFINE_CLASS_FOR_ARRAY(anon_nonclass) +DEFINE_CLASS_FOR_ARRAY(anon_class) + + +class BootLoaderChunkAllocSequence : public ChunkAllocSequence { + + // For now, this mirrors what the old code did + // (see SpaceManager::get_initial_chunk_size() and SpaceManager::calc_chunk_size). + + // Not sure how much sense this still makes, especially with CDS - by default we + // now load JDK classes from CDS and therefore most of the boot loader + // chunks remain unoccupied. + + // Also, InitialBootClassLoaderMetaspaceSize was/is confusing since it only applies + // to the non-class chunk. + + const bool _is_class; + + static chklvl_t calc_initial_chunk_level(bool is_class) { + + size_t word_size = 0; + if (is_class) { + // In the old version first class space chunk for boot loader was always medium class chunk size * 6. + word_size = (32 * K * 6) / BytesPerWord; + + } else { + //assert(InitialBootClassLoaderMetaspaceSize < chklvl::MAX_CHUNK_BYTE_SIZE, + // "InitialBootClassLoaderMetaspaceSize too large"); + word_size = MIN2(InitialBootClassLoaderMetaspaceSize, + chklvl::MAX_CHUNK_BYTE_SIZE) / BytesPerWord; + } + return chklvl::level_fitting_word_size(word_size); + } + +public: + + BootLoaderChunkAllocSequence(bool is_class) + : _is_class(is_class) + {} + + chklvl_t get_next_chunk_level(int num_allocated) const { + if (num_allocated == 0) { + return calc_initial_chunk_level(_is_class); + } + // bit arbitrary, but this is what the old code did. Can tweak later if needed. + return chklvl::CHUNK_LEVEL_64K; + } + +}; + +static BootLoaderChunkAllocSequence g_chunk_alloc_sequence_boot_non_class(false); +static BootLoaderChunkAllocSequence g_chunk_alloc_sequence_boot_class(true); + + +const ChunkAllocSequence* ChunkAllocSequence::alloc_sequence_by_space_type(MetaspaceType space_type, bool is_class) { + + if (is_class) { + switch(space_type) { + case StandardMetaspaceType: return &g_chunk_alloc_sequence_standard_class; + case ReflectionMetaspaceType: + case UnsafeAnonymousMetaspaceType: return &g_chunk_alloc_sequence_anon_class; + case BootMetaspaceType: return &g_chunk_alloc_sequence_boot_non_class; + default: ShouldNotReachHere(); + } + } else { + switch(space_type) { + case StandardMetaspaceType: return &g_chunk_alloc_sequence_standard_class; + case ReflectionMetaspaceType: + case UnsafeAnonymousMetaspaceType: return &g_chunk_alloc_sequence_anon_class; + case BootMetaspaceType: return &g_chunk_alloc_sequence_boot_class; + default: ShouldNotReachHere(); + } + } + + return NULL; + +} + + + +} // namespace metaspace + diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/chunkAllocSequence.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/chunkAllocSequence.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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_MEMORY_METASPACE_CHUNKALLOCSEQUENCE_HPP +#define SHARE_MEMORY_METASPACE_CHUNKALLOCSEQUENCE_HPP + +#include "memory/metaspace.hpp" // For Metaspace::MetaspaceType +#include "memory/metaspace/chunkLevel.hpp" +#include "memory/metaspace/metaspaceEnums.hpp" + +namespace metaspace { + + +class ChunkAllocSequence { +public: + + virtual chklvl_t get_next_chunk_level(int num_allocated) const = 0; + + // Given a space type, return the correct allocation sequence to use. + // The returned object is static and read only. + static const ChunkAllocSequence* alloc_sequence_by_space_type(MetaspaceType space_type, bool is_class); + +}; + + +} // namespace metaspace + +#endif // SHARE_MEMORY_METASPACE_CHUNKALLOCSEQUENCE_HPP diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/chunkHeaderPool.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/chunkHeaderPool.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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 "memory/metaspace/chunkHeaderPool.hpp" +#include "runtime/os.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" + +namespace metaspace { + + +// Returns reference to the one global chunk header pool. +ChunkHeaderPool ChunkHeaderPool::_chunkHeaderPool(false); + + +ChunkHeaderPool::ChunkHeaderPool(bool delete_on_destruction) + : _num_slabs(), _first_slab(NULL), _current_slab(NULL), _delete_on_destruction(delete_on_destruction) +{ +} + +ChunkHeaderPool::~ChunkHeaderPool() { + if (_delete_on_destruction) { + // This is only done when tests are running, but not for the global chunk pool, + // since that is supposed to live until the process ends. + slab_t* s = _first_slab; + while (s != NULL) { + slab_t* next_slab = s->next; + os::free(s); + s = next_slab; + } + } +} + +void ChunkHeaderPool::allocate_new_slab() { + slab_t* slab = (slab_t*)os::malloc(sizeof(slab_t), mtInternal); + memset(slab, 0, sizeof(slab_t)); + if (_current_slab != NULL) { + _current_slab->next = slab; + } + _current_slab = slab; + if (_first_slab == NULL) { + _first_slab = slab; + } + _num_slabs.increment(); +} + +// Returns size of memory used. +size_t ChunkHeaderPool::memory_footprint_words() const { + return (_num_slabs.get() * sizeof(slab_t)) / BytesPerWord; +} + +#ifdef ASSERT +void ChunkHeaderPool::verify(bool slow) const { + const slab_t* s = _first_slab; + int num = 0; + while (s != NULL) { + assert(s->top >= 0 && s->top <= slab_capacity, + "invalid slab at " PTR_FORMAT ", top: %d, slab cap: %d", + p2i(s), s->top, slab_capacity ); + s = s->next; + num ++; + } + _num_slabs.check(num); +} +#endif + +} // namespace metaspace + + diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/chunkHeaderPool.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/chunkHeaderPool.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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_MEMORY_METASPACE_CHUNKHEADERPOOL_HPP +#define SHARE_MEMORY_METASPACE_CHUNKHEADERPOOL_HPP + +#include "memory/metaspace/internStat.hpp" +#include "memory/metaspace/metachunk.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" + +namespace metaspace { + +// A simple helper class; A pool of unused chunk headers. + +class ChunkHeaderPool { + + static const int slab_capacity = 128; + + struct slab_t { + slab_t* next; + int top; + Metachunk elems [slab_capacity]; // var sized; + }; + + IntCounter _num_slabs; + slab_t* _first_slab; + slab_t* _current_slab; + + IntCounter _num_handed_out; + + MetachunkList _freelist; + + const bool _delete_on_destruction; + + void allocate_new_slab(); + + static ChunkHeaderPool _chunkHeaderPool; + + +public: + + ChunkHeaderPool(bool delete_on_destruction); + + ~ChunkHeaderPool(); + + // Allocates a Metachunk structure. The structure is uninitialized. + Metachunk* allocate_chunk_header() { + + Metachunk* c = NULL; + + DEBUG_ONLY(verify(false)); + + c = _freelist.remove_first(); + assert(c == NULL || c->is_dead(), "Not a freelist chunk header?"); + + if (c == NULL) { + + if (_current_slab == NULL || + _current_slab->top == slab_capacity) { + allocate_new_slab(); + assert(_current_slab->top < slab_capacity, "Sanity"); + } + + c = _current_slab->elems + _current_slab->top; + _current_slab->top ++; + + } + + _num_handed_out.increment(); + + // By contract, the returned structure is uninitialized. + // Zap to make this clear. + DEBUG_ONLY(c->zap_header(0xBB);) + + return c; + + } + + void return_chunk_header(Metachunk* c) { + // We only ever should return free chunks, since returning chunks + // happens only on merging and merging only works with free chunks. + assert(c != NULL && c->is_free(), "Sanity"); +#ifdef ASSERT + // In debug, fill dead header with pattern. + c->zap_header(0xCC); + c->set_next(NULL); + c->set_prev(NULL); +#endif + c->set_dead(); + _freelist.add(c); + _num_handed_out.decrement(); + + } + + // Returns number of allocated elements. + int used() const { return _num_handed_out.get(); } + + // Returns number of elements in free list. + int freelist_size() const { return _freelist.size(); } + + // Returns size of memory used. + size_t memory_footprint_words() const; + + DEBUG_ONLY(void verify(bool slow) const;) + + // Returns reference to the one global chunk header pool. + static ChunkHeaderPool& pool() { return _chunkHeaderPool; } + +}; + + +} // namespace metaspace + + + + +#endif // SHARE_MEMORY_METASPACE_CHUNKHEADERPOOL_HPP diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/chunkLevel.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/chunkLevel.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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 +#include "precompiled.hpp" +#include "memory/metaspace/chunkLevel.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" +#include "utilities/ostream.hpp" + +namespace metaspace { + +chklvl_t chklvl::level_fitting_word_size(size_t word_size) { + + assert(chklvl::MAX_CHUNK_WORD_SIZE >= word_size, + "too large allocation size (" SIZE_FORMAT ")", word_size * BytesPerWord); + + // TODO: This can be done much better. + chklvl_t l = chklvl::HIGHEST_CHUNK_LEVEL; + while (l >= chklvl::LOWEST_CHUNK_LEVEL && + word_size > chklvl::word_size_for_level(l)) { + l --; + } + + return l; +} + +void chklvl::print_chunk_size(outputStream* st, chklvl_t lvl) { + if (chklvl::is_valid_level(lvl)) { + const size_t s = chklvl::word_size_for_level(lvl) * BytesPerWord; + if (s < 1 * M) { + st->print("%3uk", (unsigned)(s / K)); + } else { + st->print("%3um", (unsigned)(s / M)); + } + } else { + st->print("?-?"); + } + +} + +} // namespace metaspace + + diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/chunkLevel.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/chunkLevel.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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_MEMORY_METASPACE_CHUNKLEVEL_HPP +#define SHARE_MEMORY_METASPACE_CHUNKLEVEL_HPP + +#include "utilities/globalDefinitions.hpp" + +// Constants for the chunk levels and some utility functions. + +class outputStream; + +namespace metaspace { + +// Metachunk level (must be signed) +typedef signed char chklvl_t; + +#define CHKLVL_FORMAT "lv%.2d" + +// Chunks are managed by a binary buddy allocator. + +// Chunk sizes range from 1K to 4MB (64bit). +// +// Reasoning: .... TODO explain + +// Each chunk has a level; the level corresponds to its position in the tree +// and describes its size. +// +// The largest chunks are called root chunks, of 4MB in size, and have level 0. +// From there on it goes: +// +// size level +// 4MB 0 +// 2MB 1 +// 1MB 2 +// 512K 3 +// 256K 4 +// 128K 5 +// 64K 6 +// 32K 7 +// 16K 8 +// 8K 9 +// 4K 10 +// 2K 11 +// 1K 12 + +namespace chklvl { + +static const size_t MAX_CHUNK_BYTE_SIZE = 4 * M; +static const int NUM_CHUNK_LEVELS = 13; +static const size_t MIN_CHUNK_BYTE_SIZE = (MAX_CHUNK_BYTE_SIZE >> (size_t)NUM_CHUNK_LEVELS); + +static const size_t MIN_CHUNK_WORD_SIZE = MIN_CHUNK_BYTE_SIZE / sizeof(MetaWord); +static const size_t MAX_CHUNK_WORD_SIZE = MAX_CHUNK_BYTE_SIZE / sizeof(MetaWord); + +static const chklvl_t ROOT_CHUNK_LEVEL = 0; + +static const chklvl_t HIGHEST_CHUNK_LEVEL = NUM_CHUNK_LEVELS - 1; +static const chklvl_t LOWEST_CHUNK_LEVEL = 0; + +static const chklvl_t INVALID_CHUNK_LEVEL = (chklvl_t) -1; + +inline bool is_valid_level(chklvl_t level) { + return level >= LOWEST_CHUNK_LEVEL && + level <= HIGHEST_CHUNK_LEVEL; +} + +inline void check_valid_level(chklvl_t lvl) { + assert(is_valid_level(lvl), "invalid level (%d)", (int)lvl); +} + +// Given a level return the chunk size, in words. +inline size_t word_size_for_level(chklvl_t level) { + check_valid_level(level); + return (MAX_CHUNK_BYTE_SIZE >> level) / BytesPerWord; +} + +// Given an arbitrary word size smaller than the highest chunk size, +// return the highest chunk level able to hold this size. +// Returns INVALID_CHUNK_LEVEL if no fitting level can be found. +chklvl_t level_fitting_word_size(size_t word_size); + +// Shorthands to refer to exact sizes +static const chklvl_t CHUNK_LEVEL_4M = ROOT_CHUNK_LEVEL; +static const chklvl_t CHUNK_LEVEL_2M = (ROOT_CHUNK_LEVEL + 1); +static const chklvl_t CHUNK_LEVEL_1M = (ROOT_CHUNK_LEVEL + 2); +static const chklvl_t CHUNK_LEVEL_512K = (ROOT_CHUNK_LEVEL + 3); +static const chklvl_t CHUNK_LEVEL_256K = (ROOT_CHUNK_LEVEL + 4); +static const chklvl_t CHUNK_LEVEL_128K = (ROOT_CHUNK_LEVEL + 5); +static const chklvl_t CHUNK_LEVEL_64K = (ROOT_CHUNK_LEVEL + 6); +static const chklvl_t CHUNK_LEVEL_32K = (ROOT_CHUNK_LEVEL + 7); +static const chklvl_t CHUNK_LEVEL_16K = (ROOT_CHUNK_LEVEL + 8); +static const chklvl_t CHUNK_LEVEL_8K = (ROOT_CHUNK_LEVEL + 9); +static const chklvl_t CHUNK_LEVEL_4K = (ROOT_CHUNK_LEVEL + 10); +static const chklvl_t CHUNK_LEVEL_2K = (ROOT_CHUNK_LEVEL + 11); +static const chklvl_t CHUNK_LEVEL_1K = (ROOT_CHUNK_LEVEL + 12); + +STATIC_ASSERT(CHUNK_LEVEL_1K == HIGHEST_CHUNK_LEVEL); + +///////////////////////////////////////////////////////// +// print helpers +void print_chunk_size(outputStream* st, chklvl_t lvl); + + +} // namespace chklvl + +} // namespace metaspace + +#endif // SHARE_MEMORY_METASPACE_BLOCKFREELIST_HPP diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/chunkManager.cpp --- a/src/hotspot/share/memory/metaspace/chunkManager.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace/chunkManager.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -1,5 +1,6 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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 @@ -21,619 +22,463 @@ * questions. * */ +#include #include "precompiled.hpp" + #include "logging/log.hpp" #include "logging/logStream.hpp" -#include "memory/binaryTreeDictionary.inline.hpp" -#include "memory/freeList.inline.hpp" +#include "memory/metaspace/chunkAllocSequence.hpp" +#include "memory/metaspace/chunkLevel.hpp" #include "memory/metaspace/chunkManager.hpp" +#include "memory/metaspace/internStat.hpp" #include "memory/metaspace/metachunk.hpp" -#include "memory/metaspace/metaDebug.hpp" #include "memory/metaspace/metaspaceCommon.hpp" #include "memory/metaspace/metaspaceStatistics.hpp" -#include "memory/metaspace/occupancyMap.hpp" #include "memory/metaspace/virtualSpaceNode.hpp" +#include "memory/metaspace/virtualSpaceList.hpp" #include "runtime/mutexLocker.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" -#include "utilities/ostream.hpp" namespace metaspace { -ChunkManager::ChunkManager(bool is_class) - : _is_class(is_class), _free_chunks_total(0), _free_chunks_count(0) { - _free_chunks[SpecializedIndex].set_size(get_size_for_nonhumongous_chunktype(SpecializedIndex, is_class)); - _free_chunks[SmallIndex].set_size(get_size_for_nonhumongous_chunktype(SmallIndex, is_class)); - _free_chunks[MediumIndex].set_size(get_size_for_nonhumongous_chunktype(MediumIndex, is_class)); -} -void ChunkManager::remove_chunk(Metachunk* chunk) { - size_t word_size = chunk->word_size(); - ChunkIndex index = list_index(word_size); - if (index != HumongousIndex) { - free_chunks(index)->remove_chunk(chunk); - } else { - humongous_dictionary()->remove_chunk(chunk); - } - - // Chunk has been removed from the chunks free list, update counters. - account_for_removed_chunk(chunk); -} - -bool ChunkManager::attempt_to_coalesce_around_chunk(Metachunk* chunk, ChunkIndex target_chunk_type) { - assert_lock_strong(MetaspaceExpand_lock); - assert(chunk != NULL, "invalid chunk pointer"); - // Check for valid merge combinations. - assert((chunk->get_chunk_type() == SpecializedIndex && - (target_chunk_type == SmallIndex || target_chunk_type == MediumIndex)) || - (chunk->get_chunk_type() == SmallIndex && target_chunk_type == MediumIndex), - "Invalid chunk merge combination."); - - const size_t target_chunk_word_size = - get_size_for_nonhumongous_chunktype(target_chunk_type, this->is_class()); - - // [ prospective merge region ) - MetaWord* const p_merge_region_start = - (MetaWord*) align_down(chunk, target_chunk_word_size * sizeof(MetaWord)); - MetaWord* const p_merge_region_end = - p_merge_region_start + target_chunk_word_size; - - // We need the VirtualSpaceNode containing this chunk and its occupancy map. - VirtualSpaceNode* const vsn = chunk->container(); - OccupancyMap* const ocmap = vsn->occupancy_map(); - - // The prospective chunk merge range must be completely contained by the - // committed range of the virtual space node. - if (p_merge_region_start < vsn->bottom() || p_merge_region_end > vsn->top()) { - return false; - } - - // Only attempt to merge this range if at its start a chunk starts and at its end - // a chunk ends. If a chunk (can only be humongous) straddles either start or end - // of that range, we cannot merge. - if (!ocmap->chunk_starts_at_address(p_merge_region_start)) { - return false; - } - if (p_merge_region_end < vsn->top() && - !ocmap->chunk_starts_at_address(p_merge_region_end)) { - return false; - } - - // Now check if the prospective merge area contains live chunks. If it does we cannot merge. - if (ocmap->is_region_in_use(p_merge_region_start, target_chunk_word_size)) { - return false; - } - - // Success! Remove all chunks in this region... - log_trace(gc, metaspace, freelist)("%s: coalescing chunks in area [%p-%p)...", - (is_class() ? "class space" : "metaspace"), - p_merge_region_start, p_merge_region_end); - - const int num_chunks_removed = - remove_chunks_in_area(p_merge_region_start, target_chunk_word_size); - - // ... and create a single new bigger chunk. - Metachunk* const p_new_chunk = - ::new (p_merge_region_start) Metachunk(target_chunk_type, is_class(), target_chunk_word_size, vsn); - assert(p_new_chunk == (Metachunk*)p_merge_region_start, "Sanity"); - p_new_chunk->set_origin(origin_merge); - - log_trace(gc, metaspace, freelist)("%s: created coalesced chunk at %p, size " SIZE_FORMAT_HEX ".", - (is_class() ? "class space" : "metaspace"), - p_new_chunk, p_new_chunk->word_size() * sizeof(MetaWord)); - - // Fix occupancy map: remove old start bits of the small chunks and set new start bit. - ocmap->wipe_chunk_start_bits_in_region(p_merge_region_start, target_chunk_word_size); - ocmap->set_chunk_starts_at_address(p_merge_region_start, true); - - // Mark chunk as free. Note: it is not necessary to update the occupancy - // map in-use map, because the old chunks were also free, so nothing - // should have changed. - p_new_chunk->set_is_tagged_free(true); - - // Add new chunk to its freelist. - ChunkList* const list = free_chunks(target_chunk_type); - list->return_chunk_at_head(p_new_chunk); - - // And adjust ChunkManager:: _free_chunks_count (_free_chunks_total - // should not have changed, because the size of the space should be the same) - _free_chunks_count -= num_chunks_removed; - _free_chunks_count ++; - - // VirtualSpaceNode::chunk_count does not have to be modified: - // it means "number of active (non-free) chunks", so merging free chunks - // should not affect that count. - - // At the end of a chunk merge, run verification tests. -#ifdef ASSERT - - EVERY_NTH(VerifyMetaspaceInterval) - locked_verify(true); - vsn->verify(true); - END_EVERY_NTH - - g_internal_statistics.num_chunk_merges ++; - -#endif - - return true; -} - -// Remove all chunks in the given area - the chunks are supposed to be free - -// from their corresponding freelists. Mark them as invalid. -// - This does not correct the occupancy map. -// - This does not adjust the counters in ChunkManager. -// - Does not adjust container count counter in containing VirtualSpaceNode -// Returns number of chunks removed. -int ChunkManager::remove_chunks_in_area(MetaWord* p, size_t word_size) { - assert(p != NULL && word_size > 0, "Invalid range."); - const size_t smallest_chunk_size = get_size_for_nonhumongous_chunktype(SpecializedIndex, is_class()); - assert_is_aligned(word_size, smallest_chunk_size); - - Metachunk* const start = (Metachunk*) p; - const Metachunk* const end = (Metachunk*)(p + word_size); - Metachunk* cur = start; - int num_removed = 0; - while (cur < end) { - Metachunk* next = (Metachunk*)(((MetaWord*)cur) + cur->word_size()); - DEBUG_ONLY(do_verify_chunk(cur)); - assert(cur->get_chunk_type() != HumongousIndex, "Unexpected humongous chunk found at %p.", cur); - assert(cur->is_tagged_free(), "Chunk expected to be free (%p)", cur); - log_trace(gc, metaspace, freelist)("%s: removing chunk %p, size " SIZE_FORMAT_HEX ".", - (is_class() ? "class space" : "metaspace"), - cur, cur->word_size() * sizeof(MetaWord)); - cur->remove_sentinel(); - // Note: cannot call ChunkManager::remove_chunk, because that - // modifies the counters in ChunkManager, which we do not want. So - // we call remove_chunk on the freelist directly (see also the - // splitting function which does the same). - ChunkList* const list = free_chunks(list_index(cur->word_size())); - list->remove_chunk(cur); - num_removed ++; - cur = next; - } - return num_removed; -} - -// Update internal accounting after a chunk was added -void ChunkManager::account_for_added_chunk(const Metachunk* c) { - assert_lock_strong(MetaspaceExpand_lock); - _free_chunks_count ++; - _free_chunks_total += c->word_size(); -} - -// Update internal accounting after a chunk was removed -void ChunkManager::account_for_removed_chunk(const Metachunk* c) { - assert_lock_strong(MetaspaceExpand_lock); - assert(_free_chunks_count >= 1, - "ChunkManager::_free_chunks_count: about to go negative (" SIZE_FORMAT ").", _free_chunks_count); - assert(_free_chunks_total >= c->word_size(), - "ChunkManager::_free_chunks_total: about to go negative" - "(now: " SIZE_FORMAT ", decrement value: " SIZE_FORMAT ").", _free_chunks_total, c->word_size()); - _free_chunks_count --; - _free_chunks_total -= c->word_size(); -} - -ChunkIndex ChunkManager::list_index(size_t size) { - return get_chunk_type_by_size(size, is_class()); -} - -size_t ChunkManager::size_by_index(ChunkIndex index) const { - index_bounds_check(index); - assert(index != HumongousIndex, "Do not call for humongous chunks."); - return get_size_for_nonhumongous_chunktype(index, is_class()); -} - -#ifdef ASSERT -void ChunkManager::verify(bool slow) const { - MutexLocker cl(MetaspaceExpand_lock, - Mutex::_no_safepoint_check_flag); - locked_verify(slow); -} - -void ChunkManager::locked_verify(bool slow) const { - log_trace(gc, metaspace, freelist)("verifying %s chunkmanager (%s).", - (is_class() ? "class space" : "metaspace"), (slow ? "slow" : "quick")); +// Return a single chunk to the freelist and adjust accounting. No merge is attempted. +void ChunkManager::return_chunk_simple(Metachunk* c) { assert_lock_strong(MetaspaceExpand_lock); - size_t chunks_counted = 0; - size_t wordsize_chunks_counted = 0; - for (ChunkIndex i = ZeroIndex; i < NumberOfFreeLists; i = next_chunk_index(i)) { - const ChunkList* list = _free_chunks + i; - if (list != NULL) { - Metachunk* chunk = list->head(); - while (chunk) { - if (slow) { - do_verify_chunk(chunk); - } - assert(chunk->is_tagged_free(), "Chunk should be tagged as free."); - chunks_counted ++; - wordsize_chunks_counted += chunk->size(); - chunk = chunk->next(); + DEBUG_ONLY(c->verify(false)); + + const chklvl_t lvl = c->level(); + _chunks.add(c); + c->reset_used_words(); + + // Tracing + log_debug(metaspace)("ChunkManager %s: returned chunk " METACHUNK_FORMAT ".", + _name, METACHUNK_FORMAT_ARGS(c)); + +} + +// Take a single chunk from the given freelist and adjust counters. Returns NULL +// if there is no fitting chunk for this level. +Metachunk* ChunkManager::remove_first_chunk_at_level(chklvl_t l) { + + assert_lock_strong(MetaspaceExpand_lock); + DEBUG_ONLY(chklvl::check_valid_level(l);) + + Metachunk* c = _chunks.remove_first(l); + + // Tracing + if (c != NULL) { + log_debug(metaspace)("ChunkManager %s: removed chunk " METACHUNK_FORMAT ".", + _name, METACHUNK_FORMAT_ARGS(c)); + } else { + log_trace(metaspace)("ChunkManager %s: no chunk found for level " CHKLVL_FORMAT, + _name, l); + } + + return c; +} + +// Creates a chunk manager with a given name (which is for debug purposes only) +// and an associated space list which will be used to request new chunks from +// (see get_chunk()) +ChunkManager::ChunkManager(const char* name, VirtualSpaceList* space_list) + : _vslist(space_list), + _name(name), + _chunks() +{ +} + +// Given a chunk we are about to handout to the caller, make sure it is committed +// according to constants::committed_words_on_fresh_chunks +bool ChunkManager::commit_chunk_before_handout(Metachunk* c) { + assert_lock_strong(MetaspaceExpand_lock); + const size_t must_be_committed = MIN2(c->word_size(), Settings::committed_words_on_fresh_chunks()); + return c->ensure_committed_locked(must_be_committed); +} + +// Given a chunk which must be outside of a freelist and must be free, split it to +// meet a target level and return it. Splinters are added to the freelist. +Metachunk* ChunkManager::split_chunk_and_add_splinters(Metachunk* c, chklvl_t target_level) { + + assert_lock_strong(MetaspaceExpand_lock); + + assert(c->is_free() && c->level() < target_level, "Invalid chunk for splitting"); + DEBUG_ONLY(chklvl::check_valid_level(target_level);) + + DEBUG_ONLY(c->verify(true);) + DEBUG_ONLY(c->vsnode()->verify(true);) + + // Chunk must be outside of our freelists + assert(_chunks.contains(c) == false, "Chunk is in freelist."); + + log_debug(metaspace)("ChunkManager %s: will split chunk " METACHUNK_FORMAT " to " CHKLVL_FORMAT ".", + _name, METACHUNK_FORMAT_ARGS(c), target_level); + + const chklvl_t orig_level = c->level(); + c = c->vsnode()->split(target_level, c, &_chunks); + + // Splitting should never fail. + assert(c != NULL, "Split failed"); + assert(c->level() == target_level, "Sanity"); + + DEBUG_ONLY(c->verify(false)); + + DEBUG_ONLY(verify_locked(true);) + + DEBUG_ONLY(c->vsnode()->verify(true);) + + return c; +} + +// Get a chunk and be smart about it. +// - 1) Attempt to find a free chunk of exactly the pref_level level +// - 2) Failing that, attempt to find a chunk smaller or equal the max level. +// - 3) Failing that, attempt to find a free chunk of larger size and split it. +// - 4) Failing that, attempt to allocate a new chunk from the connected virtual space. +// - Failing that, give up and return NULL. +// Note: this is not guaranteed to return a *committed* chunk. The chunk manager will +// attempt to commit the returned chunk according to constants::committed_words_on_fresh_chunks; +// but this may fail if we hit a commit limit. In that case, a partly uncommit chunk +// will be returned, and the commit is attempted again when we allocate from the chunk's +// uncommitted area. See also Metachunk::allocate. +Metachunk* ChunkManager::get_chunk(chklvl_t max_level, chklvl_t pref_level) { + + MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); + + DEBUG_ONLY(verify_locked(false);) + + DEBUG_ONLY(chklvl::check_valid_level(max_level);) + DEBUG_ONLY(chklvl::check_valid_level(pref_level);) + assert(max_level >= pref_level, "invalid level."); + + Metachunk* c = NULL; + + // Tracing + log_debug(metaspace)("ChunkManager %s: get chunk: max " CHKLVL_FORMAT " (" SIZE_FORMAT ")," + "preferred " CHKLVL_FORMAT " (" SIZE_FORMAT ").", + _name, max_level, chklvl::word_size_for_level(max_level), + pref_level, chklvl::word_size_for_level(pref_level)); + + // 1) Attempt to find a free chunk of exactly the pref_level level + c = remove_first_chunk_at_level(pref_level); + + // Todo: + // + // We need to meditate about steps (2) and (3) a bit more. + // By simply preferring to reuse small chunks vs splitting larger chunks we may emphasize + // fragmentation, strangely enough, if callers wanting medium sized chunks take small chunks + // instead, and taking them away from the many users which prefer small chunks. + // Alternatives: + // - alternating between (2) (3) and (3) (2) + // - mixing (2) and (3) into one loop with a growing delta, and maybe a "hesitance" barrier function + // - keeping track of chunk demand and adding this into the equation: if e.g. 4K chunks were heavily + // preferred in the past, maybe for callers wanting larger chunks leave those alone. + // - Take into account which chunks are committed? This would require some extra bookkeeping... + + // 2) Failing that, attempt to find a chunk smaller or equal the minimal level. + if (c == NULL) { + for (chklvl_t lvl = pref_level + 1; lvl <= max_level; lvl ++) { + c = remove_first_chunk_at_level(lvl); + if (c != NULL) { + break; } } } - chunks_counted += humongous_dictionary()->total_free_blocks(); - wordsize_chunks_counted += humongous_dictionary()->total_size(); - - assert(chunks_counted == _free_chunks_count && wordsize_chunks_counted == _free_chunks_total, - "freelist accounting mismatch: " - "we think: " SIZE_FORMAT " chunks, total " SIZE_FORMAT " words, " - "reality: " SIZE_FORMAT " chunks, total " SIZE_FORMAT " words.", - _free_chunks_count, _free_chunks_total, - chunks_counted, wordsize_chunks_counted); -} -#endif // ASSERT - -void ChunkManager::locked_print_free_chunks(outputStream* st) { - assert_lock_strong(MetaspaceExpand_lock); - st->print_cr("Free chunk total " SIZE_FORMAT " count " SIZE_FORMAT, - _free_chunks_total, _free_chunks_count); -} - -ChunkList* ChunkManager::free_chunks(ChunkIndex index) { - assert(index == SpecializedIndex || index == SmallIndex || index == MediumIndex, - "Bad index: %d", (int)index); - return &_free_chunks[index]; -} - -ChunkList* ChunkManager::find_free_chunks_list(size_t word_size) { - ChunkIndex index = list_index(word_size); - assert(index < HumongousIndex, "No humongous list"); - return free_chunks(index); -} - -// Helper for chunk splitting: given a target chunk size and a larger free chunk, -// split up the larger chunk into n smaller chunks, at least one of which should be -// the target chunk of target chunk size. The smaller chunks, including the target -// chunk, are returned to the freelist. The pointer to the target chunk is returned. -// Note that this chunk is supposed to be removed from the freelist right away. -Metachunk* ChunkManager::split_chunk(size_t target_chunk_word_size, Metachunk* larger_chunk) { - assert(larger_chunk->word_size() > target_chunk_word_size, "Sanity"); - - const ChunkIndex larger_chunk_index = larger_chunk->get_chunk_type(); - const ChunkIndex target_chunk_index = get_chunk_type_by_size(target_chunk_word_size, is_class()); - - MetaWord* const region_start = (MetaWord*)larger_chunk; - const size_t region_word_len = larger_chunk->word_size(); - MetaWord* const region_end = region_start + region_word_len; - VirtualSpaceNode* const vsn = larger_chunk->container(); - OccupancyMap* const ocmap = vsn->occupancy_map(); - - // Any larger non-humongous chunk size is a multiple of any smaller chunk size. - // Since non-humongous chunks are aligned to their chunk size, the larger chunk should start - // at an address suitable to place the smaller target chunk. - assert_is_aligned(region_start, target_chunk_word_size); - - // Remove old chunk. - free_chunks(larger_chunk_index)->remove_chunk(larger_chunk); - larger_chunk->remove_sentinel(); - - // Prevent access to the old chunk from here on. - larger_chunk = NULL; - // ... and wipe it. - DEBUG_ONLY(memset(region_start, 0xfe, region_word_len * BytesPerWord)); - - // In its place create first the target chunk... - MetaWord* p = region_start; - Metachunk* target_chunk = ::new (p) Metachunk(target_chunk_index, is_class(), target_chunk_word_size, vsn); - assert(target_chunk == (Metachunk*)p, "Sanity"); - target_chunk->set_origin(origin_split); - - // Note: we do not need to mark its start in the occupancy map - // because it coincides with the old chunk start. - - // Mark chunk as free and return to the freelist. - do_update_in_use_info_for_chunk(target_chunk, false); - free_chunks(target_chunk_index)->return_chunk_at_head(target_chunk); - - // This chunk should now be valid and can be verified. - DEBUG_ONLY(do_verify_chunk(target_chunk)); - - // In the remaining space create the remainder chunks. - p += target_chunk->word_size(); - assert(p < region_end, "Sanity"); - - while (p < region_end) { - - // Find the largest chunk size which fits the alignment requirements at address p. - ChunkIndex this_chunk_index = prev_chunk_index(larger_chunk_index); - size_t this_chunk_word_size = 0; - for(;;) { - this_chunk_word_size = get_size_for_nonhumongous_chunktype(this_chunk_index, is_class()); - if (is_aligned(p, this_chunk_word_size * BytesPerWord)) { + // 3) Failing that, attempt to find a free chunk of larger size and split it. + if (c == NULL) { + for (chklvl_t lvl = pref_level - 1; lvl >= chklvl::ROOT_CHUNK_LEVEL; lvl --) { + c = remove_first_chunk_at_level(lvl); + if (c != NULL) { + // Split chunk; add splinters to freelist + c = split_chunk_and_add_splinters(c, pref_level); break; - } else { - this_chunk_index = prev_chunk_index(this_chunk_index); - assert(this_chunk_index >= target_chunk_index, "Sanity"); - } - } - - assert(this_chunk_word_size >= target_chunk_word_size, "Sanity"); - assert(is_aligned(p, this_chunk_word_size * BytesPerWord), "Sanity"); - assert(p + this_chunk_word_size <= region_end, "Sanity"); - - // Create splitting chunk. - Metachunk* this_chunk = ::new (p) Metachunk(this_chunk_index, is_class(), this_chunk_word_size, vsn); - assert(this_chunk == (Metachunk*)p, "Sanity"); - this_chunk->set_origin(origin_split); - ocmap->set_chunk_starts_at_address(p, true); - do_update_in_use_info_for_chunk(this_chunk, false); - - // This chunk should be valid and can be verified. - DEBUG_ONLY(do_verify_chunk(this_chunk)); - - // Return this chunk to freelist and correct counter. - free_chunks(this_chunk_index)->return_chunk_at_head(this_chunk); - _free_chunks_count ++; - - log_trace(gc, metaspace, freelist)("Created chunk at " PTR_FORMAT ", word size " - SIZE_FORMAT_HEX " (%s), in split region [" PTR_FORMAT "..." PTR_FORMAT ").", - p2i(this_chunk), this_chunk->word_size(), chunk_size_name(this_chunk_index), - p2i(region_start), p2i(region_end)); - - p += this_chunk_word_size; - - } - - // Note: at this point, the VirtualSpaceNode is invalid since we split a chunk and - // did not yet hand out part of that split; so, vsn->verify_free_chunks_are_ideally_merged() - // would assert. Instead, do all verifications in the caller. - - DEBUG_ONLY(g_internal_statistics.num_chunk_splits ++); - - return target_chunk; -} - -Metachunk* ChunkManager::free_chunks_get(size_t word_size) { - assert_lock_strong(MetaspaceExpand_lock); - - 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); - assert(free_list != NULL, "Sanity check"); - - chunk = free_list->head(); - - if (chunk == NULL) { - // Split large chunks into smaller chunks if there are no smaller chunks, just large chunks. - // This is the counterpart of the coalescing-upon-chunk-return. - - ChunkIndex target_chunk_index = get_chunk_type_by_size(word_size, is_class()); - - // Is there a larger chunk we could split? - 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) { - assert(larger_chunk->word_size() > word_size, "Sanity"); - assert(larger_chunk->get_chunk_type() == larger_chunk_index, "Sanity"); - - // We found a larger chunk. Lets split it up: - // - remove old chunk - // - in its place, create new smaller chunks, with at least one chunk - // being of target size, the others sized as large as possible. This - // is to make sure the resulting chunks are "as coalesced as possible" - // (similar to VirtualSpaceNode::retire()). - // Note: during this operation both ChunkManager and VirtualSpaceNode - // are temporarily invalid, so be careful with asserts. - - log_trace(gc, metaspace, freelist)("%s: splitting chunk " PTR_FORMAT - ", word size " SIZE_FORMAT_HEX " (%s), to get a chunk of word size " SIZE_FORMAT_HEX " (%s)...", - (is_class() ? "class space" : "metaspace"), p2i(larger_chunk), larger_chunk->word_size(), - chunk_size_name(larger_chunk_index), word_size, chunk_size_name(target_chunk_index)); - - chunk = split_chunk(word_size, larger_chunk); - - // This should have worked. - assert(chunk != NULL, "Sanity"); - assert(chunk->word_size() == word_size, "Sanity"); - assert(chunk->is_tagged_free(), "Sanity"); - - we_did_split_a_chunk = true; - - } - } - - if (chunk == NULL) { - return NULL; - } - - // Remove the chunk as the head of the list. - free_list->remove_chunk(chunk); - - log_trace(gc, metaspace, freelist)("ChunkManager::free_chunks_get: free_list: " PTR_FORMAT " chunks left: " SSIZE_FORMAT ".", - p2i(free_list), free_list->count()); - - } else { - chunk = humongous_dictionary()->get_chunk(word_size); - - if (chunk == NULL) { - return NULL; - } - - log_trace(gc, metaspace, alloc)("Free list allocate humongous chunk size " SIZE_FORMAT " for requested size " SIZE_FORMAT " waste " SIZE_FORMAT, - chunk->word_size(), word_size, chunk->word_size() - word_size); - } - - // Chunk has been removed from the chunk manager; update counters. - account_for_removed_chunk(chunk); - do_update_in_use_info_for_chunk(chunk, true); - chunk->container()->inc_container_count(); - chunk->inc_use_count(); - - // Remove it from the links to this freelist - chunk->set_next(NULL); - chunk->set_prev(NULL); - - // Run some verifications (some more if we did a chunk split) -#ifdef ASSERT - - EVERY_NTH(VerifyMetaspaceInterval) - // Be extra verify-y when chunk split happened. - locked_verify(true); - VirtualSpaceNode* const vsn = chunk->container(); - vsn->verify(true); - if (we_did_split_a_chunk) { - vsn->verify_free_chunks_are_ideally_merged(); - } - END_EVERY_NTH - - g_internal_statistics.num_chunks_removed_from_freelist ++; - -#endif - - return chunk; -} - -Metachunk* ChunkManager::chunk_freelist_allocate(size_t word_size) { - assert_lock_strong(MetaspaceExpand_lock); - - // Take from the beginning of the list - Metachunk* chunk = free_chunks_get(word_size); - if (chunk == NULL) { - return NULL; - } - - assert((word_size <= chunk->word_size()) || - (list_index(chunk->word_size()) == HumongousIndex), - "Non-humongous variable sized chunk"); - LogTarget(Trace, gc, metaspace, freelist) lt; - if (lt.is_enabled()) { - size_t list_count; - if (list_index(word_size) < HumongousIndex) { - ChunkList* list = find_free_chunks_list(word_size); - list_count = list->count(); - } else { - list_count = humongous_dictionary()->total_count(); - } - LogStream ls(lt); - ls.print("ChunkManager::chunk_freelist_allocate: " PTR_FORMAT " chunk " PTR_FORMAT " size " SIZE_FORMAT " count " SIZE_FORMAT " ", - p2i(this), p2i(chunk), chunk->word_size(), list_count); - ResourceMark rm; - locked_print_free_chunks(&ls); - } - - return chunk; -} - -void ChunkManager::return_single_chunk(Metachunk* chunk) { - -#ifdef ASSERT - EVERY_NTH(VerifyMetaspaceInterval) - this->locked_verify(false); - do_verify_chunk(chunk); - END_EVERY_NTH -#endif - - const ChunkIndex index = chunk->get_chunk_type(); - assert_lock_strong(MetaspaceExpand_lock); - DEBUG_ONLY(g_internal_statistics.num_chunks_added_to_freelist ++;) - assert(chunk != NULL, "Expected chunk."); - assert(chunk->container() != NULL, "Container should have been set."); - assert(chunk->is_tagged_free() == false, "Chunk should be in use."); - index_bounds_check(index); - - // Note: mangle *before* returning the chunk to the freelist or dictionary. It does not - // matter for the freelist (non-humongous chunks), but the humongous chunk dictionary - // keeps tree node pointers in the chunk payload area which mangle will overwrite. - DEBUG_ONLY(chunk->mangle(badMetaWordVal);) - - // may need node for verification later after chunk may have been merged away. - DEBUG_ONLY(VirtualSpaceNode* vsn = chunk->container(); ) - - if (index != HumongousIndex) { - // Return non-humongous chunk to freelist. - ChunkList* list = free_chunks(index); - assert(list->size() == chunk->word_size(), "Wrong chunk type."); - list->return_chunk_at_head(chunk); - log_trace(gc, metaspace, freelist)("returned one %s chunk at " PTR_FORMAT " to freelist.", - chunk_size_name(index), p2i(chunk)); - } else { - // Return humongous chunk to dictionary. - assert(chunk->word_size() > free_chunks(MediumIndex)->size(), "Wrong chunk type."); - assert(chunk->word_size() % free_chunks(SpecializedIndex)->size() == 0, - "Humongous chunk has wrong alignment."); - _humongous_dictionary.return_chunk(chunk); - log_trace(gc, metaspace, freelist)("returned one %s chunk at " PTR_FORMAT " (word size " SIZE_FORMAT ") to freelist.", - chunk_size_name(index), p2i(chunk), chunk->word_size()); - } - chunk->container()->dec_container_count(); - do_update_in_use_info_for_chunk(chunk, false); - - // Chunk has been added; update counters. - account_for_added_chunk(chunk); - - // Attempt coalesce returned chunks with its neighboring chunks: - // if this chunk is small or special, attempt to coalesce to a medium chunk. - if (index == SmallIndex || index == SpecializedIndex) { - if (!attempt_to_coalesce_around_chunk(chunk, MediumIndex)) { - // This did not work. But if this chunk is special, we still may form a small chunk? - if (index == SpecializedIndex) { - if (!attempt_to_coalesce_around_chunk(chunk, SmallIndex)) { - // give up. - } } } } - // From here on do not access chunk anymore, it may have been merged with another chunk. + // 4) Failing that, attempt to allocate a new chunk from the connected virtual space. + if (c == NULL) { -#ifdef ASSERT - EVERY_NTH(VerifyMetaspaceInterval) - this->locked_verify(true); - vsn->verify(true); - vsn->verify_free_chunks_are_ideally_merged(); - END_EVERY_NTH -#endif + // Tracing + log_debug(metaspace)("ChunkManager %s: need new root chunk.", _name); + + c = _vslist->allocate_root_chunk(); + + // This may have failed if the virtual space list is exhausted but it cannot be expanded + // by a new node (class space). + if (c == NULL) { + return NULL; + } + + assert(c->level() == chklvl::LOWEST_CHUNK_LEVEL, "Not a root chunk?"); + + // Split this root chunk to the desired chunk size. + if (pref_level > c->level()) { + c = split_chunk_and_add_splinters(c, pref_level); + } + } + + // Note that we should at this point have a chunk; should always work. If we hit + // a commit limit in the meantime, the chunk may still be uncommitted, but the chunk + // itself should exist now. + assert(c != NULL, "Unexpected"); + + // Before returning the chunk, attempt to commit it according to the handout rules. + // If that fails, we ignore the error and return the uncommitted chunk. + if (commit_chunk_before_handout(c) == false) { + log_info(gc, metaspace)("Failed to commit chunk prior to handout."); + } + + // Any chunk returned from ChunkManager shall be marked as in use. + c->set_in_use(); + + DEBUG_ONLY(verify_locked(false);) + + log_debug(metaspace)("ChunkManager %s: handing out chunk " METACHUNK_FORMAT ".", + _name, METACHUNK_FORMAT_ARGS(c)); + + + DEBUG_ONLY(InternalStats::inc_num_chunks_taken_from_freelist();) + + return c; + +} // ChunkManager::get_chunk + + +// Return a single chunk to the ChunkManager and adjust accounting. May merge chunk +// with neighbors. +// As a side effect this removes the chunk from whatever list it has been in previously. +// Happens after a Classloader was unloaded and releases its metaspace chunks. +// !! Note: this may invalidate the chunk. Do not access the chunk after +// this function returns !! +void ChunkManager::return_chunk(Metachunk* c) { + + MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); + + log_debug(metaspace)("ChunkManager %s: returning chunk " METACHUNK_FORMAT ".", + _name, METACHUNK_FORMAT_ARGS(c)); + + DEBUG_ONLY(verify_locked(false);) + DEBUG_ONLY(c->verify(false);) + + assert(!_chunks.contains(c), "A chunk to be added to the freelist must not be in the freelist already."); + + assert(c->is_in_use(), "Unexpected chunk state"); + assert(!c->in_list(), "Remove from list first"); + c->set_free(); + c->reset_used_words(); + + const chklvl_t orig_lvl = c->level(); + + Metachunk* merged = NULL; + if (!c->is_root_chunk()) { + // Only attempt merging if we are not of the lowest level already. + merged = c->vsnode()->merge(c, &_chunks); + } + + if (merged != NULL) { + + DEBUG_ONLY(merged->verify(false)); + + // We did merge our chunk into a different chunk. + + // We did merge chunks and now have a bigger chunk. + assert(merged->level() < orig_lvl, "Sanity"); + + log_trace(metaspace)("ChunkManager %s: merged into chunk " METACHUNK_FORMAT ".", + _name, METACHUNK_FORMAT_ARGS(merged)); + + c = merged; + + } + + if (Settings::uncommit_on_return() && + Settings::uncommit_on_return_min_word_size() <= c->word_size()) + { + log_trace(metaspace)("ChunkManager %s: uncommitting free chunk " METACHUNK_FORMAT ".", + _name, METACHUNK_FORMAT_ARGS(c)); + c->uncommit_locked(); + } + + return_chunk_simple(c); + + DEBUG_ONLY(verify_locked(false);) + DEBUG_ONLY(c->vsnode()->verify(true);) + + DEBUG_ONLY(InternalStats::inc_num_chunks_returned_to_freelist();) } -void ChunkManager::return_chunk_list(Metachunk* chunks) { - if (chunks == NULL) { - return; - } - LogTarget(Trace, gc, metaspace, freelist) log; - if (log.is_enabled()) { // tracing - log.print("returning list of chunks..."); - } - unsigned num_chunks_returned = 0; - size_t size_chunks_returned = 0; - Metachunk* cur = chunks; - while (cur != NULL) { - // Capture the next link before it is changed - // by the call to return_chunk_at_head(); - Metachunk* next = cur->next(); - if (log.is_enabled()) { // tracing - num_chunks_returned ++; - size_chunks_returned += cur->word_size(); +// Given a chunk c, which must be "in use" and must not be a root chunk, attempt to +// enlarge it in place by claiming its trailing buddy. +// +// This will only work if c is the leader of the buddy pair and the trailing buddy is free. +// +// If successful, the follower chunk will be removed from the freelists, the leader chunk c will +// double in size (level decreased by one). +// +// On success, true is returned, false otherwise. +bool ChunkManager::attempt_enlarge_chunk(Metachunk* c) { + MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); + return c->vsnode()->attempt_enlarge_chunk(c, &_chunks); +} + +static void print_word_size_delta(outputStream* st, size_t word_size_1, size_t word_size_2) { + if (word_size_1 == word_size_2) { + print_scaled_words(st, word_size_1); + st->print (" (no change)"); + } else { + print_scaled_words(st, word_size_1); + st->print("->"); + print_scaled_words(st, word_size_2); + st->print(" ("); + if (word_size_2 <= word_size_1) { + st->print("-"); + print_scaled_words(st, word_size_1 - word_size_2); + } else { + st->print("+"); + print_scaled_words(st, word_size_2 - word_size_1); } - return_single_chunk(cur); - cur = next; - } - if (log.is_enabled()) { // tracing - log.print("returned %u chunks to freelist, total word size " SIZE_FORMAT ".", - num_chunks_returned, size_chunks_returned); + st->print(")"); } } -void ChunkManager::collect_statistics(ChunkManagerStatistics* out) const { - MutexLocker cl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); - for (ChunkIndex i = ZeroIndex; i < NumberOfInUseLists; i = next_chunk_index(i)) { - out->chunk_stats(i).add(num_free_chunks(i), size_free_chunks_in_bytes(i) / sizeof(MetaWord)); +// Attempt to reclaim free areas in metaspace wholesale: +// - first, attempt to purge nodes of the backing virtual space. This can only be successful +// if whole nodes are only containing free chunks, so it highly depends on fragmentation. +// - then, it will uncommit areas of free chunks according to the rules laid down in +// settings (see settings.hpp). +void ChunkManager::wholesale_reclaim() { + + MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); + + log_info(metaspace)("ChunkManager \"%s\": reclaiming memory...", _name); + + const size_t reserved_before = _vslist->reserved_words(); + const size_t committed_before = _vslist->committed_words(); + int num_nodes_purged = 0; + + if (Settings::delete_nodes_on_purge()) { + num_nodes_purged = _vslist->purge(&_chunks); + DEBUG_ONLY(InternalStats::inc_num_purges();) } + + if (Settings::uncommit_on_purge()) { + const chklvl_t max_level = + chklvl::level_fitting_word_size(Settings::uncommit_on_purge_min_word_size()); + for (chklvl_t l = chklvl::LOWEST_CHUNK_LEVEL; + l <= max_level; + l ++) + { + for (Metachunk* c = _chunks.first_at_level(l); c != NULL; c = c->next()) { + c->uncommit_locked(); + } + } + DEBUG_ONLY(InternalStats::inc_num_wholesale_uncommits();) + } + + const size_t reserved_after = _vslist->reserved_words(); + const size_t committed_after = _vslist->committed_words(); + + // Print a nice report. + if (reserved_after == reserved_before && committed_after == committed_before) { + log_info(metaspace)("ChunkManager %s: ... nothing reclaimed.", _name); + } else { + LogTarget(Info, metaspace) lt; + if (lt.is_enabled()) { + LogStream ls(lt); + ls.print_cr("ChunkManager %s: finished reclaiming memory: ", _name); + + ls.print("reserved: "); + print_word_size_delta(&ls, reserved_before, reserved_after); + ls.cr(); + + ls.print("committed: "); + print_word_size_delta(&ls, committed_before, committed_after); + ls.cr(); + + ls.print_cr("full nodes purged: %d", num_nodes_purged); + } + } + + DEBUG_ONLY(_vslist->verify_locked(true)); + DEBUG_ONLY(verify_locked(true)); + +} + + +ChunkManager* ChunkManager::_chunkmanager_class = NULL; +ChunkManager* ChunkManager::_chunkmanager_nonclass = NULL; + +void ChunkManager::set_chunkmanager_class(ChunkManager* cm) { + assert(_chunkmanager_class == NULL, "Sanity"); + _chunkmanager_class = cm; +} + +void ChunkManager::set_chunkmanager_nonclass(ChunkManager* cm) { + assert(_chunkmanager_nonclass == NULL, "Sanity"); + _chunkmanager_nonclass = cm; +} + + +// Update statistics. +void ChunkManager::add_to_statistics(cm_stats_t* out) const { + + MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); + + for (chklvl_t l = chklvl::ROOT_CHUNK_LEVEL; l <= chklvl::HIGHEST_CHUNK_LEVEL; l ++) { + out->num_chunks[l] += _chunks.num_chunks_at_level(l); + out->committed_word_size[l] += _chunks.committed_word_size_at_level(l); + } + + DEBUG_ONLY(out->verify();) + +} + +#ifdef ASSERT + +void ChunkManager::verify(bool slow) const { + MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); + verify_locked(slow); +} + +void ChunkManager::verify_locked(bool slow) const { + + assert_lock_strong(MetaspaceExpand_lock); + + assert(_vslist != NULL, "No vslist"); + + // This checks that the lists are wired up correctly, that the counters are valid and + // that each chunk is (only) in its correct list. + _chunks.verify(true); + + // Need to check that each chunk is free..._size = 0; + for (chklvl_t l = chklvl::LOWEST_CHUNK_LEVEL; l <= chklvl::HIGHEST_CHUNK_LEVEL; l ++) { + for (const Metachunk* c = _chunks.first_at_level(l); c != NULL; c = c->next()) { + assert(c->is_free(), "Chunk is not free."); + assert(c->used_words() == 0, "Chunk should have not used words."); + } + } + +} + +#endif // ASSERT + +void ChunkManager::print_on(outputStream* st) const { + MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); + print_on_locked(st); +} + +void ChunkManager::print_on_locked(outputStream* st) const { + assert_lock_strong(MetaspaceExpand_lock); + st->print_cr("cm %s: %d chunks, total word size: " SIZE_FORMAT ", committed word size: " SIZE_FORMAT, _name, + total_num_chunks(), total_word_size(), _chunks.total_committed_word_size()); + _chunks.print_on(st); } } // namespace metaspace diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/chunkManager.hpp --- a/src/hotspot/share/memory/metaspace/chunkManager.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace/chunkManager.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -1,5 +1,6 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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 @@ -26,173 +27,137 @@ #define SHARE_MEMORY_METASPACE_CHUNKMANAGER_HPP #include "memory/allocation.hpp" -#include "memory/binaryTreeDictionary.hpp" -#include "memory/freeList.hpp" +#include "memory/metaspace/chunkLevel.hpp" +#include "memory/metaspace/counter.hpp" #include "memory/metaspace/metachunk.hpp" -#include "memory/metaspace/metaspaceStatistics.hpp" -#include "memory/metaspaceChunkFreeListSummary.hpp" -#include "utilities/globalDefinitions.hpp" - -class ChunkManagerTestAccessor; namespace metaspace { -typedef class FreeList ChunkList; -typedef BinaryTreeDictionary > ChunkTreeDictionary; +class VirtualSpaceList; +struct cm_stats_t; -// Manages the global free lists of chunks. +// class ChunkManager +// +// The ChunkManager has a central role. Callers request chunks from it. +// It keeps the freelists for chunks. If the freelist is exhausted it +// allocates new chunks from a connected VirtualSpaceList. +// class ChunkManager : public CHeapObj { - friend class ::ChunkManagerTestAccessor; - // Free list of chunks of different sizes. - // SpecializedChunk - // SmallChunk - // MediumChunk - ChunkList _free_chunks[NumberOfFreeLists]; + // A chunk manager is connected to a virtual space list which is used + // to allocate new root chunks when no free chunks are found. + VirtualSpaceList* const _vslist; - // Whether or not this is the class chunkmanager. - const bool _is_class; + // Name + const char* const _name; - // Return non-humongous chunk list by its index. - ChunkList* free_chunks(ChunkIndex index); + // Freelist + MetachunkListCluster _chunks; - // Returns non-humongous chunk list for the given chunk word size. - ChunkList* find_free_chunks_list(size_t word_size); + // Returns true if this manager contains the given chunk. Slow (walks free list) and + // only needed for verifications. + DEBUG_ONLY(bool contains_chunk(Metachunk* c) const;) - // HumongousChunk - ChunkTreeDictionary _humongous_dictionary; + // Given a chunk we are about to handout to the caller, make sure it is committed + // according to constants::committed_words_on_fresh_chunks. + // May fail if we hit the commit limit. + static bool commit_chunk_before_handout(Metachunk* c); - // Returns the humongous chunk dictionary. - ChunkTreeDictionary* humongous_dictionary() { return &_humongous_dictionary; } - const ChunkTreeDictionary* humongous_dictionary() const { return &_humongous_dictionary; } + // Take a single chunk from the given freelist and adjust counters. Returns NULL + // if there is no fitting chunk for this level. + Metachunk* remove_first_chunk_at_level(chklvl_t l); - // Size, in metaspace words, of all chunks managed by this ChunkManager - size_t _free_chunks_total; - // Number of chunks in this ChunkManager - size_t _free_chunks_count; + // Given a chunk which must be outside of a freelist and must be free, split it to + // meet a target level and return it. Splinters are added to the freelist. + Metachunk* split_chunk_and_add_splinters(Metachunk* c, chklvl_t target_level); - // Update counters after a chunk was added or removed removed. - void account_for_added_chunk(const Metachunk* c); - void account_for_removed_chunk(const Metachunk* c); + // Uncommit all chunks equal or below the given level. + void uncommit_free_chunks(chklvl_t max_level); - // Given a pointer to a chunk, attempts to merge it with neighboring - // free chunks to form a bigger chunk. Returns true if successful. - bool attempt_to_coalesce_around_chunk(Metachunk* chunk, ChunkIndex target_chunk_type); +public: - // Helper for chunk merging: - // Given an address range with 1-n chunks which are all supposed to be - // free and hence currently managed by this ChunkManager, remove them - // from this ChunkManager and mark them as invalid. - // - This does not correct the occupancy map. - // - This does not adjust the counters in ChunkManager. - // - Does not adjust container count counter in containing VirtualSpaceNode. - // Returns number of chunks removed. - int remove_chunks_in_area(MetaWord* p, size_t word_size); + // Creates a chunk manager with a given name (which is for debug purposes only) + // and an associated space list which will be used to request new chunks from + // (see get_chunk()) + ChunkManager(const char* name, VirtualSpaceList* space_list); - // Helper for chunk splitting: given a target chunk size and a larger free chunk, - // split up the larger chunk into n smaller chunks, at least one of which should be - // the target chunk of target chunk size. The smaller chunks, including the target - // chunk, are returned to the freelist. The pointer to the target chunk is returned. - // Note that this chunk is supposed to be removed from the freelist right away. - Metachunk* split_chunk(size_t target_chunk_word_size, Metachunk* chunk); + // Get a chunk and be smart about it. + // - 1) Attempt to find a free chunk of exactly the pref_level level + // - 2) Failing that, attempt to find a chunk smaller or equal the maximal level. + // - 3) Failing that, attempt to find a free chunk of larger size and split it. + // - 4) Failing that, attempt to allocate a new chunk from the connected virtual space. + // - Failing that, give up and return NULL. + // Note: this is not guaranteed to return a *committed* chunk. The chunk manager will + // attempt to commit the returned chunk according to constants::committed_words_on_fresh_chunks; + // but this may fail if we hit a commit limit. In that case, a partly uncommitted chunk + // will be returned, and the commit is attempted again when we allocate from the chunk's + // uncommitted area. See also Metachunk::allocate. + Metachunk* get_chunk(chklvl_t max_level, chklvl_t pref_level); - public: + // Return a single chunk to the ChunkManager and adjust accounting. May merge chunk + // with neighbors. + // Happens after a Classloader was unloaded and releases its metaspace chunks. + // !! Notes: + // 1) After this method returns, c may not be valid anymore. Do not access the chunk after this function returns. + // 2) This function will not remove c from its current chunk list. This has to be done by the caller prior to + // calling this method. + void return_chunk(Metachunk* c); - ChunkManager(bool is_class); + // Return a single chunk to the freelist and adjust accounting. No merge is attempted. + void return_chunk_simple(Metachunk* c); - // Add or delete (return) a chunk to the global freelist. - Metachunk* chunk_freelist_allocate(size_t word_size); + // Given a chunk c, which must be "in use" and must not be a root chunk, attempt to + // enlarge it in place by claiming its trailing buddy. + // + // This will only work if c is the leader of the buddy pair and the trailing buddy is free. + // + // If successful, the follower chunk will be removed from the freelists, the leader chunk c will + // double in size (level decreased by one). + // + // On success, true is returned, false otherwise. + bool attempt_enlarge_chunk(Metachunk* c); - // Map a size to a list index assuming that there are lists - // for special, small, medium, and humongous chunks. - ChunkIndex list_index(size_t size); + // Attempt to reclaim free areas in metaspace wholesale: + // - first, attempt to purge nodes of the backing virtual space. This can only be successful + // if whole nodes are only containing free chunks, so it highly depends on fragmentation. + // - then, it will uncommit areas of free chunks according to the rules laid down in + // settings (see settings.hpp). + void wholesale_reclaim(); - // Map a given index to the chunk size. - size_t size_by_index(ChunkIndex index) const; + // Run verifications. slow=true: verify chunk-internal integrity too. + DEBUG_ONLY(void verify(bool slow) const;) + DEBUG_ONLY(void verify_locked(bool slow) const;) - bool is_class() const { return _is_class; } + // Returns the name of this chunk manager. + const char* name() const { return _name; } - // Convenience accessors. - size_t medium_chunk_word_size() const { return size_by_index(MediumIndex); } - size_t small_chunk_word_size() const { return size_by_index(SmallIndex); } - size_t specialized_chunk_word_size() const { return size_by_index(SpecializedIndex); } + // Returns total number of chunks + int total_num_chunks() const { return _chunks.total_num_chunks(); } - // Take a chunk from the ChunkManager. The chunk is expected to be in - // the chunk manager (the freelist if non-humongous, the dictionary if - // humongous). - void remove_chunk(Metachunk* chunk); + // Returns number of words in all free chunks. + size_t total_word_size() const { return _chunks.total_word_size(); } - // Return a single chunk of type index to the ChunkManager. - void return_single_chunk(Metachunk* chunk); + // Update statistics. + void add_to_statistics(cm_stats_t* out) const; - // Add the simple linked list of chunks to the freelist of chunks - // of type index. - void return_chunk_list(Metachunk* chunk); + void print_on(outputStream* st) const; + void print_on_locked(outputStream* st) const; - // Total of the space in the free chunks list - size_t free_chunks_total_words() const { return _free_chunks_total; } - size_t free_chunks_total_bytes() const { return free_chunks_total_words() * BytesPerWord; } +private: - // Number of chunks in the free chunks list - size_t free_chunks_count() const { return _free_chunks_count; } + static ChunkManager* _chunkmanager_class; + static ChunkManager* _chunkmanager_nonclass; - // Remove from a list by size. Selects list based on size of chunk. - Metachunk* free_chunks_get(size_t chunk_word_size); +public: -#define index_bounds_check(index) \ - assert(is_valid_chunktype(index), "Bad index: %d", (int) index) + static ChunkManager* chunkmanager_class() { return _chunkmanager_class; } + static ChunkManager* chunkmanager_nonclass() { return _chunkmanager_nonclass; } - size_t num_free_chunks(ChunkIndex index) const { - index_bounds_check(index); - - if (index == HumongousIndex) { - return _humongous_dictionary.total_free_blocks(); - } - - ssize_t count = _free_chunks[index].count(); - return count == -1 ? 0 : (size_t) count; - } - - size_t size_free_chunks_in_bytes(ChunkIndex index) const { - index_bounds_check(index); - - size_t word_size = 0; - if (index == HumongousIndex) { - word_size = _humongous_dictionary.total_size(); - } else { - const size_t size_per_chunk_in_words = _free_chunks[index].size(); - word_size = size_per_chunk_in_words * num_free_chunks(index); - } - - return word_size * BytesPerWord; - } - - MetaspaceChunkFreeListSummary chunk_free_list_summary() const { - return MetaspaceChunkFreeListSummary(num_free_chunks(SpecializedIndex), - num_free_chunks(SmallIndex), - num_free_chunks(MediumIndex), - num_free_chunks(HumongousIndex), - size_free_chunks_in_bytes(SpecializedIndex), - size_free_chunks_in_bytes(SmallIndex), - size_free_chunks_in_bytes(MediumIndex), - size_free_chunks_in_bytes(HumongousIndex)); - } - -#ifdef ASSERT - // Debug support - // Verify free list integrity. slow=true: verify chunk-internal integrity too. - void verify(bool slow) const; - void locked_verify(bool slow) const; -#endif - - void locked_print_free_chunks(outputStream* st); - - // Fill in current statistic values to the given statistics object. - void collect_statistics(ChunkManagerStatistics* out) const; + static void set_chunkmanager_class(ChunkManager* cm); + static void set_chunkmanager_nonclass(ChunkManager* cm); }; } // namespace metaspace - #endif // SHARE_MEMORY_METASPACE_CHUNKMANAGER_HPP diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/classLoaderMetaspace.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/classLoaderMetaspace.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2018, 2019, SAP SE. All rights reserved. + * Copyright (c) 2018, 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 "logging/log.hpp" +#include "memory/metaspace.hpp" +#include "memory/metaspaceTracer.hpp" +#include "memory/metaspace/chunkAllocSequence.hpp" +#include "memory/metaspace/chunkManager.hpp" +#include "memory/metaspace/classLoaderMetaspace.hpp" +#include "memory/metaspace/internStat.hpp" +#include "memory/metaspace/metaspaceEnums.hpp" +#include "memory/metaspace/metaspaceStatistics.hpp" +#include "memory/metaspace/runningCounters.hpp" +#include "memory/metaspace/settings.hpp" +#include "memory/metaspace/spaceManager.hpp" +#include "runtime/atomic.hpp" +#include "utilities/debug.hpp" + +namespace metaspace { + +static bool use_class_space(bool is_class) { + if (Metaspace::using_class_space()) { + if (is_class) { + return true; + } + } + return false; +} + +static bool use_class_space(MetadataType mdType) { + return use_class_space(is_class(mdType)); +} + +ClassLoaderMetaspace::ClassLoaderMetaspace(Mutex* lock, MetaspaceType space_type) + : _lock(lock) + , _space_type(space_type) + , _non_class_space_manager(NULL) + , _class_space_manager(NULL) +{ + // Initialize non-class spacemanager + _non_class_space_manager = new SpaceManager( + ChunkManager::chunkmanager_nonclass(), + ChunkAllocSequence::alloc_sequence_by_space_type(space_type, false), + lock, + RunningCounters::used_nonclass_counter(), + "non-class sm"); + + // If needed, initialize class spacemanager + if (Metaspace::using_class_space()) { + _class_space_manager = new SpaceManager( + ChunkManager::chunkmanager_class(), + ChunkAllocSequence::alloc_sequence_by_space_type(space_type, true), + lock, + RunningCounters::used_class_counter(), + "class sm"); + } + + DEBUG_ONLY(InternalStats::inc_num_metaspace_births();) + +} + +ClassLoaderMetaspace::~ClassLoaderMetaspace() { + Metaspace::assert_not_frozen(); + + delete _non_class_space_manager; + delete _class_space_manager; + + DEBUG_ONLY(InternalStats::inc_num_metaspace_deaths();) + +} + +// Allocate word_size words from Metaspace. +MetaWord* ClassLoaderMetaspace::allocate(size_t word_size, MetadataType mdType) { + Metaspace::assert_not_frozen(); + if (use_class_space(mdType)) { + return class_space_manager()->allocate(word_size); + } else { + return non_class_space_manager()->allocate(word_size); + } +} + +// Attempt to expand the GC threshold to be good for at least another word_size words +// and allocate. Returns NULL if failure. Used during Metaspace GC. +MetaWord* ClassLoaderMetaspace::expand_and_allocate(size_t word_size, MetadataType mdType) { + Metaspace::assert_not_frozen(); + size_t delta_bytes = MetaspaceGC::delta_capacity_until_GC(word_size * BytesPerWord); + assert(delta_bytes > 0, "Must be"); + + size_t before = 0; + size_t after = 0; + bool can_retry = true; + MetaWord* res; + bool incremented; + + // Each thread increments the HWM at most once. Even if the thread fails to increment + // the HWM, an allocation is still attempted. This is because another thread must then + // have incremented the HWM and therefore the allocation might still succeed. + do { + incremented = MetaspaceGC::inc_capacity_until_GC(delta_bytes, &after, &before, &can_retry); + res = allocate(word_size, mdType); + } while (!incremented && res == NULL && can_retry); + + if (incremented) { + Metaspace::tracer()->report_gc_threshold(before, after, + MetaspaceGCThresholdUpdater::ExpandAndAllocate); + log_trace(gc, metaspace)("Increase capacity to GC from " SIZE_FORMAT " to " SIZE_FORMAT, before, after); + } + + return res; +} + +// Prematurely returns a metaspace allocation to the _block_freelists +// because it is not needed anymore. +void ClassLoaderMetaspace::deallocate(MetaWord* ptr, size_t word_size, bool is_class) { + + Metaspace::assert_not_frozen(); + + if (use_class_space(is_class)) { + class_space_manager()->deallocate(ptr, word_size); + } else { + non_class_space_manager()->deallocate(ptr, word_size); + } + + DEBUG_ONLY(InternalStats::inc_num_deallocs();) + +} + +// Update statistics. This walks all in-use chunks. +void ClassLoaderMetaspace::add_to_statistics(clms_stats_t* out) const { + if (non_class_space_manager() != NULL) { + non_class_space_manager()->add_to_statistics(&out->sm_stats_nonclass); + } + if (class_space_manager() != NULL) { + class_space_manager()->add_to_statistics(&out->sm_stats_class); + } +} + +#ifdef ASSERT +void ClassLoaderMetaspace::verify(bool slow) const { + check_valid_spacetype(_space_type); + if (non_class_space_manager() != NULL) { + non_class_space_manager()->verify(slow); + } + if (class_space_manager() != NULL) { + class_space_manager()->verify(slow); + } +} +#endif // ASSERT + +} // end namespace metaspace + + + + diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/classLoaderMetaspace.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/classLoaderMetaspace.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018, 2019, SAP SE. All rights reserved. + * Copyright (c) 2018, 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_MEMORY_METASPACE_CLASSLOADERMETASPACE_HPP +#define SHARE_MEMORY_METASPACE_CLASSLOADERMETASPACE_HPP + +#include "memory/allocation.hpp" +#include "memory/metaspace/metaspaceEnums.hpp" + +class Mutex; + +namespace metaspace { + +class SpaceManager; +struct clms_stats_t; + +class ClassLoaderMetaspace : public CHeapObj { + + // The CLD lock. + Mutex* const _lock; + + const MetaspaceType _space_type; + + SpaceManager* _non_class_space_manager; + SpaceManager* _class_space_manager; + + Mutex* lock() const { return _lock; } + SpaceManager* non_class_space_manager() const { return _non_class_space_manager; } + SpaceManager* class_space_manager() const { return _class_space_manager; } + + SpaceManager* get_space_manager(bool is_class) { + return is_class ? class_space_manager() : non_class_space_manager(); + } + +public: + + ClassLoaderMetaspace(Mutex* lock, MetaspaceType space_type); + + ~ClassLoaderMetaspace(); + + MetaspaceType space_type() const { return _space_type; } + + // Allocate word_size words from Metaspace. + MetaWord* allocate(size_t word_size, MetadataType mdType); + + // Attempt to expand the GC threshold to be good for at least another word_size words + // and allocate. Returns NULL if failure. Used during Metaspace GC. + MetaWord* expand_and_allocate(size_t word_size, MetadataType mdType); + + // Prematurely returns a metaspace allocation to the _block_freelists + // because it is not needed anymore. + void deallocate(MetaWord* ptr, size_t word_size, bool is_class); + + // Update statistics. This walks all in-use chunks. + void add_to_statistics(clms_stats_t* out) const; + + DEBUG_ONLY(void verify(bool slow) const;) + + // TODO + size_t allocated_blocks_bytes() const { return 0; } + size_t allocated_chunks_bytes() const { return 0; } + +}; + +} // namespace metaspace + +#endif // SHARE_MEMORY_METASPACE_CLASSLOADERMETASPACE_HPP diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/commitLimiter.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/commitLimiter.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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 "memory/metaspace/commitLimiter.hpp" +#include "memory/metaspace.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" + +namespace metaspace { + +// Returns the size, in words, by which we may expand the metaspace committed area without: +// - _cap == 0: hitting GC threshold or the MaxMetaspaceSize +// - _cap > 0: hitting cap (this is just for testing purposes) +size_t CommitLimiter::possible_expansion_words() const { + + if (_cap > 0) { // Testing. + assert(_cnt.get() <= _cap, "Beyond limit?"); + return _cap - _cnt.get(); + } + + assert(_cnt.get() * BytesPerWord <= MaxMetaspaceSize, "Beyond limit?"); + const size_t words_left_below_max = MaxMetaspaceSize / BytesPerWord - _cnt.get(); + + const size_t words_left_below_gc_threshold = MetaspaceGC::allowed_expansion(); + + return MIN2(words_left_below_max, words_left_below_gc_threshold); + +} + +static CommitLimiter g_global_limiter(0); + +// Returns the global metaspace commit counter +CommitLimiter* CommitLimiter::globalLimiter() { + return &g_global_limiter; +} + +} // namespace metaspace + diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/commitLimiter.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/commitLimiter.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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_MEMORY_METASPACE_COMMITLIMITER_HPP +#define SHARE_MEMORY_METASPACE_COMMITLIMITER_HPP + +#include "memory/allocation.hpp" +#include "memory/metaspace/counter.hpp" + +namespace metaspace { + +class CommitLimiter : public CHeapObj { + + // Counts total words committed for metaspace + SizeCounter _cnt; + + // Purely for testing purposes: cap, in words. + const size_t _cap; + +public: + + // Create a commit limiter. This is only useful for testing, with a cap != 0, + // since normal code should use the global commit limiter. + // If cap != 0 (word size), the cap replaces the internal logic of limiting. + CommitLimiter(size_t cap = 0) : _cnt(), _cap(cap) {} + + // Returns the size, in words, by which we may expand the metaspace committed area without: + // - _cap == 0: hitting GC threshold or the MaxMetaspaceSize + // - _cap > 0: hitting cap (this is just for testing purposes) + size_t possible_expansion_words() const; + + void increase_committed(size_t word_size) { _cnt.increment_by(word_size); } + void decrease_committed(size_t word_size) { _cnt.decrement_by(word_size); } + size_t committed_words() const { return _cnt.get(); } + + // Returns the global metaspace commit counter + static CommitLimiter* globalLimiter(); + +}; + +} // namespace metaspace + +#endif // SHARE_MEMORY_METASPACE_COMMITLIMITER_HPP diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/commitMask.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/commitMask.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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 +#include "precompiled.hpp" + +#include "memory/metaspace/commitMask.hpp" +#include "memory/metaspace/metaspaceCommon.hpp" +#include "memory/metaspace/settings.hpp" +#include "runtime/stubRoutines.hpp" + +#include "utilities/align.hpp" +#include "utilities/debug.hpp" + +namespace metaspace { + +CommitMask::CommitMask(const MetaWord* start, size_t word_size) + : CHeapBitMap(mask_size(word_size, Settings::commit_granule_words())) + , _base(start) + , _word_size(word_size) + , _words_per_bit(Settings::commit_granule_words()) +{ + assert(_word_size > 0 && _words_per_bit > 0 && + is_aligned(_word_size, _words_per_bit), "Sanity"); +} + +#ifdef ASSERT + +static const bool TEST_UNCOMMITTED_REGION = false; + +volatile u1 x; + +void CommitMask::verify(bool slow, bool do_touch_test) const { + + // Walk the whole commit mask. + // For each 1 bit, check if the associated granule is accessible. + // For each 0 bit, check if the associated granule is not accessible. Slow mode only. + + assert_is_aligned(_base, _words_per_bit * BytesPerWord); + assert_is_aligned(_word_size, _words_per_bit); + + if (do_touch_test) { + for (idx_t i = 0; i < size(); i ++) { + const MetaWord* const p = _base + (i * _words_per_bit); + if (at(i)) { + // Should be accessible. Just touch it. + x ^= *(u1*)p; + } else { + // Should not be accessible. + if (slow) { + // Note: results may differ between platforms. On Linux, this should be true since + // we uncommit memory by setting protection to PROT_NONE. We may have to look if + // this works as expected on other platforms. + if (TEST_UNCOMMITTED_REGION && CanUseSafeFetch32()) { + assert(os::is_readable_pointer(p) == false, + "index %u, pointer " PTR_FORMAT ", should not be accessible.", + (unsigned)i, p2i(p)); + } + } + } + } + } + +} + +#endif // ASSERT + +void CommitMask::print_on(outputStream* st) const { + + st->print("commit mask, base " PTR_FORMAT ":", p2i(base())); + + for (idx_t i = 0; i < size(); i ++) { + st->print("%c", at(i) ? 'X' : '-'); + } + + st->cr(); + +} + +} // namespace metaspace + diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/commitMask.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/commitMask.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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_MEMORY_METASPACE_COMMITMASK_HPP +#define SHARE_MEMORY_METASPACE_COMMITMASK_HPP + +#include "utilities/debug.hpp" +#include "utilities/bitMap.hpp" +#include "utilities/globalDefinitions.hpp" + +class outputStream; + +namespace metaspace { + +// A bitmap covering a range of metaspace; each bit in this mask corresponds to +// +class CommitMask : public CHeapBitMap { + + const MetaWord* const _base; + const size_t _word_size; + const size_t _words_per_bit; + + // Given an offset, in words, into the area, return the number of the bit + // covering it. + static idx_t bitno_for_word_offset(size_t offset, size_t words_per_bit) { + return offset / words_per_bit; + } + + idx_t bitno_for_address(const MetaWord* p) const { + // Note: we allow one-beyond since this is a typical need. + assert(p >= _base && p <= _base + _word_size, "Invalid address"); + const size_t off = p - _base; + return bitno_for_word_offset(off, _words_per_bit); + } + + static idx_t mask_size(size_t word_size, size_t words_per_bit) { + return bitno_for_word_offset(word_size, words_per_bit); + } + + struct BitCounterClosure : public BitMapClosure { + idx_t cnt; + bool do_bit(BitMap::idx_t offset) { cnt ++; return true; } + }; + + // Missing from BitMap. + // Count 1 bits in range [start, end). + idx_t count_one_bits_in_range(idx_t start, idx_t end) const { + assert(start < end, "Zero range"); + // TODO: This can be done more efficiently. + BitCounterClosure bcc; + bcc.cnt = 0; + iterate(&bcc, start, end); + return bcc.cnt; + } + +#ifdef ASSERT + // Given a pointer, check if it points into the range this bitmap covers. + bool is_pointer_valid(const MetaWord* p) const { + return p >= _base && p < _base + _word_size; + } + + // Given a pointer, check if it points into the range this bitmap covers. + void check_pointer(const MetaWord* p) const { + assert(is_pointer_valid(p), + "Pointer " PTR_FORMAT " not in range of this bitmap [" PTR_FORMAT ", " PTR_FORMAT ").", + p2i(p), p2i(_base), p2i(_base + _word_size)); + } + // Given a pointer, check if it points into the range this bitmap covers, + // and if it is aligned to commit granule border. + void check_pointer_aligned(const MetaWord* p) const { + check_pointer(p); + assert(is_aligned(p, _words_per_bit * BytesPerWord), + "Pointer " PTR_FORMAT " should be aligned to commit granule size " SIZE_FORMAT ".", + p2i(p), _words_per_bit * BytesPerWord); + } + // Given a range, check if it points into the range this bitmap covers, + // and if its borders are aligned to commit granule border. + void check_range(const MetaWord* start, size_t word_size) const { + check_pointer_aligned(start); + assert(is_aligned(word_size, _words_per_bit), + "Range " SIZE_FORMAT " should be aligned to commit granule size " SIZE_FORMAT ".", + word_size, _words_per_bit); + check_pointer(start + word_size - 1); + } +#endif + + // Marks a single commit granule as committed (value == true) + // or uncomitted (value == false) and returns + // its prior state. + bool mark_granule(idx_t bitno, bool value) { + bool b = at(bitno); + at_put(bitno, value); + return b; + } + +public: + + CommitMask(const MetaWord* start, size_t word_size); + + const MetaWord* base() const { return _base; } + size_t word_size() const { return _word_size; } + const MetaWord* end() const { return _base + word_size(); } + + // Given an address, returns true if the address is committed, false if not. + bool is_committed_address(const MetaWord* p) const { + DEBUG_ONLY(check_pointer(p)); + const idx_t bitno = bitno_for_address(p); + return at(bitno); + } + + // Given an address range [start..end), returns true if area is fully committed through. + bool is_fully_committed_range(const MetaWord* start, size_t word_size) const { + DEBUG_ONLY(check_range(start, word_size)); + assert(word_size > 0, "zero range"); + const idx_t b1 = bitno_for_address(start); + const idx_t b2 = bitno_for_address(start + word_size); + return get_next_zero_offset(b1, b2) == b2; + } + + // Given an address range, return size, in number of words, of committed area within that range. + size_t get_committed_size_in_range(const MetaWord* start, size_t word_size) const { + DEBUG_ONLY(check_range(start, word_size)); + assert(word_size > 0, "zero range"); + const idx_t b1 = bitno_for_address(start); + const idx_t b2 = bitno_for_address(start + word_size); + const idx_t num_bits = count_one_bits_in_range(b1, b2); + return num_bits * _words_per_bit; + } + + // Return total committed size, in number of words. + size_t get_committed_size() const { + return count_one_bits() * _words_per_bit; + } + + // Mark a whole address range [start, end) as committed. + // Return the number of words which had already been committed before this operation. + size_t mark_range_as_committed(const MetaWord* start, size_t word_size) { + DEBUG_ONLY(check_range(start, word_size)); + assert(word_size > 0, "zero range"); + const idx_t b1 = bitno_for_address(start); + const idx_t b2 = bitno_for_address(start + word_size); + if (b1 == b2) { // Simple case, 1 granule + bool was_committed = mark_granule(b1, true); + return was_committed ? _words_per_bit : 0; + } + const idx_t one_bits_in_range_before = count_one_bits_in_range(b1, b2); + set_range(b1, b2); + return one_bits_in_range_before * _words_per_bit; + } + + // Mark a whole address range [start, end) as uncommitted. + // Return the number of words which had already been uncommitted before this operation. + size_t mark_range_as_uncommitted(const MetaWord* start, size_t word_size) { + DEBUG_ONLY(check_range(start, word_size)); + assert(word_size > 0, "zero range"); + const idx_t b1 = bitno_for_address(start); + const idx_t b2 = bitno_for_address(start + word_size); + if (b1 == b2) { // Simple case, 1 granule + bool was_committed = mark_granule(b1, false); + return was_committed ? 0 : _words_per_bit; + } + const idx_t zero_bits_in_range_before = + (b2 - b1) - count_one_bits_in_range(b1, b2); + clear_range(b1, b2); + return zero_bits_in_range_before * _words_per_bit; + } + + + //// Debug stuff //// + DEBUG_ONLY(void verify(bool slow, bool do_touch_test = true) const;) + + void print_on(outputStream* st) const; + +}; + +} // namespace metaspace + +#endif // SHARE_MEMORY_METASPACE_COMMITMASK_HPP diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/counter.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/counter.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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_MEMORY_METASPACE_COUNTER_HPP +#define SHARE_MEMORY_METASPACE_COUNTER_HPP + +#include "metaprogramming/isSigned.hpp" +#include "runtime/atomic.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" + + +namespace metaspace { + +// A very simple helper class which counts something, offers decrement/increment +// methods and checks for overflow/underflow on increment/decrement. +// +// (since we seem to do that alot....) + +template +class AbstractCounter { + + T _c; + + // Only allow unsigned values for now + STATIC_ASSERT(IsSigned::value == false); + +public: + + AbstractCounter() : _c(0) {} + + T get() const { return _c; } + + void increment() { assert(_c + 1 > _c, "overflow"); _c ++; } + void increment_by(T v) { assert(_c + v >= _c, "overflow"); _c += v; } + void decrement() { assert(_c - 1 < _c, "underflow"); _c --; } + void decrement_by(T v) { assert(_c - v <= _c, "underflow"); _c -= v; } + + void reset() { _c = 0; } + +#ifdef ASSERT + void check(T expected) const { + assert(_c == expected, "Counter mismatch: %d, expected: %d.", + (int)_c, (int)expected); + } +#endif + +}; + +typedef AbstractCounter SizeCounter; +typedef AbstractCounter IntCounter; + + +template +class AbstractAtomicCounter { + + volatile T _c; + + // Only allow unsigned values for now + STATIC_ASSERT(IsSigned::value == false); + +public: + + AbstractAtomicCounter() : _c(0) {} + + T get() const { return _c; } + + void increment() { assert(_c + 1 > _c, "overflow"); Atomic::inc(&_c); } + void increment_by(T v) { assert(_c + v >= _c, "overflow"); Atomic::add(v, &_c); } + void decrement() { assert(_c - 1 < _c, "underflow"); Atomic::dec(&_c); } + void decrement_by(T v) { assert(_c - v <= _c, "underflow"); Atomic::sub(v, &_c); } + +#ifdef ASSERT + void check(T expected) const { + assert(_c == expected, "Counter mismatch: %d, expected: %d.", + (int)_c, (int)expected); + } +#endif + +}; + +typedef AbstractAtomicCounter SizeAtomicCounter; + + + +} // namespace metaspace + +#endif // SHARE_MEMORY_METASPACE_WORDSIZECOUNTER_HPP + diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/internStat.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/internStat.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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 "memory/metaspace/internStat.hpp" +#include "utilities/globalDefinitions.hpp" +#include "utilities/ostream.hpp" + +#ifdef ASSERT + +namespace metaspace { + +#define CREATE_COUNTER(name) uintx InternalStats::_##name; +#define CREATE_ATOMIC_COUNTER(name) volatile uintx InternalStats::_##name; + + ALL_MY_COUNTERS(CREATE_COUNTER, CREATE_ATOMIC_COUNTER) + +#undef MATERIALIZE_COUNTER +#undef MATERIALIZE_ATOMIC_COUNTER + +void InternalStats::print_on(outputStream* st) { + +#define xstr(s) str(s) +#define str(s) #s + +#define PRINTER(name) st->print_cr("%s: " UINTX_FORMAT ".", xstr(name), _##name); + + ALL_MY_COUNTERS(PRINTER, PRINTER) + +#undef PRINTER + +} + +} // namespace metaspace + +#endif // ASSERT diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/internStat.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/internStat.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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_MEMORY_METASPACE_INTERNSTAT_HPP +#define SHARE_MEMORY_METASPACE_INTERNSTAT_HPP + +#ifdef ASSERT + + +#include "memory/allocation.hpp" +#include "runtime/atomic.hpp" +#include "utilities/globalDefinitions.hpp" + +class outputStream; + +namespace metaspace { + +class InternalStats : public AllStatic { + + // Note: all counters which are modified on the classloader local allocation path + // (not under ExpandLock protection) have to be atomic. + +#define ALL_MY_COUNTERS(x, x_atomic) \ + \ + /* Number of allocations. */ \ + x_atomic(num_allocs) \ + \ + /* Number of deallocations */ \ + x_atomic(num_deallocs) \ + /* Number of times an allocation was satisfied */ \ + /* from deallocated blocks. */ \ + x_atomic(num_allocs_from_deallocated_blocks) \ + \ + /* Number of times an allocation failed */ \ + /* because the chunk was too small. */ \ + x_atomic(num_allocs_failed_chunk_too_small) \ + \ + /* Number of times an allocation failed */ \ + /* because we hit a limit. */ \ + x_atomic(num_allocs_failed_limit) \ + \ + /* Number of times a ClassLoaderMetaspace was */ \ + /* born... */ \ + x(num_metaspace_births) \ + /* ... and died. */ \ + x(num_metaspace_deaths) \ + \ + /* Number of times VirtualSpaceNode were */ \ + /* created... */ \ + x(num_vsnodes_created) \ + /* ... and purged. */ \ + x(num_vsnodes_destroyed) \ + \ + /* Number of times we committed space. */ \ + x(num_space_committed) \ + /* Number of times we uncommitted space. */ \ + x(num_space_uncommitted) \ + \ + /* Number of times a chunk was returned to the */ \ + /* freelist (external only). */ \ + x(num_chunks_returned_to_freelist) \ + /* Number of times a chunk was taken from */ \ + /* freelist (external only) */ \ + x(num_chunks_taken_from_freelist) \ + \ + /* Number of successful chunk merges */ \ + x(num_chunk_merges) \ + /* Number of chunks removed from freelist as */ \ + /* result of a merge operation */ \ + x(num_chunks_removed_from_freelist_due_to_merge) \ + \ + /* Number of chunk splits */ \ + x(num_chunk_splits) \ + /* Number of chunks added to freelist as */ \ + /* result of a split operation */ \ + x(num_chunks_added_to_freelist_due_to_split) \ + /* Number of chunk in place enlargements */ \ + x(num_chunk_enlarged) \ + /* Number of chunks retired */ \ + x(num_chunks_retired) \ + \ + /* Number of times we did a purge */ \ + x(num_purges) \ + /* Number of times we did a wholesale uncommit */ \ + x(num_wholesale_uncommits) \ + + + +#define DEFINE_COUNTER(name) static uintx _##name; +#define DEFINE_ATOMIC_COUNTER(name) static volatile uintx _##name; + + ALL_MY_COUNTERS(DEFINE_COUNTER, DEFINE_ATOMIC_COUNTER) + +#undef DEFINE_COUNTER +#undef DEFINE_ATOMIC_COUNTER + +public: + +#define INCREMENTOR(name) static void inc_##name() { _##name ++; } +#define INCREMENTOR_ATOMIC(name) static void inc_##name() { Atomic::inc(&_##name); } + + ALL_MY_COUNTERS(INCREMENTOR, INCREMENTOR_ATOMIC) + + static void print_on(outputStream* st); + +#undef INCREMENTOR +#undef INCREMENTOR_ATOMIC + +}; + +} // namespace metaspace + +#endif // ASSERT + +#endif // SHARE_MEMORY_METASPACE_INTERNSTAT_HPP diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/metachunk.cpp --- a/src/hotspot/share/memory/metaspace/metachunk.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace/metachunk.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -1,5 +1,6 @@ /* - * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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 @@ -22,150 +23,461 @@ * */ + +#include #include "precompiled.hpp" -#include "memory/allocation.hpp" + +#include "logging/log.hpp" +#include "memory/metaspace/chunkLevel.hpp" #include "memory/metaspace/metachunk.hpp" -#include "memory/metaspace/occupancyMap.hpp" +#include "memory/metaspace/metaspaceCommon.hpp" #include "memory/metaspace/virtualSpaceNode.hpp" + #include "utilities/align.hpp" #include "utilities/copy.hpp" #include "utilities/debug.hpp" namespace metaspace { -size_t Metachunk::object_alignment() { - // Must align pointers and sizes to 8, - // so that 64 bit types get correctly aligned. - const size_t alignment = 8; +// Make sure that the Klass alignment also agree. +STATIC_ASSERT(Metachunk::allocation_alignment_bytes == (size_t)KlassAlignmentInBytes); - // Make sure that the Klass alignment also agree. - STATIC_ASSERT(alignment == (size_t)KlassAlignmentInBytes); - - return alignment; +// Return a single char presentation of the state ('f', 'u', 'd') +char Metachunk::get_state_char() const { + switch (_state) { + case state_free: return 'f'; + case state_in_use: return 'u'; + case state_dead: return 'd'; + } + return '?'; } -size_t Metachunk::overhead() { - return align_up(sizeof(Metachunk), object_alignment()) / BytesPerWord; +// Commit uncommitted section of the chunk. +// Fails if we hit a commit limit. +bool Metachunk::commit_up_to(size_t new_committed_words) { + + // Please note: + // + // VirtualSpaceNode::ensure_range_is_committed(), when called over a range containing both committed and uncommitted parts, + // will replace the whole range with a new mapping, thus erasing the existing content in the committed parts. Therefore + // we must make sure never to call VirtualSpaceNode::ensure_range_is_committed() over a range containing live data. + // + // Luckily, this cannot happen by design. We have two cases: + // + // 1) chunks equal or larger than a commit granule. + // In this case, due to chunk geometry, the chunk should cover whole commit granules (in other words, a chunk equal or larger than + // a commit granule will never share a granule with a neighbor). That means whatever we commit or uncommit here does not affect + // neighboring chunks. We only have to take care not to re-commit used parts of ourself. We do this by moving the committed_words + // limit in multiple of commit granules. + // + // 2) chunks smaller than a commit granule. + // In this case, a chunk shares a single commit granule with its neighbors. But this never can be a problem: + // - Either the commit granule is already committed (and maybe the neighbors contain live data). In that case calling + // ensure_range_is_committed() will do nothing. + // - Or the commit granule is not committed, but in this case, the neighbors are uncommitted too and cannot contain live data. + +#ifdef ASSERT + if (word_size() >= Settings::commit_granule_words()) { + // case (1) + assert(is_aligned(base(), Settings::commit_granule_bytes()) && + is_aligned(end(), Settings::commit_granule_bytes()), + "Chunks larger than a commit granule must cover whole granules."); + assert(is_aligned(_committed_words, Settings::commit_granule_words()), + "The commit boundary must be aligned to commit granule size"); + assert(_used_words <= _committed_words, "Sanity"); + } else { + // case (2) + assert(_committed_words == 0 || _committed_words == word_size(), "Sanity"); + } +#endif + + // We should hold the expand lock at this point. + assert_lock_strong(MetaspaceExpand_lock); + + const size_t commit_from = _committed_words; + const size_t commit_to = MIN2(align_up(new_committed_words, Settings::commit_granule_words()), word_size()); + + assert(commit_from >= used_words(), "Sanity"); + assert(commit_to <= word_size(), "Sanity"); + + if (commit_to > commit_from) { + log_debug(metaspace)("Chunk " METACHUNK_FORMAT ": attempting to move commit line to " + SIZE_FORMAT " words.", METACHUNK_FORMAT_ARGS(this), commit_to); + + if (!_vsnode->ensure_range_is_committed(base() + commit_from, commit_to - commit_from)) { + DEBUG_ONLY(verify(true);) + return false; + } + } + + // Remember how far we have committed. + _committed_words = commit_to; + + DEBUG_ONLY(verify(true);) + + return true; + } -// Metachunk methods -Metachunk::Metachunk(ChunkIndex chunktype, bool is_class, size_t word_size, - VirtualSpaceNode* container) - : Metabase(word_size), - _container(container), - _top(NULL), - _sentinel(CHUNK_SENTINEL), - _chunk_type(chunktype), - _is_class(is_class), - _origin(origin_normal), - _use_count(0) -{ - _top = initial_top(); - set_is_tagged_free(false); -#ifdef ASSERT - mangle(uninitMetaWordVal); - verify(); -#endif +// Ensure that chunk is committed up to at least new_committed_words words. +// Fails if we hit a commit limit. +bool Metachunk::ensure_committed(size_t new_committed_words) { + + bool rc = true; + + if (new_committed_words > committed_words()) { + MutexLocker cl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); + rc = commit_up_to(new_committed_words); + } + + return rc; + } -MetaWord* Metachunk::allocate(size_t word_size) { - MetaWord* result = NULL; - // If available, bump the pointer to allocate. - if (free_word_size() >= word_size) { - result = _top; - _top = _top + word_size; +bool Metachunk::ensure_committed_locked(size_t new_committed_words) { + + // the .._locked() variant should be called if we own the lock already. + assert_lock_strong(MetaspaceExpand_lock); + + bool rc = true; + + if (new_committed_words > committed_words()) { + rc = commit_up_to(new_committed_words); } - return result; + + return rc; + } -// _bottom points to the start of the chunk including the overhead. -size_t Metachunk::used_word_size() const { - return pointer_delta(_top, bottom(), sizeof(MetaWord)); +// 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 Metachunk::uncommit() { + MutexLocker cl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); + uncommit_locked(); } -size_t Metachunk::free_word_size() const { - return pointer_delta(end(), _top, sizeof(MetaWord)); +void Metachunk::uncommit_locked() { + // Only uncommit chunks which are free, have no used words set (extra precaution) and are equal or larger in size than a single commit granule. + assert_lock_strong(MetaspaceExpand_lock); + assert(_state == state_free && _used_words == 0 && word_size() >= Settings::commit_granule_words(), + "Only free chunks equal or larger than commit granule size can be uncommitted " + "(chunk " METACHUNK_FULL_FORMAT ").", METACHUNK_FULL_FORMAT_ARGS(this)); + if (word_size() >= Settings::commit_granule_words()) { + _vsnode->uncommit_range(base(), word_size()); + _committed_words = 0; + } +} +void Metachunk::set_committed_words(size_t v) { + // Set committed words. Since we know that we only commit whole commit granules, we can round up v here. + v = MIN2(align_up(v, Settings::commit_granule_words()), word_size()); + _committed_words = v; } -void Metachunk::print_on(outputStream* st) const { - st->print_cr("Metachunk:" - " bottom " PTR_FORMAT " top " PTR_FORMAT - " end " PTR_FORMAT " size " SIZE_FORMAT " (%s)", - p2i(bottom()), p2i(_top), p2i(end()), word_size(), - chunk_size_name(get_chunk_type())); - if (Verbose) { - st->print_cr(" used " SIZE_FORMAT " free " SIZE_FORMAT, - used_word_size(), free_word_size()); +// Allocate word_size words from this chunk. +// +// May cause memory to be committed. That may fail if we hit a commit limit. In that case, +// NULL is returned and p_did_hit_commit_limit will be set to true. +// If the remainder portion of the chunk was too small to hold the allocation, +// NULL is returned and p_did_hit_commit_limit will be set to false. +MetaWord* Metachunk::allocate(size_t request_word_size, bool* p_did_hit_commit_limit) { + + assert_is_aligned(request_word_size, allocation_alignment_words); + + log_trace(metaspace)("Chunk " METACHUNK_FULL_FORMAT ": allocating " SIZE_FORMAT " words.", + METACHUNK_FULL_FORMAT_ARGS(this), request_word_size); + + assert(committed_words() <= word_size(), "Sanity"); + + if (free_below_committed_words() < request_word_size) { + + // We may need to expand the comitted area... + if (free_words() < request_word_size) { + // ... but cannot do this since we ran out of space. + *p_did_hit_commit_limit = false; + log_trace(metaspace)("Chunk " METACHUNK_FULL_FORMAT ": .. does not fit (remaining space: " + SIZE_FORMAT " words).", METACHUNK_FULL_FORMAT_ARGS(this), free_words()); + return NULL; + } + + log_trace(metaspace)("Chunk " METACHUNK_FULL_FORMAT ": .. attempting to increase committed range.", + METACHUNK_FULL_FORMAT_ARGS(this)); + + if (ensure_committed(used_words() + request_word_size) == false) { + + // Commit failed. We may have hit the commit limit or the gc threshold. + *p_did_hit_commit_limit = true; + log_trace(metaspace)("Chunk " METACHUNK_FULL_FORMAT ": .. failed, we hit a limit.", + METACHUNK_FULL_FORMAT_ARGS(this)); + return NULL; + + } + + } + + assert(committed_words() >= request_word_size, "Sanity"); + + MetaWord* const p = top(); + + _used_words += request_word_size; + + return p; + +} + +// Given a memory range which may or may not have been allocated from this chunk, attempt +// to roll its allocation back. This can work if this is the very last allocation we did +// from this chunk, in which case we just lower the top pointer again. +// Returns true if this succeeded, false if it failed. +bool Metachunk::attempt_rollback_allocation(MetaWord* p, size_t word_size) { + assert(p != NULL && word_size > 0, "Sanity"); + assert(is_in_use() && base() != NULL, "Sanity"); + + // Is this allocation at the top? + if (used_words() >= word_size && + base() + (used_words() - word_size) == p) { + log_trace(metaspace)("Chunk " METACHUNK_FULL_FORMAT ": rolling back allocation...", + METACHUNK_FULL_FORMAT_ARGS(this)); + _used_words -= word_size; + log_trace(metaspace)("Chunk " METACHUNK_FULL_FORMAT ": rolled back allocation.", + METACHUNK_FULL_FORMAT_ARGS(this)); + DEBUG_ONLY(verify(false);) + + return true; + + } + + return false; +} + + +#ifdef ASSERT + +// Zap this structure. +void Metachunk::zap_header(uint8_t c) { + memset(this, c, sizeof(Metachunk)); +} + +void Metachunk::fill_with_pattern(MetaWord pattern, size_t word_size) { + assert(word_size <= committed_words(), "Sanity"); + for (size_t l = 0; l < word_size; l ++) { + _base[l] = pattern; } } -#ifdef ASSERT -void Metachunk::mangle(juint word_value) { - // Overwrite the payload of the chunk and not the links that - // maintain list of chunks. - HeapWord* start = (HeapWord*)initial_top(); - size_t size = word_size() - overhead(); - Copy::fill_to_words(start, size, word_value); +void Metachunk::check_pattern(MetaWord pattern, size_t word_size) { + assert(word_size <= committed_words(), "Sanity"); + for (size_t l = 0; l < word_size; l ++) { + assert(_base[l] == pattern, + "chunk " METACHUNK_FULL_FORMAT ": pattern change at " PTR_FORMAT ": expected " UINTX_FORMAT " but got " UINTX_FORMAT ".", + METACHUNK_FULL_FORMAT_ARGS(this), p2i(_base + l), (uintx)pattern, (uintx)_base[l]); + } } -void Metachunk::verify() const { - assert(is_valid_sentinel(), "Chunk " PTR_FORMAT ": sentinel invalid", p2i(this)); - const ChunkIndex chunk_type = get_chunk_type(); - assert(is_valid_chunktype(chunk_type), "Chunk " PTR_FORMAT ": Invalid chunk type.", p2i(this)); - if (chunk_type != HumongousIndex) { - assert(word_size() == get_size_for_nonhumongous_chunktype(chunk_type, is_class()), - "Chunk " PTR_FORMAT ": wordsize " SIZE_FORMAT " does not fit chunk type %s.", - p2i(this), word_size(), chunk_size_name(chunk_type)); +volatile MetaWord dummy = 0; + +void Metachunk::verify(bool slow) const { + + assert(!is_dead(), "dead chunk."); + + // Note: only call this on a life Metachunk. + chklvl::check_valid_level(level()); + + assert(base() != NULL, "No base ptr"); + assert(committed_words() >= used_words(), + "mismatch: committed: " SIZE_FORMAT ", used: " SIZE_FORMAT ".", + committed_words(), used_words()); + assert(word_size() >= committed_words(), + "mismatch: word_size: " SIZE_FORMAT ", committed: " SIZE_FORMAT ".", + word_size(), committed_words()); + + // Test base pointer + assert(vsnode() != NULL, "No space"); + vsnode()->check_pointer(base()); + assert(base() != NULL, "Base pointer NULL"); + + // Neighbors shall be adjacent to us... + if (prev_in_vs() != NULL) { + assert(prev_in_vs()->end() == base() && + prev_in_vs()->next_in_vs() == this, + "Chunk " METACHUNK_FORMAT ": broken link to left neighbor: " METACHUNK_FORMAT ".", + METACHUNK_FORMAT_ARGS(this), METACHUNK_FORMAT_ARGS(prev_in_vs())); } - assert(is_valid_chunkorigin(get_origin()), "Chunk " PTR_FORMAT ": Invalid chunk origin.", p2i(this)); - assert(bottom() <= _top && _top <= (MetaWord*)end(), - "Chunk " PTR_FORMAT ": Chunk top out of chunk bounds.", p2i(this)); - // For non-humongous chunks, starting address shall be aligned - // to its chunk size. Humongous chunks start address is - // aligned to specialized chunk size. - const size_t required_alignment = - (chunk_type != HumongousIndex ? word_size() : get_size_for_nonhumongous_chunktype(SpecializedIndex, is_class())) * sizeof(MetaWord); - assert(is_aligned((address)this, required_alignment), - "Chunk " PTR_FORMAT ": (size " SIZE_FORMAT ") not aligned to " SIZE_FORMAT ".", - p2i(this), word_size() * sizeof(MetaWord), required_alignment); + if (next_in_vs() != NULL) { + assert(end() == next_in_vs()->base() && + next_in_vs()->prev_in_vs() == this, + "Chunk " METACHUNK_FORMAT ": broken link to right neighbor: " METACHUNK_FORMAT ".", + METACHUNK_FORMAT_ARGS(this), METACHUNK_FORMAT_ARGS(next_in_vs())); + } + + // Starting address shall be aligned to chunk size. + const size_t required_alignment = word_size() * sizeof(MetaWord); + assert_is_aligned(base(), required_alignment); + + if (!is_root_chunk()) { + + assert(next_in_vs() != NULL || prev_in_vs() != NULL, + "A non-root chunk should have neighbors (chunk @" PTR_FORMAT + ", base " PTR_FORMAT ", level " CHKLVL_FORMAT ".", + p2i(this), p2i(base()), level()); + + // check buddy. Note: the chunk following us or preceeding us may + // be our buddy or a splintered part of it. + Metachunk* buddy = is_leader() ? next_in_vs() : prev_in_vs(); + + assert(buddy != NULL, "Missing neighbor."); + assert(!buddy->is_dead(), "buddy dead."); + + // This neighbor is either or buddy (same level) or a splinter of our buddy - hence + // the level can never be smaller (larger chunk size). + assert(buddy->level() >= level(), "Wrong level."); + if (buddy->level() == level()) { + + // we have a direct, unsplintered buddy. + assert(buddy->is_leader() == !is_leader(), "Only one chunk can be leader in a pair"); + + // When direct buddies are neighbors, one or both should be in use, otherwise they should + // have been merged. + + // Since we call verify() from internal functions where we are about to merge or just did split, + // do not test this. + // assert(buddy->is_in_use() || is_in_use(), "incomplete merging?"); + + if (is_leader()) { + assert(buddy->base() == end(), "Sanity"); + assert(is_aligned(base(), word_size() * 2 * BytesPerWord), "Sanity"); + } else { + assert(buddy->end() == base(), "Sanity"); + assert(is_aligned(buddy->base(), word_size() * 2 * BytesPerWord), "Sanity"); + } + } else { + // Buddy, but splintered, and this is a part of it. + if (is_leader()) { + assert(buddy->base() == end(), "Sanity"); + } else { + assert(buddy->end() > (base() - word_size()), "Sanity"); + } + } + } + + // If slow, test the committed area + if (slow && _committed_words > 0) { + for (const MetaWord* p = _base; p < _base + _committed_words; p += os::vm_page_size()) { + dummy = *p; + } + dummy = *(_base + _committed_words - 1); + } + +} +#endif // ASSERT + +void Metachunk::print_on(outputStream* st) const { + + // Note: must also work with invalid/random data. + st->print("Chunk @" PTR_FORMAT ", state %c, base " PTR_FORMAT ", " + "level " CHKLVL_FORMAT " (" SIZE_FORMAT " words), " + "used " SIZE_FORMAT " words, committed " SIZE_FORMAT " words.", + p2i(this), get_state_char(), p2i(base()), level(), + (chklvl::is_valid_level(level()) ? chklvl::word_size_for_level(level()) : 0), + used_words(), committed_words()); + +} + +///////////////////////////////////7 +// MetachunkList + +#ifdef ASSERT + +bool MetachunkList::contains(const Metachunk* c) const { + for (Metachunk* c2 = first(); c2 != NULL; c2 = c2->next()) { + if (c == c2) { + return true; + } + } + return false; +} + +void MetachunkList::verify(bool slow) const { + int num = 0; + const Metachunk* last_c = NULL; + for (const Metachunk* c = first(); c != NULL; c = c->next()) { + num ++; + assert(c->prev() == last_c, + "Broken link to predecessor. Chunk " METACHUNK_FULL_FORMAT ".", + METACHUNK_FULL_FORMAT_ARGS(c)); + if (slow) { + c->verify(false); + } + last_c = c; + } + _num.check(num); } #endif // ASSERT -// Helper, returns a descriptive name for the given index. -const char* chunk_size_name(ChunkIndex index) { - switch (index) { - case SpecializedIndex: - return "specialized"; - case SmallIndex: - return "small"; - case MediumIndex: - return "medium"; - case HumongousIndex: - return "humongous"; - default: - return "Invalid index"; +void MetachunkList::print_on(outputStream* st) const { + + if (_num.get() > 0) { + for (const Metachunk* c = first(); c != NULL; c = c->next()) { + st->print(" - <"); + c->print_on(st); + st->print(">"); + } + st->print(" - total : %d chunks.", _num.get()); + } else { + st->print("empty"); } + } +///////////////////////////////////7 +// MetachunkListCluster + #ifdef ASSERT -void do_verify_chunk(Metachunk* chunk) { - guarantee(chunk != NULL, "Sanity"); - // Verify chunk itself; then verify that it is consistent with the - // occupany map of its containing node. - chunk->verify(); - VirtualSpaceNode* const vsn = chunk->container(); - OccupancyMap* const ocmap = vsn->occupancy_map(); - ocmap->verify_for_chunk(chunk); + +bool MetachunkListCluster::contains(const Metachunk* c) const { + for (chklvl_t l = chklvl::LOWEST_CHUNK_LEVEL; l <= chklvl::HIGHEST_CHUNK_LEVEL; l ++) { + if (list_for_level(l)->contains(c)) { + return true; + } + } + return false; } -#endif -void do_update_in_use_info_for_chunk(Metachunk* chunk, bool inuse) { - chunk->set_is_tagged_free(!inuse); - OccupancyMap* const ocmap = chunk->container()->occupancy_map(); - ocmap->set_region_in_use((MetaWord*)chunk, chunk->word_size(), inuse); +void MetachunkListCluster::verify(bool slow) const { + + int num = 0; size_t word_size = 0; + + for (chklvl_t l = chklvl::LOWEST_CHUNK_LEVEL; l <= chklvl::HIGHEST_CHUNK_LEVEL; l ++) { + + // Check, for each chunk in this list, exclusivity. + for (const Metachunk* c = first_at_level(l); c != NULL; c = c->next()) { + assert(c->level() == l, "Chunk in wrong list."); + } + + // Check each list. + list_for_level(l)->verify(slow); + + num += list_for_level(l)->size(); + word_size += list_for_level(l)->size() * chklvl::word_size_for_level(l); + } + _total_num_chunks.check(num); + _total_word_size.check(word_size); + +} +#endif // ASSERT + +void MetachunkListCluster::print_on(outputStream* st) const { + + for (chklvl_t l = chklvl::LOWEST_CHUNK_LEVEL; l <= chklvl::HIGHEST_CHUNK_LEVEL; l ++) { + st->print("-- List[" CHKLVL_FORMAT "]: ", l); + list_for_level(l)->print_on(st); + st->cr(); + } + st->print_cr("total chunks: %d, total word size: " SIZE_FORMAT ".", _total_num_chunks.get(), _total_word_size.get()); + } } // namespace metaspace diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/metachunk.hpp --- a/src/hotspot/share/memory/metaspace/metachunk.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace/metachunk.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -1,5 +1,6 @@ /* - * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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 @@ -24,12 +25,14 @@ #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 { @@ -39,134 +42,369 @@ // 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 | +// | | | +// | | | | size (aka capacity) +// | | | | +// | ----------- | <- top -- + | +// | | | | +// | | | used | +// +--------------+ <- start -- + -- + -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, +// Note: this is a chunk **descriptor**. The real Payload area lives in metaspace, +// this class lives somewhere else. +class Metachunk { - origin_minimum = origin_normal, - origin_maximum = origin_split, - origins_count = origin_maximum + 1 -}; + // start of chunk memory; NULL if dead. + MetaWord* _base; -inline bool is_valid_chunkorigin(ChunkOrigin origin) { - return origin == origin_normal || - origin == origin_pad || - origin == origin_leftover || - origin == origin_merge || - origin == origin_split; -} + // Used words. + size_t _used_words; -class Metachunk : public Metabase { + // Guaranteed-to-be-committed-words, counted from base + // (This is a performance optimization. The underlying VirtualSpaceNode knows + // which granules are committed; but we want to avoid asking it unnecessarily + // in Metachunk::allocate(), so we keep a limit until which we are guaranteed + // to have committed memory under us.) + size_t _committed_words; - friend class ::MetachunkTest; + chklvl_t _level; // aka size. - // The VirtualSpaceNode containing this chunk. - VirtualSpaceNode* const _container; + // state_free: free, owned by ChunkManager + // state_in_use: in-use, owned by SpaceManager + // 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; - // Current allocation top. - MetaWord* _top; + // We need unfortunately a back link to the virtual space node + // for splitting and merging nodes. + VirtualSpaceNode* _vsnode; - // A 32bit sentinel for debugging purposes. - enum { CHUNK_SENTINEL = 0x4d4554EF, // "MET" - CHUNK_SENTINEL_INVALID = 0xFEEEEEEF - }; - uint32_t _sentinel; + // A chunk header is kept in a list: + // - in the list of used chunks inside a SpaceManager, if it is in use + // - in the list of free chunks inside a ChunkManager, if it is free + // - 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; - 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; + // Furthermore, we keep, per chunk, information about the neighboring chunks. + // This is needed to split and merge chunks. + Metachunk* _prev_in_vs; + Metachunk* _next_in_vs; - ChunkOrigin _origin; - int _use_count; + MetaWord* top() const { return base() + _used_words; } - MetaWord* initial_top() const { return (MetaWord*)this + overhead(); } - MetaWord* top() const { return _top; } + // Commit uncommitted section of the chunk. + // Fails if we hit a commit limit. + bool commit_up_to(size_t new_committed_words); - 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. +public: - // Alignment of each allocation in the chunks. - static size_t object_alignment(); + Metachunk() + : _base(NULL), + _used_words(0), + _committed_words(0), + _level(chklvl::ROOT_CHUNK_LEVEL), + _state(state_free), + _vsnode(NULL), + _prev(NULL), _next(NULL), + _prev_in_vs(NULL), _next_in_vs(NULL) + {} - // Size of the Metachunk header, in words, including alignment. - static size_t overhead(); + size_t word_size() const { return chklvl::word_size_for_level(_level); } - Metachunk(ChunkIndex chunktype, bool is_class, size_t word_size, VirtualSpaceNode* container); + MetaWord* base() const { return _base; } +// void set_base(MetaWord* p) { _base = p; } + MetaWord* end() const { return base() + word_size(); } - MetaWord* allocate(size_t 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; } - VirtualSpaceNode* container() const { return _container; } + DEBUG_ONLY(bool in_list() const { return _prev != NULL || _next != NULL; }) - MetaWord* bottom() const { return (MetaWord*) this; } + // Physical neighbors wiring + void set_prev_in_vs(Metachunk* c) { _prev_in_vs = c; } + Metachunk* prev_in_vs() const { return _prev_in_vs; } + void set_next_in_vs(Metachunk* c) { _next_in_vs = c; } + Metachunk* next_in_vs() const { return _next_in_vs; } - // 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(); } + 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; } - // 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; + // Return a single char presentation of the state ('f', 'u', 'd') + char get_state_char() const; - bool is_tagged_free() { return _is_tagged_free; } - void set_is_tagged_free(bool v) { _is_tagged_free = v; } + void inc_level() { _level ++; DEBUG_ONLY(chklvl::is_valid_level(_level);) } + void dec_level() { _level --; DEBUG_ONLY(chklvl::is_valid_level(_level);) } +// void set_level(chklvl_t v) { _level = v; DEBUG_ONLY(chklvl::is_valid_level(_level);) } + chklvl_t level() const { return _level; } - bool contains(const void* ptr) { return bottom() <= ptr && ptr < _top; } + // Convenience functions for extreme levels. + bool is_root_chunk() const { return chklvl::ROOT_CHUNK_LEVEL == _level; } + bool is_leaf_chunk() const { return chklvl::HIGHEST_CHUNK_LEVEL == _level; } + + VirtualSpaceNode* vsnode() const { return _vsnode; } +// void set_vsnode(VirtualSpaceNode* n) { _vsnode = n; } + + 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()); } + + // 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(); + + // Alignment of an allocation. + static const size_t allocation_alignment_bytes = 8; + static const size_t allocation_alignment_words = allocation_alignment_bytes / BytesPerWord; + + // Allocation from a chunk + + // Allocate word_size words from this chunk (word_size must be aligned to + // allocation_alignment_words). + // + // May cause memory to be committed. That may fail if we hit a commit limit. In that case, + // NULL is returned and p_did_hit_commit_limit will be set to true. + // If the remainder portion of the chunk was too small to hold the allocation, + // NULL is returned and p_did_hit_commit_limit will be set to false. + MetaWord* allocate(size_t net_word_size, bool* p_did_hit_commit_limit); + + // Given a memory range which may or may not have been allocated from this chunk, attempt + // to roll its allocation back. This can work if this is the very last allocation we did + // from this chunk, in which case we just lower the top pointer again. + // Returns true if this succeeded, false if it failed. + bool attempt_rollback_allocation(MetaWord* p, size_t word_size); + + // Initialize structure for reuse. + void initialize(VirtualSpaceNode* node, MetaWord* base, chklvl_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. + // Must not be called for root chunks. + bool is_leader() const { + assert(!is_root_chunk(), "Root chunks have no buddy."); + // I am sure this can be done smarter... + return is_aligned(base(), chklvl::word_size_for_level(level() - 1) * BytesPerWord); + } + + //// Debug stuff //// +#ifdef ASSERT + void verify(bool slow) 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 pointer points into the used area of this chunk. + bool is_valid_pointer(const MetaWord* p) const { + return base() <= p && p < 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 ++; } +// 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() - ChunkOrigin get_origin() const { return _origin; } - void set_origin(ChunkOrigin orig) { _origin = orig; } +#define METACHUNK_FULL_FORMAT "@" PTR_FORMAT ", %c, base " PTR_FORMAT ", level " CHKLVL_FORMAT " (" SIZE_FORMAT "), used: " SIZE_FORMAT ", committed: " 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() - ChunkIndex get_chunk_type() const { return _chunk_type; } - bool is_class() const { return _is_class; } +///////// +// A list of Metachunks. +class MetachunkList { - DEBUG_ONLY(void mangle(juint word_value);) - DEBUG_ONLY(void verify() const;) + Metachunk* _first; + + // Number of chunks + IntCounter _num; + +public: + + MetachunkList() : _first(NULL), _num() {} + + Metachunk* first() const { return _first; } + int size() const { return _num.get(); } + + void add(Metachunk* c) { + assert(!c->in_list(), "Chunk must not be in a list"); + if (_first) { + _first->set_prev(c); + } + c->set_next(_first); + c->set_prev(NULL); + _first = c; + _num.increment(); + } + + // Remove first node unless empty. Returns node or NULL. + Metachunk* remove_first() { + Metachunk* c = _first; + if (c != NULL) { + assert(c->prev() == NULL, "Sanity"); + Metachunk* c2 = c->next(); + if (c2 != NULL) { + c2->set_prev(NULL); + } + _first = c2; + c->set_next(NULL); + _num.decrement(); + } + return c; + } + + // Remove given chunk from list. List must contain that chunk. + void remove(Metachunk* c) { + assert(contains(c), "List does not contain this chunk"); + if (_first == c) { + _first = c->next(); + if (_first != NULL) { + _first->set_prev(NULL); + } + } else { + if (c->next() != NULL) { + c->next()->set_prev(c->prev()); + } + if (c->prev() != NULL) { + c->prev()->set_next(c->next()); + } + } + c->set_prev(NULL); + c->set_next(NULL); + _num.decrement(); + } + +#ifdef ASSERT + bool contains(const Metachunk* c) const; + void verify(bool slow) const; +#endif + + // Returns size, in words, of committed space of all chunks in this list. + // Note: walks list. + size_t committed_word_size() const { + size_t l = 0; + for (const Metachunk* c = _first; c != NULL; c = c->next()) { + l += c->committed_words(); + } + return l; + } + + void print_on(outputStream* st) const; }; +////////////////// +// A cluster of Metachunk Lists, one for each chunk level, together with associated counters. +class MetachunkListCluster { -// Helper function that does a bunch of checks for a chunk. -DEBUG_ONLY(void do_verify_chunk(Metachunk* chunk);) + MetachunkList _lists[chklvl::NUM_CHUNK_LEVELS]; + SizeCounter _total_word_size; + IntCounter _total_num_chunks; -// 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); + const MetachunkList* list_for_level(chklvl_t lvl) const { DEBUG_ONLY(chklvl::check_valid_level(lvl)); return _lists + lvl; } + MetachunkList* list_for_level(chklvl_t lvl) { DEBUG_ONLY(chklvl::check_valid_level(lvl)); return _lists + lvl; } + + const MetachunkList* list_for_chunk(const Metachunk* c) const { return list_for_level(c->level()); } + MetachunkList* list_for_chunk(const Metachunk* c) { return list_for_level(c->level()); } + +public: + + const Metachunk* first_at_level(chklvl_t lvl) const { return list_for_level(lvl)->first(); } + Metachunk* first_at_level(chklvl_t lvl) { return list_for_level(lvl)->first(); } + + // Remove given chunk from its list. List must contain that chunk. + void remove(Metachunk* c) { + list_for_chunk(c)->remove(c); + _total_word_size.decrement_by(c->word_size()); + _total_num_chunks.decrement(); + } + + // Remove first node unless empty. Returns node or NULL. + Metachunk* remove_first(chklvl_t lvl) { + Metachunk* c = list_for_level(lvl)->remove_first(); + if (c != NULL) { + _total_word_size.decrement_by(c->word_size()); + _total_num_chunks.decrement(); + } + return c; + } + + void add(Metachunk* c) { + list_for_chunk(c)->add(c); + _total_word_size.increment_by(c->word_size()); + _total_num_chunks.increment(); + } + + // Returns number of chunks for a given level. + int num_chunks_at_level(chklvl_t lvl) const { + return list_for_level(lvl)->size(); + } + + // Returns number of chunks for a given level. + size_t committed_word_size_at_level(chklvl_t lvl) const { + return list_for_level(lvl)->committed_word_size(); + } + + // Returs word size, in total, of all chunks in all lists. + size_t total_word_size() const { return _total_word_size.get(); } + + // Returns number of chunks in total + int total_num_chunks() const { return _total_num_chunks.get(); } + + // Returns size, in words, of committed space of all chunks in all list. + // Note: walks lists. + size_t total_committed_word_size() const { + size_t sum = 0; + for (chklvl_t l = chklvl::LOWEST_CHUNK_LEVEL; l <= chklvl::HIGHEST_CHUNK_LEVEL; l ++) { + sum += list_for_level(l)->committed_word_size(); + } + return sum; + } + + DEBUG_ONLY(void verify(bool slow) const;) + DEBUG_ONLY(bool contains(const Metachunk* c) const;) + + void print_on(outputStream* st) const; + +}; + } // namespace metaspace diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/metaspaceCommon.cpp --- a/src/hotspot/share/memory/metaspace/metaspaceCommon.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace/metaspaceCommon.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -1,4 +1,5 @@ /* + * Copyright (c) 2018, 2019, SAP SE. All rights reserved. * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -32,8 +33,6 @@ namespace metaspace { -DEBUG_ONLY(internal_statistics_t g_internal_statistics;) - // Print a size, in words, scaled. void print_scaled_words(outputStream* st, size_t word_size, size_t scale, int width) { print_human_readable_size(st, word_size * sizeof(MetaWord), scale, width); @@ -47,6 +46,19 @@ st->print(")"); } +static const char* display_unit_for_scale(size_t scale) { + const char* s = NULL; + switch(scale) { + case 1: s = "bytes"; break; + case BytesPerWord: s = "words"; break; + case K: s = "KB"; break; + case M: s = "MB"; break; + case G: s = "GB"; break; + default: + ShouldNotReachHere(); + } + return s; +} // Print a human readable size. // byte_size: size, in bytes, to be printed. @@ -74,36 +86,45 @@ } #ifdef ASSERT - assert(scale == 1 || scale == BytesPerWord || scale == K || scale == M || scale == G, "Invalid scale"); + assert(scale == 1 || scale == BytesPerWord || + scale == K || scale == M || scale == G, "Invalid scale"); // Special case: printing wordsize should only be done with word-sized values if (scale == BytesPerWord) { assert(byte_size % BytesPerWord == 0, "not word sized"); } #endif - if (scale == 1) { - st->print("%*" PRIuPTR " bytes", width, byte_size); - } else if (scale == BytesPerWord) { - st->print("%*" PRIuPTR " words", width, byte_size / BytesPerWord); + if (width == -1) { + if (scale == 1) { + st->print(SIZE_FORMAT " bytes", byte_size); + } else if (scale == BytesPerWord) { + st->print(SIZE_FORMAT " words", byte_size / BytesPerWord); + } else { + const char* display_unit = display_unit_for_scale(scale); + float display_value = (float) byte_size / scale; + // Prevent very small but non-null values showing up as 0.00. + if (byte_size > 0 && display_value < 0.01f) { + st->print("<0.01 %s", display_unit); + } else { + st->print("%.2f %s", display_value, display_unit); + } + } } else { - const char* display_unit = ""; - switch(scale) { - case 1: display_unit = "bytes"; break; - case BytesPerWord: display_unit = "words"; break; - case K: display_unit = "KB"; break; - case M: display_unit = "MB"; break; - case G: display_unit = "GB"; break; - default: - ShouldNotReachHere(); - } - float display_value = (float) byte_size / scale; - // Since we use width to display a number with two trailing digits, increase it a bit. - width += 3; - // Prevent very small but non-null values showing up as 0.00. - if (byte_size > 0 && display_value < 0.01f) { - st->print("%*s %s", width, "<0.01", display_unit); + if (scale == 1) { + st->print("%*" PRIuPTR " bytes", width, byte_size); + } else if (scale == BytesPerWord) { + st->print("%*" PRIuPTR " words", width, byte_size / BytesPerWord); } else { - st->print("%*.2f %s", width, display_value, display_unit); + const char* display_unit = display_unit_for_scale(scale); + float display_value = (float) byte_size / scale; + // Since we use width to display a number with two trailing digits, increase it a bit. + width += 3; + // Prevent very small but non-null values showing up as 0.00. + if (byte_size > 0 && display_value < 0.01f) { + st->print("%*s %s", width, "<0.01", display_unit); + } else { + st->print("%*.2f %s", width, display_value, display_unit); + } } } } @@ -130,70 +151,6 @@ } } -// Returns size of this chunk type. -size_t get_size_for_nonhumongous_chunktype(ChunkIndex chunktype, bool is_class) { - assert(is_valid_nonhumongous_chunktype(chunktype), "invalid chunk type."); - size_t size = 0; - if (is_class) { - switch(chunktype) { - case SpecializedIndex: size = ClassSpecializedChunk; break; - case SmallIndex: size = ClassSmallChunk; break; - case MediumIndex: size = ClassMediumChunk; break; - default: - ShouldNotReachHere(); - } - } else { - switch(chunktype) { - case SpecializedIndex: size = SpecializedChunk; break; - case SmallIndex: size = SmallChunk; break; - case MediumIndex: size = MediumChunk; break; - default: - ShouldNotReachHere(); - } - } - return size; -} - -ChunkIndex get_chunk_type_by_size(size_t size, bool is_class) { - if (is_class) { - if (size == ClassSpecializedChunk) { - return SpecializedIndex; - } else if (size == ClassSmallChunk) { - return SmallIndex; - } else if (size == ClassMediumChunk) { - return MediumIndex; - } else if (size > ClassMediumChunk) { - // A valid humongous chunk size is a multiple of the smallest chunk size. - assert(is_aligned(size, ClassSpecializedChunk), "Invalid chunk size"); - return HumongousIndex; - } - } else { - if (size == SpecializedChunk) { - return SpecializedIndex; - } else if (size == SmallChunk) { - return SmallIndex; - } else if (size == MediumChunk) { - return MediumIndex; - } else if (size > MediumChunk) { - // A valid humongous chunk size is a multiple of the smallest chunk size. - assert(is_aligned(size, SpecializedChunk), "Invalid chunk size"); - return HumongousIndex; - } - } - ShouldNotReachHere(); - return (ChunkIndex)-1; -} - -ChunkIndex next_chunk_index(ChunkIndex i) { - assert(i < NumberOfInUseLists, "Out of bound"); - return (ChunkIndex) (i+1); -} - -ChunkIndex prev_chunk_index(ChunkIndex i) { - assert(i > ZeroIndex, "Out of bound"); - return (ChunkIndex) (i-1); -} - const char* loaders_plural(uintx num) { return num == 1 ? "loader" : "loaders"; } diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/metaspaceCommon.hpp --- a/src/hotspot/share/memory/metaspace/metaspaceCommon.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace/metaspaceCommon.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -1,4 +1,5 @@ /* + * Copyright (c) 2018, 2019, SAP SE. All rights reserved. * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -33,14 +34,6 @@ namespace metaspace { -enum ChunkSizes { // in words. - ClassSpecializedChunk = 128, - SpecializedChunk = 128, - ClassSmallChunk = 256, - SmallChunk = 512, - ClassMediumChunk = 4 * K, - MediumChunk = 8 * K -}; // Print a size, in words, scaled. void print_scaled_words(outputStream* st, size_t word_size, size_t scale = 0, int width = -1); @@ -65,80 +58,6 @@ SIZE_FORMAT_HEX " is not aligned to " \ SIZE_FORMAT, (size_t)(uintptr_t)value, (alignment)) -// Internal statistics. -#ifdef ASSERT -struct internal_statistics_t { - // Number of allocations. - uintx num_allocs; - // Number of times a ClassLoaderMetaspace was born... - uintx num_metaspace_births; - // ... and died. - uintx num_metaspace_deaths; - // Number of times VirtualSpaceListNodes were created... - uintx num_vsnodes_created; - // ... and purged. - uintx num_vsnodes_purged; - // Number of times we expanded the committed section of the space. - uintx num_committed_space_expanded; - // Number of deallocations - uintx num_deallocs; - // Number of deallocations triggered from outside ("real" deallocations). - uintx num_external_deallocs; - // Number of times an allocation was satisfied from deallocated blocks. - uintx num_allocs_from_deallocated_blocks; - // Number of times a chunk was added to the freelist - uintx num_chunks_added_to_freelist; - // Number of times a chunk was removed from the freelist - uintx num_chunks_removed_from_freelist; - // Number of chunk merges - uintx num_chunk_merges; - // Number of chunk splits - uintx num_chunk_splits; -}; -extern internal_statistics_t g_internal_statistics; -#endif - -// ChunkIndex defines the type of chunk. -// Chunk types differ by size: specialized < small < medium, chunks -// larger than medium are humongous chunks of varying size. -enum ChunkIndex { - ZeroIndex = 0, - SpecializedIndex = ZeroIndex, - SmallIndex = SpecializedIndex + 1, - MediumIndex = SmallIndex + 1, - HumongousIndex = MediumIndex + 1, - NumberOfFreeLists = 3, - NumberOfInUseLists = 4 -}; - -// Utility functions. -size_t get_size_for_nonhumongous_chunktype(ChunkIndex chunk_type, bool is_class); -ChunkIndex get_chunk_type_by_size(size_t size, bool is_class); - -ChunkIndex next_chunk_index(ChunkIndex i); -ChunkIndex prev_chunk_index(ChunkIndex i); -// Returns a descriptive name for a chunk type. -const char* chunk_size_name(ChunkIndex index); - -// Verify chunk sizes. -inline bool is_valid_chunksize(bool is_class, size_t size) { - const size_t reasonable_maximum_humongous_chunk_size = 1 * G; - return is_aligned(size, sizeof(MetaWord)) && - size < reasonable_maximum_humongous_chunk_size && - is_class ? - (size == ClassSpecializedChunk || size == ClassSmallChunk || size >= ClassMediumChunk) : - (size == SpecializedChunk || size == SmallChunk || size >= MediumChunk); -} - -// Verify chunk type. -inline bool is_valid_chunktype(ChunkIndex index) { - return index == SpecializedIndex || index == SmallIndex || - index == MediumIndex || index == HumongousIndex; -} - -inline bool is_valid_nonhumongous_chunktype(ChunkIndex index) { - return is_valid_chunktype(index) && index != HumongousIndex; -} // Pretty printing helpers const char* classes_plural(uintx num); diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/metaspaceDCmd.cpp --- a/src/hotspot/share/memory/metaspace/metaspaceDCmd.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace/metaspaceDCmd.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -1,6 +1,6 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2018, SAP and/or its affiliates. + * Copyright (c) 2018, 2019 SAP and/or its affiliates. + * Copyright (c) 2018, 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 @@ -23,7 +23,7 @@ * */ #include "precompiled.hpp" -#include "memory/metaspace.hpp" +#include "memory/metaspace/metaspaceReport.hpp" #include "memory/metaspace/metaspaceDCmd.hpp" #include "memory/resourceArea.hpp" #include "services/diagnosticCommand.hpp" @@ -89,12 +89,12 @@ } else { // Full mode. Requires safepoint. int flags = 0; - if (_show_loaders.value()) flags |= MetaspaceUtils::rf_show_loaders; - if (_show_classes.value()) flags |= MetaspaceUtils::rf_show_classes; - if (_by_chunktype.value()) flags |= MetaspaceUtils::rf_break_down_by_chunktype; - if (_by_spacetype.value()) flags |= MetaspaceUtils::rf_break_down_by_spacetype; - if (_show_vslist.value()) flags |= MetaspaceUtils::rf_show_vslist; - if (_show_vsmap.value()) flags |= MetaspaceUtils::rf_show_vsmap; + if (_show_loaders.value()) flags |= MetaspaceReporter::rf_show_loaders; + if (_show_classes.value()) flags |= MetaspaceReporter::rf_show_classes; + if (_by_chunktype.value()) flags |= MetaspaceReporter::rf_break_down_by_chunktype; + if (_by_spacetype.value()) flags |= MetaspaceReporter::rf_break_down_by_spacetype; + if (_show_vslist.value()) flags |= MetaspaceReporter::rf_show_vslist; + if (_show_vsmap.value()) flags |= MetaspaceReporter::rf_show_vsmap; VM_PrintMetadata op(output(), scale, flags); VMThread::execute(&op); } diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/metaspaceDCmd.hpp --- a/src/hotspot/share/memory/metaspace/metaspaceDCmd.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace/metaspaceDCmd.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -1,6 +1,6 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2018, SAP and/or its affiliates. + * Copyright (c) 2018, 2019 SAP and/or its affiliates. + * Copyright (c) 2018, 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 diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/metaspaceEnums.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/metaspaceEnums.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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 "memory/metaspace/metaspaceEnums.hpp" +#include "utilities/debug.hpp" + +namespace metaspace { + +const char* describe_spacetype(MetaspaceType st) { + const char* s = NULL; + switch (st) { + case StandardMetaspaceType: s = "Standard"; break; + case BootMetaspaceType: s = "Boot"; break; + case UnsafeAnonymousMetaspaceType: s = "UnsafeAnonymous"; break; + case ReflectionMetaspaceType: s = "Reflection"; break; + default: ShouldNotReachHere(); + } + return s; +} + +const char* describe_mdtype(MetadataType md) { + const char* s = NULL; + switch (md) { + case NonClassType: s = "nonclass"; break; + case ClassType: s = "class"; break; + default: ShouldNotReachHere(); + } + return s; +} + +} // namespace metaspace + diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/metaspaceEnums.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/metaspaceEnums.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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_MEMORY_METASPACEENUMS_HPP +#define SHARE_MEMORY_METASPACEENUMS_HPP + +#include "utilities/debug.hpp" + +// MetadataType and MetaspaceType, as well as some convenience functions surrounding them. +namespace metaspace { + +/////////////////////// + +enum MetadataType { + ClassType, + NonClassType, + MetadataTypeCount +}; + +inline bool is_class(MetadataType md) { return md == ClassType; } + +inline MetadataType mdtype_from_bool(bool is_class) { return is_class ? ClassType : NonClassType; } + +const char* describe_mdtype(MetadataType md); + +#ifdef ASSERT +inline bool is_valid_mdtype(MetadataType md) { + return (int)md >= 0 && (int)md < MetadataTypeCount; +} +inline void check_valid_mdtype(MetadataType md) { + assert(is_valid_mdtype(md), "Wrong value for MetadataType: %d", (int) md); +} +#endif // ASSERT + +/////////////////////// + +enum MetaspaceType { + ZeroMetaspaceType = 0, + StandardMetaspaceType = ZeroMetaspaceType, + BootMetaspaceType = StandardMetaspaceType + 1, + UnsafeAnonymousMetaspaceType = BootMetaspaceType + 1, + ReflectionMetaspaceType = UnsafeAnonymousMetaspaceType + 1, + MetaspaceTypeCount +}; + +const char* describe_spacetype(MetaspaceType st); + +#ifdef ASSERT +inline bool is_valid_spacetype(MetaspaceType st) { + return (int)st >= 0 && (int)st < MetaspaceTypeCount; +} +inline void check_valid_spacetype(MetaspaceType st) { + assert(is_valid_spacetype(st), "Wrong value for MetaspaceType: %d", (int) st); +} +#endif + +} // namespace metaspace + +#endif // SHARE_MEMORY_METASPACEENUMS_HPP diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/metaspaceReport.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/metaspaceReport.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2018, 2019 SAP and/or its affiliates. + * Copyright (c) 2018, 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 "classfile/classLoaderData.hpp" +#include "classfile/classLoaderDataGraph.hpp" +#include "memory/metaspace/chunkHeaderPool.hpp" +#include "memory/metaspace/chunkManager.hpp" +#include "memory/metaspace/internStat.hpp" +#include "memory/metaspace/metaspaceCommon.hpp" +#include "memory/metaspace/metaspaceEnums.hpp" +#include "memory/metaspace/metaspaceReport.hpp" +#include "memory/metaspace/metaspaceStatistics.hpp" +#include "memory/metaspace/printCLDMetaspaceInfoClosure.hpp" +#include "memory/metaspace/runningCounters.hpp" +#include "memory/metaspace/virtualSpaceList.hpp" +#include "memory/metaspace.hpp" +#include "runtime/os.hpp" + +namespace metaspace { + +static void print_vs(outputStream* out, size_t scale) { + + const size_t reserved_nc = RunningCounters::reserved_words_nonclass(); + const size_t committed_nc = RunningCounters::committed_words_nonclass(); + const int num_nodes_nc = VirtualSpaceList::vslist_nonclass()->num_nodes(); + const size_t reserved_c = RunningCounters::reserved_words_class(); + const size_t committed_c = RunningCounters::committed_words_class(); + const int num_nodes_c = VirtualSpaceList::vslist_class()->num_nodes(); + + if (Metaspace::using_class_space()) { + + out->print(" Non-class space: "); + print_scaled_words(out, reserved_nc, scale, 7); + out->print(" reserved, "); + print_scaled_words_and_percentage(out, committed_nc, reserved_nc, scale, 7); + out->print(" committed, "); + out->print(" %d nodes.", num_nodes_nc); + out->cr(); + out->print(" Class space: "); + print_scaled_words(out, reserved_c, scale, 7); + out->print(" reserved, "); + print_scaled_words_and_percentage(out, committed_c, reserved_c, scale, 7); + out->print(" committed, "); + out->print(" %d nodes.", num_nodes_c); + out->cr(); + out->print(" Both: "); + print_scaled_words(out, reserved_c + reserved_nc, scale, 7); + out->print(" reserved, "); + print_scaled_words_and_percentage(out, committed_c + committed_nc, reserved_c + reserved_nc, scale, 7); + out->print(" committed. "); + out->cr(); + + } else { + assert(committed_c == 0 && reserved_c == 0 && num_nodes_c == 0, "Sanity"); + print_scaled_words(out, reserved_nc, scale, 7); + out->print(" reserved, "); + print_scaled_words_and_percentage(out, committed_nc, reserved_nc, scale, 7); + out->print(" committed, "); + out->print(" %d nodes.", num_nodes_nc); + out->cr(); + } +} + +static void print_settings(outputStream* out, size_t scale) { + out->print("MaxMetaspaceSize: "); + if (MaxMetaspaceSize >= (max_uintx) - (2 * os::vm_page_size())) { + // aka "very big". Default is max_uintx, but due to rounding in arg parsing the real + // value is smaller. + out->print("unlimited"); + } else { + print_human_readable_size(out, MaxMetaspaceSize, scale); + } + out->cr(); + if (Metaspace::using_class_space()) { + out->print("CompressedClassSpaceSize: "); + print_human_readable_size(out, CompressedClassSpaceSize, scale); + } + out->cr(); + out->print("InitialBootClassLoaderMetaspaceSize: "); + print_human_readable_size(out, InitialBootClassLoaderMetaspaceSize, scale); + out->cr(); + Settings::print_on(out); +} + +// This will print out a basic metaspace usage report but +// unlike print_report() is guaranteed not to lock or to walk the CLDG. +void MetaspaceReporter::print_basic_report(outputStream* out, size_t scale) { + + if (!Metaspace::initialized()) { + out->print_cr("Metaspace not yet initialized."); + return; + } + + out->cr(); + out->print_cr("Usage:"); + + if (Metaspace::using_class_space()) { + out->print(" Non-class: "); + } + + // Note: since we want to purely rely on counters, without any locking or walking the CLDG, + // for Usage stats (statistics over in-use chunks) all we can print is the + // used words. We cannot print committed areas, or free/waste areas, of in-use chunks require + // walking. + const size_t used_nc = MetaspaceUtils::used_words(metaspace::NonClassType); + + print_scaled_words(out, used_nc, scale, 5); + out->print(" used."); + out->cr(); + + if (Metaspace::using_class_space()) { + const size_t used_c = MetaspaceUtils::used_words(metaspace::ClassType); + out->print(" Class: "); + print_scaled_words(out, used_c, scale, 5); + out->print(" used."); + out->cr(); + + out->print(" Both: "); + const size_t used = used_nc + used_c; + print_scaled_words(out, used, scale, 5); + out->print(" used."); + out->cr(); + } + + out->cr(); + out->print_cr("Virtual space:"); + + print_vs(out, scale); + + out->cr(); + out->print_cr("Chunk freelists:"); + + if (Metaspace::using_class_space()) { + out->print(" Non-Class: "); + } + print_scaled_words(out, ChunkManager::chunkmanager_nonclass()->total_word_size(), scale); + out->cr(); + if (Metaspace::using_class_space()) { + out->print(" Class: "); + print_scaled_words(out, ChunkManager::chunkmanager_class()->total_word_size(), scale); + out->cr(); + out->print(" Both: "); + print_scaled_words(out, ChunkManager::chunkmanager_nonclass()->total_word_size() + + ChunkManager::chunkmanager_class()->total_word_size(), scale); + out->cr(); + } + + out->cr(); + + // Print basic settings + print_settings(out, scale); + + out->cr(); + +#ifdef ASSERT + out->cr(); + out->print_cr("Internal statistics:"); + out->cr(); + InternalStats::print_on(out); + out->cr(); +#endif +} + +void MetaspaceReporter::print_report(outputStream* out, size_t scale, int flags) { + + if (!Metaspace::initialized()) { + out->print_cr("Metaspace not yet initialized."); + return; + } + + const bool print_loaders = (flags & rf_show_loaders) > 0; + const bool print_classes = (flags & rf_show_classes) > 0; + const bool print_by_chunktype = (flags & rf_break_down_by_chunktype) > 0; + const bool print_by_spacetype = (flags & rf_break_down_by_spacetype) > 0; + + // Some report options require walking the class loader data graph. + metaspace::PrintCLDMetaspaceInfoClosure cl(out, scale, print_loaders, print_classes, print_by_chunktype); + if (print_loaders) { + out->cr(); + out->print_cr("Usage per loader:"); + out->cr(); + } + + ClassLoaderDataGraph::loaded_cld_do(&cl); // collect data and optionally print + + // Print totals, broken up by space type. + if (print_by_spacetype) { + out->cr(); + out->print_cr("Usage per space type:"); + out->cr(); + for (int space_type = (int)metaspace::ZeroMetaspaceType; + space_type < (int)metaspace::MetaspaceTypeCount; space_type ++) + { + uintx num_loaders = cl._num_loaders_by_spacetype[space_type]; + uintx num_classes = cl._num_classes_by_spacetype[space_type]; + out->print("%s - " UINTX_FORMAT " %s", + describe_spacetype((MetaspaceType)space_type), + num_loaders, loaders_plural(num_loaders)); + if (num_classes > 0) { + out->print(", "); + + print_number_of_classes(out, num_classes, cl._num_classes_shared_by_spacetype[space_type]); + out->print(":"); + cl._stats_by_spacetype[space_type].print_on(out, scale, print_by_chunktype); + } else { + out->print("."); + out->cr(); + } + out->cr(); + } + } + + // Print totals for in-use data: + out->cr(); + { + uintx num_loaders = cl._num_loaders; + out->print("Total Usage - " UINTX_FORMAT " %s, ", + num_loaders, loaders_plural(num_loaders)); + print_number_of_classes(out, cl._num_classes, cl._num_classes_shared); + out->print(":"); + cl._stats_total.print_on(out, scale, print_by_chunktype); + out->cr(); + } + + ///////////////////////////////////////////////// + // -- Print Virtual space. + out->cr(); + out->print_cr("Virtual space:"); + + print_vs(out, scale); + + // -- Print VirtualSpaceList details. + if ((flags & rf_show_vslist) > 0) { + out->cr(); + out->print_cr("Virtual space list%s:", Metaspace::using_class_space() ? "s" : ""); + + if (Metaspace::using_class_space()) { + out->print_cr(" Non-Class:"); + } + VirtualSpaceList::vslist_nonclass()->print_on(out); + out->cr(); + if (Metaspace::using_class_space()) { + out->print_cr(" Class:"); + VirtualSpaceList::vslist_class()->print_on(out); + out->cr(); + } + } + out->cr(); + + // -- Print VirtualSpaceList map. +/* Deactivated for now. + if ((flags & rf_show_vsmap) > 0) { + out->cr(); + out->print_cr("Virtual space map:"); + + if (Metaspace::using_class_space()) { + out->print_cr(" Non-Class:"); + } + Metaspace::space_list()->print_map(out); + if (Metaspace::using_class_space()) { + out->print_cr(" Class:"); + Metaspace::class_space_list()->print_map(out); + } + } + out->cr(); +*/ + + //////////// Freelists (ChunkManager) section /////////////////////////// + + out->cr(); + out->print_cr("Chunk freelist%s:", Metaspace::using_class_space() ? "s" : ""); + + cm_stats_t non_class_cm_stat; + cm_stats_t class_cm_stat; + cm_stats_t total_cm_stat; + + ChunkManager::chunkmanager_nonclass()->add_to_statistics(&non_class_cm_stat); + if (Metaspace::using_class_space()) { + ChunkManager::chunkmanager_nonclass()->add_to_statistics(&non_class_cm_stat); + ChunkManager::chunkmanager_class()->add_to_statistics(&class_cm_stat); + total_cm_stat.add(non_class_cm_stat); + total_cm_stat.add(class_cm_stat); + + out->print_cr(" Non-Class:"); + non_class_cm_stat.print_on(out, scale); + out->cr(); + out->print_cr(" Class:"); + class_cm_stat.print_on(out, scale); + out->cr(); + out->print_cr(" Both:"); + total_cm_stat.print_on(out, scale); + out->cr(); + } else { + ChunkManager::chunkmanager_nonclass()->add_to_statistics(&non_class_cm_stat); + non_class_cm_stat.print_on(out, scale); + out->cr(); + } + + //////////// Waste section /////////////////////////// + // As a convenience, print a summary of common waste. + out->cr(); + out->print("Waste (unused committed space):"); + // For all wastages, print percentages from total. As total use the total size of memory committed for metaspace. + const size_t committed_words = RunningCounters::committed_words(); + + out->print("(percentages refer to total committed size "); + print_scaled_words(out, committed_words, scale); + out->print_cr("):"); + + // Print waste for in-use chunks. + in_use_chunk_stats_t ucs_nonclass = cl._stats_total.sm_stats_nonclass.totals(); + in_use_chunk_stats_t ucs_class = cl._stats_total.sm_stats_class.totals(); + const size_t waste_in_chunks_in_use = ucs_nonclass.waste_words + ucs_class.waste_words; + const size_t free_in_chunks_in_use = ucs_nonclass.free_words + ucs_class.free_words; + + out->print(" Waste in chunks in use: "); + print_scaled_words_and_percentage(out, waste_in_chunks_in_use, committed_words, scale, 6); + out->cr(); + out->print(" Free in chunks in use: "); + print_scaled_words_and_percentage(out, free_in_chunks_in_use, committed_words, scale, 6); + out->cr(); + + // Print waste in free chunks. + const size_t committed_in_free_chunks = total_cm_stat.total_committed_word_size(); + out->print(" In free chunks: "); + print_scaled_words_and_percentage(out, committed_in_free_chunks, committed_words, scale, 6); + out->cr(); + + // Print waste in deallocated blocks. + const uintx free_blocks_num = + cl._stats_total.sm_stats_nonclass.free_blocks_num + + cl._stats_total.sm_stats_class.free_blocks_num; + const size_t free_blocks_cap_words = + cl._stats_total.sm_stats_nonclass.free_blocks_word_size + + cl._stats_total.sm_stats_class.free_blocks_word_size; + out->print("Deallocated from chunks in use: "); + print_scaled_words_and_percentage(out, free_blocks_cap_words, committed_words, scale, 6); + out->print(" (" UINTX_FORMAT " blocks)", free_blocks_num); + out->cr(); + + // Print total waste. + const size_t total_waste = + waste_in_chunks_in_use + + free_in_chunks_in_use + + committed_in_free_chunks + + free_blocks_cap_words; + out->print(" -total-: "); + print_scaled_words_and_percentage(out, total_waste, committed_words, scale, 6); + out->cr(); + + // Also print chunk header pool size. + out->cr(); + out->print("chunk header pool: %u items, ", ChunkHeaderPool::pool().used()); + print_scaled_words(out, ChunkHeaderPool::pool().memory_footprint_words(), scale); + out->print("."); + out->cr(); + + // Print internal statistics +#ifdef ASSERT + out->cr(); + out->print_cr("Internal statistics:"); + out->cr(); + InternalStats::print_on(out); + out->cr(); +#endif + + // Print some interesting settings + out->cr(); + out->print_cr("Settings:"); + print_settings(out, scale); + + out->cr(); + out->cr(); + +} // MetaspaceUtils::print_report() + +} // namespace metaspace + diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/metaspaceReport.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/metaspaceReport.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2018, 2019 SAP and/or its affiliates. + * Copyright (c) 2018, 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_MEMORY_METASPACE_METASPACEREPORT_HPP +#define SHARE_MEMORY_METASPACE_METASPACEREPORT_HPP + +#include "memory/allocation.hpp" + +namespace metaspace { + +class MetaspaceReporter : public AllStatic { +public: + + // Flags for print_report(). + enum ReportFlag { + // Show usage by class loader. + rf_show_loaders = (1 << 0), + // Breaks report down by chunk type (small, medium, ...). + rf_break_down_by_chunktype = (1 << 1), + // Breaks report down by space type (anonymous, reflection, ...). + rf_break_down_by_spacetype = (1 << 2), + // Print details about the underlying virtual spaces. + rf_show_vslist = (1 << 3), + // Print metaspace map. + rf_show_vsmap = (1 << 4), + // If show_loaders: show loaded classes for each loader. + rf_show_classes = (1 << 5) + }; + + // This will print out a basic metaspace usage report but + // unlike print_report() is guaranteed not to lock or to walk the CLDG. + static void print_basic_report(outputStream* st, size_t scale); + + // Prints a report about the current metaspace state. + // Optional parts can be enabled via flags. + // Function will walk the CLDG and will lock the expand lock; if that is not + // convenient, use print_basic_report() instead. + static void print_report(outputStream* out, size_t scale = 0, int flags = 0); + +}; + +} // namespace metaspace + +#endif // SHARE_MEMORY_METASPACE_METASPACEREPORT_HPP diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/metaspaceSizesSnapshot.cpp --- a/src/hotspot/share/memory/metaspace/metaspaceSizesSnapshot.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace/metaspaceSizesSnapshot.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -26,6 +26,7 @@ #include "precompiled.hpp" #include "memory/metaspace.hpp" +#include "memory/metaspace/metaspaceEnums.hpp" #include "memory/metaspace/metaspaceSizesSnapshot.hpp" namespace metaspace { @@ -33,9 +34,9 @@ MetaspaceSizesSnapshot::MetaspaceSizesSnapshot() : _used(MetaspaceUtils::used_bytes()), _committed(MetaspaceUtils::committed_bytes()), - _non_class_used(MetaspaceUtils::used_bytes(Metaspace::NonClassType)), - _non_class_committed(MetaspaceUtils::committed_bytes(Metaspace::NonClassType)), - _class_used(MetaspaceUtils::used_bytes(Metaspace::ClassType)), - _class_committed(MetaspaceUtils::committed_bytes(Metaspace::ClassType)) { } + _non_class_used(MetaspaceUtils::used_bytes(metaspace::NonClassType)), + _non_class_committed(MetaspaceUtils::committed_bytes(metaspace::NonClassType)), + _class_used(MetaspaceUtils::used_bytes(metaspace::ClassType)), + _class_committed(MetaspaceUtils::committed_bytes(metaspace::ClassType)) { } } // namespace metaspace diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/metaspaceStatistics.cpp --- a/src/hotspot/share/memory/metaspace/metaspaceStatistics.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace/metaspaceStatistics.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -1,6 +1,6 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2018 SAP SE. All rights reserved. + * Copyright (c) 2018, 2019 SAP SE. All rights reserved. + * Copyright (c) 2018, 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,177 +24,158 @@ */ #include "precompiled.hpp" -#include "memory/metaspace/metachunk.hpp" + +#include "memory/metaspace/chunkLevel.hpp" #include "memory/metaspace/metaspaceCommon.hpp" #include "memory/metaspace/metaspaceStatistics.hpp" + #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/ostream.hpp" namespace metaspace { -// FreeChunksStatistics methods - -FreeChunksStatistics::FreeChunksStatistics() -: _num(0), _cap(0) -{} - -void FreeChunksStatistics::reset() { - _num = 0; _cap = 0; -} - -void FreeChunksStatistics::add(uintx n, size_t s) { - _num += n; _cap += s; -} - -void FreeChunksStatistics::add(const FreeChunksStatistics& other) { - _num += other._num; - _cap += other._cap; -} - -void FreeChunksStatistics::print_on(outputStream* st, size_t scale) const { - st->print(UINTX_FORMAT, _num); - st->print(" chunks, total capacity "); - print_scaled_words(st, _cap, scale); -} - // ChunkManagerStatistics methods -void ChunkManagerStatistics::reset() { - for (ChunkIndex i = ZeroIndex; i < NumberOfInUseLists; i = next_chunk_index(i)) { - _chunk_stats[i].reset(); +// Returns total word size of all chunks in this manager. +void cm_stats_t::add(const cm_stats_t& other) { + for (chklvl_t l = chklvl::LOWEST_CHUNK_LEVEL; l <= chklvl::HIGHEST_CHUNK_LEVEL; l ++) { + num_chunks[l] += other.num_chunks[l]; + committed_word_size[l] += other.committed_word_size[l]; } } -size_t ChunkManagerStatistics::total_capacity() const { - return _chunk_stats[SpecializedIndex].cap() + - _chunk_stats[SmallIndex].cap() + - _chunk_stats[MediumIndex].cap() + - _chunk_stats[HumongousIndex].cap(); +// Returns total word size of all chunks in this manager. +size_t cm_stats_t::total_word_size() const { + size_t s = 0; + for (chklvl_t l = chklvl::LOWEST_CHUNK_LEVEL; l <= chklvl::HIGHEST_CHUNK_LEVEL; l ++) { + s += num_chunks[l] * chklvl::word_size_for_level(l); + } + return s; } -void ChunkManagerStatistics::print_on(outputStream* st, size_t scale) const { - FreeChunksStatistics totals; - for (ChunkIndex i = ZeroIndex; i < NumberOfInUseLists; i = next_chunk_index(i)) { +// Returns total committed word size of all chunks in this manager. +size_t cm_stats_t::total_committed_word_size() const { + size_t s = 0; + for (chklvl_t l = chklvl::LOWEST_CHUNK_LEVEL; l <= chklvl::HIGHEST_CHUNK_LEVEL; l ++) { + s += committed_word_size[l]; + } + return s; +} + + +void cm_stats_t::print_on(outputStream* st, size_t scale) const { + // Note: used as part of MetaspaceReport so formatting matters. + size_t total_size = 0; + size_t total_committed_size = 0; + for (chklvl_t l = chklvl::LOWEST_CHUNK_LEVEL; l <= chklvl::HIGHEST_CHUNK_LEVEL; l ++) { st->cr(); - st->print("%12s chunks: ", chunk_size_name(i)); - if (_chunk_stats[i].num() > 0) { - st->print(UINTX_FORMAT_W(4) ", capacity ", _chunk_stats[i].num()); - print_scaled_words(st, _chunk_stats[i].cap(), scale); + chklvl::print_chunk_size(st, l); + st->print(": "); + if (num_chunks[l] > 0) { + const size_t word_size = num_chunks[l] * chklvl::word_size_for_level(l); + + st->print("%4d, capacity=", num_chunks[l]); + print_scaled_words(st, word_size, scale); + + st->print(", committed="); + print_scaled_words_and_percentage(st, committed_word_size[l], word_size, scale); + + total_size += word_size; + total_committed_size += committed_word_size[l]; } else { st->print("(none)"); } - totals.add(_chunk_stats[i]); } st->cr(); - st->print("%19s: " UINTX_FORMAT_W(4) ", capacity=", "Total", totals.num()); - print_scaled_words(st, totals.cap(), scale); + st->print("Total word size: "); + print_scaled_words(st, total_size, scale); + st->print(", committed: "); + print_scaled_words_and_percentage(st, total_committed_size, total_size, scale); st->cr(); } +#ifdef ASSERT +void cm_stats_t::verify() const { + assert(total_committed_word_size() <= total_word_size(), + "Sanity"); +} +#endif + // UsedChunksStatistics methods -UsedChunksStatistics::UsedChunksStatistics() -: _num(0), _cap(0), _used(0), _free(0), _waste(0), _overhead(0) -{} - -void UsedChunksStatistics::reset() { - _num = 0; - _cap = _overhead = _used = _free = _waste = 0; -} - -void UsedChunksStatistics::add(const UsedChunksStatistics& other) { - _num += other._num; - _cap += other._cap; - _used += other._used; - _free += other._free; - _waste += other._waste; - _overhead += other._overhead; - DEBUG_ONLY(check_sanity()); -} - -void UsedChunksStatistics::print_on(outputStream* st, size_t scale) const { +void in_use_chunk_stats_t::print_on(outputStream* st, size_t scale) const { int col = st->position(); - st->print(UINTX_FORMAT_W(4) " chunk%s, ", _num, _num != 1 ? "s" : ""); - if (_num > 0) { + st->print("%4d chunk%s, ", num, num != 1 ? "s" : ""); + if (num > 0) { col += 14; st->fill_to(col); - print_scaled_words(st, _cap, scale, 5); - st->print(" capacity, "); + print_scaled_words(st, word_size, scale, 5); + st->print(" capacity,"); + + col += 20; st->fill_to(col); + print_scaled_words_and_percentage(st, committed_words, word_size, scale, 5); + st->print(" committed, "); col += 18; st->fill_to(col); - print_scaled_words_and_percentage(st, _used, _cap, scale, 5); + print_scaled_words_and_percentage(st, used_words, word_size, scale, 5); st->print(" used, "); col += 20; st->fill_to(col); - print_scaled_words_and_percentage(st, _free, _cap, scale, 5); + print_scaled_words_and_percentage(st, free_words, word_size, scale, 5); st->print(" free, "); col += 20; st->fill_to(col); - print_scaled_words_and_percentage(st, _waste, _cap, scale, 5); - st->print(" waste, "); + print_scaled_words_and_percentage(st, waste_words, word_size, scale, 5); + st->print(" waste "); - col += 20; st->fill_to(col); - print_scaled_words_and_percentage(st, _overhead, _cap, scale, 5); - st->print(" overhead"); } - DEBUG_ONLY(check_sanity()); } #ifdef ASSERT -void UsedChunksStatistics::check_sanity() const { - assert(_overhead == (Metachunk::overhead() * _num), "Sanity: Overhead."); - assert(_cap == _used + _free + _waste + _overhead, "Sanity: Capacity."); +void in_use_chunk_stats_t::verify() const { + assert(word_size >= committed_words && + committed_words == used_words + free_words + waste_words, + "Sanity: cap " SIZE_FORMAT ", committed " SIZE_FORMAT ", used " SIZE_FORMAT ", free " SIZE_FORMAT ", waste " SIZE_FORMAT ".", + word_size, committed_words, used_words, free_words, waste_words); } #endif // SpaceManagerStatistics methods -SpaceManagerStatistics::SpaceManagerStatistics() { reset(); } - -void SpaceManagerStatistics::reset() { - for (int i = 0; i < NumberOfInUseLists; i ++) { - _chunk_stats[i].reset(); - _free_blocks_num = 0; _free_blocks_cap_words = 0; +void sm_stats_t::add(const sm_stats_t& other) { + for (chklvl_t l = chklvl::LOWEST_CHUNK_LEVEL; l <= chklvl::HIGHEST_CHUNK_LEVEL; l ++) { + stats[l].add(other.stats[l]); } + free_blocks_num += other.free_blocks_num; + free_blocks_word_size += other.free_blocks_word_size; } -void SpaceManagerStatistics::add_free_blocks_info(uintx num, size_t cap) { - _free_blocks_num += num; - _free_blocks_cap_words += cap; + +// Returns total chunk statistics over all chunk types. +in_use_chunk_stats_t sm_stats_t::totals() const { + in_use_chunk_stats_t out; + for (chklvl_t l = chklvl::LOWEST_CHUNK_LEVEL; l <= chklvl::HIGHEST_CHUNK_LEVEL; l ++) { + out.add(stats[l]); + } + return out; } -void SpaceManagerStatistics::add(const SpaceManagerStatistics& other) { - for (ChunkIndex i = ZeroIndex; i < NumberOfInUseLists; i = next_chunk_index(i)) { - _chunk_stats[i].add(other._chunk_stats[i]); - } - _free_blocks_num += other._free_blocks_num; - _free_blocks_cap_words += other._free_blocks_cap_words; -} - -// Returns total chunk statistics over all chunk types. -UsedChunksStatistics SpaceManagerStatistics::totals() const { - UsedChunksStatistics stat; - for (ChunkIndex i = ZeroIndex; i < NumberOfInUseLists; i = next_chunk_index(i)) { - stat.add(_chunk_stats[i]); - } - return stat; -} - -void SpaceManagerStatistics::print_on(outputStream* st, size_t scale, bool detailed) const { +void sm_stats_t::print_on(outputStream* st, size_t scale, bool detailed) const { streamIndentor sti(st); if (detailed) { st->cr_indent(); - st->print("Usage by chunk type:"); + st->print("Usage by chunk level:"); { streamIndentor sti2(st); - for (ChunkIndex i = ZeroIndex; i < NumberOfInUseLists; i = next_chunk_index(i)) { + for (chklvl_t l = chklvl::LOWEST_CHUNK_LEVEL; l <= chklvl::HIGHEST_CHUNK_LEVEL; l ++) { st->cr_indent(); - st->print("%15s: ", chunk_size_name(i)); - if (_chunk_stats[i].num() == 0) { + chklvl::print_chunk_size(st, l); + st->print(" chunks: "); + if (stats[l].num == 0) { st->print(" (none)"); } else { - _chunk_stats[i].print_on(st, scale); + stats[l].print_on(st, scale); } } @@ -202,61 +183,57 @@ st->print("%15s: ", "-total-"); totals().print_on(st, scale); } - if (_free_blocks_num > 0) { + if (free_blocks_num > 0) { st->cr_indent(); - st->print("deallocated: " UINTX_FORMAT " blocks with ", _free_blocks_num); - print_scaled_words(st, _free_blocks_cap_words, scale); + st->print("deallocated: " UINTX_FORMAT " blocks with ", free_blocks_num); + print_scaled_words(st, free_blocks_word_size, scale); } } else { totals().print_on(st, scale); st->print(", "); - st->print("deallocated: " UINTX_FORMAT " blocks with ", _free_blocks_num); - print_scaled_words(st, _free_blocks_cap_words, scale); + st->print("deallocated: " UINTX_FORMAT " blocks with ", free_blocks_num); + print_scaled_words(st, free_blocks_word_size, scale); } } +#ifdef ASSERT + +void sm_stats_t::verify() const { + size_t total_used = 0; + for (chklvl_t l = chklvl::LOWEST_CHUNK_LEVEL; l <= chklvl::HIGHEST_CHUNK_LEVEL; l ++) { + stats[l].verify(); + total_used += stats[l].used_words; + } + // Deallocated allocations still count as used + assert(total_used >= free_blocks_word_size, + "Sanity"); +} +#endif + // ClassLoaderMetaspaceStatistics methods -ClassLoaderMetaspaceStatistics::ClassLoaderMetaspaceStatistics() { reset(); } - -void ClassLoaderMetaspaceStatistics::reset() { - nonclass_sm_stats().reset(); - if (Metaspace::using_class_space()) { - class_sm_stats().reset(); - } +// Returns total space manager statistics for both class and non-class metaspace +sm_stats_t clms_stats_t::totals() const { + sm_stats_t out; + out.add(sm_stats_nonclass); + out.add(sm_stats_class); + return out; } -// Returns total space manager statistics for both class and non-class metaspace -SpaceManagerStatistics ClassLoaderMetaspaceStatistics::totals() const { - SpaceManagerStatistics stats; - stats.add(nonclass_sm_stats()); - if (Metaspace::using_class_space()) { - stats.add(class_sm_stats()); - } - return stats; -} - -void ClassLoaderMetaspaceStatistics::add(const ClassLoaderMetaspaceStatistics& other) { - nonclass_sm_stats().add(other.nonclass_sm_stats()); - if (Metaspace::using_class_space()) { - class_sm_stats().add(other.class_sm_stats()); - } -} - -void ClassLoaderMetaspaceStatistics::print_on(outputStream* st, size_t scale, bool detailed) const { +void clms_stats_t::print_on(outputStream* st, size_t scale, bool detailed) const { streamIndentor sti(st); st->cr_indent(); if (Metaspace::using_class_space()) { st->print("Non-Class: "); } - nonclass_sm_stats().print_on(st, scale, detailed); + sm_stats_nonclass.print_on(st, scale, detailed); if (detailed) { st->cr(); } if (Metaspace::using_class_space()) { st->cr_indent(); st->print(" Class: "); - class_sm_stats().print_on(st, scale, detailed); + sm_stats_class.print_on(st, scale, detailed); if (detailed) { st->cr(); } @@ -270,6 +247,14 @@ st->cr(); } + +#ifdef ASSERT +void clms_stats_t::verify() const { + sm_stats_nonclass.verify(); + sm_stats_class.verify(); +} +#endif + } // end namespace metaspace diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/metaspaceStatistics.hpp --- a/src/hotspot/share/memory/metaspace/metaspaceStatistics.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace/metaspaceStatistics.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -1,6 +1,6 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2018 SAP SE. All rights reserved. + * Copyright (c) 2018, 2019 SAP SE. All rights reserved. + * Copyright (c) 2018, 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 @@ -26,162 +26,135 @@ #ifndef SHARE_MEMORY_METASPACE_METASPACESTATISTICS_HPP #define SHARE_MEMORY_METASPACE_METASPACESTATISTICS_HPP +#include "memory/metaspace.hpp" // for MetadataType enum +#include "memory/metaspace/chunkLevel.hpp" #include "utilities/globalDefinitions.hpp" -#include "memory/metaspace.hpp" // for MetadataType enum -#include "memory/metaspace/metachunk.hpp" // for ChunkIndex enum class outputStream; namespace metaspace { -// Contains statistics for a number of free chunks. -class FreeChunksStatistics { - uintx _num; // Number of chunks - size_t _cap; // Total capacity, in words -public: - FreeChunksStatistics(); +// Contains statistics for one or multiple ChunkManager. +struct cm_stats_t { - void reset(); + // How many chunks per level are checked in. + int num_chunks[chklvl::NUM_CHUNK_LEVELS]; - uintx num() const { return _num; } - size_t cap() const { return _cap; } + // Size, in words, of the sum of all committed areas in this chunk manager, per level. + size_t committed_word_size[chklvl::NUM_CHUNK_LEVELS]; - void add(uintx n, size_t s); - void add(const FreeChunksStatistics& other); - void print_on(outputStream* st, size_t scale) const; + cm_stats_t() : num_chunks(), committed_word_size() {} -}; // end: FreeChunksStatistics + void add(const cm_stats_t& other); + // Returns total word size of all chunks in this manager. + size_t total_word_size() const; -// Contains statistics for a ChunkManager. -class ChunkManagerStatistics { - - FreeChunksStatistics _chunk_stats[NumberOfInUseLists]; - -public: - - // Free chunk statistics, by chunk index. - const FreeChunksStatistics& chunk_stats(ChunkIndex index) const { return _chunk_stats[index]; } - FreeChunksStatistics& chunk_stats(ChunkIndex index) { return _chunk_stats[index]; } - - void reset(); - size_t total_capacity() const; + // Returns total committed word size of all chunks in this manager. + size_t total_committed_word_size() const; void print_on(outputStream* st, size_t scale) const; + DEBUG_ONLY(void verify() const;) + }; // ChunkManagerStatistics -// Contains statistics for a number of chunks in use. -// Each chunk has a used and free portion; however, there are current chunks (serving -// potential future metaspace allocations) and non-current chunks. Unused portion of the -// former is counted as free, unused portion of the latter counts as waste. -class UsedChunksStatistics { - uintx _num; // Number of chunks - size_t _cap; // Total capacity in words. - size_t _used; // Total used area, in words - size_t _free; // Total free area (unused portions of current chunks), in words - size_t _waste; // Total waste area (unused portions of non-current chunks), in words - size_t _overhead; // Total sum of chunk overheads, in words. +// Contains statistics for one or multiple chunks in use. +struct in_use_chunk_stats_t { -public: + // Number of chunks + int num; - UsedChunksStatistics(); + // Note: + // capacity = committed + uncommitted + // committed = used + free + waste - void reset(); + // Capacity (total sum of all chunk sizes) in words. + // May contain committed and uncommitted space. + size_t word_size; - uintx num() const { return _num; } + // Total committed area, in words. + size_t committed_words; - // Total capacity, in words - size_t cap() const { return _cap; } + // Total used area, in words. + size_t used_words; - // Total used area, in words - size_t used() const { return _used; } + // Total free committed area, in words. + size_t free_words; - // Total free area (unused portions of current chunks), in words - size_t free() const { return _free; } + // Total waste committed area, in words. + size_t waste_words; - // Total waste area (unused portions of non-current chunks), in words - size_t waste() const { return _waste; } + in_use_chunk_stats_t() + : num(0), word_size(0), committed_words(0), + used_words(0), free_words(0), waste_words(0) + {} - // Total area spent in overhead (chunk headers), in words - size_t overhead() const { return _overhead; } + void add(const in_use_chunk_stats_t& other) { + num += other.num; + word_size += other.word_size; + committed_words += other.committed_words; + used_words += other.used_words; + free_words += other.free_words; + waste_words += other.waste_words; - void add_num(uintx n) { _num += n; } - void add_cap(size_t s) { _cap += s; } - void add_used(size_t s) { _used += s; } - void add_free(size_t s) { _free += s; } - void add_waste(size_t s) { _waste += s; } - void add_overhead(size_t s) { _overhead += s; } - - void add(const UsedChunksStatistics& other); + } void print_on(outputStream* st, size_t scale) const; -#ifdef ASSERT - void check_sanity() const; -#endif + DEBUG_ONLY(void verify() const;) }; // UsedChunksStatistics // Class containing statistics for one or more space managers. -class SpaceManagerStatistics { +struct sm_stats_t { - UsedChunksStatistics _chunk_stats[NumberOfInUseLists]; - uintx _free_blocks_num; - size_t _free_blocks_cap_words; + // chunk statistics by chunk level + in_use_chunk_stats_t stats[chklvl::NUM_CHUNK_LEVELS]; + uintx free_blocks_num; + size_t free_blocks_word_size; -public: + sm_stats_t() + : stats(), + free_blocks_num(0), + free_blocks_word_size(0) + {} - SpaceManagerStatistics(); + void add(const sm_stats_t& other); - // Chunk statistics by chunk index - const UsedChunksStatistics& chunk_stats(ChunkIndex index) const { return _chunk_stats[index]; } - UsedChunksStatistics& chunk_stats(ChunkIndex index) { return _chunk_stats[index]; } + void print_on(outputStream* st, size_t scale = K, bool detailed = true) const; - uintx free_blocks_num () const { return _free_blocks_num; } - size_t free_blocks_cap_words () const { return _free_blocks_cap_words; } + in_use_chunk_stats_t totals() const; - void reset(); - - void add_free_blocks_info(uintx num, size_t cap); - - // Returns total chunk statistics over all chunk types. - UsedChunksStatistics totals() const; - - void add(const SpaceManagerStatistics& other); - - void print_on(outputStream* st, size_t scale, bool detailed) const; + DEBUG_ONLY(void verify() const;) }; // SpaceManagerStatistics -class ClassLoaderMetaspaceStatistics { +// Statistics for one or multiple ClassLoaderMetaspace objects +struct clms_stats_t { - SpaceManagerStatistics _sm_stats[Metaspace::MetadataTypeCount]; + sm_stats_t sm_stats_nonclass; + sm_stats_t sm_stats_class; -public: + clms_stats_t() : sm_stats_nonclass(), sm_stats_class() {} - ClassLoaderMetaspaceStatistics(); + void add(const clms_stats_t& other) { + sm_stats_nonclass.add(other.sm_stats_nonclass); + sm_stats_class.add(other.sm_stats_class); + } - const SpaceManagerStatistics& sm_stats(Metaspace::MetadataType mdType) const { return _sm_stats[mdType]; } - SpaceManagerStatistics& sm_stats(Metaspace::MetadataType mdType) { return _sm_stats[mdType]; } - - const SpaceManagerStatistics& nonclass_sm_stats() const { return sm_stats(Metaspace::NonClassType); } - SpaceManagerStatistics& nonclass_sm_stats() { return sm_stats(Metaspace::NonClassType); } - const SpaceManagerStatistics& class_sm_stats() const { return sm_stats(Metaspace::ClassType); } - SpaceManagerStatistics& class_sm_stats() { return sm_stats(Metaspace::ClassType); } - - void reset(); - - void add(const ClassLoaderMetaspaceStatistics& other); + void print_on(outputStream* st, size_t scale, bool detailed) const; // Returns total space manager statistics for both class and non-class metaspace - SpaceManagerStatistics totals() const; + sm_stats_t totals() const; - void print_on(outputStream* st, size_t scale, bool detailed) const; + + DEBUG_ONLY(void verify() const;) }; // ClassLoaderMetaspaceStatistics } // namespace metaspace #endif // SHARE_MEMORY_METASPACE_METASPACESTATISTICS_HPP + diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/occupancyMap.cpp --- a/src/hotspot/share/memory/metaspace/occupancyMap.cpp Tue Sep 17 19:09:37 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2018 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. - * - * 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 "utilities/debug.hpp" -#include "utilities/globalDefinitions.hpp" -#include "memory/metaspace/metachunk.hpp" -#include "memory/metaspace/occupancyMap.hpp" -#include "runtime/os.hpp" - -namespace metaspace { - -OccupancyMap::OccupancyMap(const MetaWord* reference_address, size_t word_size, size_t smallest_chunk_word_size) : - _reference_address(reference_address), _word_size(word_size), - _smallest_chunk_word_size(smallest_chunk_word_size) -{ - assert(reference_address != NULL, "invalid reference address"); - assert(is_aligned(reference_address, smallest_chunk_word_size), - "Reference address not aligned to smallest chunk size."); - assert(is_aligned(word_size, smallest_chunk_word_size), - "Word_size shall be a multiple of the smallest chunk size."); - // Calculate bitmap size: one bit per smallest_chunk_word_size'd area. - size_t num_bits = word_size / smallest_chunk_word_size; - _map_size = (num_bits + 7) / 8; - assert(_map_size * 8 >= num_bits, "sanity"); - _map[0] = (uint8_t*) os::malloc(_map_size, mtInternal); - _map[1] = (uint8_t*) os::malloc(_map_size, mtInternal); - assert(_map[0] != NULL && _map[1] != NULL, "Occupancy Map: allocation failed."); - memset(_map[1], 0, _map_size); - memset(_map[0], 0, _map_size); - // Sanity test: the first respectively last possible chunk start address in - // the covered range shall map to the first and last bit in the bitmap. - assert(get_bitpos_for_address(reference_address) == 0, - "First chunk address in range must map to fist bit in bitmap."); - assert(get_bitpos_for_address(reference_address + word_size - smallest_chunk_word_size) == num_bits - 1, - "Last chunk address in range must map to last bit in bitmap."); -} - -OccupancyMap::~OccupancyMap() { - os::free(_map[0]); - os::free(_map[1]); -} - -#ifdef ASSERT -// Verify occupancy map for the address range [from, to). -// We need to tell it the address range, because the memory the -// occupancy map is covering may not be fully comitted yet. -void OccupancyMap::verify(MetaWord* from, MetaWord* to) { - Metachunk* chunk = NULL; - int nth_bit_for_chunk = 0; - MetaWord* chunk_end = NULL; - for (MetaWord* p = from; p < to; p += _smallest_chunk_word_size) { - const unsigned pos = get_bitpos_for_address(p); - // Check the chunk-starts-info: - if (get_bit_at_position(pos, layer_chunk_start_map)) { - // Chunk start marked in bitmap. - chunk = (Metachunk*) p; - if (chunk_end != NULL) { - assert(chunk_end == p, "Unexpected chunk start found at %p (expected " - "the next chunk to start at %p).", p, chunk_end); - } - assert(chunk->is_valid_sentinel(), "Invalid chunk at address %p.", p); - if (chunk->get_chunk_type() != HumongousIndex) { - guarantee(is_aligned(p, chunk->word_size()), "Chunk %p not aligned.", p); - } - chunk_end = p + chunk->word_size(); - nth_bit_for_chunk = 0; - assert(chunk_end <= to, "Chunk end overlaps test address range."); - } else { - // No chunk start marked in bitmap. - assert(chunk != NULL, "Chunk should start at start of address range."); - assert(p < chunk_end, "Did not find expected chunk start at %p.", p); - nth_bit_for_chunk ++; - } - // Check the in-use-info: - const bool in_use_bit = get_bit_at_position(pos, layer_in_use_map); - if (in_use_bit) { - assert(!chunk->is_tagged_free(), "Chunk %p: marked in-use in map but is free (bit %u).", - chunk, nth_bit_for_chunk); - } else { - assert(chunk->is_tagged_free(), "Chunk %p: marked free in map but is in use (bit %u).", - chunk, nth_bit_for_chunk); - } - } -} - -// Verify that a given chunk is correctly accounted for in the bitmap. -void OccupancyMap::verify_for_chunk(Metachunk* chunk) { - assert(chunk_starts_at_address((MetaWord*) chunk), - "No chunk start marked in map for chunk %p.", chunk); - // For chunks larger than the minimal chunk size, no other chunk - // must start in its area. - if (chunk->word_size() > _smallest_chunk_word_size) { - assert(!is_any_bit_set_in_region(((MetaWord*) chunk) + _smallest_chunk_word_size, - chunk->word_size() - _smallest_chunk_word_size, layer_chunk_start_map), - "No chunk must start within another chunk."); - } - if (!chunk->is_tagged_free()) { - assert(is_region_in_use((MetaWord*)chunk, chunk->word_size()), - "Chunk %p is in use but marked as free in map (%d %d).", - chunk, chunk->get_chunk_type(), chunk->get_origin()); - } else { - assert(!is_region_in_use((MetaWord*)chunk, chunk->word_size()), - "Chunk %p is free but marked as in-use in map (%d %d).", - chunk, chunk->get_chunk_type(), chunk->get_origin()); - } -} - -#endif // ASSERT - -} // namespace metaspace - - diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/occupancyMap.hpp --- a/src/hotspot/share/memory/metaspace/occupancyMap.hpp Tue Sep 17 19:09:37 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,242 +0,0 @@ -/* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2018 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. - * - * 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_MEMORY_METASPACE_OCCUPANCYMAP_HPP -#define SHARE_MEMORY_METASPACE_OCCUPANCYMAP_HPP - -#include "memory/allocation.hpp" -#include "utilities/debug.hpp" -#include "utilities/globalDefinitions.hpp" - - -namespace metaspace { - -class Metachunk; - -// Helper for Occupancy Bitmap. A type trait to give an all-bits-are-one-unsigned constant. -template struct all_ones { static const T value; }; -template <> struct all_ones { static const uint64_t value = 0xFFFFFFFFFFFFFFFFULL; }; -template <> struct all_ones { static const uint32_t value = 0xFFFFFFFF; }; - -// The OccupancyMap is a bitmap which, for a given VirtualSpaceNode, -// keeps information about -// - where a chunk starts -// - whether a chunk is in-use or free -// A bit in this bitmap represents one range of memory in the smallest -// chunk size (SpecializedChunk or ClassSpecializedChunk). -class OccupancyMap : public CHeapObj { - - // The address range this map covers. - const MetaWord* const _reference_address; - const size_t _word_size; - - // The word size of a specialized chunk, aka the number of words one - // bit in this map represents. - const size_t _smallest_chunk_word_size; - - // map data - // Data are organized in two bit layers: - // The first layer is the chunk-start-map. Here, a bit is set to mark - // the corresponding region as the head of a chunk. - // The second layer is the in-use-map. Here, a set bit indicates that - // the corresponding belongs to a chunk which is in use. - uint8_t* _map[2]; - - enum { layer_chunk_start_map = 0, layer_in_use_map = 1 }; - - // length, in bytes, of bitmap data - size_t _map_size; - - // Returns true if bit at position pos at bit-layer layer is set. - bool get_bit_at_position(unsigned pos, unsigned layer) const { - assert(layer == 0 || layer == 1, "Invalid layer %d", layer); - const unsigned byteoffset = pos / 8; - assert(byteoffset < _map_size, - "invalid byte offset (%u), map size is " SIZE_FORMAT ".", byteoffset, _map_size); - const unsigned mask = 1 << (pos % 8); - return (_map[layer][byteoffset] & mask) > 0; - } - - // Changes bit at position pos at bit-layer layer to value v. - void set_bit_at_position(unsigned pos, unsigned layer, bool v) { - assert(layer == 0 || layer == 1, "Invalid layer %d", layer); - const unsigned byteoffset = pos / 8; - assert(byteoffset < _map_size, - "invalid byte offset (%u), map size is " SIZE_FORMAT ".", byteoffset, _map_size); - const unsigned mask = 1 << (pos % 8); - if (v) { - _map[layer][byteoffset] |= mask; - } else { - _map[layer][byteoffset] &= ~mask; - } - } - - // Optimized case of is_any_bit_set_in_region for 32/64bit aligned access: - // pos is 32/64 aligned and num_bits is 32/64. - // This is the typical case when coalescing to medium chunks, whose size is - // 32 or 64 times the specialized chunk size (depending on class or non class - // case), so they occupy 64 bits which should be 64bit aligned, because - // chunks are chunk-size aligned. - template - bool is_any_bit_set_in_region_3264(unsigned pos, unsigned num_bits, unsigned layer) const { - assert(_map_size > 0, "not initialized"); - assert(layer == 0 || layer == 1, "Invalid layer %d.", layer); - assert(pos % (sizeof(T) * 8) == 0, "Bit position must be aligned (%u).", pos); - assert(num_bits == (sizeof(T) * 8), "Number of bits incorrect (%u).", num_bits); - const size_t byteoffset = pos / 8; - assert(byteoffset <= (_map_size - sizeof(T)), - "Invalid byte offset (" SIZE_FORMAT "), map size is " SIZE_FORMAT ".", byteoffset, _map_size); - const T w = *(T*)(_map[layer] + byteoffset); - return w > 0 ? true : false; - } - - // Returns true if any bit in region [pos1, pos1 + num_bits) is set in bit-layer layer. - bool is_any_bit_set_in_region(unsigned pos, unsigned num_bits, unsigned layer) const { - if (pos % 32 == 0 && num_bits == 32) { - return is_any_bit_set_in_region_3264(pos, num_bits, layer); - } else if (pos % 64 == 0 && num_bits == 64) { - return is_any_bit_set_in_region_3264(pos, num_bits, layer); - } else { - for (unsigned n = 0; n < num_bits; n ++) { - if (get_bit_at_position(pos + n, layer)) { - return true; - } - } - } - return false; - } - - // Returns true if any bit in region [p, p+word_size) is set in bit-layer layer. - bool is_any_bit_set_in_region(MetaWord* p, size_t word_size, unsigned layer) const { - assert(word_size % _smallest_chunk_word_size == 0, - "Region size " SIZE_FORMAT " not a multiple of smallest chunk size.", word_size); - const unsigned pos = get_bitpos_for_address(p); - const unsigned num_bits = (unsigned) (word_size / _smallest_chunk_word_size); - return is_any_bit_set_in_region(pos, num_bits, layer); - } - - // Optimized case of set_bits_of_region for 32/64bit aligned access: - // pos is 32/64 aligned and num_bits is 32/64. - // This is the typical case when coalescing to medium chunks, whose size - // is 32 or 64 times the specialized chunk size (depending on class or non - // class case), so they occupy 64 bits which should be 64bit aligned, - // because chunks are chunk-size aligned. - template - void set_bits_of_region_T(unsigned pos, unsigned num_bits, unsigned layer, bool v) { - assert(pos % (sizeof(T) * 8) == 0, "Bit position must be aligned to %u (%u).", - (unsigned)(sizeof(T) * 8), pos); - assert(num_bits == (sizeof(T) * 8), "Number of bits incorrect (%u), expected %u.", - num_bits, (unsigned)(sizeof(T) * 8)); - const size_t byteoffset = pos / 8; - assert(byteoffset <= (_map_size - sizeof(T)), - "invalid byte offset (" SIZE_FORMAT "), map size is " SIZE_FORMAT ".", byteoffset, _map_size); - T* const pw = (T*)(_map[layer] + byteoffset); - *pw = v ? all_ones::value : (T) 0; - } - - // Set all bits in a region starting at pos to a value. - void set_bits_of_region(unsigned pos, unsigned num_bits, unsigned layer, bool v) { - assert(_map_size > 0, "not initialized"); - assert(layer == 0 || layer == 1, "Invalid layer %d.", layer); - if (pos % 32 == 0 && num_bits == 32) { - set_bits_of_region_T(pos, num_bits, layer, v); - } else if (pos % 64 == 0 && num_bits == 64) { - set_bits_of_region_T(pos, num_bits, layer, v); - } else { - for (unsigned n = 0; n < num_bits; n ++) { - set_bit_at_position(pos + n, layer, v); - } - } - } - - // Helper: sets all bits in a region [p, p+word_size). - void set_bits_of_region(MetaWord* p, size_t word_size, unsigned layer, bool v) { - assert(word_size % _smallest_chunk_word_size == 0, - "Region size " SIZE_FORMAT " not a multiple of smallest chunk size.", word_size); - const unsigned pos = get_bitpos_for_address(p); - const unsigned num_bits = (unsigned) (word_size / _smallest_chunk_word_size); - set_bits_of_region(pos, num_bits, layer, v); - } - - // Helper: given an address, return the bit position representing that address. - unsigned get_bitpos_for_address(const MetaWord* p) const { - assert(_reference_address != NULL, "not initialized"); - assert(p >= _reference_address && p < _reference_address + _word_size, - "Address %p out of range for occupancy map [%p..%p).", - p, _reference_address, _reference_address + _word_size); - assert(is_aligned(p, _smallest_chunk_word_size * sizeof(MetaWord)), - "Address not aligned (%p).", p); - const ptrdiff_t d = (p - _reference_address) / _smallest_chunk_word_size; - assert(d >= 0 && (size_t)d < _map_size * 8, "Sanity."); - return (unsigned) d; - } - - public: - - OccupancyMap(const MetaWord* reference_address, size_t word_size, size_t smallest_chunk_word_size); - ~OccupancyMap(); - - // Returns true if at address x a chunk is starting. - bool chunk_starts_at_address(MetaWord* p) const { - const unsigned pos = get_bitpos_for_address(p); - return get_bit_at_position(pos, layer_chunk_start_map); - } - - void set_chunk_starts_at_address(MetaWord* p, bool v) { - const unsigned pos = get_bitpos_for_address(p); - set_bit_at_position(pos, layer_chunk_start_map, v); - } - - // Removes all chunk-start-bits inside a region, typically as a - // result of a chunk merge. - void wipe_chunk_start_bits_in_region(MetaWord* p, size_t word_size) { - set_bits_of_region(p, word_size, layer_chunk_start_map, false); - } - - // Returns true if there are life (in use) chunks in the region limited - // by [p, p+word_size). - bool is_region_in_use(MetaWord* p, size_t word_size) const { - return is_any_bit_set_in_region(p, word_size, layer_in_use_map); - } - - // Marks the region starting at p with the size word_size as in use - // or free, depending on v. - void set_region_in_use(MetaWord* p, size_t word_size, bool v) { - set_bits_of_region(p, word_size, layer_in_use_map, v); - } - - // Verify occupancy map for the address range [from, to). - // We need to tell it the address range, because the memory the - // occupancy map is covering may not be fully comitted yet. - DEBUG_ONLY(void verify(MetaWord* from, MetaWord* to);) - - // Verify that a given chunk is correctly accounted for in the bitmap. - DEBUG_ONLY(void verify_for_chunk(Metachunk* chunk);) - -}; - -} // namespace metaspace - -#endif // SHARE_MEMORY_METASPACE_OCCUPANCYMAP_HPP diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/printCLDMetaspaceInfoClosure.cpp --- a/src/hotspot/share/memory/metaspace/printCLDMetaspaceInfoClosure.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace/printCLDMetaspaceInfoClosure.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -1,5 +1,6 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 SAP SE. All rights reserved. + * Copyright (c) 2018, 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,9 +25,10 @@ #include "precompiled.hpp" #include "classfile/classLoaderData.inline.hpp" #include "classfile/javaClasses.hpp" +#include "memory/metaspace/classLoaderMetaspace.hpp" #include "memory/metaspace/printCLDMetaspaceInfoClosure.hpp" #include "memory/metaspace/printMetaspaceInfoKlassClosure.hpp" -#include "memory/metaspaceShared.hpp" +#include "memory/metaspace/metaspaceCommon.hpp" #include "memory/resourceArea.hpp" #include "runtime/safepoint.hpp" #include "utilities/globalDefinitions.hpp" @@ -80,7 +82,7 @@ } // Collect statistics for this class loader metaspace - ClassLoaderMetaspaceStatistics this_cld_stat; + clms_stats_t this_cld_stat; msp->add_to_statistics(&this_cld_stat); // And add it to the running totals diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/printCLDMetaspaceInfoClosure.hpp --- a/src/hotspot/share/memory/metaspace/printCLDMetaspaceInfoClosure.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace/printCLDMetaspaceInfoClosure.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -1,5 +1,6 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 SAP SE. All rights reserved. + * Copyright (c) 2018, 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 @@ -28,6 +29,7 @@ #include "memory/iterator.hpp" #include "memory/metaspace.hpp" #include "memory/metaspace/metaspaceStatistics.hpp" +#include "memory/metaspace/metaspaceEnums.hpp" #include "utilities/globalDefinitions.hpp" class outputStream; @@ -47,13 +49,13 @@ uintx _num_loaders; uintx _num_loaders_without_metaspace; uintx _num_loaders_unloading; - ClassLoaderMetaspaceStatistics _stats_total; + clms_stats_t _stats_total; - uintx _num_loaders_by_spacetype [Metaspace::MetaspaceTypeCount]; - ClassLoaderMetaspaceStatistics _stats_by_spacetype [Metaspace::MetaspaceTypeCount]; + uintx _num_loaders_by_spacetype [MetaspaceTypeCount]; + clms_stats_t _stats_by_spacetype [MetaspaceTypeCount]; - uintx _num_classes_by_spacetype [Metaspace::MetaspaceTypeCount]; - uintx _num_classes_shared_by_spacetype [Metaspace::MetaspaceTypeCount]; + uintx _num_classes_by_spacetype [MetaspaceTypeCount]; + uintx _num_classes_shared_by_spacetype [MetaspaceTypeCount]; uintx _num_classes; uintx _num_classes_shared; diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/printMetaspaceInfoKlassClosure.cpp --- a/src/hotspot/share/memory/metaspace/printMetaspaceInfoKlassClosure.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace/printMetaspaceInfoKlassClosure.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -1,6 +1,6 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2018, SAP and/or its affiliates. + * Copyright (c) 2018, 2019 SAP SE. All rights reserved. + * Copyright (c) 2018, 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 diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/printMetaspaceInfoKlassClosure.hpp --- a/src/hotspot/share/memory/metaspace/printMetaspaceInfoKlassClosure.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace/printMetaspaceInfoKlassClosure.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -1,6 +1,6 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2018, SAP and/or its affiliates. + * Copyright (c) 2018, 2019 SAP SE. All rights reserved. + * Copyright (c) 2018, 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 diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/rootChunkArea.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/rootChunkArea.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,583 @@ +/* + * Copyright (c) 2019 SAP SE. All rights reserved. + * 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 "logging/log.hpp" +#include "memory/allocation.hpp" +#include "memory/metaspace/chunkHeaderPool.hpp" +#include "memory/metaspace/chunkManager.hpp" +#include "memory/metaspace/internStat.hpp" +#include "memory/metaspace/metachunk.hpp" +#include "memory/metaspace/metaspaceCommon.hpp" +#include "memory/metaspace/rootChunkArea.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" + +namespace metaspace { + +RootChunkArea::RootChunkArea(const MetaWord* base) + : _base(base), _first_chunk(NULL) +{} + +RootChunkArea::~RootChunkArea() { + // This is called when a VirtualSpaceNode is destructed (purged). + // All chunks should be free of course. In fact, there should only + // be one chunk, since all free chunks should have been merged. + if (_first_chunk != NULL) { + assert(_first_chunk->is_root_chunk() && _first_chunk->is_free(), + "Cannot delete root chunk area if not all chunks are free."); + ChunkHeaderPool::pool().return_chunk_header(_first_chunk); + } +} + +// Initialize: allocate a root node and a root chunk header; return the +// root chunk header. It will be partly initialized. +// Note: this just allocates a memory-less header; memory itself is allocated inside VirtualSpaceNode. +Metachunk* RootChunkArea::alloc_root_chunk_header(VirtualSpaceNode* node) { + + assert(_first_chunk == 0, "already have a root"); + + Metachunk* c = ChunkHeaderPool::pool().allocate_chunk_header(); + c->initialize(node, const_cast(_base), chklvl::ROOT_CHUNK_LEVEL); + + _first_chunk = c; + + return c; + +} + + + +// Given a chunk c, split it recursively until you get a chunk of the given target_level. +// +// The original chunk must not be part of a freelist. +// +// Returns pointer to the result chunk; the splitted-off chunks are added as +// free chunks to the freelists. +// +// Returns NULL if chunk cannot be split at least once. +Metachunk* RootChunkArea::split(chklvl_t target_level, Metachunk* c, MetachunkListCluster* freelists) { + + DEBUG_ONLY(c->verify(true);) + + // Splitting a chunk once works like this: + // + // For a given chunk we want to split: + // - increase the chunk level (which halves its size) + // - (but leave base address as it is since it will be the leader of the newly + // created chunk pair) + // - then create a new chunk header of the same level, set its memory range + // to cover the second halfof the old chunk. + // - wire them up (prev_in_vs/next_in_vs) + // - return the follower chunk as "splinter chunk" in the splinters array. + + // Doing this multiple times will create a new free splinter chunk for every + // level we split: + // + // A <- original chunk + // + // B B <- split into two halves + // + // C C B <- first half split again + // + // D D C B <- first half split again ... + // + + // As an optimization, since we usually do not split once but multiple times, + // to not do each split separately, since we would have to wire up prev_in_vs/next_in_vs + // on every level just to tear it open in the next level when we reintroduce a new + // half chunk splinter. + // Instead, just split split split and delay building up the double linked list of the + // new chunks at the end of all splits. + + DEBUG_ONLY(check_pointer(c->base());) + assert(c->is_free(), "Can only split free chunks."); + + DEBUG_ONLY(chklvl::check_valid_level(target_level)); + assert(target_level > c->level(), "Wrong target level"); + + DEBUG_ONLY(verify(true);) + + const chklvl_t starting_level = c->level(); + + Metachunk* result = c; + + log_trace(metaspace)("Splitting chunk @" PTR_FORMAT ", base " PTR_FORMAT ", level " CHKLVL_FORMAT "...", + p2i(c), p2i(c->base()), c->level()); + + while (result->level() < target_level) { + + result->inc_level(); + Metachunk* splinter_chunk = ChunkHeaderPool::pool().allocate_chunk_header(); + splinter_chunk->initialize(result->vsnode(), result->end(), result->level()); + + // Fix committed words info: If over the half of the original chunk was + // committed, committed area spills over into the follower chunk. + const size_t old_committed_words = result->committed_words(); + if (old_committed_words > result->word_size()) { + result->set_committed_words(result->word_size()); + splinter_chunk->set_committed_words(old_committed_words - result->word_size()); + } else { + splinter_chunk->set_committed_words(0); + } + + // Insert splinter chunk into vs list + if (result->next_in_vs() != NULL) { + result->next_in_vs()->set_prev_in_vs(splinter_chunk); + } + splinter_chunk->set_next_in_vs(result->next_in_vs()); + splinter_chunk->set_prev_in_vs(result); + result->set_next_in_vs(splinter_chunk); + + log_trace(metaspace)("Created splinter chunk @" PTR_FORMAT ", base " PTR_FORMAT ", level " CHKLVL_FORMAT "...", + p2i(splinter_chunk), p2i(splinter_chunk->base()), splinter_chunk->level()); + + // Add splinter to free lists + freelists->add(splinter_chunk); + + DEBUG_ONLY(InternalStats::inc_num_chunks_added_to_freelist_due_to_split();) + + } + + assert(result->level() == target_level, "Sanity"); + + DEBUG_ONLY(verify(true);) + DEBUG_ONLY(result->verify(true);) + + DEBUG_ONLY(InternalStats::inc_num_chunk_splits();) + + return result; + +} + + +// Given a chunk, attempt to merge it recursively with its neighboring chunks. +// +// If successful (merged at least once), returns address of +// the merged chunk; NULL otherwise. +// +// The merged chunks are removed from the freelists. +// +// !!! Please note that if this method returns a non-NULL value, the +// original chunk will be invalid and should not be accessed anymore! !!! +Metachunk* RootChunkArea::merge(Metachunk* c, MetachunkListCluster* freelists) { + + // Note rules: + // + // - a chunk always has a buddy, unless it is a root chunk. + // - In that buddy pair, a chunk is either leader or follower. + // - a chunk's base address is always aligned at its size. + // - if chunk is leader, its base address is also aligned to the size of the next + // lower level, at least. A follower chunk is not. + + // How we merge once: + // + // For a given chunk c, which has to be free and non-root, we do: + // - find out if we are the leader or the follower chunk + // - if we are leader, next_in_vs must be the follower; if we are follower, + // prev_in_vs must be the leader. Now we have the buddy chunk. + // - However, if the buddy chunk itself is split (of a level higher than us) + // we cannot merge. + // - we can only merge if the buddy is of the same level as we are and it is + // free. + // - Then we merge by simply removing the follower chunk from the address range + // linked list (returning the now useless header to the pool) and decreasing + // the leader chunk level by one. That makes it double the size. + + // Example: + // (lower case chunks are free, the * indicates the chunk we want to merge): + // + // ........................ + // d d*c b A <- we return the second (d*) chunk... + // + // c* c b A <- we merge it with its predecessor and decrease its level... + // + // b* b A <- we merge it again, since its new neighbor was free too... + // + // a* A <- we merge it again, since its new neighbor was free too... + // + // And we are done, since its new neighbor, (A), is not free. We would also be done + // if the new neighbor itself is splintered. + + DEBUG_ONLY(check_pointer(c->base());) + assert(!c->is_root_chunk(), "Cannot be merged further."); + assert(c->is_free(), "Can only merge free chunks."); + + DEBUG_ONLY(c->verify(false);) + + log_trace(metaspace)("Attempting to merge chunk " METACHUNK_FORMAT ".", METACHUNK_FORMAT_ARGS(c)); + + const chklvl_t starting_level = c->level(); + + bool stop = false; + Metachunk* result = NULL; + + do { + + // First find out if this chunk is the leader of its pair + const bool is_leader = c->is_leader(); + + // Note: this is either our buddy or a splinter of the buddy. + Metachunk* const buddy = c->is_leader() ? c->next_in_vs() : c->prev_in_vs(); + DEBUG_ONLY(buddy->verify(true);) + + // A buddy chunk must be of the same or higher level (so, same size or smaller) + // never be larger. + assert(buddy->level() >= c->level(), "Sanity"); + + // Is this really my buddy (same level) or a splinter of it (higher level)? + // Also, is it free? + if (buddy->level() != c->level() || buddy->is_free() == false) { + + log_trace(metaspace)("cannot merge with chunk " METACHUNK_FORMAT ".", METACHUNK_FORMAT_ARGS(buddy)); + + stop = true; + + } else { + + log_trace(metaspace)("will merge with chunk " METACHUNK_FORMAT ".", METACHUNK_FORMAT_ARGS(buddy)); + + // We can merge with the buddy. + + // First, remove buddy from the chunk manager. + assert(buddy->is_free(), "Sanity"); + freelists->remove(buddy); + DEBUG_ONLY(InternalStats::inc_num_chunks_removed_from_freelist_due_to_merge();) + + // Determine current leader and follower + Metachunk* leader; + Metachunk* follower; + if (is_leader) { + leader = c; follower = buddy; + } else { + leader = buddy; follower = c; + } + + // Last checkpoint + assert(leader->end() == follower->base() && + leader->level() == follower->level() && + leader->is_free() && follower->is_free(), "Sanity"); + + // The new merged chunk is as far committed as possible (if leader + // chunk is fully committed, as far as the follower chunk). + size_t merged_committed_words = leader->committed_words(); + if (merged_committed_words == leader->word_size()) { + merged_committed_words += follower->committed_words(); + } + + // Leader survives, follower chunk is freed. Remove follower from vslist .. + leader->set_next_in_vs(follower->next_in_vs()); + if (follower->next_in_vs() != NULL) { + follower->next_in_vs()->set_prev_in_vs(leader); + } + + // .. and return follower chunk header to pool for reuse. + ChunkHeaderPool::pool().return_chunk_header(follower); + + // Leader level gets decreased (leader chunk doubles in size) but + // base address stays the same. + leader->dec_level(); + + // set commit boundary + leader->set_committed_words(merged_committed_words); + + // If the leader is now of root chunk size, stop merging + if (leader->is_root_chunk()) { + stop = true; + } + + result = c = leader; + + DEBUG_ONLY(leader->verify(true);) + + } + + } while (!stop); + +#ifdef ASSERT + verify(true); + if (result != NULL) { + result->verify(true); + if (result->level() < starting_level) { + DEBUG_ONLY(InternalStats::inc_num_chunk_merges();) + } + } +#endif // ASSERT + + return result; + +} + +// Given a chunk c, which must be "in use" and must not be a root chunk, attempt to +// enlarge it in place by claiming its trailing buddy. +// +// This will only work if c is the leader of the buddy pair and the trailing buddy is free. +// +// If successful, the follower chunk will be removed from the freelists, the leader chunk c will +// double in size (level decreased by one). +// +// On success, true is returned, false otherwise. +bool RootChunkArea::attempt_enlarge_chunk(Metachunk* c, MetachunkListCluster* freelists) { + + DEBUG_ONLY(check_pointer(c->base());) + assert(!c->is_root_chunk(), "Cannot be merged further."); + + // There is no real reason for this limitation other than it is not + // needed on free chunks since they should be merged already: + assert(c->is_in_use(), "Can only enlarge in use chunks."); + + DEBUG_ONLY(c->verify(false);) + + if (!c->is_leader()) { + return false; + } + + // We are the leader, so the buddy must follow us. + Metachunk* const buddy = c->next_in_vs(); + DEBUG_ONLY(buddy->verify(true);) + + // Of course buddy cannot be larger than us. + assert(buddy->level() >= c->level(), "Sanity"); + + // We cannot merge buddy in if it is not free... + if (!buddy->is_free()) { + return false; + } + + // ... nor if it is splintered. + if (buddy->level() != c->level()) { + return false; + } + + // Okay, lets enlarge c. + + log_trace(metaspace)("Enlarging chunk " METACHUNK_FULL_FORMAT " by merging in follower " METACHUNK_FULL_FORMAT ".", + METACHUNK_FULL_FORMAT_ARGS(c), METACHUNK_FULL_FORMAT_ARGS(buddy)); + + // the enlarged c is as far committed as possible: + size_t merged_committed_words = c->committed_words(); + if (merged_committed_words == c->word_size()) { + merged_committed_words += buddy->committed_words(); + } + + // Remove buddy from vs list... + Metachunk* successor = buddy->next_in_vs(); + if (successor != NULL) { + successor->set_prev_in_vs(c); + } + c->set_next_in_vs(successor); + + // .. and from freelist ... + freelists->remove(buddy); + + // .. and return its empty husk to the pool... + ChunkHeaderPool::pool().return_chunk_header(buddy); + + // Then decrease level of c. + c->dec_level(); + + // and correct committed words if needed. + c->set_committed_words(merged_committed_words); + + log_debug(metaspace)("Enlarged chunk " METACHUNK_FULL_FORMAT ".", METACHUNK_FULL_FORMAT_ARGS(c)); +// log_debug(metaspace)("Enlarged chunk " METACHUNK_FORMAT ".", METACHUNK_FORMAT_ARGS(c)); + + DEBUG_ONLY(verify(true)); + + return true; + +} + + +#ifdef ASSERT + +#define assrt_(cond, ...) \ + if (!(cond)) { \ + fdStream errst(2); \ + this->print_on(&errst); \ + vmassert(cond, __VA_ARGS__); \ + } + +void RootChunkArea::verify(bool slow) const { + + assert_is_aligned(_base, chklvl::MAX_CHUNK_BYTE_SIZE); + + // Iterate thru all chunks in this area. They must be ordered correctly, + // being adjacent to each other, and cover the complete area + int num_chunk = 0; + + if (_first_chunk != NULL) { + + assrt_(_first_chunk->prev_in_vs() == NULL, "Sanity"); + + const Metachunk* c = _first_chunk; + const Metachunk* c_last = NULL; + const MetaWord* expected_next_base = _base; + const MetaWord* const area_end = _base + word_size(); + + while (c != NULL) { + + assrt_(c->base() == expected_next_base, + "Chunk No. %d " METACHUNK_FORMAT " - unexpected base.", + num_chunk, METACHUNK_FORMAT_ARGS(c)); + + assrt_(c->base() >= base() && c->end() <= end(), + "chunk %d " METACHUNK_FORMAT " oob for this root area [" PTR_FORMAT ".." PTR_FORMAT ").", + num_chunk, METACHUNK_FORMAT_ARGS(c), p2i(base()), p2i(end())); + + assrt_(is_aligned(c->base(), c->word_size()), + "misaligned chunk %d " METACHUNK_FORMAT ".", num_chunk, METACHUNK_FORMAT_ARGS(c)); + + const Metachunk* const successor = c->next_in_vs(); + if (successor != NULL) { + assrt_(successor->prev_in_vs() == c, + "Chunk No. %d " METACHUNK_FORMAT " - vs link to successor " METACHUNK_FORMAT " broken.", num_chunk, + METACHUNK_FORMAT_ARGS(c), METACHUNK_FORMAT_ARGS(successor)); + assrt_(c->end() == successor->base(), + "areas between neighbor chunks do not connect: " + "this chunk %d " METACHUNK_FORMAT " and successor chunk %d " METACHUNK_FORMAT ".", + num_chunk, METACHUNK_FORMAT_ARGS(c), num_chunk + 1, METACHUNK_FORMAT_ARGS(successor)); + } + + if (c_last != NULL) { + assrt_(c->prev_in_vs() == c_last, + "Chunk No. %d " METACHUNK_FORMAT " - vs backlink invalid.", num_chunk, METACHUNK_FORMAT_ARGS(c)); + assrt_(c_last->end() == c->base(), + "areas between neighbor chunks do not connect: " + "previous chunk %d " METACHUNK_FORMAT " and this chunk %d " METACHUNK_FORMAT ".", + num_chunk - 1, METACHUNK_FORMAT_ARGS(c_last), num_chunk, METACHUNK_FORMAT_ARGS(c)); + } else { + assrt_(c->prev_in_vs() == NULL, + "unexpected back link: chunk %d " METACHUNK_FORMAT ".", + num_chunk, METACHUNK_FORMAT_ARGS(c)); + assrt_(c == _first_chunk, + "should be first: chunk %d " METACHUNK_FORMAT ".", + num_chunk, METACHUNK_FORMAT_ARGS(c)); + } + + c->verify(slow); // <- also checks alignment and level etc + + expected_next_base = c->end(); + c_last = c; + num_chunk ++; + + c = c->next_in_vs(); + + } + assrt_(expected_next_base == _base + word_size(), "Sanity"); + } + +} + +void RootChunkArea::verify_area_is_ideally_merged() const { + int num_chunk = 0; + for (const Metachunk* c = _first_chunk; c != NULL; c = c->next_in_vs()) { + if (!c->is_root_chunk() && c->is_free()) { + // If a chunk is free, it must not have a buddy which is also free, because + // those chunks should have been merged. + // In other words, a buddy shall be either in-use or splintered + // (which in turn would mean part of it are in use). + Metachunk* const buddy = c->is_leader() ? c->next_in_vs() : c->prev_in_vs(); + assrt_(buddy->is_in_use() || buddy->level() > c->level(), + "Chunk No. %d " METACHUNK_FORMAT " : missed merge opportunity with neighbor " METACHUNK_FORMAT ".", + num_chunk, METACHUNK_FORMAT_ARGS(c), METACHUNK_FORMAT_ARGS(buddy)); + } + num_chunk ++; + } +} + +#endif + +void RootChunkArea::print_on(outputStream* st) const { + + st->print(PTR_FORMAT ": ", p2i(base())); + if (_first_chunk != NULL) { + const Metachunk* c = _first_chunk; + // 01234567890123 + const char* letters_for_levels_cap = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const char* letters_for_levels = "abcdefghijklmnopqrstuvwxyz"; + while (c != NULL) { + const chklvl_t l = c->level(); + if (l >= 0 && (size_t)l < strlen(letters_for_levels)) { +// c->print_on(st); st->cr(); + st->print("%c", c->is_free() ? letters_for_levels[c->level()] : letters_for_levels_cap[c->level()]); + } else { + // Obviously garbage, but lets not crash. + st->print("?"); + } + c = c->next_in_vs(); + } + } else { + st->print(" (no chunks)"); + } + st->cr(); + +} + + +// Create an array of ChunkTree objects, all initialized to NULL, covering +// a given memory range. Memory range must be a multiple of root chunk size. +RootChunkAreaLUT::RootChunkAreaLUT(const MetaWord* base, size_t word_size) + : _base(base), + _num((int)(word_size / chklvl::MAX_CHUNK_WORD_SIZE)), + _arr(NULL) +{ + assert_is_aligned(word_size, chklvl::MAX_CHUNK_WORD_SIZE); + _arr = NEW_C_HEAP_ARRAY(RootChunkArea, _num, mtClass); + const MetaWord* this_base = _base; + for (int i = 0; i < _num; i ++) { + RootChunkArea* rca = new(_arr + i) RootChunkArea(this_base); + assert(rca == _arr + i, "Sanity"); + this_base += chklvl::MAX_CHUNK_WORD_SIZE; + } +} + +RootChunkAreaLUT::~RootChunkAreaLUT() { + for (int i = 0; i < _num; i ++) { + _arr[i].~RootChunkArea(); + } + FREE_C_HEAP_ARRAY(RootChunkArea, _arr); +} + +#ifdef ASSERT + +void RootChunkAreaLUT::verify(bool slow) const { + for (int i = 0; i < _num; i ++) { + check_pointer(_arr[i].base()); + _arr[i].verify(slow); + } +} + +#endif + +void RootChunkAreaLUT::print_on(outputStream* st) const { + for (int i = 0; i < _num; i ++) { + st->print("%2d:", i); + _arr[i].print_on(st); + } +} + + +} // end: namespace metaspace diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/rootChunkArea.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/rootChunkArea.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2019 SAP SE. All rights reserved. + * 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_MEMORY_METASPACE_ROOTCHUNKAREA_HPP +#define SHARE_MEMORY_METASPACE_ROOTCHUNKAREA_HPP + +#include "memory/allocation.hpp" +#include "memory/metaspace/chunkLevel.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" + +class outputStream; + +namespace metaspace { + +class Metachunk; +class MetachunkClosure; +class MetachunkListCluster; +class VirtualSpaceNode; + + +// RootChunkArea describes the chunk composition of a root-chunk-sized areal. +// + +class RootChunkArea { + + // The base address of this area. + const MetaWord* const _base; + + // The first chunk in this area; if this area is maximally + // folded, this is the root chunk covering the whole area size. + Metachunk* _first_chunk; + +public: + + RootChunkArea(const MetaWord* base); + ~RootChunkArea(); + + // Initialize: allocate a root node and a root chunk header; return the + // root chunk header. It will be partly initialized. + // Note: this just allocates a memory-less header; memory itself is allocated inside VirtualSpaceNode. + Metachunk* alloc_root_chunk_header(VirtualSpaceNode* node); + + // Given a chunk c, split it recursively until you get a chunk of the given target_level. + // + // The original chunk must not be part of a freelist. + // + // Returns pointer to the result chunk; the splitted-off chunks are added as + // free chunks to the freelists. + // + // Returns NULL if chunk cannot be split at least once. + Metachunk* split(chklvl_t target_level, Metachunk* c, MetachunkListCluster* freelists); + + // Given a chunk, attempt to merge it recursively with its neighboring chunks. + // + // If successful (merged at least once), returns address of + // the merged chunk; NULL otherwise. + // + // The merged chunks are removed from the freelists. + // + // !!! Please note that if this method returns a non-NULL value, the + // original chunk will be invalid and should not be accessed anymore! !!! + Metachunk* merge(Metachunk* c, MetachunkListCluster* freelists); + + // Given a chunk c, which must be "in use" and must not be a root chunk, attempt to + // enlarge it in place by claiming its trailing buddy. + // + // This will only work if c is the leader of the buddy pair and the trailing buddy is free. + // + // If successful, the follower chunk will be removed from the freelists, the leader chunk c will + // double in size (level decreased by one). + // + // On success, true is returned, false otherwise. + bool attempt_enlarge_chunk(Metachunk* c, MetachunkListCluster* freelists); + + // Returns true if all chunks in this area are free; false if not. + bool all_chunks_are_free() const; + + /// range /// + + const MetaWord* base() const { return _base; } + size_t word_size() const { return chklvl::MAX_CHUNK_WORD_SIZE; } + const MetaWord* end() const { return _base + word_size(); } + + // Direct access to the first chunk (use with care) + Metachunk* first_chunk() { return _first_chunk; } + const Metachunk* first_chunk() const { return _first_chunk; } + + //// Debug stuff //// + +#ifdef ASSERT + void check_pointer(const MetaWord* p) const { + assert(p >= _base && p < _base + word_size(), + "pointer " PTR_FORMAT " oob for this root area [" PTR_FORMAT ".." PTR_FORMAT ")", + p2i(p), p2i(_base), p2i(_base + word_size())); + } + void verify(bool slow) const; + + // This is a separate operation from verify(). We should be able to call verify() + // from almost anywhere, regardless of state, but verify_area_is_ideally_merged() + // can only be called outside split and merge ops. + void verify_area_is_ideally_merged() const; +#endif // ASSERT + + void print_on(outputStream* st) const; + +}; + + +/////////////////////// +// A lookup table for RootChunkAreas: given an address into a VirtualSpaceNode, +// it gives the RootChunkArea containing this address. +// To reduce pointer chasing, the LUT entries (of type RootChunkArea) are +// following this object. +class RootChunkAreaLUT { + + // Base address of the whole area. + const MetaWord* const _base; + + // Number of root chunk areas. + const int _num; + + // Array of RootChunkArea objects. + RootChunkArea* _arr; + +#ifdef ASSERT + void check_pointer(const MetaWord* p) const { + assert(p >= base() && p < base() + word_size(), "Invalid pointer"); + } +#endif + + // Given an address into this range, return the index into the area array for the + // area this address falls into. + int index_by_address(const MetaWord* p) const { + DEBUG_ONLY(check_pointer(p);) + int idx = (int)((p - base()) / chklvl::MAX_CHUNK_WORD_SIZE); + assert(idx >= 0 && idx < _num, "Sanity"); + return idx; + } + +public: + + RootChunkAreaLUT(const MetaWord* base, size_t word_size); + ~RootChunkAreaLUT(); + + // Given a memory address into the range this array covers, return the + // corresponding area object. If none existed at this position, create it + // on demand. + RootChunkArea* get_area_by_address(const MetaWord* p) const { + const int idx = index_by_address(p); + RootChunkArea* ra = _arr + idx; + DEBUG_ONLY(ra->check_pointer(p);) + return _arr + idx; + } + + // Access area by its index + int number_of_areas() const { return _num; } + RootChunkArea* get_area_by_index(int index) { assert(index >= 0 && index < _num, "oob"); return _arr + index; } + const RootChunkArea* get_area_by_index(int index) const { assert(index >= 0 && index < _num, "oob"); return _arr + index; } + + /// range /// + + const MetaWord* base() const { return _base; } + size_t word_size() const { return _num * chklvl::MAX_CHUNK_WORD_SIZE; } + const MetaWord* end() const { return _base + word_size(); } + + DEBUG_ONLY(void verify(bool slow) const;) + + void print_on(outputStream* st) const; + +}; + + +} // namespace metaspace + +#endif // SHARE_MEMORY_METASPACE_ROOTCHUNKAREA_HPP diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/runningCounters.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/runningCounters.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2019 SAP SE. All rights reserved. + * 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 "memory/metaspace/counter.hpp" +#include "memory/metaspace/runningCounters.hpp" +#include "memory/metaspace/virtualSpaceList.hpp" +#include "memory/metaspace/chunkManager.hpp" + +namespace metaspace { + +SizeAtomicCounter RunningCounters::_used_class_counter; +SizeAtomicCounter RunningCounters::_used_nonclass_counter; + +// Return reserved size, in words, for Metaspace +size_t RunningCounters::reserved_words() { + return reserved_words_class() + reserved_words_nonclass(); +} + +size_t RunningCounters::reserved_words_class() { + VirtualSpaceList* vs = VirtualSpaceList::vslist_class(); + return vs != NULL ? vs->reserved_words() : 0; +} + +size_t RunningCounters::reserved_words_nonclass() { + return VirtualSpaceList::vslist_nonclass()->reserved_words(); +} + +// Return total committed size, in words, for Metaspace +size_t RunningCounters::committed_words() { + return committed_words_class() + committed_words_nonclass(); +} + +size_t RunningCounters::committed_words_class() { + VirtualSpaceList* vs = VirtualSpaceList::vslist_class(); + return vs != NULL ? vs->committed_words() : 0; +} + +size_t RunningCounters::committed_words_nonclass() { + return VirtualSpaceList::vslist_nonclass()->committed_words(); +} + + +// ---- used chunks ----- + +// Returns size, in words, used for metadata. +size_t RunningCounters::used_words() { + return used_words_class() + used_words_nonclass(); +} + +size_t RunningCounters::used_words_class() { + return _used_class_counter.get(); +} + +size_t RunningCounters::used_words_nonclass() { + return _used_nonclass_counter.get(); +} + +// ---- free chunks ----- + +// Returns size, in words, of all chunks in all freelists. +size_t RunningCounters::free_chunks_words() { + return free_chunks_words_class() + free_chunks_words_nonclass(); +} + +size_t RunningCounters::free_chunks_words_class() { + ChunkManager* cm = ChunkManager::chunkmanager_class(); + return cm != NULL ? cm->total_word_size() : 0; +} + +size_t RunningCounters::free_chunks_words_nonclass() { + return ChunkManager::chunkmanager_nonclass()->total_word_size(); +} + +} // namespace metaspace + + diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/runningCounters.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/runningCounters.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019 SAP SE. All rights reserved. + * 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_MEMORY_METASPACE_RUNNINGCOUNTERS_HPP +#define SHARE_MEMORY_METASPACE_RUNNINGCOUNTERS_HPP + +#include "memory/allocation.hpp" +#include "memory/metaspace/counter.hpp" + +namespace metaspace { + +class ClassLoaderMetaspace; + +// These are running counters for some basic Metaspace statistics. +// Their value can be obtained quickly without locking. + +class RunningCounters : public AllStatic { + + friend class ClassLoaderMetaspace; + + // ---- in-use chunks ---- + + // Used space, in words. + // (Note that the used counter is on the hot path of Metaspace allocation. + // Do we really need it? We may get by with capacity only and get more details + // with get_statistics_slow().) + static SizeAtomicCounter _used_class_counter; + static SizeAtomicCounter _used_nonclass_counter; + +public: + + // ---- virtual memory ----- + + // Return reserved size, in words, for Metaspace + static size_t reserved_words(); + static size_t reserved_words_class(); + static size_t reserved_words_nonclass(); + + // Return total committed size, in words, for Metaspace + static size_t committed_words(); + static size_t committed_words_class(); + static size_t committed_words_nonclass(); + + + // ---- used chunks ----- + + // Returns size, in words, used for metadata. + static size_t used_words(); + static size_t used_words_class(); + static size_t used_words_nonclass(); + + // ---- free chunks ----- + + // Returns size, in words, of all chunks in all freelists. + static size_t free_chunks_words(); + static size_t free_chunks_words_class(); + static size_t free_chunks_words_nonclass(); + + // Direct access to the counters. + static SizeAtomicCounter* used_nonclass_counter() { return &_used_nonclass_counter; } + static SizeAtomicCounter* used_class_counter() { return &_used_class_counter; } + +}; + +} // namespace metaspace + +#endif // SHARE_MEMORY_METASPACE_RUNNINGCOUNTERS_HPP + + diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/settings.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/settings.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2019 SAP SE. All rights reserved. + * 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 +#include "precompiled.hpp" + +#include "logging/log.hpp" +#include "logging/logStream.hpp" + +#include "memory/metaspace/chunkLevel.hpp" +#include "memory/metaspace/settings.hpp" + +#include "utilities/globalDefinitions.hpp" +#include "utilities/debug.hpp" + +namespace metaspace { + +size_t Settings::_commit_granule_bytes = 0; +size_t Settings::_commit_granule_words = 0; +bool Settings::_newborn_root_chunks_are_fully_committed = false; +size_t Settings::_committed_words_on_fresh_chunks = 0; + +bool Settings::_enlarge_chunks_in_place = false; +size_t Settings::_enlarge_chunks_in_place_max_word_size = 0; + +bool Settings::_uncommit_on_return = false; +size_t Settings::_uncommit_on_return_min_word_size = 0; + +bool Settings::_delete_nodes_on_purge = false; +bool Settings::_uncommit_on_purge = false; +size_t Settings::_uncommit_on_purge_min_word_size = 0; + + +void Settings::initialize(strategy_t strat) { + + switch (strat) { + case strategy_no_reclaim: + + log_info(metaspace)("Initialized with strategy: no reclaim."); + + _commit_granule_bytes = MAX2((size_t)os::vm_page_size(), 64 * K); + _commit_granule_words = _commit_granule_bytes / BytesPerWord; + + _newborn_root_chunks_are_fully_committed = true; + + _committed_words_on_fresh_chunks = chklvl::MAX_CHUNK_WORD_SIZE; + + _uncommit_on_return = false; + _uncommit_on_return_min_word_size = 3; // does not matter; should not be used resp. assert when used. + + _delete_nodes_on_purge = false; + _uncommit_on_purge = false; + _uncommit_on_purge_min_word_size = 3; // does not matter; should not be used resp. assert when used. + + break; + + case strategy_aggressive_reclaim: + + log_info(metaspace)("Initialized with strategy: aggressive reclaim."); + + // Set the granule size rather small; may increase + // mapping fragmentation but also increase chance to uncommit. + _commit_granule_bytes = MAX2((size_t)os::vm_page_size(), 16 * K); + _commit_granule_words = _commit_granule_bytes / BytesPerWord; + + _newborn_root_chunks_are_fully_committed = false; + + // When handing out fresh chunks, only commit the minimum sensible amount (0 would be possible + // but not make sense since the chunk is immediately used for allocation after being handed out, so the + // first granule would be committed right away anyway). + _committed_words_on_fresh_chunks = _commit_granule_words; + + _uncommit_on_return = true; + _uncommit_on_return_min_word_size = _commit_granule_words; + + _delete_nodes_on_purge = true; + _uncommit_on_purge = true; + _uncommit_on_purge_min_word_size = _commit_granule_words; // does not matter; should not be used resp. assert when used. + + break; + + case strategy_balanced_reclaim: + + log_info(metaspace)("Initialized with strategy: balanced reclaim."); + + _commit_granule_bytes = MAX2((size_t)os::vm_page_size(), 64 * K); + _commit_granule_words = _commit_granule_bytes / BytesPerWord; + + _newborn_root_chunks_are_fully_committed = false; + + // When handing out fresh chunks, only commit the minimum sensible amount (0 would be possible + // but not make sense since the chunk is immediately used for allocation after being handed out, so the + // first granule would be committed right away anyway). + _committed_words_on_fresh_chunks = _commit_granule_words; + + _uncommit_on_return = true; + _uncommit_on_return_min_word_size = _commit_granule_words; + + _delete_nodes_on_purge = true; + _uncommit_on_purge = true; + _uncommit_on_purge_min_word_size = _commit_granule_words; + + break; + + } + + // Since this has nothing to do with reclaiming, set it independently from the + // strategy. This is rather arbitrarily choosen. + _enlarge_chunks_in_place = true; + _enlarge_chunks_in_place_max_word_size = 256 * K; + + + // Sanity checks. + guarantee(commit_granule_words() <= chklvl::MAX_CHUNK_WORD_SIZE, "Too large granule size"); + guarantee(is_power_of_2(commit_granule_words()), "granule size must be a power of 2"); + + LogStream ls(Log(metaspace)::info()); + Settings::print_on(&ls); + +} + +void Settings::print_on(outputStream* st) { + + st->print_cr(" - commit_granule_bytes: " SIZE_FORMAT ".", commit_granule_bytes()); + st->print_cr(" - commit_granule_words: " SIZE_FORMAT ".", commit_granule_words()); + + st->print_cr(" - newborn_root_chunks_are_fully_committed: %d.", (int)newborn_root_chunks_are_fully_committed()); + st->print_cr(" - committed_words_on_fresh_chunks: " SIZE_FORMAT ".", committed_words_on_fresh_chunks()); + + st->print_cr(" - virtual_space_node_default_size: " SIZE_FORMAT ".", virtual_space_node_default_word_size()); + st->print_cr(" - allocation_from_dictionary_limit: " SIZE_FORMAT ".", allocation_from_dictionary_limit()); + + st->print_cr(" - enlarge_chunks_in_place: %d.", (int)enlarge_chunks_in_place()); + st->print_cr(" - enlarge_chunks_in_place_max_word_size: " SIZE_FORMAT ".", enlarge_chunks_in_place_max_word_size()); + + st->print_cr(" - uncommit_on_return: %d.", (int)uncommit_on_return()); + st->print_cr(" - uncommit_on_return_min_word_size: " SIZE_FORMAT ".", uncommit_on_return_min_word_size()); + + st->print_cr(" - delete_nodes_on_purge: %d.", (int)delete_nodes_on_purge()); + + st->print_cr(" - uncommit_on_purge: %d.", (int)uncommit_on_purge()); + st->print_cr(" - uncommit_on_purge_min_word_size: " SIZE_FORMAT ".", uncommit_on_purge_min_word_size()); + + +} + +} // namespace metaspace + diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/settings.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/memory/metaspace/settings.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2019 SAP SE. All rights reserved. + * 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_MEMORY_METASPACE_CONSTANTS_HPP +#define SHARE_MEMORY_METASPACE_CONSTANTS_HPP + +#include "memory/allocation.hpp" +#include "memory/metaspace/chunkLevel.hpp" +#include "utilities/globalDefinitions.hpp" + +namespace metaspace { + +class Settings : public AllStatic { + + // Granularity, in bytes, metaspace is committed with. + static size_t _commit_granule_bytes; + + // Granularity, in words, metaspace is committed with. + static size_t _commit_granule_words; + + // Whether or not commit new-born root chunks thru after creation. + static bool _newborn_root_chunks_are_fully_committed; + + // When a chunk is handed out by the ChunkManager to a class loader, how much + // of a chunk should be committed up-front? + // Size in words. Will be rounded up to the nearest multiple of commit_granule_words. + // (Note: 0 is possible but inefficient, since it will cause the ClassLoaderMetaspace + // to commit the first granule right away anyway, so nothing is saved. + // chklvl::MAX_CHUNK_WORD_SIZE pretty much means every chunk is committed thru + // from the start. + static size_t _committed_words_on_fresh_chunks; + + // The default size of a non-class VirtualSpaceNode (unless created differently). + // Must be a multiple of the root chunk size. + static const size_t _virtual_space_node_default_word_size = chklvl::MAX_CHUNK_WORD_SIZE * 2; // lets go with 8mb virt size. Seems a good compromise betw. virt and mapping fragmentation. + + static const size_t _allocation_from_dictionary_limit = 4 * K; + + // When allocating from a chunk, if the remaining area in the chunk is too small to hold + // the requested size, we attempt to double the chunk size in place... + static bool _enlarge_chunks_in_place; + + // .. but we do only do this for chunks below a given size to prevent unnecessary memory blowup. + static size_t _enlarge_chunks_in_place_max_word_size; + + // If true, chunks are uncommitted after gc (when metaspace is purged). + static bool _uncommit_on_return; + + // If true, vsnodes which only contain free chunks will be deleted (purged) as part of a gc. + static bool _delete_nodes_on_purge; + + // If _uncommit_on_return is true: + // Minimum word size a chunk has to have after returning and merging with neighboring free chunks + // to be candidate for uncommitting. Must be a multiple of and not smaller than commit granularity. + static size_t _uncommit_on_return_min_word_size; + + // If true, chunks are uncommitted after gc (when metaspace is purged). + static bool _uncommit_on_purge; + + // If _uncommit_on_purge is true: + // Minimum word size of an area to be candidate for uncommitting. + // Must be a multiple of and not smaller than commit granularity. + static size_t _uncommit_on_purge_min_word_size; + +public: + + static size_t commit_granule_bytes() { return _commit_granule_bytes; } + static size_t commit_granule_words() { return _commit_granule_words; } + static bool newborn_root_chunks_are_fully_committed() { return _newborn_root_chunks_are_fully_committed; } + static size_t committed_words_on_fresh_chunks() { return _committed_words_on_fresh_chunks; } + static size_t virtual_space_node_default_word_size() { return _virtual_space_node_default_word_size; } + static size_t allocation_from_dictionary_limit() { return _allocation_from_dictionary_limit; } + static bool enlarge_chunks_in_place() { return _enlarge_chunks_in_place; } + static size_t enlarge_chunks_in_place_max_word_size() { return _enlarge_chunks_in_place_max_word_size; } + static bool uncommit_on_return() { return _uncommit_on_return; } + static size_t uncommit_on_return_min_word_size() { return _uncommit_on_return_min_word_size; } + static bool delete_nodes_on_purge() { return _delete_nodes_on_purge; } + static bool uncommit_on_purge() { return _uncommit_on_purge; } + static size_t uncommit_on_purge_min_word_size() { return _uncommit_on_purge_min_word_size; } + + // Describes a group of settings + enum strategy_t { + + // Do not uncommit chunks. New chunks are completely committed thru from the start. + strategy_no_reclaim, + + // Uncommit very aggressively. + // - a rather small granule size of 16K + // - New chunks are committed for one granule size + // - returned chunks are uncommitted whenever possible + strategy_aggressive_reclaim, + + // Uncommit, but try to strike a balance with CPU load + strategy_balanced_reclaim + + }; + + static void initialize(strategy_t theme); + + static void print_on(outputStream* st); + +}; + +} // namespace metaspace + +#endif // SHARE_MEMORY_METASPACE_BLOCKFREELIST_HPP diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/smallBlocks.cpp --- a/src/hotspot/share/memory/metaspace/smallBlocks.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace/smallBlocks.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -31,8 +31,8 @@ void SmallBlocks::print_on(outputStream* st) const { st->print_cr("SmallBlocks:"); - for (uint i = _small_block_min_size; i < _small_block_max_size; i++) { - uint k = i - _small_block_min_size; + for (uint i = _small_block_min_word_size; i < _small_block_max_word_size; i++) { + uint k = i - _small_block_min_word_size; st->print_cr("small_lists size " SIZE_FORMAT " count " SIZE_FORMAT, _small_lists[k].size(), _small_lists[k].count()); } } @@ -41,8 +41,8 @@ // Returns the total size, in words, of all blocks, across all block sizes. size_t SmallBlocks::total_size() const { size_t result = 0; - for (uint i = _small_block_min_size; i < _small_block_max_size; i++) { - uint k = i - _small_block_min_size; + for (uint i = _small_block_min_word_size; i < _small_block_max_word_size; i++) { + uint k = i - _small_block_min_word_size; result = result + _small_lists[k].count() * _small_lists[k].size(); } return result; @@ -51,8 +51,8 @@ // Returns the total number of all blocks across all block sizes. uintx SmallBlocks::total_num_blocks() const { uintx result = 0; - for (uint i = _small_block_min_size; i < _small_block_max_size; i++) { - uint k = i - _small_block_min_size; + for (uint i = _small_block_min_word_size; i < _small_block_max_word_size; i++) { + uint k = i - _small_block_min_word_size; result = result + _small_lists[k].count(); } return result; diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/smallBlocks.hpp --- a/src/hotspot/share/memory/metaspace/smallBlocks.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace/smallBlocks.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -28,6 +28,7 @@ #include "memory/allocation.hpp" #include "memory/binaryTreeDictionary.hpp" #include "memory/metaspace/metablock.hpp" +#include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" class outputStream; @@ -36,22 +37,27 @@ class SmallBlocks : public CHeapObj { - const static uint _small_block_max_size = sizeof(TreeChunk >)/HeapWordSize; + const static uint _small_block_max_byte_size = sizeof(TreeChunk >); + const static uint _small_block_max_word_size = _small_block_max_byte_size / BytesPerWord; + STATIC_ASSERT(_small_block_max_word_size * BytesPerWord == _small_block_max_byte_size); + // Note: this corresponds to the imposed miminum allocation size, see SpaceManager::get_allocation_word_size() - const static uint _small_block_min_size = sizeof(Metablock)/HeapWordSize; + const static uint _small_block_min_byte_size = sizeof(Metablock); + const static uint _small_block_min_word_size = _small_block_min_byte_size / BytesPerWord; + STATIC_ASSERT(_small_block_min_word_size * BytesPerWord == _small_block_min_byte_size); private: - FreeList _small_lists[_small_block_max_size - _small_block_min_size]; + FreeList _small_lists[_small_block_max_word_size - _small_block_min_word_size]; FreeList& list_at(size_t word_size) { - assert(word_size >= _small_block_min_size, "There are no metaspace objects less than %u words", _small_block_min_size); - return _small_lists[word_size - _small_block_min_size]; + assert(word_size >= _small_block_min_word_size, "There are no metaspace objects less than %u words", _small_block_min_word_size); + return _small_lists[word_size - _small_block_min_word_size]; } public: SmallBlocks() { - for (uint i = _small_block_min_size; i < _small_block_max_size; i++) { - uint k = i - _small_block_min_size; + for (uint i = _small_block_min_word_size; i < _small_block_max_word_size; i++) { + uint k = i - _small_block_min_word_size; _small_lists[k].set_size(i); } } @@ -62,8 +68,10 @@ // Returns the total number of all blocks across all block sizes. uintx total_num_blocks() const; - static uint small_block_max_size() { return _small_block_max_size; } - static uint small_block_min_size() { return _small_block_min_size; } + static uint small_block_max_byte_size() { return _small_block_max_byte_size; } + static uint small_block_max_word_size() { return _small_block_max_word_size; } + static uint small_block_min_byte_size() { return _small_block_min_byte_size; } + static uint small_block_min_word_size() { return _small_block_min_word_size; } MetaWord* get_block(size_t word_size) { if (list_at(word_size).count() > 0) { diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/spaceManager.cpp --- a/src/hotspot/share/memory/metaspace/spaceManager.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace/spaceManager.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -1,5 +1,6 @@ /* * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 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 @@ -26,501 +27,402 @@ #include "logging/log.hpp" #include "logging/logStream.hpp" #include "memory/metaspace/chunkManager.hpp" +#include "memory/metaspace/internStat.hpp" #include "memory/metaspace/metachunk.hpp" #include "memory/metaspace/metaDebug.hpp" #include "memory/metaspace/metaspaceCommon.hpp" +#include "memory/metaspace/metaspaceStatistics.hpp" +#include "memory/metaspace/smallBlocks.hpp" #include "memory/metaspace/spaceManager.hpp" #include "memory/metaspace/virtualSpaceList.hpp" #include "runtime/atomic.hpp" #include "runtime/init.hpp" #include "services/memoryService.hpp" +#include "utilities/align.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" namespace metaspace { -#define assert_counter(expected_value, real_value, msg) \ - assert( (expected_value) == (real_value), \ - "Counter mismatch (%s): expected " SIZE_FORMAT \ - ", but got: " SIZE_FORMAT ".", msg, expected_value, \ - real_value); +// Given a net allocation word size, return the raw word size +// we need to actually allocate in order to: +// 1) be able to deallocate the allocation - deallocated blocks are stored either in SmallBlocks +// (an array of short lists) or, beyond a certain size, in a dictionary tree. +// For that to work the allocated block must be at least three words. +// 2) be aligned to sizeof(void*) -// SpaceManager methods +// Note: externally visible for gtests. +//static +size_t get_raw_allocation_word_size(size_t net_word_size) { -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) - }; + size_t byte_size = net_word_size * BytesPerWord; + byte_size = MAX2(byte_size, (size_t)SmallBlocks::small_block_min_byte_size()); + byte_size = align_up(byte_size, Metachunk::allocation_alignment_bytes); - // Adjust up to one of the fixed chunk sizes ... - for (size_t i = 0; i < ARRAY_SIZE(chunk_sizes); i++) { - if (requested <= chunk_sizes[i]) { - return chunk_sizes[i]; - } + size_t word_size = byte_size / BytesPerWord; + assert(word_size * BytesPerWord == byte_size, "Sanity"); + + return word_size; + +} + +static const size_t highest_possible_delta_between_raw_and_net_size = get_raw_allocation_word_size(1) - 1; + +// The inverse function to get_raw_allocation_word_size: Given a raw size, return the max net word size +// fitting into it. +static size_t get_net_allocation_word_size(size_t raw_word_size) { + + size_t byte_size = raw_word_size * BytesPerWord; + byte_size = align_down(byte_size, Metachunk::allocation_alignment_bytes); + if (byte_size < SmallBlocks::small_block_min_byte_size()) { + return 0; + } + return byte_size / BytesPerWord; + +} + +// Given a requested word size, will allocate a chunk large enough to at least fit that +// size, but may be larger according to internal heuristics. +// +// On success, it will replace the current chunk with the newly allocated one, which will +// become the current chunk. The old current chunk should be retired beforehand. +// +// May fail if we could not allocate a new chunk. In that case the current chunk remains +// unchanged and false is returned. +bool SpaceManager::allocate_new_current_chunk(size_t requested_word_size) { + + assert_lock_strong(lock()); + + guarantee(requested_word_size < chklvl::MAX_CHUNK_WORD_SIZE, + "Requested size too large (" SIZE_FORMAT ").", requested_word_size); + + // If we have a current chunk, it should have been retired (almost empty) beforehand. + // See: retire_current_chunk(). + assert(current_chunk() == NULL || current_chunk()->free_below_committed_words() <= 10, "Must retire chunk beforehand"); + + const chklvl_t min_level = chklvl::level_fitting_word_size(requested_word_size); + chklvl_t pref_level = _chunk_alloc_sequence->get_next_chunk_level(_chunks.size()); + + if (pref_level > min_level) { + pref_level = min_level; } - // ... or return the size as a humongous chunk. - return requested; + Metachunk* c = _chunk_manager->get_chunk(min_level, pref_level); + if (c == NULL) { + log_debug(metaspace)("SpaceManager %s: failed to allocate new chunk for requested word size " SIZE_FORMAT ".", + _name, requested_word_size); + return false; + } + + assert(c->is_in_use(), "Wrong chunk state."); + assert(c->level() <= min_level && c->level() >= pref_level, "Sanity"); + + _chunks.add(c); + + log_debug(metaspace)("SpaceManager %s: allocated new chunk " METACHUNK_FORMAT " for requested word size " SIZE_FORMAT ".", + _name, METACHUNK_FORMAT_ARGS(c), requested_word_size); + + return c; + } -size_t SpaceManager::adjust_initial_chunk_size(size_t requested) const { - return adjust_initial_chunk_size(requested, is_class()); +void SpaceManager::create_block_freelist() { + assert(_block_freelist == NULL, "Only call once"); + _block_freelist = new BlockFreelist(); } -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::UnsafeAnonymousMetaspaceType: 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::UnsafeAnonymousMetaspaceType: requested = SpecializedChunk; break; - case Metaspace::ReflectionMetaspaceType: requested = SpecializedChunk; break; - default: requested = SmallChunk; break; - } +void SpaceManager::add_allocation_to_block_freelist(MetaWord* p, size_t word_size) { + if (_block_freelist == NULL) { + _block_freelist = new BlockFreelist(); // Create only on demand } - - // Adjust to one of the fixed chunk sizes (unless humongous) - 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; + _block_freelist->return_block(p, word_size); } -void SpaceManager::locked_print_chunks_in_use_on(outputStream* st) const { - - for (ChunkIndex i = ZeroIndex; i < NumberOfInUseLists; i = next_chunk_index(i)) { - st->print("SpaceManager: " UINTX_FORMAT " %s chunks.", - num_chunks_by_type(i), chunk_size_name(i)); - } - - chunk_manager()->locked_print_free_chunks(st); -} - -size_t SpaceManager::calc_chunk_size(size_t word_size) { - - // Decide between a small chunk and a medium chunk. Up to - // _small_chunk_limit small chunks can be allocated. - // After that a medium chunk is preferred. - size_t chunk_word_size; - - // Special case for unsafe anonymous metadata space. - // UnsafeAnonymous metadata space is usually small since it is used for - // class loader data's whose life cycle is governed by one class such as an - // unsafe anonymous class. The majority within 1K - 2K range and - // rarely about 4K (64-bits JVM). - // Instead of jumping to SmallChunk after initial chunk exhausted, keeping allocation - // from SpecializeChunk up to _anon_or_delegating_metadata_specialize_chunk_limit (4) - // reduces space waste from 60+% to around 30%. - if ((_space_type == Metaspace::UnsafeAnonymousMetaspaceType || _space_type == Metaspace::ReflectionMetaspaceType) && - _mdtype == Metaspace::NonClassType && - num_chunks_by_type(SpecializedIndex) < anon_and_delegating_metadata_specialize_chunk_limit && - word_size + Metachunk::overhead() <= SpecializedChunk) { - return SpecializedChunk; - } - - if (num_chunks_by_type(MediumIndex) == 0 && - num_chunks_by_type(SmallIndex) < small_chunk_limit) { - chunk_word_size = (size_t) small_chunk_size(); - if (word_size + Metachunk::overhead() > small_chunk_size()) { - chunk_word_size = medium_chunk_size(); - } - } else { - chunk_word_size = medium_chunk_size(); - } - - // Might still need a humongous chunk. Enforce - // humongous allocations sizes to be aligned up to - // the smallest chunk size. - size_t if_humongous_sized_chunk = - align_up(word_size + Metachunk::overhead(), - smallest_chunk_size()); - chunk_word_size = - MAX2((size_t) chunk_word_size, if_humongous_sized_chunk); - - assert(!SpaceManager::is_humongous(word_size) || - chunk_word_size == if_humongous_sized_chunk, - "Size calculation is wrong, word_size " SIZE_FORMAT - " chunk_word_size " SIZE_FORMAT, - word_size, chunk_word_size); - Log(gc, metaspace, alloc) log; - if (log.is_trace() && SpaceManager::is_humongous(word_size)) { - log.trace("Metadata humongous allocation:"); - log.trace(" word_size " PTR_FORMAT, word_size); - log.trace(" chunk_word_size " PTR_FORMAT, chunk_word_size); - log.trace(" chunk overhead " PTR_FORMAT, Metachunk::overhead()); - } - return chunk_word_size; -} - -void SpaceManager::track_metaspace_memory_usage() { - if (is_init_completed()) { - if (is_class()) { - MemoryService::track_compressed_class_memory_usage(); - } - MemoryService::track_metaspace_memory_usage(); - } -} - -MetaWord* SpaceManager::grow_and_allocate(size_t word_size) { - assert_lock_strong(_lock); - assert(vs_list()->current_virtual_space() != NULL, - "Should have been set"); - assert(current_chunk() == NULL || - current_chunk()->allocate(word_size) == NULL, - "Don't need to expand"); - MutexLocker cl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); - - if (log_is_enabled(Trace, gc, metaspace, freelist)) { - size_t words_left = 0; - size_t words_used = 0; - if (current_chunk() != NULL) { - words_left = current_chunk()->free_word_size(); - words_used = current_chunk()->used_word_size(); - } - log_trace(gc, metaspace, freelist)("SpaceManager::grow_and_allocate for " SIZE_FORMAT " words " SIZE_FORMAT " words used " SIZE_FORMAT " words left", - word_size, words_used, words_left); - } - - // Get another chunk - size_t chunk_word_size = calc_chunk_size(word_size); - Metachunk* next = get_new_chunk(chunk_word_size); - - MetaWord* mem = NULL; - - // If a chunk was available, add it to the in-use chunk list - // and do an allocation from it. - if (next != NULL) { - // Add to this manager's list of chunks in use. - // If the new chunk is humongous, it was created to serve a single large allocation. In that - // case it usually makes no sense to make it the current chunk, since the next allocation would - // need to allocate a new chunk anyway, while we would now prematurely retire a perfectly - // good chunk which could be used for more normal allocations. - bool make_current = true; - if (next->get_chunk_type() == HumongousIndex && - current_chunk() != NULL) { - make_current = false; - } - add_chunk(next, make_current); - mem = next->allocate(word_size); - } - - // Track metaspace memory usage statistic. - track_metaspace_memory_usage(); - - return mem; -} - -void SpaceManager::print_on(outputStream* st) const { - SpaceManagerStatistics stat; - add_to_statistics(&stat); // will lock _lock. - stat.print_on(st, 1*K, false); -} - -SpaceManager::SpaceManager(Metaspace::MetadataType mdtype, - Metaspace::MetaspaceType space_type,// - Mutex* lock) : - _lock(lock), - _mdtype(mdtype), - _space_type(space_type), - _chunk_list(NULL), - _current_chunk(NULL), - _overhead_words(0), - _capacity_words(0), - _used_words(0), - _block_freelists(NULL) { - Metadebug::init_allocation_fail_alot_count(); - memset(_num_chunks_by_type, 0, sizeof(_num_chunks_by_type)); - log_trace(gc, metaspace, freelist)("SpaceManager(): " PTR_FORMAT, p2i(this)); -} - -void SpaceManager::account_for_new_chunk(const Metachunk* new_chunk) { - - assert_lock_strong(MetaspaceExpand_lock); - - _capacity_words += new_chunk->word_size(); - _overhead_words += Metachunk::overhead(); - DEBUG_ONLY(new_chunk->verify()); - _num_chunks_by_type[new_chunk->get_chunk_type()] ++; - - // Adjust global counters: - MetaspaceUtils::inc_capacity(mdtype(), new_chunk->word_size()); - MetaspaceUtils::inc_overhead(mdtype(), Metachunk::overhead()); -} - -void SpaceManager::account_for_allocation(size_t words) { - // Note: we should be locked with the ClassloaderData-specific metaspace lock. - // We may or may not be locked with the global metaspace expansion lock. - assert_lock_strong(lock()); - - // Add to the per SpaceManager totals. This can be done non-atomically. - _used_words += words; - - // Adjust global counters. This will be done atomically. - MetaspaceUtils::inc_used(mdtype(), words); -} - -void SpaceManager::account_for_spacemanager_death() { - - assert_lock_strong(MetaspaceExpand_lock); - - MetaspaceUtils::dec_capacity(mdtype(), _capacity_words); - MetaspaceUtils::dec_overhead(mdtype(), _overhead_words); - MetaspaceUtils::dec_used(mdtype(), _used_words); +SpaceManager::SpaceManager(ChunkManager* chunk_manager, + const ChunkAllocSequence* alloc_sequence, + Mutex* lock, + SizeAtomicCounter* total_used_words_counter, + const char* name) +: _lock(lock), + _chunk_manager(chunk_manager), + _chunk_alloc_sequence(alloc_sequence), + _chunks(), + _block_freelist(NULL), + _total_used_words_counter(total_used_words_counter), + _name(name) +{ } SpaceManager::~SpaceManager() { - // This call this->_lock which can't be done while holding MetaspaceExpand_lock - DEBUG_ONLY(verify_metrics()); + MutexLocker fcl(lock(), Mutex::_no_safepoint_check_flag); + Metachunk* c = _chunks.first(); + Metachunk* c2 = NULL; + while(c) { + // c may become invalid. Take care while iterating. + c2 = c->next(); + _total_used_words_counter->decrement_by(c->used_words()); + _chunks.remove(c); + _chunk_manager->return_chunk(c); + c = c2; + } - MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); + DEBUG_ONLY(chunk_manager()->verify(true);) - account_for_spacemanager_death(); + delete _block_freelist; - Log(gc, metaspace, freelist) log; - if (log.is_trace()) { - log.trace("~SpaceManager(): " PTR_FORMAT, p2i(this)); - ResourceMark rm; - LogStream ls(log.trace()); - locked_print_chunks_in_use_on(&ls); - if (block_freelists() != NULL) { - block_freelists()->print_on(&ls); +} + +// The remaining committed free space in the current chunk is chopped up and stored in the block +// free list for later use. As a result, the current chunk will remain current but completely +// used up. This is a preparation for calling allocate_new_current_chunk(). +void SpaceManager::retire_current_chunk() { + assert_lock_strong(lock()); + + Metachunk* c = current_chunk(); + assert(c != NULL, "Sanity"); + + log_debug(metaspace)("SpaceManager %s: retiring chunk " METACHUNK_FULL_FORMAT ".", + _name, METACHUNK_FULL_FORMAT_ARGS(c)); + + // Side note: + // In theory it could happen that we are asked to retire a completely empty chunk. This may be the + // result of rolled back allocations (see deallocate in place) and a lot of luck. + // But since these cases should be exceedingly rare, we do not handle them special in order to keep + // the code simple. + + size_t raw_remaining_words = c->free_below_committed_words(); + size_t net_remaining_words = get_net_allocation_word_size(raw_remaining_words); + if (net_remaining_words > 0) { + bool did_hit_limit = false; + MetaWord* ptr = c->allocate(net_remaining_words, &did_hit_limit); + assert(ptr != NULL && did_hit_limit == false, "Should have worked"); + add_allocation_to_block_freelist(ptr, net_remaining_words); + _total_used_words_counter->increment_by(net_remaining_words); + } + + // After this operation: the current chunk should have (almost) no free committed space left. + assert(current_chunk()->free_below_committed_words() <= highest_possible_delta_between_raw_and_net_size, + "Chunk retiring did not work (current chunk " METACHUNK_FULL_FORMAT ").", + METACHUNK_FULL_FORMAT_ARGS(current_chunk())); + +} + +// Allocate memory from Metaspace. +// 1) Attempt to allocate from the dictionary of deallocated blocks. +// 2) Attempt to allocate from the current chunk. +// 3) Attempt to enlarge the current chunk in place if it is too small. +// 4) Attempt to get a new chunk and allocate from that chunk. +// At any point, if we hit a commit limit, we return NULL. +MetaWord* SpaceManager::allocate(size_t requested_word_size) { + + MutexLocker cl(lock(), Mutex::_no_safepoint_check_flag); + + const size_t raw_word_size = get_raw_allocation_word_size(requested_word_size); + + log_trace(metaspace)("SpaceManager %s: requested " SIZE_FORMAT " words, " + "raw word size: " SIZE_FORMAT ".", + _name, requested_word_size, raw_word_size); + + MetaWord* p = NULL; + + bool did_hit_limit = false; + + // Allocate first chunk if needed. + if (current_chunk() == NULL) { + if (allocate_new_current_chunk(raw_word_size) == false) { + did_hit_limit = true; + } else { + assert(current_chunk() != NULL && current_chunk()->free_words() >= raw_word_size, "Sanity"); } } - // Add all the chunks in use by this space manager - // to the global list of free chunks. - - // Follow each list of chunks-in-use and add them to the - // free lists. Each list is NULL terminated. - chunk_manager()->return_chunk_list(chunk_list()); -#ifdef ASSERT - _chunk_list = NULL; - _current_chunk = NULL; -#endif - -#ifdef ASSERT - EVERY_NTH(VerifyMetaspaceInterval) - chunk_manager()->locked_verify(true); - END_EVERY_NTH -#endif - - if (_block_freelists != NULL) { - delete _block_freelists; - } -} - -void SpaceManager::deallocate(MetaWord* p, size_t word_size) { - assert_lock_strong(lock()); - // Allocations and deallocations are in raw_word_size - size_t raw_word_size = get_allocation_word_size(word_size); - // Lazily create a block_freelist - if (block_freelists() == NULL) { - _block_freelists = new BlockFreelist(); - } - block_freelists()->return_block(p, raw_word_size); - DEBUG_ONLY(Atomic::inc(&(g_internal_statistics.num_deallocs))); -} - -// Adds a chunk to the list of chunks in use. -void SpaceManager::add_chunk(Metachunk* new_chunk, bool make_current) { - - assert_lock_strong(_lock); - assert(new_chunk != NULL, "Should not be NULL"); - assert(new_chunk->next() == NULL, "Should not be on a list"); - - new_chunk->reset_empty(); - - // Find the correct list and and set the current - // chunk for that list. - ChunkIndex index = chunk_manager()->list_index(new_chunk->word_size()); - - if (make_current) { - // If we are to make the chunk current, retire the old current chunk and replace - // it with the new chunk. - retire_current_chunk(); - set_current_chunk(new_chunk); - } - - // Add the new chunk at the head of its respective chunk list. - new_chunk->set_next(_chunk_list); - _chunk_list = new_chunk; - - // Adjust counters. - account_for_new_chunk(new_chunk); - - assert(new_chunk->is_empty(), "Not ready for reuse"); - Log(gc, metaspace, freelist) log; - if (log.is_trace()) { - log.trace("SpaceManager::added chunk: "); - ResourceMark rm; - LogStream ls(log.trace()); - new_chunk->print_on(&ls); - chunk_manager()->locked_print_free_chunks(&ls); - } -} - -void SpaceManager::retire_current_chunk() { - if (current_chunk() != NULL) { - size_t remaining_words = current_chunk()->free_word_size(); - if (remaining_words >= SmallBlocks::small_block_min_size()) { - MetaWord* ptr = current_chunk()->allocate(remaining_words); - deallocate(ptr, remaining_words); - account_for_allocation(remaining_words); - } - } -} - -Metachunk* SpaceManager::get_new_chunk(size_t chunk_word_size) { - // Get a chunk from the chunk freelist - Metachunk* next = chunk_manager()->chunk_freelist_allocate(chunk_word_size); - - if (next == NULL) { - next = vs_list()->get_new_chunk(chunk_word_size, - medium_chunk_bunch()); - } - - Log(gc, metaspace, alloc) log; - if (log.is_trace() && next != NULL && - SpaceManager::is_humongous(next->word_size())) { - log.trace(" new humongous chunk word size " PTR_FORMAT, next->word_size()); - } - - return next; -} - -MetaWord* SpaceManager::allocate(size_t word_size) { - MutexLocker 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; + // 1) Attempt to allocate from the dictionary of deallocated blocks. // Allocation from the dictionary is expensive in the sense that // the dictionary has to be searched for a size. Don't allocate // from the dictionary until it starts to get fat. Is this // a reasonable policy? Maybe an skinny dictionary is fast enough // for allocations. Do some profiling. JJJ - if (fl != NULL && fl->total_size() > allocation_from_dictionary_limit) { - p = fl->get_block(raw_word_size); + if (_block_freelist != NULL && _block_freelist->total_size() > Settings::allocation_from_dictionary_limit()) { + p = _block_freelist->get_block(raw_word_size); + if (p != NULL) { - DEBUG_ONLY(Atomic::inc(&g_internal_statistics.num_allocs_from_deallocated_blocks)); + DEBUG_ONLY(InternalStats::inc_num_allocs_from_deallocated_blocks();) + log_trace(metaspace)("SpaceManager %s: .. taken from freelist.", _name); + } + + } + + // 2) Failing that, attempt to allocate from the current chunk. If we hit commit limit, return NULL. + if (p == NULL && !did_hit_limit) { + p = current_chunk()->allocate(raw_word_size, &did_hit_limit); + log_trace(metaspace)("SpaceManager %s: .. taken from current chunk.", _name); + } + + // 3) Failing that because the remaining chunk space is too small for the requested size + // (and not because commit limit), attempt to enlarge the chunk in place. + if (p == NULL && !did_hit_limit) { + + // Since we did not hit the commit limit, the current chunk must have been too small. + assert(current_chunk()->free_words() < raw_word_size, "Sanity"); + + DEBUG_ONLY(InternalStats::inc_num_allocs_failed_chunk_too_small();) + + // Under certain conditions we can just attempt to enlarge the chunk. + // - obviously, this only works for non-root chunks which are leader of their buddy pair. + // - only if doubling chunk size would actually help - if the requested size does not fit into + // the enlarged chunk either, better just attempt to allocate a new fitting chunk. + // - below a certain chunk size to not blow up memory usage unnecessarily. + if (Settings::enlarge_chunks_in_place() && + current_chunk()->is_root_chunk() == false && + current_chunk()->is_leader() && + current_chunk()->word_size() + current_chunk()->free_words() >= requested_word_size && + current_chunk()->word_size() <= Settings::enlarge_chunks_in_place_max_word_size()) + { + + if (_chunk_manager->attempt_enlarge_chunk(current_chunk())) { + + // Re-attempt allocation. + p = current_chunk()->allocate(raw_word_size, &did_hit_limit); + + if (p != NULL) { + DEBUG_ONLY(InternalStats::inc_num_chunk_enlarged();) + log_trace(metaspace)("SpaceManager %s: .. taken from current chunk (enlarged chunk).", _name); + } + } } } - if (p == NULL) { - p = allocate_work(raw_word_size); + + // 4) Failing that, attempt to get a new chunk and allocate from that chunk. Again, we may hit a commit + // limit, in which case we return NULL. + if (p == NULL && !did_hit_limit) { + + // Since we did not hit the commit limit, the current chunk must have been too small. + assert(current_chunk()->free_words() < raw_word_size, "Sanity"); + + // Before we allocate a new chunk we need to retire the old chunk, which is too small to serve our request + // but may still have free committed words. + retire_current_chunk(); + + DEBUG_ONLY(InternalStats::inc_num_chunks_retired();) + + // Allocate a new chunk. + if (allocate_new_current_chunk(raw_word_size) == false) { + did_hit_limit = true; + } else { + assert(current_chunk() != NULL && current_chunk()->free_words() >= raw_word_size, "Sanity"); + p = current_chunk()->allocate(raw_word_size, &did_hit_limit); + log_trace(metaspace)("SpaceManager %s: .. allocated new chunk " CHKLVL_FORMAT " and taken from that.", + _name, current_chunk()->level()); + } + } -#ifdef ASSERT - EVERY_NTH(VerifyMetaspaceInterval) - verify_metrics_locked(); - END_EVERY_NTH -#endif + assert(p != NULL || (p == NULL && did_hit_limit), "Sanity"); + + if (p == NULL) { + DEBUG_ONLY(InternalStats::inc_num_allocs_failed_limit();) + } else { + DEBUG_ONLY(InternalStats::inc_num_allocs();) + _total_used_words_counter->increment_by(raw_word_size); + } + + log_trace(metaspace)("SpaceManager %s: returned " PTR_FORMAT ".", + _name, p2i(p)); return p; + } -// Returns the address of spaced allocated for "word_size". -// This methods does not know about blocks (Metablocks) -MetaWord* SpaceManager::allocate_work(size_t word_size) { +// Prematurely returns a metaspace allocation to the _block_freelists +// because it is not needed anymore (requires CLD lock to be active). +void SpaceManager::deallocate_locked(MetaWord* p, size_t word_size) { assert_lock_strong(lock()); -#ifdef ASSERT - if (Metadebug::test_metadata_failure()) { - return NULL; - } -#endif - // Is there space in the current chunk? - MetaWord* result = NULL; - if (current_chunk() != NULL) { - result = current_chunk()->allocate(word_size); + // Allocations and deallocations are in raw_word_size + size_t raw_word_size = get_raw_allocation_word_size(word_size); + + log_debug(metaspace)("SpaceManager %s: deallocating " PTR_FORMAT + ", word size: " SIZE_FORMAT ", raw size: " SIZE_FORMAT ".", + _name, p2i(p), word_size, raw_word_size); + + assert(current_chunk() != NULL, "SpaceManager is empty."); + + assert(is_valid_area(p, word_size), + "Pointer range not part of this SpaceManager and cannot be deallocated: (" PTR_FORMAT ".." PTR_FORMAT ").", + p2i(p), p2i(p + word_size)); + + // If this allocation has just been allocated from the current chunk, it may still be on the top of the + // current chunk. In that case, just roll back the allocation. + if (current_chunk()->attempt_rollback_allocation(p, raw_word_size)) { + log_trace(metaspace)("SpaceManager %s: ... rollback succeeded.", _name); + return; } - if (result == NULL) { - result = grow_and_allocate(word_size); + add_allocation_to_block_freelist(p, raw_word_size); + +} + +// Prematurely returns a metaspace allocation to the _block_freelists because it is not +// needed anymore. +void SpaceManager::deallocate(MetaWord* p, size_t word_size) { + MutexLocker cl(lock(), Mutex::_no_safepoint_check_flag); + deallocate_locked(p, word_size); +} + +// Update statistics. This walks all in-use chunks. +void SpaceManager::add_to_statistics(sm_stats_t* out) const { + + MutexLocker cl(lock(), Mutex::_no_safepoint_check_flag); + + for (const Metachunk* c = _chunks.first(); c != NULL; c = c->next()) { + in_use_chunk_stats_t& ucs = out->stats[c->level()]; + ucs.num ++; + ucs.word_size += c->word_size(); + ucs.committed_words += c->committed_words(); + ucs.used_words += c->used_words(); + // Note: for free and waste, we only count what's committed. + if (c == current_chunk()) { + ucs.free_words += c->free_below_committed_words(); + } else { + ucs.waste_words += c->free_below_committed_words(); + } } - if (result != NULL) { - account_for_allocation(word_size); + if (block_freelist() != NULL) { + out->free_blocks_num += block_freelist()->num_blocks(); + out->free_blocks_word_size += block_freelist()->total_size(); } - return result; -} - -void SpaceManager::verify() { - Metachunk* curr = chunk_list(); - while (curr != NULL) { - DEBUG_ONLY(do_verify_chunk(curr);) - assert(curr->is_tagged_free() == false, "Chunk should be tagged as in use."); - curr = curr->next(); - } -} - -void SpaceManager::verify_chunk_size(Metachunk* chunk) { - assert(is_humongous(chunk->word_size()) || - chunk->word_size() == medium_chunk_size() || - chunk->word_size() == small_chunk_size() || - chunk->word_size() == specialized_chunk_size(), - "Chunk size is wrong"); - return; -} - -void SpaceManager::add_to_statistics_locked(SpaceManagerStatistics* out) const { - assert_lock_strong(lock()); - Metachunk* chunk = chunk_list(); - while (chunk != NULL) { - UsedChunksStatistics& chunk_stat = out->chunk_stats(chunk->get_chunk_type()); - chunk_stat.add_num(1); - chunk_stat.add_cap(chunk->word_size()); - chunk_stat.add_overhead(Metachunk::overhead()); - chunk_stat.add_used(chunk->used_word_size() - Metachunk::overhead()); - if (chunk != current_chunk()) { - chunk_stat.add_waste(chunk->free_word_size()); - } else { - chunk_stat.add_free(chunk->free_word_size()); - } - chunk = chunk->next(); - } - if (block_freelists() != NULL) { - out->add_free_blocks_info(block_freelists()->num_blocks(), block_freelists()->total_size()); - } -} - -void SpaceManager::add_to_statistics(SpaceManagerStatistics* out) const { - MutexLocker cl(lock(), Mutex::_no_safepoint_check_flag); - add_to_statistics_locked(out); + DEBUG_ONLY(out->verify();) } #ifdef ASSERT -void SpaceManager::verify_metrics_locked() const { - assert_lock_strong(lock()); - SpaceManagerStatistics stat; - add_to_statistics_locked(&stat); +void SpaceManager::verify(bool slow) const { - UsedChunksStatistics chunk_stats = stat.totals(); + MutexLocker cl(lock(), Mutex::_no_safepoint_check_flag); - DEBUG_ONLY(chunk_stats.check_sanity()); + assert(_chunk_alloc_sequence != NULL && _chunk_manager != NULL, "Sanity"); - assert_counter(_capacity_words, chunk_stats.cap(), "SpaceManager::_capacity_words"); - assert_counter(_used_words, chunk_stats.used(), "SpaceManager::_used_words"); - assert_counter(_overhead_words, chunk_stats.overhead(), "SpaceManager::_overhead_words"); + _chunks.verify(true); + } -void SpaceManager::verify_metrics() const { - MutexLocker cl(lock(), Mutex::_no_safepoint_check_flag); - verify_metrics_locked(); +// Returns true if the area indicated by pointer and size have actually been allocated +// from this space manager. +bool SpaceManager::is_valid_area(MetaWord* p, size_t word_size) const { + assert(p != NULL && word_size > 0, "Sanity"); + for (const Metachunk* c = _chunks.first(); c != NULL; c = c->next()) { + if (c->is_valid_pointer(p)) { + assert(c->is_valid_pointer(p + word_size - 1), "Range partly oob"); + return true; + } + } + return false; } + #endif // ASSERT diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/spaceManager.hpp --- a/src/hotspot/share/memory/metaspace/spaceManager.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace/spaceManager.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -1,5 +1,6 @@ /* * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 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 @@ -28,206 +29,115 @@ #include "memory/allocation.hpp" #include "memory/metaspace.hpp" #include "memory/metaspace/blockFreelist.hpp" +#include "memory/metaspace/chunkAllocSequence.hpp" +#include "memory/metaspace/chunkManager.hpp" +#include "memory/metaspace/metachunk.hpp" #include "memory/metaspace/metaspaceCommon.hpp" -#include "memory/metaspace/metachunk.hpp" -#include "memory/metaspace/metaspaceStatistics.hpp" -#include "utilities/debug.hpp" -#include "utilities/globalDefinitions.hpp" class outputStream; class Mutex; namespace metaspace { -// SpaceManager - used by Metaspace to handle allocations +struct sm_stats_t; + +// The SpaceManager: +// - keeps a list of chunks-in-use by the class loader, as well as a current chunk used +// to allocate from +// - keeps a dictionary of free MetaBlocks. Those can be remnants of a retired chunk or +// allocations which were not needed anymore for some reason (e.g. releasing half-allocated +// structures when class loading fails) + class SpaceManager : public CHeapObj { - friend class ::ClassLoaderMetaspace; - friend class Metadebug; - private: - - // protects allocations + // Lock handed down from the associated ClassLoaderData. + // Protects allocations from this space. Mutex* const _lock; - // Type of metadata allocated. - const Metaspace::MetadataType _mdtype; + // The chunk manager to allocate chunks from. + ChunkManager* const _chunk_manager; - // Type of metaspace - const Metaspace::MetaspaceType _space_type; + // The chunk allocation strategy to use. + const ChunkAllocSequence* const _chunk_alloc_sequence; // List of chunks in use by this SpaceManager. Allocations - // are done from the current chunk. The list is used for deallocating + // are done from the current chunk. The list is used for deallocating // chunks when the SpaceManager is freed. - Metachunk* _chunk_list; - Metachunk* _current_chunk; + MetachunkList _chunks; - enum { + Metachunk* current_chunk() { return _chunks.first(); } + const Metachunk* current_chunk() const { return _chunks.first(); } - // Maximum number of small chunks to allocate to a SpaceManager - small_chunk_limit = 4, + // Prematurely released metablocks. + BlockFreelist* _block_freelist; - // Maximum number of specialize chunks to allocate for anonymous and delegating - // metadata space to a SpaceManager - anon_and_delegating_metadata_specialize_chunk_limit = 4, + // Points to outside size counter which we are to increase/decrease when we allocate memory + // on behalf of a user or when we are destroyed. + SizeAtomicCounter* const _total_used_words_counter; - allocation_from_dictionary_limit = 4 * K + const char* const _name; - }; + Mutex* lock() const { return _lock; } + ChunkManager* chunk_manager() const { return _chunk_manager; } + const ChunkAllocSequence* chunk_alloc_sequence() const { return _chunk_alloc_sequence; } - // Some running counters, but lets keep their number small to not add to much to - // the per-classloader footprint. - // Note: capacity = used + free + waste + overhead. We do not keep running counters for - // free and waste. Their sum can be deduced from the three other values. - size_t _overhead_words; - size_t _capacity_words; - size_t _used_words; - uintx _num_chunks_by_type[NumberOfInUseLists]; + BlockFreelist* block_freelist() const { return _block_freelist; } + void create_block_freelist(); + void add_allocation_to_block_freelist(MetaWord* p, size_t word_size); - // Free lists of blocks are per SpaceManager since they - // are assumed to be in chunks in use by the SpaceManager - // and all chunks in use by a SpaceManager are freed when - // the class loader using the SpaceManager is collected. - BlockFreelist* _block_freelists; - - private: - // Accessors - Metachunk* chunk_list() const { return _chunk_list; } - - BlockFreelist* block_freelists() const { return _block_freelists; } - - Metaspace::MetadataType mdtype() { return _mdtype; } - - VirtualSpaceList* vs_list() const { return Metaspace::get_space_list(_mdtype); } - ChunkManager* chunk_manager() const { return Metaspace::get_chunk_manager(_mdtype); } - - Metachunk* current_chunk() const { return _current_chunk; } - void set_current_chunk(Metachunk* v) { - _current_chunk = v; - } - - Metachunk* find_current_chunk(size_t word_size); - - // Add chunk to the list of chunks in use - void add_chunk(Metachunk* v, bool make_current); + // The remaining committed free space in the current chunk is chopped up and stored in the block + // free list for later use. As a result, the current chunk will remain current but completely + // used up. This is a preparation for calling allocate_new_current_chunk(). void retire_current_chunk(); - Mutex* lock() const { return _lock; } + // Given a requested word size, will allocate a chunk large enough to at least fit that + // size, but may be larger according to internal heuristics. + // + // On success, it will replace the current chunk with the newly allocated one, which will + // become the current chunk. The old current chunk should be retired beforehand. + // + // May fail if we could not allocate a new chunk. In that case the current chunk remains + // unchanged and false is returned. + bool allocate_new_current_chunk(size_t requested_word_size); - // Adds to the given statistic object. Expects to be locked with lock(). - void add_to_statistics_locked(SpaceManagerStatistics* out) const; + // Prematurely returns a metaspace allocation to the _block_freelists + // because it is not needed anymore (requires CLD lock to be active). + void deallocate_locked(MetaWord* p, size_t word_size); - // Verify internal counters against the current state. Expects to be locked with lock(). - DEBUG_ONLY(void verify_metrics_locked() const;) + // Returns true if the area indicated by pointer and size have actually been allocated + // from this space manager. + DEBUG_ONLY(bool is_valid_area(MetaWord* p, size_t word_size) const;) - public: - SpaceManager(Metaspace::MetadataType mdtype, - Metaspace::MetaspaceType space_type, - Mutex* lock); +public: + + SpaceManager(ChunkManager* chunk_manager, + const ChunkAllocSequence* alloc_sequence, + Mutex* lock, + SizeAtomicCounter* total_used_words_counter, + const char* name); + ~SpaceManager(); - enum ChunkMultiples { - MediumChunkMultiple = 4 - }; - - static size_t specialized_chunk_size(bool is_class) { return is_class ? ClassSpecializedChunk : SpecializedChunk; } - static size_t small_chunk_size(bool is_class) { return is_class ? ClassSmallChunk : SmallChunk; } - static size_t medium_chunk_size(bool is_class) { return is_class ? ClassMediumChunk : MediumChunk; } - - static size_t smallest_chunk_size(bool is_class) { return specialized_chunk_size(is_class); } - - // Accessors - bool is_class() const { return _mdtype == Metaspace::ClassType; } - - size_t specialized_chunk_size() const { return specialized_chunk_size(is_class()); } - size_t small_chunk_size() const { return small_chunk_size(is_class()); } - size_t medium_chunk_size() const { return medium_chunk_size(is_class()); } - - size_t smallest_chunk_size() const { return smallest_chunk_size(is_class()); } - - size_t medium_chunk_bunch() const { return medium_chunk_size() * MediumChunkMultiple; } - - bool is_humongous(size_t word_size) { return word_size > medium_chunk_size(); } - - size_t capacity_words() const { return _capacity_words; } - size_t used_words() const { return _used_words; } - size_t overhead_words() const { return _overhead_words; } - - // Adjust local, global counters after a new chunk has been added. - void account_for_new_chunk(const Metachunk* new_chunk); - - // Adjust local, global counters after space has been allocated from the current chunk. - void account_for_allocation(size_t words); - - // Adjust global counters just before the SpaceManager dies, after all its chunks - // have been returned to the freelist. - void account_for_spacemanager_death(); - - // Adjust the initial chunk size to match one of the fixed chunk list sizes, - // or return the unadjusted size if the requested size is humongous. - static size_t adjust_initial_chunk_size(size_t requested, bool is_class_space); - size_t adjust_initial_chunk_size(size_t requested) const; - - // Get the initial chunks size for this metaspace type. - size_t get_initial_chunk_size(Metaspace::MetaspaceType type) const; - - // Todo: remove this once we have counters by chunk type. - uintx num_chunks_by_type(ChunkIndex chunk_type) const { return _num_chunks_by_type[chunk_type]; } - - Metachunk* get_new_chunk(size_t chunk_word_size); - - // Block allocation and deallocation. - // Allocates a block from the current chunk + // Allocate memory from Metaspace. + // 1) Attempt to allocate from the dictionary of deallocated blocks. + // 2) Attempt to allocate from the current chunk. + // 3) Attempt to enlarge the current chunk in place if it is too small. + // 4) Attempt to get a new chunk and allocate from that chunk. + // At any point, if we hit a commit limit, we return NULL. MetaWord* allocate(size_t word_size); - // Helper for allocations - MetaWord* allocate_work(size_t word_size); - - // Returns a block to the per manager freelist + // Prematurely returns a metaspace allocation to the _block_freelists because it is not + // needed anymore. void deallocate(MetaWord* p, size_t word_size); - // Based on the allocation size and a minimum chunk size, - // returned chunk size (for expanding space for chunk allocation). - size_t calc_chunk_size(size_t allocation_word_size); + // Update statistics. This walks all in-use chunks. + void add_to_statistics(sm_stats_t* out) const; - // Called when an allocation from the current chunk fails. - // Gets a new chunk (may require getting a new virtual space), - // and allocates from that chunk. - MetaWord* grow_and_allocate(size_t word_size); - - // Notify memory usage to MemoryService. - void track_metaspace_memory_usage(); - - // debugging support. - - void print_on(outputStream* st) const; - void locked_print_chunks_in_use_on(outputStream* st) const; - - void verify(); - void verify_chunk_size(Metachunk* chunk); - - // This adjusts the size given to be greater than the minimum allocation size in - // words for data in metaspace. Esentially the minimum size is currently 3 words. - size_t get_allocation_word_size(size_t word_size) { - size_t byte_size = word_size * BytesPerWord; - - size_t raw_bytes_size = MAX2(byte_size, sizeof(Metablock)); - raw_bytes_size = align_up(raw_bytes_size, Metachunk::object_alignment()); - - size_t raw_word_size = raw_bytes_size / BytesPerWord; - assert(raw_word_size * BytesPerWord == raw_bytes_size, "Size problem"); - - return raw_word_size; - } - - // Adds to the given statistic object. - void add_to_statistics(SpaceManagerStatistics* out) const; - - // Verify internal counters against the current state. - DEBUG_ONLY(void verify_metrics() const;) + // Run verifications. slow=true: verify chunk-internal integrity too. + DEBUG_ONLY(void verify(bool slow) const;) }; - } // namespace metaspace #endif // SHARE_MEMORY_METASPACE_SPACEMANAGER_HPP diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/virtualSpaceList.cpp --- a/src/hotspot/share/memory/metaspace/virtualSpaceList.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace/virtualSpaceList.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -1,5 +1,6 @@ /* * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 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 @@ -25,423 +26,248 @@ #include "precompiled.hpp" #include "logging/log.hpp" -#include "logging/logStream.hpp" #include "memory/metaspace.hpp" #include "memory/metaspace/chunkManager.hpp" -#include "memory/metaspace/metachunk.hpp" -#include "memory/metaspace/metaspaceCommon.hpp" +#include "memory/metaspace/counter.hpp" +#include "memory/metaspace/commitLimiter.hpp" +#include "memory/metaspace/counter.hpp" #include "memory/metaspace/virtualSpaceList.hpp" #include "memory/metaspace/virtualSpaceNode.hpp" -#include "runtime/orderAccess.hpp" #include "runtime/mutexLocker.hpp" -#include "runtime/safepoint.hpp" + namespace metaspace { +static int next_node_id = 0; + +// Create a new, empty, expandable list. +VirtualSpaceList::VirtualSpaceList(const char* name, CommitLimiter* commit_limiter) + : _name(name), + _first_node(NULL), + _can_expand(true), + _can_purge(true), + _commit_limiter(commit_limiter), + _reserved_words_counter(), + _committed_words_counter() +{ +} + +// Create a new list. The list will contain one node only, which uses the given ReservedSpace. +// It will be not expandable beyond that first node. +VirtualSpaceList::VirtualSpaceList(const char* name, ReservedSpace rs, CommitLimiter* commit_limiter) +: _name(name), + _first_node(NULL), + _can_expand(false), + _can_purge(false), + _commit_limiter(commit_limiter), + _reserved_words_counter(), + _committed_words_counter() +{ + // Create the first node spanning the existing ReservedSpace. This will be the only node created + // for this list since we cannot expand. + VirtualSpaceNode* vsn = VirtualSpaceNode::create_node(next_node_id++, + rs, _commit_limiter, + &_reserved_words_counter, &_committed_words_counter); + assert(vsn != NULL, "node creation failed"); + _first_node = vsn; + _first_node->set_next(NULL); + _nodes_counter.increment(); +} VirtualSpaceList::~VirtualSpaceList() { - VirtualSpaceListIterator iter(virtual_space_list()); - while (iter.repeat()) { - VirtualSpaceNode* vsl = iter.get_next(); - delete vsl; + // Note: normally, there is no reason ever to delete a vslist since they are + // global objects, but for gtests it makes sense to allow this. + VirtualSpaceNode* vsn = _first_node; + VirtualSpaceNode* vsn2 = vsn; + while (vsn != NULL) { + vsn2 = vsn->next(); + delete vsn; + vsn = vsn2; } } -void VirtualSpaceList::inc_reserved_words(size_t v) { +// Create a new node and append it to the list. After +// this function, _current_node shall point to a new empty node. +// List must be expandable for this to work. +void VirtualSpaceList::create_new_node() { + assert(_can_expand, "List is not expandable"); assert_lock_strong(MetaspaceExpand_lock); - _reserved_words = _reserved_words + v; -} -void VirtualSpaceList::dec_reserved_words(size_t v) { - assert_lock_strong(MetaspaceExpand_lock); - _reserved_words = _reserved_words - v; + + VirtualSpaceNode* vsn = VirtualSpaceNode::create_node(next_node_id ++, + Settings::virtual_space_node_default_word_size(), + _commit_limiter, + &_reserved_words_counter, &_committed_words_counter); + assert(vsn != NULL, "node creation failed"); + vsn->set_next(_first_node); + _first_node = vsn; + _nodes_counter.increment(); } -#define assert_committed_below_limit() \ - assert(MetaspaceUtils::committed_bytes() <= MaxMetaspaceSize, \ - "Too much committed memory. Committed: " SIZE_FORMAT \ - " limit (MaxMetaspaceSize): " SIZE_FORMAT, \ - MetaspaceUtils::committed_bytes(), MaxMetaspaceSize); +// Allocate a root chunk from this list. +// Note: this just returns a chunk whose memory is reserved; no memory is committed yet. +// Hence, before using this chunk, it must be committed. +// Also, no limits are checked, since no committing takes place. +Metachunk* VirtualSpaceList::allocate_root_chunk() { + assert_lock_strong(MetaspaceExpand_lock); -void VirtualSpaceList::inc_committed_words(size_t v) { - assert_lock_strong(MetaspaceExpand_lock); - _committed_words = _committed_words + v; + log_debug(metaspace)("VirtualSpaceList %s: allocate root chunk.", _name); - assert_committed_below_limit(); -} -void VirtualSpaceList::dec_committed_words(size_t v) { - assert_lock_strong(MetaspaceExpand_lock); - _committed_words = _committed_words - v; + if (_first_node == NULL || + _first_node->free_words() == 0) { - assert_committed_below_limit(); -} + // The current node is fully used up. + log_debug(metaspace)("VirtualSpaceList %s: need new node.", _name); -void VirtualSpaceList::inc_virtual_space_count() { - assert_lock_strong(MetaspaceExpand_lock); - _virtual_space_count++; -} + // Since all allocations from a VirtualSpaceNode happen in + // root-chunk-size units, and the node size must be root-chunk-size aligned, + // we should never have left-over space. + assert(_first_node == NULL || + _first_node->free_words() == 0, "Sanity"); -void VirtualSpaceList::dec_virtual_space_count() { - assert_lock_strong(MetaspaceExpand_lock); - _virtual_space_count--; -} - -// Walk the list of VirtualSpaceNodes and delete -// nodes with a 0 container_count. Remove Metachunks in -// the node from their respective freelists. -void VirtualSpaceList::purge(ChunkManager* chunk_manager) { - assert_lock_strong(MetaspaceExpand_lock); - // Don't use a VirtualSpaceListIterator because this - // list is being changed and a straightforward use of an iterator is not safe. - VirtualSpaceNode* prev_vsl = virtual_space_list(); - VirtualSpaceNode* next_vsl = prev_vsl; - int num_purged_nodes = 0; - while (next_vsl != NULL) { - VirtualSpaceNode* vsl = next_vsl; - DEBUG_ONLY(vsl->verify(false);) - next_vsl = vsl->next(); - // Don't free the current virtual space since it will likely - // be needed soon. - if (vsl->container_count() == 0 && vsl != current_virtual_space()) { - log_trace(gc, metaspace, freelist)("Purging VirtualSpaceNode " PTR_FORMAT " (capacity: " SIZE_FORMAT - ", used: " SIZE_FORMAT ").", p2i(vsl), vsl->capacity_words_in_vs(), vsl->used_words_in_vs()); - DEBUG_ONLY(Atomic::inc(&g_internal_statistics.num_vsnodes_purged)); - // Unlink it from the list - if (prev_vsl == vsl) { - // This is the case of the current node being the first node. - assert(vsl == virtual_space_list(), "Expected to be the first node"); - set_virtual_space_list(vsl->next()); - } else { - prev_vsl->set_next(vsl->next()); - } - - vsl->purge(chunk_manager); - dec_reserved_words(vsl->reserved_words()); - dec_committed_words(vsl->committed_words()); - dec_virtual_space_count(); - delete vsl; - num_purged_nodes ++; + if (_can_expand) { + create_new_node(); } else { - prev_vsl = vsl; + return NULL; // We cannot expand this list. } } - // Verify list -#ifdef ASSERT - if (num_purged_nodes > 0) { - verify(false); - } -#endif + Metachunk* c = _first_node->allocate_root_chunk(); + + assert(c != NULL, "This should have worked"); + + return c; + } +// Attempts to purge nodes. This will remove and delete nodes which only contain free chunks. +// The free chunks are removed from the freelists before the nodes are deleted. +// Return number of purged nodes. +int VirtualSpaceList::purge(MetachunkListCluster* freelists) { -// This function looks at the mmap regions in the metaspace without locking. -// The chunks are added with store ordering and not deleted except for at -// unloading time during a safepoint. -VirtualSpaceNode* VirtualSpaceList::find_enclosing_space(const void* ptr) { - // List should be stable enough to use an iterator here because removing virtual - // space nodes is only allowed at a safepoint. - if (is_within_envelope((address)ptr)) { - VirtualSpaceListIterator iter(virtual_space_list()); - while (iter.repeat()) { - VirtualSpaceNode* vsn = iter.get_next(); - if (vsn->contains(ptr)) { - return vsn; + // Note: I am not sure all that purging business is even necessary anymore + // since we have a good reclaim mechanism in place. Need to measure. + + assert_lock_strong(MetaspaceExpand_lock); + + if (_can_purge == false) { + log_debug(metaspace)("VirtualSpaceList %s: cannot purge this list.", _name); + return 0; + } + + log_debug(metaspace)("VirtualSpaceList %s: purging...", _name); + + VirtualSpaceNode* vsn = _first_node; + VirtualSpaceNode* prev_vsn = NULL; + int num = 0, num_purged = 0; + while (vsn != NULL) { + VirtualSpaceNode* next_vsn = vsn->next(); + bool purged = vsn->attempt_purge(freelists); + if (purged) { + // Note: from now on do not dereference vsn! + log_debug(metaspace)("VirtualSpaceList %s: purged node @" PTR_FORMAT, _name, p2i(vsn)); + if (_first_node == vsn) { + _first_node = next_vsn; } + DEBUG_ONLY(vsn = (VirtualSpaceNode*)((uintptr_t)(0xdeadbeef));) + if (prev_vsn != NULL) { + prev_vsn->set_next(next_vsn); + } + num_purged ++; + _nodes_counter.decrement(); + } else { + prev_vsn = vsn; } + vsn = next_vsn; + num ++; } - return NULL; + + log_debug(metaspace)("VirtualSpaceList %s: purged %d/%d nodes.", _name, num_purged, num); + + return num_purged; + } -void VirtualSpaceList::retire_current_virtual_space() { +// Print all nodes in this space list. +void VirtualSpaceList::print_on(outputStream* st) const { + MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); + + st->print_cr("vsl %s:", _name); + const VirtualSpaceNode* vsn = _first_node; + int n = 0; + while (vsn != NULL) { + st->print("- node #%d: ", n); + vsn->print_on(st); + vsn = vsn->next(); + n ++; + } + st->print_cr("- total %d nodes, " SIZE_FORMAT " reserved words, " SIZE_FORMAT " committed words.", + n, reserved_words(), committed_words()); +} + +#ifdef ASSERT +void VirtualSpaceList::verify_locked(bool slow) const { + assert_lock_strong(MetaspaceExpand_lock); - VirtualSpaceNode* vsn = current_virtual_space(); + assert(_name != NULL, "Sanity"); - ChunkManager* cm = is_class() ? Metaspace::chunk_manager_class() : - Metaspace::chunk_manager_metadata(); + int n = 0; - vsn->retire(cm); -} + if (_first_node != NULL) { -VirtualSpaceList::VirtualSpaceList(size_t word_size) : - _virtual_space_list(NULL), - _current_virtual_space(NULL), - _is_class(false), - _reserved_words(0), - _committed_words(0), - _virtual_space_count(0), - _envelope_lo((address)max_uintx), - _envelope_hi(NULL) { - MutexLocker cl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); - create_new_virtual_space(word_size); -} + size_t total_reserved_words = 0; + size_t total_committed_words = 0; + const VirtualSpaceNode* vsn = _first_node; + while (vsn != NULL) { + n ++; + vsn->verify(slow); + total_reserved_words += vsn->word_size(); + total_committed_words += vsn->committed_words(); + vsn = vsn->next(); + } -VirtualSpaceList::VirtualSpaceList(ReservedSpace rs) : - _virtual_space_list(NULL), - _current_virtual_space(NULL), - _is_class(true), - _reserved_words(0), - _committed_words(0), - _virtual_space_count(0), - _envelope_lo((address)max_uintx), - _envelope_hi(NULL) { - MutexLocker cl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); - VirtualSpaceNode* class_entry = new VirtualSpaceNode(is_class(), rs); - bool succeeded = class_entry->initialize(); - if (succeeded) { - expand_envelope_to_include_node(class_entry); - // ensure lock-free iteration sees fully initialized node - OrderAccess::storestore(); - link_vs(class_entry); + _nodes_counter.check(n); + _reserved_words_counter.check(total_reserved_words); + _committed_words_counter.check(total_committed_words); + + } else { + + _reserved_words_counter.check(0); + _committed_words_counter.check(0); + } } -size_t VirtualSpaceList::free_bytes() { - return current_virtual_space()->free_words_in_vs() * BytesPerWord; +void VirtualSpaceList::verify(bool slow) const { + MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); + verify_locked(slow); } +#endif -// Allocate another meta virtual space and add it to the list. -bool VirtualSpaceList::create_new_virtual_space(size_t vs_word_size) { - assert_lock_strong(MetaspaceExpand_lock); - - if (is_class()) { - assert(false, "We currently don't support more than one VirtualSpace for" - " the compressed class space. The initialization of the" - " CCS uses another code path and should not hit this path."); - return false; - } - - if (vs_word_size == 0) { - assert(false, "vs_word_size should always be at least _reserve_alignment large."); - return false; - } - - // Reserve the space - size_t vs_byte_size = vs_word_size * BytesPerWord; - assert_is_aligned(vs_byte_size, Metaspace::reserve_alignment()); - - // Allocate the meta virtual space and initialize it. - VirtualSpaceNode* new_entry = new VirtualSpaceNode(is_class(), vs_byte_size); - if (!new_entry->initialize()) { - delete new_entry; - return false; - } else { - assert(new_entry->reserved_words() == vs_word_size, - "Reserved memory size differs from requested memory size"); - expand_envelope_to_include_node(new_entry); - // ensure lock-free iteration sees fully initialized node - OrderAccess::storestore(); - link_vs(new_entry); - DEBUG_ONLY(Atomic::inc(&g_internal_statistics.num_vsnodes_created)); - return true; - } - - DEBUG_ONLY(verify(false);) - -} - -void VirtualSpaceList::link_vs(VirtualSpaceNode* new_entry) { - if (virtual_space_list() == NULL) { - set_virtual_space_list(new_entry); - } else { - current_virtual_space()->set_next(new_entry); - } - set_current_virtual_space(new_entry); - inc_reserved_words(new_entry->reserved_words()); - inc_committed_words(new_entry->committed_words()); - inc_virtual_space_count(); -#ifdef ASSERT - new_entry->mangle(); -#endif - LogTarget(Trace, gc, metaspace) lt; - if (lt.is_enabled()) { - LogStream ls(lt); - VirtualSpaceNode* vsl = current_virtual_space(); - ResourceMark rm; - vsl->print_on(&ls); - } -} - -bool VirtualSpaceList::expand_node_by(VirtualSpaceNode* node, - size_t min_words, - size_t preferred_words) { - size_t before = node->committed_words(); - - bool result = node->expand_by(min_words, preferred_words); - - size_t after = node->committed_words(); - - // after and before can be the same if the memory was pre-committed. - assert(after >= before, "Inconsistency"); - inc_committed_words(after - before); - - return result; -} - -bool VirtualSpaceList::expand_by(size_t min_words, size_t preferred_words) { - assert_is_aligned(min_words, Metaspace::commit_alignment_words()); - assert_is_aligned(preferred_words, Metaspace::commit_alignment_words()); - assert(min_words <= preferred_words, "Invalid arguments"); - - const char* const class_or_not = (is_class() ? "class" : "non-class"); - - if (!MetaspaceGC::can_expand(min_words, this->is_class())) { - log_trace(gc, metaspace, freelist)("Cannot expand %s virtual space list.", - class_or_not); - return false; - } - - size_t allowed_expansion_words = MetaspaceGC::allowed_expansion(); - if (allowed_expansion_words < min_words) { - log_trace(gc, metaspace, freelist)("Cannot expand %s virtual space list (must try gc first).", - class_or_not); - return false; - } - - size_t max_expansion_words = MIN2(preferred_words, allowed_expansion_words); - - // Commit more memory from the the current virtual space. - bool vs_expanded = expand_node_by(current_virtual_space(), - min_words, - max_expansion_words); - if (vs_expanded) { - log_trace(gc, metaspace, freelist)("Expanded %s virtual space list.", - class_or_not); - return true; - } - log_trace(gc, metaspace, freelist)("%s virtual space list: retire current node.", - class_or_not); - retire_current_virtual_space(); - - // Get another 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()) { - // The memory was pre-committed, so we are done here. - assert(min_words <= current_virtual_space()->committed_words(), - "The new VirtualSpace was pre-committed, so it" - "should be large enough to fit the alloc request."); +// Returns true if this pointer is contained in one of our nodes. +bool VirtualSpaceList::contains(const MetaWord* p) const { + const VirtualSpaceNode* vsn = _first_node; + while (vsn != NULL) { + if (vsn->contains(p)) { return true; } - - return expand_node_by(current_virtual_space(), - min_words, - max_expansion_words); + vsn = vsn->next(); } - return false; } -// Given a chunk, calculate the largest possible padding space which -// could be required when allocating it. -static size_t largest_possible_padding_size_for_chunk(size_t chunk_word_size, bool is_class) { - const ChunkIndex chunk_type = get_chunk_type_by_size(chunk_word_size, is_class); - if (chunk_type != HumongousIndex) { - // Normal, non-humongous chunks are allocated at chunk size - // boundaries, so the largest padding space required would be that - // minus the smallest chunk size. - const size_t smallest_chunk_size = is_class ? ClassSpecializedChunk : SpecializedChunk; - return chunk_word_size - smallest_chunk_size; - } else { - // Humongous chunks are allocated at smallest-chunksize - // boundaries, so there is no padding required. - return 0; - } +VirtualSpaceList* VirtualSpaceList::_vslist_class = NULL; +VirtualSpaceList* VirtualSpaceList::_vslist_nonclass = NULL; + +void VirtualSpaceList::set_vslist_class(VirtualSpaceList* vsl) { + assert(_vslist_class == NULL, "Sanity"); + _vslist_class = vsl; } - -Metachunk* VirtualSpaceList::get_new_chunk(size_t chunk_word_size, size_t suggested_commit_granularity) { - - // Allocate a chunk out of the current virtual space. - Metachunk* next = current_virtual_space()->get_chunk_vs(chunk_word_size); - - if (next != NULL) { - return next; - } - - // The expand amount is currently only determined by the requested sizes - // and not how much committed memory is left in the current virtual space. - - // We must have enough space for the requested size and any - // additional reqired padding chunks. - 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) { - // Can happen when humongous chunks are allocated. - 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); - assert(next != NULL, "The allocation was expected to succeed after the expansion"); - } - - return next; +void VirtualSpaceList::set_vslist_nonclass(VirtualSpaceList* vsl) { + assert(_vslist_nonclass == NULL, "Sanity"); + _vslist_nonclass = vsl; } -void VirtualSpaceList::print_on(outputStream* st, size_t scale) const { - st->print_cr(SIZE_FORMAT " nodes, current node: " PTR_FORMAT, - _virtual_space_count, p2i(_current_virtual_space)); - VirtualSpaceListIterator iter(virtual_space_list()); - while (iter.repeat()) { - st->cr(); - VirtualSpaceNode* node = iter.get_next(); - node->print_on(st, scale); - } -} - -void VirtualSpaceList::print_map(outputStream* st) const { - VirtualSpaceNode* list = virtual_space_list(); - VirtualSpaceListIterator iter(list); - unsigned i = 0; - while (iter.repeat()) { - st->print_cr("Node %u:", i); - VirtualSpaceNode* node = iter.get_next(); - node->print_map(st, this->is_class()); - i ++; - } -} - -// Given a node, expand range such that it includes the node. -void VirtualSpaceList::expand_envelope_to_include_node(const VirtualSpaceNode* node) { - _envelope_lo = MIN2(_envelope_lo, (address)node->low_boundary()); - _envelope_hi = MAX2(_envelope_hi, (address)node->high_boundary()); -} - - -#ifdef ASSERT -void VirtualSpaceList::verify(bool slow) { - VirtualSpaceNode* list = virtual_space_list(); - VirtualSpaceListIterator iter(list); - size_t reserved = 0; - size_t committed = 0; - size_t node_count = 0; - while (iter.repeat()) { - VirtualSpaceNode* node = iter.get_next(); - if (slow) { - node->verify(true); - } - // Check that the node resides fully within our envelope. - assert((address)node->low_boundary() >= _envelope_lo && (address)node->high_boundary() <= _envelope_hi, - "Node " SIZE_FORMAT " [" PTR_FORMAT ", " PTR_FORMAT ") outside envelope [" PTR_FORMAT ", " PTR_FORMAT ").", - node_count, p2i(node->low_boundary()), p2i(node->high_boundary()), p2i(_envelope_lo), p2i(_envelope_hi)); - reserved += node->reserved_words(); - committed += node->committed_words(); - node_count ++; - } - assert(reserved == reserved_words() && committed == committed_words() && node_count == _virtual_space_count, - "Mismatch: reserved real: " SIZE_FORMAT " expected: " SIZE_FORMAT - ", committed real: " SIZE_FORMAT " expected: " SIZE_FORMAT - ", node count real: " SIZE_FORMAT " expected: " SIZE_FORMAT ".", - reserved, reserved_words(), committed, committed_words(), - node_count, _virtual_space_count); -} -#endif // ASSERT - } // namespace metaspace diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/virtualSpaceList.hpp --- a/src/hotspot/share/memory/metaspace/virtualSpaceList.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace/virtualSpaceList.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -1,5 +1,6 @@ /* * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 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 @@ -26,143 +27,113 @@ #define SHARE_MEMORY_METASPACE_VIRTUALSPACELIST_HPP #include "memory/allocation.hpp" +#include "memory/metaspace/counter.hpp" +#include "memory/metaspace/commitLimiter.hpp" #include "memory/metaspace/virtualSpaceNode.hpp" +#include "memory/virtualspace.hpp" #include "utilities/globalDefinitions.hpp" +class outputStream; namespace metaspace { class Metachunk; -class ChunkManager; +class MetachunkListCluster; -// List of VirtualSpaces for metadata allocation. class VirtualSpaceList : public CHeapObj { - friend class VirtualSpaceNode; - enum VirtualSpaceSizes { - VirtualSpaceSize = 256 * K - }; + // Name + const char* const _name; - // Head of the list - VirtualSpaceNode* _virtual_space_list; - // virtual space currently being used for allocations - VirtualSpaceNode* _current_virtual_space; + // Head of the list. + VirtualSpaceNode* _first_node; - // Is this VirtualSpaceList used for the compressed class space - bool _is_class; + // Number of nodes (kept for statistics only). + IntCounter _nodes_counter; - // Sum of reserved and committed memory in the virtual spaces - size_t _reserved_words; - size_t _committed_words; + // Whether this list can expand by allocating new nodes. + const bool _can_expand; - // Number of virtual spaces - size_t _virtual_space_count; + // Whether this list can be purged. + const bool _can_purge; - // Optimization: we keep an address range to quickly exclude pointers - // which are clearly not pointing into metaspace. This is an optimization for - // VirtualSpaceList::contains(). - address _envelope_lo; - address _envelope_hi; + // Used to check limits before committing memory. + CommitLimiter* const _commit_limiter; - bool is_within_envelope(address p) const { - return p >= _envelope_lo && p < _envelope_hi; - } + // Statistics - // Given a node, expand range such that it includes the node. - void expand_envelope_to_include_node(const VirtualSpaceNode* node); + // Holds sum of reserved space, in words, over all list nodes. + SizeCounter _reserved_words_counter; - ~VirtualSpaceList(); + // Holds sum of committed space, in words, over all list nodes. + SizeCounter _committed_words_counter; - VirtualSpaceNode* virtual_space_list() const { return _virtual_space_list; } + // Create a new node and append it to the list. After + // this function, _current_node shall point to a new empty node. + // List must be expandable for this to work. + void create_new_node(); - void set_virtual_space_list(VirtualSpaceNode* v) { - _virtual_space_list = v; - } - void set_current_virtual_space(VirtualSpaceNode* v) { - _current_virtual_space = v; - } +public: - void link_vs(VirtualSpaceNode* new_entry); + // Create a new, empty, expandable list. + VirtualSpaceList(const char* name, CommitLimiter* commit_limiter); - // Get another virtual space and add it to the list. This - // is typically prompted by a failed attempt to allocate a chunk - // and is typically followed by the allocation of a chunk. - bool create_new_virtual_space(size_t vs_word_size); + // Create a new list. The list will contain one node only, which uses the given ReservedSpace. + // It will be not expandable beyond that first node. + VirtualSpaceList(const char* name, ReservedSpace rs, CommitLimiter* commit_limiter); - // Chunk up the unused committed space in the current - // virtual space and add the chunks to the free list. - void retire_current_virtual_space(); + virtual ~VirtualSpaceList(); - DEBUG_ONLY(bool contains_node(const VirtualSpaceNode* node) const;) + // Allocate a root chunk from this list. + // Note: this just returns a chunk whose memory is reserved; no memory is committed yet. + // Hence, before using this chunk, it must be committed. + // May return NULL if vslist would need to be expanded to hold the new root node but + // the list cannot be expanded (in practice this means we reached CompressedClassSpaceSize). + Metachunk* allocate_root_chunk(); - public: - VirtualSpaceList(size_t word_size); - VirtualSpaceList(ReservedSpace rs); + // Attempts to purge nodes. This will remove and delete nodes which only contain free chunks. + // The free chunks are removed from the freelists before the nodes are deleted. + // Return number of purged nodes. + int purge(MetachunkListCluster* freelists); - size_t free_bytes(); + //// Statistics //// - Metachunk* get_new_chunk(size_t chunk_word_size, - size_t suggested_commit_granularity); + // Return sum of reserved words in all nodes. + size_t reserved_words() const { return _reserved_words_counter.get(); } - bool expand_node_by(VirtualSpaceNode* node, - size_t min_words, - size_t preferred_words); + // Return sum of committed words in all nodes. + size_t committed_words() const { return _committed_words_counter.get(); } - bool expand_by(size_t min_words, - size_t preferred_words); + // Return number of nodes in this list. + int num_nodes() const { return _nodes_counter.get(); } - VirtualSpaceNode* current_virtual_space() { - return _current_virtual_space; - } + //// Debug stuff //// + DEBUG_ONLY(void verify(bool slow) const;) + DEBUG_ONLY(void verify_locked(bool slow) const;) - bool is_class() const { return _is_class; } + // Print all nodes in this space list. + void print_on(outputStream* st) const; - bool initialization_succeeded() { return _virtual_space_list != NULL; } + // Returns true if this pointer is contained in one of our nodes. + bool contains(const MetaWord* p) const; - size_t reserved_words() { return _reserved_words; } - size_t reserved_bytes() { return reserved_words() * BytesPerWord; } - size_t committed_words() { return _committed_words; } - size_t committed_bytes() { return committed_words() * BytesPerWord; } +private: - void inc_reserved_words(size_t v); - void dec_reserved_words(size_t v); - void inc_committed_words(size_t v); - void dec_committed_words(size_t v); - void inc_virtual_space_count(); - void dec_virtual_space_count(); + static VirtualSpaceList* _vslist_class; + static VirtualSpaceList* _vslist_nonclass; - VirtualSpaceNode* find_enclosing_space(const void* ptr); - bool contains(const void* ptr) { return find_enclosing_space(ptr) != NULL; } +public: - // Unlink empty VirtualSpaceNodes and free it. - void purge(ChunkManager* chunk_manager); + static VirtualSpaceList* vslist_class() { return _vslist_class; } + static VirtualSpaceList* vslist_nonclass() { return _vslist_nonclass; } - void print_on(outputStream* st) const { print_on(st, K); } - void print_on(outputStream* st, size_t scale) const; - void print_map(outputStream* st) const; + static void set_vslist_class(VirtualSpaceList* vslist_class); + static void set_vslist_nonclass(VirtualSpaceList* vslist_class); - DEBUG_ONLY(void verify(bool slow);) - class VirtualSpaceListIterator : public StackObj { - VirtualSpaceNode* _virtual_spaces; - public: - VirtualSpaceListIterator(VirtualSpaceNode* virtual_spaces) : - _virtual_spaces(virtual_spaces) {} - - bool repeat() { - return _virtual_spaces != NULL; - } - - VirtualSpaceNode* get_next() { - VirtualSpaceNode* result = _virtual_spaces; - if (_virtual_spaces != NULL) { - _virtual_spaces = _virtual_spaces->next(); - } - return result; - } - }; }; } // namespace metaspace #endif // SHARE_MEMORY_METASPACE_VIRTUALSPACELIST_HPP + diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/virtualSpaceNode.cpp --- a/src/hotspot/share/memory/metaspace/virtualSpaceNode.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace/virtualSpaceNode.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -1,5 +1,6 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 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 @@ -22,567 +23,503 @@ * */ + +#include #include "precompiled.hpp" #include "logging/log.hpp" -#include "logging/logStream.hpp" + +#include "memory/metaspace/chunkLevel.hpp" +#include "memory/metaspace/chunkHeaderPool.hpp" +#include "memory/metaspace/commitLimiter.hpp" +#include "memory/metaspace/counter.hpp" +#include "memory/metaspace/internStat.hpp" #include "memory/metaspace/metachunk.hpp" -#include "memory/metaspace.hpp" -#include "memory/metaspace/chunkManager.hpp" -#include "memory/metaspace/metaDebug.hpp" #include "memory/metaspace/metaspaceCommon.hpp" -#include "memory/metaspace/occupancyMap.hpp" +#include "memory/metaspace/rootChunkArea.hpp" +#include "memory/metaspace/runningCounters.hpp" #include "memory/metaspace/virtualSpaceNode.hpp" -#include "memory/virtualspace.hpp" + +#include "runtime/mutexLocker.hpp" #include "runtime/os.hpp" -#include "services/memTracker.hpp" -#include "utilities/copy.hpp" + +#include "utilities/align.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" +#include "utilities/ostream.hpp" namespace metaspace { -// Decide if large pages should be committed when the memory is reserved. -static bool should_commit_large_pages_when_reserving(size_t bytes) { - if (UseLargePages && UseLargePagesInMetaspace && !os::can_commit_large_page_memory()) { - size_t words = bytes / BytesPerWord; - bool is_class = false; // We never reserve large pages for the class space. - if (MetaspaceGC::can_expand(words, is_class) && - MetaspaceGC::allowed_expansion() >= words) { - return true; - } +#ifdef ASSERT +void check_pointer_is_aligned_to_commit_granule(const MetaWord* p) { + assert(is_aligned(p, Settings::commit_granule_bytes()), + "Pointer not aligned to commit granule size: " PTR_FORMAT ".", + p2i(p)); +} +void check_word_size_is_aligned_to_commit_granule(size_t word_size) { + assert(is_aligned(word_size, Settings::commit_granule_words()), + "Not aligned to commit granule size: " SIZE_FORMAT ".", word_size); +} +#endif + +// Given an address range, ensure it is committed. +// +// The range has to be aligned to granule size. +// +// Function will: +// - check how many granules in that region are uncommitted; If all are committed, it +// returns true immediately. +// - check if committing those uncommitted granules would bring us over the commit limit +// (GC threshold, MaxMetaspaceSize). If true, it returns false. +// - commit the memory. +// - mark the range as committed in the commit mask +// +// Returns true if success, false if it did hit a commit limit. +bool VirtualSpaceNode::commit_range(MetaWord* p, size_t word_size) { + + DEBUG_ONLY(check_pointer_is_aligned_to_commit_granule(p);) + DEBUG_ONLY(check_word_size_is_aligned_to_commit_granule(word_size);) + assert_lock_strong(MetaspaceExpand_lock); + + // First calculate how large the committed regions in this range are + const size_t committed_words_in_range = _commit_mask.get_committed_size_in_range(p, word_size); + DEBUG_ONLY(check_word_size_is_aligned_to_commit_granule(committed_words_in_range);) + + // By how much words we would increase commit charge + // were we to commit the given address range completely. + const size_t commit_increase_words = word_size - committed_words_in_range; + + log_debug(metaspace)("VirtualSpaceNode %d, base " PTR_FORMAT ": committing range " PTR_FORMAT ".." PTR_FORMAT "(" SIZE_FORMAT " words)", + _node_id, p2i(_base), p2i(p), p2i(p + word_size), word_size); + + if (commit_increase_words == 0) { + log_debug(metaspace)("VirtualSpaceNode %d, base " PTR_FORMAT ": ... already fully committed.", + _node_id, p2i(_base)); + return true; // Already fully committed, nothing to do. } - return false; + // Before committing any more memory, check limits. + if (_commit_limiter->possible_expansion_words() < commit_increase_words) { + return false; + } + + // Commit... + if (os::commit_memory((char*)p, word_size * BytesPerWord, false) == false) { + vm_exit_out_of_memory(word_size * BytesPerWord, OOM_MMAP_ERROR, "Failed to commit metaspace."); + } + + log_debug(gc, metaspace)("Increased metaspace by " SIZE_FORMAT " bytes.", + commit_increase_words * BytesPerWord); + + // ... tell commit limiter... + _commit_limiter->increase_committed(commit_increase_words); + + // ... update counters in containing vslist ... + _total_committed_words_counter->increment_by(commit_increase_words); + + // ... and update the commit mask. + _commit_mask.mark_range_as_committed(p, word_size); + +#ifdef ASSERT + // The commit boundary maintained in the CommitLimiter should be equal the sum of committed words + // in both class and non-class vslist (outside gtests). + if (_commit_limiter == CommitLimiter::globalLimiter()) { + assert(_commit_limiter->committed_words() == RunningCounters::committed_words(), "counter mismatch"); + } +#endif + + DEBUG_ONLY(InternalStats::inc_num_space_committed();) + + return true; + } -// byte_size is the size of the associated virtualspace. -VirtualSpaceNode::VirtualSpaceNode(bool is_class, size_t bytes) : - _next(NULL), _is_class(is_class), _rs(), _top(NULL), _container_count(0), _occupancy_map(NULL) { - assert_is_aligned(bytes, Metaspace::reserve_alignment()); - bool large_pages = should_commit_large_pages_when_reserving(bytes); - _rs = ReservedSpace(bytes, Metaspace::reserve_alignment(), large_pages); +// Given an address range, ensure it is committed. +// +// The range does not have to be aligned to granule size. However, the function will always commit +// whole granules. +// +// Function will: +// - check how many granules in that region are uncommitted; If all are committed, it +// returns true immediately. +// - check if committing those uncommitted granules would bring us over the commit limit +// (GC threshold, MaxMetaspaceSize). If true, it returns false. +// - commit the memory. +// - mark the range as committed in the commit mask +// +// !! Careful: +// calling ensure_range_is_committed on a range which contains both committed and uncommitted +// areas will commit the whole area, thus erase the content in the existing committed parts. +// Make sure you never call this on an address range containing live data. !! +// +// Returns true if success, false if it did hit a commit limit. +bool VirtualSpaceNode::ensure_range_is_committed(MetaWord* p, size_t word_size) { - if (_rs.is_reserved()) { - assert(_rs.base() != NULL, "Catch if we get a NULL address"); - assert(_rs.size() != 0, "Catch if we get a 0 size"); - assert_is_aligned(_rs.base(), Metaspace::reserve_alignment()); - assert_is_aligned(_rs.size(), Metaspace::reserve_alignment()); + assert_lock_strong(MetaspaceExpand_lock); + assert(p != NULL && word_size > 0, "Sanity"); - MemTracker::record_virtual_memory_type((address)_rs.base(), mtClass); - } + MetaWord* p_start = align_down(p, Settings::commit_granule_bytes()); + MetaWord* p_end = align_up(p + word_size, Settings::commit_granule_bytes()); + + // Todo: simple for now. Make it more intelligent late + return commit_range(p_start, p_end - p_start); + } -void VirtualSpaceNode::purge(ChunkManager* chunk_manager) { - // When a node is purged, lets give it a thorough examination. - DEBUG_ONLY(verify(true);) - Metachunk* chunk = first_chunk(); - Metachunk* invalid_chunk = (Metachunk*) top(); - while (chunk < invalid_chunk ) { - assert(chunk->is_tagged_free(), "Should be tagged free"); - MetaWord* next = ((MetaWord*)chunk) + chunk->word_size(); - chunk_manager->remove_chunk(chunk); - chunk->remove_sentinel(); - assert(chunk->next() == NULL && - chunk->prev() == NULL, - "Was not removed from its list"); - chunk = (Metachunk*) next; +// Given an address range (which has to be aligned to commit granule size): +// - uncommit it +// - mark it as uncommitted in the commit mask +void VirtualSpaceNode::uncommit_range(MetaWord* p, size_t word_size) { + + DEBUG_ONLY(check_pointer_is_aligned_to_commit_granule(p);) + DEBUG_ONLY(check_word_size_is_aligned_to_commit_granule(word_size);) + assert_lock_strong(MetaspaceExpand_lock); + + // First calculate how large the committed regions in this range are + const size_t committed_words_in_range = _commit_mask.get_committed_size_in_range(p, word_size); + DEBUG_ONLY(check_word_size_is_aligned_to_commit_granule(committed_words_in_range);) + + log_debug(metaspace)("VirtualSpaceNode %d, base " PTR_FORMAT ": uncommitting range " PTR_FORMAT ".." PTR_FORMAT "(" SIZE_FORMAT " words)", + _node_id, p2i(_base), p2i(p), p2i(p + word_size), word_size); + + if (committed_words_in_range == 0) { + log_debug(metaspace)("VirtualSpaceNode %d, base " PTR_FORMAT ": ... already fully uncommitted.", + _node_id, p2i(_base)); + return; // Already fully uncommitted, nothing to do. } + + // Uncommit... + if (os::uncommit_memory((char*)p, word_size * BytesPerWord) == false) { + // Note: this can actually happen, since uncommit may increase the number of mappings. + fatal("Failed to uncommit metaspace."); + } + + log_debug(metaspace)("Decreased metaspace by " SIZE_FORMAT " bytes.", + committed_words_in_range * BytesPerWord); + + // ... tell commit limiter... + _commit_limiter->decrease_committed(committed_words_in_range); + + // ... and global counters... + _total_committed_words_counter->decrement_by(committed_words_in_range); + + // ... and update the commit mask. + _commit_mask.mark_range_as_uncommitted(p, word_size); + +#ifdef ASSERT + // The commit boundary maintained in the CommitLimiter should be equal the sum of committed words + // in both class and non-class vslist (outside gtests). + if (_commit_limiter == CommitLimiter::globalLimiter()) { // We are outside a test scenario + assert(_commit_limiter->committed_words() == RunningCounters::committed_words(), "counter mismatch"); + } +#endif + + DEBUG_ONLY(InternalStats::inc_num_space_uncommitted();) + } -void VirtualSpaceNode::print_map(outputStream* st, bool is_class) const { +//// creation, destruction //// - if (bottom() == top()) { - return; +VirtualSpaceNode::VirtualSpaceNode(int node_id, + ReservedSpace rs, + CommitLimiter* limiter, + SizeCounter* reserve_words_counter, + SizeCounter* commit_words_counter) + : _next(NULL), + _rs(rs), + _base((MetaWord*)rs.base()), + _word_size(rs.size() / BytesPerWord), + _used_words(0), + _commit_mask((MetaWord*)rs.base(), rs.size() / BytesPerWord), + _root_chunk_area_lut((MetaWord*)rs.base(), rs.size() / BytesPerWord), + _commit_limiter(limiter), + _total_reserved_words_counter(reserve_words_counter), + _total_committed_words_counter(commit_words_counter), + _node_id(node_id) +{ + + log_debug(metaspace)("Create new VirtualSpaceNode %d, base " PTR_FORMAT ", word size " SIZE_FORMAT ".", + _node_id, p2i(_base), _word_size); + + // Update reserved counter in vslist + _total_reserved_words_counter->increment_by(_word_size); + + assert_is_aligned(_base, chklvl::MAX_CHUNK_BYTE_SIZE); + assert_is_aligned(_word_size, chklvl::MAX_CHUNK_WORD_SIZE); + + // Explicitly uncommit the whole node to make it guaranteed + // inaccessible, for testing +// os::uncommit_memory((char*)_base, _word_size * BytesPerWord); + +} + +// Create a node of a given size +VirtualSpaceNode* VirtualSpaceNode::create_node(int node_id, + size_t word_size, + CommitLimiter* limiter, + SizeCounter* reserve_words_counter, + SizeCounter* commit_words_counter) +{ + + DEBUG_ONLY(assert_is_aligned(word_size, chklvl::MAX_CHUNK_WORD_SIZE);) + + ReservedSpace rs(word_size * BytesPerWord, + chklvl::MAX_CHUNK_BYTE_SIZE, + false, // TODO deal with large pages + false); + + if (!rs.is_reserved()) { + vm_exit_out_of_memory(word_size * BytesPerWord, OOM_MMAP_ERROR, "Failed to reserve memory for metaspace"); } - const size_t spec_chunk_size = is_class ? ClassSpecializedChunk : SpecializedChunk; - const size_t small_chunk_size = is_class ? ClassSmallChunk : SmallChunk; - const size_t med_chunk_size = is_class ? ClassMediumChunk : MediumChunk; + assert_is_aligned(rs.base(), chklvl::MAX_CHUNK_BYTE_SIZE); - int line_len = 100; - const size_t section_len = align_up(spec_chunk_size * line_len, med_chunk_size); - line_len = (int)(section_len / spec_chunk_size); + return create_node(node_id, rs, limiter, reserve_words_counter, commit_words_counter); - static const int NUM_LINES = 4; - - char* lines[NUM_LINES]; - for (int i = 0; i < NUM_LINES; i ++) { - lines[i] = (char*)os::malloc(line_len, mtInternal); - } - int pos = 0; - const MetaWord* p = bottom(); - const Metachunk* chunk = (const Metachunk*)p; - const MetaWord* chunk_end = p + chunk->word_size(); - while (p < top()) { - if (pos == line_len) { - pos = 0; - for (int i = 0; i < NUM_LINES; i ++) { - st->fill_to(22); - st->print_raw(lines[i], line_len); - st->cr(); - } - } - if (pos == 0) { - st->print(PTR_FORMAT ":", p2i(p)); - } - if (p == chunk_end) { - chunk = (Metachunk*)p; - chunk_end = p + chunk->word_size(); - } - // line 1: chunk starting points (a dot if that area is a chunk start). - lines[0][pos] = p == (const MetaWord*)chunk ? '.' : ' '; - - // Line 2: chunk type (x=spec, s=small, m=medium, h=humongous), uppercase if - // chunk is in use. - const bool chunk_is_free = ((Metachunk*)chunk)->is_tagged_free(); - if (chunk->word_size() == spec_chunk_size) { - lines[1][pos] = chunk_is_free ? 'x' : 'X'; - } else if (chunk->word_size() == small_chunk_size) { - lines[1][pos] = chunk_is_free ? 's' : 'S'; - } else if (chunk->word_size() == med_chunk_size) { - lines[1][pos] = chunk_is_free ? 'm' : 'M'; - } else if (chunk->word_size() > med_chunk_size) { - lines[1][pos] = chunk_is_free ? 'h' : 'H'; - } else { - ShouldNotReachHere(); - } - - // Line 3: chunk origin - const ChunkOrigin origin = chunk->get_origin(); - lines[2][pos] = origin == origin_normal ? ' ' : '0' + (int) origin; - - // Line 4: Virgin chunk? Virgin chunks are chunks created as a byproduct of padding or splitting, - // but were never used. - lines[3][pos] = chunk->get_use_count() > 0 ? ' ' : 'v'; - - p += spec_chunk_size; - pos ++; - } - if (pos > 0) { - for (int i = 0; i < NUM_LINES; i ++) { - st->fill_to(22); - st->print_raw(lines[i], line_len); - st->cr(); - } - } - for (int i = 0; i < NUM_LINES; i ++) { - os::free(lines[i]); - } } - -#ifdef ASSERT - -// Verify counters, all chunks in this list node and the occupancy map. -void VirtualSpaceNode::verify(bool slow) { - log_trace(gc, metaspace, freelist)("verifying %s virtual space node (%s).", - (is_class() ? "class space" : "metaspace"), (slow ? "slow" : "quick")); - // Fast mode: just verify chunk counters and basic geometry - // Slow mode: verify chunks and occupancy map - uintx num_in_use_chunks = 0; - Metachunk* chunk = first_chunk(); - Metachunk* invalid_chunk = (Metachunk*) top(); - - // Iterate the chunks in this node and verify each chunk. - while (chunk < invalid_chunk ) { - if (slow) { - do_verify_chunk(chunk); - } - if (!chunk->is_tagged_free()) { - num_in_use_chunks ++; - } - const size_t s = chunk->word_size(); - // Prevent endless loop on invalid chunk size. - assert(is_valid_chunksize(is_class(), s), "Invalid chunk size: " SIZE_FORMAT ".", s); - MetaWord* next = ((MetaWord*)chunk) + s; - chunk = (Metachunk*) next; - } - assert(_container_count == num_in_use_chunks, "Container count mismatch (real: " UINTX_FORMAT - ", counter: " UINTX_FORMAT ".", num_in_use_chunks, _container_count); - // Also verify the occupancy map. - if (slow) { - occupancy_map()->verify(bottom(), top()); - } -} - -// Verify that all free chunks in this node are ideally merged -// (there not should be multiple small chunks where a large chunk could exist.) -void VirtualSpaceNode::verify_free_chunks_are_ideally_merged() { - Metachunk* chunk = first_chunk(); - Metachunk* invalid_chunk = (Metachunk*) top(); - // Shorthands. - const size_t size_med = (is_class() ? ClassMediumChunk : MediumChunk) * BytesPerWord; - const size_t size_small = (is_class() ? ClassSmallChunk : SmallChunk) * BytesPerWord; - int num_free_chunks_since_last_med_boundary = -1; - int num_free_chunks_since_last_small_boundary = -1; - bool error = false; - char err[256]; - while (!error && chunk < invalid_chunk ) { - // Test for missed chunk merge opportunities: count number of free chunks since last chunk boundary. - // Reset the counter when encountering a non-free chunk. - if (chunk->get_chunk_type() != HumongousIndex) { - if (chunk->is_tagged_free()) { - // Count successive free, non-humongous chunks. - if (is_aligned(chunk, size_small)) { - if (num_free_chunks_since_last_small_boundary > 0) { - error = true; - jio_snprintf(err, sizeof(err), "Missed chunk merge opportunity to merge a small chunk preceding " PTR_FORMAT ".", p2i(chunk)); - } else { - num_free_chunks_since_last_small_boundary = 0; - } - } else if (num_free_chunks_since_last_small_boundary != -1) { - num_free_chunks_since_last_small_boundary ++; - } - if (is_aligned(chunk, size_med)) { - if (num_free_chunks_since_last_med_boundary > 0) { - error = true; - jio_snprintf(err, sizeof(err), "Missed chunk merge opportunity to merge a medium chunk preceding " PTR_FORMAT ".", p2i(chunk)); - } else { - num_free_chunks_since_last_med_boundary = 0; - } - } else if (num_free_chunks_since_last_med_boundary != -1) { - num_free_chunks_since_last_med_boundary ++; - } - } else { - // Encountering a non-free chunk, reset counters. - num_free_chunks_since_last_med_boundary = -1; - num_free_chunks_since_last_small_boundary = -1; - } - } else { - // One cannot merge areas with a humongous chunk in the middle. Reset counters. - num_free_chunks_since_last_med_boundary = -1; - num_free_chunks_since_last_small_boundary = -1; - } - - if (error) { - print_map(tty, is_class()); - fatal("%s", err); - } - - MetaWord* next = ((MetaWord*)chunk) + chunk->word_size(); - chunk = (Metachunk*) next; - } -} -#endif // ASSERT - -void VirtualSpaceNode::inc_container_count() { - assert_lock_strong(MetaspaceExpand_lock); - _container_count++; -} - -void VirtualSpaceNode::dec_container_count() { - assert_lock_strong(MetaspaceExpand_lock); - _container_count--; +// Create a node over an existing space +VirtualSpaceNode* VirtualSpaceNode::create_node(int node_id, + ReservedSpace rs, + CommitLimiter* limiter, + SizeCounter* reserve_words_counter, + SizeCounter* commit_words_counter) +{ + DEBUG_ONLY(InternalStats::inc_num_vsnodes_created();) + return new VirtualSpaceNode(node_id, rs, limiter, reserve_words_counter, commit_words_counter); } VirtualSpaceNode::~VirtualSpaceNode() { _rs.release(); - if (_occupancy_map != NULL) { - delete _occupancy_map; - } -#ifdef ASSERT - size_t word_size = sizeof(*this) / BytesPerWord; - Copy::fill_to_words((HeapWord*) this, word_size, 0xf1f1f1f1); -#endif + + log_debug(metaspace)("Destroying VirtualSpaceNode %d, base " PTR_FORMAT ", word size " SIZE_FORMAT ".", + _node_id, p2i(_base), _word_size); + + // Update counters in vslist + _total_committed_words_counter->decrement_by(committed_words()); + _total_reserved_words_counter->decrement_by(_word_size); + + DEBUG_ONLY(InternalStats::inc_num_vsnodes_destroyed();) + } -size_t VirtualSpaceNode::used_words_in_vs() const { - return pointer_delta(top(), bottom(), sizeof(MetaWord)); + + +//// Chunk allocation, splitting, merging ///// + +// Allocate a root chunk from this node. Will fail and return NULL +// if the node is full. +// Note: this just returns a chunk whose memory is reserved; no memory is committed yet. +// Hence, before using this chunk, it must be committed. +// Also, no limits are checked, since no committing takes place. +Metachunk* VirtualSpaceNode::allocate_root_chunk() { + + assert_lock_strong(MetaspaceExpand_lock); + + assert_is_aligned(free_words(), chklvl::MAX_CHUNK_WORD_SIZE); + + if (free_words() >= chklvl::MAX_CHUNK_WORD_SIZE) { + + MetaWord* loc = _base + _used_words; + _used_words += chklvl::MAX_CHUNK_WORD_SIZE; + + RootChunkArea* rca = _root_chunk_area_lut.get_area_by_address(loc); + + // Create a root chunk header and initialize it; + Metachunk* c = rca->alloc_root_chunk_header(this); + + assert(c->base() == loc && c->vsnode() == this && + c->is_free(), "Sanity"); + + DEBUG_ONLY(c->verify(true);) + + log_debug(metaspace)("VirtualSpaceNode %d, base " PTR_FORMAT ": newborn root chunk " METACHUNK_FORMAT ".", + _node_id, p2i(_base), METACHUNK_FORMAT_ARGS(c)); + + if (Settings::newborn_root_chunks_are_fully_committed()) { + log_trace(metaspace)("VirtualSpaceNode %d, base " PTR_FORMAT ": committing newborn root chunk.", + _node_id, p2i(_base)); + // Note: use Metachunk::ensure_commit, do not commit directly. This makes sure the chunk knows + // its commit range and does not ask needlessly. + c->ensure_fully_committed_locked(); + } + + return c; + + } + + return NULL; // Node is full. + } -// Space committed in the VirtualSpace -size_t VirtualSpaceNode::capacity_words_in_vs() const { - return pointer_delta(end(), bottom(), sizeof(MetaWord)); +// Given a chunk c, split it recursively until you get a chunk of the given target_level. +// +// The original chunk must not be part of a freelist. +// +// Returns pointer to the result chunk; the splitted-off chunks are added as +// free chunks to the freelists. +// +// Returns NULL if chunk cannot be split at least once. +Metachunk* VirtualSpaceNode::split(chklvl_t target_level, Metachunk* c, MetachunkListCluster* freelists) { + + assert_lock_strong(MetaspaceExpand_lock); + + // Get the area associated with this chunk and let it handle the splitting + RootChunkArea* rca = _root_chunk_area_lut.get_area_by_address(c->base()); + + DEBUG_ONLY(rca->verify_area_is_ideally_merged();) + + return rca->split(target_level, c, freelists); + } -size_t VirtualSpaceNode::free_words_in_vs() const { - return pointer_delta(end(), top(), sizeof(MetaWord)); + +// Given a chunk, attempt to merge it recursively with its neighboring chunks. +// +// If successful (merged at least once), returns address of +// the merged chunk; NULL otherwise. +// +// The merged chunks are removed from the freelists. +// +// !!! Please note that if this method returns a non-NULL value, the +// original chunk will be invalid and should not be accessed anymore! !!! +Metachunk* VirtualSpaceNode::merge(Metachunk* c, MetachunkListCluster* freelists) { + + assert(c != NULL && c->is_free(), "Sanity"); + assert_lock_strong(MetaspaceExpand_lock); + + // Get the tree associated with this chunk and let it handle the merging + RootChunkArea* rca = _root_chunk_area_lut.get_area_by_address(c->base()); + + Metachunk* c2 = rca->merge(c, freelists); + + DEBUG_ONLY(rca->verify_area_is_ideally_merged();) + + return c2; + } -// Given an address larger than top(), allocate padding chunks until top is at the given address. -void VirtualSpaceNode::allocate_padding_chunks_until_top_is_at(MetaWord* target_top) { +// Given a chunk c, which must be "in use" and must not be a root chunk, attempt to +// enlarge it in place by claiming its trailing buddy. +// +// This will only work if c is the leader of the buddy pair and the trailing buddy is free. +// +// If successful, the follower chunk will be removed from the freelists, the leader chunk c will +// double in size (level decreased by one). +// +// On success, true is returned, false otherwise. +bool VirtualSpaceNode::attempt_enlarge_chunk(Metachunk* c, MetachunkListCluster* freelists) { - assert(target_top > top(), "Sanity"); + assert(c != NULL && c->is_in_use() && !c->is_root_chunk(), "Sanity"); + assert_lock_strong(MetaspaceExpand_lock); - // Padding chunks are added to the freelist. - ChunkManager* const chunk_manager = Metaspace::get_chunk_manager(is_class()); + // Get the tree associated with this chunk and let it handle the merging + RootChunkArea* rca = _root_chunk_area_lut.get_area_by_address(c->base()); - // shorthands - const size_t spec_word_size = chunk_manager->specialized_chunk_word_size(); - const size_t small_word_size = chunk_manager->small_chunk_word_size(); - const size_t med_word_size = chunk_manager->medium_chunk_word_size(); + bool rc = rca->attempt_enlarge_chunk(c, freelists); - while (top() < target_top) { + DEBUG_ONLY(rca->verify_area_is_ideally_merged();) - // We could make this coding more generic, but right now we only deal with two possible chunk sizes - // for padding chunks, so it is not worth it. - size_t padding_chunk_word_size = small_word_size; - if (is_aligned(top(), small_word_size * sizeof(MetaWord)) == false) { - assert_is_aligned(top(), spec_word_size * sizeof(MetaWord)); // Should always hold true. - padding_chunk_word_size = spec_word_size; + return rc; + +} + +// Attempts to purge the node: +// +// If all chunks living in this node are free, they will all be removed from their freelists +// and deletes the node. +// +// Returns true if the node has been deleted, false if not. +// !! If this returns true, do not access the node from this point on. !! +bool VirtualSpaceNode::attempt_purge(MetachunkListCluster* freelists) { + + assert_lock_strong(MetaspaceExpand_lock); + + // First find out if all areas are empty. Since empty chunks collapse to root chunk + // size, if all chunks in this node are free root chunks we are good to go. + for (int narea = 0; narea < _root_chunk_area_lut.number_of_areas(); narea ++) { + const RootChunkArea* ra = _root_chunk_area_lut.get_area_by_index(narea); + const Metachunk* c = ra->first_chunk(); + if (c != NULL) { + if (!(c->is_root_chunk() && c->is_free())) { + return false; + } } - MetaWord* here = top(); - assert_is_aligned(here, padding_chunk_word_size * sizeof(MetaWord)); - inc_top(padding_chunk_word_size); - - // Create new padding chunk. - ChunkIndex padding_chunk_type = get_chunk_type_by_size(padding_chunk_word_size, is_class()); - assert(padding_chunk_type == SpecializedIndex || padding_chunk_type == SmallIndex, "sanity"); - - Metachunk* const padding_chunk = - ::new (here) Metachunk(padding_chunk_type, is_class(), padding_chunk_word_size, this); - assert(padding_chunk == (Metachunk*)here, "Sanity"); - DEBUG_ONLY(padding_chunk->set_origin(origin_pad);) - log_trace(gc, metaspace, freelist)("Created padding chunk in %s at " - PTR_FORMAT ", size " SIZE_FORMAT_HEX ".", - (is_class() ? "class space " : "metaspace"), - p2i(padding_chunk), padding_chunk->word_size() * sizeof(MetaWord)); - - // Mark chunk start in occupancy map. - occupancy_map()->set_chunk_starts_at_address((MetaWord*)padding_chunk, true); - - // Chunks are born as in-use (see MetaChunk ctor). So, before returning - // the padding chunk to its chunk manager, mark it as in use (ChunkManager - // will assert that). - do_update_in_use_info_for_chunk(padding_chunk, true); - - // Return Chunk to freelist. - inc_container_count(); - chunk_manager->return_single_chunk(padding_chunk); - // Please note: at this point, ChunkManager::return_single_chunk() - // may already have merged the padding chunk with neighboring chunks, so - // it may have vanished at this point. Do not reference the padding - // chunk beyond this point. } - assert(top() == target_top, "Sanity"); + log_debug(metaspace)("VirtualSpaceNode %d, base " PTR_FORMAT ": purging.", _node_id, p2i(_base)); -} // allocate_padding_chunks_until_top_is_at() - -// Allocates the chunk from the virtual space only. -// This interface is also used internally for debugging. Not all -// chunks removed here are necessarily used for allocation. -Metachunk* VirtualSpaceNode::take_from_committed(size_t chunk_word_size) { - // Non-humongous chunks are to be allocated aligned to their chunk - // size. So, start addresses of medium chunks are aligned to medium - // chunk size, those of small chunks to small chunk size and so - // forth. This facilitates merging of free chunks and reduces - // fragmentation. Chunk sizes are spec < small < medium, with each - // larger chunk size being a multiple of the next smaller chunk - // size. - // Because of this alignment, me may need to create a number of padding - // chunks. These chunks are created and added to the freelist. - - // The chunk manager to which we will give our padding chunks. - ChunkManager* const chunk_manager = Metaspace::get_chunk_manager(is_class()); - - // shorthands - const size_t spec_word_size = chunk_manager->specialized_chunk_word_size(); - const size_t small_word_size = chunk_manager->small_chunk_word_size(); - const size_t med_word_size = chunk_manager->medium_chunk_word_size(); - - assert(chunk_word_size == spec_word_size || chunk_word_size == small_word_size || - chunk_word_size >= med_word_size, "Invalid chunk size requested."); - - // Chunk alignment (in bytes) == chunk size unless humongous. - // Humongous chunks are aligned to the smallest chunk size (spec). - const size_t required_chunk_alignment = (chunk_word_size > med_word_size ? - spec_word_size : chunk_word_size) * sizeof(MetaWord); - - // Do we have enough space to create the requested chunk plus - // any padding chunks needed? - MetaWord* const next_aligned = - static_cast(align_up(top(), required_chunk_alignment)); - if (!is_available((next_aligned - top()) + chunk_word_size)) { - return NULL; + // Okay, we can purge. Before we can do this, we need to remove all chunks from the freelist. + for (int narea = 0; narea < _root_chunk_area_lut.number_of_areas(); narea ++) { + RootChunkArea* ra = _root_chunk_area_lut.get_area_by_index(narea); + Metachunk* c = ra->first_chunk(); + if (c != NULL) { + log_trace(metaspace)("VirtualSpaceNode %d, base " PTR_FORMAT ": removing chunk " METACHUNK_FULL_FORMAT ".", + _node_id, p2i(_base), METACHUNK_FULL_FORMAT_ARGS(c)); + assert(c->is_free() && c->is_root_chunk(), "Sanity"); + freelists->remove(c); + } } - // Before allocating the requested chunk, allocate padding chunks if necessary. - // We only need to do this for small or medium chunks: specialized chunks are the - // smallest size, hence always aligned. Homungous chunks are allocated unaligned - // (implicitly, also aligned to smallest chunk size). - if ((chunk_word_size == med_word_size || chunk_word_size == small_word_size) && next_aligned > top()) { - log_trace(gc, metaspace, freelist)("Creating padding chunks in %s between %p and %p...", - (is_class() ? "class space " : "metaspace"), - top(), next_aligned); - allocate_padding_chunks_until_top_is_at(next_aligned); - // Now, top should be aligned correctly. - assert_is_aligned(top(), required_chunk_alignment); - } + // Now, delete the node, then right away return since this object is invalid. + delete this; - // Now, top should be aligned correctly. - assert_is_aligned(top(), required_chunk_alignment); + return true; - // Bottom of the new chunk - MetaWord* chunk_limit = top(); - assert(chunk_limit != NULL, "Not safe to call this method"); - - // The virtual spaces are always expanded by the - // commit granularity to enforce the following condition. - // Without this the is_available check will not work correctly. - assert(_virtual_space.committed_size() == _virtual_space.actual_committed_size(), - "The committed memory doesn't match the expanded memory."); - - if (!is_available(chunk_word_size)) { - LogTarget(Trace, gc, metaspace, freelist) lt; - if (lt.is_enabled()) { - LogStream ls(lt); - ls.print("VirtualSpaceNode::take_from_committed() not available " SIZE_FORMAT " words ", chunk_word_size); - // Dump some information about the virtual space that is nearly full - print_on(&ls); - } - return NULL; - } - - // Take the space (bump top on the current virtual space). - inc_top(chunk_word_size); - - // Initialize the chunk - ChunkIndex chunk_type = get_chunk_type_by_size(chunk_word_size, is_class()); - Metachunk* result = ::new (chunk_limit) Metachunk(chunk_type, is_class(), chunk_word_size, this); - assert(result == (Metachunk*)chunk_limit, "Sanity"); - occupancy_map()->set_chunk_starts_at_address((MetaWord*)result, true); - do_update_in_use_info_for_chunk(result, true); - - inc_container_count(); - -#ifdef ASSERT - EVERY_NTH(VerifyMetaspaceInterval) - chunk_manager->locked_verify(true); - verify(true); - END_EVERY_NTH - do_verify_chunk(result); -#endif - - result->inc_use_count(); - - return result; } -// Expand the virtual space (commit more of the reserved space) -bool VirtualSpaceNode::expand_by(size_t min_words, size_t preferred_words) { - size_t min_bytes = min_words * BytesPerWord; - size_t preferred_bytes = preferred_words * BytesPerWord; +void VirtualSpaceNode::print_on(outputStream* st) const { - size_t uncommitted = virtual_space()->reserved_size() - virtual_space()->actual_committed_size(); + size_t scale = K; - if (uncommitted < min_bytes) { - return false; - } + st->print("id: %d, base " PTR_FORMAT ": ", _node_id, p2i(base())); + st->print("reserved="); + print_scaled_words(st, word_size(), scale); + st->print(", committed="); + print_scaled_words_and_percentage(st, committed_words(), word_size(), scale); + st->print(", used="); + print_scaled_words_and_percentage(st, used_words(), word_size(), scale); - size_t commit = MIN2(preferred_bytes, uncommitted); - bool result = virtual_space()->expand_by(commit, false); + st->cr(); - if (result) { - log_trace(gc, metaspace, freelist)("Expanded %s virtual space list node by " SIZE_FORMAT " words.", - (is_class() ? "class" : "non-class"), commit); - DEBUG_ONLY(Atomic::inc(&g_internal_statistics.num_committed_space_expanded)); - } else { - log_trace(gc, metaspace, freelist)("Failed to expand %s virtual space list node by " SIZE_FORMAT " words.", - (is_class() ? "class" : "non-class"), commit); - } + _root_chunk_area_lut.print_on(st); + _commit_mask.print_on(st); - assert(result, "Failed to commit memory"); - - return result; } -Metachunk* VirtualSpaceNode::get_chunk_vs(size_t chunk_word_size) { - assert_lock_strong(MetaspaceExpand_lock); - Metachunk* result = take_from_committed(chunk_word_size); - return result; -} - -bool VirtualSpaceNode::initialize() { - - if (!_rs.is_reserved()) { - return false; - } - - // These are necessary restriction to make sure that the virtual space always - // grows in steps of Metaspace::commit_alignment(). If both base and size are - // aligned only the middle alignment of the VirtualSpace is used. - assert_is_aligned(_rs.base(), Metaspace::commit_alignment()); - assert_is_aligned(_rs.size(), Metaspace::commit_alignment()); - - // ReservedSpaces marked as special will have the entire memory - // pre-committed. Setting a committed size will make sure that - // committed_size and actual_committed_size agrees. - size_t pre_committed_size = _rs.special() ? _rs.size() : 0; - - bool result = virtual_space()->initialize_with_granularity(_rs, pre_committed_size, - Metaspace::commit_alignment()); - if (result) { - assert(virtual_space()->committed_size() == virtual_space()->actual_committed_size(), - "Checking that the pre-committed memory was registered by the VirtualSpace"); - - set_top((MetaWord*)virtual_space()->low()); - } - - // Initialize Occupancy Map. - const size_t smallest_chunk_size = is_class() ? ClassSpecializedChunk : SpecializedChunk; - _occupancy_map = new OccupancyMap(bottom(), reserved_words(), smallest_chunk_size); - - return result; -} - -void VirtualSpaceNode::print_on(outputStream* st, size_t scale) const { - size_t used_words = used_words_in_vs(); - size_t commit_words = committed_words(); - size_t res_words = reserved_words(); - VirtualSpace* vs = virtual_space(); - - st->print("node @" PTR_FORMAT ": ", p2i(this)); - st->print("reserved="); - print_scaled_words(st, res_words, scale); - st->print(", committed="); - print_scaled_words_and_percentage(st, commit_words, res_words, scale); - st->print(", used="); - print_scaled_words_and_percentage(st, used_words, res_words, scale); - st->cr(); - st->print(" [" PTR_FORMAT ", " PTR_FORMAT ", " - PTR_FORMAT ", " PTR_FORMAT ")", - p2i(bottom()), p2i(top()), p2i(end()), - p2i(vs->high_boundary())); +// Returns size, in words, of committed space in this node alone. +// Note: iterates over commit mask and hence may be a tad expensive on large nodes. +size_t VirtualSpaceNode::committed_words() const { + return _commit_mask.get_committed_size(); } #ifdef ASSERT -void VirtualSpaceNode::mangle() { - size_t word_size = capacity_words_in_vs(); - Copy::fill_to_words((HeapWord*) low(), word_size, 0xf1f1f1f1); +// Verify counters and basic structure. Slow mode: verify all chunks in depth +void VirtualSpaceNode::verify(bool slow) const { + + assert_lock_strong(MetaspaceExpand_lock); + + assert(base() != NULL, "Invalid base"); + assert(base() == (MetaWord*)_rs.base() && + word_size() == _rs.size() / BytesPerWord, + "Sanity"); + assert_is_aligned(base(), chklvl::MAX_CHUNK_BYTE_SIZE); + assert(used_words() <= word_size(), "Sanity"); + + // Since we only ever hand out root chunks from a vsnode, top should always be aligned + // to root chunk size. + assert_is_aligned(used_words(), chklvl::MAX_CHUNK_WORD_SIZE); + + _commit_mask.verify(slow); + assert(committed_words() <= word_size(), "Sanity"); + assert_is_aligned(committed_words(), Settings::commit_granule_words()); + _root_chunk_area_lut.verify(slow); + } -#endif // ASSERT -void VirtualSpaceNode::retire(ChunkManager* chunk_manager) { - assert(is_class() == chunk_manager->is_class(), "Wrong ChunkManager?"); -#ifdef ASSERT - verify(false); - EVERY_NTH(VerifyMetaspaceInterval) - verify(true); - END_EVERY_NTH #endif - for (int i = (int)MediumIndex; i >= (int)ZeroIndex; --i) { - ChunkIndex index = (ChunkIndex)i; - size_t chunk_size = chunk_manager->size_by_index(index); - while (free_words_in_vs() >= chunk_size) { - Metachunk* chunk = get_chunk_vs(chunk_size); - // Chunk will be allocated aligned, so allocation may require - // additional padding chunks. That may cause above allocation to - // fail. Just ignore the failed allocation and continue with the - // next smaller chunk size. As the VirtualSpaceNode comitted - // size should be a multiple of the smallest chunk size, we - // should always be able to fill the VirtualSpace completely. - if (chunk == NULL) { - break; - } - chunk_manager->return_single_chunk(chunk); - } - } - assert(free_words_in_vs() == 0, "should be empty now"); -} } // namespace metaspace diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspace/virtualSpaceNode.hpp --- a/src/hotspot/share/memory/metaspace/virtualSpaceNode.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspace/virtualSpaceNode.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -1,5 +1,6 @@ /* * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019 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 @@ -25,139 +26,247 @@ #ifndef SHARE_MEMORY_METASPACE_VIRTUALSPACENODE_HPP #define SHARE_MEMORY_METASPACE_VIRTUALSPACENODE_HPP + +#include +#include "memory/allocation.hpp" +#include "memory/metaspace/counter.hpp" +#include "memory/metaspace/commitMask.hpp" +#include "memory/metaspace/rootChunkArea.hpp" #include "memory/virtualspace.hpp" #include "memory/memRegion.hpp" #include "utilities/debug.hpp" +#include "utilities/bitMap.hpp" #include "utilities/globalDefinitions.hpp" + class outputStream; namespace metaspace { -class Metachunk; -class ChunkManager; -class OccupancyMap; +class CommitLimiter; +class MetachunkListCluster; -// A VirtualSpaceList node. +// VirtualSpaceNode manage a single address range of the Metaspace. +// +// That address range may contain interleaved committed and uncommitted +// regions. It keeps track of which regions have committed and offers +// functions to commit and uncommit regions. +// +// It allocates and hands out memory ranges, starting at the bottom. +// +// Address range must be aligned to root chunk size. +// class VirtualSpaceNode : public CHeapObj { - friend class VirtualSpaceList; // Link to next VirtualSpaceNode VirtualSpaceNode* _next; - // Whether this node is contained in class or metaspace. - const bool _is_class; + ReservedSpace _rs; - // total in the VirtualSpace - ReservedSpace _rs; - VirtualSpace _virtual_space; - MetaWord* _top; - // count of chunks contained in this VirtualSpace - uintx _container_count; + // Start pointer of the area. + MetaWord* const _base; - OccupancyMap* _occupancy_map; + // Size, in words, of the whole node + const size_t _word_size; - // Convenience functions to access the _virtual_space - char* low() const { return virtual_space()->low(); } - char* high() const { return virtual_space()->high(); } - char* low_boundary() const { return virtual_space()->low_boundary(); } - char* high_boundary() const { return virtual_space()->high_boundary(); } + // Size, in words, of the range of this node which has been handed out in + // the form of chunks. + size_t _used_words; - // The first Metachunk will be allocated at the bottom of the - // VirtualSpace - Metachunk* first_chunk() { return (Metachunk*) bottom(); } + // The bitmap describing the commit state of the region: + // Each bit covers a region of 64K (see constants::commit_granule_size). + CommitMask _commit_mask; - // Committed but unused space in the virtual space - size_t free_words_in_vs() const; + // An array/LUT of RootChunkArea objects. Each one describes + // fragmentation inside a root chunk. + RootChunkAreaLUT _root_chunk_area_lut; - // True if this node belongs to class metaspace. - bool is_class() const { return _is_class; } + // Limiter object to ask before expanding the committed size of this node. + CommitLimiter* const _commit_limiter; - // Helper function for take_from_committed: allocate padding chunks - // until top is at the given address. - void allocate_padding_chunks_until_top_is_at(MetaWord* target_top); + // Points to outside size counters which we are to increase/decrease when we commit/uncommit + // space from this node. + SizeCounter* const _total_reserved_words_counter; + SizeCounter* const _total_committed_words_counter; - public: + // For debug and tracing purposes + const int _node_id; - VirtualSpaceNode(bool is_class, size_t byte_size); - VirtualSpaceNode(bool is_class, ReservedSpace rs) : - _next(NULL), _is_class(is_class), _rs(rs), _top(NULL), _container_count(0), _occupancy_map(NULL) {} + /// committing, uncommitting /// + + // Given a pointer into this node, calculate the start of the commit granule + // the pointer points into. + MetaWord* calc_start_of_granule(MetaWord* p) const { + DEBUG_ONLY(check_pointer(p)); + return align_down(p, Settings::commit_granule_bytes()); + } + + // Given an address range, ensure it is committed. + // + // The range has to be aligned to granule size. + // + // Function will: + // - check how many granules in that region are uncommitted; If all are committed, it + // returns true immediately. + // - check if committing those uncommitted granules would bring us over the commit limit + // (GC threshold, MaxMetaspaceSize). If true, it returns false. + // - commit the memory. + // - mark the range as committed in the commit mask + // + // Returns true if success, false if it did hit a commit limit. + bool commit_range(MetaWord* p, size_t word_size); + + //// creation //// + + // Create a new empty node spanning the given reserved space. + VirtualSpaceNode(int node_id, + ReservedSpace rs, + CommitLimiter* limiter, + SizeCounter* reserve_counter, + SizeCounter* commit_counter); + + MetaWord* base() const { return _base; } + +public: + + // Create a node of a given size + static VirtualSpaceNode* create_node(int node_id, + size_t word_size, + CommitLimiter* limiter, + SizeCounter* reserve_words_counter, + SizeCounter* commit_words_counter); + + // Create a node over an existing space + static VirtualSpaceNode* create_node(int node_id, + ReservedSpace rs, + CommitLimiter* limiter, + SizeCounter* reserve_words_counter, + SizeCounter* commit_words_counter); + ~VirtualSpaceNode(); - // Convenience functions for logical bottom and end - MetaWord* bottom() const { return (MetaWord*) _virtual_space.low(); } - MetaWord* end() const { return (MetaWord*) _virtual_space.high(); } - const OccupancyMap* occupancy_map() const { return _occupancy_map; } - OccupancyMap* occupancy_map() { return _occupancy_map; } + // Reserved size of the whole node. + size_t word_size() const { return _word_size; } - bool contains(const void* ptr) { return ptr >= low() && ptr < high(); } + //// Chunk allocation, splitting, merging ///// - size_t reserved_words() const { return _virtual_space.reserved_size() / BytesPerWord; } - size_t committed_words() const { return _virtual_space.actual_committed_size() / BytesPerWord; } + // Allocate a root chunk from this node. Will fail and return NULL + // if the node is full. + // Note: this just returns a chunk whose memory is reserved; no memory is committed yet. + // Hence, before using this chunk, it must be committed. + // Also, no limits are checked, since no committing takes place. + Metachunk* allocate_root_chunk(); - bool is_pre_committed() const { return _virtual_space.special(); } + // Given a chunk c, split it recursively until you get a chunk of the given target_level. + // + // The original chunk must not be part of a freelist. + // + // Returns pointer to the result chunk; the splitted-off chunks are added as + // free chunks to the freelists. + // + // Returns NULL if chunk cannot be split at least once. + Metachunk* split(chklvl_t target_level, Metachunk* c, MetachunkListCluster* freelists); - // address of next available space in _virtual_space; - // Accessors - VirtualSpaceNode* next() { return _next; } - void set_next(VirtualSpaceNode* v) { _next = v; } + // Given a chunk, attempt to merge it recursively with its neighboring chunks. + // + // If successful (merged at least once), returns address of + // the merged chunk; NULL otherwise. + // + // The merged chunks are removed from the freelists. + // + // !!! Please note that if this method returns a non-NULL value, the + // original chunk will be invalid and should not be accessed anymore! !!! + Metachunk* merge(Metachunk* c, MetachunkListCluster* freelists); - void set_top(MetaWord* v) { _top = v; } + // Given a chunk c, which must be "in use" and must not be a root chunk, attempt to + // enlarge it in place by claiming its trailing buddy. + // + // This will only work if c is the leader of the buddy pair and the trailing buddy is free. + // + // If successful, the follower chunk will be removed from the freelists, the leader chunk c will + // double in size (level decreased by one). + // + // On success, true is returned, false otherwise. + bool attempt_enlarge_chunk(Metachunk* c, MetachunkListCluster* freelists); - // Accessors - VirtualSpace* virtual_space() const { return (VirtualSpace*) &_virtual_space; } + // Attempts to purge the node: + // + // If all chunks living in this node are free, they will all be removed from their freelists + // and deletes the node. + // + // Returns true if the node has been deleted, false if not. + // !! If this returns true, do not access the node from this point on. !! + bool attempt_purge(MetachunkListCluster* freelists); - // Returns true if "word_size" is available in the VirtualSpace - bool is_available(size_t word_size) { return word_size <= pointer_delta(end(), _top, sizeof(MetaWord)); } + // Attempts to uncommit free areas according to the rules set in settings. + // Returns number of words uncommitted. + size_t uncommit_free_areas(); - MetaWord* top() const { return _top; } - void inc_top(size_t word_size) { _top += word_size; } + /// misc ///// - uintx container_count() { return _container_count; } - void inc_container_count(); - void dec_container_count(); + // Returns size, in words, of the used space in this node alone. + // (Notes: + // - This is the space handed out to the ChunkManager, so it is "used" from the viewpoint of this node, + // but not necessarily used for Metadata. + // - This may or may not be committed memory. + size_t used_words() const { return _used_words; } - // used and capacity in this single entry in the list - size_t used_words_in_vs() const; - size_t capacity_words_in_vs() const; + // Returns size, in words, of how much space is left in this node alone. + size_t free_words() const { return _word_size - _used_words; } - bool initialize(); + // Returns size, in words, of committed space in this node alone. + // Note: iterates over commit mask and hence may be a tad expensive on large nodes. + size_t committed_words() const; - // get space from the virtual space - Metachunk* take_from_committed(size_t chunk_word_size); + //// Committing/uncommitting memory ///// - // Allocate a chunk from the virtual space and return it. - Metachunk* get_chunk_vs(size_t chunk_word_size); + // Given an address range, ensure it is committed. + // + // The range does not have to be aligned to granule size. However, the function will always commit + // whole granules. + // + // Function will: + // - check how many granules in that region are uncommitted; If all are committed, it + // returns true immediately. + // - check if committing those uncommitted granules would bring us over the commit limit + // (GC threshold, MaxMetaspaceSize). If true, it returns false. + // - commit the memory. + // - mark the range as committed in the commit mask + // + // Returns true if success, false if it did hit a commit limit. + bool ensure_range_is_committed(MetaWord* p, size_t word_size); - // Expands the committed space by at least min_words words. - bool expand_by(size_t min_words, size_t preferred_words); + // Given an address range (which has to be aligned to commit granule size): + // - uncommit it + // - mark it as uncommitted in the commit mask + void uncommit_range(MetaWord* p, size_t word_size); - // In preparation for deleting this node, remove all the chunks - // in the node from any freelist. - void purge(ChunkManager* chunk_manager); + //// List stuff //// + VirtualSpaceNode* next() const { return _next; } + void set_next(VirtualSpaceNode* vsn) { _next = vsn; } - // If an allocation doesn't fit in the current node a new node is created. - // Allocate chunks out of the remaining committed space in this node - // to avoid wasting that memory. - // This always adds up because all the chunk sizes are multiples of - // the smallest chunk size. - void retire(ChunkManager* chunk_manager); - void print_on(outputStream* st) const { print_on(st, K); } - void print_on(outputStream* st, size_t scale) const; - void print_map(outputStream* st, bool is_class) const; + /// Debug stuff //// - // Debug support - DEBUG_ONLY(void mangle();) - // Verify counters and basic structure. Slow mode: verify all chunks in depth and occupancy map. - DEBUG_ONLY(void verify(bool slow);) - // Verify that all free chunks in this node are ideally merged - // (there should not be multiple small chunks where a large chunk could exist.) - DEBUG_ONLY(void verify_free_chunks_are_ideally_merged();) + // Print a description about this node. + void print_on(outputStream* st) const; + + // Verify counters and basic structure. Slow mode: verify all chunks in depth + bool contains(const MetaWord* p) const { + return p >= _base && p < _base + _used_words; + } + +#ifdef ASSERT + void check_pointer(const MetaWord* p) const { + assert(contains(p), "invalid pointer"); + } + void verify(bool slow) const; +#endif }; + } // namespace metaspace #endif // SHARE_MEMORY_METASPACE_VIRTUALSPACENODE_HPP diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspaceChunkFreeListSummary.hpp --- a/src/hotspot/share/memory/metaspaceChunkFreeListSummary.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspaceChunkFreeListSummary.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -25,6 +25,7 @@ #ifndef SHARE_MEMORY_METASPACECHUNKFREELISTSUMMARY_HPP #define SHARE_MEMORY_METASPACECHUNKFREELISTSUMMARY_HPP +#include "utilities/globalDefinitions.hpp" class MetaspaceChunkFreeListSummary { size_t _num_specialized_chunks; diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspaceCounters.cpp --- a/src/hotspot/share/memory/metaspaceCounters.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspaceCounters.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -98,15 +98,15 @@ MetaspacePerfCounters* CompressedClassSpaceCounters::_perf_counters = NULL; size_t CompressedClassSpaceCounters::used() { - return MetaspaceUtils::used_bytes(Metaspace::ClassType); + return MetaspaceUtils::used_bytes(metaspace::ClassType); } size_t CompressedClassSpaceCounters::capacity() { - return MetaspaceUtils::committed_bytes(Metaspace::ClassType); + return MetaspaceUtils::committed_bytes(metaspace::ClassType); } size_t CompressedClassSpaceCounters::max_capacity() { - return MetaspaceUtils::reserved_bytes(Metaspace::ClassType); + return MetaspaceUtils::reserved_bytes(metaspace::ClassType); } void CompressedClassSpaceCounters::update_performance_counters() { diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspaceGCThresholdUpdater.hpp --- a/src/hotspot/share/memory/metaspaceGCThresholdUpdater.hpp Tue Sep 17 19:09:37 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2014, 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_MEMORY_METASPACEGCTHRESHOLDUPDATER_HPP -#define SHARE_MEMORY_METASPACEGCTHRESHOLDUPDATER_HPP - -#include "memory/allocation.hpp" -#include "utilities/debug.hpp" - -class MetaspaceGCThresholdUpdater : public AllStatic { - public: - enum Type { - ComputeNewSize, - ExpandAndAllocate, - Last - }; - - static const char* to_string(MetaspaceGCThresholdUpdater::Type updater) { - switch (updater) { - case ComputeNewSize: - return "compute_new_size"; - case ExpandAndAllocate: - return "expand_and_allocate"; - default: - assert(false, "Got bad updater: %d", (int) updater); - return NULL; - }; - } -}; - -#endif // SHARE_MEMORY_METASPACEGCTHRESHOLDUPDATER_HPP diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspaceTracer.cpp --- a/src/hotspot/share/memory/metaspaceTracer.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspaceTracer.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -43,14 +43,14 @@ void MetaspaceTracer::report_metaspace_allocation_failure(ClassLoaderData *cld, size_t word_size, MetaspaceObj::Type objtype, - Metaspace::MetadataType mdtype) const { + metaspace::MetadataType mdtype) const { send_allocation_failure_event(cld, word_size, objtype, mdtype); } void MetaspaceTracer::report_metadata_oom(ClassLoaderData *cld, size_t word_size, MetaspaceObj::Type objtype, - Metaspace::MetadataType mdtype) const { + metaspace::MetadataType mdtype) const { send_allocation_failure_event(cld, word_size, objtype, mdtype); } @@ -58,7 +58,7 @@ void MetaspaceTracer::send_allocation_failure_event(ClassLoaderData *cld, size_t word_size, MetaspaceObj::Type objtype, - Metaspace::MetadataType mdtype) const { + metaspace::MetadataType mdtype) const { E event; if (event.should_commit()) { event.set_classLoader(cld); diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/metaspaceTracer.hpp --- a/src/hotspot/share/memory/metaspaceTracer.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/metaspaceTracer.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -27,7 +27,7 @@ #include "memory/allocation.hpp" #include "memory/metaspace.hpp" -#include "memory/metaspaceGCThresholdUpdater.hpp" +#include "memory/metaspace/metaspaceEnums.hpp" class ClassLoaderData; @@ -36,7 +36,7 @@ void send_allocation_failure_event(ClassLoaderData *cld, size_t word_size, MetaspaceObj::Type objtype, - Metaspace::MetadataType mdtype) const; + metaspace::MetadataType mdtype) const; public: void report_gc_threshold(size_t old_val, size_t new_val, @@ -44,11 +44,11 @@ void report_metaspace_allocation_failure(ClassLoaderData *cld, size_t word_size, MetaspaceObj::Type objtype, - Metaspace::MetadataType mdtype) const; + metaspace::MetadataType mdtype) const; void report_metadata_oom(ClassLoaderData *cld, size_t word_size, MetaspaceObj::Type objtype, - Metaspace::MetadataType mdtype) const; + metaspace::MetadataType mdtype) const; }; diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/memory/universe.cpp --- a/src/hotspot/share/memory/universe.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/memory/universe.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -1114,7 +1114,7 @@ #endif if (should_verify_subset(Verify_MetaspaceUtils)) { log_debug(gc, verify)("MetaspaceUtils"); - MetaspaceUtils::verify_free_chunks(); + DEBUG_ONLY(MetaspaceUtils::verify(true);) } if (should_verify_subset(Verify_JNIHandles)) { log_debug(gc, verify)("JNIHandles"); diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/runtime/globals.hpp --- a/src/hotspot/share/runtime/globals.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/runtime/globals.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -1613,6 +1613,9 @@ "class pointers are used") \ range(1*M, 3*G) \ \ + product(ccstr, MetaspaceReclaimStrategy, "balanced", \ + "options: balanced, aggressive, none") \ + \ manageable(uintx, MinHeapFreeRatio, 40, \ "The minimum percentage of heap free after GC to avoid expansion."\ " For most GCs this applies to the old generation. In G1 and" \ diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/runtime/os.hpp --- a/src/hotspot/share/runtime/os.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/runtime/os.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -416,6 +416,8 @@ static void make_polling_page_readable(); // Check if pointer points to readable memory (by 4-byte read access) + // !! Unreliable before VM initialization! Use CanUseSafeFetch32() to test + // if this function is reliable !! static bool is_readable_pointer(const void* p); static bool is_readable_range(const void* from, const void* to); diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/runtime/vmOperations.cpp --- a/src/hotspot/share/runtime/vmOperations.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/runtime/vmOperations.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -33,6 +33,7 @@ #include "logging/logStream.hpp" #include "logging/logConfiguration.hpp" #include "memory/heapInspection.hpp" +#include "memory/metaspace/metaspaceReport.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "oops/symbol.hpp" @@ -221,7 +222,7 @@ } void VM_PrintMetadata::doit() { - MetaspaceUtils::print_report(_out, _scale, _flags); + metaspace::MetaspaceReporter::print_report(_out, _scale, _flags); } VM_FindDeadlocks::~VM_FindDeadlocks() { diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/services/memReporter.cpp --- a/src/hotspot/share/services/memReporter.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/services/memReporter.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -200,35 +200,35 @@ amount_in_current_scale(_malloc_snapshot->malloc_overhead()->size()), scale); } else if (flag == mtClass) { // Metadata information - report_metadata(Metaspace::NonClassType); + report_metadata(metaspace::NonClassType); if (Metaspace::using_class_space()) { - report_metadata(Metaspace::ClassType); + report_metadata(metaspace::ClassType); } } out->print_cr(" "); } } -void MemSummaryReporter::report_metadata(Metaspace::MetadataType type) const { - assert(type == Metaspace::NonClassType || type == Metaspace::ClassType, - "Invalid metadata type"); - const char* name = (type == Metaspace::NonClassType) ? - "Metadata: " : "Class space:"; +void MemSummaryReporter::report_metadata(metaspace::MetadataType mdType) const { + DEBUG_ONLY(metaspace::check_valid_mdtype(mdType)); + const char* const name = metaspace::describe_mdtype(mdType); outputStream* out = output(); const char* scale = current_scale(); - size_t committed = MetaspaceUtils::committed_bytes(type); - size_t used = MetaspaceUtils::used_bytes(type); - size_t free = (MetaspaceUtils::capacity_bytes(type) - used) - + MetaspaceUtils::free_chunks_total_bytes(type) - + MetaspaceUtils::free_in_vs_bytes(type); + size_t committed = MetaspaceUtils::committed_bytes(mdType); + size_t used = MetaspaceUtils::used_bytes(mdType); + size_t free = 0; // + // TODO think this thru. What is free in this context? + // (MetaspaceUtils::capacity_bytes(type) - used) + // + MetaspaceUtils::free_chunks_total_bytes(type) + // + MetaspaceUtils::free_in_vs_bytes(type); assert(committed >= used + free, "Sanity"); size_t waste = committed - (used + free); out->print_cr("%27s ( %s)", " ", name); out->print("%27s ( ", " "); - print_total(MetaspaceUtils::reserved_bytes(type), committed); + print_total(MetaspaceUtils::reserved_bytes(mdType), committed); out->print_cr(")"); out->print_cr("%27s ( used=" SIZE_FORMAT "%s)", " ", amount_in_current_scale(used), scale); out->print_cr("%27s ( free=" SIZE_FORMAT "%s)", " ", amount_in_current_scale(free), scale); @@ -593,43 +593,43 @@ void MemSummaryDiffReporter::print_metaspace_diff(const MetaspaceSnapshot* current_ms, const MetaspaceSnapshot* early_ms) const { - print_metaspace_diff(Metaspace::NonClassType, current_ms, early_ms); + print_metaspace_diff(metaspace::NonClassType, current_ms, early_ms); if (Metaspace::using_class_space()) { - print_metaspace_diff(Metaspace::ClassType, current_ms, early_ms); + print_metaspace_diff(metaspace::ClassType, current_ms, early_ms); } } -void MemSummaryDiffReporter::print_metaspace_diff(Metaspace::MetadataType type, +void MemSummaryDiffReporter::print_metaspace_diff(metaspace::MetadataType mdType, const MetaspaceSnapshot* current_ms, const MetaspaceSnapshot* early_ms) const { - const char* name = (type == Metaspace::NonClassType) ? - "Metadata: " : "Class space:"; + DEBUG_ONLY(metaspace::check_valid_mdtype(mdType)); + const char* const name = metaspace::describe_mdtype(mdType); outputStream* out = output(); const char* scale = current_scale(); out->print_cr("%27s ( %s)", " ", name); out->print("%27s ( ", " "); - print_virtual_memory_diff(current_ms->reserved_in_bytes(type), - current_ms->committed_in_bytes(type), - early_ms->reserved_in_bytes(type), - early_ms->committed_in_bytes(type)); + print_virtual_memory_diff(current_ms->reserved_in_bytes(mdType), + current_ms->committed_in_bytes(mdType), + early_ms->reserved_in_bytes(mdType), + early_ms->committed_in_bytes(mdType)); out->print_cr(")"); - long diff_used = diff_in_current_scale(current_ms->used_in_bytes(type), - early_ms->used_in_bytes(type)); - long diff_free = diff_in_current_scale(current_ms->free_in_bytes(type), - early_ms->free_in_bytes(type)); + long diff_used = diff_in_current_scale(current_ms->used_in_bytes(mdType), + early_ms->used_in_bytes(mdType)); + long diff_free = diff_in_current_scale(current_ms->free_in_bytes(mdType), + early_ms->free_in_bytes(mdType)); - size_t current_waste = current_ms->committed_in_bytes(type) - - (current_ms->used_in_bytes(type) + current_ms->free_in_bytes(type)); - size_t early_waste = early_ms->committed_in_bytes(type) - - (early_ms->used_in_bytes(type) + early_ms->free_in_bytes(type)); + size_t current_waste = current_ms->committed_in_bytes(mdType) + - (current_ms->used_in_bytes(mdType) + current_ms->free_in_bytes(mdType)); + size_t early_waste = early_ms->committed_in_bytes(mdType) + - (early_ms->used_in_bytes(mdType) + early_ms->free_in_bytes(mdType)); long diff_waste = diff_in_current_scale(current_waste, early_waste); // Diff used out->print("%27s ( used=" SIZE_FORMAT "%s", " ", - amount_in_current_scale(current_ms->used_in_bytes(type)), scale); + amount_in_current_scale(current_ms->used_in_bytes(mdType)), scale); if (diff_used != 0) { out->print(" %+ld%s", diff_used, scale); } @@ -637,7 +637,7 @@ // Diff free out->print("%27s ( free=" SIZE_FORMAT "%s", " ", - amount_in_current_scale(current_ms->free_in_bytes(type)), scale); + amount_in_current_scale(current_ms->free_in_bytes(mdType)), scale); if (diff_free != 0) { out->print(" %+ld%s", diff_free, scale); } @@ -647,7 +647,7 @@ // Diff waste out->print("%27s ( waste=" SIZE_FORMAT "%s =%2.2f%%", " ", amount_in_current_scale(current_waste), scale, - ((float)current_waste * 100) / current_ms->committed_in_bytes(type)); + ((float)current_waste * 100) / current_ms->committed_in_bytes(mdType)); if (diff_waste != 0) { out->print(" %+ld%s", diff_waste, scale); } diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/services/memReporter.hpp --- a/src/hotspot/share/services/memReporter.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/services/memReporter.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -27,7 +27,7 @@ #if INCLUDE_NMT -#include "memory/metaspace.hpp" +#include "memory/metaspace/metaspaceEnums.hpp" #include "oops/instanceKlass.hpp" #include "services/memBaseline.hpp" #include "services/nmtCommon.hpp" @@ -114,7 +114,7 @@ void report_summary_of_type(MEMFLAGS type, MallocMemory* malloc_memory, VirtualMemory* virtual_memory); - void report_metadata(Metaspace::MetadataType type) const; + void report_metadata(metaspace::MetadataType type) const; }; /* @@ -189,7 +189,7 @@ void print_metaspace_diff(const MetaspaceSnapshot* current_ms, const MetaspaceSnapshot* early_ms) const; - void print_metaspace_diff(Metaspace::MetadataType type, + void print_metaspace_diff(metaspace::MetadataType type, const MetaspaceSnapshot* current_ms, const MetaspaceSnapshot* early_ms) const; }; diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/services/memoryPool.cpp --- a/src/hotspot/share/services/memoryPool.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/services/memoryPool.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -25,6 +25,7 @@ #include "precompiled.hpp" #include "classfile/systemDictionary.hpp" #include "classfile/vmSymbols.hpp" +#include "memory/metaspace/metaspaceEnums.hpp" #include "memory/metaspace.hpp" #include "oops/oop.inline.hpp" #include "runtime/handles.inline.hpp" @@ -211,10 +212,10 @@ MemoryPool("Compressed Class Space", NonHeap, 0, CompressedClassSpaceSize, true, false) { } size_t CompressedKlassSpacePool::used_in_bytes() { - return MetaspaceUtils::used_bytes(Metaspace::ClassType); + return MetaspaceUtils::used_bytes(metaspace::ClassType); } MemoryUsage CompressedKlassSpacePool::get_memory_usage() { - size_t committed = MetaspaceUtils::committed_bytes(Metaspace::ClassType); + size_t committed = MetaspaceUtils::committed_bytes(metaspace::ClassType); return MemoryUsage(initial_size(), used_in_bytes(), committed, max_size()); } diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/services/virtualMemoryTracker.cpp --- a/src/hotspot/share/services/virtualMemoryTracker.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/services/virtualMemoryTracker.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -602,8 +602,8 @@ // Metaspace Support MetaspaceSnapshot::MetaspaceSnapshot() { - for (int index = (int)Metaspace::ClassType; index < (int)Metaspace::MetadataTypeCount; index ++) { - Metaspace::MetadataType type = (Metaspace::MetadataType)index; + for (int index = (int)metaspace::ClassType; index < (int)metaspace::MetadataTypeCount; index ++) { + metaspace::MetadataType type = (metaspace::MetadataType)index; assert_valid_metadata_type(type); _reserved_in_bytes[type] = 0; _committed_in_bytes[type] = 0; @@ -612,22 +612,22 @@ } } -void MetaspaceSnapshot::snapshot(Metaspace::MetadataType type, MetaspaceSnapshot& mss) { +void MetaspaceSnapshot::snapshot(metaspace::MetadataType type, MetaspaceSnapshot& mss) { assert_valid_metadata_type(type); mss._reserved_in_bytes[type] = MetaspaceUtils::reserved_bytes(type); mss._committed_in_bytes[type] = MetaspaceUtils::committed_bytes(type); mss._used_in_bytes[type] = MetaspaceUtils::used_bytes(type); - size_t free_in_bytes = (MetaspaceUtils::capacity_bytes(type) - MetaspaceUtils::used_bytes(type)) - + MetaspaceUtils::free_chunks_total_bytes(type) - + MetaspaceUtils::free_in_vs_bytes(type); + size_t free_in_bytes = 0;// TODO fix(MetaspaceUtils::capacity_bytes(type) - MetaspaceUtils::used_bytes(type)) + // + MetaspaceUtils::free_chunks_total_bytes(type) + // + MetaspaceUtils::free_in_vs_bytes(type); mss._free_in_bytes[type] = free_in_bytes; } void MetaspaceSnapshot::snapshot(MetaspaceSnapshot& mss) { - snapshot(Metaspace::ClassType, mss); + snapshot(metaspace::ClassType, mss); if (Metaspace::using_class_space()) { - snapshot(Metaspace::NonClassType, mss); + snapshot(metaspace::NonClassType, mss); } } diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/services/virtualMemoryTracker.hpp --- a/src/hotspot/share/services/virtualMemoryTracker.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/services/virtualMemoryTracker.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -28,7 +28,7 @@ #if INCLUDE_NMT #include "memory/allocation.hpp" -#include "memory/metaspace.hpp" +#include "memory/metaspace/metaspaceEnums.hpp" #include "services/allocationSite.hpp" #include "services/nmtCommon.hpp" #include "utilities/linkedlist.hpp" @@ -420,25 +420,25 @@ class MetaspaceSnapshot : public ResourceObj { private: - size_t _reserved_in_bytes[Metaspace::MetadataTypeCount]; - size_t _committed_in_bytes[Metaspace::MetadataTypeCount]; - size_t _used_in_bytes[Metaspace::MetadataTypeCount]; - size_t _free_in_bytes[Metaspace::MetadataTypeCount]; + size_t _reserved_in_bytes[metaspace::MetadataTypeCount]; + size_t _committed_in_bytes[metaspace::MetadataTypeCount]; + size_t _used_in_bytes[metaspace::MetadataTypeCount]; + size_t _free_in_bytes[metaspace::MetadataTypeCount]; public: MetaspaceSnapshot(); - size_t reserved_in_bytes(Metaspace::MetadataType type) const { assert_valid_metadata_type(type); return _reserved_in_bytes[type]; } - size_t committed_in_bytes(Metaspace::MetadataType type) const { assert_valid_metadata_type(type); return _committed_in_bytes[type]; } - size_t used_in_bytes(Metaspace::MetadataType type) const { assert_valid_metadata_type(type); return _used_in_bytes[type]; } - size_t free_in_bytes(Metaspace::MetadataType type) const { assert_valid_metadata_type(type); return _free_in_bytes[type]; } + size_t reserved_in_bytes(metaspace::MetadataType type) const { assert_valid_metadata_type(type); return _reserved_in_bytes[type]; } + size_t committed_in_bytes(metaspace::MetadataType type) const { assert_valid_metadata_type(type); return _committed_in_bytes[type]; } + size_t used_in_bytes(metaspace::MetadataType type) const { assert_valid_metadata_type(type); return _used_in_bytes[type]; } + size_t free_in_bytes(metaspace::MetadataType type) const { assert_valid_metadata_type(type); return _free_in_bytes[type]; } static void snapshot(MetaspaceSnapshot& s); private: - static void snapshot(Metaspace::MetadataType type, MetaspaceSnapshot& s); + static void snapshot(metaspace::MetadataType type, MetaspaceSnapshot& s); - static void assert_valid_metadata_type(Metaspace::MetadataType type) { - assert(type == Metaspace::ClassType || type == Metaspace::NonClassType, + static void assert_valid_metadata_type(metaspace::MetadataType type) { + assert(type == metaspace::ClassType || type == metaspace::NonClassType, "Invalid metadata type"); } }; diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/utilities/bitMap.cpp --- a/src/hotspot/share/utilities/bitMap.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/utilities/bitMap.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -609,7 +609,7 @@ // then modifications in and to the left of the _bit_ being // currently sampled will not be seen. Note also that the // interval [leftOffset, rightOffset) is right open. -bool BitMap::iterate(BitMapClosure* blk, idx_t leftOffset, idx_t rightOffset) { +bool BitMap::iterate(BitMapClosure* blk, idx_t leftOffset, idx_t rightOffset) const { verify_range(leftOffset, rightOffset); idx_t startIndex = word_index(leftOffset); diff -r cea6839598e8 -r 595fcbebaa77 src/hotspot/share/utilities/bitMap.hpp --- a/src/hotspot/share/utilities/bitMap.hpp Tue Sep 17 19:09:37 2019 -0400 +++ b/src/hotspot/share/utilities/bitMap.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -195,6 +195,7 @@ return calc_size_in_words(size_in_bits) * BytesPerWord; } + // Size, in number of bits, of this map. idx_t size() const { return _size; } idx_t size_in_words() const { return calc_size_in_words(size()); } idx_t size_in_bytes() const { return calc_size_in_bytes(size()); } @@ -253,11 +254,11 @@ void clear_large(); inline void clear(); - // Iteration support. Returns "true" if the iteration completed, false + // Iteration support [leftIndex, rightIndex). Returns "true" if the iteration completed, false // if the iteration terminated early (because the closure "blk" returned // false). - bool iterate(BitMapClosure* blk, idx_t leftIndex, idx_t rightIndex); - bool iterate(BitMapClosure* blk) { + bool iterate(BitMapClosure* blk, idx_t leftIndex, idx_t rightIndex) const; + bool iterate(BitMapClosure* blk) const { // call the version that takes an interval return iterate(blk, 0, size()); } @@ -279,6 +280,9 @@ // aligned to bitsizeof(bm_word_t). idx_t get_next_one_offset_aligned_right(idx_t l_index, idx_t r_index) const; + // Returns the number of bits set between [l_index, r_index) in the bitmap. + idx_t count_one_bits(idx_t l_index, idx_t r_index) const; + // Returns the number of bits set in the bitmap. idx_t count_one_bits() const; diff -r cea6839598e8 -r 595fcbebaa77 test/hotspot/gtest/memory/test_chunkManager.cpp --- a/test/hotspot/gtest/memory/test_chunkManager.cpp Tue Sep 17 19:09:37 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2016, 2018 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 "memory/metaspace/chunkManager.hpp" -#include "memory/metaspace/metaspaceCommon.hpp" - -// The test function is only available in debug builds -#ifdef ASSERT - -#include "unittest.hpp" - -using namespace metaspace; - -TEST(ChunkManager, list_index) { - - // Test previous bug where a query for a humongous class metachunk, - // incorrectly matched the non-class medium metachunk size. - { - ChunkManager manager(true); - - ASSERT_TRUE(MediumChunk > ClassMediumChunk) << "Precondition for test"; - - ChunkIndex index = manager.list_index(MediumChunk); - - ASSERT_TRUE(index == HumongousIndex) << - "Requested size is larger than ClassMediumChunk," - " so should return HumongousIndex. Got index: " << index; - } - - // Check the specified sizes as well. - { - ChunkManager manager(true); - ASSERT_TRUE(manager.list_index(ClassSpecializedChunk) == SpecializedIndex); - ASSERT_TRUE(manager.list_index(ClassSmallChunk) == SmallIndex); - ASSERT_TRUE(manager.list_index(ClassMediumChunk) == MediumIndex); - ASSERT_TRUE(manager.list_index(ClassMediumChunk + ClassSpecializedChunk) == HumongousIndex); - } - { - ChunkManager manager(false); - ASSERT_TRUE(manager.list_index(SpecializedChunk) == SpecializedIndex); - ASSERT_TRUE(manager.list_index(SmallChunk) == SmallIndex); - ASSERT_TRUE(manager.list_index(MediumChunk) == MediumIndex); - ASSERT_TRUE(manager.list_index(MediumChunk + SpecializedChunk) == HumongousIndex); - } - -} - -#endif // ASSERT diff -r cea6839598e8 -r 595fcbebaa77 test/hotspot/gtest/memory/test_is_metaspace_obj.cpp --- a/test/hotspot/gtest/memory/test_is_metaspace_obj.cpp Tue Sep 17 19:09:37 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2016, 2018 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 "memory/allocation.inline.hpp" -#include "memory/metaspace.hpp" -#include "memory/metaspace/virtualSpaceList.hpp" -#include "runtime/mutex.hpp" -#include "runtime/mutexLocker.hpp" -#include "runtime/os.hpp" -#include "unittest.hpp" - -using namespace metaspace; - - -// Test the cheerful multitude of metaspace-contains-functions. -class MetaspaceIsMetaspaceObjTest : public ::testing::Test { - Mutex* _lock; - ClassLoaderMetaspace* _ms; - -public: - - MetaspaceIsMetaspaceObjTest() : _lock(NULL), _ms(NULL) {} - - virtual void SetUp() { - } - - virtual void TearDown() { - delete _ms; - delete _lock; - } - - void do_test(Metaspace::MetadataType mdType) { - _lock = new Mutex(Monitor::native, "gtest-IsMetaspaceObjTest-lock", false, Monitor::_safepoint_check_never); - { - MutexLocker ml(_lock, Mutex::_no_safepoint_check_flag); - _ms = new ClassLoaderMetaspace(_lock, Metaspace::StandardMetaspaceType); - } - - const MetaspaceObj* p = (MetaspaceObj*) _ms->allocate(42, mdType); - - // Test MetaspaceObj::is_metaspace_object - ASSERT_TRUE(MetaspaceObj::is_valid(p)); - - // A misaligned object shall not be recognized - const MetaspaceObj* p_misaligned = (MetaspaceObj*)((address)p) + 1; - ASSERT_FALSE(MetaspaceObj::is_valid(p_misaligned)); - - // Test VirtualSpaceList::contains and find_enclosing_space - VirtualSpaceList* list = Metaspace::space_list(); - if (mdType == Metaspace::ClassType && Metaspace::using_class_space()) { - list = Metaspace::class_space_list(); - } - ASSERT_TRUE(list->contains(p)); - VirtualSpaceNode* const n = list->find_enclosing_space(p); - ASSERT_TRUE(n != NULL); - ASSERT_TRUE(n->contains(p)); - - // A misaligned pointer shall be recognized by list::contains - ASSERT_TRUE(list->contains((address)p) + 1); - - // Now for some bogus values - ASSERT_FALSE(MetaspaceObj::is_valid((MetaspaceObj*)NULL)); - - // Should exercise various paths in MetaspaceObj::is_valid() - ASSERT_FALSE(MetaspaceObj::is_valid((MetaspaceObj*)1024)); - ASSERT_FALSE(MetaspaceObj::is_valid((MetaspaceObj*)8192)); - - MetaspaceObj* p_stack = (MetaspaceObj*) &_lock; - ASSERT_FALSE(MetaspaceObj::is_valid(p_stack)); - - MetaspaceObj* p_heap = (MetaspaceObj*) os::malloc(41, mtInternal); - ASSERT_FALSE(MetaspaceObj::is_valid(p_heap)); - os::free(p_heap); - - // Test Metaspace::contains_xxx - ASSERT_TRUE(Metaspace::contains(p)); - ASSERT_TRUE(Metaspace::contains_non_shared(p)); - - delete _ms; - _ms = NULL; - delete _lock; - _lock = NULL; - } - -}; - -TEST_VM_F(MetaspaceIsMetaspaceObjTest, non_class_space) { - do_test(Metaspace::NonClassType); -} - -TEST_VM_F(MetaspaceIsMetaspaceObjTest, class_space) { - do_test(Metaspace::ClassType); -} - diff -r cea6839598e8 -r 595fcbebaa77 test/hotspot/gtest/memory/test_metachunk.cpp --- a/test/hotspot/gtest/memory/test_metachunk.cpp Tue Sep 17 19:09:37 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2016, 2018, 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 "memory/allocation.hpp" -#include "memory/metaspace/metachunk.hpp" -#include "unittest.hpp" -#include "utilities/align.hpp" -#include "utilities/copy.hpp" -#include "utilities/debug.hpp" - -using namespace metaspace; - -class MetachunkTest { - public: - static MetaWord* initial_top(Metachunk* metachunk) { - return metachunk->initial_top(); - } - static MetaWord* top(Metachunk* metachunk) { - return metachunk->top(); - } - -}; - -TEST(Metachunk, basic) { - const ChunkIndex chunk_type = MediumIndex; - const bool is_class = false; - const size_t word_size = get_size_for_nonhumongous_chunktype(chunk_type, is_class); - // Allocate the chunk with correct alignment. - void* memory = malloc(word_size * BytesPerWord * 2); - ASSERT_TRUE(NULL != memory) << "Failed to malloc 2MB"; - - void* p_placement = align_up(memory, word_size * BytesPerWord); - - Metachunk* metachunk = ::new (p_placement) Metachunk(chunk_type, is_class, word_size, NULL); - - EXPECT_EQ((MetaWord*) metachunk, metachunk->bottom()); - EXPECT_EQ((uintptr_t*) metachunk + metachunk->size(), metachunk->end()); - - // Check sizes - EXPECT_EQ(metachunk->size(), metachunk->word_size()); - EXPECT_EQ(pointer_delta(metachunk->end(), metachunk->bottom(), - sizeof (MetaWord)), - metachunk->word_size()); - - // Check usage - EXPECT_EQ(metachunk->used_word_size(), metachunk->overhead()); - EXPECT_EQ(metachunk->word_size() - metachunk->used_word_size(), - metachunk->free_word_size()); - EXPECT_EQ(MetachunkTest::top(metachunk), MetachunkTest::initial_top(metachunk)); - EXPECT_TRUE(metachunk->is_empty()); - - // Allocate - size_t alloc_size = 64; // Words - EXPECT_TRUE(is_aligned(alloc_size, Metachunk::object_alignment())); - - MetaWord* mem = metachunk->allocate(alloc_size); - - // Check post alloc - EXPECT_EQ(MetachunkTest::initial_top(metachunk), mem); - EXPECT_EQ(MetachunkTest::top(metachunk), mem + alloc_size); - EXPECT_EQ(metachunk->overhead() + alloc_size, metachunk->used_word_size()); - EXPECT_EQ(metachunk->word_size() - metachunk->used_word_size(), - metachunk->free_word_size()); - EXPECT_FALSE(metachunk->is_empty()); - - // Clear chunk - metachunk->reset_empty(); - - // Check post clear - EXPECT_EQ(metachunk->used_word_size(), metachunk->overhead()); - EXPECT_EQ(metachunk->word_size() - metachunk->used_word_size(), - metachunk->free_word_size()); - EXPECT_EQ(MetachunkTest::top(metachunk), MetachunkTest::initial_top(metachunk)); - EXPECT_TRUE(metachunk->is_empty()); - - free(memory); -} diff -r cea6839598e8 -r 595fcbebaa77 test/hotspot/gtest/memory/test_metaspace.cpp --- a/test/hotspot/gtest/memory/test_metaspace.cpp Tue Sep 17 19:09:37 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2018, 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 "memory/metaspace.hpp" -#include "memory/metaspace/virtualSpaceList.hpp" -#include "runtime/mutexLocker.hpp" -#include "runtime/os.hpp" -#include "unittest.hpp" - -using namespace metaspace; - -TEST_VM(MetaspaceUtils, reserved) { - size_t reserved = MetaspaceUtils::reserved_bytes(); - EXPECT_GT(reserved, 0UL); - - size_t reserved_metadata = MetaspaceUtils::reserved_bytes(Metaspace::NonClassType); - EXPECT_GT(reserved_metadata, 0UL); - EXPECT_LE(reserved_metadata, reserved); -} - -TEST_VM(MetaspaceUtils, reserved_compressed_class_pointers) { - if (!UseCompressedClassPointers) { - return; - } - size_t reserved = MetaspaceUtils::reserved_bytes(); - EXPECT_GT(reserved, 0UL); - - size_t reserved_class = MetaspaceUtils::reserved_bytes(Metaspace::ClassType); - EXPECT_GT(reserved_class, 0UL); - EXPECT_LE(reserved_class, reserved); -} - -TEST_VM(MetaspaceUtils, committed) { - size_t committed = MetaspaceUtils::committed_bytes(); - EXPECT_GT(committed, 0UL); - - size_t reserved = MetaspaceUtils::reserved_bytes(); - EXPECT_LE(committed, reserved); - - size_t committed_metadata = MetaspaceUtils::committed_bytes(Metaspace::NonClassType); - EXPECT_GT(committed_metadata, 0UL); - EXPECT_LE(committed_metadata, committed); -} - -TEST_VM(MetaspaceUtils, committed_compressed_class_pointers) { - if (!UseCompressedClassPointers) { - return; - } - size_t committed = MetaspaceUtils::committed_bytes(); - EXPECT_GT(committed, 0UL); - - size_t committed_class = MetaspaceUtils::committed_bytes(Metaspace::ClassType); - EXPECT_GT(committed_class, 0UL); - EXPECT_LE(committed_class, committed); -} - -TEST_VM(MetaspaceUtils, virtual_space_list_large_chunk) { - VirtualSpaceList* vs_list = new VirtualSpaceList(os::vm_allocation_granularity()); - MutexLocker cl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); - // A size larger than VirtualSpaceSize (256k) and add one page to make it _not_ be - // vm_allocation_granularity aligned on Windows. - size_t large_size = (size_t)(2*256*K + (os::vm_page_size() / BytesPerWord)); - large_size += (os::vm_page_size() / BytesPerWord); - vs_list->get_new_chunk(large_size, 0); -} diff -r cea6839598e8 -r 595fcbebaa77 test/hotspot/gtest/memory/test_metaspace_allocation.cpp --- a/test/hotspot/gtest/memory/test_metaspace_allocation.cpp Tue Sep 17 19:09:37 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,273 +0,0 @@ -/* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2018, SAP. - * 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 "memory/allocation.inline.hpp" -#include "memory/metaspace.hpp" -#include "runtime/mutex.hpp" -#include "runtime/mutexLocker.hpp" -#include "runtime/os.hpp" -#include "utilities/align.hpp" -#include "utilities/debug.hpp" -#include "utilities/globalDefinitions.hpp" -#include "utilities/ostream.hpp" -#include "unittest.hpp" - -#define NUM_PARALLEL_METASPACES 50 -#define MAX_PER_METASPACE_ALLOCATION_WORDSIZE (512 * K) - -//#define DEBUG_VERBOSE true - -#ifdef DEBUG_VERBOSE - -struct chunkmanager_statistics_t { - int num_specialized_chunks; - int num_small_chunks; - int num_medium_chunks; - int num_humongous_chunks; -}; - -extern void test_metaspace_retrieve_chunkmanager_statistics(Metaspace::MetadataType mdType, chunkmanager_statistics_t* out); - -static void print_chunkmanager_statistics(outputStream* st, Metaspace::MetadataType mdType) { - chunkmanager_statistics_t stat; - test_metaspace_retrieve_chunkmanager_statistics(mdType, &stat); - st->print_cr("free chunks: %d / %d / %d / %d", stat.num_specialized_chunks, stat.num_small_chunks, - stat.num_medium_chunks, stat.num_humongous_chunks); -} - -#endif - -struct chunk_geometry_t { - size_t specialized_chunk_word_size; - size_t small_chunk_word_size; - size_t medium_chunk_word_size; -}; - -extern void test_metaspace_retrieve_chunk_geometry(Metaspace::MetadataType mdType, chunk_geometry_t* out); - - -class MetaspaceAllocationTest : public ::testing::Test { -protected: - - struct { - size_t allocated; - Mutex* lock; - ClassLoaderMetaspace* space; - bool is_empty() const { return allocated == 0; } - bool is_full() const { return allocated >= MAX_PER_METASPACE_ALLOCATION_WORDSIZE; } - } _spaces[NUM_PARALLEL_METASPACES]; - - chunk_geometry_t _chunk_geometry; - - virtual void SetUp() { - ::memset(_spaces, 0, sizeof(_spaces)); - test_metaspace_retrieve_chunk_geometry(Metaspace::NonClassType, &_chunk_geometry); - } - - virtual void TearDown() { - for (int i = 0; i < NUM_PARALLEL_METASPACES; i ++) { - if (_spaces[i].space != NULL) { - delete _spaces[i].space; - delete _spaces[i].lock; - } - } - } - - void create_space(int i) { - assert(i >= 0 && i < NUM_PARALLEL_METASPACES, "Sanity"); - assert(_spaces[i].space == NULL && _spaces[i].allocated == 0, "Sanity"); - if (_spaces[i].lock == NULL) { - _spaces[i].lock = new Mutex(Monitor::native, "gtest-MetaspaceAllocationTest-lock", false, Monitor::_safepoint_check_never); - ASSERT_TRUE(_spaces[i].lock != NULL); - } - // Let every ~10th space be an unsafe anonymous one to test different allocation patterns. - const Metaspace::MetaspaceType msType = (os::random() % 100 < 10) ? - Metaspace::UnsafeAnonymousMetaspaceType : Metaspace::StandardMetaspaceType; - { - // Pull lock during space creation, since this is what happens in the VM too - // (see ClassLoaderData::metaspace_non_null(), which we mimick here). - MutexLocker ml(_spaces[i].lock, Mutex::_no_safepoint_check_flag); - _spaces[i].space = new ClassLoaderMetaspace(_spaces[i].lock, msType); - } - _spaces[i].allocated = 0; - ASSERT_TRUE(_spaces[i].space != NULL); - } - - // Returns the index of a random space where index is [0..metaspaces) and which is - // empty, non-empty or full. - // Returns -1 if no matching space exists. - enum fillgrade { fg_empty, fg_non_empty, fg_full }; - int get_random_matching_space(int metaspaces, fillgrade fg) { - const int start_index = os::random() % metaspaces; - int i = start_index; - do { - if (fg == fg_empty && _spaces[i].is_empty()) { - return i; - } else if ((fg == fg_full && _spaces[i].is_full()) || - (fg == fg_non_empty && !_spaces[i].is_full() && !_spaces[i].is_empty())) { - return i; - } - i ++; - if (i == metaspaces) { - i = 0; - } - } while (i != start_index); - return -1; - } - - int get_random_emtpy_space(int metaspaces) { return get_random_matching_space(metaspaces, fg_empty); } - int get_random_non_emtpy_space(int metaspaces) { return get_random_matching_space(metaspaces, fg_non_empty); } - int get_random_full_space(int metaspaces) { return get_random_matching_space(metaspaces, fg_full); } - - void do_test(Metaspace::MetadataType mdType, int metaspaces, int phases, int allocs_per_phase, - float probability_for_large_allocations // 0.0-1.0 - ) { - // Alternate between breathing in (allocating n blocks for a random Metaspace) and - // breathing out (deleting a random Metaspace). The intent is to stress the coalescation - // and splitting of free chunks. - int phases_done = 0; - bool allocating = true; - while (phases_done < phases) { - bool force_switch = false; - if (allocating) { - // Allocate space from metaspace, with a preference for completely empty spaces. This - // should provide a good mixture of metaspaces in the virtual space. - int index = get_random_emtpy_space(metaspaces); - if (index == -1) { - index = get_random_non_emtpy_space(metaspaces); - } - if (index == -1) { - // All spaces are full, switch to freeing. - force_switch = true; - } else { - // create space if it does not yet exist. - if (_spaces[index].space == NULL) { - create_space(index); - } - // Allocate a bunch of blocks from it. Mostly small stuff but mix in large allocations - // to force humongous chunk allocations. - int allocs_done = 0; - while (allocs_done < allocs_per_phase && !_spaces[index].is_full()) { - size_t size = 0; - int r = os::random() % 1000; - if ((float)r < probability_for_large_allocations * 1000.0) { - size = (os::random() % _chunk_geometry.medium_chunk_word_size) + _chunk_geometry.medium_chunk_word_size; - } else { - size = os::random() % 64; - } - // Note: In contrast to space creation, no need to lock here. ClassLoaderMetaspace::allocate() will lock itself. - MetaWord* const p = _spaces[index].space->allocate(size, mdType); - if (p == NULL) { - // We very probably did hit the metaspace "until-gc" limit. -#ifdef DEBUG_VERBOSE - tty->print_cr("OOM for " SIZE_FORMAT " words. ", size); -#endif - // Just switch to deallocation and resume tests. - force_switch = true; - break; - } else { - _spaces[index].allocated += size; - allocs_done ++; - } - } - } - } else { - // freeing: find a metaspace and delete it, with preference for completely filled spaces. - int index = get_random_full_space(metaspaces); - if (index == -1) { - index = get_random_non_emtpy_space(metaspaces); - } - if (index == -1) { - force_switch = true; - } else { - assert(_spaces[index].space != NULL && _spaces[index].allocated > 0, "Sanity"); - // Note: do not lock here. In the "wild" (the VM), we do not so either (see ~ClassLoaderData()). - delete _spaces[index].space; - _spaces[index].space = NULL; - _spaces[index].allocated = 0; - } - } - - if (force_switch) { - allocating = !allocating; - } else { - // periodically switch between allocating and freeing, but prefer allocation because - // we want to intermingle allocations of multiple metaspaces. - allocating = os::random() % 5 < 4; - } - phases_done ++; -#ifdef DEBUG_VERBOSE - int metaspaces_in_use = 0; - size_t total_allocated = 0; - for (int i = 0; i < metaspaces; i ++) { - if (_spaces[i].allocated > 0) { - total_allocated += _spaces[i].allocated; - metaspaces_in_use ++; - } - } - tty->print("%u:\tspaces: %d total words: " SIZE_FORMAT "\t\t\t", phases_done, metaspaces_in_use, total_allocated); - print_chunkmanager_statistics(tty, mdType); -#endif - } -#ifdef DEBUG_VERBOSE - tty->print_cr("Test finished. "); - MetaspaceUtils::print_metaspace_map(tty, mdType); - print_chunkmanager_statistics(tty, mdType); -#endif - } -}; - - - -TEST_F(MetaspaceAllocationTest, chunk_geometry) { - ASSERT_GT(_chunk_geometry.specialized_chunk_word_size, (size_t) 0); - ASSERT_GT(_chunk_geometry.small_chunk_word_size, _chunk_geometry.specialized_chunk_word_size); - ASSERT_EQ(_chunk_geometry.small_chunk_word_size % _chunk_geometry.specialized_chunk_word_size, (size_t)0); - ASSERT_GT(_chunk_geometry.medium_chunk_word_size, _chunk_geometry.small_chunk_word_size); - ASSERT_EQ(_chunk_geometry.medium_chunk_word_size % _chunk_geometry.small_chunk_word_size, (size_t)0); -} - - -TEST_VM_F(MetaspaceAllocationTest, single_space_nonclass) { - do_test(Metaspace::NonClassType, 1, 1000, 100, 0); -} - -TEST_VM_F(MetaspaceAllocationTest, single_space_class) { - do_test(Metaspace::ClassType, 1, 1000, 100, 0); -} - -TEST_VM_F(MetaspaceAllocationTest, multi_space_nonclass) { - do_test(Metaspace::NonClassType, NUM_PARALLEL_METASPACES, 100, 1000, 0.0); -} - -TEST_VM_F(MetaspaceAllocationTest, multi_space_class) { - do_test(Metaspace::ClassType, NUM_PARALLEL_METASPACES, 100, 1000, 0.0); -} - -TEST_VM_F(MetaspaceAllocationTest, multi_space_nonclass_2) { - // many metaspaces, with humongous chunks mixed in. - do_test(Metaspace::NonClassType, NUM_PARALLEL_METASPACES, 100, 1000, .006f); -} - diff -r cea6839598e8 -r 595fcbebaa77 test/hotspot/gtest/memory/test_spaceManager.cpp --- a/test/hotspot/gtest/memory/test_spaceManager.cpp Tue Sep 17 19:09:37 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2016, 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 "memory/metaspace/spaceManager.hpp" - -using metaspace::SpaceManager; - -// The test function is only available in debug builds -#ifdef ASSERT - -#include "unittest.hpp" - - -static void test_adjust_initial_chunk_size(bool is_class) { - const size_t smallest = SpaceManager::smallest_chunk_size(is_class); - const size_t normal = SpaceManager::small_chunk_size(is_class); - const size_t medium = SpaceManager::medium_chunk_size(is_class); - -#define do_test(value, expected, is_class_value) \ - do { \ - size_t v = value; \ - size_t e = expected; \ - assert(SpaceManager::adjust_initial_chunk_size(v, (is_class_value)) == e, \ - "Expected: " SIZE_FORMAT " got: " SIZE_FORMAT, e, v); \ - } while (0) - - // Smallest (specialized) - do_test(1, smallest, is_class); - do_test(smallest - 1, smallest, is_class); - do_test(smallest, smallest, is_class); - - // Small - do_test(smallest + 1, normal, is_class); - do_test(normal - 1, normal, is_class); - do_test(normal, normal, is_class); - - // Medium - do_test(normal + 1, medium, is_class); - do_test(medium - 1, medium, is_class); - do_test(medium, medium, is_class); - - // Humongous - do_test(medium + 1, medium + 1, is_class); - -#undef test_adjust_initial_chunk_size -} - -TEST(SpaceManager, adjust_initial_chunk_size) { - test_adjust_initial_chunk_size(true); - test_adjust_initial_chunk_size(false); -} - -#endif // ASSERT diff -r cea6839598e8 -r 595fcbebaa77 test/hotspot/gtest/memory/test_virtualspace.cpp --- a/test/hotspot/gtest/memory/test_virtualspace.cpp Tue Sep 17 19:09:37 2019 -0400 +++ b/test/hotspot/gtest/memory/test_virtualspace.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -28,6 +28,7 @@ #include "utilities/align.hpp" #include "unittest.hpp" + namespace { class MemoryReleaser { ReservedSpace* const _rs; @@ -337,3 +338,6 @@ EXPECT_NO_FATAL_FAILURE(test_virtual_space_actual_committed_space(10 * M, 5 * M, Commit)); EXPECT_NO_FATAL_FAILURE(test_virtual_space_actual_committed_space(10 * M, 10 * M, Commit)); } + + + diff -r cea6839598e8 -r 595fcbebaa77 test/hotspot/gtest/metaspace/metaspaceTestsCommon.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/gtest/metaspace/metaspaceTestsCommon.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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 "metaspaceTestsCommon.hpp" + +#include "utilities/globalDefinitions.hpp" + +#ifdef _WIN32 +#include +#endif + +void calc_random_range(size_t outer_range_len, range_t* out, size_t alignment) { + + assert(is_aligned(outer_range_len, alignment), "bad input range"); + assert(outer_range_len > 0, "no zero range"); + + size_t l1 = os::random() % outer_range_len; + size_t l2 = os::random() % outer_range_len; + if (l1 > l2) { + size_t l = l1; + l1 = l2; + l2 = l; + } + l1 = align_down(l1, alignment); + l2 = align_up(l2, alignment); + + // disallow zero range + if (l2 == l1) { + if (l1 >= alignment) { + l1 -= alignment; + } else { + assert(l2 <= outer_range_len - alignment, "Sanity"); + l2 += alignment; + } + } + + assert(l2 - l1 > 0 && l2 - l1 <= outer_range_len, "Sanity " SIZE_FORMAT "-" SIZE_FORMAT ".", l1, l2); + assert(is_aligned(l1, alignment), "Sanity"); + assert(is_aligned(l2, alignment), "Sanity"); + + out->from = l1; out->to = l2; + +} + +void calc_random_address_range(const address_range_t* outer_range, address_range_t* out, size_t alignment) { + range_t r; + calc_random_range(outer_range->word_size, &r, alignment); + + out->p = outer_range->p + r.from; + out->word_size = r.from - r.to; +} + +size_t get_workingset_size() { +#if defined(_WIN32) + PROCESS_MEMORY_COUNTERS info; + GetProcessMemoryInfo(GetCurrentProcess(), &info, sizeof(info)); + return (size_t)info.WorkingSetSize; +#elif defined(LINUX) + long result = 0L; + FILE* f = fopen("/proc/self/statm", "r"); + if (f == NULL) { + return 0; + } + // Second number in statm, in num of pages + if (fscanf(f, "%*s%ld", &result) != 1 ) { + fclose(f); + return 0; + } + fclose(f); + return (size_t)result * (size_t)os::vm_page_size(); +#else + return 0L; +#endif +} + + + +void zap_range(MetaWord* p, size_t word_size) { + for (MetaWord* pzap = p; pzap < p + word_size; pzap += os::vm_page_size()) { + *pzap = 0; + } +} + + + +// Writes a uniqe pattern to p +void mark_address(MetaWord* p, uintx pattern) { + MetaWord x = (MetaWord)((uintx) p ^ pattern); + *p = x; +} + +// checks pattern at address +bool check_marked_address(const MetaWord* p, uintx pattern) { + MetaWord x = (MetaWord)((uintx) p ^ pattern); + EXPECT_EQ(*p, x); + return *p == x; +} + +// "fill_range_with_pattern" fills a range of heap words with pointers to itself. +// +// The idea is to fill a memory range with a pattern which is both marked clearly to the caller +// and cannot be moved without becoming invalid. +// +// The filled range can be checked with check_range_for_pattern. One also can only check +// a sub range of the original range. +void fill_range_with_pattern(MetaWord* p, uintx pattern, size_t word_size) { + assert(word_size > 0 && p != NULL, "sanity"); + for (MetaWord* p2 = p; p2 < p + word_size; p2 ++) { + mark_address(p2, pattern); + } +} + +bool check_range_for_pattern(const MetaWord* p, uintx pattern, size_t word_size) { + assert(word_size > 0 && p != NULL, "sanity"); + const MetaWord* p2 = p; + while (p2 < p + word_size && check_marked_address(p2, pattern)) { + p2 ++; + } + return p2 < p + word_size; +} + + +// Similar to fill_range_with_pattern, but only marks start and end. This is optimized for cases +// where fill_range_with_pattern just is too slow. +// Use check_marked_range to check the range. In contrast to check_range_for_pattern, only the original +// range can be checked. +void mark_range(MetaWord* p, uintx pattern, size_t word_size) { + assert(word_size > 0 && p != NULL, "sanity"); + mark_address(p, pattern); + mark_address(p + word_size - 1, pattern); +} + +bool check_marked_range(const MetaWord* p, uintx pattern, size_t word_size) { + assert(word_size > 0 && p != NULL, "sanity"); + return check_marked_address(p, pattern) && check_marked_address(p + word_size - 1, pattern); +} + diff -r cea6839598e8 -r 595fcbebaa77 test/hotspot/gtest/metaspace/metaspaceTestsCommon.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/gtest/metaspace/metaspaceTestsCommon.hpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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 GTEST_METASPACE_METASPACETESTCOMMON_HPP + +#include "memory/allocation.hpp" + +#include "memory/metaspace/chunkHeaderPool.hpp" +#include "memory/metaspace/chunkLevel.hpp" +#include "memory/metaspace/chunkManager.hpp" +#include "memory/metaspace/counter.hpp" +#include "memory/metaspace/commitLimiter.hpp" +#include "memory/metaspace/metachunk.hpp" +#include "memory/metaspace/metaspaceCommon.hpp" +#include "memory/metaspace/metaspaceStatistics.hpp" +#include "memory/metaspace/virtualSpaceList.hpp" +#include "memory/metaspace/spaceManager.hpp" +#include "runtime/mutexLocker.hpp" +#include "runtime/os.hpp" + +#include "utilities/align.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" + +#include "unittest.hpp" + +#include + + +////////////////////////////////////////////////////////// +// handy aliases + +using metaspace::ChunkAllocSequence; +using metaspace::ChunkHeaderPool; +using metaspace::ChunkManager; +using metaspace::CommitLimiter; +using metaspace::CommitMask; +using metaspace::SizeCounter; +using metaspace::SizeAtomicCounter; +using metaspace::IntCounter; +using metaspace::Metachunk; +using metaspace::MetachunkList; +using metaspace::MetachunkListCluster; +using metaspace::Settings; +using metaspace::sm_stats_t; +using metaspace::in_use_chunk_stats_t; +using metaspace::cm_stats_t; +using metaspace::SizeCounter; +using metaspace::SpaceManager; +using metaspace::VirtualSpaceList; +using metaspace::VirtualSpaceNode; + +using metaspace::chklvl_t; +using metaspace::chklvl::HIGHEST_CHUNK_LEVEL; +using metaspace::chklvl::MAX_CHUNK_WORD_SIZE; +using metaspace::chklvl::MAX_CHUNK_BYTE_SIZE; +using metaspace::chklvl::LOWEST_CHUNK_LEVEL; +using metaspace::chklvl::NUM_CHUNK_LEVELS; + + +///////////////////////////////////////////////////////////////////// +// A little mockup to mimick and test the CommitMask in various tests + +class TestMap { + const size_t _len; + char* _arr; +public: + TestMap(size_t len) : _len(len), _arr(NULL) { + _arr = NEW_C_HEAP_ARRAY(char, len, mtInternal); + memset(_arr, 0, _len); + } + ~TestMap() { FREE_C_HEAP_ARRAY(char, _arr); } + + int get_num_set(size_t from, size_t to) const { + int result = 0; + for(size_t i = from; i < to; i ++) { + if (_arr[i] > 0) { + result ++; + } + } + return result; + } + + size_t get_num_set() const { return get_num_set(0, _len); } + + void set_range(size_t from, size_t to) { + memset(_arr + from, 1, to - from); + } + + void clear_range(size_t from, size_t to) { + memset(_arr + from, 0, to - from); + } + +}; + + + +/////////////////////////////////////////////////////////////// +// Functions to calculate random ranges in outer ranges + +// Note: [ from..to ) +struct range_t { + size_t from; size_t to; +}; + +void calc_random_range(size_t outer_range_len, range_t* out, size_t alignment); + +// Note: [ p..p+word_size ) +struct address_range_t { + MetaWord* p; size_t word_size; +}; + +void calc_random_address_range(const address_range_t* outer_range, address_range_t* out, size_t alignment); + + +/////////////////////////////////////////////////////////// +// Helper class for generating random allocation sizes +class RandSizeGenerator { + const size_t _min; // [ + const size_t _max; // ) + const float _outlier_chance; // 0.0 -- 1.0 + const size_t _outlier_min; // [ + const size_t _outlier_max; // ) +public: + RandSizeGenerator(size_t min, size_t max) + : _min(min), _max(max), _outlier_chance(0.0), _outlier_min(min), _outlier_max(max) + {} + + RandSizeGenerator(size_t min, size_t max, float outlier_chance, size_t outlier_min, size_t outlier_max) + : _min(min), _max(max), _outlier_chance(outlier_chance), _outlier_min(outlier_min), _outlier_max(outlier_max) + {} + + size_t get() const { + size_t l1 = _min; + size_t l2 = _max; + int r = os::random() % 1000; + if ((float)r < _outlier_chance * 1000.0) { + l1 = _outlier_min; + l2 = _outlier_max; + } + const size_t d = l2 - l1; + return l1 + (os::random() % d); + } + +}; // end RandSizeGenerator + + +/////////////////////////////////////////////////////////// +// Function to test-access a memory range + +void zap_range(MetaWord* p, size_t word_size); + +// "fill_range_with_pattern" fills a range of heap words with pointers to itself. +// +// The idea is to fill a memory range with a pattern which is both marked clearly to the caller +// and cannot be moved without becoming invalid. +// +// The filled range can be checked with check_range_for_pattern. One also can only check +// a sub range of the original range. +void fill_range_with_pattern(MetaWord* p, uintx pattern, size_t word_size); +bool check_range_for_pattern(const MetaWord* p, uintx pattern, size_t word_size); + +// Writes a uniqe pattern to p +void mark_address(MetaWord* p, uintx pattern); +// checks pattern at address +bool check_marked_address(const MetaWord* p, uintx pattern); + +// Similar to fill_range_with_pattern, but only marks start and end. This is optimized for cases +// where fill_range_with_pattern just is too slow. +// Use check_marked_range to check the range. In contrast to check_range_for_pattern, only the original +// range can be checked. +void mark_range(MetaWord* p, uintx pattern, size_t word_size); +bool check_marked_range(const MetaWord* p, uintx pattern, size_t word_size); + +////////////////////////////////////////////////////////// +// Some helpers to avoid typing out those annoying casts for NULL + +#define ASSERT_NOT_NULL(ptr) ASSERT_NE((void*)NULL, (void*)ptr) +#define ASSERT_NULL(ptr) ASSERT_EQ((void*)NULL, (void*)ptr) +#define EXPECT_NOT_NULL(ptr) EXPECT_NE((void*)NULL, (void*)ptr) +#define EXPECT_NULL(ptr) EXPECT_EQ((void*)NULL, (void*)ptr) + + +////////////////////////////////////////////////////////// +// logging + +// Define "LOG_PLEASE" to switch on logging for a particular test before inclusion of this header. +#ifdef LOG_PLEASE + #define LOG(...) { printf(__VA_ARGS__); printf("\n"); } +#else + #define LOG(...) +#endif + +////////////////////////////////////////////////////////// +// Helper + +size_t get_workingset_size(); + + +#endif // GTEST_METASPACE_METASPACETESTCOMMON_HPP diff -r cea6839598e8 -r 595fcbebaa77 test/hotspot/gtest/metaspace/test_chunkManager.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/gtest/metaspace/test_chunkManager.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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 "metaspace/metaspaceTestsCommon.hpp" + +class ChunkManagerTest { + + static const int max_num_chunks = 0x100; + + // These counters are updated by the Node. + CommitLimiter _commit_limiter; + VirtualSpaceList* _vs_list; + + ChunkManager* _cm; + + Metachunk* _elems[max_num_chunks]; + SizeCounter _word_size_allocated; + IntCounter _num_allocated; + + // Note: [min_level ... max_level] (inclusive max) + static chklvl_t get_random_level(chklvl_t min_level, chklvl_t max_level) { + int range = max_level - min_level + 1; // [ ] + chklvl_t l = min_level + (os::random() % range); + return l; + } + + struct chklvl_range_t { chklvl_t from; chklvl_t to; }; + + // Note: [min_level ... max_level] (inclusive max) + static void get_random_level_range(chklvl_t min_level, chklvl_t max_level, chklvl_range_t* out) { + chklvl_t l1 = get_random_level(min_level, max_level); + chklvl_t l2 = get_random_level(min_level, max_level); + if (l1 > l2) { + chklvl_t l = l2; + l1 = l2; + l2 = l; + } + out->from = l1; out->to = l2; + } + + bool attempt_free_at(size_t index) { + + LOG("attempt_free_at " SIZE_FORMAT ".", index); + + if (_elems[index] == NULL) { + return false; + } + + Metachunk* c = _elems[index]; + const size_t chunk_word_size = c->word_size(); + _cm->return_chunk(c); + + _elems[index] = NULL; + + DEBUG_ONLY(_vs_list->verify(true);) + DEBUG_ONLY(_cm->verify(true);) + + _word_size_allocated.decrement_by(chunk_word_size); + _num_allocated.decrement(); + + return true; + } + + bool attempt_allocate_at(size_t index, chklvl_t max_level, chklvl_t pref_level, bool fully_commit = false) { + + LOG("attempt_allocate_at " SIZE_FORMAT ". (" CHKLVL_FORMAT "-" CHKLVL_FORMAT ")", index, max_level, pref_level); + + if (_elems[index] != NULL) { + return false; + } + + Metachunk* c = _cm->get_chunk(max_level, pref_level); + + EXPECT_NOT_NULL(c); + EXPECT_TRUE(c->is_in_use()); + EXPECT_LE(c->level(), max_level); + EXPECT_GE(c->level(), pref_level); + + _elems[index] = c; + + DEBUG_ONLY(c->verify(true);) + DEBUG_ONLY(_vs_list->verify(true);) + DEBUG_ONLY(_cm->verify(true);) + + _word_size_allocated.increment_by(c->word_size()); + _num_allocated.increment(); + + if (fully_commit) { + c->ensure_fully_committed(); + } + + return true; + } + + // Note: [min_level ... max_level] (inclusive max) + void allocate_n_random_chunks(int n, chklvl_t min_level, chklvl_t max_level) { + + assert(n <= max_num_chunks, "Sanity"); + + for (int i = 0; i < n; i ++) { + chklvl_range_t r; + get_random_level_range(min_level, max_level, &r); + attempt_allocate_at(i, r.to, r.from); + } + + } + + void free_all_chunks() { + for (int i = 0; i < max_num_chunks; i ++) { + attempt_free_at(i); + } + assert(_num_allocated.get() == 0, "Sanity"); + assert(_word_size_allocated.get() == 0, "Sanity"); + } + + void random_alloc_free(int iterations, chklvl_t min_level, chklvl_t max_level) { + for (int i = 0; i < iterations; i ++) { + int index = os::random() % max_num_chunks; + if ((os::random() % 100) > 50) { + attempt_allocate_at(index, max_level, min_level); + } else { + attempt_free_at(index); + } + } + } + +public: + + ChunkManagerTest() + : _commit_limiter(50 * M), _vs_list(NULL), _cm(NULL), _word_size_allocated() + { + _vs_list = new VirtualSpaceList("test_vs_lust", &_commit_limiter); + _cm = new ChunkManager("test_cm", _vs_list); + memset(_elems, 0, sizeof(_elems)); + } + + void test(int iterations, chklvl_t min_level, chklvl_t max_level) { + for (int run = 0; run < iterations; run ++) { + allocate_n_random_chunks(max_num_chunks, min_level, max_level); + random_alloc_free(iterations, min_level, max_level); + free_all_chunks(); + } + } + + void test_enlarge_chunk() { + // On an empty state, request a chunk of the smallest possible size from chunk manager; then, + // attempt to enlarge it in place. Since all splinters should be free, this should work until + // we are back at root chunk size. + assert(_cm->total_num_chunks() == 0, "Call this on an empty cm."); + Metachunk* c = _cm->get_chunk(HIGHEST_CHUNK_LEVEL, HIGHEST_CHUNK_LEVEL); + ASSERT_NOT_NULL(c); + ASSERT_EQ(c->level(), HIGHEST_CHUNK_LEVEL); + + int num_splinter_chunks = _cm->total_num_chunks(); + + // Getting a chunk of the smallest size there is should have yielded us one splinter for every level beyond 0. + ASSERT_EQ(num_splinter_chunks, NUM_CHUNK_LEVELS - 1); + + // Now enlarge n-1 times until c is of root chunk level size again. + for (chklvl_t l = HIGHEST_CHUNK_LEVEL; l > LOWEST_CHUNK_LEVEL; l --) { + bool rc = _cm->attempt_enlarge_chunk(c); + ASSERT_TRUE(rc); + ASSERT_EQ(c->level(), l - 1); + num_splinter_chunks --; + ASSERT_EQ(num_splinter_chunks, _cm->total_num_chunks()); + } + } + + void test_recommit_chunk() { + + // test that if a chunk is committed again, already committed content stays. + assert(_cm->total_num_chunks() == 0, "Call this on an empty cm."); + const chklvl_t lvl = metaspace::chklvl::level_fitting_word_size(Settings::commit_granule_words()); + Metachunk* c = _cm->get_chunk(lvl, lvl); + ASSERT_NOT_NULL(c); + ASSERT_EQ(c->level(), lvl); + + // clean slate. + c->set_free(); + c->uncommit(); + c->set_in_use(); + + c->ensure_committed(10); + + const size_t committed_words_1 = c->committed_words(); + fill_range_with_pattern(c->base(), (uintx) this, committed_words_1); + + // enlarge chunk, then recommit again, which will make sure we + bool rc = _cm->attempt_enlarge_chunk(c); + ASSERT_TRUE(rc); + rc = _cm->attempt_enlarge_chunk(c); + ASSERT_TRUE(rc); + rc = _cm->attempt_enlarge_chunk(c); + ASSERT_TRUE(rc); + ASSERT_EQ(c->level(), lvl - 3); + + c->ensure_committed(c->word_size()); + check_range_for_pattern(c->base(), (uintx) this, committed_words_1); + + } + + void test_wholesale_reclaim() { + + // test that if a chunk is committed again, already committed content stays. + assert(_num_allocated.get() == 0, "Call this on an empty cm."); + + // Get a number of random sized but large chunks, be sure to cover multiple vsnodes. + // Also, commit those chunks. + const size_t min_words_to_allocate = 4 * Settings::virtual_space_node_default_word_size(); // about 16 m + + while (_num_allocated.get() < max_num_chunks && + _word_size_allocated.get() < min_words_to_allocate) { + const chklvl_t lvl = LOWEST_CHUNK_LEVEL + os::random() % 4; + attempt_allocate_at(_num_allocated.get(), lvl, lvl, true); // < fully committed please + } + +//_cm->print_on(tty); +//_vs_list->print_on(tty); + DEBUG_ONLY(_cm->verify(true);) + DEBUG_ONLY(_vs_list->verify(true);) + + // Return about three quarter of the chunks. + for (int i = 0; i < max_num_chunks; i ++) { + if (os::random() % 100 < 75) { + attempt_free_at(i); + } + } + + // Now do a reclaim. + _cm->wholesale_reclaim(); + +//_cm->print_on(tty); +//_vs_list->print_on(tty); + DEBUG_ONLY(_cm->verify(true);) + DEBUG_ONLY(_vs_list->verify(true);) + + // Return all chunks + free_all_chunks(); + + // Now do a second reclaim. + _cm->wholesale_reclaim(); + +//_cm->print_on(tty); +//_vs_list->print_on(tty); + DEBUG_ONLY(_cm->verify(true);) + DEBUG_ONLY(_vs_list->verify(true);) + + // All space should be gone now, if the settings are not preventing reclaim + if (Settings::delete_nodes_on_purge()) { + ASSERT_EQ(_vs_list->reserved_words(), (size_t)0); + } + if (Settings::uncommit_on_purge() || Settings::delete_nodes_on_purge()) { + ASSERT_EQ(_vs_list->committed_words(), (size_t)0); + } + + } + +}; + +// Note: we unfortunately need TEST_VM even though the system tested +// should be pretty independent since we need things like os::vm_page_size() +// which in turn need OS layer initialization. +TEST_VM(metaspace, chunkmanager_test_whole_range) { + ChunkManagerTest ct; + ct.test(100, LOWEST_CHUNK_LEVEL, HIGHEST_CHUNK_LEVEL); +} + +TEST_VM(metaspace, chunkmanager_test_small_chunks) { + ChunkManagerTest ct; + ct.test(100, HIGHEST_CHUNK_LEVEL / 2, HIGHEST_CHUNK_LEVEL); +} + +TEST_VM(metaspace, chunkmanager_test_large_chunks) { + ChunkManagerTest ct; + ct.test(100, LOWEST_CHUNK_LEVEL, HIGHEST_CHUNK_LEVEL / 2); +} + +TEST_VM(metaspace, chunkmanager_test_enlarge_chunk) { + ChunkManagerTest ct; + ct.test_enlarge_chunk(); +} + +TEST_VM(metaspace, chunkmanager_test_recommit_chunk) { + ChunkManagerTest ct; + ct.test_recommit_chunk(); +} + +TEST_VM(metaspace, chunkmanager_test_wholesale_reclaim) { + ChunkManagerTest ct; + ct.test_wholesale_reclaim(); +} + diff -r cea6839598e8 -r 595fcbebaa77 test/hotspot/gtest/metaspace/test_chunkheaderpool.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/gtest/metaspace/test_chunkheaderpool.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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 "metaspaceTestsCommon.hpp" + +class ChunkHeaderPoolTest { + + static const size_t max_cap = 0x1000; + + ChunkHeaderPool _pool; + + // Array of the same size as the pool max capacity; holds the allocated elements. + Metachunk* _elems[max_cap]; + SizeCounter _num_allocated; + + void attempt_free_at(size_t index) { + + LOG("attempt_free_at " SIZE_FORMAT ".", index); + + if (_elems[index] == NULL) { + return; + } + + _pool.return_chunk_header(_elems[index]); + _elems[index] = NULL; + + _num_allocated.decrement(); + DEBUG_ONLY(_num_allocated.check(_pool.used());) + + DEBUG_ONLY(_pool.verify(true);) + + } + + void attempt_allocate_at(size_t index) { + + LOG("attempt_allocate_at " SIZE_FORMAT ".", index); + + if (_elems[index] != NULL) { + return; + } + + Metachunk* c = _pool.allocate_chunk_header(); + EXPECT_NOT_NULL(c); + _elems[index] = c; + c->set_free(); + + _num_allocated.increment(); + DEBUG_ONLY(_num_allocated.check(_pool.used());) + + DEBUG_ONLY(_pool.verify(true);) + } + + void attempt_allocate_or_free_at(size_t index) { + if (_elems[index] == NULL) { + attempt_allocate_at(index); + } else { + attempt_free_at(index); + } + } + + // Randomly allocate from the pool and free. Slight preference for allocation. + void test_random_alloc_free(int num_iterations) { + + for (int iter = 0; iter < num_iterations; iter ++) { + size_t index = (size_t)os::random() % max_cap; + attempt_allocate_or_free_at(index); + } + + DEBUG_ONLY(_pool.verify(true);) + + } + + static void test_once() { + ChunkHeaderPoolTest test; + test.test_random_alloc_free(100); + } + + +public: + + ChunkHeaderPoolTest() : _pool(true) { + memset(_elems, 0, sizeof(_elems)); + } + + static void run_tests() { + for (int i = 0; i < 1000; i ++) { + test_once(); + } + } + +}; + +TEST_VM(metaspace, chunk_header_pool) { + ChunkHeaderPoolTest::run_tests(); +} diff -r cea6839598e8 -r 595fcbebaa77 test/hotspot/gtest/metaspace/test_commitmask.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/gtest/metaspace/test_commitmask.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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 "runtime/os.hpp" + +#include "metaspaceTestsCommon.hpp" + +static int get_random(int limit) { return os::random() % limit; } + +class CommitMaskTest { + const MetaWord* const _base; + const size_t _word_size; + + CommitMask _mask; + + void verify_mask() { + // Note: we omit the touch test since we operate on fictional + // memory + DEBUG_ONLY(_mask.verify(true, false);) + } + + // Return a random sub range within [_base.._base + word_size), + // aligned to granule size + const MetaWord* calc_random_subrange(size_t* p_word_size) { + size_t l1 = get_random((int)_word_size); + size_t l2 = get_random((int)_word_size); + if (l1 > l2) { + size_t l = l1; + l1 = l2; + l2 = l; + } + l1 = align_down(l1, Settings::commit_granule_words()); + l2 = align_up(l2, Settings::commit_granule_words()); + + const MetaWord* p = _base + l1; + const size_t len = l2 - l1; + + assert(p >= _base && p + len <= _base + _word_size, + "Sanity"); + *p_word_size = len; + + return p; + } + + void test1() { + + LOG("test1"); + + // Commit everything + size_t prior_committed = _mask.mark_range_as_committed(_base, _word_size); + verify_mask(); + ASSERT_LE(prior_committed, _word_size); // We do not really know + + // Commit everything again, should be a noop + prior_committed = _mask.mark_range_as_committed(_base, _word_size); + verify_mask(); + ASSERT_EQ(prior_committed, _word_size); + + ASSERT_EQ(_mask.get_committed_size(), + _word_size); + ASSERT_EQ(_mask.get_committed_size_in_range(_base, _word_size), + _word_size); + + for (const MetaWord* p = _base; p < _base + _word_size; p ++) { + ASSERT_TRUE(_mask.is_committed_address(p)); + } + + // Now make an uncommitted hole + size_t sr_word_size; + const MetaWord* sr_base = calc_random_subrange(&sr_word_size); + LOG("subrange " PTR_FORMAT "-" PTR_FORMAT ".", + p2i(sr_base), p2i(sr_base + sr_word_size)); + + size_t prior_uncommitted = + _mask.mark_range_as_uncommitted(sr_base, sr_word_size); + verify_mask(); + ASSERT_EQ(prior_uncommitted, (size_t)0); + + // Again, for fun, should be a noop now. + prior_uncommitted = _mask.mark_range_as_uncommitted(sr_base, sr_word_size); + verify_mask(); + ASSERT_EQ(prior_uncommitted, sr_word_size); + + ASSERT_EQ(_mask.get_committed_size_in_range(sr_base, sr_word_size), + (size_t)0); + ASSERT_EQ(_mask.get_committed_size(), + _word_size - sr_word_size); + ASSERT_EQ(_mask.get_committed_size_in_range(_base, _word_size), + _word_size - sr_word_size); + for (const MetaWord* p = _base; p < _base + _word_size; p ++) { + if (p >= sr_base && p < sr_base + sr_word_size) { + ASSERT_FALSE(_mask.is_committed_address(p)); + } else { + ASSERT_TRUE(_mask.is_committed_address(p)); + } + } + + // Recommit whole range + prior_committed = _mask.mark_range_as_committed(_base, _word_size); + verify_mask(); + ASSERT_EQ(prior_committed, _word_size - sr_word_size); + + ASSERT_EQ(_mask.get_committed_size_in_range(sr_base, sr_word_size), + sr_word_size); + ASSERT_EQ(_mask.get_committed_size(), + _word_size); + ASSERT_EQ(_mask.get_committed_size_in_range(_base, _word_size), + _word_size); + for (const MetaWord* p = _base; p < _base + _word_size; p ++) { + ASSERT_TRUE(_mask.is_committed_address(p)); + } + + } + + void test2() { + + LOG("test2"); + + // Uncommit everything + size_t prior_uncommitted = _mask.mark_range_as_uncommitted(_base, _word_size); + verify_mask(); + ASSERT_LE(prior_uncommitted, _word_size); + + // Uncommit everything again, should be a noop + prior_uncommitted = _mask.mark_range_as_uncommitted(_base, _word_size); + verify_mask(); + ASSERT_EQ(prior_uncommitted, _word_size); + + ASSERT_EQ(_mask.get_committed_size(), + (size_t)0); + ASSERT_EQ(_mask.get_committed_size_in_range(_base, _word_size), + (size_t)0); + + // Now make an committed region + size_t sr_word_size; + const MetaWord* sr_base = calc_random_subrange(&sr_word_size); + LOG("subrange " PTR_FORMAT "-" PTR_FORMAT ".", + p2i(sr_base), p2i(sr_base + sr_word_size)); + + ASSERT_EQ(_mask.get_committed_size_in_range(sr_base, sr_word_size), + (size_t)0); + for (const MetaWord* p = _base; p < _base + _word_size; p ++) { + ASSERT_FALSE(_mask.is_committed_address(p)); + } + + size_t prior_committed = _mask.mark_range_as_committed(sr_base, sr_word_size); + verify_mask(); + ASSERT_EQ(prior_committed, (size_t)0); + + // Again, for fun, should be a noop now. + prior_committed = _mask.mark_range_as_committed(sr_base, sr_word_size); + verify_mask(); + ASSERT_EQ(prior_committed, sr_word_size); + + ASSERT_EQ(_mask.get_committed_size_in_range(sr_base, sr_word_size), + sr_word_size); + ASSERT_EQ(_mask.get_committed_size(), + sr_word_size); + ASSERT_EQ(_mask.get_committed_size_in_range(_base, _word_size), + sr_word_size); + for (const MetaWord* p = _base; p < _base + _word_size; p ++) { + if (p >= sr_base && p < sr_base + sr_word_size) { + ASSERT_TRUE(_mask.is_committed_address(p)); + } else { + ASSERT_FALSE(_mask.is_committed_address(p)); + } + } + + // Re-uncommit whole range + prior_uncommitted = _mask.mark_range_as_uncommitted(_base, _word_size); + verify_mask(); + ASSERT_EQ(prior_uncommitted, _word_size - sr_word_size); + + EXPECT_EQ(_mask.get_committed_size_in_range(sr_base, sr_word_size), + (size_t)0); + EXPECT_EQ(_mask.get_committed_size(), + (size_t)0); + EXPECT_EQ(_mask.get_committed_size_in_range(_base, _word_size), + (size_t)0); + for (const MetaWord* p = _base; p < _base + _word_size; p ++) { + ASSERT_FALSE(_mask.is_committed_address(p)); + } + + } + + + void test3() { + + // arbitrary ranges are set and cleared and compared with the test map + TestMap map(_word_size); + + _mask.clear_large(); + + for (int run = 0; run < 100; run ++) { + + range_t r; + calc_random_range(_word_size, &r, Settings::commit_granule_words()); + + ASSERT_EQ(_mask.get_committed_size(), (size_t)map.get_num_set()); + ASSERT_EQ(_mask.get_committed_size_in_range(_base + r.from, r.to - r.from), + (size_t)map.get_num_set(r.from, r.to)); + + if (os::random() % 100 < 50) { + _mask.mark_range_as_committed(_base + r.from, r.to - r.from); + map.set_range(r.from, r.to); + } else { + _mask.mark_range_as_uncommitted(_base + r.from, r.to - r.from); + map.clear_range(r.from, r.to); + } + + ASSERT_EQ(_mask.get_committed_size(), (size_t)map.get_num_set()); + ASSERT_EQ(_mask.get_committed_size_in_range(_base + r.from, r.to - r.from), + (size_t)map.get_num_set(r.from, r.to)); + + } + + } + + +public: + + CommitMaskTest(const MetaWord* base, size_t size) + : _base(base), _word_size(size), _mask(base, size) + {} + + void test() { + LOG("mask range: " PTR_FORMAT "-" PTR_FORMAT + " (" SIZE_FORMAT " words).", + p2i(_base), p2i(_base + _word_size), _word_size); + for (int i = 0; i < 5; i ++) { + test1(); test2(); test3(); + } + } + + +}; + +TEST(metaspace, commit_mask_basics) { + + const MetaWord* const base = (const MetaWord*) 0x100000; + + CommitMask mask1(base, Settings::commit_granule_words()); + ASSERT_EQ(mask1.size(), (BitMap::idx_t)1); + + CommitMask mask2(base, Settings::commit_granule_words() * 4); + ASSERT_EQ(mask2.size(), (BitMap::idx_t)4); + + CommitMask mask3(base, Settings::commit_granule_words() * 43); + ASSERT_EQ(mask3.size(), (BitMap::idx_t)43); + + mask3.mark_range_as_committed(base, Settings::commit_granule_words()); + mask3.mark_range_as_committed(base + (Settings::commit_granule_words() * 42), Settings::commit_granule_words()); + + ASSERT_EQ(mask3.at(0), 1); + for (int i = 1; i < 42; i ++) { + ASSERT_EQ(mask3.at(i), 0); + } + ASSERT_EQ(mask3.at(42), 1); + +} + +TEST(metaspace, commit_mask_small) { + + const MetaWord* const base = (const MetaWord*) 0x100000; + + CommitMaskTest test(base, Settings::commit_granule_words()); + test.test(); + +} + +TEST(metaspace, commit_mask_range) { + + const MetaWord* const base = (const MetaWord*) 0x100000; + const size_t len = Settings::commit_granule_words() * 4; + const MetaWord* const end = base + len; + CommitMask mask(base, len); + + LOG("total range: " PTR_FORMAT "-" PTR_FORMAT "\n", p2i(base), p2i(end)); + + size_t l = mask.mark_range_as_committed(base, len); + ASSERT_LE(l, len); + + for (const MetaWord* p = base; p <= end - Settings::commit_granule_words(); + p += Settings::commit_granule_words()) { + for (const MetaWord* p2 = p + Settings::commit_granule_words(); + p2 <= end; p2 += Settings::commit_granule_words()) { + LOG(PTR_FORMAT "-" PTR_FORMAT "\n", p2i(p), p2i(p2)); + EXPECT_EQ(mask.get_committed_size_in_range(p, p2 - p), + (size_t)(p2 - p)); + } + } + + l = mask.mark_range_as_uncommitted(base, len); + ASSERT_EQ(l, (size_t)0); + + for (const MetaWord* p = base; p <= end - Settings::commit_granule_words(); + p += Settings::commit_granule_words()) { + for (const MetaWord* p2 = p + Settings::commit_granule_words(); + p2 <= end; p2 += Settings::commit_granule_words()) { + LOG(PTR_FORMAT "-" PTR_FORMAT "\n", p2i(p), p2i(p2)); + EXPECT_EQ(mask.get_committed_size_in_range(p, p2 - p), + (size_t)(0)); + } + } + +} + + +TEST(metaspace, commit_mask_random) { + + for (int i = 0; i < 5; i ++) { + + // make up a range out of thin air + const MetaWord* const base = + align_down( (const MetaWord*) ((uintptr_t) os::random() * os::random()), + Settings::commit_granule_bytes()); + const size_t len = align_up( 1 + (os::random() % M), + Settings::commit_granule_words()); + + CommitMaskTest test(base, len); + test.test(); + + } + +} diff -r cea6839598e8 -r 595fcbebaa77 test/hotspot/gtest/metaspace/test_is_metaspace_obj.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/gtest/metaspace/test_is_metaspace_obj.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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 "memory/allocation.inline.hpp" +#include "memory/metaspace/classLoaderMetaspace.hpp" +#include "memory/metaspace/virtualSpaceList.hpp" +#include "memory/metaspace.hpp" +#include "runtime/mutex.hpp" +#include "runtime/mutexLocker.hpp" +#include "runtime/os.hpp" +#include "unittest.hpp" + +using namespace metaspace; + + + +// Test the cheerful multitude of metaspace-contains-functions. +class MetaspaceIsMetaspaceObjTest { + Mutex* _lock; + ClassLoaderMetaspace* _ms; + +public: + + MetaspaceIsMetaspaceObjTest() : _lock(NULL), _ms(NULL) {} + ~MetaspaceIsMetaspaceObjTest() { + delete _ms; + delete _lock; + } + + void do_test(MetadataType mdType) { + _lock = new Mutex(Monitor::native, "gtest-IsMetaspaceObjTest-lock", false, Monitor::_safepoint_check_never); + { + MutexLocker ml(_lock, Mutex::_no_safepoint_check_flag); + _ms = new ClassLoaderMetaspace(_lock, StandardMetaspaceType); + } + + const MetaspaceObj* p = (MetaspaceObj*) _ms->allocate(42, mdType); + + // Test MetaspaceObj::is_metaspace_object + ASSERT_TRUE(MetaspaceObj::is_valid(p)); + + // A misaligned object shall not be recognized + const MetaspaceObj* p_misaligned = (MetaspaceObj*)((address)p) + 1; + ASSERT_FALSE(MetaspaceObj::is_valid(p_misaligned)); + + // Test VirtualSpaceList::contains + const VirtualSpaceList* const vslist = + (mdType == ClassType && Metaspace::using_class_space()) ? + VirtualSpaceList::vslist_class() : VirtualSpaceList::vslist_nonclass(); + + ASSERT_TRUE(vslist->contains((MetaWord*)p)); + + // A misaligned pointer shall still be recognized by list::contains + ASSERT_TRUE(vslist->contains((MetaWord*)((address)p) + 1)); + + // Now for some bogus values + ASSERT_FALSE(MetaspaceObj::is_valid((MetaspaceObj*)NULL)); + + // Should exercise various paths in MetaspaceObj::is_valid() + ASSERT_FALSE(MetaspaceObj::is_valid((MetaspaceObj*)1024)); + ASSERT_FALSE(MetaspaceObj::is_valid((MetaspaceObj*)8192)); + + MetaspaceObj* p_stack = (MetaspaceObj*) &_lock; + ASSERT_FALSE(MetaspaceObj::is_valid(p_stack)); + + MetaspaceObj* p_heap = (MetaspaceObj*) os::malloc(41, mtInternal); + ASSERT_FALSE(MetaspaceObj::is_valid(p_heap)); + os::free(p_heap); + + // Test Metaspace::contains_xxx + ASSERT_TRUE(Metaspace::contains(p)); + ASSERT_TRUE(Metaspace::contains_non_shared(p)); + + delete _ms; + _ms = NULL; + delete _lock; + _lock = NULL; + } + +}; + +TEST_VM(metaspace, is_metaspace_obj_non_class) { + MetaspaceIsMetaspaceObjTest test; + test.do_test(NonClassType); +} + +TEST_VM(metaspace, is_metaspace_obj_class) { + MetaspaceIsMetaspaceObjTest test; + test.do_test(ClassType); +} + diff -r cea6839598e8 -r 595fcbebaa77 test/hotspot/gtest/metaspace/test_metachunk.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/gtest/metaspace/test_metachunk.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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 "metaspace/metaspaceTestsCommon.hpp" + + + +class MetachunkTest { + + CommitLimiter _commit_limiter; + VirtualSpaceList _vs_list; + ChunkManager _cm; + + Metachunk* alloc_chunk(chklvl_t lvl) { + + Metachunk* c = _cm.get_chunk(lvl, lvl); + EXPECT_NOT_NULL(c); + EXPECT_EQ(c->level(), lvl); + check_chunk(c); + + DEBUG_ONLY(c->verify(true);) + + return c; + } + + void check_chunk(const Metachunk* c) const { + EXPECT_LE(c->used_words(), c->committed_words()); + EXPECT_LE(c->committed_words(), c->word_size()); + EXPECT_NOT_NULL(c->base()); + EXPECT_TRUE(_vs_list.contains(c->base())); + EXPECT_TRUE(is_aligned(c->base(), MAX_CHUNK_BYTE_SIZE)); + EXPECT_TRUE(is_aligned(c->word_size(), MAX_CHUNK_WORD_SIZE)); + EXPECT_TRUE(metaspace::chklvl::is_valid_level(c->level())); + + if (c->next() != NULL) EXPECT_EQ(c->next()->prev(), c); + if (c->prev() != NULL) EXPECT_EQ(c->prev()->next(), c); + if (c->next_in_vs() != NULL) EXPECT_EQ(c->next_in_vs()->prev_in_vs(), c); + if (c->prev_in_vs() != NULL) EXPECT_EQ(c->prev_in_vs()->next_in_vs(), c); + + DEBUG_ONLY(c->verify(true);) + } + +public: + + MetachunkTest(size_t commit_limit) + : _commit_limiter(commit_limit), + _vs_list("test_vs_list", &_commit_limiter), + _cm("test_cm", &_vs_list) + { + } + + void test_random_allocs() { + + // Randomly alloc from a chunk until it is full. + Metachunk* c = alloc_chunk(LOWEST_CHUNK_LEVEL); + + check_chunk(c); + + EXPECT_TRUE(c->is_in_use()); + EXPECT_EQ(c->used_words(), (size_t)0); + + // uncommit to start off with uncommitted chunk; then start allocating. + c->set_free(); + c->uncommit(); + c->set_in_use(); + + EXPECT_EQ(c->committed_words(), (size_t)0); + + RandSizeGenerator rgen(1, 256, 0.1f, 1024, 4096); // note: word sizes + SizeCounter words_allocated; + + MetaWord* p = NULL; + bool did_hit_commit_limit = false; + do { + + const size_t alloc_size = align_up(rgen.get(), Metachunk::allocation_alignment_words); + + // Note: about net and raw sizes: these concepts only exist at the SpaceManager level. + // At the chunk level (where we test here), we allocate exactly what we ask, in number of words. + + const bool may_hit_commit_limit = + _commit_limiter.possible_expansion_words() <= align_up(alloc_size, Settings::commit_granule_words()); + + p = c->allocate(alloc_size, &did_hit_commit_limit); + LOG("Allocated " SIZE_FORMAT " words, chunk: " METACHUNK_FULL_FORMAT, alloc_size, METACHUNK_FULL_FORMAT_ARGS(c)); + + check_chunk(c); + + if (p != NULL) { + // From time to time deallocate to test deallocation. Since we do this on the very last allocation, + // this should always work. + if (os::random() % 100 > 95) { + LOG("Test dealloc in place"); + EXPECT_TRUE(c->attempt_rollback_allocation(p, alloc_size)); + } else { + fill_range_with_pattern(p, (uintx) this, alloc_size); // test that we can access this. + words_allocated.increment_by(alloc_size); + EXPECT_EQ(c->used_words(), words_allocated.get()); + } + } else { + // Allocating from a chunk can only fail for one of two reasons: Either the chunk is full, or + // we attempted to increase the chunk's commit region and hit the commit limit. + if (did_hit_commit_limit) { + EXPECT_TRUE(may_hit_commit_limit); + } else { + // Chunk is full. + EXPECT_LT(c->free_words(), alloc_size); + } + } + + } while(p != NULL); + + check_range_for_pattern(c->base(), (uintx) this, c->used_words()); + + // At the end of the test return the chunk to the chunk manager to + // avoid asserts on destruction time. + _cm.return_chunk(c); + + } + + + +}; + + + +TEST_VM(metaspace, metachunk_test_random_allocs_no_commit_limit) { + + // The test only allocates one root chunk and plays with it, so anything + // above the size of a root chunk should not hit commit limit. + MetachunkTest test(2 * MAX_CHUNK_WORD_SIZE); + test.test_random_allocs(); + +} + +TEST_VM(metaspace, metachunk_test_random_allocs_with_commit_limit) { + + // The test allocates one root chunk and plays with it, so a limit smaller + // than root chunk size will be hit. + MetachunkTest test(MAX_CHUNK_WORD_SIZE / 2); + test.test_random_allocs(); + +} diff -r cea6839598e8 -r 595fcbebaa77 test/hotspot/gtest/metaspace/test_metaspaceUtils.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/gtest/metaspace/test_metaspaceUtils.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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 "memory/metaspace.hpp" +#include "unittest.hpp" + + +TEST_VM(MetaspaceUtils, reserved) { + size_t reserved = MetaspaceUtils::reserved_bytes(); + EXPECT_GT(reserved, 0UL); + + size_t reserved_metadata = MetaspaceUtils::reserved_bytes(metaspace::NonClassType); + EXPECT_GT(reserved_metadata, 0UL); + EXPECT_LE(reserved_metadata, reserved); +} + +TEST_VM(MetaspaceUtils, reserved_compressed_class_pointers) { + if (!UseCompressedClassPointers) { + return; + } + size_t reserved = MetaspaceUtils::reserved_bytes(); + EXPECT_GT(reserved, 0UL); + + size_t reserved_class = MetaspaceUtils::reserved_bytes(metaspace::ClassType); + EXPECT_GT(reserved_class, 0UL); + EXPECT_LE(reserved_class, reserved); +} + +TEST_VM(MetaspaceUtils, committed) { + size_t committed = MetaspaceUtils::committed_bytes(); + EXPECT_GT(committed, 0UL); + + size_t reserved = MetaspaceUtils::reserved_bytes(); + EXPECT_LE(committed, reserved); + + size_t committed_metadata = MetaspaceUtils::committed_bytes(metaspace::NonClassType); + EXPECT_GT(committed_metadata, 0UL); + EXPECT_LE(committed_metadata, committed); +} + +TEST_VM(MetaspaceUtils, committed_compressed_class_pointers) { + if (!UseCompressedClassPointers) { + return; + } + size_t committed = MetaspaceUtils::committed_bytes(); + EXPECT_GT(committed, 0UL); + + size_t committed_class = MetaspaceUtils::committed_bytes(metaspace::ClassType); + EXPECT_GT(committed_class, 0UL); + EXPECT_LE(committed_class, committed); +} + diff -r cea6839598e8 -r 595fcbebaa77 test/hotspot/gtest/metaspace/test_spacemanager.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/gtest/metaspace/test_spacemanager.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,624 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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" + +#define LOG_PLEASE + +#include "metaspace/metaspaceTestsCommon.hpp" + + + +// See spaceManager.cpp : needed for predicting commit sizes. +namespace metaspace { + extern size_t get_raw_allocation_word_size(size_t net_word_size); +} + + + +class SpaceManagerTest { + + // One global chunk manager, with an assiociated global virtual space list as backing, + // and a number of space managers feeding from that manager in parallel. + CommitLimiter _commit_limiter; + + const size_t _avg_occupancy; + + VirtualSpaceList* _vslist; + ChunkManager* _cm; + const ChunkAllocSequence* _alloc_sequence; + + const int _num_spaces; + + size_t _rss_at_start; + size_t _rss_at_end; + size_t _rss_after_cleanup; + + + // A little test bed holding one SpaceManager and its lock + // and keeping track of its allocations. + struct SpaceManagerTestBed : public CHeapObj { + + Mutex* _lock; + SpaceManager* _sm; + int _index; + + // We keep track of individual allocations. Note that this adds + // 256K per instance of SpaceManagerTestBed. + struct allocation_t { + MetaWord* p; + size_t word_size; + }; + static const int _max_allocations = 0x4000; + int _num_allocations; + size_t _words_allocated; + allocation_t _allocations[_max_allocations]; + + // Note: used counter contains "used" from the chunk perspective, which is + // used + freelist + alignment corrections. This does not translate 1:1 to + // _words_allocated, so this is difficult to test. + SizeAtomicCounter _used_counter; + + public: + + SpaceManager* sm() { return _sm; } + + SpaceManagerTestBed(int index, ChunkManager* cm, const ChunkAllocSequence* alloc_sequence) + : _lock(NULL), _sm(NULL), _index(index), _num_allocations(0), _words_allocated(0) + { + memset(_allocations, 0, sizeof(_allocations)); + _lock = new Mutex(Monitor::native, "gtest-SpaceManagerTestBed-lock", false, Monitor::_safepoint_check_never); + { + // Pull lock during space creation, since this is what happens in the VM too + // (see ClassLoaderData::metaspace_non_null(), which we mimick here). + MutexLocker ml(_lock, Mutex::_no_safepoint_check_flag); + _sm = new SpaceManager(cm, alloc_sequence, _lock, &_used_counter, "gtest-SpaceManagerTestBed-sm"); + } + } + + ~SpaceManagerTestBed() { + + // EXPECT_EQ(_used_counter.get(), _words_allocated); + + // Iterate thru all allocation records and test content first. + for (int i = 0; i < _num_allocations; i ++) { + const allocation_t* al = _allocations + i; + EXPECT_TRUE(al->p == NULL || check_marked_range(al->p, (uintx)this, al->word_size)); + } + + // Delete SpaceManager. That should clean up all metaspace. + delete _sm; + delete _lock; + + } + + + size_t words_allocated() const { return _words_allocated; } + int num_allocations() const { return _num_allocations; } + + int index() const { return _index; } + + bool is_full() const { return _num_allocations == _max_allocations; } + bool is_empty() const { return _num_allocations == 0; } + + // Allocate from space. Returns NULL if either the bed is full or if the allocation + // itself failed. + MetaWord* allocate_and_test(size_t word_size) { + if (is_full()) { + return NULL; + } + MetaWord* p = _sm->allocate(word_size); + if (p != NULL) { + EXPECT_TRUE(is_aligned(p, sizeof(void*))); + mark_range(p, (uintx)this, word_size); + // Remember this allocation. + allocation_t* al = _allocations + _num_allocations; + al->p = p; al->word_size = word_size; + _num_allocations ++; + _words_allocated += word_size; + // EXPECT_EQ(_used_counter.get(), _words_allocated); + } + return p; + } + + // Deallocate the last allocation done. + void deallocate_last() { + assert(_num_allocations > 0, "Sanity"); + allocation_t* al = &_allocations[_num_allocations - 1]; + _sm->deallocate(al->p, al->word_size); + al->p = NULL; + _num_allocations --; + } + + // Deallocate a random single allocation. + void deallocate_random() { + if (_num_allocations > 0) { + int idx = os::random() % _num_allocations; + allocation_t* al = _allocations + idx; + if (al->p == NULL) { + // already deallocated? Should still have its old word size set + assert(al->word_size > 0, "Sanity"); + } else { + _sm->deallocate(al->p, al->word_size); + al->p = NULL; // but leave word_size, see above + } + } + } + + }; // End: SpaceManagerTestBed + + SpaceManagerTestBed** _testbeds; + + SpaceManagerTestBed* testbed_at(int index) { + assert(index < _num_spaces, "Sanity"); + // Create on the fly if necessary. + if (_testbeds[index] == NULL) { + _testbeds[index] = new SpaceManagerTestBed(index, _cm, _alloc_sequence); + } + return _testbeds[index]; + } + + SpaceManagerTestBed* next_testbed(SpaceManagerTestBed* bed) { + int i = bed->index() + 1; + if (i == _num_spaces) { + i = 0; + } + return testbed_at(i); + } + + SpaceManagerTestBed* get_random_testbed() { + const int index = os::random() % _num_spaces; + return testbed_at(index); + } + + SpaceManagerTestBed* get_random_matching_testbed(bool should_be_empty) { + const int start_index = os::random() % _num_spaces; + int i = start_index; + do { + SpaceManagerTestBed* bed = testbed_at(i); + if ((should_be_empty && bed->words_allocated() == 0) || + (!should_be_empty && bed->words_allocated() > 0)) { + return bed; + } + i ++; + if (i == _num_spaces) { + i = 0; + } + } while (i != start_index); + return NULL; + } + + SpaceManagerTestBed* get_random_nonempty_testbed() { + return get_random_matching_testbed(false); + } + + SpaceManagerTestBed* get_random_empty_testbed() { + return get_random_matching_testbed(true); + } + + MetaWord* alloc_from_testbed(SpaceManagerTestBed* bed, size_t word_size) { + MetaWord* p = bed->allocate_and_test(word_size); + if (p == NULL) { + // Getting NULL back may mean: + // - testbed is full. + // - We hit the commit limit. + if (!bed->is_full()) { + EXPECT_LT(_commit_limiter.possible_expansion_words(), + metaspace::get_raw_allocation_word_size(word_size)); + } + } + return p; + } + + void delete_testbed_at(int index) { + delete _testbeds[index]; + _testbeds[index] = NULL; + } + + void delete_testbed(SpaceManagerTestBed* bed) { + assert(_testbeds[bed->index()] == bed, "Sanity"); + _testbeds[bed->index()] = NULL; + delete bed; + } + + // Allocate multiple times random sizes from a single spacemanager. + // Will stop allocation prematurely if per-space max is reached or if commit limit is reached. + bool allocate_multiple_random(SpaceManagerTestBed* bed, int num_allocations, RandSizeGenerator* rgen) { + for (int n = 0; n < num_allocations; n ++) { + const size_t alloc_size = rgen->get(); + if (alloc_from_testbed(bed, alloc_size) == NULL) { + return false; + } + } + return true; + } + + int get_total_number_of_allocations() const { + int sum = 0; + for (int i = 0; i < _num_spaces; i ++) { + if (_testbeds[i] != NULL) { + sum += _testbeds[i]->num_allocations(); + } + } + return sum; + } + + size_t get_total_words_allocated() const { + size_t sum = 0; + for (int i = 0; i < _num_spaces; i ++) { + if (_testbeds[i] != NULL) { + sum += _testbeds[i]->words_allocated(); + } + } + return sum; + } + + // Allocate until you reach avg occupancy, hover there by allocating/freeing. + void test_hover(int num_cycles, int avg_allocs_per_space_manager, RandSizeGenerator* rgen, + bool exercise_reclaim, bool exercise_dealloc) { + + int alloc_cycles = 0; + int free_cycles = 0; + for (int cyc = 0; cyc < num_cycles; cyc ++) { + if (get_total_words_allocated() < _avg_occupancy) { + SpaceManagerTestBed* bed = get_random_testbed(); + if (allocate_multiple_random(bed, avg_allocs_per_space_manager, rgen)) { + alloc_cycles ++; + } + } else { + SpaceManagerTestBed* bed = get_random_nonempty_testbed(); + if (bed != NULL) { + free_cycles ++; + delete_testbed(bed); + } + } + if (exercise_dealloc) { + if (os::random() % 100 > 95) { + SpaceManagerTestBed* bed = get_random_nonempty_testbed(); + if (bed != NULL) { + bed->deallocate_random(); + } + } + } + if (cyc % 100 == 0) { + const size_t committed_before = _vslist->committed_words(); + if (exercise_reclaim) { + _cm->wholesale_reclaim(); + } + LOG("cyc: %d (a %d f %d) allocated: " SIZE_FORMAT ", committed " SIZE_FORMAT "->" SIZE_FORMAT ".", + cyc, alloc_cycles, free_cycles, get_total_words_allocated(), committed_before, _vslist->committed_words()); + } + } + +// _vslist->print_on(tty); +// _cm->print_on(tty); + + } // end: test_hover + + // Allocate until you reach avg occupancy, then drain completely. Repeat. + void test_wave(int num_cycles, int avg_allocs_per_space_manager, RandSizeGenerator* rgen, + bool exercise_reclaim, bool exercise_dealloc) { + + bool rising = true; + int num_waves = 0; + for (int cyc = 0; cyc < num_cycles; cyc ++) { + if (rising) { + num_waves ++; + if (get_total_words_allocated() >= _avg_occupancy) { + rising = false; + } else { + SpaceManagerTestBed* bed = get_random_testbed(); + allocate_multiple_random(bed, avg_allocs_per_space_manager, rgen); + } + } else { + SpaceManagerTestBed* bed = get_random_nonempty_testbed(); + if (bed == NULL) { + EXPECT_EQ(get_total_words_allocated(), (size_t)0); + rising = true; + } else { + delete_testbed(bed); + } + } + if (exercise_dealloc) { + if (os::random() % 100 > 95) { + SpaceManagerTestBed* bed = get_random_nonempty_testbed(); + if (bed != NULL) { + bed->deallocate_random(); + } + } + } + if (cyc % 100 == 0) { + LOG("cyc: %d num waves: %d num allocations: %d , words allocated: " SIZE_FORMAT ", committed " SIZE_FORMAT ".", + cyc, num_waves, get_total_number_of_allocations(), get_total_words_allocated(), _vslist->committed_words()); + const size_t committed_before = _vslist->committed_words(); + if (exercise_reclaim) { + _cm->wholesale_reclaim(); + LOG(".. reclaim: " SIZE_FORMAT "->" SIZE_FORMAT ".", committed_before, _vslist->committed_words()); + } + } + } + +// _vslist->print_on(tty); + // _cm->print_on(tty); + + } // end: test_wave + + + static void check_sm_stat_is_empty(sm_stats_t& stat) { + in_use_chunk_stats_t totals = stat.totals(); + EXPECT_EQ(totals.word_size, (size_t)0); + EXPECT_EQ(totals.committed_words, (size_t)0); + EXPECT_EQ(totals.used_words, (size_t)0); + EXPECT_EQ(totals.free_words, (size_t)0); + EXPECT_EQ(totals.waste_words, (size_t)0); + } + + static void check_sm_stat_is_consistent(sm_stats_t& stat) { + in_use_chunk_stats_t totals = stat.totals(); + EXPECT_GE(totals.word_size, totals.committed_words); + EXPECT_EQ(totals.committed_words, totals.used_words + totals.free_words + totals.waste_words); + EXPECT_GE(totals.used_words, stat.free_blocks_word_size); + } + + void test_total_statistics() { + sm_stats_t totals1; + check_sm_stat_is_empty(totals1); + sm_stats_t totals2; + check_sm_stat_is_empty(totals1); + for (int i = 0; i < _num_spaces; i ++) { + if (_testbeds[i] != NULL) { + sm_stats_t stat; + _testbeds[i]->_sm->add_to_statistics(&stat); + check_sm_stat_is_consistent(stat); + DEBUG_ONLY(stat.verify()); + _testbeds[i]->_sm->add_to_statistics(&totals1); + check_sm_stat_is_consistent(totals1); + totals2.add(stat); + check_sm_stat_is_consistent(totals2); + } + } + EXPECT_EQ(totals1.totals().used_words, + totals2.totals().used_words); + } + +public: + + void run_test(int num_cycles, int avg_allocs_per_space_manager, RandSizeGenerator* rgen, + bool exercise_reclaim, bool exercise_dealloc) { + LOG("hover test"); + test_hover(num_cycles, avg_allocs_per_space_manager, rgen, exercise_reclaim, exercise_dealloc); + + test_total_statistics(); + + LOG("wave test"); + test_wave(num_cycles, avg_allocs_per_space_manager, rgen, exercise_reclaim, exercise_dealloc); + + test_total_statistics(); + + } + + SpaceManagerTest(int num_spaces, size_t avg_occupancy, size_t max_commit_limit, const ChunkAllocSequence* alloc_sequence) + : _commit_limiter(max_commit_limit), _avg_occupancy(avg_occupancy), _vslist(NULL), _cm(NULL), + _alloc_sequence(alloc_sequence), _num_spaces(num_spaces), + _rss_at_start(0), _rss_at_end(0), _rss_after_cleanup(0), + _testbeds(NULL) + { + _rss_at_start = get_workingset_size(); + + // Allocate test bed array + _testbeds = NEW_C_HEAP_ARRAY(SpaceManagerTestBed*, _num_spaces, mtInternal); + for (int i = 0; i < _num_spaces; i ++) { + _testbeds[i] = NULL; + } + + // Create VirtualSpaceList and ChunkManager as backing memory + _vslist = new VirtualSpaceList("test_vs", &_commit_limiter); + + _cm = new ChunkManager("test_cm", _vslist); + + } + + ~SpaceManagerTest () { + + _rss_at_end = get_workingset_size(); + + // Is the memory footprint abnormal? This is necessarily very fuzzy. The memory footprint of these tests + // is dominated by all metaspace allocations done and the number of spaces, since the SpaceManagerTestBed + // - due to the fact that we track individual allocations - is rather big. + const size_t reasonable_expected_footprint = _avg_occupancy * BytesPerWord + + sizeof(SpaceManagerTestBed) * _num_spaces + + sizeof(SpaceManagerTestBed*) * _num_spaces + + sizeof(ChunkManager) + sizeof(VirtualSpaceList); + const size_t reasonable_expected_footprint_with_margin = + (reasonable_expected_footprint * 2) + 1 * M; + EXPECT_LE(_rss_at_end, _rss_at_start + reasonable_expected_footprint_with_margin); + + for (int i = 0; i < _num_spaces; i ++) { + delete _testbeds[i]; + } + + FREE_C_HEAP_ARRAY(SpaceManagerTestBed*, _testbeds); + + delete _cm; + delete _vslist; + + _rss_after_cleanup = get_workingset_size(); + + // Check for memory leaks. We should ideally be at the baseline of _rss_at_start. However, this depends + // on whether this gtest was executed as a first test in the suite, since gtest suite adds overhead of 2-4 MB. + EXPECT_LE(_rss_after_cleanup, _rss_at_start + 4 * M); + + LOG("rss at start: " INTX_FORMAT ", at end " INTX_FORMAT " (" INTX_FORMAT "), after cleanup: " INTX_FORMAT " (" INTX_FORMAT ").", \ + _rss_at_start, _rss_at_end, _rss_at_end - _rss_at_start, _rss_after_cleanup, _rss_after_cleanup - _rss_at_start); \ + + } + + + + void test_deallocation_in_place() { + + // When deallocating, it is attempted to deallocate in place, i.e. + // if the allocation is the most recent one, the current usage pointer + // in the current chunk is just reversed back to its original position + // before the original allocation. + // + // But in-place-deallocation will not reverse allocation of the + // current chunk itself if its usage pointer reaches 0 due to in-place + // deallocation! + // + // In theory, allocating n times, then deallocation in reverse order should + // happin in place and at the end the usage counter of the Space Manager should + // be at the original place. + // However, this is fragile, since when one of the allocations happens to + // cause the current chunk to be retired and a new one created, the chain + // breaks at that point (one cannot deallocate in-place from a non-current chunk). + // + // Therefore, to make this test reliable, we work on a new empty testbed - so + // we have a fresh chunk - and with miniscule allocation sizes, to not + // cause allocation beyond the smallest possible chunk size. That way we + // will never cause the initial chunk to be retired, regardless of how small it + // is. + + delete_testbed_at(0); + SpaceManagerTestBed* bed = testbed_at(0); // new bed + + const int num_loops = 5; + const size_t max_alloc_size = metaspace::chklvl::MIN_CHUNK_WORD_SIZE / num_loops * 2; + + // Doing this multiple times should work too as long as we keep the + // reverse allocation order. + sm_stats_t stat[10]; // taken before each allocation + size_t alloc_sizes[10] = { + max_alloc_size, + 1, 2, 3, // <- small sizes to have difference between raw size and net size + 0, 0, 0, 0, 0, 0 // <- use random values; + }; + + RandSizeGenerator rgen(1, max_alloc_size); + int i = 0; + while (i < 10) { + // take stats before allocating... + bed->sm()->add_to_statistics(stat + i); + check_sm_stat_is_consistent(stat[i]); + + // and allocate. + LOG("alloc round #%d (used: " SIZE_FORMAT ").", i, stat[i].totals().used_words); + const size_t alloc_size = alloc_sizes[i] > 0 ? alloc_sizes[i] : rgen.get(); + MetaWord* p = bed->allocate_and_test(alloc_size); + ASSERT_NOT_NULL(p); + i ++; + } + + // Now reverse-deallocate and compare used_words while doing so. + // (Note: used_words should be the same after deallocating as before the + // original allocation. All other stats cannot be relied on being the same.) + i --; + while (i >= 0) { + // dealloc, measure statistics after each deallocation and compare with + // the statistics taken at allocation time. + LOG("dealloc round #%d", i); + bed->deallocate_last(); + sm_stats_t stat_now; + bed->sm()->add_to_statistics(&stat_now); + check_sm_stat_is_consistent(stat_now); + ASSERT_EQ(stat_now.totals().used_words, + stat[i].totals().used_words); + i --; + } + + } // end: test_deallocation_in_place + +}; + +// for convenience, here some shorthands for standard alloc sequences. +static const ChunkAllocSequence* const g_standard_allocseq_class = + ChunkAllocSequence::alloc_sequence_by_space_type(metaspace::StandardMetaspaceType, true); +static const ChunkAllocSequence* const g_standard_allocseq_nonclass = + ChunkAllocSequence::alloc_sequence_by_space_type(metaspace::StandardMetaspaceType, false); +static const ChunkAllocSequence* const g_boot_allocseq_class = + ChunkAllocSequence::alloc_sequence_by_space_type(metaspace::BootMetaspaceType, true); +static const ChunkAllocSequence* const g_boot_allocseq_nonclass = + ChunkAllocSequence::alloc_sequence_by_space_type(metaspace::BootMetaspaceType, false); +static const ChunkAllocSequence* const g_refl_allocseq_class = + ChunkAllocSequence::alloc_sequence_by_space_type(metaspace::ReflectionMetaspaceType, true); +static const ChunkAllocSequence* const g_refl_allocseq_nonclass = + ChunkAllocSequence::alloc_sequence_by_space_type(metaspace::ReflectionMetaspaceType, false); + +// Some standard random size gens. + +// generates sizes between 1 and 128 words. +static RandSizeGenerator rgen_1K_no_outliers(1, 128); + +// generates sizes between 1 and 256 words, small chance of large outliers +static RandSizeGenerator rgen_1K_some_huge_outliers(1, 256, 0.05f, MAX_CHUNK_WORD_SIZE / 64, MAX_CHUNK_WORD_SIZE / 2); + +// generates medium sized sizes +static RandSizeGenerator rgen_32K_no_outliers(128, 0x4000); + +// large (and pretty unrealistic) spread +static RandSizeGenerator rgen_large_spread(1, MAX_CHUNK_WORD_SIZE); + + + +#define TEST_WITH_PARAMS(name, num_spaces, avg_occ, commit_limit, alloc_seq, rgen, exercise_reclaim, exercise_dealloc) \ +TEST_VM(metaspace, space_manager_test_##name) { \ + SpaceManagerTest stest(num_spaces, avg_occ, commit_limit, alloc_seq); \ + stest.run_test(1000, 50, &rgen, exercise_reclaim, exercise_dealloc); \ +} + +TEST_WITH_PARAMS(test0, 1, 64 * K, SIZE_MAX, g_standard_allocseq_nonclass, rgen_1K_no_outliers, true, false); + +TEST_WITH_PARAMS(test1, 10, 1 * M, SIZE_MAX, g_standard_allocseq_nonclass, rgen_1K_no_outliers, true, false); +TEST_WITH_PARAMS(test2, 10, 1 * M, SIZE_MAX, g_standard_allocseq_nonclass, rgen_1K_no_outliers, false, true); +TEST_WITH_PARAMS(test3, 10, 1 * M, SIZE_MAX, g_standard_allocseq_nonclass, rgen_1K_no_outliers, false, false); + +TEST_WITH_PARAMS(test4, 10, 1 * M, SIZE_MAX, g_boot_allocseq_nonclass, rgen_1K_no_outliers, true, false); +TEST_WITH_PARAMS(test5, 10, 1 * M, SIZE_MAX, g_boot_allocseq_nonclass, rgen_1K_no_outliers, false, false); + +TEST_WITH_PARAMS(test6, 10, 1 * M, SIZE_MAX, g_standard_allocseq_nonclass, rgen_1K_some_huge_outliers, true, false); +TEST_WITH_PARAMS(test7, 10, 1 * M, SIZE_MAX, g_standard_allocseq_nonclass, rgen_1K_some_huge_outliers, false, false); + +TEST_WITH_PARAMS(test8, 10, 1 * M, SIZE_MAX, g_standard_allocseq_nonclass, rgen_32K_no_outliers, true, false); +TEST_WITH_PARAMS(test9, 10, 1 * M, SIZE_MAX, g_standard_allocseq_nonclass, rgen_32K_no_outliers, false, false); + +TEST_WITH_PARAMS(test10, 10, 10 * M, 2 * M, g_standard_allocseq_nonclass, rgen_1K_some_huge_outliers, true, false); +TEST_WITH_PARAMS(test11, 10, 10 * M, 2 * M, g_standard_allocseq_nonclass, rgen_1K_some_huge_outliers, false, false); + +TEST_WITH_PARAMS(test12, 10, 10 * M, SIZE_MAX, g_standard_allocseq_nonclass, rgen_large_spread, true, false); +TEST_WITH_PARAMS(test13, 10, 10 * M, SIZE_MAX, g_standard_allocseq_nonclass, rgen_large_spread, false, false); + +TEST_WITH_PARAMS(test_14, 10, 1 * M, SIZE_MAX, g_standard_allocseq_nonclass, rgen_32K_no_outliers, true, true); +TEST_WITH_PARAMS(test_15, 10, 1 * M, SIZE_MAX, g_standard_allocseq_nonclass, rgen_large_spread, true, false); + + +TEST_VM(metaspace, space_manager_test_deallocation_in_place) { + // Test deallocation with no commit limit + SpaceManagerTest stest(1, 1 * M, 2 * M, g_boot_allocseq_class); + stest.test_deallocation_in_place(); +} + + + diff -r cea6839598e8 -r 595fcbebaa77 test/hotspot/gtest/metaspace/test_virtualspacenode.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/hotspot/gtest/metaspace/test_virtualspacenode.cpp Wed Sep 18 07:46:02 2019 +0200 @@ -0,0 +1,524 @@ +/* + * Copyright (c) 2019, SAP SE. All rights reserved. + * 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 "metaspace/metaspaceTestsCommon.hpp" + +static int test_node_id = 100000; // start high to make it stick out in logs. + + + +class VirtualSpaceNodeTest { + + // These counters are updated by the Node. + SizeCounter _counter_reserved_words; + SizeCounter _counter_committed_words; + CommitLimiter _commit_limiter; + VirtualSpaceNode* _node; + + // These are my checks and counters. + const size_t _vs_word_size; + const size_t _commit_limit; + + MetachunkList _root_chunks; + + void verify() const { + + ASSERT_EQ(_root_chunks.size() * metaspace::chklvl::MAX_CHUNK_WORD_SIZE, + _node->used_words()); + + ASSERT_GE(_commit_limit, _counter_committed_words.get()); + ASSERT_EQ(_commit_limiter.committed_words(), _counter_committed_words.get()); + + // Since we know _counter_committed_words serves our single node alone, the counter has to + // match the number of bits in the node internal commit mask. + ASSERT_EQ(_counter_committed_words.get(), _node->committed_words()); + + ASSERT_EQ(_counter_reserved_words.get(), _vs_word_size); + ASSERT_EQ(_counter_reserved_words.get(), _node->word_size()); + + } + + void lock_and_verify_node() { +#ifdef ASSERT + MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); + _node->verify(true); +#endif + } + + Metachunk* alloc_root_chunk() { + + verify(); + + const bool node_is_full = _node->used_words() == _node->word_size(); + bool may_hit_commit_limit = _commit_limiter.possible_expansion_words() < MAX_CHUNK_WORD_SIZE; + Metachunk* c = NULL; + { + MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); + c = _node->allocate_root_chunk(); + } + + lock_and_verify_node(); + + if (node_is_full) { + + EXPECT_NULL(c); + + } else { + + DEBUG_ONLY(c->verify(true);) + EXPECT_NOT_NULL(c); + EXPECT_TRUE(c->is_root_chunk()); + EXPECT_TRUE(c->is_free()); + EXPECT_EQ(c->word_size(), metaspace::chklvl::MAX_CHUNK_WORD_SIZE); + + if (!may_hit_commit_limit) { + if (Settings::newborn_root_chunks_are_fully_committed()) { + EXPECT_TRUE(c->is_fully_committed()); + } else { + EXPECT_TRUE(c->is_fully_uncommitted()); + } + } + + EXPECT_TRUE(_node->contains(c->base())); + + _root_chunks.add(c); + + } + + verify(); + + return c; + + } + + bool commit_root_chunk(Metachunk* c, size_t request_commit_words) { + + verify(); + + const size_t committed_words_before = _counter_committed_words.get(); + + bool rc = c->ensure_committed(request_commit_words); + + verify(); + DEBUG_ONLY(c->verify(true);) + + lock_and_verify_node(); + + if (rc == false) { + + // We must have hit the commit limit. + EXPECT_GE(committed_words_before + request_commit_words, _commit_limit); + + } else { + + // We should not have hit the commit limit. + EXPECT_LE(_counter_committed_words.get(), _commit_limit); + + // We do not know how much we really committed - maybe nothing if the + // chunk had been committed before - but we know the numbers should have + // risen or at least stayed equal. + EXPECT_GE(_counter_committed_words.get(), committed_words_before); + + // The chunk should be as far committed as was requested + EXPECT_GE(c->committed_words(), request_commit_words); + + // Zap committed portion. + DEBUG_ONLY(zap_range(c->base(), c->committed_words());) + + } + + verify(); + + return rc; + + } // commit_root_chunk + + void uncommit_chunk(Metachunk* c) { + + verify(); + + const size_t committed_words_before = _counter_committed_words.get(); + const size_t available_words_before = _commit_limiter.possible_expansion_words(); + + c->uncommit(); + + DEBUG_ONLY(c->verify(true);) + + lock_and_verify_node(); + + EXPECT_EQ(c->committed_words(), (size_t)0); + + // Commit counter should have gone down (by exactly the size of the chunk) if chunk + // is larger than a commit granule. + // For smaller chunks, we do not know, but at least we know the commit size should not + // have gone up. + if (c->word_size() >= Settings::commit_granule_words()) { + + EXPECT_EQ(_counter_committed_words.get(), committed_words_before - c->word_size()); + + // also, commit number in commit limiter should have gone down, so we have more space + EXPECT_EQ(_commit_limiter.possible_expansion_words(), + available_words_before + c->word_size()); + + } else { + + EXPECT_LE(_counter_committed_words.get(), committed_words_before); + + } + + verify(); + + } // uncommit_chunk + + Metachunk* split_chunk_with_checks(Metachunk* c, chklvl_t target_level, MetachunkListCluster* freelist) { + + DEBUG_ONLY(c->verify(true);) + + const chklvl_t orig_level = c->level(); + assert(orig_level < target_level, "Sanity"); + DEBUG_ONLY(metaspace::chklvl::check_valid_level(target_level);) + + const int total_num_chunks_in_freelist_before = freelist->total_num_chunks(); + const size_t total_word_size_in_freelist_before = freelist->total_word_size(); + + // freelist->print_on(tty); + + // Split... + Metachunk* result = NULL; + { + MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); + result = _node->split(target_level, c, freelist); + } + + // freelist->print_on(tty); + + EXPECT_NOT_NULL(result); + EXPECT_EQ(result->level(), target_level); + EXPECT_TRUE(result->is_free()); + + // ... check that we get the proper amount of splinters. For every chunk split we expect one + // buddy chunk to appear of level + 1 (aka, half size). + size_t expected_wordsize_increase = 0; + int expected_num_chunks_increase = 0; + for (chklvl_t l = orig_level + 1; l <= target_level; l ++) { + expected_wordsize_increase += metaspace::chklvl::word_size_for_level(l); + expected_num_chunks_increase ++; + } + + const int total_num_chunks_in_freelist_after = freelist->total_num_chunks(); + const size_t total_word_size_in_freelist_after = freelist->total_word_size(); + + EXPECT_EQ(total_num_chunks_in_freelist_after, total_num_chunks_in_freelist_before + expected_num_chunks_increase); + EXPECT_EQ(total_word_size_in_freelist_after, total_word_size_in_freelist_before + expected_wordsize_increase); + + return result; + + } // end: split_chunk_with_checks + + + Metachunk* merge_chunk_with_checks(Metachunk* c, chklvl_t expected_target_level, MetachunkListCluster* freelist) { + + const chklvl_t orig_level = c->level(); + assert(expected_target_level < orig_level, "Sanity"); + + const int total_num_chunks_in_freelist_before = freelist->total_num_chunks(); + const size_t total_word_size_in_freelist_before = freelist->total_word_size(); + + //freelist->print_on(tty); + + Metachunk* result = NULL; + { + MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); + result = _node->merge(c, freelist); + } + EXPECT_NOT_NULL(result); + EXPECT_TRUE(result->level() == expected_target_level); + + //freelist->print_on(tty); + + // ... check that we merged in the proper amount of chunks. For every decreased level + // of the original chunk (each size doubling) we should see one buddy chunk swallowed up. + size_t expected_wordsize_decrease = 0; + int expected_num_chunks_decrease = 0; + for (chklvl_t l = orig_level; l > expected_target_level; l --) { + expected_wordsize_decrease += metaspace::chklvl::word_size_for_level(l); + expected_num_chunks_decrease ++; + } + + const int total_num_chunks_in_freelist_after = freelist->total_num_chunks(); + const size_t total_word_size_in_freelist_after = freelist->total_word_size(); + + EXPECT_EQ(total_num_chunks_in_freelist_after, total_num_chunks_in_freelist_before - expected_num_chunks_decrease); + EXPECT_EQ(total_word_size_in_freelist_after, total_word_size_in_freelist_before - expected_wordsize_decrease); + + return result; + + } // end: merge_chunk_with_checks + +public: + + VirtualSpaceNodeTest(size_t vs_word_size, size_t commit_limit) + : _counter_reserved_words(), _counter_committed_words(), _commit_limiter(commit_limit), + _node(NULL), _vs_word_size(vs_word_size), _commit_limit(commit_limit) + { + { + MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); + _node = VirtualSpaceNode::create_node(test_node_id++, + vs_word_size, &_commit_limiter, + &_counter_reserved_words, &_counter_committed_words); + } + EXPECT_TRUE(_commit_limiter.possible_expansion_words() == _commit_limit); + verify(); + } + + ~VirtualSpaceNodeTest() { + delete _node; + } + + void test_simple() { + Metachunk* c = alloc_root_chunk(); + commit_root_chunk(c, Settings::commit_granule_words()); + commit_root_chunk(c, c->word_size()); + uncommit_chunk(c); + } + + void test_exhaust_node() { + Metachunk* c = NULL; + bool rc = true; + do { + c = alloc_root_chunk(); + if (c != NULL) { + rc = commit_root_chunk(c, c->word_size()); + } + } while (c != NULL && rc); + } + + void test_arbitrary_commits() { + + assert(_commit_limit >= _vs_word_size, "For this test no commit limit."); + + // Get a root chunk to have a committable region + Metachunk* c = alloc_root_chunk(); + ASSERT_NOT_NULL(c); + const address_range_t outer = { c->base(), c->word_size() }; + + if (c->committed_words() > 0) { + c->uncommit(); + } + + ASSERT_EQ(_node->committed_words(), (size_t)0); + ASSERT_EQ(_counter_committed_words.get(), (size_t)0); + + TestMap testmap(c->word_size()); + assert(testmap.get_num_set() == 0, "Sanity"); + + for (int run = 0; run < 1000; run ++) { + + const size_t committed_words_before = testmap.get_num_set(); + ASSERT_EQ(_commit_limiter.committed_words(), committed_words_before); + ASSERT_EQ(_counter_committed_words.get(), committed_words_before); + + range_t range; + calc_random_range(c->word_size(), &range, Settings::commit_granule_words()); + + const size_t committed_words_in_range_before = + testmap.get_num_set(range.from, range.to); + + if (os::random() % 100 < 50) { + + bool rc = false; + { + MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); + rc = _node->ensure_range_is_committed(c->base() + range.from, range.to - range.from); + } + + // Test-zap + zap_range(c->base() + range.from, range.to - range.from); + + // We should never reach commit limit since it is as large as the whole area. + ASSERT_TRUE(rc); + + testmap.set_range(range.from, range.to); + + } else { + + { + MutexLocker fcl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag); + _node->uncommit_range(c->base() + range.from, range.to - range.from); + } + + testmap.clear_range(range.from, range.to); + + } + + const size_t committed_words_after = testmap.get_num_set(); + + ASSERT_EQ(_commit_limiter.committed_words(), committed_words_after); + ASSERT_EQ(_counter_committed_words.get(), committed_words_after); + + verify(); + } + } + + // Helper function for test_splitting_chunks_1 + static void check_chunk_is_committed_at_least_up_to(const Metachunk* c, size_t& word_size) { + if (word_size >= c->word_size()) { + EXPECT_TRUE(c->is_fully_committed()); + word_size -= c->word_size(); + } else { + EXPECT_EQ(c->committed_words(), word_size); + word_size = 0; // clear remaining size if there is. + } + } + + void test_split_and_merge_chunks() { + + assert(_commit_limit >= _vs_word_size, "No commit limit here pls"); + + // Allocate a root chunk and commit a random part of it. Then repeatedly split + // it and merge it back together; observe the committed regions of the split chunks. + + Metachunk* c = alloc_root_chunk(); + + if (c->committed_words() > 0) { + c->uncommit(); + } + + // To capture split-off chunks. Note: it is okay to use this here as a temp object. + MetachunkListCluster freelist; + + const int granules_per_root_chunk = (int)(c->word_size() / Settings::commit_granule_words()); + + for (int granules_to_commit = 0; granules_to_commit < granules_per_root_chunk; granules_to_commit ++) { + + const size_t words_to_commit = Settings::commit_granule_words() * granules_to_commit; + + c->ensure_committed(words_to_commit); + + ASSERT_EQ(c->committed_words(), words_to_commit); + ASSERT_EQ(_counter_committed_words.get(), words_to_commit); + ASSERT_EQ(_commit_limiter.committed_words(), words_to_commit); + + const size_t committed_words_before = c->committed_words(); + + verify(); + + for (chklvl_t target_level = LOWEST_CHUNK_LEVEL + 1; + target_level <= HIGHEST_CHUNK_LEVEL; target_level ++) { + + // Split: + Metachunk* c2 = split_chunk_with_checks(c, target_level, &freelist); + c2->set_in_use(); + + // Split smallest leftover chunk. + if (c2->level() < HIGHEST_CHUNK_LEVEL) { + + Metachunk* c3 = freelist.remove_first(c2->level()); + ASSERT_NOT_NULL(c3); // Must exist since c2 must have a splinter buddy now. + + Metachunk* c4 = split_chunk_with_checks(c3, HIGHEST_CHUNK_LEVEL, &freelist); + c4->set_in_use(); + + // Merge it back. We expect this to merge up to c2's level, since c2 is in use. + c4->set_free(); + Metachunk* c5 = merge_chunk_with_checks(c4, c2->level(), &freelist); + ASSERT_NOT_NULL(c5); + freelist.add(c5); + + } + + // Merge c2 back. + c2->set_free(); + merge_chunk_with_checks(c2, LOWEST_CHUNK_LEVEL, &freelist); + + // After all this splitting and combining committed size should not have changed. + ASSERT_EQ(c2->committed_words(), committed_words_before); + + } + + } + + } // end: test_splitting_chunks + + + + +}; + +// Note: we unfortunately need TEST_VM even though the system tested +// should be pretty independent since we need things like os::vm_page_size() +// which in turn need OS layer initialization. +TEST_VM(metaspace, virtual_space_node_test_1) { + VirtualSpaceNodeTest test(metaspace::chklvl::MAX_CHUNK_WORD_SIZE, + metaspace::chklvl::MAX_CHUNK_WORD_SIZE); + test.test_simple(); +} + +TEST_VM(metaspace, virtual_space_node_test_2) { + // Should not hit commit limit + VirtualSpaceNodeTest test(3 * metaspace::chklvl::MAX_CHUNK_WORD_SIZE, + 3 * metaspace::chklvl::MAX_CHUNK_WORD_SIZE); + test.test_simple(); + test.test_exhaust_node(); +} + +TEST_VM(metaspace, virtual_space_node_test_3) { + // Should hit commit limit + VirtualSpaceNodeTest test(10 * metaspace::chklvl::MAX_CHUNK_WORD_SIZE, + 3 * metaspace::chklvl::MAX_CHUNK_WORD_SIZE); + test.test_exhaust_node(); +} + +TEST_VM(metaspace, virtual_space_node_test_4) { + // Test committing uncommitting arbitrary ranges + VirtualSpaceNodeTest test(metaspace::chklvl::MAX_CHUNK_WORD_SIZE, + metaspace::chklvl::MAX_CHUNK_WORD_SIZE); + test.test_arbitrary_commits(); +} + +TEST_VM(metaspace, virtual_space_node_test_5) { + // Test committing uncommitting arbitrary ranges + for (int run = 0; run < 100; run ++) { + VirtualSpaceNodeTest test(metaspace::chklvl::MAX_CHUNK_WORD_SIZE, + metaspace::chklvl::MAX_CHUNK_WORD_SIZE); + test.test_split_and_merge_chunks(); + } +} + +TEST_VM(metaspace, virtual_space_node_test_6) { + // Test large allocation and freeing. + { + VirtualSpaceNodeTest test(metaspace::chklvl::MAX_CHUNK_WORD_SIZE * 100, + metaspace::chklvl::MAX_CHUNK_WORD_SIZE * 100); + test.test_exhaust_node(); + } + { + VirtualSpaceNodeTest test(metaspace::chklvl::MAX_CHUNK_WORD_SIZE * 100, + metaspace::chklvl::MAX_CHUNK_WORD_SIZE * 100); + test.test_exhaust_node(); + } + +}