Redis 报”OutOfDirectMemoryError“(堆外内存溢出)

打印 上一主题 下一主题

主题 924|帖子 924|积分 2772

Redis 报错“OutOfDirectMemoryError(堆外内存溢出) ”问题如下:

一、报错信息:

使用 Redis 的业务接口 ,产生 OutOfDirectMemoryError(堆外内存溢出),如图:

格式化后的报错信息:
  1. {
  2.         "timestamp": "2023-04-17 22:46:36",
  3.         "status": 500,
  4.         "error": "Internal Server Error",
  5.         "message": "Java heap space",
  6.         "trace": "java.lang.OutOfMemoryError: Java heap
  7.     ......
  8. }
复制代码
二、报错原因:

源码分析:
  1. public final class PlatformDependent {
  2.      // 直接内存大小,可通过 “-Dio.netty.maxDirectMemory”参数设置。
  3.      private static final long DIRECT_MEMORY_LIMIT;
  4.      
  5.      // 默认的最大直接内存大小 ,方法略。大概意思还是:先获取“Eclipse OpenJ9”的“sun.misc.VM”参数,如果没有则获取JVM的“-XX:MaxDirectMemorySize”作为默认值。
  6.      private static final long MAX_DIRECT_MEMORY = maxDirectMemory0();
  7.      
  8.      static {
  9.         // 其他赋值操作,略
  10.          
  11.       // 给 直接内存大小赋值,如果有设置 "-Dio.netty.maxDirectMemory" 参数,则使用用户设置的,如果没有则使用默认的
  12.       logger.debug("-Dio.netty.maxDirectMemory: {} bytes", maxDirectMemory);
  13.       DIRECT_MEMORY_LIMIT = maxDirectMemory >= 1 ? maxDirectMemory : MAX_DIRECT_MEMORY;
  14.                
  15.      }
  16.     private static void incrementMemoryCounter(int capacity) {
  17.         if (DIRECT_MEMORY_COUNTER != null) {
  18.             long newUsedMemory = DIRECT_MEMORY_COUNTER.addAndGet(capacity);
  19.             
  20.             //关键判断:如果 netty内部使用的内存大小 大于 “直接内存大小”的话,就抛出 "OutOfDirectMemoryError"异常。
  21.             if (newUsedMemory > DIRECT_MEMORY_LIMIT) {
  22.                 DIRECT_MEMORY_COUNTER.addAndGet(-capacity);
  23.                 throw new OutOfDirectMemoryError("failed to allocate " + capacity
  24.                                                  + " byte(s) of direct memory (used: " + (newUsedMemory - capacity)
  25.                                                  + ", max: " + DIRECT_MEMORY_LIMIT + ')');
  26.             }
  27.         }
  28.     }
  29. }   
复制代码
总结原因:
1)、Springboot 2.x 以后默认使用 Lettuce作为操作 redis 的客户端。它是使用 netty 进行网络通信的。
2)、从spring-boot-starter-data-redis(2.2.3.RELEASE) 依赖可以看出内置使用的确实是 Lettuce 客户端,分析源码得知,lettuce 使用的 netty 框架,引用的netty包netty-common-4.1.43.Final.jar里面有一个PlatformDependent.java类 ,底层有个-Dio.netty.maxDirectMemory 参数,会自己校验堆外内存是否大于当前服务可使用的内存,如果大于则抛出 OutOfDirectMemoryError(堆外内存溢出)。显然,这是属于 Netty(netty-common-4.1.43.Final.jar)的bug导致堆外内存溢出的。
三、解决方法:

不能使用-Dio.netty.maxDirectMemory 只调大堆外内存,这只能延迟bug出现的时机,不能完全解决该问题。想解决有如下两个方案:
1、升级 Lettuce客户端,期待新版本会解决该问题。
2、排除 Lettuce客户端,切换使用没有该问题的 Jedis 客户端。
Netty 框架性能更好,吞吐量更大,但是Springboot默认使用的Lettuce 客户端对Netty的支持不够好;
Jedis客户端虽然没有Netty更快,但胜在稳定,没有上述bug。
因此下面使用第二种解决方案:
切换使用Jedis 客户端:
在data-redis中排除lettuce,在引入jedis
  1.                 <dependency>
  2.             <groupId>org.springframework.boot</groupId>
  3.             <artifactId>spring-boot-starter-data-redis</artifactId>
  4.             <exclusions>
  5.                
  6.                 <exclusion>
  7.                     <groupId>io.lettuce</groupId>
  8.                     <artifactId>lettuce-core</artifactId>
  9.                 </exclusion>
  10.             </exclusions>
  11.         </dependency>
  12.         
  13.         
  14.         <dependency>
  15.             <groupId>redis.clients</groupId>
  16.             <artifactId>jedis</artifactId>
  17.         </dependency>
复制代码
四、检验结果:

压力测试:

测试结果:


解决  Redis 报“OutOfDirectMemoryError(堆外内存溢出)    成功”

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

南飓风

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表