前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【面经】闪送Java一面面经(上)

【面经】闪送Java一面面经(上)

原创
作者头像
后端码匠
发布2023-11-10 21:28:36
2540
发布2023-11-10 21:28:36
举报
文章被收录于专栏:后端码匠后端码匠

闪送 Java 一面

一、JVM了解多少,内存区域介绍,对应的功能

Java 虚拟机(JVM)是Java程序的运行环境,它负责将 Java 源代码编译成字节码,并在运行时执行这些字节码。JVM 主要负责管理程序的内存、执行字节码、进行垃圾回收等任务。下面是 JVM 的内存区域及其功能的简要介绍:

JVM 内存区域主要分为线程共享区域(Java堆、方法区)、线程私有区域(程序计数器、虚拟机栈、本地方法栈)。

1、程序计数器(Program Counter Register)

程序计数器也叫PC寄存器。是一块较小的内存空间。 在 JVM 规范中,每个线程都有自己的程序计数器,独立存储,互不影响,也就是说程序计数器是线程私有的。 如果当前线程执行的是一个 Java 方法,程序计数器记录的是正在执行的虚拟机字节码指令的地址,如果正在执行的是本地(Native)方法,则是空(Undefined)。 此内存区域是唯一一个在 JVM 规范中没有规定任何 OutOfMemoryError 情况的区域。

2、Java 虚拟机栈(VM Stack)

Java 虚拟机栈也是线程私有的,每个线程在创建时都会创建一个虚拟机栈,生命周期与线程相同。 虚拟机栈描述的是 Java 方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)压入虚拟机栈,方法执行完毕栈帧出栈。 栈帧中存储着局部变量表、操作数栈、动态链接、方法出口等信息。

在 JVM 规范中对虚拟机栈规定了两类异常:

如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常; 如果 Java 虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出 OutOfMemoryError 异常。 可以通过参数-Xss 设定Java虚拟机栈空间大小。

3、本地方法栈(Native Method Stack)

本地方法栈与虚拟机栈类似,也是每个线程都会创建一个。区别是,虚拟机栈为虚拟机执行 Java 方法服务,本地方法栈则是为虚拟机使用到的本地(Native)方法服务。

JVM 规范中并未对本地方法栈的实现做强制规定,具体虚拟机可以根据需要自由实现它。甚至在 Oracle Hotspot JVM 中将本地方法栈和虚拟机栈合二为一。 与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出 StackOverflowError 和 OutOfMemoryError 异常。

4、堆(Heap)

Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。它是Java内存管理的核心区域,用来存放 Java 对象实例,几乎所有的 Java 对象实例都被直接分配在堆上。 但是随着即时编译技术的进步和逃逸分析技术的逐渐成熟,栈上分配、标量替换优化手段将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。

从 JDK1.7 开始已经默认开启逃逸分析,如果某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去),那么对象可以直接在栈上分配内存。

Java堆是垃圾收集器管理的主要区域,因此也被称为GC堆。从垃圾回收的角度看,由于现代垃圾收集器大部分都是基于分代收集理论设计的, 所以 Java堆还可以细分为:新生代(Eden区、From Survivor区 和 To Survivor区)和老年代。 无论如何划分,Java 中存储的都是对象的实例,这一点上是不变的,而将 Java堆细分的目的只是为了更好的回收内存,或者更快的分配内存。

如果在Java堆中没有足够的内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将抛出 OutOfMemoryError 异常。 通过参数-Xms 和 -Xmx 设定初始堆大小和最大堆大小。

5、方法区(Method Area)

方法区也是所有线程共享的一块内存区域,用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。 由于早期 HotSpot JVM 使用永久代实现方法区,很多人习惯将方法区称为永久代(Permanent Generation)。 方法区是Java虚拟机规范中的定义,是一种规范,而永久代则是一种是实现,一个是标准一个是实现, 其他的虚拟机(比如 BEA JRockit、IBM J9等)实现并没有永久代这一说法。

方法区的发展

由于永久代的大小是有限的,并且 JVM 对永久代垃圾回收(如,常量池回收、类型的卸载)的效果比较难以令人满意, 我们通常使用 -XX:PermSize 和 -XX:MaxPermSize 设置永久代的大小, 32位机器默认的永久代大小为64M,64位的机器则为85M。 一旦类的元数据超过了设定的大小,程序就会耗尽内存,并出现内存溢出错误(OOM)。

在 JDK6 的时候 HotSpot 团队就有放弃永久代逐步改为本地内存(Native Memory)来实现方法区的计划了, 到了 JDK7 已经把原本放在永久代的字符串常量池、静态变量等移到堆上分配, 在 JDK8 中彻底移除了永久代,将 JDK7 中永久代剩余的内容(主要是类的元数据)移到元空间(Metaspace)中。 移除永久代是为融合 HotSpot JVM 与 JRockit VM 而做出的努力,因为 JRockit 没有永久代,不需要配置永久代。

元空间(Metaspace)

元空间与永久代最大的区别在于:元空间并不在 Java虚拟机中,而是使用本地内存(Native Memory)。因此,默认情况下,元空间大小仅受本地内存限制。

类的元数据放入本地内存,字符串常量池和类的静态变量放入 Java堆中,这样加载多少类的元数据就不再由 MaxPermSize 控制,而由系统的实际可用空间来控制。通过参数 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 设定元空间初始值和最大值。

二、类加载器有哪些,双亲委派机制,类加载过程

Java 类加载器(Class Loader)是 Java 运行时环境的一部分,负责将类的字节码加载到内存中,并将其转换为 java.lang.Class 的实例。Java 类加载器主要分为以下几种:

  1. 启动类加载器(Bootstrap Class Loader): 负责加载 Java 的核心类库,通常由 C++ 编写,是 JVM 的一部分,不是 Java 类。
  2. 扩展类加载器(Extension Class Loader): 负责加载 Java 的扩展类库,位于 jre/lib/ext 目录下。
  3. 应用程序类加载器(Application Class Loader): 也称为系统类加载器,负责加载应用程序的类,是默认的类加载器。
  4. 自定义类加载器(Custom Class Loader): 开发者可以通过继承 ClassLoader 类,编写自定义的类加载器。

Java 类加载器采用了双亲委派机制(Parent Delegation Model)。该机制的基本原则是:当一个类加载器收到类加载请求时,它首先不会自己去尝试加载,而是将请求委派给父类加载器去完成。每个类加载器都是如此,只有在父类加载器无法完成加载时,子类加载器才会尝试自己去加载。

类加载的过程包括以下步骤:

  1. 加载(Loading): 将类的二进制数据读入内存,并生成 java.lang.Class 对象。
  2. 链接(Linking):
    • 验证(Verification): 确保被加载的类的正确性。
    • 准备(Preparation): 为类的静态变量分配内存,并设置默认初始值。
    • 解析(Resolution): 将常量池中的符号引用替换为直接引用。
  3. 初始化(Initialization): 执行类的初始化代码,包括静态代码块和静态变量赋值。

类加载器采用懒加载的策略,即只有在需要使用某个类时才会加载该类。这样可以提高系统的启动速度,并减小资源消耗。在类加载器的工作过程中,双亲委派机制保证了类的一致性和防止类的重复加载。

三、GC算法有哪些,对应哪些垃圾回收器,哪个适用于吞吐量大的场景

Java 虚拟机中使用了多种垃圾回收算法和相应的垃圾回收器。以下是常见的垃圾回收算法和垃圾回收器,以及它们适用的场景:

1. 标记-清除算法(Mark and Sweep):

  • 垃圾回收器: 由于标记-清除算法的效率不高,实际应用中较少单独使用。在垃圾回收器中,CMS(Concurrent Mark-Sweep)垃圾回收器是基于标记-清除算法的改进版本,用于提高垃圾回收的效率。

2. 复制算法(Copying):

  • 垃圾回收器: 主要有新生代垃圾回收器,如Serial、ParNew、G1中的Young区。这个算法的特点是将内存分为两块,每次只使用其中一块,当这一块的内存用尽时,将存活的对象复制到另一块内存中。

3. 标记-整理算法(Mark and Compact):

  • 垃圾回收器: 主要用于老年代,如Serial Old、CMS垃圾回收器、G1中的Old区。标记-整理算法的特点是标记存活对象,然后将存活对象压缩到内存的一端,清理边界外的内存。

4. 分代垃圾回收理论:

  • 垃圾回收器: 基于分代理论,将堆内存分为新生代和老年代。新生代使用复制算法,老年代使用标记-整理算法。垃圾回收器有Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS等。

5. G1(Garbage-First)算法:

  • 垃圾回收器: G1垃圾回收器,是一款面向服务端应用的垃圾回收器,通过划分多个内存区域,可以在吞吐量和低延迟之间取得平衡。G1算法在处理大堆、多核处理器、对低延迟要求较高的应用场景时表现出色。

适用场景:

  • 吞吐量优先场景: 如果系统注重吞吐量,即希望在给定时间内尽量处理更多的请求,可以考虑使用Parallel垃圾回收器(包括Parallel Scavenge和Parallel Old)。
  • 低延迟场景: 如果系统对延迟要求较高,尤其是需要更短的停顿时间,可以考虑使用G1垃圾回收器。 G1 垃圾回收器通过将整个堆划分为多个区域,可以更灵活地控制垃圾回收的停顿时间,适用于要求低延迟的应用场景。

在实际应用中,选择合适的垃圾回收器需要综合考虑应用的特性、硬件环境、性能要求等因素。

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 闪送 Java 一面
    • 一、JVM了解多少,内存区域介绍,对应的功能
      • 1、程序计数器(Program Counter Register)
      • 2、Java 虚拟机栈(VM Stack)
      • 3、本地方法栈(Native Method Stack)
      • 4、堆(Heap)
      • 5、方法区(Method Area)
    • 二、类加载器有哪些,双亲委派机制,类加载过程
      • 三、GC算法有哪些,对应哪些垃圾回收器,哪个适用于吞吐量大的场景
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档