2020 面试阿里字节跳动 90% 被问到的 JVM 面试题附答案
java 堆 = 新生代+老年代;新生代 = Eden + Suivivor(S0 + S1),默认分配比例是 8:1:1;当 Eden 区空间满了的时候,就会触发一次 Minor GC,以收集新生代的垃圾,存活下来的对象会被分配到 Survivor 区大对象(需要大量连续内存空间的对象)会直接被分配到老年代如果对象在 Eden 中出生,并且在经历过一次 Minor GC 之后仍然存活,被分配到存活区的话,年龄+1,此后每经历过一次 Minor GC 并且存活下来,年龄就+1,当年龄达到 15 的时候,会被晋升到老年代;当老年代满了,而无法容纳更多对象的话,会触发一次 full gc;full gc 存储的是整个内存堆(包括年轻代和老年代);;Major GC 是发生在老年代的 GC,清理老年区,经常会伴随至少一次 minor gc;
四、Java 中的垃圾回收算法?
java 中有四种垃圾回收算法,分别是标记清除法、标记整理法、复制算法、分代收集算法;
标记清除法:第一步:利用可达性去遍历内存,把存活对象和垃圾对象进行标记;第二步:在遍历一遍,将所有标记的对象回收掉;特点:效率不行,标记和清除的效率都不高;标记和清除后会产生大量的不连续的空间分片,可能会导致之后程序运行的时候需分配大对象而找不到连续分片而不得不触发一次 GC;
标记整理法:第一步:利用可达性去遍历内存,把存活对象和垃圾对象进行标记;第二步:将所有的存活的对象向一段移动,将端边界以外的对象都回收掉;特点:适用于存活对象多,垃圾少的情况;需要整理的过程,无空间碎片产生;
复制算法:将内存按照容量大小分为大小相等的两块,每次只使用一块,当一块使用完了,就将还存活的对象移到另一块上,然后在把使用过的内存空间移除;特点:不会产生空间碎片;内存使用率极低;
分代收集算法:根据内存对象的存活周期不同,将内存划分成几块,java 虚拟机一般将内存分成新生代和老生代,在新生代中,有大量对象死去和少量对象存活,所以采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集;老年代中因为对象的存活率极高,没有额外的空间对他进行分配担保,所以采用标记清理或者标记整理算法进行回收;
五、如何判断一个对象是否存活?
判断一个对象是否存活,分为两种算法 1:引用计数法;2:可达性分析算法;
引用计数法:给每一个对象设置一个引用计数器,当有一个地方引用该对象的时候,引用计数器就+1,引用失效时,引用计数器就-1;当引用计数器为 0 的时候,就说明这个对象没有被引用,也就是垃圾对象,等待回收;缺点:无法解决循环引用的问题,当 A 引用 B,B 也引用 A 的时候,此时 AB 对象的引用都不为 0,此时也就无法垃圾回收,所以一般主流虚拟机都不采用这个方法;
可达性分析法从一个被称为 GC Roots 的对象向下搜索,如果一个对象到 GC Roots 没有任何引用链相连接时,说明此对象不可用,在 java 中可以作为 GC Roots 的对象有以下几种:
虚拟机栈中引用的对象方法区类静态属性引用的变量方法区常量池引用的对象本地方法栈 JNI 引用的对象但一个对象满足上述条件的时候,不会马上被回收,还需要进行两次标记;第一次标记:判断当前对象是否有 finalize()方法并且该方法没有被执行过,若不存在则标记为垃圾对象,等待回收;若有的话,则进行第二次标记;第二次标记将当前对象放入 F-Queue 队列,并生成一个 finalize 线程去执行该方法,虚拟机不保证该方法一定会被执行,这是因为如果线程执行缓慢或进入了死锁,会导致回收系统的崩溃;如果执行了 finalize 方法之后仍然没有与 GC Roots 有直接或者间接的引用,则该对象会被回收;
六、有哪几种垃圾回收器,有哪些优缺点?cms 和 g1 的区别?
垃圾回收器主要分为以下几种:Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1;
Serial:单线程的收集器,收集垃圾时,必须 stop the world,使用复制算法。
ParNew:Serial 收集器的多线程版本,也需要 stop the world,复制算法.
ParallelScavenge:新生代收集器,复制算法的收集器,并发的多线程收集器,目标是达到一个可控的吞吐量,和 ParNew 的最大区别是 GC 自动调节策略;虚拟机会根据系统的运行状态收集性能监控信息,动态设置这些参数,以提供最优停顿时间和最高的吞吐量;
Serial Old:Serial 收集器的老年代版本,单线程收集器,使用标记整理算法。
Parallel Old:是 Parallel Scavenge 收集器的老年代版本,使用多线程,标记-整理算法。
CMS:是一种以获得最短回收停顿时间为目标的收集器,标记清除算法,运作过程:初始标记,并发标记,重新标记,并发清除,收集结束会产生大量空间碎片;
G1:标记整理算法实现,运作流程主要包括以下:初始标记,并发标记,最终标记,筛选回收。不会产生空间碎片,可以精确地控制停顿;G1 将整个堆分为大小相等的多个 Region(区域),G1 跟踪每个区域的垃圾大小,在后台维护一个优先级列表,每次根据允许的收集时间,优先回收价值最大的区域,已达到在有限时间内获取尽可能高的回收效率;
七、什么是类加载?
虚拟机把描述类的数据加载到内存里面,并对数据进行校验、解析和初始化,最终变成可以被虚拟机直接使用的 class 对象;
八、类加载的过程?
主要分为以下几个过程:加载、验证、准备、解析、初始化;加载:加载分为三步:
1、通过类的全限定性类名获取该类的二进制流;
2、将该二进制流的静态存储结构转为方法区的运行时数据结构;
3、在堆中为该类生成一个 class 对象;
验证:验证该 class 文件中的字节流信息复合虚拟机的要求,不会威胁到 jvm 的安全;
准备:为 class 对象的静态变量分配内存,初始化其初始值;
解析:该阶段主要完成符号引用转化成直接引用;
初始化:到了初始化阶段,才开始执行类中定义的 java 代码;初始化阶段是调用类构造器的过程;
九、什么是类加载器,常见的类加载器有哪些?
类加载器是指:通过一个类的全限定性类名获取该类的二进制字节流叫做类加载器;类加载器分为以下四种:启动类加载器:用来加载 java 核心类库,无法被 java 程序直接引用;
扩展类加载器:用来加载 java 的扩展库,java 的虚拟机实现会提供一个扩展库目录,该类加载器在扩展库目录里面查找并加载 java 类;
系统类加载器:它根据 java 的类路径来加载类,一般来说,java 应用的类都是通过它来加载的;
自定义类加载器:由 java 语言实现,继承自 ClassLoader;
十、什么是双亲委派模型?
当一个类加载器收到一个类加载的请求,他首先不会尝试自己去加载,而是将这个请求委派给父类加载器去加载,只有父类加载器在自己的搜索范围类查找不到给类时,子加载器才会尝试自己去加载该类;
十一、为什么需要双亲委派模型?
为了防止内存中出现多个相同的字节码;因为如果没有双亲委派的话,用户就可以自己定义一个 java.lang.String 类,那么就无法保证类的唯一性;
十二、怎么打破双亲委派模型?
自定义类加载器,继承 ClassLoader 类,重写 loadClass 方法和 findClass 方法;
十三、强引用、软应用、弱引用、虚引用的区别?
强引用:强引用是我们使用最广泛的引用,如果一个对象具有强引用,那么垃圾回收期绝对不会回收它,当内存空间不足时,垃圾回收器宁愿抛出 OutOfMemoryError,也不会回收具有强引用的对象;我们可以通过显示的将强引用对象置为 null,让 gc 认为该对象不存在引用,从而来回收它;
软引用:软应用是用来描述一些有用但不是必须的对象,在 java 中用 SoftReference 来表示,当一个对象只有软应
用时,只有当内存不足时,才会回收它;软引用可以和引用队列联合使用,如果软引用所引用的对象被垃圾回收器所回收了,虚拟机会把这个软引用加入到与之对应的引用队列中;
弱引用:弱引用是用来描述一些可有可无的对象,在 java 中用 WeakReference 来表示,在垃圾回收时,一旦发现一个对象只具有软引用的时候,无论当前内存空间是否充足,都会回收掉该对象;弱引用可以和引用队列联合使用,如果弱引用所引用的对象被垃圾回收了,虚拟机会将该对象的引用加入到与之关联的引用队列中;
虚引用:虚引用就是一种可有可无的引用,无法用来表示对象的生命周期,任何时候都可能被回收,虚引用主要使用来跟踪对象被垃圾回收的活动,虚引用和软引用与弱引用的区别在于:虚引用必须和引用队列联合使用;在进行垃圾回收的时候,如果发现一个对象只有虚引用,那么就会将这个对象的引用加入到与之关联的引用队列中,程序可以通过发现一个引用队列中是否已经加入了虚引用,来了解被引用的对象是否需要被进行垃圾回收;
评论