G1 释放物理内存,避免长期无效占用内存
背景用户咨询了一个内存资源利用的场景。场景如下他们的 java服务主要是白天有访问晚上量很少。有一些零散的任务需要运行。他们的想法是把这些零散的任务在晚上放在 java 服务的机器上运行这样就可以在不买新的机器的情况下处理的事情更多了。他们在实施上遇到了问题java 服务为什么不能释放物理内存这个导致他们晚上的程序因内存不足起不来。这种场景是有解决办法的jdk 社区做了不少功能来支持这个场景释放内存的能力每种 gc 都不相同并且是不同的参数来控制。下面我以 G1 为例来讲讲 jdk 支持的实现方式。选择G1 的原因如下jdk9 之后的默认 GC。支持的场景更丰富。释放的效果相对较好。实现下面的 code 来自 jdk25。不同版本看到的结果会有差异。我们围绕两个角度来进行分析。释放内存的策略释放内存的时机释放内存的策略内存释放的策略和内存增长的策略都在同一个方法中。size_t minimum_desired_capacity target_heap_capacity(used_after_gc, MinHeapFreeRatio); size_t maximum_desired_capacity target_heap_capacity(used_after_gc, MaxHeapFreeRatio); minimum_desired_capacity MIN2(minimum_desired_capacity, _g1h-max_capacity()); maximum_desired_capacity MAX2(maximum_desired_capacity, _g1h-min_capacity());计算预期目标内存大小的核心就是上面 4 行。先根据 gc 之后内存作为计算参考转化成预期的内存上下界在把范围控制在 xms 和 xmx之间。也就是说内存最大能申请到 xmx最小能缩减到 xms。如果当前的内存在范围内则本次不调整 内存。target_heap_capacity的计算代码如下主要就是根据内存根据比率来计算边界。const double desired_free_percentage (double) free_ratio / 100.0; const double desired_used_percentage 1.0 - desired_free_percentage; double used_bytes_d (double) used_bytes; double desired_capacity_d used_bytes_d / desired_used_percentage;预期释放的内存大小就是上面计算 两者的差值。size_t shrink_bytes capacity_after_gc - maximum_desired_capacity;可以释放的内存大小是计算出来了但是实际是否能释放还得看实际情况。这里是 G1 自身内存管理带来的限制他本身是 region 管理所以释放的单位就是 region。uint num_regions_to_remove (uint)(shrink_bytes / G1HeapRegion::GrainBytes);根据shrink_bytes转化成释放的 region 个数。既然是整体 region 释放region 中肯定是不能存在对象的。while ((removed num_regions_to_remove) (num_last_found find_empty_from_idx_reverse(cur, idx_last_found)) 0) { uint to_remove MIN2(num_regions_to_remove - removed, num_last_found); shrink_at(idx_last_found num_last_found - to_remove, to_remove); cur idx_last_found; removed to_remove; }G1 在计算出 region 数之后会通过find_empty_from_idx_reverse从后找完全空闲的 region实际能释放的内存量是这边找出的 region 个数。如果有大对象占用一半多 region导致了空间的碎片就无法释放这个 region 了。释放内存的时机下面释放的时机主要讲的是平稳释放的路径。G1在 full gc 的时候也会触发检测逻辑是可以通过这个路径来释放但是 full gc 对在线服务的影响比较大这个路径不在本次的讨论中。jdk 会启动一个定时任务来做检测和触发。void G1PeriodicGCTask::execute() { check_for_periodic_gc(); schedule(G1PeriodicGCInterval 0 ? 1000 : G1PeriodicGCInterval); }检测的 2 个判定条件第一个判定条件是 gc 间隔uintx time_since_last_gc (uintx)g1h-time_since_last_collection().milliseconds(); if ((time_since_last_gc G1PeriodicGCInterval)) { log_debug(gc, periodic)(Last GC occurred %zums before which is below threshold %zums. Skipping., time_since_last_gc, G1PeriodicGCInterval); return false; }我们期待的是业务量很低或者没有的时候来回收内存gc 的间隔代表的是业务的内存申请量如果内存一直申请一直做 gc说明并不适合做内存释放这个配置可以自己设置。第二个判断条件是 cpu 的 load。double recent_load; if (G1PeriodicGCSystemLoadThreshold 0.0) { if (os::loadavg(recent_load, 1) -1) { G1PeriodicGCSystemLoadThreshold 0.0; log_warning(gc, periodic)(System loadavg() call failed, disabling G1PeriodicGCSystemLoadThreshold check.); } else if (recent_load G1PeriodicGCSystemLoadThreshold) { log_debug(gc, periodic)(Load %1.2f is higher than threshold %1.2f. Skipping., recent_load, G1PeriodicGCSystemLoadThreshold); return false; } }这个是为了兼容计算场景如果分配内存少但是cpu 一直运行得用户自己来判断如果计算时候还是大量申请内存其实并不适合释放内存因为后面还要申请内存内存来回申请和释放也是一种性能问题。这个参数是可配置参数如果不配置默认不检测 cpu。在满足上述条件的情况下G1 会发起一次 gc并把原因标注为G1 Periodic Collection。g1h-try_collect(0 /* allocation_word_size */, GCCause::_g1_periodic_collection, counters)_g1_periodic_collection根据G1PeriodicGCInvokesConcurrent会有不同的表现默认是触发一次 concurrent也可以通过参数改成 fullgc但是不建议。bool G1CollectedHeap::should_do_concurrent_full_gc(GCCause::Cause cause) { switch (cause) { case GCCause::_g1_humongous_allocation: return true; case GCCause::_g1_periodic_collection: return G1PeriodicGCInvokesConcurrent; case GCCause::_wb_breakpoint: return true; case GCCause::_codecache_GC_aggressive: return true; case GCCause::_codecache_GC_threshold: return true; default: return is_user_requested_concurrent_full_gc(cause); } }在ConcurrentMark的过程中会根据是否是Periodic来触发内存的调整逻辑。if (_g1h-last_gc_was_periodic()) { _g1h-resize_heap_after_full_collection(0 /* allocation_word_size */); }释放内存的方式也很安全munmapstatic int anon_munmap(char * addr, size_t size) { if (::munmap(addr, size) ! 0) { ErrnoPreserver ep; log_trace(os, map)(munmap failed: RANGEFMT errno(%s), RANGEFMTARGS(addr, size), os::strerror(ep.saved_errno())); return 0; } return 1; }实践我们通过 gc 日志来展示效果gc 日志比较直观可以看到上述的过程。第一步先把 xms 和 xmx 设置不一样。这个是最重要的步骤很多早期的 java 实践都是要把 xms 和 xmx 设置一样来避免内存一直停留在 xms 上这条经验在现在的 G1 上不适用。这是最后一次业务正常的 gc。可以看到 heap 是 60m。[5.952s][info ][gc ] GC(4026) Pause Young (Concurrent Start) (G1 Humongous Allocation) 25M-1M(60M) 1.032ms这里我通过 jinfo 来动态设置触发。jinfo -flag G1PeriodicGCInterval10000 pid我把间隔设置成 10s这里作为一个参考建议不建议设置太长因为程序一般有心跳检测会分配一些对象这就导致了 gc 间隔不会特别长。先触发了一次Undo Cycle这个是 G1 的一个优化避免浪费资源的Concurrent这里有启发式逻辑不一定会真实遇到。[5.952s][info ][gc ] GC(4027) Concurrent Undo Cycle [5.952s][debug][gc,task ] G1 Service Thread (Card Set Free Memory Task) (run: 0.537ms) (cpu: 0.046ms) [5.952s][debug][gc,stats ] Mark stats cache hits 801 misses 0 ratio 100.000 [5.952s][info ][gc,marking ] GC(4027) Concurrent Clear Claimed Marks [5.952s][info ][gc,marking ] GC(4027) Concurrent Clear Claimed Marks 0.003ms [5.952s][info ][gc,marking ] GC(4027) Concurrent Cleanup for Next Mark [5.952s][debug][gc,ergo ] GC(4027) Running G1 Clear Bitmap with 1 workers for 1 work units. [5.952s][info ][gc,marking ] GC(4027) Concurrent Cleanup for Next Mark 0.042ms [5.952s][info ][gc ] GC(4027) Concurrent Undo Cycle 0.104ms下次开始了因Concurrent触发的一次 ygc这就回到了正常的 G1 流程里来了先来一次 ygc接下来就是Concurrent Mark Cycle。[23.030s][info ][gc ] GC(4028) Pause Young (Concurrent Start) (G1 Periodic Collection) 26M-1M(60M) 2.041ms [23.030s][info ][gc ] GC(4029) Concurrent Mark CycleConcurrent阶段我们就看到了标记可以释放的 region[23.033s][debug][gc,ergo,heap ] GC(4029) Attempt heap shrinking (capacity higher than max desired capacity). Capacity: 62914560B occupancy: 4194304B live: 1271544B maximum_desired_capacity: 13981013B (70 %) [23.033s][debug][gc,heap,region ] GC(4029) Deactivate regions [56, 58) [23.033s][debug][gc,heap,region ] GC(4029) Deactivate regions [19, 55) [23.033s][debug][gc,heap,region ] GC(4029) Deactivate regions [10, 18) [23.033s][debug][gc,ergo,heap ] GC(4029) Shrink the heap. requested shrinking amount: 48933547B aligned shrinking amount: 48234496B actual amount shrunk: 48234496B [23.033s][debug][gc,heap ] GC(4029) Uncommittable regions after shrink: 46在Remark阶段已经看到了 heap 从60m变成了 14m。这里只是把heap 的边界修改了。[23.033s][info ][gc ] GC(4029) Pause Remark 1M-1M(14M) 0.994ms [23.033s][info ][gc,cpu ] GC(4029) User0.01s Sys0.00s Real0.00sG1 Uncommit Region Task开始做真正的Uncommit到这里内存才真正的释放我们可以通过系统监控的 rss 中看到内存降低了。[23.034s][debug][gc,heap,region ] Uncommit regions [10, 18) [23.036s][debug][gc,heap,region ] Uncommit regions [19, 55) [23.036s][info ][gc,marking ] GC(4029) Concurrent Rebuild Remembered Sets and Scrub Regions 2.733ms [23.036s][debug][gc,heap,region ] Uncommit regions [56, 58) [23.036s][debug][gc,heap ] Concurrent Uncommit Summary: 47104K, 46 regions, 2.793ms [23.036s][debug][gc,task ] G1 Service Thread (G1 Uncommit Region Task) (run: 2.968ms) (cpu: 2.795ms)总结在 G1 的使用中重点关注如下参数-XX:G1PeriodicGCInterval -XX:G1PeriodicGCSystemLoadThreshold同时考虑自己的真实场景要避免短时间内频繁的申请和释放物理内存。相关链接

相关新闻

AI失业雷达图曝光!你的工作还能撑多久?程序员、客服首当其冲,30%职业受影响为零!

AI失业雷达图曝光!你的工作还能撑多久?程序员、客服首当其冲,30%职业受影响为零!

本文解读了Anthropic发布的AI失业雷达图,揭示了AI对不同职业的替代规律。程序员和客服因高度依赖信息处理和重复性语言交互,替代率极高。而厨师、维修工等职业受影响较小,因其涉及物理操作、多感官判断,且行业缺乏资本投入。文章强…

2026/6/26 1:22:24阅读更多 →
C++课后习题训练记录Day140

C++课后习题训练记录Day140

1.练习项目 :问题描述小蓝有很多齿轮,每个齿轮的凸起和凹陷分别用一个字符表示,一个字符串表示一个齿轮。如果两个齿轮的对应位分别是同一个字母的大小写,我们称这两个齿轮是契合的。例如:AbCDeFgh 和 aBcdEfGH 就是契…

2026/6/26 1:22:24阅读更多 →
Trae中运行python文件报错ModuleNotFoundError: No module named ‘requests‘

Trae中运行python文件报错ModuleNotFoundError: No module named ‘requests‘

此为学习记录 核心结论 终端能正常导入 requests,PyCharm(trae 运行工具)报错找不到,100% 是 PyCharm 当前项目选的 Python 解释器不是你装 requests 的 Anaconda base 环境,两个环境完全隔离,包互不通用。…

2026/6/26 1:22:24阅读更多 →
近期碎片0625

近期碎片0625

自我反思reflection的prompt,agent会不会通过用户反馈自动调优1、我的workflow和tools的边界2、场景颗粒度,针对什么场景,如何拆解意图3、数据闭环,任务的成功率,单论对话解决率,用户干预次数4、prompt结构…

2026/6/26 2:32:32阅读更多 →
如何免费制作专业PPT:PPTist在线演示文稿工具终极指南

如何免费制作专业PPT:PPTist在线演示文稿工具终极指南

如何免费制作专业PPT:PPTist在线演示文稿工具终极指南 【免费下载链接】PPTist PowerPoint-ist(/pauəpɔintist/), An online presentation application that replicates most of the commonly used features of MS PowerPoint, allowing fo…

2026/6/26 2:32:31阅读更多 →
反序列化漏洞:从原理到防护的深度解析

反序列化漏洞:从原理到防护的深度解析

1. 项目概述:从“数据还原”到“系统沦陷”的惊险一跃在软件开发的世界里,序列化和反序列化是再常见不过的操作。简单来说,序列化就是把一个内存中的对象,比如一个用户信息、一个订单数据,转换成一串可以存储或传输的字…

2026/6/26 2:32:31阅读更多 →
2026年下半年量化工具选择,先分清 AI 和 Python 分工

2026年下半年量化工具选择,先分清 AI 和 Python 分工

已有量化经验的人在选择软件工具时,往往比新手更清楚自己想提高效率,却也更容易被不同工具的能力边界困住。问题不只是“哪个工具更强”,而是自己当前缺的是规则表达、流程开发,还是对实现结构的理解。能力基础不同,合…

2026/6/26 2:32:31阅读更多 →
25元打造AI智能眼镜:OpenGlass开源项目终极指南

25元打造AI智能眼镜:OpenGlass开源项目终极指南

25元打造AI智能眼镜:OpenGlass开源项目终极指南 【免费下载链接】OpenGlass Turn any glasses into AI-powered smart glasses 项目地址: https://gitcode.com/GitHub_Trending/op/OpenGlass 想拥有自己的AI智能眼镜却担心价格昂贵?OpenGlass开源…

2026/6/26 2:32:31阅读更多 →
终极免费网盘下载加速解决方案:告别限速,9大平台全兼容的完整指南

终极免费网盘下载加速解决方案:告别限速,9大平台全兼容的完整指南

终极免费网盘下载加速解决方案:告别限速,9大平台全兼容的完整指南 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 ,支持 百度网盘 / 阿里云盘…

2026/6/26 2:27:31阅读更多 →
【人工智能】一文搞定到底什么是智能体

【人工智能】一文搞定到底什么是智能体

【人工智能】一文搞定到底什么是智能体 一文搞定到底什么是智能体【人工智能】一文搞定到底什么是智能体一. LM,WorkFlow,Agent分别有什么么不同二. Agent的思考过程是怎样的三. Agent的五个核心部分1)LLM2)Prompt3)Me…

2026/6/25 9:39:54阅读更多 →
嵌入式GUI控件实战:ROTARY、SCROLLBAR、SLIDER原理与应用

嵌入式GUI控件实战:ROTARY、SCROLLBAR、SLIDER原理与应用

1. 嵌入式GUI控件:从原理到实战的深度解析在嵌入式系统开发中,图形用户界面(GUI)的设计与实现往往是项目从“能用”到“好用”的关键一跃。不同于资源充沛的PC或移动平台,嵌入式设备的GUI需要在有限的CPU性能、内存空间…

2026/6/25 2:52:24阅读更多 →
Google AI Studio 300美元额度的真相与实战指南

Google AI Studio 300美元额度的真相与实战指南

1. 这300美金不是“送钱”,而是Google埋下的第一道技术门槛 你看到标题里那个醒目的“$300美金”时,第一反应可能是:又一个免费额度?领完就完事?我亲手试过——这300美金根本不是红包,而是一张入场券&…

2026/6/25 9:01:34阅读更多 →
HPE (慧与) 服务器专用 ESXi 9 全套官方定制资源详解 + 完整部署升级教程

HPE (慧与) 服务器专用 ESXi 9 全套官方定制资源详解 + 完整部署升级教程

一、前言:企业运维痛点与资源价值自博通收购 VMware 之后,原 VMware 公开免费下载渠道全面关闭,企业运维人员想要获取适配 HPE 慧与服务器的 ESXi 9 原厂镜像,必须注册博通账号、绑定有效授权才能下载,无授权账号无法获…

2026/6/26 0:02:15阅读更多 →
Kotlin的@JvmStatic与@JvmField:与Java互操作的注解

Kotlin的@JvmStatic与@JvmField:与Java互操作的注解

Kotlin作为一门现代编程语言,与Java的互操作性一直是其核心优势之一。为了让Kotlin代码能够无缝对接Java,Kotlin提供了多种注解来优化互操作体验,其中JvmStatic和JvmField是两个关键注解。它们分别用于解决静态成员和字段在Java中的访问问题&…

2026/6/26 0:02:15阅读更多 →
深入解析musl libc中的mmap实现源码

深入解析musl libc中的mmap实现源码

最近在阅读musl libc源码时,发现其mmap的实现非常精妙,特分享给大家。 一、代码整体结构 这段代码实现了__mmap函数,并通过weak_alias导出为mmap。这是典型的musl libc风格——提供弱符号以便用户可以重写。 weak_alias(__mmap, mmap); 二…

2026/6/26 0:02:15阅读更多 →