jvm 04-JVM堆内存划分

JVM堆内存划分

  • 在JDK1.8之后,将最初的永久带内存空间取消了,该图为JDK1.8之前的内存空间组成
  • 取消永久代目的是为了将HotSpot于JRockit两个虚拟机标准联合为一个
  • 在整个JVM堆内存之中实际上将内存分为了三部分:
    • 新生带(年轻代):新对象和没达到一定年龄的对象都在年轻代
    • 老年代:被长时间使用的对象,老年代的内存空间应该要比年轻代更大
    • 元空间(JDK1.8之前叫永久代):像一些方法中的操作临时对象等,JDK1.8之前是占用JVM内存,JDK1.8之后直接使用物理内存

年轻代

  • 年轻代属于JVM堆内存空间的一个组成部分
  • 所有使用关键字new的新实例化对象都会在Eden区进行保存
  • 在存活区保存的一定是已经在Eden区中存活好久的,并经历或多次Minor GC的活跃对象
  • 存活区一般有两块空间,这两块空间大小相等且两块中一定有一块是空的
  • 存活区两块空间的作用:
    • 一块存活区为了晋升
    • 一块存活区为了对象回收

Minor GC算法

  • 年轻代中的Minor GC算法采用的事复制算法
  • 复制算法:从根集合扫描出存活对象,并将该对象复制到一块新的完全为使用的空间中
**(红色为不存活对象所占用的内存空间;绿色为存活对象所占用的内存空间)**
  • 由于Eden区保存的对象可能大部分为临时对象,容易频繁发生Minor GC

  • 为此HotSpot虚拟机采用了两种技术加快空间的内存分配操作

  • Bump-The-Pointer

    • 该技术跟踪Eden区保存的最后一个对象,该对象一般会保存在Eden区的顶部
    • 当创建新对象时,需要检测最后保存的对象后面是否有足够的空间
    • 这样可以很快的判断Eden区是否还有剩余空间
    • 该技术可以极大提高内存分配速度
  • TLAB(Thread-Local Allocation Buffers)

    • 为了适应多线程环境,TLAB算法将Eden区分为多个数据块
    • 每个数据块分别使用Bump-The-Pointer技术进行对象保存与内存分配

年轻代内存调整参数

  • -Xmn:设置年轻代堆内存大小,默认为物理内存的1/64
  • -Xss:设置每个线程栈大小,JDK 1.5之后默认为每个线程分配1M的栈大小
  • 减少此数值可以产生更多的线程对象,但是不能无限生成
  • -XX:SurvivorRatio:设置Eden与Survivor空间的大小比例,默认8:1:1
  • -XX:NewSize:设置年轻代内存区大小
  • -XX:NewRatio:设置年轻代与老年代的比率

例如:改变存活区比率

-Xmx10m -Xms10m -XX:SurvivorRatio=6 -XX:+PrintGCDetails

输出结果:

max_memory=9.5M
total_memory=9.5M
Heap
 PSYoungGen      total 2560K, used 606K [0x00000007bfd00000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 2048K, 29% used [0x00000007bfd00000,0x00000007bfd97a38,0x00000007bff00000)
  from space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
  to   space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
 ParOldGen       total 7168K, used 0K [0x00000007bf600000, 0x00000007bfd00000, 0x00000007bfd00000)
  object space 7168K, 0% used [0x00000007bf600000,0x00000007bf600000,0x00000007bfd00000)
 Metaspace       used 2708K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 293K, capacity 386K, committed 512K, reserved 1048576K

老年代

  • 主要接收由年轻代发送来的对象
  • 经历过多次Minor GC后还保存下来的对象才会进入到老年代
  • 如果需要保存的对象超过了Eden区大小,那此对象也将直接保存到老年代中
  • 当老年代内存不足时将引发Full GC(Major GC)
  • 老年代采用两种算法结合模式实现GC处理:整理-压缩

Full GC算法

  • 标记-清楚(Mark-Sweep)

    • 从根集合开始扫描,对存活的对象进行标记,标记完毕后,在扫描整个空间中未标记的对象,并进行回收
    • 优缺点:在空间中存活对象较多的情况下较为高效,但由于该算法为直接回收不存活对象所占用的内存,因此会造成内存碎片。
  • 标记-压缩(Mark-Compact)

    • 在回收不存活对象所占用的内存空间后,会将其他所有存活对象都往左端空闲的空间进行移动,并更新引用其对象指针。
    • 优缺点:在标记-清除的基础上还需要进行对象移动,成本相对较高,好处则是不产生内存碎片

老年代内存调整参数

  • -XX:NewRatio:设置年轻代与老年代的比率
  • -XX:+UseAdaptiveSizePolicy:控制是否采用动态控制策略
  • 如果动态控制,则动态调整java堆中各个区域的大小以及进入老年代的条件
  • -XX:PretenureSizeThreshold:控制直接进入老年代对象的大小,大于该值的对象会直接分配在老年代中

例如:设置老年代参数

-Xmx10m -Xms10m -XX:PretenureSizeThreshold=512k -XX:+PrintGCDetails

元空间

  • 元空间是在JDK 1.8之后新增的,功能上和永久代一样
  • 永久代使用的是JVM堆内存空间,元空间使用的是物理内存
  • 元空间直接受到本机的物理内存限制

元空间内存调整参数

  • -XX:MetaspaceSize:设置元空间大小
  • -XX:MaxMetaspaceSize:这种元空间最大容量,默认没限制,直接受到本地物理内存限制
  • -XX:MinMetaspaceFreeRatio:执行GC之后,最小的剩余元空间百分比,减少为分配空间所导致垃圾收集
  • -XX:MaxMetaspaceFreeRatio:执行GC之后,最大的剩余元空间百分比,减少为释放空间所导致的垃圾收集

例如:设置一些参数导致元空间出错

-XX:MetaspaceSize=1m -XX:MaxMetaspaceSize=1m

输出结果:

Error occurred during initialization of VM
OutOfMemoryError: Metaspace

总结:

  • Java中的GC会有两种回收:
    • 年轻代的Minor GC -- 新对象创建时如果Eden区空间不足会触发Minor GC
    • 老年代的Full GC -- 如果老年代内存空间不足会触发Full GC,也是gc()方法执行
    • 如果所有空间都不足则抛出OutOfMemoryError
  • 如果整个java项目运行缓慢,为了避免堆内存的伸缩空间进行操作,可将初始化内存空间和整体堆内存空间大小设置为一样。(使用 -Xms、-Xmx)
原文地址:https://www.cnblogs.com/liangjingfu/p/9951938.html