From da777e57e0d4625e13f630321fe0defd439f8d61 Mon Sep 17 00:00:00 2001 From: Maoni Stephens Date: Wed, 2 Mar 2016 12:23:49 -0800 Subject: [PATCH] Integrate GC changes from full framework [tfs-changeset: 1580785] --- src/gc/env/gcenv.os.h | 18 +++++ src/gc/gc.cpp | 163 ++++++++++++++++++++++++++++--------- src/gc/gcpriv.h | 14 +++- src/gc/sample/gcenv.windows.cpp | 20 +++++ src/vm/gcenv.os.cpp | 174 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 352 insertions(+), 37 deletions(-) diff --git a/src/gc/env/gcenv.os.h b/src/gc/env/gcenv.os.h index 68646e1..0ef979c 100644 --- a/src/gc/env/gcenv.os.h +++ b/src/gc/env/gcenv.os.h @@ -224,6 +224,24 @@ public: static bool GetCurrentProcessAffinityMask(uintptr_t *processMask, uintptr_t *systemMask); // + // Support for acting on memory limit imposed on this process, eg, running in a job object on Windows. + // + + // If the process's memory is restricted (ie, beyond what's available on the machine), return that limit. + // Return: + // non zero if it has succeeded, 0 if it has failed + // Remarks: + // If a process runs with a restricted memory limit, and we are successful at getting + // that limit, it returns the limit. If there's no limit specified, or there's an error + // at getting that limit, it returns 0. + static uint64_t GetRestrictedPhysicalMemoryLimit(); + + // Get the current physical memory this process is using. + // Return: + // non zero if it has succeeded, 0 if it has failed + static size_t GetCurrentPhysicalMemory(); + + // // Misc // diff --git a/src/gc/gc.cpp b/src/gc/gc.cpp index 81e1572..d039947 100644 --- a/src/gc/gc.cpp +++ b/src/gc/gc.cpp @@ -2307,7 +2307,9 @@ uint32_t gc_heap::high_memory_load_th; uint64_t gc_heap::total_physical_mem; -uint64_t gc_heap::available_physical_mem; +uint64_t gc_heap::entry_available_physical_mem; + +bool gc_heap::restricted_physical_memory_p = false; #ifdef BACKGROUND_GC CLREvent gc_heap::bgc_start_event; @@ -11214,6 +11216,8 @@ size_t gc_heap::limit_from_size (size_t size, size_t room, int gen_number, void gc_heap::handle_oom (int heap_num, oom_reason reason, size_t alloc_size, uint8_t* allocated, uint8_t* reserved) { + dprintf (1, ("total committed on the heap is %Id", get_total_committed_size())); + UNREFERENCED_PARAMETER(heap_num); if (reason == oom_budget) @@ -11924,10 +11928,9 @@ void gc_heap::wait_for_bgc_high_memory (alloc_wait_reason awr) { if (recursive_gc_sync::background_running_p()) { - GCMemoryStatus ms; - memset (&ms, 0, sizeof(ms)); - GCToOSInterface::GetMemoryStatus(&ms); - if (ms.dwMemoryLoad >= 95) + uint32_t memory_load; + get_memory_info (&memory_load); + if (memory_load >= 95) { dprintf (GTC_LOG, ("high mem - wait for BGC to finish, wait reason: %d", awr)); wait_for_background (awr); @@ -14348,9 +14351,10 @@ int gc_heap::generation_to_condemn (int n_initial, } int i = 0; int temp_gen = 0; - GCMemoryStatus ms; - memset (&ms, 0, sizeof(ms)); BOOL low_memory_detected = g_low_memory_status; + uint32_t memory_load = 0; + uint64_t available_physical = 0; + uint64_t available_page_file = 0; BOOL check_memory = FALSE; BOOL high_fragmentation = FALSE; BOOL v_high_memory_load = FALSE; @@ -14567,31 +14571,30 @@ int gc_heap::generation_to_condemn (int n_initial, if (check_memory) { //find out if we are short on memory - GCToOSInterface::GetMemoryStatus(&ms); + get_memory_info (&memory_load, &available_physical, &available_page_file); if (heap_number == 0) { - dprintf (GTC_LOG, ("ml: %d", ms.dwMemoryLoad)); + dprintf (GTC_LOG, ("ml: %d", memory_load)); } - + // Need to get it early enough for all heaps to use. - available_physical_mem = ms.ullAvailPhys; - local_settings->entry_memory_load = ms.dwMemoryLoad; + entry_available_physical_mem = available_physical; + local_settings->entry_memory_load = memory_load; // @TODO: Force compaction more often under GCSTRESS - if (ms.dwMemoryLoad >= high_memory_load_th || low_memory_detected) + if (memory_load >= high_memory_load_th || low_memory_detected) { #ifdef SIMPLE_DPRINTF // stress log can't handle any parameter that's bigger than a void*. if (heap_number == 0) { - dprintf (GTC_LOG, ("tp: %I64d, ap: %I64d, tp: %I64d, ap: %I64d", - ms.ullTotalPhys, ms.ullAvailPhys, ms.ullTotalPageFile, ms.ullAvailPageFile)); + dprintf (GTC_LOG, ("tp: %I64d, ap: %I64d", total_physical_mem, available_physical)); } #endif //SIMPLE_DPRINTF high_memory_load = TRUE; - if (ms.dwMemoryLoad >= v_high_memory_load_th || low_memory_detected) + if (memory_load >= v_high_memory_load_th || low_memory_detected) { // TODO: Perhaps in 64-bit we should be estimating gen1's fragmentation as well since // gen1/gen0 may take a lot more memory than gen2. @@ -14605,7 +14608,7 @@ int gc_heap::generation_to_condemn (int n_initial, { if (!high_fragmentation) { - high_fragmentation = dt_estimate_high_frag_p (tuning_deciding_condemned_gen, max_generation, ms.ullAvailPhys); + high_fragmentation = dt_estimate_high_frag_p (tuning_deciding_condemned_gen, max_generation, available_physical); } } @@ -14815,7 +14818,7 @@ exit: if (check_memory) { - fgm_result.available_pagefile_mb = (size_t)(ms.ullAvailPageFile / (1024 * 1024)); + fgm_result.available_pagefile_mb = (size_t)(available_page_file / (1024 * 1024)); } local_condemn_reasons->set_gen (gen_final_per_heap, n); @@ -17879,9 +17882,9 @@ gc_heap::ha_mark_object_simple (uint8_t** po THREAD_NUMBER_DCL) { size_t new_size = 2*internal_root_array_length; - GCMemoryStatus statex; - GCToOSInterface::GetMemoryStatus(&statex); - if (new_size > (size_t)(statex.ullAvailPhys / 10)) + uint64_t available_physical = 0; + get_memory_info (NULL, &available_physical); + if (new_size > (size_t)(available_physical / 10)) { heap_analyze_success = FALSE; } @@ -18857,6 +18860,84 @@ size_t gc_heap::get_total_heap_size() return total_heap_size; } +size_t gc_heap::committed_size() +{ + generation* gen = generation_of (max_generation); + heap_segment* seg = heap_segment_rw (generation_start_segment (gen)); + size_t total_committed = 0; + + while (1) + { + total_committed += heap_segment_committed (seg) - (uint8_t*)seg; + + seg = heap_segment_next (seg); + if (!seg) + { + if (gen != large_object_generation) + { + gen = generation_of (max_generation + 1); + seg = generation_start_segment (gen); + } + else + break; + } + } + + return total_committed; +} + +size_t gc_heap::get_total_committed_size() +{ + size_t total_committed = 0; + +#ifdef MULTIPLE_HEAPS + int hn = 0; + + for (hn = 0; hn < gc_heap::n_heaps; hn++) + { + gc_heap* hp = gc_heap::g_heaps [hn]; + total_committed += hp->committed_size(); + } +#else + total_committed = committed_size(); +#endif //MULTIPLE_HEAPS + + return total_committed; +} + +void gc_heap::get_memory_info (uint32_t* memory_load, + uint64_t* available_physical, + uint64_t* available_page_file) +{ + if (restricted_physical_memory_p) + { + size_t working_set_size = GCToOSInterface::GetCurrentPhysicalMemory(); + if (working_set_size) + { + if (memory_load) + *memory_load = (uint32_t)((float)working_set_size * 100.0 / (float)total_physical_mem); + if (available_physical) + *available_physical = total_physical_mem - working_set_size; + // Available page file doesn't mean much when physical memory is restricted since + // we don't know how much of it is available to this process so we are not going to + // bother to make another OS call for it. + if (available_page_file) + *available_page_file = 0; + + return; + } + } + + GCMemoryStatus ms; + GCToOSInterface::GetMemoryStatus(&ms); + if (memory_load) + *memory_load = ms.dwMemoryLoad; + if (available_physical) + *available_physical = ms.ullAvailPhys; + if (available_page_file) + *available_page_file = ms.ullAvailPageFile; +} + void fire_mark_event (int heap_num, int root_type, size_t bytes_marked) { dprintf (DT_LOG_0, ("-----------[%d]mark %d: %Id", heap_num, root_type, bytes_marked)); @@ -29519,14 +29600,12 @@ size_t gc_heap::desired_new_allocation (dynamic_data* dd, } else //large object heap { - GCMemoryStatus ms; - GCToOSInterface::GetMemoryStatus (&ms); - uint64_t available_ram = ms.ullAvailPhys; + uint64_t available_physical = 0; + get_memory_info (NULL, &available_physical); + if (available_physical > 1024*1024) + available_physical -= 1024*1024; - if (ms.ullAvailPhys > 1024*1024) - available_ram -= 1024*1024; - - uint64_t available_free = available_ram + (uint64_t)generation_free_list_space (generation_of (gen_number)); + uint64_t available_free = available_physical + (uint64_t)generation_free_list_space (generation_of (gen_number)); if (available_free > (uint64_t)MAX_PTR) { available_free = (uint64_t)MAX_PTR; @@ -29754,14 +29833,12 @@ size_t gc_heap::joined_youngest_desired (size_t new_allocation) if ((settings.entry_memory_load >= MAX_ALLOWED_MEM_LOAD) || (total_new_allocation > max (youngest_gen_desired_th, total_min_allocation))) { - uint32_t dwMemoryLoad = 0; - GCMemoryStatus ms; - GCToOSInterface::GetMemoryStatus(&ms); - dprintf (2, ("Current memory load: %d", ms.dwMemoryLoad)); - dwMemoryLoad = ms.dwMemoryLoad; + uint32_t memory_load = 0; + get_memory_info (&memory_load); + dprintf (2, ("Current emory load: %d", memory_load)); size_t final_total = - trim_youngest_desired (dwMemoryLoad, total_new_allocation, total_min_allocation); + trim_youngest_desired (memory_load, total_new_allocation, total_min_allocation); size_t max_new_allocation = #ifdef MULTIPLE_HEAPS dd_max_size (g_heaps[0]->dynamic_data_of (0)); @@ -30190,7 +30267,7 @@ BOOL gc_heap::decide_on_compacting (int condemned_gen_number, ptrdiff_t reclaim_space = generation_size(max_generation) - generation_plan_size(max_generation); if((settings.entry_memory_load >= high_memory_load_th) && (settings.entry_memory_load < v_high_memory_load_th)) { - if(reclaim_space > (int64_t)(min_high_fragmentation_threshold (available_physical_mem, num_heaps))) + if(reclaim_space > (int64_t)(min_high_fragmentation_threshold (entry_available_physical_mem, num_heaps))) { dprintf(GTC_LOG,("compacting due to fragmentation in high memory")); should_compact = TRUE; @@ -32070,7 +32147,7 @@ void gc_heap::descr_generations (BOOL begin_gc_p) if (heap_number == 0) { - dprintf (1, ("soh size: %Id", get_total_heap_size())); + dprintf (1, ("total heap size: %Id, commit size: %Id", get_total_heap_size(), get_total_committed_size())); } int curr_gen_number = max_generation+1; @@ -33428,9 +33505,23 @@ HRESULT GCHeap::Initialize () if (hr != S_OK) return hr; + // If there's a physical memory limit set on this process, it needs to be checked very early on, + // before we set various memory info. + uint64_t physical_memory_limit = GCToOSInterface::GetRestrictedPhysicalMemoryLimit(); + + if (physical_memory_limit) + gc_heap::restricted_physical_memory_p = true; + GCMemoryStatus ms; GCToOSInterface::GetMemoryStatus (&ms); gc_heap::total_physical_mem = ms.ullTotalPhys; + + if (gc_heap::restricted_physical_memory_p) + { + // A sanity check in case someone set a larger limit than there is actual physical memory. + gc_heap::total_physical_mem = min (gc_heap::total_physical_mem, physical_memory_limit); + } + gc_heap::mem_one_percent = gc_heap::total_physical_mem / 100; #ifndef MULTIPLE_HEAPS gc_heap::mem_one_percent /= g_SystemInfo.dwNumberOfProcessors; diff --git a/src/gc/gcpriv.h b/src/gc/gcpriv.h index d00c3b8..cf7d066 100644 --- a/src/gc/gcpriv.h +++ b/src/gc/gcpriv.h @@ -2477,6 +2477,13 @@ protected: #endif // BIT64 PER_HEAP_ISOLATED size_t get_total_heap_size (); + PER_HEAP_ISOLATED + size_t get_total_committed_size(); + + PER_HEAP_ISOLATED + void get_memory_info (uint32_t* memory_load, + uint64_t* available_physical=NULL, + uint64_t* available_page_file=NULL); PER_HEAP size_t generation_size (int gen_number); PER_HEAP_ISOLATED @@ -2506,6 +2513,8 @@ protected: PER_HEAP size_t generation_sizes (generation* gen); PER_HEAP + size_t committed_size(); + PER_HEAP size_t approximate_new_allocation(); PER_HEAP size_t end_space_after_gc(); @@ -2940,7 +2949,10 @@ public: uint64_t total_physical_mem; PER_HEAP_ISOLATED - uint64_t available_physical_mem; + uint64_t entry_available_physical_mem; + + PER_HEAP_ISOLATED + bool restricted_physical_memory_p; PER_HEAP_ISOLATED size_t last_gc_index; diff --git a/src/gc/sample/gcenv.windows.cpp b/src/gc/sample/gcenv.windows.cpp index 62e8064..04d5ac3 100644 --- a/src/gc/sample/gcenv.windows.cpp +++ b/src/gc/sample/gcenv.windows.cpp @@ -282,6 +282,26 @@ uint32_t GCToOSInterface::GetCurrentProcessCpuCount() return g_SystemInfo.dwNumberOfProcessors; } +// If the process's memory is restricted (ie, beyond what's available on the machine), return that limit. +// Return: +// non zero if it has succeeded, 0 if it has failed +// Remarks: +// If a process runs with a restricted memory limit, and we are successful at getting +// that limit, it returns the limit. If there's no limit specified, or there's an error +// at getting that limit, it returns 0. +uint64_t GCToOSInterface::GetRestrictedPhysicalMemoryLimit() +{ + return 0; +} + +// Get the current physical memory this process is using. +// Return: +// non zero if it has succeeded, 0 if it has failed +size_t GCToOSInterface::GetCurrentPhysicalMemory() +{ + return 0; +} + // Get global memory status // Parameters: // ms - pointer to the structure that will be filled in with the memory status diff --git a/src/vm/gcenv.os.cpp b/src/vm/gcenv.os.cpp index 051508e..9555072 100644 --- a/src/vm/gcenv.os.cpp +++ b/src/vm/gcenv.os.cpp @@ -14,6 +14,12 @@ #include "common.h" #include "gcenv.h" +#ifndef FEATURE_PAL +#include +#endif + +#define MAX_PTR ((uint8_t*)(~(ptrdiff_t)0)) + // Initialize the interface implementation // Return: // true if it has succeeded, false if it has failed @@ -334,6 +340,174 @@ uint32_t GCToOSInterface::GetCurrentProcessCpuCount() return ::GetCurrentProcessCpuCount(); } +#ifndef FEATURE_PAL +typedef BOOL (WINAPI *PGET_PROCESS_MEMORY_INFO)(HANDLE handle, PROCESS_MEMORY_COUNTERS* memCounters, uint32_t cb); +static PGET_PROCESS_MEMORY_INFO GCGetProcessMemoryInfo = 0; + +typedef BOOL (WINAPI *PIS_PROCESS_IN_JOB)(HANDLE processHandle, HANDLE jobHandle, BOOL* result); +typedef BOOL (WINAPI *PQUERY_INFORMATION_JOB_OBJECT)(HANDLE jobHandle, JOBOBJECTINFOCLASS jobObjectInfoClass, void* lpJobObjectInfo, DWORD cbJobObjectInfoLength, LPDWORD lpReturnLength); +#endif + +#if defined(FEATURE_CORECLR) && !defined(FEATURE_PAL) +// For coresys we need to look for an API in some apiset dll on win8 if we can't find it +// in the traditional dll. +HINSTANCE LoadDllForAPI(WCHAR* dllTraditional, WCHAR* dllApiSet) +{ + HINSTANCE hinst = WszLoadLibrary(dllTraditional); + + if (!hinst) + { + if(RunningOnWin8()) + hinst = WszLoadLibrary(dllApiSet); + } + + return hinst; +} +#endif + +// If the process's memory is restricted (ie, beyond what's available on the machine), return that limit. +// Return: +// 0 if it has failed for some reason, the real value if it has succeeded +// Remarks: +// If a process runs with a restricted memory limit, and we are successful at getting +// that limit, it returns the limit. If there's no limit specified, or there's an error +// at getting that limit, it returns 0. +uint64_t GCToOSInterface::GetRestrictedPhysicalMemoryLimit() +{ +#ifdef FEATURE_PAL + return 0; +#else + size_t job_physical_memory_limit = (size_t)MAX_PTR; + BOOL in_job_p = FALSE; +#ifdef FEATURE_CORECLR + HINSTANCE hinstApiSetPsapiOrKernel32 = 0; + // these 2 modules will need to be freed no matter what as we only use them locally in this method. + HINSTANCE hinstApiSetJob1OrKernel32 = 0; + HINSTANCE hinstApiSetJob2OrKernel32 = 0; +#else + HINSTANCE hinstPsapi = 0; +#endif + + PIS_PROCESS_IN_JOB GCIsProcessInJob = 0; + PQUERY_INFORMATION_JOB_OBJECT GCQueryInformationJobObject = 0; + +#ifdef FEATURE_CORECLR + hinstApiSetJob1OrKernel32 = LoadDllForAPI(L"kernel32.dll", L"api-ms-win-core-job-l1-1-0.dll"); + if (!hinstApiSetJob1OrKernel32) + goto exit; + + GCIsProcessInJob = (PIS_PROCESS_IN_JOB)GetProcAddress(hinstApiSetJob1OrKernel32, "IsProcessInJob"); + if (!GCIsProcessInJob) + goto exit; +#else + GCIsProcessInJob = &(::IsProcessInJob); +#endif + + if (!GCIsProcessInJob(GetCurrentProcess(), NULL, &in_job_p)) + goto exit; + + if (in_job_p) + { +#ifdef FEATURE_CORECLR + hinstApiSetPsapiOrKernel32 = LoadDllForAPI(L"kernel32.dll", L"api-ms-win-core-psapi-l1-1-0"); + if (!hinstApiSetPsapiOrKernel32) + goto exit; + + GCGetProcessMemoryInfo = (PGET_PROCESS_MEMORY_INFO)GetProcAddress(hinstApiSetPsapiOrKernel32, "K32GetProcessMemoryInfo"); +#else + // We need a way to get the working set in a job object and GetProcessMemoryInfo + // is the way to get that. According to MSDN, we should use GetProcessMemoryInfo In order to + // compensate for the incompatibility that psapi.dll introduced we are getting this dynamically. + hinstPsapi = WszLoadLibrary(L"psapi.dll"); + if (!hinstPsapi) + return 0; + GCGetProcessMemoryInfo = (PGET_PROCESS_MEMORY_INFO)GetProcAddress(hinstPsapi, "GetProcessMemoryInfo"); +#endif + + if (!GCGetProcessMemoryInfo) + goto exit; + +#ifdef FEATURE_CORECLR + hinstApiSetJob2OrKernel32 = LoadDllForAPI(L"kernel32.dll", L"api-ms-win-core-job-l2-1-0"); + if (!hinstApiSetJob2OrKernel32) + goto exit; + + GCQueryInformationJobObject = (PQUERY_INFORMATION_JOB_OBJECT)GetProcAddress(hinstApiSetJob2OrKernel32, "QueryInformationJobObject"); +#else + GCQueryInformationJobObject = &(::QueryInformationJobObject); +#endif + + if (!GCQueryInformationJobObject) + goto exit; + + JOBOBJECT_EXTENDED_LIMIT_INFORMATION limit_info; + if (GCQueryInformationJobObject (NULL, JobObjectExtendedLimitInformation, &limit_info, + sizeof(limit_info), NULL)) + { + size_t job_memory_limit = (size_t)MAX_PTR; + size_t job_process_memory_limit = (size_t)MAX_PTR; + size_t job_workingset_limit = (size_t)MAX_PTR; + + // Notes on the NT job object: + // + // You can specific a bigger process commit or working set limit than + // job limit which is pointless so we use the smallest of all 3 as + // to calculate our "physical memory load" or "available physical memory" + // when running inside a job object, ie, we treat this as the amount of physical memory + // our process is allowed to use. + // + // The commit limit is already reflected by default when you run in a + // job but the physical memory load is not. + // + if ((limit_info.BasicLimitInformation.LimitFlags & JOB_OBJECT_LIMIT_JOB_MEMORY) != 0) + job_memory_limit = limit_info.JobMemoryLimit; + if ((limit_info.BasicLimitInformation.LimitFlags & JOB_OBJECT_LIMIT_PROCESS_MEMORY) != 0) + job_process_memory_limit = limit_info.ProcessMemoryLimit; + if ((limit_info.BasicLimitInformation.LimitFlags & JOB_OBJECT_LIMIT_WORKINGSET) != 0) + job_workingset_limit = limit_info.BasicLimitInformation.MaximumWorkingSetSize; + + job_physical_memory_limit = min (job_memory_limit, job_process_memory_limit); + job_physical_memory_limit = min (job_physical_memory_limit, job_workingset_limit); + } + } + +exit: +#ifdef FEATURE_CORECLR + if (hinstApiSetJob1OrKernel32) + FreeLibrary(hinstApiSetJob1OrKernel32); + if (hinstApiSetJob2OrKernel32) + FreeLibrary(hinstApiSetJob2OrKernel32); +#endif + + if (job_physical_memory_limit == (size_t)MAX_PTR) + { + job_physical_memory_limit = 0; + +#ifdef FEATURE_CORECLR + FreeLibrary(hinstApiSetPsapiOrKernel32); +#else + FreeLibrary(hinstPsapi); +#endif + } + + return job_physical_memory_limit; +#endif +} + +// Get the current physical memory this process is using. +// Return: +// 0 if it has failed, the real value if it has succeeded +size_t GCToOSInterface::GetCurrentPhysicalMemory() +{ +#ifndef FEATURE_PAL + PROCESS_MEMORY_COUNTERS pmc; + if (GCGetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) + return pmc.WorkingSetSize; +#endif + + return 0; +} + // Get global memory status // Parameters: // ms - pointer to the structure that will be filled in with the memory status -- 2.7.4