java面试·基础(1)

老杨|十年大厂Java实战,专注拆解面试核心考点!整理高频问答与落地思路,助力求职通关~


问答1:自我介绍

面试官:先简单做个自我介绍吧。
面试者:面试官你好!我是xxx,有一年Java后端开发经验。近期完成了公司内部审批模块的重构:基于Spring Boot搭建服务,通过MyBatis-Plus操作MySQL数据库,借助慢查询日志定位全表扫描等性能问题;同时为高频访问的审批模板数据引入Redis缓存,采用“查询缓存+定时刷新”策略,重构后接口P99响应时间从800ms+降至90ms,系统稳定性大幅提升。关注到贵司在XX业务领域的布局,恰好是我深耕且感兴趣的方向,希望能加入团队贡献价值,谢谢!
分析思路

  • 考点:表达逻辑、项目真实性、技能栈匹配度(初级岗重点考察主流框架接触度)。
  • 注意:避免流水账式描述,聚焦项目核心技术与成果;应届生可侧重课程设计/实习项目参与度,无关信息(如籍贯、爱好)无需提及,避免分散面试官注意力。

问答2:JVM的内存结构

面试官:说一下你对JVM内存结构的理解?
面试者:JVM内存核心分为线程私有和线程共享两大区域,此外还有堆外的直接内存:

  • 线程私有区域
    1. 程序计数器:JVM最小内存区域,记录当前线程执行的字节码指令位置,线程切换时保证执行连续性,是唯一不会抛出OOM的区域;
    2. 虚拟机栈:方法执行时创建栈帧,存储局部变量表、操作数栈等,递归过深会触发StackOverflowError,动态扩展超限则抛OOM;
    3. 本地方法栈:为Native方法(如System.currentTimeMillis())提供支撑,功能与虚拟机栈类似。
  • 线程共享区域
    1. 堆内存(Heap):JVM最大内存区域,存储对象实例与数组,是GC核心区域,细分为新生代(Eden、Survivor区,复制算法)和老年代(标记-整理算法),80%以上OOM源于堆溢出;
    2. 元空间:存储类结构、常量池、静态变量等,默认使用本地内存,类加载过多会触发元空间OOM。
  • 直接内存(堆外内存):不属于JVM规范内存结构,如NIO的DirectByteBuffer,受物理内存限制,也可能引发OOM(如Netty编程时分配过多直接内存)。

分析思路

  • 考点:内存分区认知、与实际问题(栈溢出/堆OOM/元空间溢出)的结合能力。
  • 注意:程序计数器仅针对Java方法计数(Native方法计数器值为undefined);
  • 代码示例
/**
 * 递归无终止条件触发StackOverflowError
 * 原理:每次递归创建新栈帧,超出虚拟机栈深度限制
 */
public class StackOverflowDemo {
    private static int count = 0;

    public static void recursiveCall() {
        count++;
        recursiveCall();
    }

    public static void main(String[] args) {
        try {
            recursiveCall();
        } catch (StackOverflowError e) {
            System.out.println("递归调用次数:" + count);
            e.printStackTrace();
        }
    }
}

/**
 * 无限创建对象导致堆OOM(JVM参数:-Xms20m -Xmx20m)
 */
public class HeapOOMDemo {
    static class OOMObject {}

    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<>();
        try {
            while (true) list.add(new OOMObject());
        } catch (OutOfMemoryError e) {
            System.out.println("堆内存溢出:" + e.getMessage());
            e.printStackTrace();
        }
    }
}

/**
 * 动态生成大量类触发元空间OOM(JVM参数:-XX:MaxMetaspaceSize=10m)
 * 依赖cglib库:<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version></dependency>
 */
public class MetaspaceOOMDemo {
    static class TargetClass {}

    public static void main(String[] args) {
        int count = 0;
        try {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(TargetClass.class);
            enhancer.setUseCache(false);
            enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -> proxy.invokeSuper(obj, args1));
            while (true) {
                enhancer.create();
                count++;
            }
        } catch (OutOfMemoryError e) {
            System.out.println("生成类数量:" + count + ",元空间溢出:" + e.getMessage());
        }
    }
}

问答3:Java的面向对象特性

面试官:说说你对Java面向对象特性的理解?
面试者:Java面向对象核心是封装、继承、多态,结合抽象类/接口实现灵活设计:

  • 封装:隐藏内部实现,暴露统一接口。如订单模块将状态修改逻辑封装在OrderServiceupdateOrderStatus()方法中,防止状态被随意篡改,降低维护成本;
  • 继承:复用共性逻辑,仅适用于“is-a”关系。如WechatPayAlipay继承抽象Payment类,共性签名验签逻辑放父类,差异化回调处理在子类实现;
  • 多态:一个接口多种实现,契合开闭原则。如营销系统的优惠券、满减、折扣策略,均实现DiscountStrategy接口的calculate()方法,动态注入实现类,避免大量if-else;
  • 抽象类vs接口:抽象类(单继承)侧重代码复用(如BaseEntity封装通用字段),接口(多实现)侧重行为约定(如ExportAble定义导出方法)。

分析思路

  • 考点:三大特性的落地场景、抽象类与接口选型、面向对象设计原则(开闭/复用)。
  • 注意:避免空泛描述,需结合项目实例说明;慎用继承,防止代码耦合;多态需关联Spring IoC等实际技术说明。
  • 代码示例
// 封装:订单状态修改逻辑封装
public class OrderService {
    public void updateOrderStatus(Order order, OrderStatus newStatus) {
        if (order.getStatus() == OrderStatus.COMPLETED) {
            throw new IllegalStateException("已完成订单无法修改状态");
        }
        order.setStatus(newStatus);
    }
}

// 继承:支付抽象类与子类
abstract class Payment {
    public boolean verifySignature(String params, String sign) { return true; }
    public abstract void callback(String notifyData);
}
class WechatPay extends Payment { @Override public void callback(String notifyData) {} }
class Alipay extends Payment { @Override public void callback(String notifyData) {} }

// 多态:优惠策略接口
interface DiscountStrategy { BigDecimal calculate(Order order); }
class CouponDiscount implements DiscountStrategy {
    @Override
    public BigDecimal calculate(Order order) {
        return order.getAmount().subtract(new BigDecimal("10"));
    }
}

// 抽象类+接口:通用实体与导出能力
abstract class BaseEntity { private Long id; private Date createTime; }
interface ExportAble { void exportToExcel(OutputStream os) throws IOException; }
class Order extends BaseEntity implements ExportAble {
    @Override
    public void exportToExcel(OutputStream os) {}
}

问答4:重载和重写的区别

面试官:方法重载和重写有什么区别?
面试者

  • 重载(Overload):同一类内方法名相同,参数列表(个数/类型/顺序)不同,与返回值无关;
  • 重写(Override):子类重写父类方法,方法名、参数列表、返回值需一致(协变返回类型除外),子类访问权限不能更严格。

分析思路

  • 考点:多态表现形式、方法签名规则。
  • 注意:返回值不同不构成重载;重写方法不能抛出比父类更宽泛的异常。
  • 代码示例
// 重载
class Demo {
    public void test(int a) {}
    public void test(String a) {} // 参数类型不同,构成重载
}
// 重写
class Parent { public void say() {} }
class Child extends Parent { @Override public void say() {} }

问答5:构造方法可以重写吗

面试官:什么是构造方法?构造方法可以被重写吗?
面试者

  • 构造方法:与类名相同、无返回值,用于对象初始化;
  • 构造方法不能被重写,子类无法继承父类构造方法,仅可通过super()调用。

分析思路

  • 考点:构造方法特性、继承机制。
  • 注意:父类无无参构造时,子类需显式调用父类有参构造,否则编译报错。
  • 代码示例
class Parent { public Parent(String name) {} }
class Child extends Parent {
    public Child(String name) { super(name); // 必须显式调用 }
}

问答6:静态代码块、构造代码块、局部代码块

面试官:静态代码块、构造代码块、局部代码块有什么区别?
面试者

  • 静态代码块:static{},类加载时执行且仅一次,用于初始化静态资源;
  • 构造代码块:{},创建对象时执行(早于构造方法),用于初始化实例资源;
  • 局部代码块:方法内{},限定变量作用域,执行后立即释放资源。

分析思路

  • 考点:代码块执行顺序、初始化时机。
  • 注意:执行顺序:静态代码块 → 构造代码块 → 构造方法 → 局部代码块。
  • 代码示例
class Demo {
    static { System.out.println("静态代码块"); }
    { System.out.println("构造代码块"); }
    public Demo() { System.out.println("构造方法"); }
    public void test() { { System.out.println("局部代码块"); } }
}

问答7:StringBuilder和StringBuffer

面试官:你了解StringBuffer和StringBuilder的区别吗?在什么场景下应该使用哪一个?
面试者:核心差异仅在于线程安全性,功能完全一致:

  • 线程安全性:StringBuffer方法加synchronized,多线程安全但有同步开销;StringBuilder无同步机制,单线程性能高10%-20%;
  • 适用场景:StringBuffer用于多线程(如日志收集),StringBuilder用于单线程(如方法内字符串拼接、SQL构建)。

分析思路

  • 考点:线程安全与性能的权衡。
  • 注意:StringBuffer的synchronized仅保证单个方法原子性,复合操作仍需外层加锁。
  • 代码示例
// 单线程:StringBuilder(高性能)
public String buildOrderLog(Order order) {
    StringBuilder sb = new StringBuilder();
    sb.append("OrderID:").append(order.getId()).append(",Status:").append(order.getStatus());
    return sb.toString();
}

// 多线程:StringBuffer(加外层同步保证复合操作安全)
public class LogCollector {
    private final StringBuffer buffer = new StringBuffer();
    public void addLog(String log) {
        synchronized (buffer) { buffer.append(log).append("\n"); }
    }
}

问答8:final关键字

面试官:final关键字可以用在哪些地方,分别有什么作用?
面试者

  • 修饰类:类不可继承(如StringMath);
  • 修饰方法:方法不可被重写;
  • 修饰变量:基本类型值不可变;引用类型地址不可变(对象内容可修改)。

分析思路

  • 考点:不可变性设计、类/方法/变量的约束。
  • 注意final List<String> list = new ArrayList<>()可正常add/remove元素,仅限制引用地址不可变。
  • 代码示例
final class Demo {} // 不可继承
class Parent { final void test() {} } // 方法不可重写
final int a = 10; // 基本类型值不可变
final List<String> list = new ArrayList<>();
list.add("a"); // 允许
// list = new ArrayList<>(); // 编译报错

问答9:Java异常的理解

面试官:说说你对Java异常的理解?
面试者

  • 核心定义:异常是程序运行时的非正常情况,Java通过异常机制解耦错误处理与业务逻辑,避免程序崩溃;
  • 异常分类:继承自Throwable,分为Error(JVM严重错误,如OOM,无法处理)和Exception(程序可处理);Exception又分受检异常(编译时必须处理,如IOException)和非受检异常(运行时异常,如NPE);
  • 处理方式:try-catch-finally(捕获已知异常,finally释放资源)、throws/throw(抛出无法处理的异常,手动抛自定义异常);
  • 最佳实践:精准捕获具体异常、杜绝空catch块、自定义业务异常、优先使用try-with-resources释放资源。

分析思路

  • 考点:异常体系、处理方式、工程化最佳实践。
  • 注意:区分ErrorException,try-with-resources简化资源释放。
  • 代码示例
// 自定义业务异常
class BusinessException extends RuntimeException {
    public BusinessException(String message) { super(message); }
}

// try-with-resources自动释放资源
public static String readFile(String path) {
    if (path == null) throw new IllegalArgumentException("路径不能为空");
    try (FileReader reader = new FileReader(path)) {
        char[] buf = new char[1024];
        int len = reader.read(buf);
        return new String(buf, 0, len);
    } catch (IOException e) {
        System.err.println("读取失败:" + e.getMessage());
        throw new BusinessException("文件读取失败,请检查文件是否存在");
    }
}

问答10:反问环节

面试官:好了,我的问题问完了,你有什么问题想问我吗?
面试者:了解到咱们团队在推进XX业务/项目,想请教下当前后端面临的核心技术挑战是什么(如数据一致性、高并发、业务解耦)?如果加入团队,我大概率会优先参与哪块工作?

分析思路

  • 考点:求职关注点、岗位匹配度。
  • 注意:初级岗可问技术栈/导师/开发流程,避免直接问薪资/加班,可委婉询问“工作节奏”。

✨ 持续更新【公众号:Java 架构老羊】Java后端面试干货:高频考点、架构设计、性能调优,欢迎收藏关注~
📌 点赞收藏不迷路,助力面试通关!

未经授权,禁止任何形式的转载、抄袭、洗稿;如需引用,请注明原文出处。

posted @ 2026-03-23 23:51  野白羊  阅读(1)  评论(0)    收藏  举报