当前位置:首页 > 说编程 > 正文内容

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

2020-12-05说编程790

堆体系结构

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

Java7之前,堆内存在逻辑上分为:年轻代(新生区)、老年代(养老区)、永久代(永久存储区)。物理上分为:年轻代、老年代。Java8开始永久代改为元空间

image.png

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

当伊甸区空间用完的时候,程序还需要创建对象,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)。

image.png

复制-清空-互换

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

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

2. 清空eden、survivor from

清空eden和survivor from中对象。

3. survivor from 和 survivor to 互换

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

方法区和永久代

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

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

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

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

image.png

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。

元空间

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

这项改造也是有必要的,永久代的调优是很困难的,虽然可以设置永久代的大小,但是很难确定一个合适的大小,因为其中的影响因素很多,比如类数量的多少、常量数量的多少等。永久代中的元数据的位置也会随着一次full GC发生移动,比较消耗虚拟机性能。同时,HotSpot虚拟机的每种类型的垃圾回收器都需要特殊处理永久代中的元数据。将元数据从永久代剥离出来,不仅实现了对元空间的无缝管理,还可以简化Full GC以及对以后的并发隔离类元数据等方面进行优化。

堆内存调优

在JDK1.7中

image.png

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

image.png

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

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

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

什么是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常见有四大算法:引用计数法、复制算法、标记清除、标记压缩。具体可查看常见的垃圾回收算法

扫描二维码推送至手机访问。

本站内容仅供学习研究,转载请以链接形式注明,如有侵犯请联系处理。

本文链接:https://suyu.net/post/17.html

分享给朋友:

发表评论

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。