Integrate GC changes from full framework
authorMaoni Stephens <maonis@microsoft.com>
Wed, 2 Mar 2016 20:23:49 +0000 (12:23 -0800)
committerMaoni Stephens <maonis@microsoft.com>
Wed, 2 Mar 2016 20:23:49 +0000 (12:23 -0800)
[tfs-changeset: 1580785]

src/gc/env/gcenv.os.h
src/gc/gc.cpp
src/gc/gcpriv.h
src/gc/sample/gcenv.windows.cpp
src/vm/gcenv.os.cpp

index 68646e1..0ef979c 100644 (file)
@@ -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
     //
 
index 81e1572..d039947 100644 (file)
@@ -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;
index d00c3b8..cf7d066 100644 (file)
@@ -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;
index 62e8064..04d5ac3 100644 (file)
@@ -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
index 051508e..9555072 100644 (file)
 #include "common.h"
 #include "gcenv.h"
 
+#ifndef FEATURE_PAL
+#include <Psapi.h>
+#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