面试题汇总四 JVM 篇

本文汇总了关于JVM的面试重点,包括解析与分派、GC策略、类加载器、堆内存设置、垃圾回收算法、类加载过程和时机、内存模型、优化策略等。讲解了静态分派与动态分派的差异,探讨了不同GC策略的适用场景,以及如何处理内存和堆栈溢出问题。还深入讨论了类加载器的工作原理和自定义场景,以及对象在内存中的移动路径和GC Root的判断标准。
摘要由CSDN通过智能技术生成

前言

题目汇总来源 史上最全各类面试题汇总,没有之一,不接受反驳

 


目录

前言

JVM

谈谈你对解析与分派的认识。

分派:静态分派与动态分派。

你知道哪些或者你们线上使⽤什么GC策略?它有什么优势,适⽤于什么场景?

Java类加载器包括⼏种?它们之间的⽗⼦关系是怎么样的?双亲委派机制是什么意思?有什么好处?

如何⾃定义⼀个类加载器?你使⽤过哪些或者你在什么场景下需要⼀个⾃定义的类加载器吗?

双亲委派流程

双亲委派机制打破

堆内存设置的参数是什么?

类加载过程

类加载时机

Perm Space中保存什么数据?会引起OutOfMemory吗?

垃圾回收算法

做GC时,⼀个对象在内存各个Space中被移动的顺序是什么?

哪些对象可以作为GC Root

吞吐量和暂停时间

垃圾收集器

GC触发时机

你有没有遇到过OutOfMemory问题?你是怎么来处理这个问题的?处理 过程中有哪些收获?

StackOverflow异常有没有遇到过?⼀般你猜测会在什么情况下被触发?如何指定⼀个线程的堆栈⼤⼩?⼀般你们写多少?

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

虚拟机在运行时有哪些优化策略

请解释StackOverflowError和OutOfMemeryError的区别?

在JVM中,如何判断一个对象是否死亡?


 

JVM

谈谈你对解析与分派的认识。

分派:静态分派与动态分派。

多态方法调用的解析和分派

解析和分派属于方法调用的内容。

方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部具体的运行过程。

解析

将一部分(编译期可知,运行期不可变)的符号引用转为直接引用

调用方法的四种字节码:

invokestatic 调用静态方法

invokespacial 调用<init>、私有方法和父类方法

invokevirtual 调用所有虚方法

invokeinterface 调用接口方法,运行时确定实现此接口的方法

能被 invokestatic 和 invokespecial 调用的方法可以在解析时确定唯一版本调用。

Java中符合的有静态方法、私有方法、实例构造器和父类方法,它们不可继承或重写,称为非虚方法。

另外,final方法也是非虚方法,虽然是 invokevirtual 指令调用,但无法被覆盖,对多态来说选择结果唯一。

分派

分派与面向对象的三个基本特征之一:多态有关,如重载和重写。

解析和分派不是对立关系,如重载静态方法,既发生了解析,又发生了静态分派。

静态分派

依据静态类型来定位方法执行版本的分派动作称为静态分派。

和方法重载有关,重载方法的参数类型是依据静态类型,而静态类型编译期已知。

动态分派

运行期根据实际类型确定方法执行版本的分派过程称为动态分派。

单分派和多分派

Java静态多分派,动态单分派

静态分派,根据静态类型和参数类型两个宗量进行选择,多分派

动态分派,传入参数已确定,只要判断最终执行对象类型,单分派。

 

你知道哪些或者你们线上使⽤什么GC策略?它有什么优势,适⽤于什么场景?

触发JVM进行Full GC的情况及应对策略

Minor GC:指发生在新生代的垃圾收集动作

MajorGC / Full GC:指发生在老年代的GC

对象优先分配在Eden区

eden区空间不足时Minor GC

大对象直接进入老年代

避免短命大对象

长期存活对象进入老年代

每个对象有一个对象年龄(Age)计数器

Eden区一个对象第一次Minor GC存活并能被Survivor区容纳,则移至Survivor且对象年龄设为1

在Survivor中每经过一次Minor GC对象年龄+1

到达一定年龄(默认15)移至老年代

动态对象年龄判断

Survivor中相同年龄所有对象大小的总和大于Survivor空间的一半,大于等于该年龄的对象直接进入老年代

空间分配担保

Minor GC时,若之前每次晋升入老年代的平均大小>老年代剩余空间,则直接Full GC

否则若设置允许担保失败,则只Minor GC,否则Full GC

 

Java类加载器包括⼏种?它们之间的⽗⼦关系是怎么样的?双亲委派机制是什么意思?有什么好处?

深入理解和探究Java类加载机制----

Java类加载器包括几种?父子关系?

这里的层次关系为组合,即子加载器持有父加载器的引用,而非继承关系。

Application ClassLoader 应该和 System ClassLoader 是一个意思

双亲委派机制

双亲委派模型如上图所示。

工作过程:一个类加载器收到类加载请求,先把这个请求委托给父类加载器完成,因此所有加载请求都会传送到顶层启动加载器中,只有当父加载器反馈自己无法完成加载请求(其搜索范围没有找到所需类),子类才尝试自己加载。

双亲委派机制好处

保证java核心库的安全性(例如:如果用户自己写了一个java.lang.String类就会因为双亲委派机制不能被加载,不会破坏原生的String类的加载)

 

如何⾃定义⼀个类加载器?你使⽤过哪些或者你在什么场景下需要⼀个⾃定义的类加载器吗?

  1. 加载特定路径的class文件
  2. 加载一个加密的网络class文件
  3. 热部署加载class文件

 

双亲委派流程

深入理解Java类加载器(ClassLoader)

loadClass

找缓存 → 找父加载器 loadClass → 自己尝试 findClass → 根据条件解析 class

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);
                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

findClass

JDK1.2后不建议覆盖 loadClass,将自定义加载类的过程放在 findClass 里。

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

 

双亲委派机制打破

服务提供者接口SPI存在于rt.jar,由Bootstrap类加载器加载,但服务方提供的SPI实现类无法由Bootstrap类加载器加载,需由App类加载器加载。Thread类中的contextClassLoader默认保存App类加载器的引用,Bootstrap类加载器以此委托App类加载器加载SPI实现类。但这就打破了双亲委派模型。

 

堆内存设置的参数是什么?


 

类加载过程

Java 类加载过程

加载:通过类的全限定名获取二进制流,加载入方法区,生成一个代表这个类的java.lang.Class对象。

验证:确保class字节流符合规定且安全。

准备:类变量赋初值(准确说是置零)。

解析:符号引用转换为直接引用。

初始化:调用类的<clinit>()方法,初始化类的资源和变量。

 

类加载时机

面试必问之JVM篇

JVM类生命周期概述:加载时机与加载过程

  • new新对象 / 读取或设置static属性 / 调用static方法;
  • java.lang.reflect对类反射;
  • 子类初始化时先初始化父类;
  • 虚拟机启动时先初始化main方法所在的主类;
  • jdk1.7动态语言支持

 

Perm Space中保存什么数据?会引起OutOfMemory吗?

Java方法区和永久代

Java8内存模型—永久代(PermGen)和元空间(Metaspace)

JAVA 方法区与堆--java7前,java7,java8各不相同

方法区存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等

 

java7之前,方法区位于永久代(PermGen),永久代和堆相互隔离,永久代的大小在启动JVM时可以设置一个固定值,不可变;

java7中,存储在永久代的部分数据就已经转移到Java Heap或者Native memory。但永久代仍存在于JDK 1.7中,并没有完全移除,譬如符号引用(Symbols)转移到了native memory;字符串常量池(interned strings)转移到了Java heap;类的静态变量(class statics)转移到了Java heap。

java8中,取消永久代,方法存放于元空间(Metaspace),元空间仍然与堆不相连,但与堆共享物理内存,逻辑上可认为在堆中

由于方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。最典型的场景就是,在 jsp 页面比较多的情况,容易出现永久代内存溢出。

 

垃圾回收算法

垃圾回收算法

标记清除算法 Mark-Sweep

第一遍扫描内存标记出无用对象,第二遍扫描内存清除无用对象。

效率低且会造成内存碎片化。

复制算法 Copying

将存活对象复制到新内存空间中,清空旧内存空间。

适用于对象存活率低的情况。

JVM中,新生代的Survivor区用到该算法。

标记整理算法 Mark-Compact

在标记清除算法基础上,把对象移向内存一端,留出连续的内存空间,解决碎片化问题。

适用于对象较大、存活率较高的情况。

JVM中,老年代使用该算法。

分代收集算法 Generational Collection

JVM中,新生代使用Copying,老年代使用Mark-Compact。

 

做GC时,⼀个对象在内存各个Space中被移动的顺序是什么?

参照上面GC策略的过程。

普通对象生于Eden区,然后在两个Survivor区中往返,最后进入老年代。中途死于各种GC。

大对象或通过动态对象年龄判定的对象直接进入老年代。死于Major GC。

 

哪些对象可以作为GC Root

Java常见的GC Root

  • 通过System Class Loader或者Boot Class Loader加载的class对象,通过自定义类加载器加载的class不一定是GC Root
  • 处于激活状态的线程
  • 栈中的对象
  • JNI栈中的对象
  • JNI中的全局对象
  • 正在被用于同步的各种锁对象
  • JVM自身持有的对象,比如系统类加载器等。

 

吞吐量和暂停时间

JVM实用参数(六) 吞吐量收集器

吞吐量:应用程序线程用时占程序总用时的比例。GC线程会导致应用程序线程暂停,吞吐量越高,程序执行越快。

暂停时间:GC线程导致应用程序线程暂停的时长。过长的暂停时间会影响用户体验。

 

垃圾收集器

Jvm垃圾回收器(终结篇)

 

GC触发时机

你能不能谈谈,Java GC是在什么时候,对什么东西,做了什么事情?

java基础—常用的GC策略,什么时候会触发YGC,什么时候触发FGC?

minor GC: 新生代满了

full GC: 老年代满了;新生代晋升到老年代的平均大小超过剩余空间……

 

你有没有遇到过OutOfMemory问题?你是怎么来处理这个问题的?处理 过程中有哪些收获?

常见的原因如下:

1)内存加载的数据量太大:一次性从数据库取太多数据;

2)集合类中有对对象的引用,使用后未清空,GC不能进行回收;

3)代码中存在循环产生过多的重复对象;

4)启动参数堆内存值小。

 

StackOverflow异常有没有遇到过?⼀般你猜测会在什么情况下被触发?如何指定⼀个线程的堆栈⼤⼩?⼀般你们写多少?

触发情况:

栈内存溢出,一般由栈内存的局部变量过爆了,导致内存溢出。出现在递归方法,参数个数过多,递归过深,递归没有出口。

栈大小设置:

JVM优化系列之一(-Xss调整Stack Space的大小)

-Xss:如-Xss128k

JDK5.0以后每个线程堆 栈大小为1M,以前每个线程堆栈大小为256K。根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

线程栈的大小是个双刃剑,如果设置过小,可能会出现栈溢出,特别是在该线程内有递归、大的循环时出现溢出的可能性更大,如果该值设置过大,就有影响到创建栈的数量,如果是多线程的应用,就会出现内存溢出的错误。

 

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

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

程序计数器

较小的内存空间,当前线程所执行的字节码的行号指示器。

线程私有。

Java虚拟机栈

线程私有。

描述java方法执行的内存模型。

每个方法执行时创建一个栈帧用于存储局部变量表,操作栈,动态链接,方法出口等。

局部变量表:基本数据类型+reference,long和double两个slot,其余1个。

局部变量表的大小在编译期确定。

异常情况:栈帧过大 StackOverflowError;虚拟机栈扩展无法申请足够内存 OutOfMemoryError。

本地方法栈

与虚拟机栈类似,不过是执行Native方法服务。

java堆

线程共享。

对象实例和数组在此分配。

垃圾收集器管理的主要区域。

可以划分线程私有的分配缓冲区(TLAB)。

方法区

线程共享。

存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。

运行时常量池

方法区的一部分。

存放编译期生成的各种字面量和符号引用。

直接内存

不属于Java虚拟机内存。

某些io操作直接操控native堆。

 

虚拟机在运行时有哪些优化策略

JVM(1)---虚拟机在运行期的优化策略

 

请解释StackOverflowError和OutOfMemeryError的区别?

根据《深入理解Java虚拟机》里的说法,StackOverflowError出现在虚拟机栈和本地方法栈中,对虚拟机栈来说,StackOverflowError在线程请求的栈深度大于虚拟机所允许的深度时抛出;OutOfMemoryError出现在除程序计数器之外的其他区域,基本都是因为无法满足内存分配且无法扩展而产生的。

 

在JVM中,如何判断一个对象是否死亡?

首先使用根搜索算法判定对象是否存活。

若根搜索算法不可达,还要经历两次标记:

  1. 判定对象是否有必要执行 finalize() 方法。若对象没有覆盖 finalize() 或已被虚拟机执行过则会被判定“不必要执行”。
  2. 若被判定要执行 finalize() 方法,对象会被移进 F-Queue等待方法执行,若在方法中对象被引用链上的任意对象引用,在第二次标记中会被移出“即将回收”集合。

对象被回收后,判定真正死亡。

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值