JVM 堆

核心概述

  • 一个Java实例(进程)只存在一个堆内存
  • 内存管理的核心区域
  • 垃圾回收的重点区域
  • 所线程共享堆,可划分私有的缓冲区

  • JVM启动时即被创建,大小确定(大小可调节)
  • 物理上可不连续
  • 几乎所有对象实例都分配在堆中分配内存
  • 方法结束后、堆中的对象不会马上被移除垃圾收集时会进行移除操作

内存细分

Java8

在这里插入图片描述

设置大小

Java堆的大小在JVM启动时就设定好

  • -xms:设置堆的起始内存
  • -Xmx:堆的最大内存

通常将起始内存、最大内存设置成相同,这使得Java垃圾回收机制清理堆区后不重新分配计算堆区的大小,提升性能

年轻代与老年代

在这里插入图片描述

Java对象分为两类:

  • 生命周期较短的、创建和消亡都非常迅速
  • 生命周期长,甚至与JVM生命周期保持一致的

Java堆区进一步划分为

  • 年轻代(Young Gen)
    • Eden
    • Survivor0
    • Survivor1
  • 老年代(Old Gen)

  • 在HostSpot,Eden与两个Survivor所占比重为8:1:1
  • 几乎所有Java实例是在Eden区域被创建
  • 绝大多数Java对象的销毁在新生代进行(80%)

  • -XX:NewRatio:参数设置老年代与新生代比例
  • -XX:SurvivorRatio:参数设置空间比例
  • -Xmn:新生代的内存大小

对象分配过程

概述

为新对象分配内存时严谨、复杂的任务,JVM设计者需要考虑内存如何分配、在哪分配、GC执行完成内存回收是否会在内存空间产生内存碎片

  • new的对象放在新生代的伊甸园区
  • 伊甸园区满了,程序需要创建对象,JVM垃圾回收器将伊甸园区进行垃圾回收(Minor GC),将伊甸园区的对象销毁,再加载新的对象到伊甸园区,然后将伊甸园区剩余的对象移动到幸存者区
  • 再次触发垃圾回收,将上次幸存的对象放到另一个幸存者区
  • 认情况下,去老年区的阈值为15次
  • 养老区内存不足,触发Major GC
  • 触发Major GC依然无法保存对象,将产生OOM异常

垃圾回收频繁发生在新生区,很少再老年区、几乎不在元空间发生

垃圾回收

JVM进行GC时,大部分时候是真的新生区,针对HotSpot VM的实现,GC按照回收区域分为部分收集(Partial GC)、整堆收集(Full GC),垃圾收集会触发STW,暂停用户线程

  • 部分收集
    • 新生区收集:只收集新生区
      • 新生区空间(伊甸园区)不足,会触发Minor GC,大多数对象生命周期短,此类垃圾收集频繁、伊甸园区空间小,回收速度快
    • 老年区收集:只收集老年区,只有CMS GC会单独收集老年区
      • Major GC、Full GC发生时,会回收老年区垃圾,出现Major GC经常伴随至少一次Minor GC(parallel Scavenge收集器有直接进行Major GC策略选择过程)
      • Major GC速度比Minor GC慢10倍
      • Major GC后,内存还是不足,则产生OOM
  • 整堆收集
    • 收集整个Java堆、方法
    • 触发Full GC的情况
      • 调用System.gc,系统建议执行Full GC,但并非一定执行
      • 老年区或新生区空间不足
      • 通过Minor GC后加入老年区平均大小大于老年区可以内存
      • 由伊甸园区、survivor向survivor的to区复制时,对象的大小大于to区可以内存(将该对象转存到老年区,且老年区可以内存小于对象的大小
      • Full GC应尽量避免

堆空间分代思想

不同对象的生命周期不一样,7到9成对象时临时对象

内存分配策略

  • 对象在伊甸园区出生并在Minor GC仍然存活,且能被Survivor容纳则移动到Survivor空间,对象的年龄增加1岁
  • 往后每一次Minor GC都存活,则年龄增加1岁年龄到达15岁时(认,每个JVM、GC不同)会晋升到老年代,-XX:MaxTenuringThreshold设置

针对不同年龄段的对象分配原则

  • 优先分配到伊甸园区
  • 大对象直接分配到老年代
  • 长期存在的对象分配到老年区
  • 动态对象年龄判断
  • Survivor相同年龄的所有对象大小占用空间的总和大于Survivor空间的一半,该年龄及以上的对象进入老年区
  • 空间担保:-XX:HandlePromotionFailure

TLAB(Thread Local Allocation Buffer)

  • 堆区时线程共享的区域,任何线程都可以访问的堆中的共享数据
  • 对象的创建在JVM中很频繁,在并发环境下从堆区划分内存时线程不安全的
  • 为避免多个线程操作同一个地址,需要用到锁机制,但会影响速度
  • TLAB在Eden区,只占1%,-XXTLABWasteTargetPercent配置百分比
  • 它是给线程分配的私有缓冲区域,它可以避免非线程安全问题,同时提高吞吐量(这种分配策略是快速分配策略)
  • OpenJDK衍生的JVM都提供了TLAB的设计
  • JVM将TLAB作为内存分配的首选
  • 对象在TLAB空间分配失败时,JVM将尝试通过加锁机制保证数据原子性,直接在Eden区分配内存

    在这里插入图片描述

堆空间常用的jvm参数

  • -XX:+PrintFlagsInitial : 查看所有的参数的认初始值

  • -XX:+PrintFlagsFinal :查看所有的参数的最终值(可能会存在修改,不再是初始值)

  • 具体查看某个参数的指令
    jps:查看当前运行中的进程
    jinfo -flag SurvivorRatio 进程id

  • -xms:初始堆空间内存 (认为物理内存的1/64)

  • -Xmx:最大堆空间内存(认为物理内存的1/4)

  • -Xmn:设置新生代的大小。(初始值及最大值)

  • -XX:NewRatio:配置新生代与老年代在堆结构的占比

  • -XX:SurvivorRatio:设置新生代中Eden和S0/S1空间的比例

  • -XX:MaxTenuringThreshold:设置新生代垃圾的最大年龄

  • -XX:+PrintGCDetails:输出详细的GC处理日志

  • 打印gc简要信息
    -XX:+PrintGC
    -verbose:gc

  • -XX:HandlePromotionFailure:是否设置空间分配担保


  • 发生Minor GC前,JVM检查老年区最大的可用连续空间是否大于新生区的所有对象的总占用空间
    • 大于:此次Minor GC安全
    • 小于:如果设置了空间分配担保,检查老年区最大可用连续空间是否大于历次晋升到老年区的对象平均大小
      • 大于:进行Minor GC,有风险
      • 小于:进行Full GC
  • 进行Full GC(JDK6 update24后, -XX:HandlePromotionFailure参数不影响分配担保策略,只要老年区最大的可用连续空间大于新生区的所有对象的总占用空间,进行Minor GC,否则Full GC)

逃逸分析

  • 没有逃逸:一个对象在方法被定义后,对象只在方法内部使用
  • 发生逃逸:一个对象在方法被定义后,它被外部方法引用(如作为调用参数传递)
  • JDK 6u23版本后,HotSpot认开启了逃逸分析
  • 参数设置:
    • 开启逃逸分析:-XX:+DoEscapeAnalysis
    • 查看逃逸分析筛选结果:-XX:+PrintEscapeAnalysis

代码优化

  • 栈上分配:将堆分配转换为栈分配
  • 同步省略:发现对象只能从一个线程访问到时,对于这个对象的操作将不考虑同步(锁消除)
  • 分离对象、标量替换:对象不需要连续的内存空间,可以存储在cpu寄存器中(经过JIT优化,把对象拆分为若干个成员变量代替)

逃逸分析不成熟

  • 无法保证逃逸分析的性能消耗一定高于它的消耗,它本身是相对耗时的过程,但它是即时编译器优化技术的重要手段
  • 对象的实例都是在堆中分配的,HotSpot JVM没有实现通过逃逸分析,将不会逃逸的对象分配到栈上(优化技术是标量替换)

相关文章

jinfo 命令可以用来查看 Java 进程运行的 JVM 参数,命令如下...
原文链接:https://www.cnblogs.com/niejunlei/p/5987611.ht...
java 语言, 开发者不能直接控制程序运行内存, 对象的创建都是...
jvm
1.jvm的简单抽象模型:  2.类加载机制     双亲委派模...
堆外内存JVM启动时分配的内存,称为堆内存,与之相对的,在代...
1.springboot和tomcat2.springcloud的请求如何通过网关鉴权?...