虚拟机面试问题

[TOC]

STW

在垃圾回收过程中经常涉及到对对象的挪动(比如上文提到的对象在Survivor 0和Survivor 1之间的复制),进而导致需要对对象引用进行更新。为了保证引用更新的正确性,Java将暂停所有其他的线程,这种情况被称为“Stop-The-World”,导致系统全局停顿

JDBC和双亲委派模型关系

JVM 中一次完整的 GC 流程是什么样子的,对象如何晋升到老年代,说说你知道的几种主要的 JVM 参数

一次完整的gc过程

gc是通过垃圾收集器来实现的,现代垃圾收集器大部分都是基于分代收集理论设计的,也就是将对象划分为新生代,老年代。其中新生代分为Eden区和两块Survivor区,比例为8:1:1。
每次分配内存都只会使用Eden区和一块Survivor区,对象默认是放在Eden区,但是如果对象太大了,Eden区放不下,那么就会放入到老年代中,当发生gc时,就会把存活对象放到另一块Survivor上,如果这块survivor区不够,那么依赖老年代进行担保,假设这块为s0区,另一块是s1区。下一次Eden区满的时候,进行一次MinorGc,会将Eden区和S0区存活的对象放到s1中,如果s1,放不下就会放到老年代中。然后循环往复,始终保证s0和s1中又一个区域是空的,一个对象默认最多经历15次gc,一旦超过15次gc就会放入到老年代中。但是如果在Survivor空间中,相同年龄的对象超过Survivor空间的一半,并不会等到对象的年龄到达15才进入老年代,这些相同年龄的对象会直接进入到老年代中

对象晋升到老年代一共有四种情况
  1. 对象太大,Eden放不下
  2. 存放存活对象的Survivor区太小,不足以存下存活对象
  3. 经历超过默认15次gc或者设定的
  4. Survivor空间中相同年龄的所有对象综合大于等于Survivor空间的一半,那么这些对象就会直接进入到老年代中

cms怎么解决内存碎片的问题(full gc)

  • 增大Xmx或者减少Xmn
  • 在应用访问量最低的时候,在程序中主动调用System.gc(),比如每天凌晨。
  • 在应用启动并完成所有初始化工作后,主动调用System.gc(),它可以将初始化的数据压缩到一个单独的chunk中,以腾出更多的连续内存空间给新生代晋升使用。
  • CMS收集器提供了一个-XX:+UseCMS-CompactAtFullCollection开关参数(默认是开启的,此参数从
    JDK 9开始废弃),用于在CMS收集器不得不进行Full GC时开启内存碎片的合并整理过程,由于这个
    内存整理必须移动存活对象,(在Shenandoah和ZGC出现前)是无法并发的。这样空间碎片问题是解
    决了,但停顿时间又会变长,因此虚拟机设计者们还提供了另外一个参数-XX:CMSFullGCsBefore-
    Compaction(此参数从JDK 9开始废弃),这个参数的作用是要求CMS收集器在执行过若干次(数量
    由参数值决定)不整理空间的Full GC之后,下一次进入Full GC前会先进行碎片整理(默认值为0,表
    示每次进入Full GC时都进行碎片整理)。

new一个object对象,然后再赋值给一个静态变量,然后问这个过程在JVM内存是什么个过程

img

(回答会把创建的实例对象放到堆内存区域,然后再把指向对象的内存地址赋值给符号引用,让这个符号引用指向对应的堆内存区域)?

如果我要设置一个内存缓冲区,让垃圾收集器不对其进行操作怎么办

代码实现每五分钟一次Minor GC,如果要FullGC呢?

类加载,class.forname 和 classloader的区别,双亲委派原则。被问到不使用双亲委派原则,如何自定义java.lang.Integer会怎么样(我自定义过 java.lang.Integer,使用直接抛出异常,说包违法)。可能还是不够深入。

方法区会不会 OOM

Class.forName 是否会初始化类

Class.forName除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。 b).而classloader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。

(会进行初始化)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package classLoader;


class A{
public A(){
System.out.println("创建类的时候运行这段代码");
}
static{
System.out.println("这里是类静态代码块,初始化类的时候就会运行一次");
}
}

public class ClassLoaderT {
public static void main(String[] args) throws ClassNotFoundException {
//首先是获得类加载器
ClassLoader cl=ClassLoader.getSystemClassLoader();
cl.loadClass("classLoader.A");//这里根据全类名来查找这个类,所以如果只是输入"A"就会报错。
System.out.println("-----------------------------");
Class.forName("classLoader.A");这里根据全类名来查找这个类,所以如果只是输入"A"就会报错。
}
}

控制台:

img

Full GC 发生的条件、怎么设置永久代和堆的大小、怎么减少 Full GC 、JVM 调优

-XX:PermSize 和 -XX:MaxPermSize:指定JVM中的永久代(方法区)的大小。

-Xms 和 -Xmx (-XX:InitialHeapSize 和 -XX:MaxHeapSize):指定JVM初始占用的堆内存和最大堆内存。

img

为什么用元空间替代了永久代,元空间里面的对象是会被回收的吗?

img

原因一:因为直接内存,JVM将会在IO操作上具有更高的性能,因为它直接作用于本地系统的IO操作。而非直接内存,也就是堆内存中的数据,如果要作IO操作,会先复制到直接内存,再利用本地IO处理。

  • 从数据流的角度,非直接内存是下面这样的作用链:本地IO –> 直接内存 –> 非直接内存 –> 直接内存 –> 本地IO
  • 而直接内存是:本地IO –> 直接内存 –> 本地IO

原因二:整个永久代有一个 JVM 本身设置固定大小上线,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,并且永远不会得到java.lang.OutOfMemoryError。

  • 可以使用 -XX:MaxMetaspaceSize 标志设置最大元空间大小,默认值为 unlimited,这意味着它只受系统内存的限制。
  • -XX:MetaspaceSize 调整标志定义元空间的初始大小如果未指定此标志,则 Metaspace 将根据运行时的应用程序需求动态地重新调整大小。

原因三:

可以在GC不进行暂停的情况下并发地释放类数据。

gc引用计数法的缺点,除了循环引用,说一到两个

想了很多,跨代引用都想了,最后说不出来,提示我可以从【设计gc算法需要考虑什么因素】上来回答,想不出来

他说了可以考虑stw、访问局部性、堆大小(这个没听清楚),然后问我局部访问性知道吗,我以为是jvm里的新知识就说了不知道,然后他解释了一下,我才知道噢原来就是os的局部性原理。。。

然后他问你觉得引用计数***影响到哪几点,面试官看我没反应就说ok,然后跟我解释了,主要是一个链式更新、堆的使用效率的问题,引用计数法优点在于他是实时删除的。

其实到这里我心态已经崩了,只想退出面试间 😔

1
引用技术除了循环依赖问题(可以通过弱引用解决),最大的问题就是开销大,因为他要计算实时计算局部信息,导致整个系统的吞吐量降低,优点是几乎是实时的,这也从侧面说明了,吞吐量和延迟是不可兼得的,但是局部性原理和堆大小不知道跟这个有啥关系。可能是需要实时计算引用关系,导致cache平繁被刷新从而导致程序访问的时候命中率底?

追问不同的类加载器加载同一个类是隔离还是共享

答了隔离,他大概觉得我说的没有底气,就跟我解释了为什么是隔离的(除bug)。

类的全称是相同的,类加载器相同.这里的初始化可以理解为,类加载的最后一步(调用<clinit>),如果是两个不同的ClassLoad加载的话那么<clinit>会被执行两次,加载的Class对象在方法区(永久代),并且会对应两个Class对象.

OOM的区域

数据区 线程私有 作用 异常
程序计数器 记录当前线程执行到的字节码行号 无任何异常
虚拟机栈 存放栈帧(方法执行时的基础数据结构,存储局部变量表等信息)以及入栈出栈 StackoverflowError与OutOfMemoryError
本地方法栈 与虚拟机栈类似,用于Native方法执行 StackoverflowError与OutOfMemoryError
存放对象实例 OutOfMemoryError
方法区 存储已被虚拟机加载的类信息、常量、静态变量和JIT编译后的代码等数据 OutOfMemoryError

为什么是15次晋升老年代

HotSpot会在对象头中的标记字段里记录年龄,分配到的空间只有4位,最多只能记录到15