04-java垃圾回收面试题

四、GC

1、JVM包含的部分

JVM主要包含了:类加载器(ClassLoader)、运行时数据区(Runtime Data Area)、执行引擎(Execution Engine)和本地库接口(Native Interface)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-joJ2ZHZi-1662277051083)(C:\Users\10591\AppData\Roaming\Typora\typora-user-images\1658564404788.png)]

JVM流程:

1、先通过编译器,java代码转换为字节码文件,然后类加载器把字节码文件加载到内存中

2、放入到运行时数据区的方法区中,而字节码文件只是JVM的一套指令集规范,并不能直接交给底层操作系统去执行

3、所以需要特定的命令解析器执行引擎,将字节码文件翻译成底层系统指令,再由CPU执行

4、执行过程需要调用其他语言的本地库接口来实现整个程序的功能

2、JVM的运行时区域

运行时区域可以划分为:程序计数器、Java虚拟机栈区、本地方法栈区、方法区、java堆区

**程序计数器:**当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令。

**java虚拟机栈:**线程私有,生命周期与线程相同,用于存储局部变量表、操作数栈(操作入栈与出栈)、动态链接(使用别的方法)、方法出口(return或异常)等信息。

**本地方法栈区:**与虚拟机栈的作用一样,不过虚拟机栈是服务java方法的,而本地方法栈区是为虚拟机调用Native方法服务的

**Java堆:**被所有的线程共享,几乎所有的对象实例都在此分配内存

**方法区:**用于存储已被虚拟机加载的类信息、常量、静态变量等数据

  • **常量池:**方法区的一部分,存放编译期生成的各种字节量与符号引用
3、什么是垃圾

内存中已经不再被使用的空间就是垃圾

如何判断对象是否可以被回收

  • 引用计数法:对象中添加一个引用计数器,每当有一个地方引用了对象,计数器加一;当引用失效,计数器减一;任何时候当计数器为0时对象就是不可再被使用的。算法简单却要配合大量额外处理才能保证正确地工作,随意一般不使用。
  • 枚举根节点做可达性分析:通过一系列的GCroot对象作为起始点,向下搜索,若一个对象到GCroot没有任何的引用相连接,说明此对象不可用

GCRoot的选取

  • 虚拟机栈中引用的对象:参数、局部变量、临时变量
  • 方法区中静态属性引用的对象:
  • 方法区中常量引用的对象:常量池中
  • 本地方法栈中Native方法引用的对象
4、垃圾回收算法
  • 引用计数
  • 复制回收:将内存按容量分为两块大小相同的区域,每次只是用其中一块,当此块内存用完以后,将需要保留的对象复制到另一块区域上去,然后把此块区域全部清除。

优点:算法简单;每次对一块内存进行回收,运行高效

缺点:若内存中大量对象都是存活的,这种算法会产生大量的内存间复制开销;需要将内存缩小一半。

  • 标记清除:分为标记与清除两个阶段,对不可回收的对象进行标记,标记完后统一回收未被标记的对象,标记过程用的是枚举根节点做可达性分析。

优点:不需要进行对象的移动。

缺点:1、标记与清除的效率不稳点,需要使用一个空闲列表记录所有空闲区域及大小;2、清楚后会产生大量的不连续内存碎片

  • 标记整理:与标记清楚的过程类似,标记后堆垃圾处理情况不同。让所有存活的对象向内存空间的一端移动,然后直接清理掉端边界以外的内存。

优点:新对象的分配只需要通过指针碰撞便可完成;不会再有碎片问题

缺点:GC暂停的时间长,因为需要将所有对象都拷贝到一个新的地方,还得更新他们的引用地址

  • 分代收集算法:依据对象存活周期的不同将内存划分为几块采用不同的垃圾回收算法。在新生代,每次都会有大批对象需要被回收,只有少量存活,所以一般选择复制算法。而在老年代因为对象存活率高,没有额外空间对它进行分配,就必须得用标记清除或标记整理算法
5、简述分代垃圾回收器的工作流程

分代回收器分为两个区域:老年代与新生代。新生代默认空间占比为1/3,老年代为2/3

新生代使用的是复制算法,新生代共分3个分区:Eden、To Survivor、From Survivor,由于新生代中的对象百分之98都熬不过第一轮回收,所以他们默认占比8:1:1,每次分配内存只使用Eden与一块Survivor区,在进行回收时把eden区与survivor仍存活的对象复制到另一块survivor上,然后清除eden与survivor。执行流程如下:

  • 把Eden+From Survivor存活的对象复制到To Survivor区
  • 清空Eden与From Survivor分区
  • 把两个Survivor分区交换

虚拟机给每个对象定义了一个对象年龄计数器,每一次从From Survivor到To Survivor时都存活的对象,年龄就会加一,当年龄达到15,就升级为老年代,大的对象也会直接进入老年代。

老年代:当空间占用到达某个值的时候就会触发全局垃圾回收,一般采用标记整理算法。

6、垃圾回收器
  • Serial 串行垃圾回收器(Serial回收器):一个单线程的垃圾回收器,回收时会暂停所有用户线程,不适合服务环境。串行回收器是针对新生代的垃圾回收器,采用的是复制回收算法。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iamEH7fX-1662277051084)(C:\Users\10591\AppData\Roaming\Typora\typora-user-images\1658578869685.png)]

  • ParNew 并行垃圾回收器:多个垃圾回收线程并行工作,与串行垃圾回收器相比,极大地缩短了暂停的时间。新生代并行收集器。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iTYk1ofe-1662277051084)(C:\Users\10591\AppData\Roaming\Typora\typora-user-images\1658578900155.png)]

  • Parallel Scavenge:是针对新生代的垃圾回收器,采用复制回收算法与parNew垃圾回收器类似,跟注重吞吐量。吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)新生代并行垃圾回收器

  • Serial old收集器:是老年代单线程收集器,采用的是标记整理算法

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QtwutsmL-1662277051084)(C:\Users\10591\AppData\Roaming\Typora\typora-user-images\1658578941027.png)]

  • Parallel Old回收器:是Parallel Scavenge收集器的老年代版本,多线程收集器,采用标记整理算法

  • CMS收集器:一种以获取最短回收停顿时间为目标的收集器。仅用于老年代的收集,采用标记清除算法。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OSh9UReO-1662277051085)(C:\Users\10591\AppData\Roaming\Typora\typora-user-images\1658578975536.png)]

步骤

  • 初始标记:需要暂停其他其他线程,仅仅标记GC Roots能直接关联的对象,速度很快
  • 并发标记:标记其余与GC Root间接关联的对象 ,需要时间较长但不需要停顿用户
  • 重新标记:修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象,比初始标记长,比并发标记短。
  • 并发清除

优点:并发收集、低停顿

缺点:无法处理浮动垃圾、会有大量的空间碎片产生

  • G1垃圾回收器:将堆内存分割成多个大小相同的独立区域region,每一个区域都可以根据需要,扮演eden空间、survivor空间与老年代空间。收集器依据不同的region角色采用不同的策略去处理。Region区还有一类特殊的Humongous区,专门用于存储大对象数据(超过region容量的一半)。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cvl5VY99-1662277051085)(C:\Users\10591\AppData\Roaming\Typora\typora-user-images\1658578994658.png)]

特点

  • 并行并发:G1能够充分利用多CPU、多核环境下的硬件优势;使用多个CPU来缩短STW(stop-the-world)停顿的时间;
  • 空间整合:G1 整体采用标记-整理算法,局部是通过是通过复制算法,不会产生内存碎片
  • 分代收集:宏观上看 G1 之中不在区分年轻代和老年代,但还是保留了分代的概念

步骤

  1. 初始标记:只标记GC Roots能直接关联到的对象
  2. 并发标记:进行GC Roots Tracing的过程
  3. 最终标记:修正并发标记期间,因程序运行导致标记发生变化的那一部分对象
  4. 筛选回收:根据时间来进行价值最大化的回收

G1和CMS比较

  • G1不会产生内碎片
  • 是可以精准控制停顿。该收集器是把整个堆(新生代、老年代)划分成多个固定大小的区域,每次根据允许停顿的时间去收集垃圾最多的区域。
7、内存泄漏与内存溢出

内存泄漏:

**概念:**程序运行过程中分配内存给临时变量,用完之后却没有被GC回收,始终占用着内存,既不能被使用又不能分配给其他程序。

**原因:**长生命周期的对象持有短生命周期的对象引用,尽管短生命周期的对象已不再需要,但仍不能被收回。

内存溢出:

**概念:**程序运行过程中申请的内存大于系统能够提供的内存,导致无法申请到足够的内存。

8、JVM调优与参数配置
  • 显示指定对内存

    - -Xms等价于-XX:initialHeapSize
    - -Xmx等价于-XX:MaxHeapSize
    
  • boolean类型 -XX: + 或 - 某个参数:(+表示开启、-表示关闭)

    • -XX: +PrintGCDetails:打印GC收集细节
    • -XX: +UseSerialGC:使用串行收集器
9、强引用、软引用、弱引用和虚引用

java除了基础数据类型外,剩余的都是只想各对象的引用;依据其生命周期的长短可以被分为:

1、强引用

  • 通过关键字new创建的对象所关联的引用
  • 当JVM内存不足是,即使抛出OOM运行时错误,也不会随意回收强引用的对象
  • 若强引用对象被赋予null,是可以被回收的

2、软引用

  • 软引用是通过SoftReference类进行实现,其生命周期比强引用短
  • 当JVM内存不足是,才会试图进行回收
  • 使用场景:假如有一个应用需要读取大量的本地图片,但如果每次读取图片都从硬盘读取则会严重影响性能,如果一次性全部加载到内存中,又可能造成内存溢出,所以此时可以使用软引用解决这个问题,使用HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占的空间,从而有效地避免了OOM的问题

3、弱引用

  • 弱引用是通过WeakReference类实现,其生命周期比软引用更短
  • 当垃圾回收器扫描到其内存区域时,不管内存是否够用都会回收弱引用

4、虚引用

  • 虚引用是PhantomReference类进行实现,无法通过虚引用访问对象的任何属性和方法
  • 虚引用随时随地可能被回收,仅仅提供被垃圾回收后,做某些事情的机制
  • 必须与引用队列联合使用,当垃圾回收器准备回收某个对象时,把这个虚引用加入到与之相关联的引用队列中。
  • 场景:可用来跟踪对象被垃圾回收器回收的活动,当一个虚引用的关联对象被垃圾收集器回收前会收到一条系统通知
10、OOM的错误

概念:表示内存用完了,因为没有足够的内存来为对象分配空间并且垃圾回收器也没有空间可回收时,就会抛出OOM异常。

常见错误类型

  • Java heap space

原因:java堆内存溢出,没有足够的空间存放新创建的对象

方案:使用使用-Xms与-Xmx参数调高JVM堆内存空间

  • GC overhead limit exceeded

执行垃圾收集的时间比例太大, 有效的运算量太小,默认情况下,如果GC花费的时间超过 98%, 并且GC回收的内存少于 2%, JVM就会抛出这个错误。应用程序已经基本耗尽了所有可用内存,GC无法回收。

11、其他问题
1、堆栈的区别

物理地址:堆的物理地址分配对对象是不连续的;栈使用的是数据结构的栈,物理地址分配连续

空间大小:堆远远大于栈

存放内容:堆存放对象的实例与数组;栈存放局部变量、操作数栈、返回结果

可见度:堆共享;栈只对线程可见,私有。

2、Full GC会导致什么

Full GC会导致STW,GC期间全程暂停用户的应用程序

3、老年代为什么不使用复制回收

因为老年代中需要保留的对象较多,因此使用复制回收会进行较多的复制操作,效率会降低,所以在老年代一般不使用复制回收算法

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值