JAVA开发常见问题及解决方法(二)

1.问题描述

Java后端服务接口报堆外内存溢出错误。

2.问题报错

ERROR outOfDirectMemoryError:failed to allocate 134217728 byte(s) of direct memory (used:146800640, max:279969792)

3.问题报错日志截图

4.问题分析

可能导致堆外内存溢出的原因:

  1. 系统所在服务器物理内存不足。
  2. JVM参数设置不正确。
  3. 接口方法编程错误,如ByteBuffer可能在使用完毕后未被正确释放。
  4. 使用第三方库(如redis客户端等)存在内存管理问题。

拓展分析:

Java 中的内存溢出错误通常指的是 OutOfMemoryError,它表明 Java 虚拟机(JVM)在尝试为对象分配内存时,内存不足,且垃圾回收器也无法回收足够的空间来满足请求。OutOfMemoryError 有多种,包括但不限于:

  1. 堆内存溢出:当 Java 堆(Heap)没有足够的空间来创建新的对象时,会抛出 OutOfMemoryError: Java heap space。这通常是因为应用程序创建了太多对象,或者长时间运行后累积了太多无法被垃圾回收器回收的对象。

2、永久代/元空间溢出:在 Java 8 之前,OutOfMemoryError: PermGen space 错误表明永久代(PermGen)内存不足。永久代用于存储类的元数据。从 Java 8 开始,永久代被元空间(Metaspace)取代,如果元空间不足,会抛出 OutOfMemoryError: Metaspace。

3、直接内存溢出:直接内存(Direct Memory)是 JVM 之外的内存,由 java.nio.ByteBuffer 的某些实现(如 DirectByteBuffer)使用。如果直接内存不足,会抛出 OutOfMemoryError: Direct buffer memory。

5.排查步骤

1、使用free命令检查系统内存

# -h 选项表示以人类可读的格式(如G、M)显示

free -h

经排查,系统有足够的物理内存来支持应用程序运行。

2、使用内存诊断工具(如 jconsole、jvisualvm 或 jmap)查找问题

通过jvisualvm查看应用程序的GC的情况。

如上图所示:伊甸园区满了触发Minor GC(年轻代垃圾回收),老年代也没有做完全的移出,包括我们的元空间也一直没有占用多少,说明我们的GC是正常的,没有什么问题。

拓展说明:

Java虚拟机(JVM)的内存管理中新生代内存进一步划分为伊甸园区(Eden Space)、幸存者0区(Survivor 0 Space)和幸存者1区(Survivor 1 Space)其中所有Java对象都在伊甸园区中出生,当伊甸园区内存空间不足时,会触发Minor GC(年轻代垃圾回收),将存活的对象移动到幸存者区(Survivor Space)。

3、检查 JVM 参数设置

将-Xmx参数从100m调整为300m,接口访问恢复正常,但在大并发的情况下还是会报堆外内存溢出,问题仍未解决。

4、排查java后端接口代码问题

经排查,接口方法本身也没有问题。

5、检查依赖库及其他资源内存使用情况

经排查,项目中使用redis作为缓存中间件,springboot2.0以后默认使用lettuce作为操作redis的客户端,通过查看lettcue-core源码发现,它使用netty进行网络通信,lettuce的bug导致netty堆外内存溢出。

诊断结果:

项目中springboot2.0默认使用的 redis的客户端lettuce-core的bug导致netty堆外内存溢出。netty的特点是:如果没有为其指定堆外内存,默认使用Java虚拟机的内存,内存没有得到及时释放。

6.解决方法

6.1更换项目中使用的redis客户端

项目中放弃使用lettcue,在项目中使用jedis作为连接redis的客户端,在Spring Boot中,移除默认的Lettuce作为Redis客户端,并改用Jedis,通常不需要显式地从依赖中移除Lettuce,因为Spring Boot的spring-boot-starter-data-redis并不直接包含Lettuce的依赖,而是依赖于底层可能包含Lettuce的库。但是,你需要通过配置来确保Spring Boot使用Jedis而不是Lettuce。具体操作步骤如下:

1、确保项目中没有直接包含Lettuce的依赖。

检查pom.xml(Maven)文件,确保没有直接包含Lettuce的依赖。通常spring-boot-starter-data-redis已经足够,它会根据你的配置或类路径中的依赖自动选择使用Lettuce还是Jedis。

拓展说明:如果你的项目用的是gradle则检查build.gradle(Gradle)文件。

2、添加Jedis的依赖。

通常spring-boot-starter-data-redis已经包含了Jedis的依赖,这个步骤不是必要的,但为了确保使用了Jedis并为其指定一个特定的版本,你在pom.xml需添加以下依赖:

Maven:

<dependency>

<groupId>redis.clients</groupId>

<artifactId>jedis</artifactId>

<version>3.0.0</version> <!-- 替换为实际的版本号 -->

</dependency>

拓展说明:如果你的项目用的是gradle则添加如下依赖。

Gradle: 

dependencies {

implementation 'redis.clients:jedis:3.0.0' // 替换为实际的版本号

}

3、配置Spring Boot使用Jedis。

在application.properties或application.yml文件中,不需要显式地指定使用Jedis,因为Spring Boot会根据类路径中的依赖自动选择,直接配置Jedis连接池的属性即可。

application.properties:

spring.redis.jedis.pool.max-active=8

spring.redis.jedis.pool.max-wait=-1ms

spring.redis.jedis.pool.max-idle=8

spring.redis.jedis.pool.min-idle=0

spring.redis.client-type=jedis

application.yml:

spring:

redis:

jedis:

pool:

max-active: 8 

max-wait: -1ms 

max-idle: 8 

min-idle: 0 

client-type: jedis

注意spring.redis.client-type=jedis这个属性明确告诉Spring Boot使用Jedis作为Redis客户端。这个属性在较新版本的Spring Boot中不是必需的,因为Spring Boot通常可以自动检测并使用类路径中的Jedis。

4、清理和重建项目

执行Maven的clean install命令来清理并重建项目。

拓展说明:如果你的项目用的是gradle则使用clean build命令来清理并重建项目。

5、验证

高并发访问接口验证内存溢出问题,已修复。

拓展说明:

我们也可以不更换redis客户端去采用升级lettuce版本的方式解决该问题,但是lettuce目前没有一个版本可以解决堆外内存溢出的问题,因此我们不能直接使用官方版本升级方式去解决这个问题,我们可以自己改源码去解决这个问题。该方法解决起来比较耗时费力,未使用该方法,解决思路如下,以供参考:ByteBuffer是Java NIO中一个非常重要的组件在网络编程中非常有用你可以尝试重用 ByteBuffer 对象而不是每次都创建新的。

6.2调整JVM参数

使用 -XX:MaxDirectMemorySize JVM 参数来设置允许的最大直接内存大小,确保这个值不要超过系统的可用物理内存。我们可以通过命令行和设置tomcat配置文件中JVM参数两种方式进行调整JVM参数:

1、在命令行中:当启动Java应用程序时,将参数添加到java命令中。例如:

java -XX:MaxDirectMemorySize=1024m -jar your-application.jar

在这个例子中,我们设置了直接内存的最大大小为1024MB。

2、设置tomcat配置文件中JVM参数

(1)打开Tomcat的bin目录。
(2)配置catalina文件并JAVA_OPTS参数

Windows

找到catalina.bat文件(用于启动Tomcat的批处理文件)。

在catalina.bat文件中,找到类似于set JAVA_OPTS=的行(如果不存在,则添加它)。

在这行后面添加你的JVM参数:

set JAVA_OPTS=-XX:MaxDirectMemorySize=1024m

Linux/macOS

找到catalina.sh文件(用于启动Tomcat的shell脚本)。

在catalina.sh文件中,找到类似于export JAVA_OPTS=的行(如果不存在,则添加它)。

在这行后面添加你的JVM参数,例如:

export JAVA_OPTS="$JAVA_OPTS -XX:MaxDirectMemorySize=1024m"

注意:若已经设置了JAVA_OPTS,那么只需在现有值后面添加参数即可,确保用空格分隔。

保存并关闭文件。该步骤请根据实际情况按需配置。

(3)重新启动Tomcat以应用更改。

6.3增加服务器物理内存

云服务器直接弹性扩容服务器物理内存。如果是物理服务器,则通过加配内存条方式扩容。

说明:该步骤请根据实际情况按需配置。

  • 10
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值