Java内存相关知识

Java内存问题排查和解决

内存都有啥

  • 编译后地址是逻辑地址,需要经过编译映射到物理内存

  • MMU负责地址的转换

  • 可用内存 = 物理内存 + 虚拟内存(swap)

  • RES 实际内存占用

  • 可用内存 = free + buffers + cached

  • linux使用时,迅速占满内存

  • 堆:JVM堆中的数据是共享的,是占用内存最大的一块区域

  • 虚拟机栈:Java虚拟机栈,是基于线程的,用来服务字节码指令的运行

  • 程序计数器:当前线程所执行的字节码的行号指示器

  • 元空间:方法区的位置,非堆

  • 本地内存:其他的内存占用空间

Java内存管理基本概念:

内存:

  • Java内存
  • 操作系统

Java内存:

  • Java堆内存
  • 元空间(堆外)

Java堆内存

  • JVM分配的Java内存对象
  • 通常使用 -Xmx -Xms 控制大小

元空间

  • Metaspace默认无上限
  • 原方法区在这里

内存划分:

  • JVM进程内存 = 堆内内存 + 堆外内存
  • 堆外内存 = 元空间 + CodeCache + 本地内存
  • 堆外内存和操作系统剩余内存是此消彼长的关系
  • 可分配的内存大小 = 物理内存 - SWAP

32位内存限制4GB,目前ZGC支持16TB内存

控制参数:

  • 堆 -Xms -XMx
  • 元空间 -XX:MaxMetaspaceSize -XX:MetaspaceSize
  • 栈: -Xss
  • 直接内存: -XX:MaxDirectMemorySize
  • 其他堆外内存无法控制

垃圾回收:

  • 自动垃圾回收:JVM自动检测和释放不再使用的内存
  • Java运行时JVM会有线程执行GC,不需要程序员显示释放对象
  • GC发生的时机由复杂的策略判断,自动触发,不受外部控制
  • 不同的垃圾回收算法,甚至不同的JVM版本,回收策略都不一样

内存问题两种形式:

  • 内存溢出(OutOfMemoryError)OOM:
    • 堆是最常见的情况
    • 堆外内存排查困难
  • 内存泄漏(MemoryLeak)ML:
    • 分配的内存没有得到释放
    • 内存一直在增长,有OOM风险
    • GC回收时回收不掉
    • 能够回收但很快又占满

内存问题影响:

  • 发生OOM Error,应用停止
  • 频繁GC,GC时间长,GC线程时间片占用高
  • 服务卡顿,请求响应时间边长

排查困难:

  • 问题时间跨度大
  • 问题解决耗费精力
  • 现场保护意识不足

简单问题场景:

  • 物理内存不足
    • 主机物理内存非常少
    • 主机上应用进程非常多
  • 给应用JVM分配的内存小
  • 错误的引用方法,发生了内存泄漏,没有及时切断与GC roots的关系
  • 并发量大,计算需要内存大
  • 没有控制取数范围
  • 加载了非常多的Jar包
  • 对堆外内存无限制的使用

垃圾回收器:

  • CMS:将在Java14正式移除
  • G1:主流应用的垃圾回收器
  • ZGC 大容量(16TB),低延迟(10ms)的垃圾回收器

可达性分析法:

  • Reference Chain
  • 可达性分析法
  • GC过程:找到活跃的对象,然后清理其他的

引用级别:

  • 强引用:属于最普通最强硬的一种存在,只有在和GC Roots断绝关系时,才会被消灭掉
  • 软引用:只有在内存不足时,系统则会回收软引用独享
  • 弱引用:当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象
  • 虚引用:虚引用主要用来跟踪对象被垃圾回收的活动

对象何时提升(Promotion)

  • 常规提升 对象够老
  • 分配担保 Survivor空间不够,老年代担保
  • 大对象直接在老年代分配
  • 动态对象年龄判定

瞬时态和历史态

  • 瞬时态:
    • 指当时发生的快照类型的元素
    • 体积大
  • 历史态
    • 指按照频率抓取的
    • 有固定监控项的资源变动

预防措施:

  • 减少创建大对象的频率:比如byte数组的传递
  • 不要缓存太多的堆内存数据:使用guava的weak引用模式
  • 查询的范围一定要可控:如分库分表中间件;ES等有同样问题
  • 用完的资源一定要close掉:可以使用新的try-with-resources语法
  • 少用intern:字符串太长,且无法复用,就会造成内存泄漏
  • 合理的Session超时时间
  • 少用第三方本地代码,使用Java方案替代
  • 合理的池大小
  • XML(SAX/DOM)、JSON解析时要注意对象大小

总结:

  • 问题发现:
    1. 确保加入了日志和自动转储参数
    2. 确定物理内存足够:free
    3. 确定Java进程内存足够:jmap
    4. 确定主机环境,剩余内存大小
    5. 查看Glog和其他日志
    6. 使用jstack对线程进行摸底
    7. 对堆外内存进行排查
    8. 保留现场
  • 采取措施
  • 重复观察
  • 问题解决
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。