【无标题】

社招面经深度解析:Java 核心与系统底层


1. 说出你记得的排序算法

核心回答:
常见的排序算法可以分为两大类:

  • 比较类排序:冒泡排序、快速排序、插入排序、选择排序、归并排序、堆排序、希尔排序。
  • 非比较类排序:计数排序、桶排序、基数排序。

加分项(面试官想听的):
不要只报菜名,要按时间复杂度稳定性来分类记忆:

  • O(n²):冒泡、插入、选择(适合小规模数据)。
  • O(n log n):快排、归并、堆排(大规模数据首选)。
  • O(n):计数、桶、基数(特定场景,如整数范围已知)。
  • 稳定性:归并、插入、冒泡是稳定的;快排、堆排、选择是不稳定的。

2. 快排、冒泡实现思路

冒泡排序 (Bubble Sort)

  • 思路:像水底的气泡一样,每一轮遍历数组,比较相邻的两个元素,如果顺序错误就交换。每一轮结束后,最大的元素会“浮”到数组末尾。
  • 代码逻辑:双重循环,外层控制轮数,内层进行两两比较交换。
  • 特点:最简单,但效率低。优化点:如果某一轮没有发生任何交换,说明已经有序,可以提前退出。

快速排序 (Quick Sort)

  • 思路:分治法(Divide and Conquer)。
    1. 选基准 (Pivot):从数组中挑出一个元素作为基准。
    2. 分区 (Partition):重新排序数列,所有比基准值小的摆放在基准前面,所有比基准值大的摆在基准后面。
    3. 递归:递归地把小于基准值元素的子数列和大于基准值元素的子数列排序。
  • 关键点Partition 函数的实现是核心。
  • 优化:随机选择 Pivot 可以避免在最坏情况(已排序数组)下退化为 O(n²)。

3. TCP 为什么要四次挥手?

参考视频
https://www.bilibili.com/video/BV1MUi5BCEjz/?spm_id_from=333.337.search-card.all.click&vd_source=cd2f9979518516c6f9eeff53f8d9f13c
在这里插入图片描述

在这里插入图片描述

核心原因:TCP 是全双工通信。

  • 三次握手建立连接时,服务端在收到 SYN 后,可以将 ACK 和 SYN 合并发送,因为此时服务端没有数据要发给客户端。
  • 四次挥手断开连接时:
    1. 客户端发送 FIN,表示“我发完了”。
    2. 服务端收到 FIN,回复 ACK。注意:此时服务端可能还有数据没发完,所以不能立刻发 FIN,只能先回 ACK。
    3. 等服务端数据发完后,服务端再发送 FIN,表示“我也发完了”。
    4. 客户端收到 FIN,回复 ACK,进入 TIME_WAIT 状态。

为什么不能合并? 因为服务端接收到关闭请求时,应用层可能还有业务数据在处理,必须等数据处理完才能关闭发送通道。


4. 什么是进程?和线程的区别?

  • 进程 (Process):是资源分配的最小单位。每个进程都有独立的内存空间(代码段、数据段、堆栈等)。进程之间互不干扰,安全性高。
  • 线程 (Thread):是 CPU 调度的最小单位。线程共享进程的内存空间(堆、方法区),但拥有独立的栈空间。

区别对比表:

维度进程线程
资源拥有独立内存空间共享进程资源
开销创建/切换开销大创建/切换开销小
通信复杂(IPC)简单(共享变量)
稳定性一个进程崩溃不影响其他进程一个线程崩溃可能导致整个进程崩溃

5. 并发进程会有什么问题?

  1. 竞态条件 (Race Condition):多个进程/线程同时访问共享资源,且执行顺序依赖时序,导致结果不可预测。
  2. 死锁 (Deadlock):两个或多个进程互相持有对方需要的资源,并等待对方释放,导致永久阻塞。
  3. 饥饿 (Starvation):某个进程长期得不到所需的资源,无法执行。
  4. 活锁 (Livelock):进程虽然没有阻塞,但一直在重复相同的操作,无法推进任务。

6. 进程的通信方式有哪些?

Linux 下常见的 IPC (Inter-Process Communication) 方式:

  1. 管道 (Pipe):半双工,数据只能单向流动。父子进程间常用。
  2. 命名管道 (FIFO):允许无亲缘关系的进程通信。
  3. 消息队列 (Message Queue):克服了管道只能承载无格式字节流的缺点,可以实现消息的随机查询。
  4. 共享内存 (Shared Memory)最快的 IPC 方式。映射一段能被其他进程访问的内存。但需要配合信号量解决同步问题。
  5. 信号量 (Semaphore):主要用于进程间的同步和互斥,不传输数据,只传输信号。
  6. 信号 (Signal):用于通知接收进程某个事件已经发生(如 kill -9)。
  7. 套接字 (Socket):可用于不同机器间的进程通信。

7. 什么是虚拟内存?

虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。

核心机制:通过 MMU (内存管理单元) 和页表,将虚拟地址映射到物理地址。


8. 除了内存扩展,虚拟内存其他的作用?

面试官问这个,是想考察你对操作系统设计哲学的理解。除了让程序能使用比物理内存更大的空间,虚拟内存还有以下关键作用:

  1. 内存保护与隔离:每个进程都有独立的虚拟地址空间,进程 A 无法直接访问进程 B 的内存,提高了系统的安全性和稳定性。
  2. 简化内存管理:程序员不需要关心物理内存的碎片化问题,只需要操作连续的虚拟地址。操作系统负责后台的页面置换。
  3. 按需加载 (Lazy Loading):程序启动时,不需要将整个可执行文件全部加载到内存,只需加载当前需要的部分,加快了启动速度。
  4. 写时复制 (Copy-on-Write):在 fork() 创建子进程时,父子进程共享物理页面,只有当其中一方尝试写入时,才复制一份新的物理页面。这极大地提高了 fork 的效率。

9. Linux 常见的命令背一下

建议按功能分类记忆,不要死记硬背:

  • 文件操作ls, cd, pwd, mkdir, rm, cp, mv, touch, chmod, chown
  • 文本查看/处理cat, less, head, tail, grep, awk, sed, wc
  • 进程管理ps, top, htop, kill, pkill, jobs, bg, fg
  • 网络相关netstat, ss, curl, wget, ping, telnet, ifconfig/ip addr
  • 系统状态df (磁盘), du (目录大小), free (内存), uptime (负载)

10. Linux 怎么实现批量替换文本?

最常用的是 sed 命令。

  • 单个文件替换

    sed -i 's/old_string/new_string/g' filename.txt
    
    • -i: 直接修改原文件。
    • s: substitute (替换)。
    • g: global (全局替换,否则只换每行第一个)。
  • 批量替换目录下所有文件

    find . -name "*.java" -exec sed -i 's/old_string/new_string/g' {} \;
    

    或者结合 xargs

    grep -rl 'old_string' . | xargs sed -i 's/old_string/new_string/g'
    

11. 现在有一个程序部署在 Linux 上运行很慢,说一下排查思路

这是一个经典的全链路排查问题,思路要由外向内,由系统到应用:

  1. 看系统整体负载

    • 使用 tophtop 查看 CPU、内存使用率。
    • 使用 vmstat 查看是否有大量的上下文切换或 IO 等待。
    • 使用 dmesg 查看内核日志是否有 OOM 或硬件错误。
  2. 定位具体进程/线程

    • 如果 CPU 高:top -H -p <pid> 查看哪个线程占用 CPU 高。
    • 如果 IO 高:使用 iotopiostat 查看是哪个进程在疯狂读写磁盘。
    • 如果网络慢:使用 sar -n DEViftop 查看网络流量。
  3. 深入应用内部 (Java 为例)

    • CPU 高:拿到高 CPU 线程 ID,转换为十六进制,使用 jstack <pid> | grep <hex_tid> -A 20 查看堆栈,定位到具体代码行。
    • 内存泄漏/GC 频繁:使用 jstat -gcutil <pid> 1000 观察 GC 频率和耗时。使用 jmap dump 堆内存分析。
    • 死锁/等待:直接使用 jstack <pid> 查看是否有 DEADLOCK 字样或大量 BLOCKED 线程。
  4. 外部依赖

    • 检查数据库慢查询日志。
    • 检查下游接口响应时间。

12. Linux 有什么检测死锁的工具?

  • Java 应用

    • jstack <pid>:最直接,输出中会明确提示 Found one Java-level deadlock
    • jcmd <pid> Thread.print:效果同 jstack。
    • VisualVM / JConsole:图形化工具,有专门的死锁检测按钮。
  • C/C++ 或其他系统级死锁

    • gdb:附加到进程,查看线程堆栈。
    • perf:生成火焰图,观察线程状态。
    • strace:跟踪系统调用,看是否卡在 futexlock 相关调用上。

13. 对 volatile 的理解

volatile 是 Java 提供的轻量级同步机制,它保证了两个特性:

  1. 可见性 (Visibility):当一个线程修改了 volatile 变量的值,新值会立即刷新到主内存,并使其他线程工作内存中的缓存失效,其他线程读取时必须从主内存重新加载。
  2. 禁止指令重排序 (Ordering):通过插入内存屏障 (Memory Barrier),防止编译器和处理器对指令进行重排序,保证有序性。

注意volatile 不保证原子性。例如 i++ 操作,即使是 volatile,在多线程下也是不安全的。


14. 对 synchronized 的理解

synchronized 是 Java 关键字,基于 JVM 的 Monitor 对象实现,是一种悲观锁

  • 用法:修饰实例方法(锁当前实例)、静态方法(锁 Class 对象)、代码块(锁指定对象)。
  • 底层原理
    • JDK 1.6 之前:重量级锁,直接依赖操作系统的 Mutex Lock,用户态和内核态切换开销大。
    • JDK 1.6 之后:引入了锁升级机制,性能大幅提升。
      1. 偏向锁:只有一个线程访问时,标记线程 ID,避免 CAS。
      2. 轻量级锁:多个线程交替执行,使用 CAS 旋转锁,避免挂起。
      3. 重量级锁:竞争激烈时,膨胀为 OS 互斥锁,线程挂起。

— are 15. synchronized 在不同方法上的锁粒度?两个线程并发访问不同的方法锁会发生什么?

锁粒度取决于锁的对象是谁:

  1. 修饰实例方法:锁的是当前实例对象 (this)

    • 如果两个线程访问同一个对象的两个不同的 synchronized 方法,它们会互斥,因为锁的是同一个对象。
    • 如果两个线程访问不同对象的 synchronized 方法,它们不会互斥
  2. 修饰静态方法:锁的是类的 Class 对象

    • 无论创建多少个实例,所有线程访问该类的静态 synchronized 方法都会互斥。
  3. 修饰代码块 synchronized(obj):锁的是括号里的 obj 对象。

结论:两个线程并发访问同一个实例的不同 synchronized 方法,会发生竞争,串行执行。因为它们争夺的是同一把锁(实例锁)。


16. Java 是只有值传递吗?为什么?

是的,Java 只有值传递。

  • 基本数据类型:传递的是值的副本。方法内修改不影响原变量。
  • 引用数据类型:传递的是引用的副本(即对象在堆内存地址的拷贝)。
    • 你可以在方法内通过这个引用副本修改对象内部的属性(因为指向同一个堆对象)。
    • 但你不能在方法内改变原引用指向的对象(例如 obj = new Object()),因为这只是改变了副本的指向,原引用不变。

为什么有人觉得是引用传递? 因为他们看到了对象属性的修改。但本质上,传递进来的“地址”本身是按值拷贝的。


17. Hashmap 为什么使用红黑树?

在 JDK 1.8 中,HashMap 引入了红黑树优化。

  1. 背景:HashMap 底层是数组 + 链表。当 Hash 冲突严重时,链表会变得很长,查询效率从 O(1) 退化为 O(n)。
  2. 为什么选红黑树?
    • 当链表长度超过阈值(默认 8)且数组长度超过 64 时,链表转为红黑树。
    • 红黑树是一种自平衡二叉查找树,查询时间复杂度稳定在 O(log n)
    • 相比 AVL 树(严格平衡),红黑树的插入和删除旋转次数更少,综合性能更适合读多写少的场景。
  3. 为什么不一直用红黑树?
    • 红黑树节点占用空间比链表节点大(需要存储父节点、颜色、左右子节点指针)。
    • 在冲突较少时,链表的局部性更好,且 O(n) 在 n 很小时(<8)并不比 O(log n) 慢多少,反而更省内存。

总结:红黑树是为了防御极端 Hash 冲突情况,保证最坏情况下的查询性能,是一种空间换时间的策略。


希望这份解析能帮助你更好地准备面试!如果有具体的代码实现需求或更深层次的原理探讨,欢迎随时交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值