JVM优化之调整大内存分页(LargePage)

本文介绍了内存分页机制的基本概念,包括MMU的作用、页表和TLB的工作原理,以及如何通过调整OS和JVM设置来支持大内存分页。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载自:http://jm.taobao.org/2009/11/21/3157/

什么是内存分页?

       CPU是通过寻址来访问内存的。32位CPU的寻址宽度是 0~0xFFFFFFFF,即4G,也就是说可支持的物理内存最大是4G。但在实践过程中,程序需要使用4G内存,而可用物理内存小于4G,导致程序不得不降低内存占用。为了解决此类问题,现代CPU引入了MMU(Memory Management Unit,内存管理单元)。
       MMU 的核心思想是利用虚拟地址替代物理地址,即CPU寻址时使用虚址,由MMU负责将虚址映射为物理地址。MMU的引入,解决了对物理内存的限制,对程序来说,就像自己在使用4G内存一样。
       内存分页(Paging)是在使用MMU的基础上,提出的一种内存管理机制。它将虚拟地址和物理地址按固定大小(4K)分割成页(page)和页帧(page frame),并保证页与页帧的大小相同。这种机制,从数据结构上,保证了访问内存的高效,并使OS能支持非连续性的内存分配。在程序内存不够用时,还可以将不常用的物理内存页转移到其他存储设备上,比如磁盘,这就是虚拟内存。
       在上文中提到,虚拟地址与物理地址需要通过映射,才能使CPU正常工作。而映射就需要存储映射表。在现代CPU架构中,映射关系通常被存储在物理内存上一个被称之为页表(page table)的地方。 如下图:
             
       页表是被存储在内存中的,CPU通过总线访问内存,肯定慢于直接访问寄存器的。为了进一步优化性能,现代CPU架构引入了TLB(Translation lookaside buffer,页表寄存器缓冲),用来缓存一部分经常访问的页表内容。如下图:
             

为什么要支持大内存分页?

       TLB是有限的,这点毫无疑问。当超出TLB的存储极限时,就会发生 TLB miss,之后,OS就会命令CPU去访问内存上的页表。如果频繁的出现TLB miss,程序的性能会下降地很快。
       为了让TLB可以存储更多的页地址映射关系,我们的做法是调大内存分页大小。如果一个页4M,对比一个页4K,前者可以让TLB多存储1000个页地址映射关系,性能的提升是比较可观的。

调整OS和JVM内存分页

       在Linux确认是否支持,请在终端敲如下命令:
[java]  view plain  copy
  1. $cat /proc/meminfo | grep Huge  
  2. HugePages_Total: 0  
  3. HugePages_Free: 0  
  4. Hugepagesize: 2048 kB  
       如果有HugePage字样的输出内容,说明OS是支持大内存分页的。Hugepagesize就是默认的大内存页size。接下来,为了让JVM可以调整大内存页size,需要设置下OS 共享内存段最大值和大内存页数量。
       共享内存段最大值,建议这个值大于Java Heap size,这个例子里设置了4G内存。
[java]  view plain  copy
  1. $ echo 4294967295 > /proc/sys/kernel/shmmax  
       大内存页数量, 这个值一般是 Java进程占用最大内存/单个页的大小,比如java设置1.5G,单个页10M,那么数量为1536/10 = 154。
[java]  view plain  copy
  1. $ echo 154 > /proc/sys/vm/nr_hugepages  
       单个页大小调整,JVM启用时加参数 -XX:LargePageSizeInBytes=10m。如果JDK是在1.5 update5以前的,还需要手动加 -XX:+UseLargePages,作用是启用大内存页支持。
### MyBatis-Plus 查询大量数据时导致内存溢出的解决方案 在实际开发中,当使用 MyBatis-Plus 查询大量数据时,可能会因为一次性加载过多数据到内存而导致内存溢出(Out Of Memory, OOM)。针对这一问题,可以从以下几个方面进行优化和改进。 --- #### 一、分页查询 分页查询是最常见也是最有效的解决办法之一。通过设置每页的数据量限制,可以有效减少单次查询返回的结果集大小,从而避免内存被过度占用[^1]。 ```java // 使用 MyBatis-Plus 的分页插件实现分页查询 Page<User> page = new Page<>(current_page_number, page_size); IPage<User> userPage = userService.page(page, Wrappers.emptyWrapper()); ``` 上述代码展示了如何利用 MyBatis-Plus 提供的分页功能来获取指定页面的数据。需要注意的是,分页虽然能缓解内存压力,但如果存在深度分页或者频繁交互数据库的情况,也可能带来性能上的损耗[^3]。 --- #### 二、流式查询 相比于传统的分页方式,流式查询更适合处理超大规模的数据集。它允许程序逐步读取结果集中的一部分数据,而不是一次性将所有数据加载至内存中[^2]。 ##### 实现步骤: 1. **启用 Cursor 支持**:自 MyBatis 3.4.0 版本起支持 `Cursor` 接口,该接口提供了类似于迭代器的功能。 2. **配置 JDBC 参数**:确保 MySQL 驱动中的 `setFetchSize(Integer.MIN_VALUE)` 方法已被调用,这会触发服务器端游标的开启,使得客户端能够按需拉取数据而非一次性下载整个结果集。 3. **代码示例**: ```java @Select("SELECT * FROM large_table") public Cursor<LargeTableEntity> selectAll(); // 调用流式查询 try (Cursor<LargeTableEntity> cursor = mapper.selectAll()) { while (cursor.hasNext()) { LargeTableEntity entity = cursor.next(); process(entity); // 对每一行数据单独处理 } } catch (Exception e) { log.error("Stream query failed", e); } ``` 通过这种方式,即使表中有百万级甚至千万级记录,也可以平稳运行而不引发 OOM 错误。 --- #### 三、调整 JVM 堆内存参数 如果无法更改现有查询逻辑或暂时不适合改造成分页/流式模式,则可通过增加 JVM 的最大堆空间分配额度作为临时措施。不过这只是治标不治本的方法,在生产环境中并不推荐长期依赖此项设定[^4]。 修改启动脚本加入如下选项即可扩展可用内存容量: ```bash -Xms512m -Xmx4g ``` 这里 `-Xms` 表示初始堆大小,而 `-Xmx` 则定义了最高上限值;单位可以用 m(兆字节)或 g(千兆字节)表示。 --- #### 四、批量写入与分割操作 如果是涉及到大批量数据导入场景下发生的 OOM ,那么应该重新审视现有的插入策略。例如由原来的整批提交改为小批次循环执行,并且每次完成后立即清理不再使用的变量引用以释放资源[^4]。 --- ### 总结 综上所述,面对因海量数据检索所引起的内存不足状况,优先考虑实施分页或是切换为流式的访问形式最为合理高效。与此同时也要注意适当调节虚拟机环境下的资源配置以及持续监控应用程序的实际表现指标变化趋势。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值