《互联网大厂 Java 面试:核心知识、框架与中间件大考验》

33 阅读9分钟

第一轮面试

面试官:我们开始第一轮面试。首先,简单说一下 Java 的基本数据类型有哪些? 王铁牛:这个我知道,Java 基本数据类型有 byte、short、int、long、float、double、char、boolean。 面试官:不错,回答得很准确。那你说说 ArrayList 和 LinkedList 的区别是什么? 王铁牛:ArrayList 是基于动态数组实现的,它随机访问速度快,但是插入和删除操作效率低;LinkedList 是基于双向链表实现的,插入和删除操作效率高,随机访问速度慢。 面试官:很好,看来基础掌握得挺扎实。那在多线程环境下,ArrayList 是线程安全的吗?如果不是,怎么解决线程安全问题? 王铁牛:ArrayList 不是线程安全的。可以用 Vector 代替,Vector 是线程安全的动态数组;也可以用 Collections.synchronizedList 方法把 ArrayList 包装成线程安全的列表。

第二轮面试

面试官:进入第二轮。我们来说说 JVM,JVM 的内存区域是如何划分的? 王铁牛:JVM 内存区域主要分为堆、栈、方法区、程序计数器和本地方法栈。堆是存放对象实例的地方;栈主要存储局部变量等;方法区存储类的信息、常量等;程序计数器记录线程执行的字节码行号;本地方法栈为本地方法服务。 面试官:回答得挺全面。那 JVM 的垃圾回收机制是怎样的,能说一下常见的垃圾回收算法吗? 王铁牛:垃圾回收机制就是回收不再使用的对象所占用的内存。常见的垃圾回收算法有标记 - 清除算法、标记 - 整理算法、复制算法和分代收集算法。 面试官:不错。那你说说什么情况下会触发 Full GC? 王铁牛:呃……这个嘛,好像是当老年代空间不足,或者方法区空间不足的时候会触发吧。

第三轮面试

面试官:最后一轮面试了。我们聊聊 Spring 框架,Spring 的核心特性有哪些? 王铁牛:Spring 的核心特性有依赖注入(DI)和面向切面编程(AOP)。依赖注入是指对象之间的依赖关系由容器来管理;AOP 可以在不修改原有代码的情况下增强功能。 面试官:回答正确。那 Spring 中的 Bean 的作用域有哪些? 王铁牛:有 singleton(单例)、prototype(原型)、request、session 等。singleton 是单例模式,整个容器中只有一个实例;prototype 每次请求都会创建一个新的实例。 面试官:很好。那 Dubbo 是做什么的,它的核心组件有哪些? 王铁牛:Dubbo 是一个高性能的分布式服务框架。核心组件嘛,我记得有注册中心、远程调用、集群容错啥的,但具体的我有点说不太清楚了。

面试官:今天的面试就到这里,你先回家等通知吧。我们会综合评估你的表现,之后会尽快给你答复。

问题答案

  1. Java 基本数据类型:Java 有 8 种基本数据类型,分为 4 类。整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节);浮点类型:float(4 字节)、double(8 字节);字符类型:char(2 字节);布尔类型:boolean(1 位)。这些基本数据类型在内存中存储的是具体的值,而不是引用。
  2. ArrayList 和 LinkedList 的区别
    • 数据结构:ArrayList 基于动态数组,它在内存中是连续存储的;LinkedList 基于双向链表,每个节点包含数据和前后指针,在内存中不连续存储。
    • 访问效率:ArrayList 支持随机访问,通过索引可以直接定位到元素,时间复杂度为 O(1);LinkedList 随机访问需要从头或尾遍历链表,时间复杂度为 O(n)。
    • 插入和删除效率:ArrayList 在中间插入或删除元素时,需要移动后续元素,时间复杂度为 O(n);LinkedList 插入和删除元素只需要修改前后节点的指针,时间复杂度为 O(1),但如果是在指定位置插入或删除,需要先定位到该位置,时间复杂度为 O(n)。
  3. 多线程环境下 ArrayList 的线程安全问题及解决方法:ArrayList 不是线程安全的,因为它的方法没有进行同步处理。在多线程环境下,多个线程同时对 ArrayList 进行读写操作可能会导致数据不一致或抛出 ConcurrentModificationException 异常。解决方法有:
    • 使用 Vector:Vector 是线程安全的动态数组,它的方法都使用了 synchronized 关键字进行同步,保证了线程安全,但性能较低。
    • 使用 Collections.synchronizedList:可以使用 Collections.synchronizedList 方法将 ArrayList 包装成线程安全的列表,例如:List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());
  4. JVM 内存区域划分
    • 堆(Heap):是 JVM 中最大的一块内存区域,所有对象实例和数组都在堆上分配内存。堆是垃圾回收的主要区域,又可以分为新生代和老年代。
    • 栈(Stack):分为虚拟机栈和本地方法栈。虚拟机栈为 Java 方法服务,每个方法执行时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息;本地方法栈为本地方法服务。
    • 方法区(Method Area):主要存储类的信息、常量、静态变量、即时编译器编译后的代码等数据。在 Java 8 及以后,方法区被元空间(Metaspace)取代。
    • 程序计数器(Program Counter Register):是一块较小的内存区域,它可以看作是当前线程所执行的字节码的行号指示器。每个线程都有一个独立的程序计数器。
    • 本地方法栈(Native Method Stack):与虚拟机栈类似,为本地方法服务。
  5. JVM 垃圾回收机制及常见垃圾回收算法
    • 垃圾回收机制:自动回收不再使用的对象所占用的内存,避免内存泄漏。垃圾回收器会定期或在内存不足时进行垃圾回收操作。
    • 标记 - 清除算法(Mark - Sweep):先标记出所有需要回收的对象,然后统一回收这些对象。该算法的缺点是会产生大量的内存碎片。
    • 标记 - 整理算法(Mark - Compact):先标记出所有需要回收的对象,然后将存活的对象向一端移动,最后清理掉边界以外的内存。该算法可以避免内存碎片,但移动对象会有一定的开销。
    • 复制算法(Copying):将可用内存分为两块,每次只使用其中一块。当这一块内存用完后,将存活的对象复制到另一块内存中,然后清理掉原来的内存。该算法的优点是没有内存碎片,但会浪费一半的内存空间。
    • 分代收集算法(Generational Collection):根据对象的存活周期将内存分为新生代和老年代。新生代使用复制算法,老年代使用标记 - 清除或标记 - 整理算法。
  6. 触发 Full GC 的情况
    • 老年代空间不足:当新创建的对象无法在新生代分配内存,需要晋升到老年代,但老年代没有足够的空间时,会触发 Full GC。
    • 方法区空间不足:当方法区(元空间)存储的类信息、常量等数据过多,导致空间不足时,会触发 Full GC。
    • 显式调用 System.gc():虽然调用 System.gc() 不一定会立即触发 Full GC,但会建议 JVM 进行垃圾回收,可能会触发 Full GC。
    • Minor GC 晋升到老年代的对象大小大于老年代的剩余空间:在 Minor GC 时,会将一部分存活的对象晋升到老年代,如果晋升的对象大小大于老年代的剩余空间,会触发 Full GC。
  7. Spring 的核心特性
    • 依赖注入(Dependency Injection,DI):是一种设计模式,通过将对象之间的依赖关系的创建和管理交给容器来完成,而不是在对象内部直接创建依赖对象。这样可以降低代码的耦合度,提高可维护性和可测试性。例如,在 Spring 中可以通过构造函数注入、Setter 方法注入等方式实现依赖注入。
    • 面向切面编程(Aspect - Oriented Programming,AOP):允许在不修改原有代码的情况下,对程序的某些功能进行增强。AOP 主要通过定义切面(Aspect)、切入点(Pointcut)和通知(Advice)来实现。切面是包含了多个通知和切入点的模块;切入点定义了哪些方法会被增强;通知定义了在切入点方法执行的不同阶段执行的代码,如前置通知、后置通知、环绕通知等。
  8. Spring 中 Bean 的作用域
    • singleton:单例模式,整个 Spring 容器中只有一个 Bean 实例。这是 Spring 默认的作用域。
    • prototype:原型模式,每次请求 Bean 时都会创建一个新的实例。
    • request:在 Web 应用中,每个 HTTP 请求都会创建一个新的 Bean 实例,该实例仅在当前请求的生命周期内有效。
    • session:在 Web 应用中,每个 HTTP 会话都会创建一个新的 Bean 实例,该实例在当前会话的生命周期内有效。
  9. Dubbo 的作用及核心组件
    • 作用:Dubbo 是一个高性能的分布式服务框架,用于解决分布式系统中服务之间的远程调用、服务治理等问题。它可以实现服务的注册与发现、负载均衡、集群容错等功能,提高系统的可扩展性和可靠性。
    • 核心组件
      • 注册中心(Registry):负责服务的注册和发现。服务提供者将自己的服务信息注册到注册中心,服务消费者从注册中心获取服务提供者的地址信息。常见的注册中心有 ZooKeeper、Nacos 等。
      • 远程调用(Remote Invocation):实现服务消费者和服务提供者之间的远程通信。Dubbo 支持多种远程调用协议,如 Dubbo 协议、HTTP 协议等。
      • 集群容错(Cluster):当服务提供者出现故障或不可用时,Dubbo 提供了多种集群容错策略,如失败重试、快速失败、广播等,保证服务的高可用性。
      • 负载均衡(Load Balance):当有多个服务提供者时,Dubbo 会根据负载均衡策略选择一个合适的服务提供者进行调用。常见的负载均衡策略有随机、轮询、最少活跃调用数等。