JVM问答

零、JVM概述

1. 如何理解Java语言的跨平台性

Java语言跨平台是因为JVM是跨平台的,.java文件被编译成.class字节码文件后,可以在各个平台的JVM上运行,将其编译成对应平台的机器码。JVM从软件层面屏蔽不同操作系统上再底层硬件与指令的区别,实现“一次编译,到处运行”。

2. 如何理解JVM的语言无关性

各语言文件被各自的编译器编译成符合字节码规范的.class文件,而JVM只关心自己接收的这个字节码文件。所以各语言只要能编译成符合字节码规范的.class文件,就能在JVM上运行,使用JVM的许多功能。

3. 什么是JVM的解释执行

首先字节码文件被加载到JVM中,在执行引擎部分将字节码指令逐行解释执行为本地机器码指令。这个过程会用到程序计数器,记录下一条应该执行哪一条字节码指令。

4. 什么是JIT

解释执行的速度较慢,在JVM中存在一种情况,如果某一条字节码指令是热点代码(调用次数多,JVM会判定其为热点代码),JIT即时解释器会将其直接编译为本地机器指令,不需要再逐行执行,提高执行效率。

一、类加载器

1. 类的加载过程是怎样的?介绍一下类加载机制?

首先,类的加载机制就是将class文件加载到JVM内存中,经过一系列操作形成可以在JVM中可以使用的Java类型。
类的加载过程可以大致分为三个阶段:加载、链接、初始化。
(1). 在加载阶段,通过类的全限定名,获取类的二进制字节流,将其转化为某种静态数据结构,存到运行时数据区的方法区中,转化为方法区的运行时数据结构,并在堆中创建一个代表这个类的class对象,用来封装类在方法区的数据结构,作为访问方法区中这些数据的入口。(先找——>存进来——>创建访问入口)
(2). 在链接阶段,又可以分为三个部分:验证、准备、解析
  • 通过文件格式、元数据、字节码、符号音乐验证,确保Class文件的字节流中包含的信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全。
  • 在准备阶段为静态变量分配内存(方法区)并设置默认的初始值(零值)。
  • 解析阶段会把类中的符号引用替换为直接引用,比如A类中引用了B,class文件包含了B的符号引用,用一个字符串代表B的地址。在运行阶段,A进行了类加载,在解析阶段发现B还未加载,会触发B的类加载,将B加载到JVM中,此时A中B的符号引用会被替换成B的实际地址,即为直接引用,这样A就能真正的调用B。但解析阶段有时会发生在初始化之后,即动态解析。在Java中通过后期绑定的方式实现多态。如果A调用的B是一个具体的实现类,就称为静态解析,因为解析的目标类型很明确。而如果使用了多态,这里的B可能是一个抽象类或接口,B可能有多个具体的实现类,此时B的具体实现并不明确,也就不知道使用哪个具体类的直接引用来进行替换。所以会等到运行的时候发生了调用,JVM栈中会得到具体的类型信息,这时候再进行解析,就能用明确的直接引用替换符号引用。
(3). 在初始化阶段,对类中的静态变量和成员变量初始化为指定的值,执行静态代码块。如果有直接的父类,会先初始化父类。

2. 说明几种类加载器

(1). 启动类加载器Bootstrap ClassLoader:加载JAVA_HOME中 jre/lib/rt.jar 里所有的class或者Xbootclasspath选项指定的jar包
(2). 扩展类加载器ExtClassLoader:加载扩展功能的一些jar包,包括JAVA HOME中jre/lib/*.jar 或-Djava.ext.dirs指定目录下的jar包
(3). 系统/应用程序类加载器AppClassLoader:加载classpath中指定的jar包及Djava.class.path所指定目录下的类和jar包
(4). 自定义类加载器Custom ClassLoader:通过继承java.lang.ClassLoader,自定义类加载器,把自定义的类加载逻辑写在findClass()方法中

3. 介绍一下双亲委派机制

双亲委派机制可以概括为向上检查,向下委派。
如果一个类加载器收到了类加载请求,它并不会自己先去加载。而是把这个请求委托给父类的加载器去执行。
如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将达到顶层的启动类加载器。
如果父类的加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,逐级向下。如果都不能加载,抛出异常ClassNotFoundException。
优点是可以避免类的重复加载,保护程序安全,防止核心API被篡改。

4. 介绍一下沙箱安全机制

JVM系列(四):沙箱安全机制笔记_jvm沙箱-CSDN博客

沙箱是Java安全模型的核心。
沙箱是一个限制程序运行的环境,主要限制系统资源访问。
沙箱机制就是将 Java 代码限定在JVM特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。比如不授信的远程代码。

        多线程共享方法区(堆外内存或元空间)和堆,程序计数器、虚拟机栈、本地方法栈每个线程各一份。 

二、程序计数器: 

5. 使用程序计数器存储字节码指令地址的作用?为什么使用程序计数器记录当前线程的执行地址?

因为CPU需要不停地切换各个线程,而切换回来以后,必须知道接着从哪开始继续执行。JVM的字节码解释器就需要通过改变程序计数器的值来明确下一条应该执行什么字节码指令。

6. 程序计数器为什么设计成线程私有?

所谓的多线程并发,在一个特定时间段只会执行其中一个线程的方法(CPU时间片),CPU会不停地做任务切换,必然导致经常中断和恢复。为了能够准确地记录各个线程下一条要执行的字节码指令的地址,最好的的办法就是为每个线程都分配一个独立的程序计数器,各个线程进行独立计算,不会互相干扰。

三、虚拟机栈 

7. 举例栈溢出StackOverFlowError的情况

出现栈溢出是因为虚拟机栈的大小固定,使用-Xss 设置线程的最大栈空间,当线程请求的栈深度大于虚拟机所允许的深度,将会抛出StackOverFlow异常,比如递归,栈帧太多导致栈溢出。
虚拟机栈的大小还可以动态扩展,但当线程太多,虚拟机栈也很多,导致扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。

8. 调整栈的大小,就能保证不出现溢出吗?

不能。理论上只是让栈溢出发生的晚一点。

9. 分配的栈内存越大越好吗?

不是。
会让栈溢出发生的晚一点,但内存空间是有限的,虚拟机栈挤占空间,线程数变少。

10. 垃圾回收是否涉及虚拟机栈?通过垃圾回收避免栈溢出可行吗?

不涉及,不可行。
虚拟机栈通过出栈的方式,可以理解为是虚拟机栈的“垃圾回收”,不必去显式的垃圾回收。

11.方法中定义的局部变量是否线程安全? 

具体问题具体分析。看生命周期,有没有和外部有交集。
如果这个变量的作用域只在方法内,就是安全的。作用域不止在方法内部,不是内部产生的,传参进来的或返回给上层调用的,生命周期没有结束,就不是线程安全的。

逃逸分析

四、堆

1.  对象一定在堆中创建吗?

几乎所有对象都在堆中创建,但不绝对。
在new对象时,首先会判断这段代码是否是热点代码。如果不是热点代码,该对象在堆中创建。如果是热点代码,则会触发JIT即时编译,在即时编译器内部有一些优化技术,比如可以判断new的这个对象是否会逃逸(逃逸出这个方法或者这个线程,被外部的方法或线程访问)。如果会发生逃逸,则该对象在堆中创建。如果不会发生逃逸,再判断能不能开启标量替换(堆中对象的成员变量能不能在栈中进行替换),如果能替换,对象会在栈上分配,如果不能替换,则该对象在堆中分配。

2. 什么是堆内存?堆内存包含哪些部分?

3. 什么是内存溢出?

4. 什么是内存泄漏?与内存溢出的区别? 

垃圾回收

1. 十种垃圾回收器

1. Serial和Serial Old是JVM最初的垃圾回收器,分别处理堆中的新生代和老年代区域,是单线程独占式的垃圾回收器,垃圾回收的速度也相对较慢,已经不能满足现在的需求。
JVM又诞生了多线程垃圾回收器。
2. jdk 1.8 版本默认Parallel Scavenge和Parallel Old这对组合分别处理新生代和老年代,是Java8吞吐量最高的。
3. 如果想要减少垃圾回收的停顿时间,可以使用CMS垃圾回收器处理老年代,对应处理新生代的组合是ParNew。之前的垃圾回收都是通过暂停线程来进行的,而CMS进来减少业务线程的暂停,从而达到最小的停顿时间。但CMS也有一些问题,比如浮动垃圾、内存碎片。所以JVM又发展出了G1垃圾回收器。
4. G1垃圾回收器不再严格的区分新生代和老年代,而在内部有一个分区模型(比如堆有2G的空间,分1000个大小相等相互独立的区,每个区2兆,在这个2兆的区空间进行垃圾回收),从Java9开始,G1成为JVM默认的垃圾回收器。但G1的问题是无法应对堆空间特别大的情况,此时应该使用ZGC垃圾回收器。
5. ZGC是一种针对大空间的垃圾回收器,同时它的停顿时间非常短,可以在1ms以内,几乎感受不到,可以很广泛的满足业务需求,尤其是对延迟要求高的系统。
6. Shenandoah垃圾回收器与ZGC相似,区别在于它是外部引入的,加入到JVM中的。
7. Eplison一般不用于垃圾回收,主要用于调试、测试JVM功能是否正常。

2. 介绍一下CMS垃圾回收器

3. 介绍一下G1垃圾回收器

4. 介绍一下JVM中的垃圾回收算法

JVM从诞生到现在共有三种垃圾回收算法:复制算法、标记清除算法、标记整理算法。
  1. 复制算法:将可用的内存一分为二,然后先只使用其中一块,每次在垃圾回收时,只需清理这一部分内存,再将存活的对象复制到另一块空的内存中。优点是效率较高,适用于新生代;缺点是空间利用率只有50%
  2. 标记清除算法:根据可达性分析进行标记:可回收、不可回收、未分配三种状态。每次在垃圾回收时,清理被标记为可回收的垃圾。优点是空间利用率比复制算法高;缺点是垃圾回收后,内存区域不连续,存在内存碎片的问题,可能会导致命名内存空间足够,但无法分配给大对象。
  3. 标记整理算法:在标记清除算法的基础上,在垃圾回收后,将存活对象移动到堆的一端,完成内存的整理。优点是空间利用率较好,没有内存碎片;缺点是效率低

5. 什么是可达性分析算法?

进行垃圾回收之前,首先要判断这个对象是否存活、是不是垃圾。JVM默认使用可达性分析算法来进行判断。
可达性分析算法会创建一些根,根的集合称为GC Roots,主要包括局部变量、静态变量、常量、JNI指针。在进行垃圾回收时,JVM会先找到这四种类型的对象,进行可达性的链路分析。如果堆中的对象存在和这四种对象的链路,则判断为可达,不是垃圾,不进行回收;不可达的将视为垃圾被回收。
可达性分析算法显著优于引用计数算法的地方在于:可以解决对象间循环引用(成环)的问题。

6. 什么是垃圾回收中的STW?

STW全称为Stop The Word,指的是垃圾回收过程中的暂停。
在业务线程运行时,触发了垃圾回收,此时需要暂停业务线程,启动垃圾回收相关的线程,完成垃圾回收、空间清理操作。当垃圾回收执行完毕,重新开始业务线程。业务线程暂停的这段时间就是STW,会造成业务的卡顿,需尽量减少这段停顿时间。(ZGC垃圾回收器的主要目的就是减少STW)

7. 介绍一下JVM的分代模型 

JVM有三种垃圾回收算法,各有所长各有所短。JVM为了保证垃圾回收的效率,不会单一使用某一个垃圾回收算法,而是使用分代模型,将堆空间分为新生代(Eden、From、To)和老年代。
绝大部分对象是“朝生夕死”,复制算法的效率最高,所以在新生代使用复制算法。如果某个对象,在经历了15次垃圾回收仍然存活,说明其下一次被回收的几率很小,不适合再使用复制算法来回复制了,需要再划分出一块区域称为老年代,用来存放多次垃圾回收仍然没有被回收的对象,使用标记清除算法或标记整理算法。
分代模型可以在不同的代里面采样不同的垃圾回收算法,确保整体垃圾回收的高效。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值