SpringBoot性能优化避坑

一、应用层优化 (Spring Boot & Spring Framework)

  1. 合理使用 Spring 核心特性
    @Transactional 事务优化

将事务范围控制在最小必要单元,避免在只读操作上开启事务。
明确指定 readOnly = true 用于只读事务。
避免在事务方法中执行耗时操作(如远程调用、复杂计算)。

@Service
publicclass OrderService {

    privatefinal OrderRepository orderRepository;
    
    // 构造函数注入
    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }
    
    // 只读事务优化
    @Transactional(readOnly = true)
    public List<Order> findAllOrders() {
        return orderRepository.findAll();
    }
    
    // 常规事务 - 最小化事务范围
    @Transactional
    public Order createOrder(Order order) {
        // 仅在必要操作上开启事务
        Order savedOrder = orderRepository.save(order);
        // 非事务性操作应放在事务外执行
        return savedOrder;
    }
    
    // 明确指定传播行为和隔离级别(根据实际需求)
    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
    public void updateOrderStatus(Long orderId, OrderStatus status) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new ResourceNotFoundException("Order not found"));
        order.setStatus(status);
        orderRepository.save(order);
    }
}

懒加载与Bean作用域优化

对于启动时不立即需要的大对象或依赖,使用 @Lazy 延迟初始化,加速应用启动。
正确使用作用域(singleton, prototype, request, session)。

@Configuration
publicclass AppConfig {
    
    // 懒加载示例 - 仅在首次使用时初始化
    @Bean
    @Lazy
    public HeavyComponent heavyComponent() {
        returnnew HeavyComponent(); // 假设这是一个初始化耗时的组件
    }
    
    // 原型作用域Bean - 每次请求创建新实例
    @Bean
    @Scope("prototype")
    public RequestScopedComponent requestScopedComponent() {
        returnnew RequestScopedComponent();
    }
}

AOP切面优化

精确指定切点表达式 (@Pointcut),避免拦截不必要的 JoinPoint。评估 AOP 带来的性能开销是否可接受。

@Aspect
@Component
publicclass OptimizedAspect {
    
    // 精确的切点表达式,避免过度拦截
    @Pointcut("execution(* com.yourcompany.service.*Service.*(..)) && !execution(* com.yourcompany.service.*Service.get*(..))")
    public void serviceMethodPointcut() {}
    
    @Around("serviceMethodPointcut()")
    public Object optimizeServiceCalls(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        try {
            return joinPoint.proceed();
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            // 仅记录慢操作
            if (duration > 500) { // 超过500ms视为慢操作
                log.warn("Slow method: {} took {}ms", joinPoint.getSignature(), duration);
            }
        }
    }
}
  1. 组件扫描
    使用 @SpringBootApplication(scanBasePackages = "包路径") 或 @ComponentScan 明确指定扫描的基础包路径,避免扫描整个类路径 (classpath:*),减少启动时间和内存占用。
// 优化组件扫描 - 明确指定基础包而非扫描整个根包
@SpringBootApplication(scanBasePackages = {
    "com.yourcompany.service", 
    "com.yourcompany.controller.api"
})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  1. 启动优化配置
    Spring Boot 2.2+ 支持。将所有 Bean 设置为懒加载,可以显著减少启动时间(尤其是有大量 Bean 时)。注意: 这可能导致首次请求延迟变高,需权衡。

确保类路径干净,移除无用的 JAR 包。使用 Spring Boot 的 spring-boot-maven-plugin 打包为可执行 JAR(内嵌容器)或 WAR(部署到外部容器)时,依赖的组织方式会影响启动速度。

# application.yml
spring:
main:
    lazy-initialization:true# 全局懒加载
    web-application-type:servlet# 明确指定应用类型,避免自动检测耗时
    
# 仅在需要时激活相关自动配置
spring.autoconfigure.exclude:
-org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration
-org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
# 启动时排除不需要的自动配置类

4.日志优化
使用异步日志框架(如 Logback 的 AsyncAppender 或 Log4j2 的异步 Logger)减少 I/O 阻塞。
合理设置日志级别,生产环境避免 DEBUG/TRACE。
控制日志输出量,避免过于冗长的日志格式或记录不必要的信息。
二、JVM 层优化

  1. JVM参数优化示例
    针对不同场景的JVM配置:

  2. 常规Web应用(4核8G内存服务器):

java -jar app.jar \
  -Xms6g -Xmx6g \
  -XX:+UseG1GC \
  -XX:MaxGCPauseMillis=200 \
  -XX:InitiatingHeapOccupancyPercent=75 \
  -XX:+UseStringDeduplication \
  -XX:+UseContainerSupport \
  -XX:MaxRAMPercentage=75.0 \  # 容器环境中使用75%的可用内存
  -XX:+HeapDumpOnOutOfMemoryError \
  -XX:HeapDumpPath=/var/log/app/heapdump.hprof
  1. 微服务应用(小内存环境):
java -jar app.jar \
  -Xms2g -Xmx2g \
  -XX:+UseSerialGC \  # 小内存环境下SerialGC可能更高效
  -XX:NewRatio=3 \    # 老年代与年轻代比例1:3
  -XX:SurvivorRatio=4 \ # Eden:Survivor = 4:1:1
  -XX:MaxTenuringThreshold=15 \ # 对象晋升老年代的年龄阈值
  -XX:+UseCompressedOops \ # 压缩对象指针
  -XX:+UseCompressedClassPointers
  1. 虚拟线程配置(Java 21+)
    Spring Boot 3.2+ 支持虚拟线程:
# application.yml
server:
  tomcat:
    threads:
      virtual:
        enabled: true  # 启用虚拟线程

三、数据库层优化

  1. HikariCP连接池配置
    选择合适的连接池: HikariCP (Spring Boot 默认,性能优异) 是首选。Druid 功能强大(监控、防御 SQL 注入等)也是好选择。

关键参数:

maximum-pool-size:根据数据库处理能力和应用并发需求设置。不是越大越好! 过大会导致数据库连接耗尽或资源争抢。
minimum-idle:维持的最小空闲连接数。
connection-timeout:获取连接的超时时间,设置合理值避免长时间等待。
idle-timeout / max-lifetime:连接空闲超时和最大生命周期,防止连接泄漏或数据库端主动断开导致的问题。
监控连接池指标(活跃连接、空闲连接、等待线程数) 是调优的关键。

# application.yml
spring:
datasource:
    type:com.zaxxer.hikari.HikariDataSource
    url:jdbc:mysql://${DB_HOST:localhost}:3306/mydb?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
    username:${DB_USERNAME:root}
    password:${DB_PASSWORD:secret}
    driver-class-name:com.mysql.cj.jdbc.Driver
    hikari:
      # 核心配置
      maximum-pool-size:15# 最大连接数,根据CPU核心数和数据库性能调整
      minimum-idle:5        # 最小空闲连接数
      idle-timeout:300000   # 空闲超时时间(ms),默认600000(10分钟)
      connection-timeout:20000# 连接超时时间(ms)
      max-lifetime:1800000# 连接最大生命周期(ms),建议比数据库wait_timeout小30秒
      connection-test-query:SELECT1# 连接测试查询
      pool-name:MyAppHikariCP# 线程池名称,便于监控
      auto-commit:false       # 禁用自动提交,在事务中手动控制
      # 高级配置
      leak-detection-threshold:60000# 连接泄漏检测阈值(ms)
      data-source-properties:
        cachePrepStmts:true           # 启用预处理语句缓存
        prepStmtCacheSize:250         # 预处理语句缓存大小
        prepStmtCacheSqlLimit:2048    # 预处理语句最大长度
        useServerPrepStmts:true       # 使用服务器端预处理
  1. JPA/Hibernate优化配置
# application.yml
spring:
jpa:
    hibernate:
      ddl-auto:validate# 生产环境禁用update/create/drop
      naming:
        physical-strategy:org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    properties:
      hibernate:
        jdbc.batch_size:30               # 批量操作大小
        order_inserts:true               # 启用插入排序优化批量操作
        order_updates:true               # 启用更新排序优化批量操作
        jdbc.batch_versioned_data:true   # 批量操作时处理版本控制
        show_sql:false                   # 生产环境禁用SQL显示
        format_sql:false
        generate_statistics:false         # 生产环境禁用统计
        cache:
          use_second_level_cache:true    # 启用二级缓存
          region.factory_class:org.hibernate.cache.jcache.JCacheRegionFactory
          use_query_cache:true           # 启用查询缓存
    open-in-view:false# 关闭Open Session In View反模式!解决N+1问题和连接泄漏风险
  1. SQL优化示例
    JPA查询优化(避免N+1问题):

N+1 查询问题:这是 ORM 最常见性能问题。使用 JOIN FETCH (JPA), @Fetch(FetchMode.JOIN) (Hibernate), 或 @NamedEntityGraph 来一次性加载关联数据。避免在循环中触发懒加载。

@Repository
publicinterface OrderRepository extends JpaRepository<Order, Long> {
    
    // 正确:使用JOIN FETCH一次性加载关联
    @Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.id = :id")
    Optional<Order> findByIdWithItems(@Param("id") Long id);
    
    // 分页查询优化
    @Query("SELECT o FROM Order o WHERE o.status = :status ORDER BY o.createTime DESC")
    Page<Order> findByStatus(@Param("status") OrderStatus status, Pageable pageable);
    
    // 投影查询 - 只查询需要的字段
    @Query("SELECT new com.example.dto.OrderSummaryDTO(o.id, o.orderNo, o.createTime, o.status) " +
           "FROM Order o WHERE o.customerId = :customerId")
    List<OrderSummaryDTO> findOrderSummariesByCustomerId(@Param("customerId") Long customerId);
}

MyBatis批量操作示例:

MyBatis 优化:合理使用一级/二级缓存;避免动态 SQL 过于复杂;使用 foreach 进行批量操作;注意 #{} 和 ${} 的区别(防注入)。

<!-- OrderMapper.xml -->
<insert id="batchInsertOrders">
    INSERT INTO orders (order_no, customer_id, status, create_time)
    VALUES
    <foreach collection="orders" item="order" separator=",">
        (#{order.orderNo}, #{order.customerId}, #{order.status}, #{order.createTime})
    </foreach>
</insert>

四、缓存优化
本地缓存 (Caffeine, Ehcache): 速度快,适合缓存少量、不易变、对一致性要求不高的数据。注意单机缓存的一致性和内存限制。
分布式缓存 (Redis, Memcached): 适合共享缓存、大量缓存数据、高可用场景。Redis 功能更丰富(数据结构、持久化等)。

  1. Redis缓存集成与配置
@Configuration
@EnableCaching
publicclass RedisCacheConfig {

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        // 默认配置
        RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30))  // 默认30分钟过期
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
                new GenericJackson2JsonRedisSerializer()))
            .disableCachingNullValues();  // 不缓存null值
            
        // 特定缓存配置
        Map<String, RedisCacheConfiguration> configs = new HashMap<>();
        
        // 产品缓存时间较长
        configs.put("products", defaultConfig.entryTtl(Duration.ofHours(24)));
        
        // 用户缓存时间中等
        configs.put("users", defaultConfig.entryTtl(Duration.ofHours(1)));
        
        // 热点数据缓存时间短,但使用随机过期避免缓存雪崩
        configs.put("hotData", defaultConfig.entryTtl(Duration.ofMinutes(10)));
        
        return RedisCacheManager.builder(connectionFactory)
            .cacheDefaults(defaultConfig)
            .withInitialCacheConfigurations(configs)
            .transactionAware()  // 支持事务缓存
            .build();
    }
}
  1. 缓存使用示例
@Service
publicclass ProductService {
    
    privatefinal ProductRepository productRepository;
    
    // 构造函数注入...
    
    // 基本缓存示例
    @Cacheable(value = "products", key = "#id", unless = "#result == null")
    public Product findById(Long id) {
        return productRepository.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("Product not found"));
    }
    
    // 条件缓存示例
    @Cacheable(value = "products", key = "#code", condition = "#code.length() > 3")
    public Product findByCode(String code) {
        return productRepository.findByCode(code)
            .orElseThrow(() -> new ResourceNotFoundException("Product not found"));
    }
    
    // 缓存更新
    @CachePut(value = "products", key = "#product.id")
    public Product updateProduct(Product product) {
        return productRepository.save(product);
    }
    
    // 缓存删除
    @CacheEvict(value = "products", key = "#id")
    public void deleteProduct(Long id) {
        productRepository.deleteById(id);
    }
    
    // 清空缓存
    @CacheEvict(value = "products", allEntries = true)
    @Scheduled(fixedRate = 86400000)  // 每天凌晨清空一次缓存
    public void clearProductCache() {
        log.info("Clearing product cache");
    }
}
  1. 缓存问题解决方案示例
    缓存穿透防护(空值缓存+布隆过滤器):
@Service
publicclass ProductServiceImpl implements ProductService {
    
    privatefinal ProductRepository productRepository;
    privatefinal BloomFilter<Long> productIdBloomFilter;
    
    // 构造函数注入...
    
    @Override
    @Cacheable(value = "products", key = "#id", unless = "#result == null")
    public Product findById(Long id) {
        // 先检查布隆过滤器,如果不存在直接返回null
        if (!productIdBloomFilter.mightContain(id)) {
            returnnull;
        }
        
        Product product = productRepository.findById(id).orElse(null);
        
        // 缓存空值防止缓存穿透
        if (product == null) {
            // 缓存空值5分钟
            redisTemplate.opsForValue().set("products::" + id, null, 5, TimeUnit.MINUTES);
        }
        
        return product;
    }
}

缓存雪崩防护(添加随机过期时间):

@Configuration
publicclass CacheConfig {
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        // 默认配置
        RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30))
            // 其他配置...
            
        return RedisCacheManager.builder(connectionFactory)
            .cacheDefaults(defaultConfig)
            .withCacheConfiguration("products", 
                defaultConfig.entryTtl(Duration.ofMinutes(30 + new Random().nextInt(10)))) // 30-40分钟随机过期
            .build();
    }
}

五、异步与并发优化

  1. @Async异步方法与线程池配置
    将耗时且非结果依赖的操作(如发邮件、记录日志、调用外部系统)异步化,释放请求线程。
    配置自定义线程池 (ThreadPoolTaskExecutor),避免使用默认的 SimpleAsyncTaskExecutor(为每个任务新建线程)。
    设置合理的核心线程数、最大线程数、队列容量、拒绝策略。
@Configuration
@EnableAsync
publicclass AsyncConfig {
    
    @Bean(name = "taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数 = CPU核心数 + 1
        int corePoolSize = Runtime.getRuntime().availableProcessors() + 1;
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(corePoolSize * 2);  // 最大线程数
        executor.setQueueCapacity(100);              // 队列容量
        executor.setThreadNamePrefix("Async-");      // 线程名前缀
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 拒绝策略:调用者执行
        executor.setAwaitTerminationSeconds(60);     // 等待终止时间
        executor.setWaitForTasksToCompleteOnShutdown(true); // 关闭时等待任务完成
        executor.initialize();
        return executor;
    }
}

@Service
publicclass NotificationService {
    
    privatefinal EmailClient emailClient;
    
    // 构造函数注入...
    
    @Async("taskExecutor")  // 指定线程池
    public CompletableFuture<Void> sendOrderConfirmationEmail(Order order) {
        try {
            // 模拟邮件发送耗时操作
            Thread.sleep(1000);
            emailClient.send(order.getCustomerEmail(), "Order Confirmation", 
                            "Thank you for your order #" + order.getId());
            return CompletableFuture.completedFuture(null);
        } catch (Exception e) {
            log.error("Failed to send email", e);
            return CompletableFuture.failedFuture(e);
        }
    }
}
  1. 响应式编程示例(WebFlux)
    对于高并发、IO 密集型(如微服务间调用、数据库访问)应用,考虑使用 Spring WebFlux 和非阻塞式驱动(如 R2DBC, Reactive MongoDB)。
    利用少量线程处理大量并发连接,提高资源利用率。
    注意: 编程模型与 Servlet 不同,需要学习曲线。确保整个调用链都是非阻塞的。
@RestController
@RequestMapping("/api/orders")
publicclass OrderController {

    privatefinal OrderService orderService;
    
    // 构造函数注入...
    
    @GetMapping("/{id}")
    public Mono<ResponseEntity<OrderDTO>> getOrder(@PathVariable Long id) {
        return orderService.findById(id)
            .map(order -> ResponseEntity.ok(convertToDTO(order)))
            .defaultIfEmpty(ResponseEntity.notFound().build());
    }
    
    @GetMapping
    public Flux<OrderSummaryDTO> getOrdersByCustomer(@RequestParam Long customerId) {
        return orderService.findByCustomerId(customerId)
            .map(this::convertToSummaryDTO)
            .sort((o1, o2) -> o2.getCreateTime().compareTo(o1.getCreateTime()));
    }
}

@Service
publicclass OrderService {
    privatefinal OrderRepository orderRepository;
    
    // 构造函数注入...
    
    public Mono<Order> findById(Long id) {
        return Mono.fromCallable(() -> orderRepository.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("Order not found")));
    }
    
    public Flux<Order> findByCustomerId(Long customerId) {
        return Flux.fromIterable(orderRepository.findByCustomerId(customerId));
    }
}

六、完整的性能优化实践案例
案例1:数据库查询优化前后对比
优化前(问题代码):

// 问题:N+1查询和全表扫描
@GetMapping("/{orderId}")
public OrderDTO getOrder(@PathVariable Long orderId) {
    Order order = orderRepository.findById(orderId).orElseThrow();
    // 每次调用getItems()都会触发额外查询
    List<OrderItem> items = order.getItems(); 
    // ... 转换为DTO
}

优化后:

// 优化:使用JOIN FETCH + 投影DTO
@GetMapping("/{orderId}")
public OrderSummaryDTO getOrderSummary(@PathVariable Long orderId) {
    return orderRepository.findOrderSummaryById(orderId)
        .orElseThrow(() -> new ResourceNotFoundException("Order not found"));
}

// Repository方法
@Query("SELECT new com.example.dto.OrderSummaryDTO(o.id, o.orderNo, o.createTime, " +
       "SUM(oi.quantity * oi.unitPrice) as totalAmount, " +
       "COUNT(oi) as itemCount) " +
       "FROM Order o JOIN o.items oi WHERE o.id = :id " +
       "GROUP BY o.id, o.orderNo, o.createTime")
Optional<OrderSummaryDTO> findOrderSummaryById(@Param("id") Long id);

案例2:缓存策略实现
多级缓存实现(Caffeine本地缓存 + Redis分布式缓存):

@Configuration
@EnableCaching
publicclass MultiLevelCacheConfig {

    // Caffeine本地缓存配置
    @Bean
    public Cache<String, Object> caffeineCache() {
        return Caffeine.newBuilder()
            .maximumSize(10_000)          // 最大缓存项数
            .expireAfterWrite(5, TimeUnit.MINUTES)  // 写入后过期时间
            .recordStats()                // 开启统计
            .build();
    }
    
    // Redis分布式缓存配置
    @Bean
    public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
        // 配置Redis缓存
        // ... 省略RedisCacheManager配置,同上一节
    }
    
    // 自定义缓存管理器,结合本地缓存和Redis缓存
    @Bean
    public CacheManager cacheManager(RedisCacheManager redisCacheManager, Cache<String, Object> caffeineCache) {
        returnnew MultiLevelCacheManager(redisCacheManager, caffeineCache);
    }
}

// 自定义多级缓存管理器实现
publicclass MultiLevelCacheManager implements CacheManager {
    privatefinal RedisCacheManager redisCacheManager;
    privatefinal Cache<String, Object> caffeineCache;
    
    // 构造函数注入...
    
    @Override
    public Cache getCache(String name) {
        returnnew MultiLevelCache(
            name, 
            caffeineCache, 
            redisCacheManager.getCache(name)
        );
    }
    
    // 其他方法实现...
}

// 多级缓存实现类
class MultiLevelCache implements Cache {
    privatefinal String name;
    privatefinal Cache<String, Object> localCache;
    privatefinal Cache redisCache;
    
    // 实现get、put、evict等方法,先查本地缓存,再查Redis缓存...
}

七、监控与诊断
启用 Actuator:spring-boot-starter-actuator 提供丰富的生产就绪端点 (/actuator/metrics, /actuator/health, /actuator/prometheus 等)。

**集成监控系统:

Prometheus + Grafana: 强大的指标采集和可视化组合。使用 micrometer-registry-prometheus。
ELK Stack (Elasticsearch, Logstash, Kibana): 集中式日志管理和分析。
分布式追踪: Zipkin, Jaeger。集成 Spring Cloud Sleuth 来追踪请求在微服务间的流转。
Profiler 工具: 使用 VisualVM, YourKit, JProfiler, Arthas 等进行 CPU 采样、内存分析、线程分析,定位热点方法和内存泄漏。

APM (应用性能监控):New Relic, Dynatrace, AppDynamics, SkyWalking, Pinpoint 等提供端到端的应用性能洞察。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • """1.个性化消息: 将用户的姓名存到一个变量中,并向该用户显示一条消息。显示的消息应非常简单,如“Hello ...
    她即我命阅读 8,526评论 0 5
  • 为了让我有一个更快速、更精彩、更辉煌的成长,我将开始这段刻骨铭心的自我蜕变之旅!从今天开始,我将每天坚持阅...
    李薇帆阅读 5,974评论 0 3
  • 似乎最近一直都在路上,每次出来走的时候感受都会很不一样。 1、感恩一直遇到好心人,很幸运。在路上总是...
    时间里的花Lily阅读 5,229评论 0 2
  • 1、expected an indented block 冒号后面是要写上一定的内容的(新手容易遗忘这一点); 缩...
    庵下桃花仙阅读 3,561评论 0 1
  • 一、工具箱(多种工具共用一个快捷键的可同时按【Shift】加此快捷键选取)矩形、椭圆选框工具 【M】移动工具 【V...
    墨雅丫阅读 3,550评论 0 0