禅与计算机 禅与计算机
首页
  • Java基础

    • 聊一聊java一些核心知识点
    • 聊聊java面向对象核心知识点
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 写作

    • 写好技术博客的5大核心原则:从认知科学到AI工具的全流程指南
  • 开发工具

    • IDEA配置详解与高效使用指南
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • 从Lucene到Elasticsearch:进化之路
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

sharkchili

计算机禅修者
首页
  • Java基础

    • 聊一聊java一些核心知识点
    • 聊聊java面向对象核心知识点
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 写作

    • 写好技术博客的5大核心原则:从认知科学到AI工具的全流程指南
  • 开发工具

    • IDEA配置详解与高效使用指南
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • 从Lucene到Elasticsearch:进化之路
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java基础

  • 并发编程

  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JDK 方法区变迁史:版本间的差异与改进
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 来聊聊大厂面试常问的指针压缩
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • JITWatch工具使用指南
    • G1垃圾回收器:原理详解与调优指南
    • 写给新手的一个JVM调优入门级案例
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
      • 写在文章开头
      • JVM常见命令介绍
      • 详解JVM的GC
        • 定位GC情况的两种方式
        • 拆解与分析JVM的GC日志
      • 了解JVM几种GC的区别
      • 频繁的minor gc问题与解决思路
        • 问题复现
        • 问题拆解与修复
        • 将年轻代空间调大,是否会更加耗时?
      • 频繁的FULL GC
        • 复现问题
        • 排查思路
        • 频繁FULL GC的原因和解决对策
      • 小结
      • 参考
    • MAT内存分析工具安装配置
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
    • JVM系列文章汇总
  • 深入理解Spring框架

  • Java核心技术
  • JVM相关
sharkchili
2026-03-25
目录

JVM内存溢出排查指南

@[toc]

# 写在文章开头

本文蒋针对常见的几种GC情况结合日志进行分析介绍,希望对你有帮助。

我是 SharkChili ,Java 开发者,Java Guide 开源项目维护者。欢迎关注我的公众号:写代码的SharkChili,也欢迎您了解我的开源项目 mini-redis:https://github.com/shark-ctrl/mini-redis (opens new window)。

为方便与读者交流,现已创建读者群。关注上方公众号获取我的联系方式,添加时备注加群即可加入。

# JVM常见命令介绍

篇幅原因关于JVM常见指令的使用与介绍,需要了解的读者可以移步参考笔者写的这篇文章 ,JVM指令集概览:基础与应用:https://mp.weixin.qq.com/s/wKBAHPflLmItfM6E7Ydnyg (opens new window)

# 详解JVM的GC

# 定位GC情况的两种方式

获取GC日志方式大抵有两种,第一种就是设定JVM参数在程序启动时查看,具体的命令参数为:

-XX:+PrintGCDetails # 打印GC日志
-XX:+PrintGCTimeStamps # 打印每一次触发GC时发生的时间
1
2

第二种则是在服务器上监控:使用jstat查看,如下所示,命令格式为jstat -gc pid 输出间隔时长 输出次数,例如笔者希望每隔1秒输出1次,并且打印5次,对应的指令如下:

jstat -gc 21608 1000 5
1

# 拆解与分析JVM的GC日志

为了演示如何查看GC日志,笔者给出下面这样一段代码并结合JVM参数配置(后文会给到)展示JVM如何进行垃圾回收:


public static void main(String[] args) {
        //分配1M内存空间
        byte[] bytes = new byte[1024 * 1024];
        //触发minor gc,剩余512k,然后将1M空间存放至新生代,堆空间大约剩下1.5M
        bytes = new byte[1024 * 1024];
        //分配至新生代约2.5M
        bytes = new byte[1024 * 1024];

        //新生代空间不足,触发full gc,新生代空间全回收,并执行CMS GC,完成后将对象存放至新生代
        byte[] byte2 = new byte[2 * 1024 * 1024];
    }

1
2
3
4
5
6
7
8
9
10
11
12
13

对应我们也给出如下JVM配置参数指明新生代、老年代堆空间大小为5M,并指明新生代Eden和survivor区的比例为8:1:1,同时我们也指定的新生代和老年代垃圾回收算法分别是ParNewGC和CMS:

-XX:NewSize=5M -XX:MaxNewSize=5M -XX:InitialHeapSize=10M -XX:MaxHeapSize=10M -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=10M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
1

详细解释的含义如下,读者可自行参阅:


-XX:NewSize=5M:设置新生代的初始大小为 5MB。
-XX:MaxNewSize=5M:设置新生代的最大大小为 5MB。
-XX:InitialHeapSize=10M:设置 JVM 堆的初始大小为 10MB。
-XX:MaxHeapSize=10M:设置 JVM 堆的最大大小为 10MB。
-XX:SurvivorRatio=8:设置 Eden 区与 Survivor 区的比例为 8,即 Eden 占用 8/10 的新生代空间,两个 Survivor 各占 1/10。
-XX:PretenureSizeThreshold=10M:设置对象直接进入老年代的阈值为 10MB,超过这个大小的对象会直接分配到老年代。
-XX:+UseParNewGC:启用并行新生成收集器(Parallel New Generation Collector),用于多线程环境下的新生代垃圾回收。
-XX:+UseConcMarkSweepGC:启用并发标记清除收集器(Concurrent Mark Sweep Collector),用于多线程环境下的老年代垃圾回收。
-XX:+PrintGCDetails:打印详细的垃圾回收日志信息。
-XX:+PrintGCTimeStamps:在垃圾回收日志中添加时间戳。
1
2
3
4
5
6
7
8
9
10
11

此时我们就以逐行执行的方式讲解GC过程:

  1. 首先代码执行到byte[] bytes = new byte[1024 * 1024];,此时新生代空间充裕,没有任何输出。
  2. 执行第二行代码bytes = new byte[1024 * 1024];再次进程内存分配时,发现新生代空间不足出现以此minor gc

对应输出结果如下,我们大体可以看出GC原因是Allocation Failure即新生代不能分配对象,触发一次新生代GC,新生代GC前后空间由3348K变为512K,整堆空间由3348K变为1692K,最后输出了GC耗时、系统响应耗时以及应用程序暂停时间:

2.938: [GC (Allocation Failure) 2.938: [ParNew: 3348K->512K(4608K), 0.0016244 secs] 3348K->1692K(9728K), 0.0016904 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
1

完成上述GC,将1M的数组存放至新生代,此时新生代的堆空间大约是1.5M:

然后第三行再次分配数组,新生代空间充裕,直接存入:

最后一次分配2M数组时,从日志中我们可以看到minor gc直接将上述的所有字节数组都回收了:

9.689: [GC (Allocation Failure) 9.689: [ParNew: 2626K->0K(4608K), 0.0021520 secs] 3806K->2746K(9728K), 0.0021903 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
1

在新生代空间不足尝试到老年代分配对象时,因为堆空间且空间分配担保失败,不够直接触发FULL GC。从日志中可以看到CMS老年代GC,首先进行初始标记阶段该阶段为STW并找到所有的GC root,从日志中我们看到:

  1. 老年代使用的容量为2718K且总容量为5120K
  2. 当前堆的实际大小和总容量为4766K(9728K)标记:
2.057: [GC (CMS Initial Mark) [1 CMS-initial-mark: 2718K(5120K)] 4766K(9728K), 0.0005690 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
1

然后进入并发标记阶段该阶段不会STW,执行如下步骤:

  1. CMS-concurrent-mark标记 gc root可达的老年代对象
  2. CMS-concurrent-preclean该阶段会处理那些dirty card中标记的对象,dirty card即脏卡,本质上那个就是处理那些并发标记阶段引用关系发生变化的区域,通过在这个阶段尽可能的标记可以减少最终标记即remark阶段的耗时
  3. CMS-concurrent-abortable-preclean即并发可终止的预清理阶段,继续执行步骤2的任务,同样是为了减小remark阶段的工作量,默认情况下CMS要求这一步必须完成Eden剩余50%或耗时超过5s才能进入下一个阶段:
2.058: [CMS-concurrent-mark-start]
2.059: [CMS-concurrent-mark: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2.059: [CMS-concurrent-preclean-start]
2.059: [CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2.059: [CMS-concurrent-abortable-preclean-start]
 CMS: abort preclean due to time 7.163: [CMS-concurrent-abortable-preclean: 0.005/5.105 secs] [Times: user=0.00 sys=0.00, real=5.10 secs] 
1
2
3
4
5
6

最后就到了最终标记阶段,该阶段会STW,从日志输出可以看出新生代占用2048k,当前这个重新标记阶段Rescan 花费了0.0004620 secs,其余就是处理弱引用、卸载无用的类以及清理元数据等花费时间和耗时:

7.164: [GC (CMS Final Remark) [YG occupancy: 2048 K (4608 K)]7.164: [Rescan (parallel) , 0.0004620 secs]7.164: [weak refs processing, 0.0001727 secs]7.164: [class unloading, 0.0005772 secs]7.165: [scrub symbol table, 0.0011975 secs]7.166: [scrub string table, 0.0003404 secs][1 CMS-remark: 2718K(5120K)] 4766K(9728K), 0.0030256 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
1

最后就是并发的清理垃圾会重置标记,等待下一个周期的GC:

7.167: [CMS-concurrent-sweep-start]
7.168: [CMS-concurrent-sweep: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
7.168: [CMS-concurrent-reset-start]
7.168: [CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
1
2
3
4

最后我们查看内存使用情况可以看到,新生代的2M就是我们最后分配的数组,在eden区,而老年代使用了1677K:

Heap
 par new generation   total 4608K, used 2089K [0x00000000ff600000, 0x00000000ffb00000, 0x00000000ffb00000)
  eden space 4096K,  51% used [0x00000000ff600000, 0x00000000ff80a558, 0x00000000ffa00000)
  from space 512K,   0% used [0x00000000ffa00000, 0x00000000ffa00000, 0x00000000ffa80000)
  to   space 512K,   0% used [0x00000000ffa80000, 0x00000000ffa80000, 0x00000000ffb00000)
 concurrent mark-sweep generation total 5120K, used 1677K [0x00000000ffb00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3124K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 327K, capacity 386K, committed 512K, reserved 1048576K
1
2
3
4
5
6
7
8

# 了解JVM几种GC的区别

  1. Minor GC:发生在年轻代的空间回收,包含eden和survivor,也叫做Young GC。
  2. Major GC:在老年代堆区进行空间回收。
  3. Full GC:清理所有堆区的内存空间的垃圾内存,包括年轻代和老年代。

# 频繁的minor gc问题与解决思路

# 问题复现

我们尝试编写一个程序,设置该程序的堆内存新生代为5M,按照8:1:1的比例分配,这也就意为着Eden区内存大小为4M,然后S区分别是512K,这也就意味着在待分配对象加Eden区堆空间超过4M就会触发minor gc:

基于上述说法,我们给出下面这段代码:

public static void main(String[] args) throws Exception {
        while (true) {
            //分配3M数组
            byte[] bytes = new byte[1024 * 1024];
            bytes = new byte[1024 * 1024];
            bytes = new byte[1024 * 1024];

            //创建2M的新对象触发GC
            byte[] byte2 = new byte[2 * 1024 * 1024];

            Thread.sleep(1000);
        }

    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

为了演示年轻代的回收行为,我们需要在对这个应用程序的年轻代堆内存改为5M,且Eden区和S区的比例为8:1:1,同时也打印GC日志信息:

-XX:NewSize=5M -XX:MaxNewSize=5M -XX:InitialHeapSize=10M -XX:MaxHeapSize=10M -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=10M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
1

输出结果如下,GC日志显示每秒基本都会触发一次Minor GC,进而间接导致频繁的major gc:

# 问题拆解与修复

结合我们的配置可知,我们频繁分配对象导致新生代进行频繁的GC,又因为S区大小无法容纳存活的对象,进而使得这些对象提前进入老年代,导致major GC也随之频繁,所以解决的办法也比较简单,按照等比例调整大堆空间,即将新生代堆空间调整至10M,保证S区各有2M空间以容纳新生代存活的对象:

-XX:NewSize=10M -XX:MaxNewSize=10M -XX:InitialHeapSize=100M -XX:MaxHeapSize=100M -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=10M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
1

可以看到经过调整之后,基本上Minor gc就能解决问题:

# 将年轻代空间调大,是否会更加耗时?

答案是不会的,原因有以下2点:

  1. JVM操作本质上都是内存操作,相对而言不会太慢。
  2. 我们将一次GC的时间拆分为t1和t2,t1是扫描年轻代空间是否有垃圾的时间,这个时间的几乎可以忽略不计。而t2则是将eden空间存活的对象复制到survivor区的时间,这个复制操作则是t1时间的10倍。

由此可以看出,避免耗时的正确做法是合理评估新生代堆空间,减少非必要的复制操作,所以说调整新生代的空间并不会导致进一步的耗时问题。

# 频繁的FULL GC

# 复现问题

我们现在模拟一个场景,我们的应用中有一个定时任务,这个定时任务每隔1s会想另一个定时任务线程池中提交100个任务,每个任务都会针对Obj 对象进行方法调用:

@Component
public class Task {
    private static Logger logger = LoggerFactory.getLogger(Task.class);


    private static final ScheduledThreadPoolExecutor executor =
            new ScheduledThreadPoolExecutor(50,
                    new ThreadPoolExecutor.DiscardOldestPolicy());

	
    private static class Obj {
        private String name = "name";
        private int age = 18;
        private String gender = "man";
        private LocalDate birthday = LocalDate.MAX;

        public void func() {
            //这个方法什么也不做
        }

		//返回count个Obj对象
        private static List<Obj> getObjList(int count) {

            List<Obj> objList = new ArrayList<>(count);

            for (int i = 0; i != count; ++i) {
                objList.add(new Obj());
            }
            return objList;
        }
    }

    @Scheduled(cron = "0/1 * *  * * ? ")   //每1秒执行一次
    public void execute() {
        logger.info("1s一次定时任务");
        //向线程池提交100个任务
        Obj.getObjList(100).forEach(i -> executor.scheduleWithFixedDelay(
                i::func, 2, 3, TimeUnit.SECONDS
        ));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

完成后我们设置下面这段JVM参数后,将其启动:

-Xms20M -Xmx20M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
1

不久后,控制台出现频繁的full gc,如果在生产环境,频繁的full gc导致stw会导致系统吞吐量下降:

.......
1288.133: [Full GC (Allocation Failure) 1288.133: [CMS1288.142: [CMS-concurrent-preclean: 0.012/0.012 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
 (concurrent mode failure): 13695K->13695K(13696K), 0.0610050 secs] 19839K->19836K(19840K), [Metaspace: 29026K->29026K(1077248K)], 0.0610521 secs] [Times: user=0.06 sys=0.00, real=0.06 secs] 
1288.258: [Full GC (Allocation Failure) 1288.258: [CMS: 13695K->13695K(13696K), 0.0612134 secs] 19839K->19836K(19840K), [Metaspace: 29026K->29026K(1077248K)], 0.0612676 secs] [Times: user=0.06 sys=0.00, real=0.06 secs] 
1288.320: [GC (CMS Initial Mark) [1 CMS-initial-mark: 13695K(13696K)] 19836K(19840K), 0.0041303 secs] [Times: user=0.03 sys=0.00, real=0.00 secs] 
......
1
2
3
4
5
6

# 排查思路

我们定位到程序号后,使用jstat -gc pid10000 10观察其gc情况,可以看到每隔10s,就会增加大量的full gc:


 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
640.0  640.0   0.0   640.0   5504.0   665.5    13696.0    11176.2   31488.0 28992.6 4352.0 3889.5     39    0.084  15      0.100    0.184
640.0  640.0   0.0   640.0   5504.0   1487.2   13696.0    11176.2   31488.0 28992.6 4352.0 3889.5     39    0.084  25      0.142    0.227
640.0  640.0   0.0   640.0   5504.0   1697.8   13696.0    11176.2   31488.0 28992.6 4352.0 3889.5     39    0.084  35      0.185    0.269
......
1
2
3
4
5
6

再查看jmap -heap pid查看堆区使用情况,可以看到老年代的使用率还是蛮高的:

 
Attaching to process ID 26176, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.212-b10

using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 20971520 (20.0MB)
   NewSize                  = 6946816 (6.625MB)
   MaxNewSize               = 6946816 (6.625MB)
   OldSize                  = 14024704 (13.375MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor Space):
   capacity = 6291456 (6.0MB)
   used     = 5088288 (4.852569580078125MB)
   free     = 1203168 (1.147430419921875MB)
   80.87615966796875% used
Eden Space:
   capacity = 5636096 (5.375MB)
   used     = 5088288 (4.852569580078125MB)
   free     = 547808 (0.522430419921875MB)
   90.28036428052326% used
From Space:
   capacity = 655360 (0.625MB)
   used     = 0 (0.0MB)
   free     = 655360 (0.625MB)
   0.0% used
To Space:
   capacity = 655360 (0.625MB)
   used     = 0 (0.0MB)
   free     = 655360 (0.625MB)
   0.0% used
concurrent mark-sweep generation:
   capacity = 14024704 (13.375MB)
   used     = 13819664 (13.179458618164062MB)
   free     = 205040 (0.1955413818359375MB)
   98.53800836010514% used

12064 interned Strings occupying 1120288 bytes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

在排除内存泄漏的问题后,我们通过jmap定位进程中导致是什么对象导致老年代堆区被大量占用:

jmap -histo 7476 | head -n 20
1

可以看到前20名中的对象都是和定时任务相关,有一个Task$Obj对象非常抢眼,很明显就是因为它的数量过多导致的,此时我们就可以通过定位代码确定如何解决,常见方案无非是: 优化代码、增加空间两种方式,一般来说我们都会采用代码优化的方式去解决。

$ 

 num     #instances         #bytes  class name
----------------------------------------------
   1:         50760        3654720  java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask
   2:         30799        2901552  [C
   3:         88986        2847552  java.util.concurrent.locks.AbstractQueuedSynchronizer$Node
   4:         50700        1622400  com.example.jstackTest.Task$Obj
   5:         50760        1218240  java.util.concurrent.Executors$RunnableAdapter
   6:         50700         811200  com.example.jstackTest.Task$$Lambda$587/1605553313
   7:          6391         707928  java.lang.Class
   8:         29256         702144  java.lang.String
   9:         13577         434464  java.util.concurrent.ConcurrentHashMap$Node
  10:          6363         341016  [Ljava.lang.Object;
  11:          1722         312440  [B
  12:          3414         230424  [I
  13:             4         210680  [Ljava.util.concurrent.RunnableScheduledFuture;
  14:          5223         208920  java.util.LinkedHashMap$Entry
  15:          2297         202136  java.lang.reflect.Method
  16:          2262         193760  [Ljava.util.HashMap$Node;
  17:          5668         181376  java.util.HashMap$Node

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

而本次问题也很明显,任务是一个个提交到定时任务线程池中,是由于定时任务队列DelayedWorkQueue不断堆积任务导致内存被打满。所以最终改成将一个批处理一次性提交到定时任务中立刻将这一批对象回收从而避免耗时任务堆积一堆对象:

 @Scheduled(cron = "0/1 * *  * * ? ")   //每1秒执行一次
    public void execute() {
        logger.info("1s一次定时任务");
        //向线程池提交100个任务


        executor.scheduleWithFixedDelay(() -> {
                    Obj.getObjList(100).forEach(i -> i.func());
                }, 2, 3, TimeUnit.SECONDS
        );


    }
1
2
3
4
5
6
7
8
9
10
11
12
13

# 频繁FULL GC的原因和解决对策

总的来说原因可以频繁FULL GC分为3个:

  1. 用户频繁调用System.gc():这种情况需要修改代码即可,我们不该频繁调用这个方法的。
  2. 老年区空间过小:视情况适当扩大空间。
  3. 大对象过多:这种情况视情况决定是扩大老年代空间或者将大对象拆分。

一般来说,我们优先考虑调整堆内存空间,其次才是针对业务逻辑的代码处理进行更进一步的优化。

# 小结

本文通过几个比较经典的示例演示了JVM进行垃圾回收的过程以及如何进行JVM内存调优,希望对你有帮助。

我是 SharkChili ,Java 开发者,Java Guide 开源项目维护者。欢迎关注我的公众号:写代码的SharkChili,也欢迎您了解我的开源项目 mini-redis:https://github.com/shark-ctrl/mini-redis (opens new window)。

为方便与读者交流,现已创建读者群。关注上方公众号获取我的联系方式,添加时备注加群即可加入。

# 参考

JVM学习(7)Stop-The-World:https://www.jianshu.com/p/d686e108d15f (opens new window)

JVM调优——之CMS GC日志分析 :https://www.cnblogs.com/onmyway20xx/p/6590603.html (opens new window)

从实际案例聊聊Java应用的GC优化:https://tech.meituan.com/2017/12/29/jvm-optimize.html#:~:text= 通常情况下,由于新生代空间较小,Eden区很快被填满,就会导致频繁Minor GC,因此可以通过增大新生代空间来降低Minor,GC的频率。 例如在相同的内存分配率的前提下,新生代中的Eden区增加一倍,Minor GC的次数就会减少一半。 (opens new window)

Spring Boot定时任务详解(线程池方式):https://blog.csdn.net/Alian_1223/article/details/120455623 (opens new window)

SpringBoot使用@Scheduled注解实现定时任务:https://blog.csdn.net/pan_junbiao/article/details/109399280 (opens new window)

面渣逆袭(Java 虚拟机-JVM面试题八股文)必看👍:https://tobebetterjavaer.com/sidebar/sanfene/jvm.html#_39-频繁-full-gc-怎么办 (opens new window)

java8添加并查看GC日志(ParNew+CMS):https://segmentfault.com/a/1190000021453229 (opens new window) JVM: GC过程总结(minor GC 和 Full GC):https://juejin.cn/post/7249236479199969340?share_token=13CC37C0-45DD-4022-B326-4087FC939F8E (opens new window)

JVM调优实战:解决CMS concurrent-abortable-preclean LongGC的问题:https://developer.aliyun.com/article/776161 (opens new window)

编辑 (opens new window)
上次更新: 2026/03/26, 01:05:31
JVM内存问题排错最佳实践
MAT内存分析工具安装配置

← JVM内存问题排错最佳实践 MAT内存分析工具安装配置→

最近更新
01
基于EasyExcel实现高效导出
03-25
02
从开源框架中学习那些实用的位运算技巧
03-25
03
浅谈分布式架构设计思想和常见优化手段
03-25
更多文章>
Theme by Vdoing | Copyright © 2025-2026 Evan Xu | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×
×