JVM(二): 内存架构下

198 阅读4分钟

这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战

特点:

  • 虚拟机管理的内存最大的一块
  • 所有线程共享(TLAB例外)
  • 虚拟机启动时创建
  • 唯一目的事存放对象实例
  • 垃圾收集器的主要区域
  • GC堆。分代收集算法。细分为新生代和老年代, 新生代分为Eden空间, From Survivor空间, To Survivor空间。
  • 物理存储上不连续的,逻辑上连续的,大小可调节的(-Xms和-Xmx空间)
  • 方法结束后, 堆中内存不会马上移除仅仅在垃圾回收的时候才移除
  • 如果在堆中没有内存完成实例分配,并且无法扩展, 抛出OutOfMemoryError异常。

设置堆空间大小

-Xmx20m -Xms5m

分类

年轻代 Eden surivivor0 surivivor1

老年代 old

  • young Gen: 年轻代主要存放新创建的对象,内存大小相对比较小, 垃圾收集比较频繁,分成1个 Eden和2个Survivor
  • Tenured Gen: 存放JVM认为生命周期比较长的对象,内存大小相对比较大, 垃圾回收也没那么频繁
-XX:NewRatio=2 // 新生代占堆的1/3
-XX:SurvivorRatio=8 // Survivor占新生代的1/8

对象分配过程

  1. new的对象先放在Eden区
  2. 当Eden区填满时, 需要创建对象, JVM垃圾回收器对Eden进行垃圾回收(Minor GC), 将Eden区不再被其他对象引用的对象销毁,加载新的对象到Eden区
  3. 将剩余的对象移动到S0 区
  4. 如果再触发垃圾回收, 上次S0区的如果没有回收, 放到S1区
  5. 如果再次垃圾回收,此时会重新返回S0区,接着再去S1区
  6. 如果累计次数到达默认15次, 就会进入Tenured区。参数是-XX:MaxTenuringThreshold = N
  7. Tenured内存不足, 会再次触发GC: Major GC 进行Tenured区的清理, 如果执行了Major GC, 仍然没法保存对象, 就会报OMM异常。

堆GC

GC分两种: 一种是部分收集器Partial GC,一种是整堆GCFull GC

部分收集器: 不完整的堆收集, 分为

  • 新生代收集 Minor GC / Young GC
  • 老年带收集 Major GC/ Old GC(CMS GC 单独回收老年代)
  • 混合收集 Mixed GC 收集整个新生代及老年代 G1混合回收

整堆回收 Full GC: 收集整个java堆和方法区的垃圾回收器

年轻代GC触发条件:

  • 年轻代空间不足, 这里指的是Eden满, Survivor不满
  • Minor GC 会引发STW

老年代GC(Major GC)触发条件:

  • 老年代空间不足, 尝试触发MinorGC, 如果还是不足,触发Major GC, 如果Major GC , 内存还不足,OOM, Major GC速度比Minor GC慢10倍

Full GC 触发:

  • 调用System.gc(), 不是立刻执行
  • 老年代空间不足
  • 方法区空间不足

元空间

本地内存,存储类的元信息。

-XX:MetaspaceSize //初始空间大小,达到该值会触发垃圾回收进行类型卸载,GC也会对该值进行调整,如果释放了大量空间,就适当降低,如果释放了很少空间, 就在不超过MaxMetaspaceSize时, 适当提高该值
    
-XX:MaxMetaspaceSize // 最大空间, 默认没有限制, 最大可利用空间就是整个系统的内存可用空间, 没有设置,就可能导致空间不断扩展,导致内存不足, 出现swap内存被耗尽, 进程直接被kill掉。如果设置了, 当剩余空间不足时, 会抛出OutOfMemoryError
    
-XX:MinMetaspaceFreeRatio //GC后最小的metaspace剩余空间容量的百分比, 减少为分配空间所导致的垃圾收集
-XX:MaxMetaspaceFreeRatio //GC后最大的metaspace剩余空间容量的百分比,减少为释放空间导致的垃圾收集
jps
jinfo -flag MetaspaceSize pid

方法区

存储被虚拟机加载的类型信息,常量,静态常量, 即时编译器编译后的代码缓存数据。元空间和永久代是方法区具体的落地实现。

  • 线程共享
  • JVM启动创建, 物理内存可以不连续
  • 大小可以选择固定大小或动态变化
  • 决定了可以保存多少类

常量池

  • 字节码文件内部包含了常量池
  • 方法区内部包含了运行时常量池

常量池存放编译期间生成的各种字面量和符号引用

运行时常量池是常量池表在运行时的表现形式。

直接内存

堆外内存, 通过存储在Java堆中的DirectByteBuffer对象作为内存的引用进行操作。

  • 改善了堆过大时垃圾回收效率,减少停顿, FullGC 会扫描堆内存,回收效率和堆大小成正比
  • 减少了Native堆和JVM堆拷贝过程
  • 可以突破JVM内存限制
-XX:MaxDirectMemorySize //不去指定默认和堆最大值一样

越过DirectByteBuffer直接通过反射获取Unsafe类进行内存分配,如果使用DirectByteBuffer分配内存会抛出内存溢出异常,但是抛出时并没有真正向操作系统申请分配内存,而是计算得知无法分配就直接抛出异常, 真正分配方法是Unsafe::allocateMemory