JVM面试系列-05

2022年7月17日
大约 7 分钟

JVM面试系列-05

1. Java 虚拟机规范中,对Java 虚拟机栈规定几种异常情况?

• 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。

• 如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。

这两种情况存在着一些互相重叠的地方:当栈空间无法继续分配时,到底是内存太小,还是已使用的栈空间太大,其本质上只是对同一件事情的两种描述而已。在单线程的操作中,无论是由于栈帧太大,还是虚拟机栈空间太小,当栈空间无法分配时,虚拟机抛出的都是StackOverflowError异常,而不会得到OutOfMemoryError异常。而在多线程环境下,则会抛出OutOfMemoryError异常。

2. 启动一个线程是用run() 还是 start()?

启动线程肯定要用start()方法。

当用start()开始一个线程后,线程就进入就绪状态,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行。这并不意味着线程就会立即运行。

当cpu分配给它时间时,才开始执行run()方法(如果有的话)。

start()是方法,它调用run()方法;而run()方法是你必须重写的。

run()方法中包含的是线程的主体。

3. 线程的基本概念,线程的基本状态以及状态之间的关系?

线程的基本概念:线程指在程序执行过程中,能够执行程序代码的一个执行单位,每个程序至少都有一个线程,也就是程序本身。

Java中的线程有四种状态分别是:运行、就绪、挂起、结束。

4. 同步和异步有何异同,在什么情况下分别使用它们?举例说明

同步交互:指发送一个请求,需要等待返回,然后才能够发送下一个请求,有个等待过程;

异步交互:指发送一个请求,不需要等待返回,随时可以再发送下一个请求,即不需要等待。

相同的地方:都属于交互方式,都是发送请求。

不同的地方:一个需要等待,一个不需要等待。

简单而言,同步就是必须一件一件的做事,等前一件事做完后才能做下一件事。而异步这是把事情指派给别人后,接着继续做下一件事,不必等别人返回的结果。

5. JVM 中常见的调优工具有哪些?

常用调优工具分为两类,jdk自带监控工具:jconsole和jvisualvm,第三方有:MAT(Memory AnalyzerTool)、GChisto。

1)jconsole,Java Monitoring and Management Console是从java5开始,在JDK中自带的java监控和管理控制台,用于对JVM中内存, 线程和类等的监控。

2)jvisualvm,jdk自带全能工具,可以分析内存快照、线程快照;监控内存变化、GC变化等。

3)MAT,Memory Analyzer Tool,一个基于Eclipse的内存分析工具,是一个快速、功能丰富的Javaheap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗。

4)GChisto,一款专业分析gc日志的工具。

6. 什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?

Java虚拟机是一个可以执行Java字节码的虚拟机进程。

Java源文件被编译成能被Java虚拟机执行的字节码文件。

Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。

Java虚拟机让这个变为可能,因为它知道底层硬件平台的 指令长度和其他特性。

7. JVM 内存或者 CPU 飙高如何快速定位?

1、jps可以找到JAVA的进程号;

2、top -p <pid> 查看JAVA进程内存情况,按H查看每个线程的内存情况;

3、找到内存和 cpu 占用最高的线程id,比如19664;

4、转为十六进制得到Ox4cd0,此为线程id的十六进制表示;

5、执行 jstack 19663|greg -A 10 4cd0,得到线线程堆栈信息中4cd0这个线程所在行的后面10行,从堆栈中可以发现导致cpu飙高的调用方法。

8. 描述一下 Java 中类加载的步骤?

Java中类加载的步骤:加载、连接、初始化;其中连接包括验证、准备、解析,每个阶段做的事情分别如下:

加载:将编译后的.class文件加载到JVM,并创建一个Class对象。

验证:验证.class文件格式是否规范、也有安全层面的验证、验证类的元信息,字节码,符号引用。

准备:为类的静态变量分配内存,赋默认值。

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

初始化:为静态变量赋初始值。

9. Java 中 SafePoint 是什么?

Java程序中有很多线程,每个java线程又有自己的stack,并且共享heap。这些线程一直运行,不断对stack和heap进行操作。假设此时JVM需要对stack和heap做一些操作该怎么办?

比如JVM要进行GC或者要做heap dump等等,这时候如果线程都在对stack或者heap进行修改,那么将不是一个稳定的状态。GC直接在这种情况下操作stack或者heap,会导致线程的异常。

那么应该如何处理?

SafePoint是一个安全点,所有的线程执行到安全点的时候就会去检查是否需要执行SafePoint操作,如果需要执行,那么所有的线程都将会处于等待状态,直到所有的线程进入SafePoint。然后JVM执行相应的操作之后,所有的线程再恢复执行。

10. Java 中线程什么时候会进入 SafePoint?

一般来说,如果线程在竞争锁被阻塞,IO被阻塞,或者在等待获得监视器锁状态时,线程就处于SafePoint状态。

如果线程再执行JNI代码的那一个时刻,java线程也处于SafePoint状态。因为java线程在执行本地代码之前,需要保存堆栈的状态,然后再移交给native方法。

如果java的字节码正在执行,那么不能判断该线程是不是在SafePoint上。

11. Java 中 SafePoint 主要特定位置?

Java中SafePoint主要特定位置:

  • 循环的末尾 (防止大循环的时候一直不进入safepoint,而其他线程在等待它进入safepoint);

  • 方法返回前;

  • 调用方法的call之后;

  • 抛出异常的位置。

之所以选择这些位置作为safepoint的插入点,主要的考虑是“避免程序长时间运行而不进入safepoint”,比如GC的时候必须要等到Java线程都进入到safepoint的时候VMThread才能开始执行GC,如果程序长时间运行而没有进入safepoint,那么GC也无法开始,JVM可能进入到Freezen假死状态。

在stackoverflow上有人提到过一个问题,由于BigInteger的pow执行时JVM没有插入safepoint,导致大量运算时线程一直无法进入safepoint,而GC线程也在等待这个Java线程进入safepoint才能开始GC,结果JVM就Freezen了。

safepoint只能处理正在运行的线程,它们可以主动运行到safepoint。而一些Sleep或者被blocked的线程不能主动运行到safepoint。这些线程也需要在GC的时候被标记检查,JVM引入了safe region的概念。safe region是指一块区域,这块区域中的引用都不会被修改,比如线程被阻塞了,那么它的线程堆栈中的引用是不会被修改的,JVM可以安全地进行标记。线程进入到safe region的时候先标识自己进入了safe region,等它被唤醒准备离开safe region的时候,先检查能否离开,如果GC已经完成,那么可以离开,否则就在safe region呆在。这可以理解,因为如果GC还没完成,那么这些在safe region中的线程也是被stop the world所影响的线程的一部分,如果让它们正常执行,可能会影响标记的结果。

可以设置JVM参数-XX:+PrintSafepointStatistics –XX:PrintSafepointStatisticsCount=1 来输出safepoint的统计信息。