《互联网大厂Java面试大揭秘:核心知识与复杂场景问答》

32 阅读10分钟

面试官:请简要介绍一下Java核心知识中的面向对象三大特性。

王铁牛:嗯,这个我知道,是封装、继承和多态。封装就是把数据和操作数据的方法封装在一起;继承就是子类继承父类的属性和方法;多态就是同一个方法可以根据对象的不同类型而表现出不同的行为。

面试官:不错,回答得很准确。那在多线程中,如何确保线程安全?

王铁牛:可以使用synchronized关键字来同步代码块或方法,也可以使用Lock接口来实现锁机制。

面试官:很好。接下来问你关于JVM的问题,JVM的内存结构分为哪几个部分?

王铁牛:这个嘛……好像是有堆、栈、方法区,还有本地方法栈和程序计数器。

第一轮结束。

面试官:谈谈你对线程池的理解,以及它的优点。

王铁牛:线程池就是预先创建一定数量的线程,当有任务来的时候就从线程池里拿线程去执行任务。优点就是可以提高线程的复用性,减少线程创建和销毁的开销。

面试官:那在使用线程池时,如何合理设置线程池的参数?

王铁牛:嗯……这个得根据具体情况吧,比如任务的类型、数量啥的。

面试官:说说HashMap的底层实现原理。

王铁牛:它是基于数组和链表实现的,当哈希冲突时会将新元素添加到链表中。

第二轮结束。

面试官:讲讲Spring框架中IoC和AOP的概念。

王铁牛:IoC就是控制反转,把对象的创建和依赖注入交给Spring容器;AOP就是面向切面编程,在不修改原有代码的基础上增强功能。

面试官:那Spring Boot有哪些优势?

王铁牛:它很容易上手,能快速搭建项目,还集成了很多常用的功能。

面试官:MyBatis的缓存机制了解吗?

王铁牛:好像有一级缓存和二级缓存,一级缓存是会话级别的,二级缓存是namespace级别的。

第三轮结束。

面试结束,面试官表示会让王铁牛回家等通知。

答案:

  1. 面向对象三大特性
    • 封装:将数据和操作数据的方法封装在一起,对外提供统一的接口,隐藏内部实现细节。这样可以提高数据的安全性和程序的可维护性。例如,在一个类中,将成员变量设为私有,通过公有的getter和setter方法来访问和修改成员变量。
    • 继承:子类继承父类的属性和方法,实现代码的复用。子类可以扩展父类的功能,同时避免重复编写相同的代码。比如,定义一个父类“动物”,子类“猫”和“狗”可以继承“动物”的属性(如颜色、体重等)和方法(如进食方法)。
    • 多态:同一个方法可以根据对象的不同类型而表现出不同的行为。多态有两种表现形式:编译时多态(方法重载)和运行时多态(方法重写)。方法重载是指在同一个类中定义多个同名但参数列表不同的方法;方法重写是指子类重新实现父类中已有的方法。例如,定义一个父类“图形”,有一个计算面积的方法,子类“圆形”和“矩形”重写这个方法,根据各自的形状计算面积,当调用这个计算面积的方法时,根据对象是“圆形”还是“矩形”表现出不同的行为。
  2. 多线程确保线程安全的方法
    • synchronized关键字
      • 同步代码块:格式为synchronized(对象),括号里的对象称为锁对象。当一个线程进入同步代码块时,会先获取锁对象,如果锁对象被其他线程占用,该线程会等待,直到锁被释放。例如:
public class SynchronizedExample {
    private Object lock = new Object();
    public void method() {
        synchronized(lock) {
            // 同步代码
        }
    }
}
 - **同步方法**:在方法声明前加上`synchronized`关键字,该方法就成为同步方法。同步方法的锁对象是当前对象(this)。例如:
public class SynchronizedMethodExample {
    public synchronized void method() {
        // 同步代码
    }
}
  • Lock接口
    • ReentrantLock:可重入锁,它提供了比synchronized更灵活的锁控制。例如:
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
    private ReentrantLock lock = new ReentrantLock();
    public void method() {
        lock.lock();
        try {
            // 同步代码
        } finally {
            lock.unlock();
        }
    }
}
  1. JVM内存结构
    • :是JVM中最大的一块内存区域,用于存放对象实例。所有对象的创建都在堆中进行。堆可以分为新生代、老年代和永久代(Java 8后为元空间)。新生代又分为Eden区和两个Survivor区。当对象在Eden区创建后,如果经过一次Minor GC(新生代垃圾回收)后仍然存活,会被移动到Survivor区。如果在Survivor区经过多次GC后仍然存活,会被移动到老年代。
    • :每个线程都有自己独立的栈空间,用于存储局部变量、方法调用等。栈中的数据是线程私有的,并且随着方法的调用而创建,方法执行结束后销毁。
    • 方法区:存储类信息、常量、静态变量等。在Java 8之前,方法区也被称为永久代,它是一块固定大小的内存区域,容易出现内存溢出问题。Java 8后,方法区被元空间取代,元空间使用的是本地内存,大小不受限制。
    • 本地方法栈:与栈类似,用于执行本地方法(用C或C++实现的方法)。
    • 程序计数器:记录当前线程正在执行的字节码指令的地址,是线程私有的,也是JVM中唯一不会出现OutOfMemoryError的内存区域。
  2. 线程池
    • 理解:预先创建一定数量的线程,当有任务到来时,从线程池中获取线程去执行任务,任务执行完毕后线程不会销毁,而是放回线程池供下次使用。这样可以提高线程的复用性,减少线程创建和销毁的开销,提高系统的性能和响应速度。
    • 优点
      • 提高性能:避免频繁创建和销毁线程带来的开销,线程可以复用,执行任务更快。
      • 便于管理:可以统一管理线程的创建、销毁和复用,方便对线程资源进行控制。
      • 提高稳定性:线程池中的线程数量可以根据系统负载进行调整,避免因线程过多导致系统资源耗尽或因线程过少导致任务积压。
  3. 合理设置线程池参数
    • corePoolSize:核心线程数,当提交的任务数小于corePoolSize时,线程池会创建新线程来执行任务。
    • maximumPoolSize:最大线程数,当提交的任务数大于corePoolSize且任务队列已满时,会创建新线程来执行任务,直到线程数达到maximumPoolSize。
    • keepAliveTime:线程池中的线程在空闲时的存活时间,当线程空闲时间超过keepAliveTime时,线程会被销毁,除非线程数小于corePoolSize。
    • unit:keepAliveTime的时间单位。
    • BlockingQueue:任务队列,用于存放提交的任务。常用的任务队列有ArrayBlockingQueue(有界队列)、LinkedBlockingQueue(无界队列)等。当任务队列已满且线程数达到maximumPoolSize时,会根据拒绝策略来处理新提交的任务。
  4. HashMap底层实现原理
    • 基于数组和链表:HashMap内部有一个数组,当插入一个键值对时,会通过计算键的哈希值,然后将键值对存储在数组的相应位置。如果哈希值相同,就会将新的键值对添加到链表中,形成链表结构。在Java 8后,如果链表长度超过8且数组长度大于64,链表会转换为红黑树,以提高查询效率。
    • 哈希算法:通过对键的哈希码进行扰动函数处理,使哈希值更加均匀地分布在数组中,减少哈希冲突的发生。例如:
static final int hash(Object key) {
    int h;
    return (key == null)? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  1. Spring框架中IoC和AOP的概念
    • IoC(控制反转):把对象的创建和依赖注入交给Spring容器。传统方式下,对象之间的依赖关系由对象自身负责创建和维护;而在IoC模式下,对象只需要声明依赖,由Spring容器来创建和注入这些依赖对象。这样可以降低对象之间的耦合度,提高代码的可维护性和可测试性。例如,一个类需要依赖另一个类来完成某项功能,在IoC中,只需要在类中声明依赖,Spring容器会自动创建并注入这个依赖对象。
    • AOP(面向切面编程):在不修改原有代码的基础上增强功能。它将横切关注点(如日志记录、事务管理等)与业务逻辑分离,通过动态代理等技术,在程序运行时将横切关注点织入到业务逻辑中。比如,在方法执行前后添加日志记录,或者在业务逻辑中加入事务管理功能,都可以通过AOP来实现,而不需要在每个业务方法中重复编写这些代码。
  2. Spring Boot的优势
    • 容易上手:提供了自动配置功能,能够根据项目引入的依赖自动配置相关的组件和属性,大大简化了项目的搭建过程。例如,引入Spring Data JPA依赖后,Spring Boot会自动配置好数据库连接、JPA相关的配置等,开发者只需要编写少量的代码就可以实现数据库的访问。
    • 快速搭建项目:基于Spring框架构建,继承了Spring的优秀特性,同时提供了更简洁的项目结构和开发方式。可以使用Spring Initializr快速生成一个Spring Boot项目的基本框架,包含了项目所需的基本依赖和配置文件。
    • 集成常用功能:内置了Tomcat等服务器,不需要再手动配置服务器环境。同时集成了各种常用的功能模块,如安全模块、数据访问模块、消息队列模块等,可以方便地在项目中使用这些功能,而不需要自己去集成和配置。
  3. MyBatis的缓存机制
    • 一级缓存:是会话级别的缓存,作用域是同一个SqlSession。在同一个SqlSession中,执行相同的查询语句时,会先从一级缓存中查找,如果有则直接返回结果,不会再去数据库查询。例如:
SqlSession session = sqlSessionFactory.openSession();
User user1 = session.selectOne("selectUser", 1);
User user2 = session.selectOne("selectUser", 1);
// user1和user2会从一级缓存中获取相同的结果
  • 二级缓存:是namespace级别的缓存,作用域是同一个Mapper的namespace。当多个SqlSession执行相同的查询语句时,如果开启了二级缓存,会先从二级缓存中查找。二级缓存需要在MyBatis的配置文件中进行配置,并且Mapper对应的Java接口需要实现Cache接口。例如:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
  • 一级缓存的生命周期是SqlSession,当SqlSession关闭时,一级缓存会被清空;二级缓存的生命周期更长,直到应用程序结束或手动清空缓存。