SpringBoot服务运行时内存占用优化

一、前言

java项目尤其基于SpringBoot框架开发的项目相对于其它一些语言(go语言)占用的内存较多,我们一般使用的云服务器资源比较有限(CPU、内存等),如何精简SpringBoot框架项目运行时内存消耗,尤其是项目在启动时,并没有运行业务的情况下,这里简单做一些总结。

二、优化方案

1、使用轻量级内嵌服务器

SpringBoot框架中,我们使用最多的是Tomcat,这是SpringBoot默认的容器技术,而且是内嵌式的Tomcat,SpringBoot也支持Undertow容器,我们可以很方便的用Undertow替换Tomcat,而Undertow的性能和内存使用方面都优于Tomcat
Undertow简介:是Red Hat公司的开源产品, 它完全采用Java语言开发,是一款灵活的高性能Web服务器,支持阻塞IO和非阻塞IO。由于Undertow采用Java语言开发,可以直接嵌入到Java项目中使用。同时, Undertow完全支持Servlet和Web Socket,在高并发情况下表现非常出色。
在这里插入图片描述
内存使用对比:
在这里插入图片描述
轻量级:Undertow的代码库相对较小,这使得它在资源占用和启动时间方面具有优势,特别适合需要快速启动低内存占用的应用场景。

pom.xml配置

<!--  启动器依赖 -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
   <!-- 移除Tomcat的依赖 -->
   <exclusions>
       <exclusion>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-tomcat</artifactId>
       </exclusion>
   </exclusions>
</dependency>
<!--   采用Undertow依赖 -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter</artifactId>
</dependency>

application.yml配置

server:
  undertow:
    # 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
    io-threads: 2
    # 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
    worker-threads: 1000
    # 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
    # 每块buffer的空间大小,越小的空间被利用越充分
    buffer-size: 1024
    # 是否分配的直接内存
    direct-buffers: true

2、减少日志级别

日志占用内存问题:

  • 日志级别和数量
    如果你的应用程序配置了较低的日志级别(如DEBUG或TRACE),那么会生成大量的日志信息。这些额外的日志信息会占用更多的内存。
  • 日志实现: 不同的日志实现(如Logback, Log4j2等)在性能和内存使用方面有所不同。某些实现可能在处理大量日志时更加高效。
  • 日志附加信息: 在日志消息中包含过多的附加信息(如大型对象或复杂的数据结构)会增加每条日志消息的大小,从而导致更高的内存使用。
  • 异步日志: 使用异步日志记录可以减少对主应用程序线程的影响,但如果异步日志队列管理不当,可能会导致内存问题。
  • 日志滚动和归档: 如果日志文件的滚动和归档配置不当,旧的日志文件可能会堆积,导致磁盘空间不足,间接影响应用程序的内存使用。
  • 外部日志系统集成: 如果你的应用程序与外部日志系统集成(如ELK Stack、Splunk等),日志的格式和传输方式也可能影响内存使用。

优化建议:

  • 调整日志级别:在生产环境中,将日志级别设置为INFO或WARN,以减少日志生成量。
  • 使用高效的日志实现:比较不同的日志实现,选择最适合你应用程序需求的实现。
  • 精简日志消息:避免在日志消息中包含大型对象或复杂数据结构。
  • 合理配置异步日志:确保异步日志队列的大小和策略适合你的应用程序需求。
  • 定期滚动和归档日志:确保日志文件不会无限增长,占用大量磁盘空间。
  • 监控日志系统:定期监控日志系统的性能和内存使用,以便及时调整配置。

3、服务启动内存设置

启动命令

nohup java -jar -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=56m -Xms128m -Xmx128m -Xmn32m -Xss256k -XX:SurvivorRatio=8 -XX:+UseConcMarkSweepGC short-url.jar --spring.profiles.active=prod >/dev/null 2>&1&

参数意义

-XX:MetaspaceSize=128m:元空间默认大小

-XX:MaxMetaspaceSize=128m:元空间最大大小

-Xms1024m:堆最大大小

-Xmx1024m:堆默认大小

-Xmn256m:新生代大小

-Xss256k:栈最大深度大小

-XX:SurvivorRatio=8:新生代分区比例 8:2

-XX:+UseConcMarkSweepGC:指定使用的垃圾收集器,这里使用CMS收集器

-XX:+PrintGCDetails:打印详细的GC日志

4、使用懒加载

懒加载是一种优化技术,它延迟加载或按需加载资源,以减少初始加载时间或提高性能;在SpringBoot中配置懒加载还是很简单的,这个问题,SpringBoot已经支持了,只需要使用@Lazy注解来实现;
@Lazy注解
在Spring Boot中,你可以使用@Lazy注解来实现懒加载。@Lazy注解用于延迟加载依赖,以便在真正需要的时候才进行初始化。这可以避免在应用程序启动时立即加载所有依赖,从而提高应用程序的启动性能。
要使用@Lazy注解,你需要将其添加到需要懒加载的Bean上。例如,假设你有一个名为"myBean"的Bean,你可以在它的定义上添加@Lazy注解,如下所示:

@Component  
@Lazy  
public class MyBean {  
    // Bean的代码  
}

注意事项:

  • 懒加载的Bean必须被其他Bean引用,否则它不会被初始化。因此,如果你没有在其他地方引用"myBean",那么它永远不会被初始化。
  • 懒加载的Bean必须是单例Bean。因为懒加载是为了提高应用程序的性能,所以它只会在第一次需要时初始化一次。因此,懒加载的Bean必须是单例Bean,以便在整个应用程序中共享实例。
  • 懒加载的Bean不能被标记为@Scope(“prototype”),因为prototype作用域的Bean会在每次注入时都创建新的实例。这会导致懒加载无法正常工作。
  • 懒加载的Bean中的依赖注入也必须是懒加载的。如果一个Bean中的依赖不是懒加载的,那么整个Bean都会被立即初始化,而不是延迟初始化。

5、SpringBoot排除不需要的自动配置类

首先介绍SpringBoot启动时加载流程,在SpringBoot的可执行jar中,配置了MANIFEST.MF的Main-Class设置为JarLauncher,JarLauncher的加载过程如下图:
在这里插入图片描述

  1. 调用Launcher.launch方法,开始引导程序
  2. 通过Handlers.reigster()注册URLStreamHandler的jar协议处理类,支持getResource读取BOOT-INF/下的内容
  3. 通过archive.getClassPathUrls()筛选可执行jar的所有元素,通过JarLauncher.isIncludedOnClassPath过滤,筛选出所有ClassPath元素
  4. 用这些筛选的ClassPath创建LaunchedClassLoader
  5. 使用LaunchedClassLoader加载MANIFEST.MF的Start-Class
  6. 反射调用main方法

SpringBoot类加载器:
使用URLClassLoader加载SpringBoot Jar时不会加载BOOT-INF下的classes、lib的类和依赖包,SpringBoot通过自定义类加载器LaunchedClassLoader解决这个问题。它继承自URLClassLoader。参照SpringBoot的启动流程,我们讲手动使用LaunchedClassLoader加载可执行jar,并尝试引导SpringBoot应用。
在这里插入图片描述

SpringBoot默认封装了很多的组件,并且把这些组件都进行了自动化配置封装。也就是说,只要是SpringBoot项目,启动类在@SpringBootApplication注解下,在不需要单独配置bean的情况下,本项目所用到的bean在容器启动的时候都会被自动扫描并注入到IOC容器里。这里我所说的bean主要指的是pom文件里面配置的那些starter,以及还有一些springboot默认封装的一些组件,比如数据源,redis等一些组件。
有时候我们可能并不想使用springboot默认的数据源,就需要在容器启动的时候进行排除掉。

排除依赖的三种方式:
1)在主启动类添加SpringBootApplication注解

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
 
@SpringBootApplication (exclude = exclude = {DataSourceAutoConfiguration.class, DruidDataSourceAutoConfigure.class, MybatisAutoConfiguration.class})
public class OrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class,args);
    }
}

2)使用@EnableAutoConfiguration注解

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
 
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class, DruidDataSourceAutoConfigure.class, MybatisAutoConfiguration.class} )
public class OrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class,args);
    }
}

3)在配置文件中排除

#springboot排除自动配置
spring:
  autoconfigure:
    exclude:
      - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
      - com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
      - org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

想要排除加载的类,首先要知道服务启动时加载了哪些类,如何获取?

打印所有Spring boot载入的bean:

@Log4j2
@Component
public class ApplicationTest02 implements CommandLineRunner {

    @Autowired
    private ApplicationContext context;

    public static void main(String[] args) {
        SpringApplication.run(ApplicationTest02.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        String[] beanNames = context.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            Object bean = context.getBean(beanName);
            log.info("=====Bean名称: " + beanName + ", 类型: " + bean.getClass().getName());
        }
    }

}

输出结果:

2024-08-31T11:52:47,830 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: org.springframework.context.annotation.internalConfigurationAnnotationProcessor, 类型: org.springframework.context.annotation.ConfigurationClassPostProcessor
2024-08-31T11:52:47,831 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: org.springframework.context.annotation.internalAutowiredAnnotationProcessor, 类型: org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
2024-08-31T11:52:47,831 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: org.springframework.context.annotation.internalCommonAnnotationProcessor, 类型: org.springframework.context.annotation.CommonAnnotationBeanPostProcessor
2024-08-31T11:52:47,831 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: org.springframework.context.event.internalEventListenerProcessor, 类型: org.springframework.context.event.EventListenerMethodProcessor
2024-08-31T11:52:47,831 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: org.springframework.context.event.internalEventListenerFactory, 类型: org.springframework.context.event.DefaultEventListenerFactory
2024-08-31T11:52:47,831 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: application, 类型: com.sk.proxytest.Application$$EnhancerBySpringCGLIB$$204311b2
2024-08-31T11:52:47,831 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory, 类型: org.springframework.boot.type.classreading.ConcurrentReferenceCachingMetadataReaderFactory
2024-08-31T11:52:47,831 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: applicationTest02, 类型: com.sk.proxytest.init.ApplicationTest02
2024-08-31T11:52:47,831 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: org.springframework.boot.autoconfigure.AutoConfigurationPackages, 类型: org.springframework.boot.autoconfigure.AutoConfigurationPackages$BasePackages
2024-08-31T11:52:47,832 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: com.sk.proxytest.Application#MapperScannerRegistrar#0, 类型: org.mybatis.spring.mapper.MapperScannerConfigurer
2024-08-31T11:52:47,832 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration, 类型: org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
2024-08-31T11:52:47,832 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: propertySourcesPlaceholderConfigurer, 类型: org.springframework.context.support.PropertySourcesPlaceholderConfigurer
2024-08-31T11:52:47,832 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration$TomcatWebSocketConfiguration, 类型: org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration$TomcatWebSocketConfiguration
2024-08-31T11:52:47,832 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: websocketServletWebServerCustomizer, 类型: org.springframework.boot.autoconfigure.websocket.servlet.TomcatWebSocketServletWebServerCustomizer
2024-08-31T11:52:47,832 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration, 类型: org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration
2024-08-31T11:52:47,832 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat, 类型: org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat
2024-08-31T11:52:47,832 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: tomcatServletWebServerFactory, 类型: org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory
2024-08-31T11:52:47,832 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration, 类型: org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
2024-08-31T11:52:47,833 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: servletWebServerFactoryCustomizer, 类型: org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryCustomizer
2024-08-31T11:52:47,833 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: tomcatServletWebServerFactoryCustomizer, 类型: org.springframework.boot.autoconfigure.web.servlet.TomcatServletWebServerFactoryCustomizer
2024-08-31T11:52:47,833 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor, 类型: org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor
2024-08-31T11:52:47,833 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: org.springframework.boot.context.internalConfigurationPropertiesBinderFactory, 类型: org.springframework.boot.context.properties.ConfigurationPropertiesBinder$Factory
2024-08-31T11:52:47,833 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: org.springframework.boot.context.internalConfigurationPropertiesBinder, 类型: org.springframework.boot.context.properties.ConfigurationPropertiesBinder
2024-08-31T11:52:47,833 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: org.springframework.boot.context.properties.BoundConfigurationProperties, 类型: org.springframework.boot.context.properties.BoundConfigurationProperties
2024-08-31T11:52:47,833 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: org.springframework.boot.context.properties.ConfigurationPropertiesBeanDefinitionValidator, 类型: org.springframework.boot.context.properties.ConfigurationPropertiesBeanDefinitionValidator
2024-08-31T11:52:47,833 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata, 类型: org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata
2024-08-31T11:52:47,834 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: server-org.springframework.boot.autoconfigure.web.ServerProperties, 类型: org.springframework.boot.autoconfigure.web.ServerProperties
2024-08-31T11:52:47,834 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: webServerFactoryCustomizerBeanPostProcessor, 类型: org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor
2024-08-31T11:52:47,834 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: errorPageRegistrarBeanPostProcessor, 类型: org.springframework.boot.web.server.ErrorPageRegistrarBeanPostProcessor
2024-08-31T11:52:47,834 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletConfiguration, 类型: org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletConfiguration
2024-08-31T11:52:47,834 INFO  [main] com.sk.proxytest.init.ApplicationTest02: =====Bean名称: dispatcherServlet, 类型: org.springframework.web.servlet.DispatcherServlet

三、总结

本文主要介绍常见的优化方式,另外还有很多,例如定期检查和移除pom.xml文件中没有被使用到的依赖,使用JVM监控工具(如VisualVM, JConsole等)来监控内存使用情况,并根据需要进行调整,在业务成,控制线程池、集合等内存占用情况等等。

----------------------------------👇👇👇注:更多信息请关注公众号获取👇👇👇-------------------------------------------

  • 14
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dylan~~~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值