2023Java高频必背面试题Java 虚拟机篇合集

1、简述JVM内存模型。

线程私有的运⾏时数据区: 程序计数器、Java 虚拟机栈、本地⽅法栈
线程共享的运⾏时数据区:Java 堆、⽅法区

2、简述程序计数器。

程序计数器表示当前线程所执⾏的字节码的⾏号指示器
程序计数器不会产⽣StackOverflowError和OutOfMemoryError

3、简述虚拟机栈。

Java 虚拟机栈⽤来描述 Java ⽅法执⾏的内存模型。线程创建时就会分配⼀个栈空间,线程结束后栈空间被回收。
栈中元素⽤于⽀持虚拟机进⾏⽅法调⽤,每个⽅法在执⾏时都会创建⼀个栈帧存储⽅法的局部变量表、操作栈、动态链接和返回地址等信息。
虚拟机栈会产⽣两类异常:
(1)StackOverflowError:线程请求的栈深度⼤于虚拟机允许的深度抛出。
(2)OutOfMemoryError:如果 JVM 栈容量可以动态扩展,虚拟机栈占⽤内存超出抛出。

4、简述本地⽅法栈。

本地⽅法栈与虚拟机栈作⽤相似,不同的是虚拟机栈为虚拟机执⾏ Java ⽅法服务,本地⽅法栈为本地⽅法服务。可以将虚拟机栈看作普通的java函数对应的内存模型,本地⽅法栈看作由native关键词修饰的函数对应的内存模型。
本地⽅法栈会产⽣两类异常:
(1)StackOverflowError:线程请求的栈深度⼤于虚拟机允许的深度抛出。
(2)OutOfMemoryError:如果 JVM 栈容量可以动态扩展,虚拟机栈占⽤内存超出抛出。

5、简述JVM中的堆。

堆主要作⽤是存放对象实例,Java ⾥⼏乎所有对象实例都在堆上分配内存,堆也是内存管理中最⼤的⼀块。Java的垃圾回收主要就是针对堆这⼀区域进⾏。 可通过 -Xms 和 -Xmx 设置堆的最⼩和最⼤容量。
堆会抛出 OutOfMemoryError异常。

6、简述⽅法区。

⽅法区⽤于存储被虚拟机加载的类信息、常量、静态变量等数据
JDK6之前使⽤永久代实现⽅法区,容易内存溢出。JDK7 把放在永久代的字符串常量池、静态变量等移出,JDK8 中抛弃永久代,改⽤在本地内存中实现的元空间来实现⽅法区,把 JDK 7 中永久代内容移到元空间。
⽅法区会抛出 OutOfMemoryError异常。

7、简述运⾏时常量池。

运⾏时常量池存放常量池表,⽤于存放编译器⽣成的各种字⾯量与符号引⽤。⼀般除了保存 Class ⽂件中描述的符号引⽤外,还会把符号引⽤翻译的直接引⽤也存储在运⾏时常量池。除此之外,也会存放字符串基本类型。
JDK8之前,放在⽅法区,⼤⼩受限于⽅法区。JDK8将运⾏时常量池存放堆中。

8、简述直接内存。

直接内存也称为堆外内存,就是把内存对象分配在JVM堆外的内存区域。这部分内存不是虚拟机管理,⽽是由操作系统来管理。 Java通过DriectByteBuffer对其进⾏操作,避免了在 Java 堆和 Native堆来回复制数据。

9、简述Java创建对象的过程。

(1)检查该指令的参数能否在常量池中定位到⼀个类的符号引⽤,并检查引⽤代表的类是否已被加载、解析和初始化,如果没有就先执⾏类加载。
(2)通过检查通过后虚拟机将为新⽣对象分配内存。
(3)完成内存分配后虚拟机将成员变量设为零值
(4)设置对象头,包括哈希码、GC 信息、锁信息、对象所属类的类元信息等。
(5)执⾏ init ⽅法,初始化成员变量,执⾏实例化代码块,调⽤类的构造⽅法,并把堆内对象的⾸地址赋值给引⽤变量。

10、简述JVM给对象分配内存的策略。

(1)指针碰撞:这种⽅式在内存中放⼀个指针作为分界指示器将使⽤过的内存放在⼀边,空闲的放在另⼀边,通过指针挪动完成分配。
(2)空闲列表:对于 Java 堆内存不规整的情况,虚拟机必须维护⼀个列表记录哪些内存可⽤,在分配时从列表中找到⼀块⾜够⼤的空间划分给对象并更新列表记录。

11、Java对象内存分配是如何保证线程安全的。

(1)第⼀种⽅法,采⽤CAS机制,配合失败重试的⽅式保证更新操作的原⼦性。该⽅式效率低。
(2)第⼆种⽅法,每个线程在Java堆中预先分配⼀⼩块内存,然后再给对象分配内存的时候,直接在⾃⼰这块"私有"内存中分配。⼀般采⽤这种策略。

12、简述对象的内存布局。

对象在堆内存的存储布局可分为对象头、实例数据和对⻬填充
(1)对象头主要包含两部分数据: MarkWord、类型指针
MarkWord :⽤于存储哈希码(HashCode)、GC分代年龄、锁状态标志位、线程持有的锁、偏向线程ID等信息。
类型指针:对象指向他的类元数据指针,如果对象是⼀个 Java 数组,会有⼀块⽤于记录数组⻓度的数据。
(2)实例数据存储代码中所定义的各种类型的字段信息。
(3)对⻬填充起占位作⽤。HotSpot 虚拟机要求对象的起始地址必须是8的整数倍,因此需要对⻬填充。

13、如何判断对象是否是垃圾。

(1)引⽤计数法
设置引⽤计数器,对象被引⽤计数器加 1,引⽤失效时计数器减 1,如果计数器为 0 则被标记为垃圾。会存在对象间循环引⽤的问题,⼀般不使⽤这种⽅法。
(2)可达性分析
通过 GC Roots 的根对象作为起始节点,从这些节点开始,根据引⽤关系向下搜索,如果某个对象没有被搜到,则会被标记为垃圾。可作为 GC Roots 的对象包括虚拟机栈和本地⽅法栈中引⽤的对象、类静态属性引⽤的对象、常量引⽤的对象。

14、简述java的引⽤类型。

(1)强引⽤: 被强引⽤关联的对象不会被回收。⼀般采⽤ new ⽅法创建强引⽤。
(2)软引⽤:被软引⽤关联的对象只有在内存不够的情况下才会被回收。⼀般采⽤ SoftReference 类来创建软引⽤。
(3)弱引⽤:垃圾收集器碰到即回收,也就是说它只能存活到下⼀次垃圾回收发⽣之前。⼀般采⽤WeakReference 类来创建弱引⽤。
(4)虚引⽤: ⽆法通过该引⽤获取对象。唯⼀⽬的就是为了能在对象被回收时收到⼀个系统通知。虚引⽤必须与引⽤队列联合使⽤。

15、简述标记清除算法、标记整理算法和标记复制算法。

(1)标记清除算法:先标记需清除的对象,之后统⼀回收。这种⽅法效率不⾼,会产⽣⼤量不连续的碎⽚。
(2)标记整理算法:先标记存活对象,然后让所有存活对象向⼀端移动,之后清理端边界以外的内存。
(3)标记复制算法:将可⽤内存按容量划分为⼤⼩相等的两块,每次只使⽤其中⼀块。当使⽤的这块空间⽤完了,就将存活对象复制到另⼀块,再把已使⽤过的内存空间⼀次清理掉。

16、简述分代收集算法。

根据对象存活周期将内存划分为⼏块,不同块采⽤适当的收集算法
⼀般将堆分为新⽣代和⽼年代,对这两块采⽤不同的算法。
新⽣代使⽤:标记复制算法。
⽼年代使⽤:标记清除或者标记整理算法。

17、简述Serial垃圾收集器。

Serial垃圾收集器是单线程串⾏收集器。垃圾回收的时候,必须暂停其他所有线程。新⽣代使⽤标记复制算法,⽼年代使⽤标记整理算法。简单⾼效。

18、简述ParNew垃圾收集器。

ParNew垃圾收集器可以看作Serial垃圾收集器的多线程版本,新⽣代使⽤标记复制算法,⽼年代使⽤标记整理算法。

19、简述Parallel Scavenge垃圾收集器。

注重吞吐量,即 CPU运⾏代码时间/CPU耗时总时间(CPU运⾏代码时间+ 垃圾回收时间)。新⽣代使⽤标记复制算法,⽼年代使⽤标记整理算法。

20、简述CMS垃圾收集器。

CMS垃圾收集器注重最短时间停顿。CMS垃圾收集器为最早提出的并发收集器,垃圾收集线程与⽤户线程同时⼯作。采⽤标记清除算法。该收集器分为初始标记、并发标记、并发预清理、并发清除、并发重置这么⼏个步骤。
(1)初始标记:暂停其他线程(stop the world),标记与GC roots直接关联的对象。
(2)并发标记:可达性分析过程(程序不会停顿)。
(3)并发预清理:查找执⾏并发标记阶段从年轻代晋升到⽼年代的对象,重新标记,暂停虚拟机(stop the world)扫描CMS堆中剩余对象。
(4)并发清除:清理垃圾对象,(程序不会停顿)。
(5)并发重置:重置CMS收集器的数据结构。

21、简述G1垃圾收集器。

和Serial、Parallel Scavenge、CMS不同,G1垃圾收集器把堆划分成多个⼤⼩相等的独⽴区域
(Region),新⽣代和⽼年代不再物理隔离。通过引⼊ Region 的概念,从⽽将原来的⼀整块内存空间划分成多个的⼩空间,使得每个⼩空间可以单独进⾏垃圾回收。
(1)初始标记:标记与GC roots直接关联的对象。
(2)并发标记:可达性分析。
(3)最终标记:对并发标记过程中,⽤户线程修改的对象再次标记⼀下。
(4)筛选回收:对各个Region的回收价值和成本进⾏排序,然后根据⽤户所期望的GC停顿时间制定回收计划并回收。

22、简述Minor GC。

Minor GC指发⽣在新⽣代的垃圾收集,因为 Java 对象⼤多存活时间短,所以 Minor GC ⾮常频繁,⼀般回收速度也⽐较快。

23、简述Full GC。

Full GC 是清理整个堆空间—包括年轻代和永久代。调⽤System.gc(),⽼年代空间不⾜,空间分配担保失败,永⽣代空间不⾜会产⽣full gc。

24、常⻅内存分配策略。

⼤多数情况下对象在新⽣代 Eden 区分配,当 Eden 没有⾜够空间时将发起⼀次 Minor GC
⼤对象需要⼤量连续内存空间,直接进⼊⽼年代区分配
如果经历过第⼀次 Minor GC 仍然存活且能被 Survivor 容纳,该对象就会被移动到 Survivor 中并将年龄设置为 1,并且每熬过⼀次 Minor GC 年龄就加 1 ,当增加到⼀定程度(默认15)就会被晋升到⽼年代。
如果在 Survivor 中相同年龄所有对象⼤⼩的总和⼤于 Survivor 的⼀半,年龄不⼩于该年龄的对象就可以直接进⼊⽼年代。
MinorGC 前,虚拟机必须检查⽼年代最⼤可⽤连续空间是否⼤于新⽣代对象总空间,如果满⾜则说明这次Minor GC 确定安全。如果不,JVM会查看HandlePromotionFailure 参数是否允许担保失败,如果允许会继续检查⽼年代最⼤可⽤连续空间是否⼤于历次晋升⽼年代对象的平均⼤⼩,如果满⾜将Minor GC,否则改成⼀次 FullGC。

25、简述JVM类加载过程。

(1)加载
A、通过全类名获取类的⼆进制字节流。
B、将类的静态存储结构转化为⽅法区的运⾏时数据结构。
C、在内存中⽣成类的Class对象,作为⽅法区数据的⼊⼝。
(2)验证:对⽂件格式,元数据,字节码,符号引⽤等验证正确性。
(3)准备:在⽅法区内为类变量分配内存并设置为0值。
(4)解析:将符号引⽤转化为直接引⽤。
(5)初始化:执⾏类构造器clinit⽅法,真正初始化。

26、简述JVM中的类加载器。

(1)BootstrapClassLoader启动类加载器:加载/lib下的jar包和类。 由C++编写。
(2)ExtensionClassLoader扩展类加载器: /lib/ext⽬录下的jar包和类。由Java编写。
(3)AppClassLoader应⽤类加载器:加载当前classPath下的jar包和类。由Java编写。

27、简述双亲委派机制

⼀个类加载器收到类加载请求之后,⾸先判断当前类是否被加载过。已经被加载的类会直接返回,如果没有被加载,⾸先将类加载请求转发给⽗类加载器,⼀直转发到启动类加载器,只有当⽗类加载器⽆法完成时才尝试⾃⼰加载。
加载类顺序:BootstrapClassLoader->ExtensionClassLoader->AppClassLoader->CustomClassLoader
检查类是否加载顺序: CustomClassLoader->AppClassLoader->ExtensionClassLoader->BootstrapClassLoader

28、双亲委派机制的优点.

(1)避免类的重复加载。相同的类被不同的类加载器加载会产⽣不同的类,双亲委派保证了Java程序的稳定运⾏。
(2)保证核⼼API不被修改。
(3)如何破坏双亲委派机制
(4)重载loadClass()⽅法,即⾃定义类加载器。

29、如何构建⾃定义类加载器

新建⾃定义类继承⾃java.lang.ClassLoader,重写findClass、loadClass、defineClass⽅法。

30、JVM常⻅调优参数。

(1)-Xms 初始堆⼤⼩;
(2)-Xmx 最⼤堆⼤⼩;
(3)-XX:NewSize 年轻代⼤⼩;
(4)-XX:MaxNewSize 年轻代最⼤值;
(5)-XX:PermSize 永⽣代初始值;
(6)-XX:MaxPermSize 永⽣代最⼤值;
(7)-XX:NewRatio 新⽣代与⽼年代的⽐例。

你可能感兴趣的:(大数据开发,面试,Java,java,jvm,开发语言,面试)