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

30 阅读11分钟

互联网大厂面试:Java 核心、JUC、JVM 等知识大考验

在互联网大厂的一间明亮的面试室内,严肃的面试官正襟危坐,对面坐着略显紧张的求职者王铁牛。面试即将开始,一场关于 Java 技术知识的考验拉开了帷幕。

第一轮提问 面试官:首先问你几个基础的 Java 核心知识问题。Java 中基本数据类型有哪些? 王铁牛:这个我知道,Java 基本数据类型有 byte、short、int、long、float、double、char、boolean。 面试官:回答得不错。那说说 Java 中重载和重写的区别是什么? 王铁牛:重载是在同一个类中,方法名相同但参数列表不同;重写是子类重写父类的方法,方法名、参数列表和返回值类型都要相同。 面试官:很好,理解得很清晰。那 Java 中的访问修饰符有哪些,分别有什么作用? 王铁牛:有 public、protected、default(默认,不写修饰符时)、private。public 修饰的成员可以在任何地方访问;protected 修饰的成员可以在同一个包内以及不同包的子类中访问;default 修饰的成员只能在同一个包内访问;private 修饰的成员只能在本类中访问。 面试官:非常棒,基础很扎实。接下来我们进入 JUC 相关的问题。JUC 包是什么,有什么作用? 王铁牛:JUC 是 java.util.concurrent 包的简称,它提供了一些用于多线程编程的工具类,能帮助我们更方便地进行并发编程。

第二轮提问 面试官:看来你基础掌握得挺好。那在 JVM 方面,说说 JVM 的内存结构是怎样的? 王铁牛:嗯……JVM 内存结构有堆、栈、方法区……还有啥来着,哎呀,有点想不起来了。 面试官:没关系,尽量说全。那再问你,垃圾回收机制中,常见的垃圾回收算法有哪些? 王铁牛:好像有标记 - 清除算法,还有……其他的不太确定了。 面试官:那关于多线程,创建线程有哪几种方式? 王铁牛:这个我知道,有继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。 面试官:不错。那线程的生命周期有哪些状态? 王铁牛:有新建、就绪、运行、阻塞、死亡这几个状态。 面试官:可以。接着说说线程池,线程池的核心参数有哪些? 王铁牛:嗯……有核心线程数、最大线程数,其他的不太清楚了。

第三轮提问 面试官:下面问一些框架相关的问题。先说说 Spring,Spring 的核心特性有哪些? 王铁牛:有依赖注入和面向切面编程,好像还有个什么 IOC,对,控制反转。 面试官:对,回答得可以。那 Spring Boot 和 Spring 有什么区别? 王铁牛:Spring Boot 是 Spring 的扩展,它简化了 Spring 的配置,能快速搭建项目。 面试官:不错。再说说 MyBatis,MyBatis 中 #{} 和 {} 的区别是什么? **王铁牛**:这个……好像 #{} 是预编译的,能防止 SQL 注入,{} 就是直接替换,有 SQL 注入风险。 面试官:回答正确。那 Dubbo 是什么,有什么作用? 王铁牛:Dubbo 是个分布式服务框架,能实现服务的注册、发现和调用。 面试官:可以。最后说说 Redis,Redis 有哪些数据类型? 王铁牛:有字符串、哈希、列表、集合、有序集合。

面试官:今天的面试就到这里了,你的表现有好有坏,基础的一些知识掌握得还可以,但对于一些复杂的知识点,比如 JVM 内存结构和线程池核心参数等回答得不够完整。我们需要综合评估一下,你先回家等通知吧。

答案详解

  1. Java 基本数据类型:Java 有 8 种基本数据类型,分为 4 类。整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节);浮点类型:float(4 字节)、double(8 字节);字符类型:char(2 字节);布尔类型:boolean(理论上 1 位,但 Java 规范未明确大小)。
  2. 重载和重写的区别
    • 重载(Overloading):发生在同一个类中,方法名相同,但参数列表不同(参数个数、类型或顺序不同),与返回值类型无关。编译器根据调用时传递的参数来决定调用哪个重载方法。
    • 重写(Overriding):发生在子类和父类之间,子类重写父类的方法,方法名、参数列表和返回值类型必须相同(返回值类型可以是父类方法返回值类型的子类,称为协变返回类型)。重写的方法不能比被重写的方法有更严格的访问权限,不能抛出比被重写方法更多的异常。
  3. Java 访问修饰符
    • public:可以被任何类访问,访问权限最宽松。
    • protected:在同一个包内的类可以访问,不同包的子类也可以访问。
    • default(默认):不写修饰符时就是默认访问权限,只能被同一个包内的类访问。
    • private:只能在本类中访问,访问权限最严格。
  4. JUC 包:java.util.concurrent 包是 Java 提供的用于并发编程的工具包,包含了线程池、锁、并发集合、原子类等。它简化了多线程编程,提高了并发性能和安全性。例如,Executors 类可以方便地创建线程池,Lock 接口提供了比 synchronized 更灵活的锁机制。
  5. JVM 内存结构
    • 堆(Heap):是 JVM 中最大的一块内存区域,用于存储对象实例和数组。垃圾回收主要就是针对堆进行的。堆可以分为新生代和老年代,新生代又可以分为 Eden 区和两个 Survivor 区。
    • 栈(Stack):分为虚拟机栈和本地方法栈。虚拟机栈为 Java 方法服务,每个方法在执行时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。本地方法栈为本地方法服务。
    • 方法区(Method Area):用于存储类的元数据信息,如类的结构、常量池、静态变量等。在 JDK 1.8 之前,方法区也被称为永久代,JDK 1.8 之后,方法区被元空间(Metaspace)取代,元空间使用的是本地内存。
    • 程序计数器(Program Counter Register):是一块较小的内存区域,它可以看作是当前线程所执行的字节码的行号指示器。每个线程都有自己独立的程序计数器。
  6. 常见的垃圾回收算法
    • 标记 - 清除算法(Mark - Sweep):先标记出需要回收的对象,然后统一回收这些对象。缺点是会产生大量的内存碎片。
    • 标记 - 整理算法(Mark - Compact):先标记出需要回收的对象,然后将存活的对象向一端移动,最后清理掉边界以外的内存。解决了内存碎片问题,但效率相对较低。
    • 复制算法(Copying):将内存分为大小相等的两块,每次只使用其中一块。当这块内存用完后,将存活的对象复制到另一块内存中,然后清空使用过的这块内存。优点是不会产生内存碎片,但内存利用率较低。
    • 分代收集算法(Generational Collection):根据对象的存活周期将内存分为不同的区域,如新生代和老年代,针对不同区域采用不同的垃圾回收算法。新生代对象存活时间短,采用复制算法;老年代对象存活时间长,采用标记 - 清除或标记 - 整理算法。
  7. 创建线程的方式
    • 继承 Thread 类:创建一个类继承 Thread 类,重写 run 方法,然后创建该类的实例并调用 start 方法启动线程。
    • 实现 Runnable 接口:创建一个类实现 Runnable 接口,实现 run 方法,然后将该类的实例作为参数传递给 Thread 类的构造函数,再调用 start 方法启动线程。
    • 实现 Callable 接口:创建一个类实现 Callable 接口,实现 call 方法,该方法有返回值。通过 FutureTask 类包装 Callable 实例,再将 FutureTask 实例作为参数传递给 Thread 类的构造函数,调用 start 方法启动线程,最后可以通过 FutureTask 的 get 方法获取线程执行的结果。
    • 使用线程池:通过 Executors 类提供的静态方法创建不同类型的线程池,将实现了 Runnable 或 Callable 接口的任务提交给线程池执行。
  8. 线程的生命周期状态
    • 新建(New):线程对象被创建,但还没有调用 start 方法。
    • 就绪(Runnable):线程调用了 start 方法,等待获取 CPU 时间片,进入可运行状态。
    • 运行(Running):线程获得 CPU 时间片,正在执行 run 方法中的代码。
    • 阻塞(Blocked):线程因为某些原因(如等待锁、等待 I/O 操作等)暂时放弃 CPU 使用权,进入阻塞状态。阻塞状态分为等待阻塞、同步阻塞和其他阻塞。
    • 死亡(Terminated):线程执行完 run 方法中的代码或因异常退出,线程生命周期结束。
  9. 线程池的核心参数
    • corePoolSize(核心线程数):线程池中的核心线程数量,当提交的任务数小于核心线程数时,线程池会创建新的线程来执行任务。
    • maximumPoolSize(最大线程数):线程池允许创建的最大线程数量。当提交的任务数大于核心线程数且任务队列已满时,线程池会创建新的线程,直到达到最大线程数。
    • keepAliveTime(线程存活时间):当线程池中的线程数量超过核心线程数时,多余的空闲线程在等待新任务的最长时间,超过这个时间,空闲线程会被销毁。
    • unit(时间单位):keepAliveTime 的时间单位,如 TimeUnit.SECONDS、TimeUnit.MILLISECONDS 等。
    • workQueue(任务队列):用于存储等待执行的任务的队列。常见的任务队列有 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue 等。
    • threadFactory(线程工厂):用于创建线程的工厂,可以自定义线程的名称、优先级等属性。
    • handler(拒绝策略):当线程池中的线程数量达到最大线程数且任务队列已满时,新提交的任务会触发拒绝策略。常见的拒绝策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(由调用线程处理该任务)、DiscardPolicy(直接丢弃该任务)、DiscardOldestPolicy(丢弃队列中最老的任务,然后尝试提交新任务)。
  10. Spring 的核心特性
    • 控制反转(Inversion of Control,IOC):将对象的创建和依赖关系的管理交给 Spring 容器,而不是由对象本身来创建和管理依赖。通过依赖注入(Dependency Injection,DI)的方式实现,如构造器注入、属性注入等。
    • 面向切面编程(Aspect - Oriented Programming,AOP):将与业务逻辑无关的通用功能(如日志记录、事务管理等)从业务逻辑中分离出来,形成独立的切面,在需要的地方进行切入,提高代码的可维护性和复用性。
  11. Spring Boot 和 Spring 的区别
    • Spring 是一个功能强大的 Java 开发框架,提供了 IOC、AOP 等核心特性,但配置比较繁琐,需要编写大量的 XML 配置文件或 Java 配置类。
    • Spring Boot 是基于 Spring 构建的,它简化了 Spring 的配置,提供了自动配置功能,通过约定大于配置的原则,减少了开发者的配置工作量。Spring Boot 还内置了嵌入式服务器(如 Tomcat、Jetty 等),可以快速搭建独立的、可运行的 Spring 应用。
  12. MyBatis 中 #{} 和 ${} 的区别
    • #{} 是预编译的,MyBatis 在处理 #{} 时,会将其替换为占位符?,然后使用 PreparedStatement 来执行 SQL 语句,能有效防止 SQL 注入。
    • 是直接替换,MyBatis在处理{} 是直接替换,MyBatis 在处理 {} 时,会将其直接替换为传入的值,存在 SQL 注入风险。一般用于动态表名、动态列名等场景。
  13. Dubbo:Dubbo 是阿里巴巴开源的分布式服务框架,用于实现服务的注册、发现和调用。它提供了高性能、透明化的远程服务调用方案,支持多种协议(如 Dubbo 协议、HTTP 协议等)和集群容错策略(如负载均衡、失败重试等)。通过 Dubbo,不同的服务可以在分布式环境中相互调用,提高了系统的可扩展性和可维护性。
  14. Redis 数据类型
    • 字符串(String):最基本的数据类型,可以存储字符串、整数或浮点数。常见操作有 set、get、incr 等。
    • 哈希(Hash):类似于 Java 中的 HashMap,用于存储键值对的集合。常见操作有 hset、hget、hgetall 等。
    • 列表(List):是一个有序的字符串列表,可以在列表的两端进行插入和删除操作。常见操作有 lpush、rpush、lpop、rpop 等。
    • 集合(Set):是一个无序且唯一的字符串集合。常见操作有 sadd、srem、smembers 等。
    • 有序集合(Sorted Set):类似于集合,但每个成员都关联一个分数,根据分数进行排序。常见操作有 zadd、zrange、zrank 等。