JVM相关面试题

本文详细阐述了栈内存溢出、堆内存溢出的条件,以及JVM的内存结构(包括程序计数器、虚拟机栈、本地方法栈、堆、方法区和新生代、老年代的划分),解释了内存分区分级的原因。此外,还介绍了垃圾回收的过程、策略(如标记清除、复制和标记整理)、类加载器的双亲委派模型,以及JVM类的初始化顺序和常见参数配置。
摘要由CSDN通过智能技术生成

1.什么情况下会发生栈内存溢出

  • 栈是线程私有的,他的生命周期与线程相同,每个方法执行的时候都会创建一个栈帧,用来存储局部变量表,操作数栈,动态链栈,方法出口等信息.局部变量表又包含基本数据类型,对象应用类型
  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常,方法递归调用产生这种结果
  • 如果Java虚拟机栈可以动态扩展,并且扩展的动作已经发生过,但是没有足够的空间去完成扩展,或者创建一个新的线程的时候没有足够的内存们就会抛出OutOfMenory(线程启动过多)
  • 参数-Xss来调整JVM的大小

1.2堆内存溢出

  • 当对象一直创建而不回收
  • 加载的类越来越多时
  • 虚拟机栈线程越来越多时

2JVM的内存模型

  • 程序计数器:当前线程所执行的字节码的行号指示器,用于记录正在执行的虚拟机字节指令地址,方便后续线程切回来能继续执行代码
  • 虚拟机栈:每个方法执行的时候都会创建一个栈帧,用来存储局部变量表,操作数栈,动态链栈,方法出口等信息.局部变量表又包含基本数据类型,对象应用类型,方法执行结束后释放栈帧
  • 本地方法栈:与虚拟机栈类似不过它服务于Native方法,为native修饰的本地方法提供的空间,在Hotspot中与虚拟机合二为一
  • 堆内存:Jvm进行垃圾回收的主要区域,存放对象信息,分为新生代和老年代,内存比例为1:2,新生代的Eden区内存不够时时发生MinorGC,老年代内存不够时发生FullGC
  • 方法区:存放类信息、静态变量、常量、运行时常量池等信息。JDK1.8之前用持久代实现,JDK1.8后用元空间实现,元空间使用的是本地内存,而非在JVM内存结构中

3JVM内存为什么要分成新生代,老年代,持久代。新生代中为什么要分为Eden和Survivor

1)共享内存区划分

  • 共享内存区 = 持久带 + 堆
  • 持久带 = 方法区 + 其他
  • Java堆 = 老年代 + 新生代
  • 新生代 = Eden + S0 + S1

2)一些参数的配置

  • 默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ,可以通过参数 –XX:NewRatio 配置。
  • 默认的,Edem : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定)
  • Survivor区中的对象被复制次数为15(对应虚拟机参数 -XX:+MaxTenuringThreshold)

3)为什么要分为Eden和Survivor?为什么要设置两个Survivor区?

  • 如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC.老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多,所以需要分为Eden和Survivor。
  • Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。
  • 设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)

4.JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代

  • Java堆 = 老年代 + 新生代
  • 新生代 = Eden + S0 + S1
  • 当 Eden 区的空间满了, Java虚拟机会触发一次 Minor GC,以收集新生代的垃圾,存活下来的对象,则会转移到 Survivor区。
  • 大对象(需要大量连续内存空间的Java对象,如那种很长的字符串)直接进入老年态;
  • 如果对象在Eden出生,并经过第一次Minor GC后仍然存活,并且被Survivor容纳的话,年龄设为1,每熬过一次Minor GC,年龄+1,若年龄超过一定限制(15),则被晋升到老年态。即长期存活的对象进入老年态。
  • 老年代满了而无法容纳更多的对象,Minor GC 之后通常就会进行Full GC,Full GC 清理整个内存堆 – 包括年轻代和年老代。
  • Major GC 发生在老年代的GC,清理老年区,经常会伴随至少一次Minor GC,比Minor GC慢10倍以上。

5.JVM有哪些垃圾回收算法?

(1)标记清除算法: 标记不需要回收的对象,然后清除没有标记的对象,会造成许多内存碎片。

(2)复制算法: 将内存分为两块,只使用一块,进行垃圾回收时,先将存活的对象复制到另一块区域,然后清空之前的区域。用在新生代

(3)标记整理算法: 与标记清除算法类似,但是在标记之后,将存活对象向一端移动,然后清除边界外的垃圾对象。用在老年代

6.GC如何判断对象可以被回收?

(1)引用计数法:已淘汰,为每个对象添加引用计数器,引用为0时判定可以回收,会有两个对象相互引用无法回收的问题

(2)可达性分析法:从GCRoot开始往下搜索,搜索过的路径称为引用链,若一个对象GCRoot没有任何的引用链,则判定可以回收

GCRoot有:虚拟机栈中引用的对象,方法区中静态变量引用的对象,本地方法栈中引用的对象

7.你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms和G1,包括原理,流程,优缺点。

1)几种垃圾收集器:

  • Serial收集器: 单线程的收集器,收集垃圾时,必须stop the world,使用复制算法。
  • ParNew收集器: Serial收集器的多线程版本,也需要stop the world,复制算法。
  • Parallel Scavenge收集器: 新生代收集器,复制算法的收集器,并发的多线程收集器,目标是达到一个可控的吞吐量。如果虚拟机总共运行100分钟,其中垃圾花掉1分钟,吞吐量就是99%。
  • Serial Old收集器: 是Serial收集器的老年代版本,单线程收集器,使用标记整理算法。
  • Parallel Old收集器: 是Parallel Scavenge收集器的老年代版本,使用多线程,标记-整理算法。
  • CMS(Concurrent Mark Sweep) 收集器: 是一种以获得最短回收停顿时间为目标的收集器,标记清除算法,运作过程:初始标记,并发标记,重新标记,并发清除,收集结束会产生大量空间碎片。
  • G1收集器: 标记整理算法实现,运作流程主要包括以下:初始标记,并发标记,最终标记,筛选标记。不会产生空间碎片,可以精确地控制停顿。

2)CMS收集器和G1收集器的区别:

  • CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;
  • G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用;
  • CMS收集器以最小的停顿时间为目标的收集器;
  • G1收集器可预测垃圾回收的停顿时间
  • CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片
  • G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。

8.类加载器和双亲委派机制

从父类加载器到子类加载器分别为:

BootStrapClassLoader 加载路径为:JAVA_HOME/jre/lib

ExtensionClassLoader 加载路径为:JAVA_HOME/jre/lib/ext

ApplicationClassLoader 加载路径为:classpath

还有一个自定义类加载器

当一个类加载器收到类加载请求时,会先把这个请求交给父类加载器处理,若父类加载器找不到该类,再由自己去寻找。该机制可以避免类被重复加载,还可以避免系统级别的类被篡改

8.1为什么需要双亲委派模型?

在这里,先想一下,如果没有双亲委派,那么用户是不是可以自己定义一个java.lang.Object的同名类,java.lang.String的同名类,并把它放到ClassPath中,那么类之间的比较结果及类的唯一性将无法保证,因此,为什么需要双亲委派模型?防止内存中出现多份同样的字节码

8.2怎么打破双亲委派模型?

打破双亲委派机制则不仅要继承ClassLoader类,还要重写loadClass和findClass方法。

9.JVM中有哪些引用?

强引用:new的对象。哪怕内存溢出也不会回收

软引用:只有内存不足时才会回收

弱引用:每次垃圾回收都会回收

虚引用:必须配合引用队列使用,一般用于追踪垃圾回收动作

10.类加载过程

(1)加载 :把字节码通过二进制的方式转化到方法区中的运行数据区

(2)连接:

验证:验证字节码文件的正确性。

准备:正式为类变量在方法区中分配内存,并设置初始值,final类型的变量在编译时已经赋值了

解析:将常量池中的符号引用(如类的全限定名)解析为直接引用(类在实际内存中的地址)

(3)初始化 :执行类构造器(不是常规的构造方法),为静态变量赋初值并初始化静态代码块。

11.JVM类初始化顺序

父类静态代码块和静态成员变量->子类静态代码块和静态成员变量->父类代码块和普通成员变量->父类构造方法->子类代码块和普成员变量->子类构造方法

12.说说你知道的几种主要的JVM参数

1)堆栈配置相关

-Xmx3550m: 最大堆大小为3550m。

-Xms3550m: 设置初始堆大小为3550m。

-Xmn2g: 设置年轻代大小为2g。

-Xss128k: 每个线程的堆栈大小为128k。

-XX:MaxPermSize: 设置持久代大小为16m

-XX:NewRatio=4: 设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。

-XX:SurvivorRatio=4: 设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6

-XX:MaxTenuringThreshold=0: 设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。

2)垃圾收集器相关

-XX:+UseParallelGC: 选择垃圾收集器为并行收集器。

-XX:ParallelGCThreads=20: 配置并行收集器的线程数

-XX:+UseConcMarkSweepGC: 设置年老代为并发收集。

-XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。

-XX:+UseCMSCompactAtFullCollection: 打开对年老代的压缩。可能会影响性能,但是可以消除碎片

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值