JVM面试集合

1、什么情况下会发生栈内存溢出
栈是线程私有的,栈的生命周期和线程一样,每个方法在执行的时候就会创建一个栈帧,它包含局部变量表、操作数栈、动态链接、方法出口等信息,局部变量表又包括基本数据类型和对象的引用。当线程请求的栈深度超过了虚拟机允许的最大深度时,会抛出stackoverflowerror异常,方法递归调用可能会出现这个问题,处理的办法可以尝试去调整参数-xss去调整jvm栈的大小。

2、详解JVM内存模型
JVM将虚拟机分为五大部分(一般我们常说是三部分:栈、堆、方法区),程序计数器、虚拟机栈、本地方法栈、堆、方法区。
程序计数器:线程私有的,是一块很小的内存空间,用于记录当前虚拟机正在执行的线程指令地址;
虚拟机栈:线程私有的,每个方法执行的时候都会创建一个栈帧,用于存储局部变量表,操作数,动态链接和方法返回等信息,当线程请求的栈深度超过了虚拟机允许的最大深度时,就会抛出stackoverflowerror;
本地方法栈:线程私有的,保存的是native方法的信息(native非java的,是c++的),当一个jvm创建的线程调用native时,jvm不会在虚拟机栈中为该线程创建栈帧,而是简单的动态链接并直接调用该方法;
堆:java堆是所有线程共享的一块内存,几乎所有对象的实例和数组都要在堆上分配内存,因此该区域进行发生GC的操作;
方法区:存放已被加载的类信息、常量、静态变量、编译后的代码。其实也就是加载的类信息和常量池。

3、JVM中一次完整的GC是什么样子,对象怎么晋升到老年代
java中GC是发生在堆上的,而堆上分新生代和老年代;
而新生代又分为Eden(伊甸园)和Suivivor(幸存一区和幸存二区),默认分配比是8:1:1.
当Eden区空间满了的时候,就会触发一次轻GC,这个时候幸存下来的对象会被分配到Suivivor区,当GC对幸存区进行清理的时候,某一个对象在经历十五次GC都存活的情况下,这个时候,存活的对象就会被分到老年代区。
当老年代区满了之后,无法容纳更多对象的话,会触发一次重GC,重GC一般会清理整个堆内存。

4、java中的垃圾回收算法
java中有四种垃圾回收算法,分别是标记清除算法,标记算法、复制算法、分代收集算法;
标记清除算法:比较简单,先是一遍FGC遍历一次内存,把存活的对象和垃圾对象进行标记,然后再遍历一遍,将所有被标记的对象回收掉,这个有个弊端就是:效率不行,毕竟两次遍历,而且无论是标记也好还是清除也罢都会产生大量的空间分片,可能会导致之后程序运行因为有不连续的空间分片而不得不进行一次GC。
标记算法:也是先遍历一次内存,把存活和垃圾对象进行标记,然后将所有存活的对象向一端移动,将存活对象所处的内存区域以外的地方的对象全部都回收掉。
复制算法:就是将内存分为两个一样大小的两块,每次只使用其中一块,当一块使用完了,就把还存活的对象复制到另外一块内存中,然后把使用过的内存空间清理掉,不过这个算法弊端在,内存使用率极低。
分代收集算法:就是根据堆内存的空间分区,进行针对性的算法,在新生代中,有大量对象死去和少量对象存活,就可以采用复制算法,因为存活的对象很少,复制的成本就比较少,老年区中因为存活的对象多,死的对象少,没有额外的空间进行再分配,所以可以采用标记清理和标记算法进行回收。

5、怎么判断一个对象是否存活
判断一个对象是否存活,可以使用两种算法来判断:1.引用计数器,2.可达性分析算法;
引用计数器:给每个对象设置一个引用计数器,当有一个地方引用该对象的时候,引用计数器就+1,引用失效时,引用计数器就-1,当引用计数器为0的时候,就说明这个对象没有被引用,也就是垃圾对象,需要被GC。
可达性分析算法:从GC Root向下搜索,判断是否有对象引用, 这里的引用分为虚拟机栈中的引用、方法区的静态属性、常量池的引用,如果没有从这几个地方引用,也不会马上回收,先进行两次标记,第一次标记,先判断当前对象有没有finalize()方法,并且这个方法没有被执行(这个方法是GC回收的方法,有些c++转过来的同学应该开始写java的时候会写过这个方法吧,java中如果重写了这个方法,就会出现oom,这个时候就需要JVM优化了),若不存在就会标记成垃圾对象,等待回收,而若有的话,就进行第二次标记,第二次标记把当前对象放在一个F-Queue队列中,并且生成finalize线程去执行上面的finalize方法。

6、有哪几种垃圾回收器,有哪些优缺点,cms和G1的区别
垃圾回收器主要分为以下几种:Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1;
Serial:单线程的收集器,收集垃圾时必须stop the world,使用复制算法。
ParNew:Serial收集器的多线程版本,也需要stop the world,复制算法。
Parallel Scavenge:新生代收集器,复制算法的收集器,并发的多线程收集器,目标是达到一个可控的吞吐量,和ParNew的最大区别是GC自动调节策略,虚拟机会根据系统的运行状态收集性能监控信息,动态设置这些参数,以提供最优停顿时间和最高的吞吐量。
Serial Old:Serial收集器的老年代版本。单线程收集器使用的是标记算法。
Parallel Old:是Parallel Scavenge的老年代版本,使用的是多线程,使用的GC算法是标记算法。
CMS:是一种以最短回收停顿时间为目标的收集器,标记清除算法,过程是:初始标记,并发标记,重新标记,并发清除,收集结束会产生大量空间碎片。
G1:标记算法,过程是:初始标记,并发标记,最终标记,筛选回收,不会产生空间碎片,可以精确的控制停顿。

7、什么是类加载
虚拟机把类的数据加载到内存里面,并对数据进行校验、解析和初始化,最终变成可以被虚拟机直接使用的class对象。

8、类加载的过程
大体分为以下几个过程:加载、验证、准备、解析、初始化;
加载:分为三步。1、根据类名获取该类的二进制流(电脑里面的存储都是二进制),2、将二进制流的存储结构转为方法区运行时的数据结构(其实就是将二进制转成jvm方法区可以运行的数据结构),3、在堆中为该类生成一个class对象。
验证:验证加载的class对象中的字节流信息符合虚拟机的要求,不会威胁jvm安全。
准备:为class对象的静态变量分配内存,初始化其初始值。
解析:该阶段主要完成符号引用转化成直接引用。
初始化:到了初始化阶段,才开始执行类中定义的java代码。

9、什么是类加载器,常见的类加载器有哪些
类加载器是指:通过一个全限定性类名(也可以理解为绝对路径下的类)获取该类的二进制流叫做类加载器。
类加载器分为下面四种:
启动类加载器:用来加载java核心类库,无法被java程序直接引用,启动类加载器是用c++写的,向native一样,而且看原代码是在jre下面的。
扩展类加载器:用来加载java的扩展库,java的虚拟机实现会提供一个扩展库目录,该类加载器在扩展库目录里面查找并加载java类。
系统类加载器:它根据java的类路径来加载类,一般来说,java应用的类都是通过它来加载的。
自定义类加载器:由java语言实现,继承自ClassLoader。

10、双亲委派机制
双亲委派机制就是当一个类加载器收到一个类加载的请求时,它首先不会尝试自己去加载,而是将这个请求委派给父类加载器去加载,只有父类加载器在自己的搜索范围内查找不到该类时,子类加载器才会尝试自己去加载该类。
为什么使用双亲委派呢:主要是为了防止内存中出现多个相同的字节码, 因为如果没有双亲委派的话,用户就可以自己定义一个java.lang.String类,那么就无法保证类的唯一性。
怎么打破双亲委派呢:这个时候就需要使用自定义类加载器了,继承ClassLoader类,重写loadClass方法和findClass方法。

11、强引用、软引用、弱引用、虚引用的区别
区别主要是是否会被GC掉,强引用的话,不会被GC,垃圾回收器宁愿抛出OOM也不会被回收的,除非我们显示的将强引用的对象设置为Null,这样GC才会进行回收它,;软引用是当内存不足时,GC才会回收它;弱引用是在垃圾回收时,只要发现一个对象只有弱引用的时候,无论当前内存空间是否充足,都会回收掉该对象,虚引用是一种可有可无的引用,无法表示对象的生命周期,任何时候都可能被回收,虚引用主要使用来跟踪对象被垃圾回收的活动。

喜欢的小伙伴可以进我公众号看啊,有时候这里会更新的不及时,公众号不出意外的话,每天都会更新的
在这里插入图片描述

©️2020 CSDN 皮肤主题: 黑客帝国 设计师:上身试试 返回首页