/* * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. * */ #ifndef SHARE_VM_SERVICES_MALLOC_TRACKER_HPP #define SHARE_VM_SERVICES_MALLOC_TRACKER_HPP #if INCLUDE_NMT #include "memory/allocation.hpp" #include "runtime/atomic.hpp" #include "runtime/threadCritical.hpp" #include "services/nmtCommon.hpp" #include "utilities/nativeCallStack.hpp" /* * This counter class counts memory allocation and deallocation, * records total memory allocation size and number of allocations. * The counters are updated atomically. */ class MemoryCounter VALUE_OBJ_CLASS_SPEC { private: volatile size_t _count; volatile size_t _size; DEBUG_ONLY(size_t _peak_count;) DEBUG_ONLY(size_t _peak_size; ) public: MemoryCounter() : _count(0), _size(0) { DEBUG_ONLY(_peak_count = 0;) DEBUG_ONLY(_peak_size = 0;) } inline void allocate(size_t sz) { Atomic::inc(&_count); if (sz > 0) { Atomic::add(sz, &_size); DEBUG_ONLY(_peak_size = MAX2(_peak_size, _size)); } DEBUG_ONLY(_peak_count = MAX2(_peak_count, _count);) } inline void deallocate(size_t sz) { assert(_count > 0, "Nothing allocated yet"); assert(_size >= sz, "deallocation > allocated"); Atomic::dec(&_count); if (sz > 0) { // unary minus operator applied to unsigned type, result still unsigned #pragma warning(suppress: 4146) Atomic::sub(sz, &_size); } } inline void resize(long sz) { if (sz != 0) { Atomic::add(size_t(sz), &_size); DEBUG_ONLY(_peak_size = MAX2(_size, _peak_size);) } } inline size_t count() const { return _count; } inline size_t size() const { return _size; } DEBUG_ONLY(inline size_t peak_count() const { return _peak_count; }) DEBUG_ONLY(inline size_t peak_size() const { return _peak_size; }) }; /* * Malloc memory used by a particular subsystem. * It includes the memory acquired through os::malloc() * call and arena's backing memory. */ class MallocMemory VALUE_OBJ_CLASS_SPEC { private: MemoryCounter _malloc; MemoryCounter _arena; public: MallocMemory() { } inline void record_malloc(size_t sz) { _malloc.allocate(sz); } inline void record_free(size_t sz) { _malloc.deallocate(sz); } inline void record_new_arena() { _arena.allocate(0); } inline void record_arena_free() { _arena.deallocate(0); } inline void record_arena_size_change(long sz) { _arena.resize(sz); } inline size_t malloc_size() const { return _malloc.size(); } inline size_t malloc_count() const { return _malloc.count();} inline size_t arena_size() const { return _arena.size(); } inline size_t arena_count() const { return _arena.count(); } DEBUG_ONLY(inline const MemoryCounter& malloc_counter() const { return _malloc; }) DEBUG_ONLY(inline const MemoryCounter& arena_counter() const { return _arena; }) }; class MallocMemorySummary; // A snapshot of malloc'd memory, includes malloc memory // usage by types and memory used by tracking itself. class MallocMemorySnapshot : public ResourceObj { friend class MallocMemorySummary; private: MallocMemory _malloc[mt_number_of_types]; MemoryCounter _tracking_header; public: inline MallocMemory* by_type(MEMFLAGS flags) { int index = NMTUtil::flag_to_index(flags); return &_malloc[index]; } inline MallocMemory* by_index(int index) { assert(index >= 0, "Index out of bound"); assert(index < mt_number_of_types, "Index out of bound"); return &_malloc[index]; } inline MemoryCounter* malloc_overhead() { return &_tracking_header; } // Total malloc'd memory amount size_t total() const; // Total malloc'd memory used by arenas size_t total_arena() const; inline size_t thread_count() const { MallocMemorySnapshot* s = const_cast(this); return s->by_type(mtThreadStack)->malloc_count(); } void copy_to(MallocMemorySnapshot* s) { // Need to make sure that mtChunks don't get deallocated while the // copy is going on, because their size is adjusted using this // buffer in make_adjustment(). ThreadCritical tc; s->_tracking_header = _tracking_header; for (int index = 0; index < mt_number_of_types; index ++) { s->_malloc[index] = _malloc[index]; } } // Make adjustment by subtracting chunks used by arenas // from total chunks to get total free chunk size void make_adjustment(); }; /* * This class is for collecting malloc statistics at summary level */ class MallocMemorySummary : AllStatic { private: // Reserve memory for placement of MallocMemorySnapshot object static size_t _snapshot[CALC_OBJ_SIZE_IN_TYPE(MallocMemorySnapshot, size_t)]; public: static void initialize(); static inline void record_malloc(size_t size, MEMFLAGS flag) { as_snapshot()->by_type(flag)->record_malloc(size); } static inline void record_free(size_t size, MEMFLAGS flag) { as_snapshot()->by_type(flag)->record_free(size); } static inline void record_new_arena(MEMFLAGS flag) { as_snapshot()->by_type(flag)->record_new_arena(); } static inline void record_arena_free(MEMFLAGS flag) { as_snapshot()->by_type(flag)->record_arena_free(); } static inline void record_arena_size_change(long size, MEMFLAGS flag) { as_snapshot()->by_type(flag)->record_arena_size_change(size); } static void snapshot(MallocMemorySnapshot* s) { as_snapshot()->copy_to(s); s->make_adjustment(); } // Record memory used by malloc tracking header static inline void record_new_malloc_header(size_t sz) { as_snapshot()->malloc_overhead()->allocate(sz); } static inline void record_free_malloc_header(size_t sz) { as_snapshot()->malloc_overhead()->deallocate(sz); } // The memory used by malloc tracking headers static inline size_t tracking_overhead() { return as_snapshot()->malloc_overhead()->size(); } static MallocMemorySnapshot* as_snapshot() { return (MallocMemorySnapshot*)_snapshot; } }; /* * Malloc tracking header. * To satisfy malloc alignment requirement, NMT uses 2 machine words for tracking purpose, * which ensures 8-bytes alignment on 32-bit systems and 16-bytes on 64-bit systems (Product build). */ class MallocHeader VALUE_OBJ_CLASS_SPEC { #ifdef _LP64 size_t _size : 64; size_t _flags : 8; size_t _pos_idx : 16; size_t _bucket_idx: 40; #define MAX_MALLOCSITE_TABLE_SIZE right_n_bits(40) #define MAX_BUCKET_LENGTH right_n_bits(16) #else size_t _size : 32; size_t _flags : 8; size_t _pos_idx : 8; size_t _bucket_idx: 16; #define MAX_MALLOCSITE_TABLE_SIZE right_n_bits(16) #define MAX_BUCKET_LENGTH right_n_bits(8) #endif // _LP64 public: MallocHeader(size_t size, MEMFLAGS flags, const NativeCallStack& stack, NMT_TrackingLevel level) { assert(sizeof(MallocHeader) == sizeof(void*) * 2, "Wrong header size"); if (level == NMT_minimal) { return; } _flags = flags; set_size(size); if (level == NMT_detail) { size_t bucket_idx; size_t pos_idx; if (record_malloc_site(stack, size, &bucket_idx, &pos_idx, flags)) { assert(bucket_idx <= MAX_MALLOCSITE_TABLE_SIZE, "Overflow bucket index"); assert(pos_idx <= MAX_BUCKET_LENGTH, "Overflow bucket position index"); _bucket_idx = bucket_idx; _pos_idx = pos_idx; } } MallocMemorySummary::record_malloc(size, flags); MallocMemorySummary::record_new_malloc_header(sizeof(MallocHeader)); } inline size_t size() const { return _size; } inline MEMFLAGS flags() const { return (MEMFLAGS)_flags; } bool get_stack(NativeCallStack& stack) const; // Cleanup tracking information before the memory is released. void release() const; private: inline void set_size(size_t size) { _size = size; } bool record_malloc_site(const NativeCallStack& stack, size_t size, size_t* bucket_idx, size_t* pos_idx, MEMFLAGS flags) const; }; // Main class called from MemTracker to track malloc activities class MallocTracker : AllStatic { public: // Initialize malloc tracker for specific tracking level static bool initialize(NMT_TrackingLevel level); static bool transition(NMT_TrackingLevel from, NMT_TrackingLevel to); // malloc tracking header size for specific tracking level static inline size_t malloc_header_size(NMT_TrackingLevel level) { return (level == NMT_off) ? 0 : sizeof(MallocHeader); } // Parameter name convention: // memblock : the beginning address for user data // malloc_base: the beginning address that includes malloc tracking header // // The relationship: // memblock = (char*)malloc_base + sizeof(nmt header) // // Record malloc on specified memory block static void* record_malloc(void* malloc_base, size_t size, MEMFLAGS flags, const NativeCallStack& stack, NMT_TrackingLevel level); // Record free on specified memory block static void* record_free(void* memblock); // Offset memory address to header address static inline void* get_base(void* memblock); static inline void* get_base(void* memblock, NMT_TrackingLevel level) { if (memblock == NULL || level == NMT_off) return memblock; return (char*)memblock - malloc_header_size(level); } // Get memory size static inline size_t get_size(void* memblock) { MallocHeader* header = malloc_header(memblock); return header->size(); } // Get memory type static inline MEMFLAGS get_flags(void* memblock) { MallocHeader* header = malloc_header(memblock); return header->flags(); } // Get header size static inline size_t get_header_size(void* memblock) { return (memblock == NULL) ? 0 : sizeof(MallocHeader); } static inline void record_new_arena(MEMFLAGS flags) { MallocMemorySummary::record_new_arena(flags); } static inline void record_arena_free(MEMFLAGS flags) { MallocMemorySummary::record_arena_free(flags); } static inline void record_arena_size_change(int size, MEMFLAGS flags) { MallocMemorySummary::record_arena_size_change(size, flags); } private: static inline MallocHeader* malloc_header(void *memblock) { assert(memblock != NULL, "NULL pointer"); MallocHeader* header = (MallocHeader*)((char*)memblock - sizeof(MallocHeader)); return header; } }; #endif // INCLUDE_NMT #endif //SHARE_VM_SERVICES_MALLOC_TRACKER_HPP