8296784: Provide clean mallinfo/mallinfo2 wrapper for Linux glibc platforms

Reviewed-by: rkennke, mbaesken
This commit is contained in:
Thomas Stuefe 2022-11-19 18:06:48 +00:00
parent 7b3d581497
commit 0a3b0fc8ad
3 changed files with 141 additions and 59 deletions

View File

@ -169,8 +169,37 @@ const char * os::Linux::_libpthread_version = NULL;
size_t os::Linux::_default_large_page_size = 0;
#ifdef __GLIBC__
os::Linux::mallinfo_func_t os::Linux::_mallinfo = NULL;
os::Linux::mallinfo2_func_t os::Linux::_mallinfo2 = NULL;
// We want to be buildable and runnable on older and newer glibcs, so resolve both
// mallinfo and mallinfo2 dynamically.
struct old_mallinfo {
int arena;
int ordblks;
int smblks;
int hblks;
int hblkhd;
int usmblks;
int fsmblks;
int uordblks;
int fordblks;
int keepcost;
};
typedef struct old_mallinfo (*mallinfo_func_t)(void);
static mallinfo_func_t g_mallinfo = NULL;
struct new_mallinfo {
size_t arena;
size_t ordblks;
size_t smblks;
size_t hblks;
size_t hblkhd;
size_t usmblks;
size_t fsmblks;
size_t uordblks;
size_t fordblks;
size_t keepcost;
};
typedef struct new_mallinfo (*mallinfo2_func_t)(void);
static mallinfo2_func_t g_mallinfo2 = NULL;
#endif // __GLIBC__
static int clock_tics_per_sec = 100;
@ -2102,26 +2131,17 @@ void os::Linux::print_process_memory_info(outputStream* st) {
size_t total_allocated = 0;
size_t free_retained = 0;
bool might_have_wrapped = false;
if (_mallinfo2 != NULL) {
struct glibc_mallinfo2 mi = _mallinfo2();
total_allocated = mi.uordblks + mi.hblkhd;
free_retained = mi.fordblks;
} else if (_mallinfo != NULL) {
// mallinfo is an old API. Member names mean next to nothing and, beyond that, are 32-bit signed.
// So for larger footprints the values may have wrapped around. We try to detect this here: if the
// process whole resident set size is smaller than 4G, malloc footprint has to be less than that
// and the numbers are reliable.
struct glibc_mallinfo mi = _mallinfo();
total_allocated = (size_t)(unsigned)mi.uordblks + (size_t)(unsigned)mi.hblkhd;
free_retained = (size_t)(unsigned)mi.fordblks;
// Since mallinfo members are int, glibc values may have wrapped. Warn about this.
might_have_wrapped = (info.vmrss * K) > UINT_MAX && (info.vmrss * K) > (total_allocated + UINT_MAX);
}
if (_mallinfo2 != NULL || _mallinfo != NULL) {
st->print_cr("C-Heap outstanding allocations: " SIZE_FORMAT "K, retained: " SIZE_FORMAT "K%s",
total_allocated / K, free_retained / K,
might_have_wrapped ? " (may have wrapped)" : "");
}
glibc_mallinfo mi;
os::Linux::get_mallinfo(&mi, &might_have_wrapped);
total_allocated = mi.uordblks + mi.hblkhd;
free_retained = mi.fordblks;
#ifdef _LP64
// If legacy mallinfo(), we can still print the values if we are sure they cannot have wrapped.
might_have_wrapped = might_have_wrapped && (info.vmsize * K) > UINT_MAX;
#endif
st->print_cr("C-Heap outstanding allocations: " SIZE_FORMAT "K, retained: " SIZE_FORMAT "K%s",
total_allocated / K, free_retained / K,
might_have_wrapped ? " (may have wrapped)" : "");
// Tunables
print_glibc_malloc_tunables(st);
st->cr();
@ -4273,8 +4293,8 @@ void os::init(void) {
Linux::initialize_system_info();
#ifdef __GLIBC__
Linux::_mallinfo = CAST_TO_FN_PTR(Linux::mallinfo_func_t, dlsym(RTLD_DEFAULT, "mallinfo"));
Linux::_mallinfo2 = CAST_TO_FN_PTR(Linux::mallinfo2_func_t, dlsym(RTLD_DEFAULT, "mallinfo2"));
g_mallinfo = CAST_TO_FN_PTR(mallinfo_func_t, dlsym(RTLD_DEFAULT, "mallinfo"));
g_mallinfo2 = CAST_TO_FN_PTR(mallinfo2_func_t, dlsym(RTLD_DEFAULT, "mallinfo2"));
#endif // __GLIBC__
os::Linux::CPUPerfTicks pticks;
@ -5332,6 +5352,42 @@ void os::print_memory_mappings(char* addr, size_t bytes, outputStream* st) {
}
}
#ifdef __GLIBC__
void os::Linux::get_mallinfo(glibc_mallinfo* out, bool* might_have_wrapped) {
if (g_mallinfo2) {
new_mallinfo mi = g_mallinfo2();
out->arena = mi.arena;
out->ordblks = mi.ordblks;
out->smblks = mi.smblks;
out->hblks = mi.hblks;
out->hblkhd = mi.hblkhd;
out->usmblks = mi.usmblks;
out->fsmblks = mi.fsmblks;
out->uordblks = mi.uordblks;
out->fordblks = mi.fordblks;
out->keepcost = mi.keepcost;
*might_have_wrapped = false;
} else if (g_mallinfo) {
old_mallinfo mi = g_mallinfo();
// glibc reports unsigned 32-bit sizes in int form. First make unsigned, then extend.
out->arena = (size_t)(unsigned)mi.arena;
out->ordblks = (size_t)(unsigned)mi.ordblks;
out->smblks = (size_t)(unsigned)mi.smblks;
out->hblks = (size_t)(unsigned)mi.hblks;
out->hblkhd = (size_t)(unsigned)mi.hblkhd;
out->usmblks = (size_t)(unsigned)mi.usmblks;
out->fsmblks = (size_t)(unsigned)mi.fsmblks;
out->uordblks = (size_t)(unsigned)mi.uordblks;
out->fordblks = (size_t)(unsigned)mi.fordblks;
out->keepcost = (size_t)(unsigned)mi.keepcost;
*might_have_wrapped = NOT_LP64(false) LP64_ONLY(true);
} else {
// We should have either mallinfo or mallinfo2
ShouldNotReachHere();
}
}
#endif // __GLIBC__
bool os::trim_native_heap(os::size_change_t* rss_change) {
#ifdef __GLIBC__
os::Linux::meminfo_t info1;

View File

@ -172,7 +172,7 @@ class os::Linux {
// Return the namespace pid if so, otherwise -1.
static int get_namespace_pid(int vmid);
// Output structure for query_process_memory_info()
// Output structure for query_process_memory_info() (all values in KB)
struct meminfo_t {
ssize_t vmsize; // current virtual size
ssize_t vmpeak; // peak virtual size
@ -265,40 +265,6 @@ class os::Linux {
};
static NumaAllocationPolicy _current_numa_policy;
#ifdef __GLIBC__
struct glibc_mallinfo {
int arena;
int ordblks;
int smblks;
int hblks;
int hblkhd;
int usmblks;
int fsmblks;
int uordblks;
int fordblks;
int keepcost;
};
struct glibc_mallinfo2 {
size_t arena;
size_t ordblks;
size_t smblks;
size_t hblks;
size_t hblkhd;
size_t usmblks;
size_t fsmblks;
size_t uordblks;
size_t fordblks;
size_t keepcost;
};
typedef struct glibc_mallinfo (*mallinfo_func_t)(void);
typedef struct glibc_mallinfo2 (*mallinfo2_func_t)(void);
static mallinfo_func_t _mallinfo;
static mallinfo2_func_t _mallinfo2;
#endif
public:
static int sched_getcpu() { return _sched_getcpu != NULL ? _sched_getcpu() : -1; }
static int numa_node_to_cpus(int node, unsigned long *buffer, int bufferlen);
@ -426,6 +392,39 @@ class os::Linux {
}
static void* resolve_function_descriptor(void* p);
#ifdef __GLIBC__
// os::Linux::get_mallinfo() hides the complexity of dealing with mallinfo() or
// mallinfo2() from the user. Use this function instead of raw mallinfo/mallinfo2()
// to keep the JVM runtime-compatible with different glibc versions.
//
// mallinfo2() was added with glibc (>2.32). Legacy mallinfo() was deprecated with
// 2.33 and may vanish in future glibcs. So we may have both or either one of
// them.
//
// mallinfo2() is functionally equivalent to legacy mallinfo but returns sizes as
// 64-bit on 64-bit platforms. Legacy mallinfo uses 32-bit fields. However, legacy
// mallinfo is still perfectly fine to use if we know the sizes cannot have wrapped.
// For example, if the process virtual size does not exceed 4G, we cannot have
// malloc'ed more than 4G, so the results from legacy mallinfo() can still be used.
//
// os::Linux::get_mallinfo() will always prefer mallinfo2() if found, but will fall back
// to legacy mallinfo() if only that is available. In that case, it will return true
// in *might_have_wrapped.
struct glibc_mallinfo {
size_t arena;
size_t ordblks;
size_t smblks;
size_t hblks;
size_t hblkhd;
size_t usmblks;
size_t fsmblks;
size_t uordblks;
size_t fordblks;
size_t keepcost;
};
static void get_mallinfo(glibc_mallinfo* out, bool* might_have_wrapped);
#endif // GLIBC
};
#endif // OS_LINUX_OS_LINUX_HPP

View File

@ -35,6 +35,9 @@
#include "utilities/align.hpp"
#include "utilities/decoder.hpp"
#include "testutils.hpp"
#include "unittest.hpp"
namespace {
static void small_page_write(void* addr, size_t size) {
size_t page_size = os::vm_page_size();
@ -476,4 +479,28 @@ TEST_VM(os_linux, decoder_get_source_info_invalid) {
}
}
#endif // NOT PRODUCT
#ifdef __GLIBC__
TEST_VM(os_linux, glibc_mallinfo_wrapper) {
// Very basic test. Call it. That proves that resolution and invocation works.
os::Linux::glibc_mallinfo mi;
bool did_wrap = false;
os::Linux::get_mallinfo(&mi, &did_wrap);
void* p = os::malloc(2 * K, mtTest);
ASSERT_NOT_NULL(p);
// We should see total allocation values > 0
ASSERT_GE((mi.uordblks + mi.hblkhd), 2 * K);
// These values also should exceed some reasonable size.
ASSERT_LT(mi.fordblks, 2 * G);
ASSERT_LT(mi.uordblks, 2 * G);
ASSERT_LT(mi.hblkhd, 2 * G);
os::free(p);
}
#endif // __GLIBC__
#endif // LINUX