GC相关面试题整理

StackOverFlowError和OutOfMemoryError,谈谈你的理解;

StackOverFlowError是栈内存溢出
OutOfMemoryError是堆内存溢出
Java运行时数据区包含:

  • 方法区(也叫非堆)
  • 本地栈
  • 虚拟机栈
  • 程序计数器
    程序计数器是不会抛出StackOverFlowError或者OutOfMemoryError的。
    本地栈和虚拟机栈会抛出StackOverFlowError。
      本地栈和虚拟机栈非常类似,虚拟机栈是虚拟机执行Java方法时的内存区域,本地栈是执行本地方法时的内存区域。在Hotspot虚拟机中,直接将二者合二为一了,当某次线程运行计算时,需要占用的 Java 虚拟机栈(Java Virtual Machine Stack)大小,也就是 Java 线程栈大小,超过规定大小时,抛出 StackOverflowError。如果 Java 虚拟机栈大小可以动态扩容,发生扩容时发现内存不足,或者新建Java 虚拟机栈时发现内存不足,抛出 OutOfMemoryError。
    堆和方法区会抛出OutOfMemoryError。
      Java堆就是存储对象的,如果在Java堆中无法完成内存分配,并且堆也无法再扩展时,Java虚拟机会抛出OutOfMemoryError。方法区存储的是被虚拟机加载的类型信息、常量、静态变量、运行时常量池、即时编译器编译后的代码缓存等数据。和Java堆类似,如果方法区无法满足内存分配时,就会抛出OutOfMemoryError。

一般什么时候会发GC?如何处理?

Java的GC回收有两种:年轻代的MinorGC和老年代的FullGC。
对象会优先分配在Eden区,如果Eden区没有足够的空间进行分配,就会触发一次MinorGC。
在发生MinorGC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间(真悲观,这是预计所有对象都会进入老年代吗?),如果不满足这个条件,JVM会查看配置(-XX:HandlePromotionFailure),如果允许担保失败,会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次MinorGC,但是这次尝试是有风险的,如果小于,或者不允许冒险,就改为执行一次FullGC。

GC回收策略,谈谈你的理解;

个人理解:和把大象关冰箱,总共分几步类似,把垃圾回收总共分几步?
1. 哪些内存需要回收?
堆内存储的都是对象,当对象不再被引用时,就可以认为这个对象是个“垃圾”了。但是堆也是JVM内存管理器管理的最大的内存区域,怎么知道堆内的哪些堆存是有用的,哪些内存是无用的。一般来说有两种方式,1. 引用计数,比如PHP;2. 可达性分析,JVM采用的就是可达性分析。从GC Roots出发标记全部可达对象,没被标记的就可以理解为“垃圾”,就是需要回收的内存。
2. 什么时候回收
当堆内存还有足够空间的时候,分配就完事儿了,不需要回收。但是当堆内存不够了,无法完成内存分配了,就会开启一轮回收,清除掉一些死掉的对象来腾出空间。
3. 如何回收
回收的第一步总是标记,找出哪些对象是不可达的,可以清理的。
第二步有三种做法

  1. 清除:
    直接回收掉所有“垃圾”对象,缺点是:1.执行效率不稳定,标记和清除的效率和Java堆内对象的数量、垃圾对象的数量负相关,堆内对象越多,标记效率越慢,堆内垃圾对象越多,则清除效率越慢。2. 会造成内存碎片。
  2. 复制
    标记-清除算法的特点,堆内对象越多,标记越慢,堆内垃圾对象越多,清除越慢。同时根据大量实践发现,绝大多数的对象都是朝生夕灭的,就产生了分代假说。再根据分代假说,将Java堆划分成了不同的区域:年轻代Young-Generation和老年代Old-Generation。
    复制算法将年轻代又划分了Eden区和两个Survior区。大部分对象都诞生在Eden区,标记完成后,将Eden区和Survior区中少部分还存活的对象复制到另一个Survior区,然后将Eden区和这个Survior区直接清空。在大部分对象都是可回收的场景中,标记-复制算法的效率比标记-清楚要高很多,而且不会产生内存碎片。
  3. 整理
    标记-复制算法在对象存活率低时效率很高,但是在存活率很高的场景下,每次要搬运很多对象,效率就比较拉胯了,而老年代的存亡特征就是这样的。所以标记-整理算法是针对老年代的,为了解决标记-清楚会造成的内存碎片,标记-整理算法在标记完成之后,会让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。

GC是什么;

个人理解:Garbage Collection,垃圾收集。Java的运行时数据区域分为五大部分:程序计数器、堆、方法区(也叫非堆)、虚拟机栈和本地栈。程序计数器存的是线程运行指令的行数,我们不用管。栈(虚拟机栈和本地栈)随着线程的运行入栈、出栈也不需要我们管理。方法区存放的是jvm加载的类型信息、静态变量什么的,这个区域垃圾回收难度大,回收性价比低,目前也没有垃圾收集器去实现方法区的垃圾回收。而运行时几乎所有的对象都是存储在堆内的,我们通过new关键字创建一个对象时,就会相应的在堆内给它分配内存,内存是有限的,如果只分配不释放,迟早会用完没的用了,而且大部分对象都是“朝生夕灭”的,所以释放内存是必要的,而且可以回收的内存也很多,性价比很高。在c++中,内存都是程序员手动释放,但是在java中,并不需要我们手动释放对象的内存,jvm中有个垃圾收集器帮我们做了这个事情,它找到堆内中没有用的对象,清理掉它们,腾出内存空间这个过程叫做GC。

JVM内存模型以及分区,需要详细到每个区放什么;

内存模型:

  • 程序计数器
  • 方法区(非堆、Metadata)
  • 虚拟机栈
  • 本地栈
    分区:
  • 年轻代
    Eden区
    Survior区*2
  • 老年代

对象大部分都诞生在Eden区,所以Eden区存放的都是上一次GC后,新诞生的对象。
Survior区存放的是经历过几次GC后,仍然存活的对象。
老年代存放的是大对象,或者是经历过很多次GC,从Surivior区晋升到老年区的对象,根据分代假说,熬过越多次GC的对象就越难以消亡。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值