写点什么

JVM 堆体系结构及其内存调优

  • 2023-03-21
    湖南
  • 本文字数:3634 字

    阅读完需:约 12 分钟

堆体系结构

一个 JVM 实例只存在一个堆内存,堆内存的大小是可调节的。类加载器读取类文件后,需要把类、方法、常量、变量放在堆内存中,保存所有引用类型的真实信息,以方便执行器指向,堆内存分为三个部分:年轻代、老年代、永久代。


Java7 之前,堆内存在逻辑上分为:年轻代、老年代、永久代。物理上分为:年轻代、老年代


Java8:永久代 ---> 元空间


新生区是类的诞生、成长、消亡的区域。一个类在新生区产生,最后被垃圾回收器收集。新生区分为伊甸区和幸存者区。幸存者区分为幸存 0 区,幸存 1 区。


当伊甸区空间用完的时候,程序还需要创建对象,JVM 的垃圾回收器将对伊甸区进行垃圾回收(Minor GC),将伊甸区中不再被其他对象引用的对象进行销毁,将剩余的对象移动到幸存 0 区。


若幸存 0 区(from 区)满了,对幸存 0 区进行垃圾回收,将剩余的对象移动到幸存 1 区。如果幸存 1 区(to 区)满了,再移动到养老区。


如果养老区满了,就产生了 Major GC(Full GC),进行养老区的内存清理。如果执行了 Full GC 后依然无法进行对象的保存,就会产生 OOM 异常,OutOfMemoryError。


异常:java.lang.OutOfMemoryError: Java heap space


JVM 堆内存不够,原因:

  • JVM 的堆内存设置的太小,可以调整-Xms、-Xmx

  • 代码中创建了大量的大对象,并且长时间不能被垃圾回收器收集(存在被引用)

Minor GC 的过程

Java 堆从 GC 的角度可以细分为新生代(Eden 区、from 存活区、to 存活区,空间比例 8:1:1)和老年代(空间比例 1:2)。


复制 ☞ 清空 ☞ 互换


  1. eden、survivor from 复制到 survivor to,对象年龄+1。

当 eden 区满,触发第一次 GC,存活对象拷贝到 survivor from 区。当 eden 区再次触发 GC,会扫描 eden 和 from,对这两个区进行垃圾回收,将存活的对象,复制到 to 区,对象年龄+1。(如果有对象年龄达到了老年的标准,拷贝到老年代,对象年龄+1)


  1. 清空 eden、survivor from

清空 eden 和 survivor from 中对象,此时 from 为。


  1. survivor from 和 survivor to 互换

to 区存在对象,变成下一次 GC 的 from 区,from 区成为下一次 GC 的 to 区,部分对象会在 form 和 to 区域复制往来 15 次(JVM 的 MaxTenuringshold 参数默认是 15),如果最终还是存活,就存入老年代。

方法区和永久代

参考自博客:https://www.jianshu.com/p/66e4e64ff278


在 JDK1.6 及之前,运行时常量池是方法区的一个部分,同时方法区里面存储了类的元数据信息、静态变量、即时编译器编译后的代码(比如 spring 使用 IOC 或者 AOP 创建 bean 时,或者使用 cglib,反射的形式动态生成 class 信息等)等。在 JDK1.7 及以后,JVM 已经将运行时常量池从方法区中移了出来,在 JVM 堆开辟了一块区域存放常量池。


方法区和堆都是各个线程共享的内存区域,方法区用于存储虚拟机加载的类信息、普通常量、静态常量、编译器编译后的代码等,虽然 JVM 规范将方法区描述为堆的一个逻辑部分,但它还有一个别名叫 Non-Heap,目的是和堆分开。


方法区常被成为永久代,严格来说二者不同,只是用永久代来实现方法区而已,方法区和永久代的关系很像 Java 中接口和类的关系,类实现了接口,而永久代就是 HotSpot 虚拟机对虚拟机规范中方法区的一种实现方式。


永久代在 JDK1.7 之前有,是一个常驻内存区域,用于存放 JDK 自身携带的 class、interface 的元数据,也就是说它存储的是运行环境必须的类信息,被装在进此区域的数据是不会被垃圾回收器回收掉的,关闭 jvm 才会释放这个区域所占的内存。


HotSpot 虚拟机中存在三种垃圾回收现象,minor GC、major GC 和 full GC。对新生代进行垃圾回收叫做 minor GC,对老年代进行垃圾回收叫做 major GC,同时对新生代、老年代和永久代进行垃圾回收叫做 full GC。许多 major GC 是由 minor GC 触发的,所以很难将这两种垃圾回收区分开。


major GC 和 full GC 通常是等价的,收集整个 GC 堆。但因为 HotSpot VM 发展了这么多年,外界对各种名词的解读已经完全混乱了,当有人说“major GC”的时候一定要问清楚他想要指的是上面的 full GC 还是 major GC。

元空间

参考自博客:https://www.jianshu.com/p/66e4e64ff278


HotSpot 虚拟机在 1.8 之后已经取消了永久代,改为元空间,类的元信息被存储在元空间中。元空间没有使用堆内存,而是与堆不相连的本地内存区域。所以,理论上系统可以使用的内存有多大,元空间就有多大,所以不会出现永久代存在时的内存溢出问题。


这项改造也是有必要的,永久代的调优是很困难的,虽然可以设置永久代的大小,但是很难确定一个合适的大小,因为其中的影响因素很多,比如类数量的多少、常量数量的多少等。永久代中的元数据的位置也会随着一次 full GC 发生移动,比较消耗虚拟机性能。


同时,HotSpot 虚拟机的每种类型的垃圾回收器都需要特殊处理永久代中的元数据。将元数据从永久代剥离出来,不仅实现了对元空间的无缝管理,还可以简化 Full GC 以及对以后的并发隔离类元数据等方面进行优化。

堆内存调优

在 JDK1.7 中


在 JDK1.8 中,元空间取代永久代。元空间和永久代的最大的区别是永久代使用的是 JVM 的堆内存,元空间不在虚拟机中,而是使用本机物理内存。默认清空下,元空间只受本地内存限制,类的元数据放入本地内存,字符串常量池和类型静态变量放入 java 堆,类的元数据的加载量不再受 MaxPermSize 控制,而是由系统实际的可用空间来控制。


  • -Xms:初始分配大小,默认为物理内存的 1/64

  • -Xmx:最大分配内存,默认为物理内存的 1/4

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


配置完 Xms、Xmx 后的输出结果 java.lang.OutOfMemoryError: Java heap space 异常 GC 处理日志:

[GC (Allocation Failure) [PSYoungGen: 2045K->488K(2560K)] 2045K->781K(9728K), 0.0014360 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 2534K->488K(2560K)] 2827K->1548K(9728K), 0.0008101 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 2171K->504K(2560K)] 4318K->3194K(9728K), 0.0006870 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Ergonomics) [PSYoungGen: 2207K->0K(2560K)] [ParOldGen: 7037K->2826K(7168K)] 9245K->2826K(9728K), [Metaspace: 3454K->3454K(1056768K)], 0.0051352 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 5000K->5000K(9728K), 0.0003304 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 5000K->5000K(9728K), 0.0002962 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 5000K->3913K(7168K)] 5000K->3913K(9728K), [Metaspace: 3455K->3455K(1056768K)], 0.0028924 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 3913K->3913K(8704K), 0.0005099 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 3913K->3889K(7168K)] 3913K->3889K(8704K), [Metaspace: 3455K->3455K(1056768K)], 0.0072665 secs] [Times: user=0.06 sys=0.02, real=0.01 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space    at java.util.Arrays.copyOf(Arrays.java:3332)    at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:674)    at java.lang.StringBuilder.append(StringBuilder.java:208)    at day05JVM01.T2.main(T2.java:15)
复制代码

YoungGC

[GC (Allocation Failure) 内存分配失败

[PSYoungGen: 2045K->488K(2560K)] 2045K->781K(9728K), 0.0014360 secs]

[GC 类型:GC 前 young 区的内存占用->GC 后 young 区的内存占用(新生代的总内存)] GC 前 JVM 堆内存占用->GC 后 JVM 堆内存占用(JVM 堆的总内存),GC 耗时

[Times: user=0.00 sys=0.00, real=0.00 secs]

[GC 用户耗时,系统耗时,实际耗时]

FullGC

[Full GC (Allocation Failure)

[PSYoungGen: 0K->0K(1536K)]

[ParOldGen: 3913K->3889K(7168K)] 3913K->3889K(8704K),

[Metaspace: 3455K->3455K(1056768K)], 0.0072665 secs]

[Times: user=0.06 sys=0.02, real=0.01 secs]

什么是 GC?

GC 是分类收集算法,JVM 在进行 GC 的时候并不是每次对三个区域一起回收,大部分时候是回收新生代。频繁收集 Young 区,较少收集 Old 区,基本不动元空间。GC 按照回收的区域分成了:普通 GC minor GC 和全局 GC Full GC


Minor GC:只针对新生代区域的 GC,发生在新生代的垃圾收集,因为大多数 JAVA 对象存活率都不高,所以 Minor GC 的操作非常频繁,垃圾回收的速度比较快。


Full GC:指发生在老年代的垃圾收集操作,出现 Full GC,经常会伴随至少一次的 Minor GC(但不绝对)。Full GC 的速度一般比 Minor GC 慢 10 倍以上。


GC 有四大算法:引用计数法、复制算法、标记清除、标记压缩。

用户头像

还未添加个人签名 2021-07-28 加入

公众号:该用户快成仙了

评论

发布
暂无评论
JVM 堆体系结构及其内存调优_Java_做梦都在改BUG_InfoQ写作社区