舌战面试官-JVM篇

一.讲讲JVM的主要组成部分及其作用

JVM内存区域分为运行时数据区,类加载子系统,执行引擎,本地库接口和本方方法库;
运行时数据区:虚拟机栈,本地方法栈,堆,方法区程序计数器
类加载子系统:类加载器
执行引擎:即时编译器,垃圾回收器

作用:首先编译器将.java编译成.class字节码 ,类加载子系统通过全限定类名将字节码加载进运行时数据区的方法区。字节码文件通过执行引擎转换成底层系统指令,再交给cpu去执行,而过程中需要用到其他语言,就得通过本地库接口调用本地方法库

二.讲一下JVM的运行时数据区

堆:存放对象实例,被所有线程共享
虚拟机栈:存放局部变量表,操作数栈,动态链接,方法出口
本地方法栈:和虚拟机栈一样,不过本地方法栈是为虚拟机调用native方法服务的
方法区:存放被虚拟机加载后的类信息,常量,静态变量和即时编译的数据
程序计数器:当前线程所执行字节码的行号指示器,即获取下一条执行指令的行号

三.堆是存放对象实例并且线程共享的,那怎么保证堆分配内存时的线程安全?

两种方案:
一:使用CAS+失败重试来保证分配操作的原子性。
CAS就是每个线程有自己的工作空间,而待操作的数据存放在公共空间,每个线程的更新数据操作都是在自己的工作空间内,然后再同步到公共空间。在开始更新操作时,线程会带上一个和公共空间数据一致的期望值,然后在线程工作空间更新数据,再同步前把当前公共空间的数据和自己的期望值进行比较,如果一致就说明没有被其他线程修改,就可以正确的把工作空间的数据更新到公共空间。反之则失败,不更新。

二:使用TLAB(Thread Local Allocation Buffer, TLAB)
TLAB:即每个线程在堆内存中预先分配好一部分内存,这样哪个线程需要分配对象,就分配在自己线程的TLAB上。TLAB用完时,再通过同步锁重新分配TLAB

四.GC是什么。GC的原理,GC的几种算法

GC就是垃圾回收,当内存中的对象已经死亡(不被引用)或者长时间不被使用就会进行回收

GC的原理就是GC通过有向图来管理堆中的所有对象,通过这种方式可以判断哪些对象是可达的,哪些是不可达。当GC确定对象不可达时,就可以回收这些对象(可达性分析:从GC Root到对象之间不通时,判定对象不可达)

可以作为“gc roots”的对象
(1)虚拟机栈(栈针中的局部变量表)中引用的对象
(2)本地方法栈中JNI引用的对象。
(3)方法区中常量引用的对象
(4)方法区中类静态属性引用的对象。

还有一种引用计数法来判断对象是否可以被回收
所谓的引用计数法就是给每个对象一个引用计数器,每当有一个地方引用它时,计数器就会加1;当引用失效时,计数器的值就会减1;任何时刻计数器的值为0的对象就是不可能再被使用的。

   这个引用计数法时没有被Java所使用的,但是python有使用到它。而且最原始的引用计数法没有用到GC Roots。

GC算法有:
标记-清除算法:标记活着的对象,将无用的对象进行清除回收,但是会产生碎片

复制算法:将内存划分成等量的两块,只使用一块,回收时将活着的对象存放到另一块,回收使用过的那块,就不会产生碎片。可惜浪费了半块内存

标记整理算法:标记无用的对象,将活着的对象移到一边,在清除被标记的对象

分代算法:对象分成新生代老年代。新生代使用复制算法,老年代使用标记整理算法

五.分代算法是怎么工作的

分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。

新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:
对象优先分配在Eden区
把 Eden + From Survivor 存活的对象放入 To Survivor 区;
清空 Eden 和 From Survivor 分区(minor GC);
From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor。
每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代

老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法(full GC)。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。

六.什么是类加载器,类加载器的类别和执行流程

类加载器放在类加载子系统中,负责将硬盘中的.class文件加载进内存。一般运行代码时new一个对象就会加载一次,然后使用Class.forName(全限定类名)也会加载一次

四种类加载器,由上至下

1.启动类加载器 bootstrap classLoader 加载java核心类库

2.扩展类加载器 extensions classLoader 加载java扩展库

3.系统加载器 system classLoader 通过全限定类名加载类(我们写的类一般由他加载)

4.自定义加载器

执行流程:
加载:根据查找路径找到相应的 class 文件然后导入;
验证:检查加载的 class 文件的正确性;
准备:给类中的静态变量分配内存空间;
解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址;
初始化:对静态变量和静态代码块执行初始化工作。

七、双亲委派机制

类加载器收到类准备加载时,会先委托父类加载器去加载,如果父类加载不了,再给自己加载

八、JVM调优

使用JDK自带的Jconsole来监控JVM的情况
调优参数
-Xms2g:初始化推大小为 2g;
-Xmx2g:堆最大内存为 2g;
-XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;
-XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;
–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;
-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合;
-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;
-XX:+PrintGC:开启打印 gc 信息;
-XX:+PrintGCDetails:打印 gc 详细信息。

九、说一下 JVM 有哪些垃圾回收器?

如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。下图展示了7种作用于不同分代的收集器,其中用于回收新生代的收集器包括Serial、PraNew、Parallel Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,还有用于回收整个Java堆的G1收集器。不同收集器之间的连线表示它们可以搭配使用。

Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效;
ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;
Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;
Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本;
Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;
CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。
G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。

10.两个相互引用的对象能不能回收?

不能,因为不管是用可达性分析还是引用计数器,都没办法得到对象不可达或者引用计数器为0。

参考博客: Java虚拟机(JVM)面试题(2020最新版).

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值