Java开发第二轮面试被疯狂问JVM相关,被整懵了!!赶紧好好复习一下

作者:逍遥Sean
简介:一个主修Java的Web网站\游戏服务器后端开发者
主页:https://blog.csdn.net/Ureliable
觉得博主文章不错的话,可以三连支持一下~ 如有需要我的支持,请私信或评论留言!

前言

前几天参加了一个做web开发的面试,一面被问了几个问题,虽然有些题目比较偏,但是确实是Java开发必须了解的内容。我觉得比较简单的问题,却回答的不是很好,后来竟然通过了!到了二面我彻底懵了,全是JVM相关的内容,而我本身这方面就一般般😭

显然这些问题我并不能回答好,本文是我在面试之后总结了答案,给大家分享一下。希望大家遇到的时候,不要像我一下被问懵了💪
👇👇👇

1.堆栈的区别?

Java堆和栈是两个不同的区域,分别用于存储Java程序中不同类型的数据。
Java堆是Java虚拟机中的一块内存区域,用于存储对象实例及数组等。所有在Java中新建的对象都会被分配到堆中,而且堆中的对象实例是共享的,可以被程序中的任何部分访问。Java垃圾回收器会定期清理堆中不再被使用的对象实例,保证JVM的内存使用效率和程序的执行效率。
Java栈是另一块内存区域,用于存储基本数据类型和对象引用。每个线程都有自己的栈,当线程执行方法时,会在栈上创建一个新的栈帧,用于存储该方法的参数、局部变量和操作数栈等信息。在方法执行完毕后,相应的栈帧就会被弹出,栈空间被释放。栈的大小通常比堆要小得多,因此大量的对象实例存储应该避免使用栈。
综上所述,Java堆和栈的区别如下:

  1. Java堆存储对象实例,Java栈存储基本数据类型和对象引用。
  2. Java堆空间由垃圾回收器管理,Java栈由程序自动控制。
  3. Java堆的大小可以通过JVM参数进行调整,Java栈的大小则由操作系统限制。
  4. Java堆空间是共享的,Java栈空间是线程私有的。

栈内存存储的是局部变量,堆内存存储的是实体; 栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;
栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。

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

CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。

CMS 使用的是标记-清除的算法实现的,所以在 gc 的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。

3.怎么判断对象是否可以被回收?

  1. 引⽤计数算法: 这种⽅式是给堆内存当中的每个对象记录⼀个引⽤个数。引⽤个数为0的就认为是
    垃圾。这是早期JDK中使⽤的⽅式。引⽤计数⽆法解决循环引⽤的问题。
  2. 可达性算法: 这种⽅式是在内存中,从根对象向下⼀直找引⽤,找到的对象就不是垃圾,没找到的对象就是垃圾。

4.新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别?

目前常见的新生代垃圾回收器有

  1. Serial GC:单线程执行垃圾回收,适用于单核 CPU 系统和小程序启动时使用。
  2. Parallel GC:多线程执行垃圾回收,适用于多核 CPU 系统和系统处理大型数据时使用。
  3. G1 GC:侧重于减少停顿时间和内存占用,适用于大型内存环境。
  4. Shenandoah GC:侧重于极少停顿时间,适用于超大型内存环境。
  5. ZGC:侧重于响应速度和稳定性,适用于超大型内存环境和云环境。

老年代垃圾回收器有如下几种
6. Serial Old:是Serial收集器的老年代版本,是单线程执行的标记-整理算法。
7. Parallel Old:是Parallel Scavenge收集器的老年代版本,是多线程执行的标记-整理算法。
8. CMS(Concurrent Mark Sweep):是一种并发标记清除算法,主要针对老年代垃圾回收,它的特点是将垃圾回收的过程分为标记和清除两个阶段,并且在标记阶段可以与应用程序同时运行,缩短了垃圾回收的暂停时间。
9. G1(Garbage-First):是一种基于区域的垃圾回收器,将堆内存分为多个大小相等的区域,每个区域可以是年轻代或者老年代,实现了在标记-整理前可选择性地回收某些区域,从而缩小了垃圾回收的范围,也缩短了垃圾回收的暂停时间。同时G1还可以在进行垃圾回收的同时并行地复制存活对象到空的区域中,因此G1是一种混合了并发标记清除和新生代复制算法的垃圾回收器。

新生代垃圾回收器和老生代垃圾回收器是两种不同类型的垃圾回收器,主要区别在于它们的工作方式、应用场景和效率优化方面

  1. 工作方式
    新生代垃圾回收器主要负责处理年轻一代对象的垃圾回收,采用的是Copy算法,将内存区分为两个部分,一个是存活对象的From空间,一个是空闲的To空间。垃圾回收时,将所有存活的对象复制到To空间中,然后清空From空间,完成一次垃圾回收。
    老生代垃圾回收器则主要负责处理长时间存活的老生代对象,采用的是Mark-Sweep和Mark-Compact算法。Mark-Sweep算法首先标记存活对象,然后清除未标记的垃圾对象;Mark-Compact算法则会将所有存活对象移到内存的一端,然后清除未标记的垃圾对象。
  2. 应用场景
    新生代垃圾回收器适用于应用程序中存在大量生命周期短暂的对象,例如Java应用程序的堆栈对象、临时变量等;老生代垃圾回收器适用于应用程序中存在许多长时间存活的对象,例如Java应用程序的静态变量、全局变量、缓存对象等。
  3. 效率优化
    为了提高垃圾回收的效率,新生代垃圾回收器采用了Eden空间、Survivor空间和TLAB等优化技术;老生代垃圾回收器则采用了分代回收和增量标记等技术,可以有效地减少垃圾回收的时间和内存占用。

总之,新生代垃圾回收器和老生代垃圾回收器各自都有优缺点,应根据应用程序的特点和需求来选择合适的垃圾回收器。

5.GC的三种收集方法:标记清除、标记整理、复制算法的原理与特点,分别用在什么地方,如果让你优化收集方法,有什么思路?

标记清除、标记整理、复制算法都是垃圾回收算法,它们的原理和特点如下:

  1. 标记清除算法

标记清除算法分为两个主要步骤:标记和清除。在标记阶段,首先从可访问的对象开始遍历,标记出所有可达的对象;在清除阶段,扫描整个堆,回收未被标记的对象,并将它们的内存空间标记为可用。

特点:

  • 实现简单,容易理解;
  • 不需要一次性地移动对象,因此不会造成额外的复制开销;
  • 但是会造成内存碎片,导致分配大块内存时效率降低。
  1. 标记整理算法

标记整理算法在标记阶段和标记清除算法相同,但在清除阶段会对空间进行整理,将未被标记的内存块进行移动,然后将所有空闲块合并为一个大的空闲块。

特点:

  • 消除了内存碎片,分配大块内存时效率高;
  • 但需要移动对象,因此需要更多的时间和空间开销。
  1. 复制算法

复制算法将堆内存分成两个区域,一半为可用区域,另一半为不可用区域。在回收时,只将可用区域内的存活对象复制到不可用区域中,然后清空整个可用区域,使其成为新的空的可用堆区域。

特点:

  • 消除了内存碎片,分配大块内存时效率高;
  • 移动对象,因此需要更多的时间和空间开销;
  • 需要双倍的内存空间,因为只有一半的内存可用。

标记清除、标记整理、复制算法都是垃圾回收算法,分别适用于不同的场景:

  1. 标记清除算法:适用于内存碎片较多且空间较大的情况。它的工作流程是,首先标记出需要回收的垃圾对象,然后回收这些对象并释放它们占用的内存。该算法的主要缺点是,回收后可能会造成内存碎片,导致内存分配失败。

  2. 标记整理算法:适用于内存碎片较多且空间不足的情况。它的工作流程是,首先标记出需要回收的垃圾对象,然后将所有存活的对象都移到内存的一端,最后回收较端的一块内存。该算法可以解决内存碎片问题,但是需要额外的移动存活对象的时间。

  3. 复制算法:适用于内存碎片较少的情况。它的工作流程是,将内存分为两块,每次只使用其中一块,当这一块内存使用完后,将所有存活的对象复制到另一块内存中,然后清空原来那块内存,继续使用。该算法的优点是简单高效,且不会产生内存碎片,但是需要额外的内存空间。

如果要优化收集方法,可以考虑以下思路:

  1. 改进标记算法,采用增量标记或并发标记,减少暂停时间;
  2. 使用分代收集,根据不同的对象生命周期采用不同的算法,提高效率;
  3. 优化内存分配,采用内存池等机制,减少内存碎片;
  4. 针对大对象进行特殊处理,避免复制算法对其造成过多性能影响;
  5. 进行多线程并发处理,提高垃圾回收效率。

6.java类加载过程?

Java类加载过程可以分为三个阶段:加载、链接和初始化。

  1. 类加载阶段:在加载阶段,JVM会在类路径(classpath)中寻找类的字节码文件,并创建一个对应的Class对象。此时,JVM会检查字节码文件的正确性、类的版本号以及类的访问控制等。如果检查成功,则类加载成功,Class对象被存放在方法区中。

  2. 类链接阶段:链接阶段主要分为三个部分:验证、准备和解析。

  • 验证:验证阶段确保类的字节码符合JVM规范,并且没有被破坏或篡改。验证过程包括格式验证、语义验证、字节码验证和引用验证。
  • 准备:准备阶段为类的静态变量分配内存,并为其赋予默认值。例如,int类型的静态变量默认值为0,引用类型的静态变量默认值为null。
  • 解析:解析阶段将符号引用转换为直接引用。符号引用是一种间接引用方式,例如类名、方法名、字段名,而直接引用是直接指向方法、字段在内存中的地址引用方式。
  1. 类初始化阶段:这是类加载过程中的最后一个阶段。在此阶段,JVM会执行类的静态初始化器代码,以便为静态变量赋值,执行静态块中的代码,以及执行一些特定于类的逻辑处理。此时,类被视为已经被完全初始化,可以被使用了。

7.什么是类加载器,类加载器有哪些?

类加载器(ClassLoader)是Java的核心组件之一,是用于将类的字节码加载进Java虚拟机并生成对应的Class对象的对象。类加载器是Java语言的一个重要机制,它负责在运行时查找和装载Java类文件,使得Java程序可以动态地扩展。
在Java中,类加载器分为三种类别:启动类加载器(Bootstrap ClassLoader)扩展类加载器(Extension ClassLoader)系统类加载器(System ClassLoader)。其中,

  1. 启动类加载器:负责将Java的核心类库加载到JVM中,该加载器并不继承自java.lang.ClassLoader,所以在API中也无法获取到该类加载器的引用。
  2. 扩展类加载器:负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext目录下的jar包。
  3. 系统类加载器:也称应用程序类加载器,负责加载应用程序路径下的类库。

除此之外,Java还支持自定义类加载器,可以通过继承java.lang.ClassLoader类,实现自己的加载逻辑。

8.Eden和Survivor的比例分配

在Java的GC中,Eden和Survivor是两个不同的内存区域。Eden是新对象分配的初始内存区域,而Survivor则是用来保存经过一次GC后仍然存活的对象。Survivor被划分为两个部分:From和To,用于实现垃圾收集算法中的标记-复制过程。

通常情况下,Eden和Survivor的分配比例是8:1:1,也就是说,Eden占整个堆空间的80%,Survivor区域分别占10%。当然,这个比例也会随着具体应用的需求而有所不同,需要根据具体情况进行调整。

9.volatile的语义,它修饰的变量一定线程安全吗

volatile关键字的主要作用是强制对被修饰变量的修改操作立即写入内存中,而不是先写入本地缓存中,这样可以保证在多线程并发的情况下,各个线程对该变量的读取操作都能读取到最新的值,从而保证了数据的可见性。

但是,虽然使用volatile可以保证数据的可见性,但是并不能保证数据的原子性,也就是说,volatile修饰的变量在并发情况下仍然有可能发生竞态条件,因此不能保证线程安全。为了保证线程安全,还需要使用其他的并发控制技术,比如锁、原子变量等。

10.JVM 对象创建步骤流程是什么?

JVM 对象创建的步骤流程如下:

  1. 类加载:JVM需要先加载类定义。如果定义这个类的.class文件还没有被加载,JVM就会把这个文件读进来,然后对它进行解析和验证。

  2. 分配内存:当类被加载后,JVM需要在堆上分配内存来存储对象。根据对象的类型和大小,JVM会在堆上分配一段连续的内存空间。

  3. 初始化成员变量:在对象创建的过程中,JVM需要初始化对象的所有成员变量。如果成员变量是基本类型,JVM会给其默认值;如果成员变量是引用类型,JVM会初始化为null。

  4. 执行构造函数:当内存空间分配完成并且成员变量初始化完成后,JVM会调用构造函数对对象进行初始化。

  5. 返回对象引用:构造函数执行完成后,对象就被创建成功了。JVM会返回对象的引用,开发者就可以通过该引用来访问和操作该对象。

11.什么是 Class 文件? Class 文件主要的信息结构有哪些?

Class 文件是 Java 代码编译后生成的二进制文件,它包含了 Java 程序的字节码及相关的调试信息、版本号、常量池、字段、方法等信息。

Class 文件的主要信息结构如下:

  1. 魔数(Magic Number):Class 文件的前 4 个字节是一个固定的魔数(0xCAFEBABE),用来表明该文件是一个 Java Class 文件。

  2. 版本号(Version Number):Class 文件的版本号包括主版本号和次版本号。它们分别占用了 Class 文件中第 5 到第 8 个字节和第 9 到第 12 个字节。

  3. 常量池(Constant Pool):常量池是 Class 文件中的一个重要部分,它存储了 Class 文件中使用的常量信息,如字符串、数字、类、字段、方法等。常量池的长度和内容都可以从 Class 文件中读取出来。

  4. 访问标志(Access Flag):访问标志用来表示 Class、字段和方法的访问级别和属性,包括 public、private、final、static 等。

  5. 类信息(Class Information):类信息包括类名、父类名、实现的接口、字段和方法等信息。

  6. 字段表(Field Table):字段表存储了类中的所有字段信息,包括字段名、访问标志、数据类型等。

  7. 方法表(Method Table):方法表存储了类中的所有方法信息,包括方法名、访问标志、参数和返回值类型、方法体等。

  8. 属性表(Attribute Table):属性表用来存储一些额外的信息,如 Java 源代码行号和本地变量表等。

12.什么是内存溢出,内存泄漏

内存溢出:当程序需要分配大量内存空间时,但可用内存已经不足,程序就会出现内存溢出错误。这通常是由于程序设计错误或内存泄漏引起的。

内存泄漏:内存泄漏指的是程序中分配的内存空间一直被占用,但在程序运行过程中没有释放,导致程序最终占用的内存越来越大,最终可能导致程序崩溃或者系统不稳定。内存泄漏通常是由于程序中没有正确释放动态分配的内存,或没有及时关闭打开的资源,或者存在循环引用等问题引起的。

13.如何阻止内存泄漏?

内存泄漏是指程序在运行时分配了内存空间但没有释放,导致程序占用的内存越来越多,最终可能导致程序崩溃或系统崩溃。以下是一些防止内存泄漏的方法:

  1. 使用垃圾回收机制:在Java和Python等语言中,垃圾回收机制会自动处理对象的回收,防止内存泄漏。

  2. 手动释放内存:在C或C++等语言中,程序员需要手动管理内存,即在分配内存后记得及时释放。

  3. 避免循环引用:循环引用是指两个对象相互引用,导致引用计数器无法归零,从而出现内存泄漏。避免循环引用的方法是使用弱引用或手动解除引用。

  4. 减少内存分配次数:频繁地分配和释放内存会导致内存碎片化,增加程序的内存占用率。可以使用对象池或缓存等方法减少内存的分配次数。

  5. 使用工具检测内存泄漏:可以使用内存分析工具(如Valgrind、JProfiler等)检测程序中的内存泄漏,及时发现并修复问题。

14.垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗? 有什么办法主动通知虚拟机进行垃圾回收?

垃圾回收器的基本原理
垃圾回收器的基本原理是在运行时跟踪程序运行时使用的内存,并标记哪些内存块已经不再使用。之后,垃圾回收器将自动清理这些未使用的内存块,以便其他部分可以使用。垃圾回收器的实现通常是通过使用算法来检测哪些内存块是未使用的,并清除它们来实现的。一些常见的算法包括标记-清除、标记-整理和复制算法。这些算法的实现方式不同,但它们的目标都是实现自动内存管理,从而减少程序员需要手动管理内存的工作量。

垃圾回收器不能马上回收内存
垃圾回收器具有自动管理内存的能力,但是它并不能马上回收内存。具体来说,垃圾回收器根据特定的算法在一定时间间隔或达到一定的内存使用量后才会开始回收内存。同时,垃圾回收器的回收速度也与计算机硬件的性能、内存使用情况和回收算法等因素有关,所以无法准确预测回收时间。
主动通知虚拟机进行垃圾回收
在 Java 中,可以通过调用 System.gc() 方法来建议虚拟机进行垃圾回收操作。但需要注意的是,虚拟机不一定会立即执行垃圾回收操作,因为它可能会根据自身的算法和策略决定何时执行垃圾回收。此外,建议不要太过频繁地调用 System.gc() 方法,因为它可能会对应用程序的性能产生负面影响。

15.System.gc() 和 Runtime.gc() 会做什么事情?

System.gc() 和 Runtime.gc() 都是用来提示 JVM 执行垃圾回收的方法,但是它们并不能确保立即执行垃圾回收。具体来说,它们的作用如下:

  1. System.gc() 会调用 JVM 的垃圾回收器,并请求进行一次垃圾回收。但是,由于 JVM 的垃圾回收是自动的,所以这个方法只是提供了一种方式来提示 JVM 尽快进行垃圾回收。

  2. Runtime.gc() 方法与 System.gc() 方法类似,也是向 JVM 请求进行垃圾回收。不同的是,它返回一个 Runtime 对象,该对象可以用于控制 JVM 的各种属性,如内存使用情况、线程数量等。

需要注意的是,直接调用这些方法并不一定会立即执行垃圾回收,因为 JVM 可能会根据实际的内存情况和系统负载来决定何时进行垃圾回收。因此,调用这些方法不应该被作为代码中的常规操作,只有在确信需要进行垃圾回收的时候才应该使用。

16.深拷贝和浅拷贝。

深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是指在复制对象时所采用的不同方式。

浅拷贝是指将原对象的地址复制到新对象(即新对象和原对象使用同一个内存地址),使得新对象和原对象共享同一个内存地址。当修改新对象的值时,原对象的值也会被修改。浅拷贝一般是通过复制引用来实现的。

深拷贝是指将原对象完整地复制一份到新的对象中(即新对象和原对象使用不同的内存地址),这样可以保证新对象和原对象不共享内存地址。深拷贝一般是通过递归复制各个属性来实现的。

需要注意的是,如果原对象的属性值是引用类型(如数组、对象),那么浅拷贝只会复制引用,而不会复制引用指向的对象。因此,当修改新对象的引用属性时,原对象的引用属性也会被修改。而深拷贝则可以完整地复制引用指向的对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逍遥Sean

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值