JVM常见面试题1——垃圾回收

相关链接:

JVM常见面试题1:https://blog.csdn.net/qq_41822345/article/details/104417108
JVM常见面试题2:https://blog.csdn.net/qq_41822345/article/details/104531570
JVM常见面试题3:https://blog.csdn.net/qq_41822345/article/details/104531640
Java 内存模型:https://blog.csdn.net/qq_41822345/article/details/104617364

Java 帮我们管理内存,但是这并不代表我们不需要了解Java的内存结构,因为线上经常出现内存相关问题。

1.JVM 内存划分。

①.方法区(线程共享):常量、静态变量、JIT(即时编译器) 编译后的代码,class的相关信息也都在方法区;
②.堆内存(线程共享)(堆):存储对象实例以及数组容器,垃圾回收的主要场所;
③.程序计数器: 当前线程执行的字节码的位置指示器;
④.虚拟机栈(栈):保存局部变量、基本数据类型变量以及堆内存中某个对象的引用变量;
⑤.本地方法栈 :为 JVM 提供使用 native 方法的服务。
在这里插入图片描述

2.Java中垃圾回收有什么目的?垃圾回收?

垃圾回收的目的是识别并且丢弃应用不再使用的对象来释放和重用资源。
垃圾回收主要从三个方面去理解:
①哪些对象需要回收?引用计数法,和可达性分析算法。
②何时进行回收?CPU空闲,堆满,system.gc()。
③如何回收?标记-清除算法,复制算法,标记-整理算法,分代收集算法。

java的内存管理:对象的分配与释放
分配:程序员通过new为每个对象申请内存空间(基本类型除外),所有对象都在堆中分配空间;
释放:对象的释放是由垃圾回收机制决定和执行的。

Java内存分为两种:栈内存和堆内存
(1)在函数中定义的基本类型变量(即基本类型的局部变量)和对象的引用变量(即对象的变量名)都在栈内存中分配;
(2)堆内存用来存储由new创建的对象和数组以及对象的实例变量(即全局变量)
(3)堆的优势是可以动态分配内存大小,生存期也不必事先告诉编译器,因为它是运行时动态分配内存的;缺点是运行时动态分配内存,存取速度慢;
(4)栈的优点是存取速度较快仅仅次于CPU中的寄存器,且栈数据可以共享;但是栈中的数据大小和生存期是必须确定的,缺乏灵活性;
(5)只有一个堆区被所有线程共享,堆区不存放基本类型和对象引用只存放对象本身;
(6)每个线程包含一个栈区,保存基础数据类型的对象和自定义对象的引用,每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问;
  还有一个方法区:存储所有对象数据共享区域,存储静态变量和普通方法、静态方法、常量、字符串常量等信息,又叫静态区,是所有线程共享的。

Java对象销毁与释放
   Java中垃圾收集器自动地定期扫描Java对象的动态内存,并将所有的引用对象加上标记,在对象运行结束后(无引用变量对该对象进行关联),清除其标记,并将所有无标记的对象作为垃圾进行回收,释放垃圾对象所占的内存空间。对象运行结束后或生命周期结束时,将成为垃圾对象,但并不意味着就立即会被回收,仅当垃圾收集器空闲或内存不足时,才会回收他们。
  Java中每个对象都拥有一个finalize()方法:垃圾回收器在回收对象时自动调用对象的finalize()方法来释放系统资源。

3.哪些对象可以被作为GC Roots对象?(哪些对象会一直存活?)

①虚拟机栈(栈帧中的本地变量表)中引用的对象;
②本地方法中JNI(即一般说的native方法)引用的对象;
③方法区中静态变量和常量引用的对象。

4.垃圾回收的过程?垃圾回收分代收集算法?为什么会有两个Survivor区?new一个对象会保存在哪里?

  当系统创建一个对象的时候,总是在Eden区操作,当这个区满了,那么就会触发一次YoungGC,也就是年轻代的垃圾回收。一般来说这时候不是所有的对象都没用了,所以就会把还能用的对象复制到From区。 这样整个Eden区就被清理干净了,可以继续创建新的对象,当Eden区再次被用完,就再触发一次YoungGC,然后呢,注意,这个时候跟刚才稍稍有点区别。这次触发YoungGC后,会将Eden区与From区还在被使用的对象复制到To区, 再下一次YoungGC的时候,则是将Eden区与To区中的还在被使用的对象复制到From区。 经过若干次YoungGC后,有些对象在From与To之间来回游荡,这时候From区与To区亮出了底线(阈值),这些家伙要是到现在还没挂掉,对不起,一起滚到(复制)老年代吧。 老年代经过这么几次折腾,也就扛不住了(空间被用完),好,那就来次集体大扫除(Full GC),也就是全量回收,一起滚蛋吧。

  我是一个普通的Java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor区,我就开始漂了,有时候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年(每次GC加一岁),然后被回收。

  • jdk1.8之前
    在这里插入图片描述
  • jdk1.8之后
    在这里插入图片描述

  分代垃圾回收算法来回收垃圾,思想也很简单,就是根据对象的生命周期将内存划分,然后进行分区管理。在Java虚拟机分代垃圾回收机制中,应用程序可用的堆空间可以分为年轻代与老年代,然后呢,年轻代有被分为Eden区,From区与To区。

在这里插入图片描述

年轻代:Minor GC;老年代:Major GC(因为Major GC一般伴随着Minor GC,也可以看做触发了Full GC)
先看看为什么要有Survivor区:如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC(Full GC),Full GC消耗的时间是非常可观的,所以我们需要减少老年代的GC→减少老年代被填满。
再看看为什么有两个Survivor区:刚刚新建的对象在Eden中,一旦Eden满了,触发一次Minor GC,Eden中的存活对象就会被移动到Survivor区。这样继续循环下去,下一次Eden满了的时候,问题来了,此时进行Minor GC,Eden和Survivor各有一些存活对象,如果此时把Eden区的存活对象硬放到Survivor区,很明显这两部分对象所占有的内存是不连续的,也就导致了内存碎片化。所以应该建立两块Survivor区(S0和S1),刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块S1(这个过程非常重要,因为这种复制算法保证了永远有一个survivor space是空的,另一个非空的survivor space无碎片)。

5.G1回收器和其他回收器有什么区别?

在这里插入图片描述
并发:是指两个或多个事件在同一时段内发生,可以有时间间隔。如:CMS就是并发标记、并发清除与用户线程并发执行。
并行:是指两个或多个事件在同一时间发生。如:G1就是并发标记,并行处理。
  G1(Garbage First)垃圾收集器是当今垃圾回收技术最前沿的成果之一。同优秀的CMS垃圾回收器一样,G1也是关注最小时延的垃圾回收器,也同样适合大堆内存的垃圾收集,官方也推荐使用G1来代替选择CMS。G1最大的特点是引入分区的思路,弱化了分代的概念,合理利用垃圾收集各个周期的资源,解决了其他收集器甚至CMS的众多缺陷。

  • ①. Serial + Serial Old:串行收集器是最基本、发展时间最长、久经考验的垃圾收集器,也是client模式下的默认收集器配置。串行收集器采用单线程stop-the-world的方式进行收集。当内存不足时,串行GC设置停顿标识,待所有线程都进入安全点(Safepoint)时,应用线程暂停,串行GC开始工作,采用单线程方式回收空间并整理内存。单线程也意味着复杂度更低、占用内存更少,但同时也意味着不能有效利用多核优势。事实上,串行收集器特别适合堆内存不高、单核甚至双核CPU的场合。
    安全点就是指代码中一些特定的位置,当线程运行到这些位置时它的状态是确定的,这样JVM就可以安全的进行一些操作,比如GC. 线程怎么知道什么时候要进入到saftpoint呢,一般有抢占式和主动式两种,常见的做法就是设置一个状态位,让所有线程去检查这个状态,当检测到saftpoint标志时就停下来。 eg:抛出异常的位置、循环的末尾、方法返回之前、调用某个方法之后。
  • ②Parallel Scavenge + Parallel Old:并行收集器是以关注吞吐量为目标的垃圾收集器,也是server模式下的默认收集器配置,对吞吐量的关注主要体现在年轻代Parallel Scavenge收集器上。多线程、stop-the-world、吞吐量主要指年轻代的Parallel Scavenge收集器,通过两个目标参数-XX:MaxGCPauseMills和-XX:GCTimeRatio,调整新生代空间大小,来降低GC触发的频率。
  • ③ ParNew + CMS + Serial Old:并发标记清除(CMS)是以关注延迟为目标、十分优秀的垃圾回收算法,开启后,年轻代使用STW式的并行收集,老年代回收采用CMS进行垃圾回收,对延迟的关注也主要体现在老年代CMS上。初始标记、并发标记、重新标记、并发清除。其中,初始标记以STW的方式标记所有的根对象;并发标记则同应用线程一起并行,标记出根对象的可达路径;在进行垃圾回收前,CMS再以一个STW进行重新标记,标记那些由mutator线程(指引起数据变化的线程,即应用线程)修改而可能错过的可达对象;最后得到的不可达对象将在并发清除阶段进行回收。值得注意的是,初始标记和重新标记都已优化为多线程执行。CMS非常适合堆内存大、CPU核数多的服务器端应用,也是G1出现之前大型应用的首选收集器。
  • ④G1(Garbage First)收集与以上三组收集器有很大不同:
      (1)G1的设计原则是"首先收集尽可能多的垃圾(Garbage First)"。因此,G1并不会等内存耗尽(串行、并行)或者快耗尽(CMS)的时候开始垃圾收集,而是在内部采用了启发式算法,在老年代找出具有高收集收益的分区进行收集。同时G1可以根据用户设置的暂停时间目标自动调整年轻代和总堆大小,暂停目标越短年轻代空间越小、总空间就越大;
      (2)G1采用内存分区(Region)的思路,将内存划分为一个个相等的内存分区,回收时则以分区为单位进行回收,存活的对象复制到另一个空闲分区中。由于都是以相等的分区为单位进行操作,因此G1天然就是一种压缩方案(局部压缩);
      (3)G1虽然也是分代收集器,但整个内存分区不存在物理上的年轻代与老年代的区别,也不需要完全独立的survivor堆做复制准备。G1只有逻辑上的分代概念,或者说每个分区都可能随G1的运行在不同代之间前后切换;
      (4)G1的收集都是STW的,但年轻代和老年代的收集界限比较模糊,采用了混合(mixed)收集的方式。即每次收集既可能只收集年轻代分区(年轻代收集),也可能在收集年轻代的同时,包含部分老年代分区(混合收集),这样即使堆内存很大时,也可以限制收集范围,从而降低停顿。
      没有最好的垃圾收集器,也没有万能的垃圾收集器;只有适合具体应用场景的收集器。

6.垃圾回收为什么会停顿?哪些对象可能为GcRoots? 内存泄漏vs内存溢出?如何处理?

  垃圾收集的一个前提是要判断进程中的对象哪些是垃圾内存,哪些不是。怎么判断呢,JVM里面使用了一种叫可达性分析的技术来枚举根节点。一言以蔽之,JVM的内存空间里的若干对象都会有联系,形成树结构,如果一个对象通过寻路,能够找到根节点,那么这个对象就是活的,不能回收,否则就要回收。
  在这个可达性分析过程中,是必须要求分析过程中树结构是不变的,也就是一致的。这意味着这个过程中,当前JAVA进程必须暂停,这就是停顿的根本原因。
  在Java语言里,可作为GC Roots对象的包括如下几种: a.虚拟机栈(栈中的本地变量表)中的引用的对象 ;b.方法区中的类静态属性引用的对象 ;c.方法区中的常量引用的对象 ;d.本地方法栈中JNI(即一般说的native方法)的引用的对象。(从程序的角度来说就是,找到一段程序运行的整个过程中,始终会存活对象,这些对象的特点是始终会存活,不会死亡。即一些静态变量和常量所引用的对象等)。
  内存溢出也就是jvm分配的内存中对象过多,超出了最大可分配内存的大小。
  ①内存溢出是指内存不够用了;(栈深度>虚拟机所允许的深度(栈溢出(方法递归调用自己的时候));扩展栈时申请不到足够内存(内存溢出))。
  ②内存泄漏是指本该被GC的对象没有被GC回收;(无用对象未设置为null;长生命周期对象引用短生命周期对象)。
  ③内存泄漏(积累)→内存溢出。

简单处理思路:①若是内存泄漏,即本该被回收的对象未被及时回收。应该通过内存映像分析工具检查泄漏对象到GCRoots的引用链,查看它是通过怎样的路径与GCRoots相关联的,从而定位出泄漏代码。
②若是内存溢出,若是内存泄漏积累导致,那么①;若是所有对象应该继续存活,不该被回收,那就应该先检查是否是设置问题,看看虚拟机的堆参数(-Xms与-Xmx)与机器物理内存对比是否可以调整,然后是在代码上检查哪些对象生命周期过长,尝试减少程序运行内存。

JVM常见面试题2::https://blog.csdn.net/qq_41822345/article/details/104531570

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

进击的程序猿~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值