《互联网大厂Java面试:核心知识大考验》

60 阅读8分钟

面试官:第一轮面试开始。首先问你,Java 中的多线程有哪些实现方式?

王铁牛:这个我知道,有继承 Thread 类和实现 Runnable 接口这两种方式。

面试官:回答得不错。那再问你,线程池的核心参数有哪些?

王铁牛:有 corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory 和 handler。

面试官:嗯,掌握得还可以。最后一个问题,简述一下 HashMap 的底层数据结构。

王铁牛:HashMap 底层是数组 + 链表 + 红黑树,当链表长度大于 8 且数组长度大于 64 时,链表会转换为红黑树。

面试官:好,第一轮面试结束,表现不错。下面进入第二轮面试。说说 JVM 的内存区域有哪些?

王铁牛:有程序计数器、虚拟机栈、本地方法栈、堆和方法区。

面试官:那 JVM 的垃圾回收算法有哪些?

王铁牛:有标记清除算法、标记整理算法、复制算法、分代收集算法。

面试官:最后,简述一下 Spring 的核心特性。

王铁牛:Spring 有依赖注入、面向切面编程、IoC 容器等特性。

面试官:第二轮面试也结束了,整体还行。接下来是第三轮面试。Dubbo 的服务注册与发现原理是什么?

王铁牛:呃……这个,大概就是把服务提供者的信息注册到注册中心,消费者从注册中心获取服务提供者的信息。

面试官:回答得不太清晰。那 RabbitMq 的工作模式有哪些?

王铁牛:好像有直连模式、扇形模式、主题模式、头模式。

面试官:最后一个问题,简述一下 MyBatis 的缓存机制。

王铁牛:这个……不太记得了。

面试官:好了,三轮面试都结束了。回家等通知吧。

答案

  1. Java 多线程实现方式
    • 继承 Thread 类:定义一个类继承 Thread 类,重写 run 方法,在 run 方法中编写线程执行的逻辑。然后创建该类的实例对象,调用 start 方法启动线程。
    • 实现 Runnable 接口:定义一个类实现 Runnable 接口,实现 run 方法。创建该类的实例对象,将其作为参数传递给 Thread 类的构造函数创建线程对象,再调用 start 方法启动线程。这种方式更适合多个线程共享一个资源的情况,因为 Java 不支持多重继承,而实现接口可以实现多个。
  2. 线程池核心参数
    • corePoolSize:线程池的核心线程数,当提交的任务数小于 corePoolSize 时,线程池会创建新的线程来执行任务。
    • maximumPoolSize:线程池允许的最大线程数,当提交的任务数大于 corePoolSize 且任务队列已满时,会创建新线程直到线程数达到 maximumPoolSize。
    • keepAliveTime:线程池中的线程在空闲时的存活时间,当线程空闲时间超过 keepAliveTime 时,非核心线程会被销毁。
    • unit:keepAliveTime 的时间单位。
    • workQueue:任务队列,用于存放提交到线程池但尚未执行的任务。常用的任务队列有 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue 等。
    • threadFactory:线程工厂,用于创建线程,可自定义线程的名称、优先级等属性。
    • handler:拒绝策略,当线程池的线程数达到 maximumPoolSize 且任务队列已满时,会调用 handler 来处理新提交的任务。常见的拒绝策略有 AbortPolicy(抛出异常)、CallerRunsPolicy(调用者运行)、DiscardPolicy(丢弃任务)、DiscardOldestPolicy(丢弃最旧的任务)。
  3. HashMap 底层数据结构
    • HashMap 底层是数组 + 链表 + 红黑树。
    • 初始化时会创建一个默认大小为 16 的数组。
    • 当向 HashMap 中插入键值对时,会根据键的哈希值计算出在数组中的索引位置。
    • 如果该位置为空,则直接插入新的键值对。
    • 如果该位置不为空,且键相同,则覆盖原来的值。
    • 如果该位置不为空,且键不同,则会形成链表。当链表长度大于 8 且数组长度大于 64 时,链表会转换为红黑树,以提高查询效率。
  4. JVM 内存区域
    • 程序计数器:是一块较小的内存空间,它记录着当前线程所执行的字节码的行号指示器。每个线程都有一个独立的程序计数器。
    • 虚拟机栈:每个方法被执行的时候都会同时创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
    • 本地方法栈:与虚拟机栈类似,只不过它为本地方法服务。
    • :是 JVM 所管理的内存中最大的一块,被所有线程共享。对象实例以及数组都存放在堆中。
    • 方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  5. JVM 垃圾回收算法
    • 标记清除算法:分为两个阶段,标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。但这种算法会产生大量不连续的内存碎片。
    • 标记整理算法:标记阶段和标记清除算法一样,标记出所有需要回收的对象。在清除阶段,它不是直接回收对象,而是将存活的对象向一端移动,然后清除端边界以外的内存。
    • 复制算法:将内存分为大小相等的两块,每次只使用其中一块。当这一块内存用完了,就将还存活的对象复制到另一块上面,然后把已使用过的内存空间一次清理掉。这种算法适用于对象存活率低的情况。
    • 分代收集算法:根据对象的存活周期将内存划分为不同的区域,一般分为新生代、老年代和永久代(Java8 后为元空间)。不同区域采用不同的垃圾回收算法,如新生代采用复制算法,老年代采用标记清除或标记整理算法。
  6. Spring 核心特性
    • 依赖注入:通过 XML 配置文件或注解的方式,将对象之间的依赖关系交给 Spring 容器来管理,这样可以降低对象之间的耦合度。
    • 面向切面编程(AOP):允许将一些通用的功能(如日志记录、事务管理等)从业务逻辑中分离出来,以提高代码的可维护性和复用性。
    • IoC 容器:控制反转,将对象的创建和管理从应用程序中分离出来,由 Spring 容器负责创建、配置和管理对象,应用程序只需要从容器中获取所需的对象即可。
  7. Dubbo 服务注册与发现原理
    • 服务提供者启动时,会将自身的服务信息(包括服务接口、实现类、服务地址等)通过 Dubbo 的协议封装成特定格式的数据,发送给注册中心进行注册。
    • 注册中心接收到服务提供者的注册信息后,会将其存储起来。
    • 服务消费者启动时,会向注册中心订阅自己感兴趣的服务。注册中心会实时推送符合条件的服务提供者信息给服务消费者。
    • 服务消费者根据接收到的服务提供者信息,通过 Dubbo 的远程调用协议,与服务提供者建立连接,进行远程方法调用。
  8. RabbitMq 工作模式
    • 直连模式:消息只会发送到绑定键与路由键匹配的队列中。
    • 扇形模式:消息会发送到所有绑定到交换机的队列中,不考虑路由键。
    • 主题模式:消息会根据绑定键和路由键的匹配规则发送到相应的队列中,绑定键和路由键可以使用通配符。
    • 头模式:通过消息头中的键值对进行匹配,而不是基于路由键。
  9. MyBatis 缓存机制
    • 一级缓存:是 SqlSession 级别的缓存,当在同一个 SqlSession 中执行相同的 SQL 语句时,会直接从一级缓存中获取数据,不会再次查询数据库。当 SqlSession 关闭时,一级缓存会被清空。
    • 二级缓存:是基于 namespace 级别的缓存,多个 SqlSession 可以共享二级缓存。开启二级缓存后,同一个 namespace 下的查询语句会优先从二级缓存中获取数据。二级缓存的数据会在事务提交时更新。二级缓存需要在 MyBatis 的配置文件中进行配置,并在对应的 Mapper 接口或 XML 文件中开启。