面灵AI→

深轻科技 Java后端 二面(20 分钟速通)

轮次
二面
时间
2026-09
来源
牛客网

《面试题目》

  1. 自我介绍。
  2. Java 里想快速调用一个方法,你会怎么做?
  3. 反射具体是怎么实现的?
  4. Java 里的元空间了解吗?
  5. 除了 Java 还会其他语言吗?
  6. 平衡二叉树知不知道?能不能讲讲?
  7. 平衡二叉树怎么调整平衡的?
  8. 手撕:给你一个返回值是 Boolean 的函数、参数是一个 int 整数,不让用 char、不让用数组,怎么判断这个数是不是回文数?

《参考解析》

  1. 「想快速调用一个方法」问的是什么:这题问的是调用方式的选型,不是在问语法。可选项有四种:直接调用最快但要求编译期就知道调谁;反射(Class.getMethod + invoke)最灵活但每次调用要装箱参数、做访问检查、而且没法内联;MethodHandle 介于两者之间,JIT 能把它内联掉,是 JDK 7 之后官方推荐的「动态但可优化」的路子;再往下就是运行期生成字节码,ASM、ByteBuddy、CGLIB 和 JDK 动态代理都算这一类,一次生成、之后按普通方法调,代价是首次生成开销和类加载。所以如果「快速」指的是性能,答案是缓存查找结果 + 尽量用 MethodHandle 或生成字节码;如果指的是开发效率,Spring 的 ReflectionUtils、BeanUtils 这种封装就是现成答案。
  2. 反射到底怎么实现的:类加载的时候,JVM 会在方法区(JDK 8 之后是元空间)建好这个类的运行时数据结构,Class 对象就是访问它的入口。getDeclaredMethods 返回的 Method 对象内部持有一个 MethodAccessor:前若干次调用走 NativeMethodAccessorImpl,也就是 JNI 进 JVM 内部执行;调用次数超过阈值(默认 15)之后,会由 MethodAccessorGenerator 生成一个 GeneratedMethodAccessorXXX 类,后续直接走这段生成的字节码。这个机制叫 inflation,它解释了为什么反射「第一次慢、预热后没那么慢」。但即使预热完,参数还是要装进 Object[]、基本类型要装箱,而且调用点无法内联,所以量级上仍比直接调用慢——具体倍数取决于 JIT 和调用形态,别背死数字。
  3. 元空间(Metaspace):JDK 8 用元空间取代了永久代,最大的变化是从堆里搬到了本地内存,所以它不受 -Xmx 约束,默认也没有上限,能一直吃到本机物理内存,生产上一般用 -XX:MaxMetaspaceSize 兜底。它存的是类的元数据、运行时常量池、方法字节码这些。元空间满了一样会 OOM,报 java.lang.OutOfMemoryError: Metaspace,常见原因是动态生成的类太多:CGLIB 代理、Groovy 或 JS 脚本引擎、以及热部署场景下反复加载同一个类而旧的 ClassLoader 没被释放。排查的时候堆 dump 看不出问题,得看已加载类数量(jstat -class)和 NMT。
  4. 平衡二叉树与失衡调整:AVL 的定义是任意节点左右子树的高度差(平衡因子)绝对值不超过 1,插入或删除后一旦超过就要旋转,从离插入点最近的失衡节点开始处理。四种情形对应四种旋转:LL 型右旋一次,RR 型左旋一次,LR 型先对左孩子左旋再对自己右旋,RL 型先对右孩子右旋再对自己左旋。追问通常会转到「为什么 MySQL 索引用 B+ 树不用平衡二叉树或红黑树」,答案是树高和磁盘 IO 次数:二叉结构每个节点最多两个分叉,千万级数据树高二十多层就是二十多次随机 IO,而 B+ 树一个节点能放几百个 key,树高三四层就够,而且叶子节点串成链表,范围查询不用回根。
  5. 回文数手撕(不用 char、不用数组):思路就是纯数字运算,而且只反转后半段,这样顺带避开了 int 反转溢出的坑。
public boolean isPalindrome(int x) {
    // 负数和「末位是 0 但不是 0 本身」的数一定不是回文
    if (x < 0 || (x % 10 == 0 && x != 0)) {
        return false;
    }
    int rev = 0;
    while (x > rev) {
        rev = rev * 10 + x % 10;
        x /= 10;
    }
    // 偶数位:x == rev;奇数位:中间那位在 rev 的末位,去掉再比
    return x == rev || x == rev / 10;
}

循环条件是 x > rev 而不是 x != 0,这是「只反转一半」的关键:等反转出来的数超过剩下的数,就说明过了一半,此时 rev 的位数最多和 x 相当,不可能溢出。x == rev / 10 处理的是 12321 这种奇数位的情况。如果只反转全部位数,就得用 long 或者提前判溢出,面试官多半会顺着问一句「溢出怎么办」。