JVM内存模型和垃圾回收

JVM内存模型和垃圾回收

一、类加载过程

类加载步骤

​ 我们写出来的.java文件,是不能被执行的,需要经过JVM的加载编译等过程后才能称为可执行程序。类的加载过程可分为几个步骤:.java文件 -> javac编译 -> .class文件 -> ClassLoader类加载器 -> 方法区存放

双亲委派模型

​ 在使用ClassLoader类加载器加载.class文件时,使用的加载模型为双亲委派模型,即子加载器委派父加载器,层层委托机制,直到委托了最后一个加载器才开始加载,若顶层父加载器发现这个类它可以加载就直接加载,无法加载则会再委托给自己的下级子类,以此类推,又委托回头了,这就是双亲委派模型,具体流程如图所示:

在这里插入图片描述

但是,双亲委派模型并不是没有缺陷的,由于类加载器仅对自己加载的类可见,在某些场景下,如java SPI机制,Java定义的一套规范给厂商实现,拿数据库来说,DriverMananger是由顶层加载器BootstrapClassLoader加载的,但是它获取Connection连接对象时必然会去加载Driver类,而Driver的实现类是由AppClassLoader加载的,BootstrapClassLoader加载的类对AppClassLoader加载的类是不可见,这里的Bootstrap需要使用AppClassLoader加载的类,与双亲委派模型相悖,所以这里的SPI机制需要打破双亲委派模型,这里SPI机制使用线程上下文类加载器获取ApplicationClassLoader加载。

​ 打破双亲委派模型方法:

  • 自定义类加载器: 我们可以自定义我们自己的类加载器,重写加载类的方法loadClass,jdk的类加载器中loadClass方法中的逻辑会将需要加载类委托给父加载器,我们可以重写loadClass方法改变其中逻辑进而打破双亲委派模型。
  • 线程上下文加载器:见上文解释。

二、JVM内存模型

在JVM中,内存模型可分为方法区、虚拟机栈、本地方法栈、堆、程序计数器几大部分。

  • 方法区: 方法区为JVM的一个规范,虚拟机的类型有很多种,如HotSpot虚拟机中的方法区在jdk7的实现是永久代,而在jdk8中其实现改成了元空间其与永久代最大的区别是其不在虚拟机中,而是使用本地内存,仅受本地内存限制,使用本地内存也减少了虚拟机内存和Native之间的复制过程,在某些场景下可以提高性能,当然可以通过指令限制其大小 。主要存放类的信息、常量池、方法数据、方法代码等。
  • 虚拟机栈: 每个线程运行时产生一个栈,栈内有一个个栈帧,栈帧为每个方法执行时所分配的内存,当方法执行完毕后(弹栈)即释放内存,栈数据结构为先进后出,例如一个线程调用方法1,方法1中调用方法2,方法2中调用方法3,方法3执行完毕返回值给方法2,方法3弹栈(释放内存),同理方法2返回值给方法1,方法2弹栈,方法1弹栈,线程执行完毕,释放内存。栈是不会产生垃圾回收的,因为栈帧中的方法执行完毕后内存马上释放,不会存在垃圾。栈内存溢出可以通过无限递归调用方法复现,默认栈内存为1mb,可通过-Xss调整栈内存大小,即调优 。
  • 本地方法栈: 本地方法栈用于管理本地方法调用,本地方法即一个java方法,但该方法不是使用java语言实现,而是使用像c、c++等语言实现的方法 。
  • 堆: 用于存放不放在当前方法栈的数据,如new了一个对象,方法中局部变量使用final修饰也都会放到堆中,它不会随着方法调用结束弹栈而被清理,堆也是垃圾回收最为频繁的区域。

堆内存细分

堆中存放对象的内存可细分为几个区域,分别为伊甸区(Eden)、幸存者区(Survivor,该区域又细分为幸存者0区和幸存者1区)、老年代。

  • 伊甸区(Eden): 伊甸区,存放新创建的对象(若新创建的对象过大,会直接进入老年代),当伊甸区满后,会进行一次Minor Gc(也称为Youg Gc)。

  • 幸存者区(Survivor,该区域又细分为幸存者0区和幸存者1区): 幸存者区保存在伊甸区经历过一次垃圾后存活的对象。

  • 老年代: 保存创建的大对象(新生代的大对象直接进入老年代)、在幸存者区中经历了多次Mionr Gc(最大15岁,原因是在对象头中存储对象的年龄占4个比特位,可调整)仍然存活、触发幸存者区动态年龄判断进入老年代的对象。

    在这里插入图片描述

垃圾回收(GC过程)

  • 伊甸区(Eden)Gc过程: 当伊甸区装满对象后,将触发一次Minor Gc。

    触发Mionr Gc之前会进行老年代空间剩余容量大小的判断,判断结果有如下几种:

    1. 老年代剩余空间足够存放伊甸区的对象: 可以直接进行Mionr Gc,就算幸存者区放不下,老年代也能放下;

    2. 老年代剩余空间不足以存放伊甸区的对象(此时查看是否设置老年代空间分配担保规则,一般都会设置,具体参数为 -XX:-HandlerPromotionFailure),有以下两种情况:

      1. 老年代中剩余空间大于历次Mionr Gc后存活对象占用空间大小,则直接进行Gc,因为从概率上来说,以前可以放下,那么这次也可以放下;
      2. 老年代中的剩余空间大小小于历次Mionr Gc后存活对象占用空间大小,进行一次Full Gc,清理老年代,再进行检查,检查不通过,抛出OOM异常;

      PS: 未开启老年代空间分配担保规则,会直接触发Full Gc,若Full Gc后仍然放不下,则OOM;

    开启了空间担保规则,通过担保(这里指上文中的2.1的情况)触发Minor Gc后,出现的结果有以下三种:

    1. 幸存者区足够放下Minor Gc后存活的对象,Gc结束;
    2. 幸存者区不足以放下Minor Gc后存活的对象,但老年代可以放下,则放入老年代,Gc结束;
    3. 两个区域都放不下,进行Full Gc清理空间,Full Gc后也放不下,抛出OOM异常;
  • 幸存者区(Survivor)Gc过程: 幸存者区Minor Gc是两块空间对象复制来复制去,然后互换位置,另外有一个动态年龄判断规则。

    1. 当触发Minor Gc时,幸存者0区的存活的对象跟随年轻代的一次Minor Gc复制到幸存者1区,并且对象年龄加一,此时伊甸区和幸存者0区为空;当伊甸区再一次满了,此时伊甸区和幸存者1区存活下来的对象随本次Gc复制到幸存者0区,此时伊甸区和幸存者1区变为空;简单来说就是将幸存者两个区互相倒腾。

    2. 动态年龄判断:当然不是一直倒腾来倒腾去,总会有满的时候,但是我们也不能让它满,否则可能会无法进行1的Gc过程,所以有一个动态年龄判断的机制,不能让空间占有超过幸存者0(1)区的50%(可调整);当幸存者区内存占有率超过50%,触发动态年龄机制,从幸存者区中年龄最小的对象开始,对年龄开始累加,累加到某个年龄值时,占用内存超过了幸存者区的50%,则把年龄为x和年龄大于x的对象放到老年代里。

      Minor Gc过程:

      在这里插入图片描述

  • Full Gc:当老年代内存满了或者元空间(永久代)的内存满了,或者触发Minor Gc前后的空间不足时,会触发Full Gc,对整个堆和元空间以及方法区都进行Gc,该过程十分消耗系统资源,因此在我们设计程序,开发过程中应尽量考虑到少触发Full Gc。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

原始人~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值