其他面试问答
概述
本文收录了不属于核心八股文但面试中可能涉及的杂项问题,按主题分类整理。
一、编码实践
Q: CodeReview 都会关注哪些问题?
Code Review 通常关注以下几个方面:
- 代码质量:可读性、可维护性、可扩展性、健壮性。是否符合编码规范、是否有冗余代码、数据结构和算法是否恰当
- 易于理解:代码是否清晰易懂、是否有注释,方便其他开发人员了解实现细节
- 潜在错误:空指针异常、内存泄漏、分支是否完整等
- 代码逻辑:是否正确实现了预期的业务逻辑,边界条件处理是否恰当
- 安全性:SQL 注入、XSS 攻击等安全漏洞,是否使用了加密和防护措施
- 性能:循环嵌套过多、频繁数据库读写等,是否使用了合适的缓存策略
- 注释和文档:是否有充分的注释,是否符合命名规范和文档规范
Q: DO、DTO、VO 都是干什么的?
DO、DTO、VO 是三个常见的 Java 数据对象,在不同场景下有不同用途:
| 对象 | 全称 | 用途 | 对应层 |
|---|---|---|---|
| DO | Domain Object | 领域/实体对象,与数据库表一一对应 | 数据层 |
| DTO | Data Transfer Object | 数据传输对象,用于不同层之间传输数据 | 服务层 |
| VO | View Object | 视图对象,用于前端页面显示的数据 | 展示层 |
示例: 假设有一个 User 实体类,包含 id、username、password:
// DO - 对应数据库记录,包含所有字段
public class UserDO {
private Long id;
private String username;
private String password;
}
// DTO - 传输时去掉敏感字段
public class UserDTO {
private Long id;
private String username;
}
// VO - 前端显示只需要用户名
public class UserVO {
private String username;
}通过分层使用不同对象,可以避免将不必要的数据(如密码)传输到前端,提高安全性和性能。
二、JDK 工具命令
面试中常问的 JDK 自带工具,主要用于线上问题排查。
Q: jps 命令的作用是什么?
jps(Java Virtual Machine Process Status Tool)是 JDK 自带的命令,用于显示当前所有 Java 进程的 pid。
原理: Java 程序启动后会在 java.io.tmpdir 指定的临时目录下(Linux 中为 /tmp/hsperfdata_{userName}/)生成以 pid 为名的文件。jps 就是列出该目录下的文件名。
常用参数:
| 参数 | 作用 |
|---|---|
-q | 只显示 pid,不显示类名 |
-m | 输出传递给 main 方法的参数 |
-l | 输出 main class 的完整包名或 jar 文件路径 |
-v | 输出传递给 JVM 的参数 |
注意:jps 似乎只能显示当前用户的 Java 进程,其他用户的需要用
ps命令。
Q: jstack 命令的作用是什么?
jstack 用于生成 Java 虚拟机当前时刻的线程快照(线程 dump)。
用途: 定位线程长时间停顿的原因,如死锁、死循环、等待外部资源等。通过查看各个线程的调用堆栈,可以知道线程在做什么或等待什么资源。
常用参数:
| 参数 | 作用 |
|---|---|
-F | 进程无响应时强制打印栈信息 |
-l | 打印关于锁的附加信息 |
-m | 打印 Java 和 native C/C++ 框架的所有栈信息 |
关键信息解读:
"main" prio=10 tid=0x00007f197800a000 nid=0x7462 runnable [0x00007f197f7e1000]
java.lang.Thread.State: RUNNABLE
at javaCommand.JStackDemo1.main(JStackDemo1.java:7)- 线程状态:RUNNABLE / WAITING / TIMED_WAITING / BLOCKED
- locked:线程当前锁住的资源
- waiting on:线程当前等待的资源
注意:执行 jstack 时 JVM 会暂停所有线程,需要进入安全点才行。
Q: jmap 命令的作用是什么?
jmap 用于打印指定 Java 进程的内存细节,生成堆 Dump 文件。程序内存不足或频繁 GC 时,用它来查看对象情况。
常用操作:
| 命令 | 用途 |
|---|---|
jmap -heap <pid> | 查看堆使用情况(GC 算法、堆配置、各代使用率) |
jmap -histo <pid> | 查看堆中对象数量和大小 |
jmap -histo:live <pid> | 先触发 GC 再统计存活对象 |
jmap -dump:format=b,file=heap.bin <pid> | 导出堆 Dump 文件 |
注意:执行 jmap 时 JVM 会暂停所有线程,生产环境慎用。
Q: jhat 有什么用,如何用它分析堆 dump?
jhat(Java Heap Analysis Tool)用于分析 jmap 生成的堆 Dump 文件。
使用流程:
# 1. 生成 dump 文件
jmap -dump:format=b,file=heapDump <pid>
# 2. 启动 jhat 分析
jhat heapDump
# 启动 HTTP 服务器在端口 7000
# 3. 浏览器访问 http://localhost:7000/重点关注:
- Show instance counts for all classes (excluding platform) -- 查看非平台类的对象数量
- Show heap histogram -- 以树状图展示堆情况
排查时需结合代码,观察是否有大量应该被回收的对象一直被引用,或者有占用内存特别大的对象无法被回收。
Q: jstat 命令的作用是什么?
jstat(JVM Statistics Monitoring Tool)用于监控虚拟机各种运行状态信息(类装载、内存、GC、JIT 编译等)。在没有 GUI 的服务器上是运行期定位性能问题的首选工具。
命令格式:
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]常用 option:
| option | 作用 |
|---|---|
-class | 显示加载 class 的数量及所占空间 |
-gc | 显示 GC 信息、次数、时间 |
-gcutil | 统计 GC 信息(百分比) |
-gccapacity | 显示各代对象的使用和占用大小 |
-gcnew | 年轻代对象信息 |
-gcold | 老年代对象信息 |
-compiler | 显示 JIT 编译数量 |
示例: 每 250ms 查询一次进程 5828 的 GC 状况,共查询 5 次:
jstat -gc 5828 250 5Q: javap 命令的作用是什么?
javap 是 JDK 自带的反编译工具,可以查看 Java 编译器生成的字节码。
常用参数:
| 参数 | 作用 |
|---|---|
javap <class> | 输出类的 public/protected 域和所有方法 |
javap -c <class> | 输出反汇编后的字节码指令 |
javap -c -v <class> | 输出字节码指令 + 常量池信息 |
一般用 javap 的场景不多,主要在需要分析字节码细节时使用。例如分析 synchronized 的原理时,可以发现底层依赖 ACC_SYNCHRONIZED 标记和 monitorenter/monitorexit 两个指令实现同步。
Q: 什么是 Java Dump,如何获取?
Dump 是 Java 虚拟机的运行时快照,将运行时状态和信息保存到文件。
| 类型 | 内容 | 格式 |
|---|---|---|
| 线程 Dump | 所有线程的运行状态 | 纯文本 |
| 堆 Dump | 线程 Dump + 所有堆对象的状态 | 二进制 |
获取方式:
- JVM 参数自动生成:
-XX:+HeapDumpOnOutOfMemoryError(OOM 时自动生成堆 Dump) - 图形化工具:JDK 自带的 Java VisualVM
- 命令行:
jstack(线程 Dump)、jmap(堆 Dump)
用途: 补足传统 Bug 分析手段的不足,主要用于多线程并发问题和内存泄漏问题的排查。
三、架构与设计
Q: SaaS 系统中,多租户如何实现?
多租户是 SaaS 系统的基本功能,核心是数据隔离和资源共享的平衡。
| 方案 | 隔离性 | 扩展性 | 成本 | 适用场景 |
|---|---|---|---|---|
| 共享数据库 + tenant_id 逻辑隔离 | 低 | 低 | 低 | 小型应用,快速试错 |
| 共享数据库 + 独立分区/分表 | 中 | 中 | 中 | 中等规模,需一定隔离性 |
| 独立数据库 | 高 | 高 | 高 | 高安全性需求(金融、医疗) |
| 混合模式 | 灵活 | 高 | 中高 | 租户规模差异大 |
最常见方案: 共享数据库 + tenant_id 逻辑隔离。每条记录包含 tenant_id,应用层查询时自动附加 WHERE tenant_id = ?。简单但有数据泄露风险和资源竞争问题。
进阶方案: 根据租户规模动态选择 -- 小租户用共享数据库,大客户用独立分区,高安全性需求的用独立部署。
Q: 什么是流程引擎?有什么优缺点?
流程引擎是用于自动化、管理和执行业务流程的软件系统,将业务逻辑抽象为可定义的流程模型(如审批流、订单处理等),通常基于 BPMN 2.0 标准。
优点:
- 可视化:非技术人员也能看懂,甚至参与设计
- 标准化:避免在代码中硬编码流程逻辑
- 可维护性:改动局部(增删节点或修改节点代码),全局影响可控
- 灵活性:支持分支、循环、子流程、异步等
缺点:
- 简单逻辑反而更重:节点代码不连续,要去配置文件找下一个节点,不如硬编码直观
- 不适合频繁变动的场景:每个新场景加一个流程,导致流程爆炸,失去复用性
主流框架:
| 框架 | 特点 | 适用场景 |
|---|---|---|
| Activiti | 轻量级,支持 BPMN 2.0,Spring 集成友好 | 中小型项目 |
| Camunda | 基于 Activiti 分支,强化监控工具 | 企业级复杂流程 |
| Flowable | Activiti 的另一分支,性能更优 | 中大型项目 |
| jBPM | 与 Drools 规则引擎深度集成 | 金融、保险等需动态规则的领域 |
Q: 什么是状态机?能描述一下实现原理吗?
状态机是一种抽象计算模型,包含有限个状态和转换规则,用于描述系统在不同状态下如何响应事件并进行状态转换。
核心要素:
| 要素 | 说明 | 示例 |
|---|---|---|
| 状态(States) | 系统可能处于的状态 | 已下单、已支付、已发货 |
| 事件(Events) | 触发状态转换的事件 | 下单、支付、发货 |
| 转换(Transitions) | 状态之间的转换规则 | 已下单 + 支付 → 已支付 |
| 动作(Actions) | 转换时执行的操作 | 扣减库存、发送通知 |
为什么需要状态机?
没有状态机控制的话,可以随意修改订单状态(比如从"已下单"直接跳到"已发货"),这显然不对。状态机确保一个"已下单"状态的订单只能通过"支付"事件驱动,加上"支付金额 > 0"等条件,才能转到"已支付"状态。
简单实现:
public class StateMachine {
private State currentState;
public StateMachine() {
currentState = State.PLACED;
}
public void handleEvent(Event event) {
switch (currentState) {
case PLACED:
if (event == Event.PAY) {
currentState = State.PAID;
} else if (event == Event.CANCEL) {
currentState = State.CANCELED;
} else {
throw new StateHandleException("非法状态转换");
}
break;
case PAID:
if (event == Event.SHIP) {
currentState = State.SHIPPED;
} else if (event == Event.REFUND) {
currentState = State.REFUNDED;
} else {
throw new StateHandleException("非法状态转换");
}
break;
// ... 其他状态处理
}
}
}Spring State Machine: Spring 提供了更完善的状态机框架,支持声明式配置、状态监听、守卫条件等功能。
四、计算机基础
Q: Java 中的随机是真随机吗?
程序中使用的基本都是伪随机数。
- 真随机数:通过物理实验(掷骰子、电子噪音、核裂变、量子现象)产生,满足随机性、不可预测性、不可重现性
- 伪随机数:通过确定算法和种子生成。只要算法确定,就是伪随机
有限状态机无法产生真正的随机数。 无论哪种语言,单纯算法生成的都是伪随机数 -- 由确定的函数通过种子产生。
Java 中的随机:
java.util.Random:伪随机,线程安全但高并发下有性能问题java.security.SecureRandom:强随机数发生器,依赖操作系统底层随机事件(键盘时间间隔、鼠标移动等),初始化慢,可能阻塞。仅在需要加密级随机数时使用
Q: 什么是闰秒?
闰秒是为了消除协调世界时(UTC)与地球自转时间(UT)之间偏差而进行的 1 秒调整。因地球自转速度不恒定,1972 年起开始引入。
闰秒对程序的影响:
操作系统处理闰秒的方式不统一:
- 直接显示 23:59:60
- 显示两次 23:59:59
- 把 1 秒平均分到最后一分钟
历史事件:
- 2012 年:Reddit、Mozilla、LinkedIn 等网站宕机
- 2015 年:全球约 2000 个网络中断,美国洲际交易所中止交易 61 分钟
- 2017 年:Cloudflare 服务崩溃
闰秒无法预测且没有规律,每次发生都需要特殊处理。2022 年 Google、微软、Meta 和亚马逊联合呼吁废除闰秒。
五、Vibe Coding 面试
Q: Vibe Coding 该怎么做?
校招中越来越多公司引入 Vibe Coding 环节:给你一个 AI IDE + 题目,在规定时间内完成。
考察什么(不是编码能力):
- 问题分解与架构思维 -- 能否将模糊命题拆解成清晰可执行的步骤
- 与 AI 的高效协作 -- 提示词能力 + 多轮对话推动项目前进
- 代码审查与批判性评估 -- 能否识别 AI 代码中的 Bug、性能瓶颈和安全隐患
- 决策与权衡能力 -- 能否解释设计决策,而不是接受 AI 默认输出
- 系统性调试能力 -- 遇到问题时是否有逻辑地调试而非盲目求助 AI
推荐步骤:
1. 理解问题与规划架构
- 向面试官确认需求(用户是谁、核心痛点、成功标准)
- 让 AI 帮你做问题拆解和架构设计(可以生成设计文档)
2. 与 AI 协作实现
- 分步指令:根据规划分步向 AI 下达清晰指令,不要一个超长 Prompt 解决所有问题
- 实时审查:每段代码都快速阅读,检查逻辑、架构一致性
- 解释决策:主动向面试官说明你的选择和理由
3. 调试、优化与总结
- 系统性调试:"根据报错信息我猜测问题在 X 模块,准备先检查 Y 变量"
- 主动优化:"目前实现可行,但考虑扩展性可以用 Z 模式重构"
- 可以再开一个窗口让 AI 做审查者,帮你分析优化空间
- 总结回顾:花几分钟回顾架构,指出局限性和改进方向
4. 完善文档
- 如果有时间,让 AI 生成 README、架构设计文档
- 额外时间可以让 AI 分析优化空间,生成 TODO 文档
核心原则: Vibe Coding 不考你的编码,而是考你的思考和指挥能力。不要直接把题目复制给 AI 托管。
六、日志分析
Q: 你平常是怎么查看日志和做分析的?
通常使用 ELK 收集并查看日志。但有时需要在服务器上直接操作。
常用日志查看方式:
tail -f 日志文件名-- 滚动输出,适合实时观察less 日志文件名-- 翻页查看,空格向下、Ctrl+B 向上、/搜索、q退出
日志分析常用命令组合:
| 命令 | 用途 |
|---|---|
grep | 查找关键词 |
wc -l | 统计行数 |
sort | 排序 |
uniq -c | 去重并统计次数 |
awk | 文本处理(按列提取、计算等) |
实战示例:
假设日志格式为:[级别] 日期 时间 [线程] - 请求方法 状态码 耗时 URL requestID
统计某个接口一天的请求量:
grep "/yyy.json" http_access.log | wc -l统计状态码 500 的接口平均 QPS:
grep "/yyy.json" access.log | grep ' 500 ' | \
awk '{print substr($0,10,15)}' | uniq -c | \
awk '{sum += $1} END {print sum / NR}'统计某接口耗时 > 500ms 并按耗时排序(去重):
awk '$9>500{print $0}' access.log | \
sort -k9 -n -r | \
awk '!arr[$10]++ {print $9"ms",$10}'grep 常用参数:
grep '/xxx.json' log # 查找包含关键词的行
grep -C10 'NullPointException' log # 显示匹配行前后10行
grep -i "asc" log # 忽略大小写sort 常用参数:
sort -n file # 按数字排序
sort -r file # 降序
sort -k2 file # 按第2列排序
sort -t ',' -k2 file # 以逗号分隔,按第2列排序awk 常用技巧:
awk '{print $1}' file # 输出第1列(默认空格分隔)
awk -F ',' '{print $2}' file # 以逗号分隔,输出第2列
awk '$NF' file # 输出最后一列
awk 'BEGIN{...}{...}END{...}' file # BEGIN 前置处理,END 后置处理awk 功能非常强大,推荐阅读《sed 与 awk》了解更多用法。遇到不会的拿样本数据问 AI,会给出正确的命令和详细解释。
七、新技术概念
Q: 什么是 Web 3.0?
Web 3.0 被称为第三代互联网,核心理念是用户拥有自己的数据。
互联网演进:
| 代际 | 特征 | 代表产品 |
|---|---|---|
| Web 1.0 | 只读(信息展示) | 搜狐、雅虎、中国黄页 |
| Web 2.0 | 读 + 写(用户参与互动和内容生产) | 微信、抖音、Facebook |
| Web 3.0 | 读 + 写 + 拥有(用户拥有自己的数据) | 基于区块链的去中心化应用 |
技术基础: Web 3.0 构建在公有链之上,利用区块链技术实现去中心化。所有参与节点平等,不需要中心处理节点、不需要数字证书。
主要应用: 加密货币、NFT、DeFi(去中心化金融)、DAO(去中心化自治组织)、GameFi、智能合约等。
当前问题:
- 大量应用场景仍然偏投机(炒币、割韭菜)
- 交易慢、资源浪费(共识算法耗费资源)
- 没有中心管理 → 出了问题没人管(转错钱、诈骗等)
开发者参与: DApp(Decentralized Application)开发是主要方向,流行生态包括 Ethereum、Solana 等,涉及共识算法、密码学、分布式存储等技术栈。
附录:JDK 工具实战详解
jmap 输出详解
执行 jmap -heap <pid> 可以看到完整的堆内存配置和使用情况:
Heap Configuration:
MinHeapFreeRatio = 0 // -XX:MinHeapFreeRatio 最小空闲比率
MaxHeapFreeRatio = 100 // -XX:MaxHeapFreeRatio 最大空闲比率
MaxHeapSize = 2082471936 // -XX:MaxHeapSize 堆最大大小
NewSize = 1310720 // -XX:NewSize 新生代默认大小
OldSize = 5439488 // -XX:OldSize 老生代大小
NewRatio = 2 // -XX:NewRatio 新生代/老生代比率
SurvivorRatio = 8 // -XX:SurvivorRatio Eden/Survivor 比值
Heap Usage:
PS Young Generation
Eden Space:
capacity = 33030144 (31.5MB)
used = 1524040 (1.45MB)
free = 31506104 (30.04MB)
4.6% used
From Space:
capacity = 5242880 (5.0MB)
used = 0 (0.0MB)
To Space:
capacity = 5242880 (5.0MB)
used = 0 (0.0MB)
PS Old Generation:
capacity = 86507520 (82.5MB)
used = 0 (0.0MB)执行 jmap -histo <pid> 可以看到每个类的实例数和占用字节:
num #instances #bytes class name
1: 7 1322080 [I
2: 5603 722368 <methodKlass>
3: 34022 544352 java.lang.Integer排查思路: 结合代码观察是否有大量对象未被回收或者有特别大的对象。
jstack 线程状态分析
jstack 输出的线程信息关键字段:
"Thread-0" prio=10 tid=0x... nid=0x... waiting on condition [0x...]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000783e066e0> (a java.lang.ref.Reference$Lock)
- locked <0x0000000783e066e0> (a java.lang.ref.Reference$Lock)线程状态含义:
| 状态 | 含义 |
|---|---|
| RUNNABLE | 正在运行或等待 CPU 分配 |
| WAITING | 无限期等待另一个线程唤醒 |
| TIMED_WAITING | 有限时间的等待 |
| BLOCKED | 等待获取 monitor 锁 |
常见问题排查:
- 死锁:两个线程互相 waiting on 对方 locked 的资源
- 死循环:线程一直 RUNNABLE,CPU 占用高
- 等待外部资源:线程 WAITING,调用栈显示在网络/数据库调用处
jstat 输出字段说明
执行 jstat -gcutil <pid> 输出:
| 字段 | 含义 |
|---|---|
| S0 | Survivor0 使用比例 |
| S1 | Survivor1 使用比例 |
| E | Eden 使用比例 |
| O | Old 使用比例 |
| M | Metaspace 使用比例 |
| YGC | Young GC 次数 |
| YGCT | Young GC 总耗时 |
| FGC | Full GC 次数 |
| FGCT | Full GC 总耗时 |
| GCT | GC 总耗时 |
判断标准:
- FGC 频繁(如每分钟多次)→ 可能有内存泄漏
- FGCT 很长 → 需要调整堆大小或 GC 算法
- O 接近 100% → 老年代快满了,即将 OOM
javap 字节码分析示例
源代码:
public class JavapTest {
public static void main(String[] args) {
String s1 = "Hollis" + "Chuang"; // 编译期优化为一个常量
}
}使用 javap -c -v JavapTest 可以看到常量池中直接有 HollisChuang 字符串,证明编译器做了字符串拼接优化。
常见用途:
- 验证字符串拼接的编译期优化行为
- 分析
synchronized底层实现(monitorenter/monitorexit) - 理解 switch-case 对枚举/字符串的处理方式
- 确认 lambda 表达式的字节码生成方式
附录:状态机 Spring State Machine 示例
@Configuration
@EnableStateMachine
public class OrderStateMachineConfig
extends StateMachineConfigurerAdapter<OrderState, OrderEvent> {
@Override
public void configure(StateMachineStateConfigurer<OrderState, OrderEvent> states)
throws Exception {
states.withStates()
.initial(OrderState.PLACED)
.states(EnumSet.allOf(OrderState.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions)
throws Exception {
transitions
.withExternal()
.source(OrderState.PLACED)
.target(OrderState.PAID)
.event(OrderEvent.PAY)
.and()
.withExternal()
.source(OrderState.PAID)
.target(OrderState.SHIPPED)
.event(OrderEvent.SHIP)
.and()
.withExternal()
.source(OrderState.SHIPPED)
.target(OrderState.CONFIRMED)
.event(OrderEvent.CONFIRM);
}
}使用方式:
@Autowired
private StateMachine<OrderState, OrderEvent> stateMachine;
public void pay() {
stateMachine.sendEvent(OrderEvent.PAY);
System.out.println(stateMachine.getState().getId()); // PAID
}Spring State Machine 提供了守卫条件(Guard)、状态监听(Listener)、持久化等功能,适合复杂业务场景。
附录:日志分析完整实战
场景说明: 假设日志格式如下(各字段用空格分隔):
[INFO] 20231023 13:00:27.049 [exec-1] - 2 GET 200 600 http://xxx.com/abc/xxx.json d7af...
[INFO] 20231023 13:00:27.049 [exec-1] - 2 GET 500 800 http://xxx.com/abc/yyy.json e7af...
[INFO] 20231023 13:00:27.049 [exec-1] - 2 GET 200 810 http://xxx.com/abc/yyy.json e7af...
[INFO] 20231023 13:00:27.050 [exec-1] - 2 GET 500 210 http://xxx.com/abc/yyy.json e7af...
[INFO] 20231023 13:00:27.049 [exec-1] - 2 GET 200 550 http://xxx.com/abc/aaa.json f7af...
[INFO] 20231023 13:00:27.049 [exec-1] - 2 GET 200 50 http://xxx.com/abc/zzz.json f7af...字段位置:$1=级别, $2=日期, $3=时间, ..., $8=状态码, $9=耗时, $10=URL, $11=requestID
问题1:统计 /yyy.json 接口的请求总量
grep "/yyy.json" http_access.log | wc -l
# 结果: 3问题2:统计 /yyy.json 状态码 500 的平均 QPS
grep "/yyy.json" access.log | grep ' 500 ' | \
awk '{print substr($0,10,15)}' | uniq -c | \
awk '{sum += $1} END {print sum / NR}'
# 结果: 2解释:
- grep 筛选接口和状态码
- awk 提取时间戳部分
- uniq -c 按时间去重统计每秒请求数
- awk 计算平均 QPS
问题3:统计耗时 > 500ms 的请求并按耗时排序去重
awk '$9>500{print $0}' access.log | \
sort -k9 -n -r | \
awk '!arr[$10]++ {print $9"ms",$10}'
# 结果:
# 810ms http://xxx.com/abc/yyy.json
# 600ms http://xxx.com/abc/xxx.json
# 550ms http://xxx.com/abc/aaa.json解释:
- awk 过滤第 9 列(耗时)> 500 的行
- sort 按第 9 列数字降序
- awk 使用关联数组按第 10 列(URL)去重,只保留每个 URL 的第一条(最慢的)
uniq 命令详解:
| 参数 | 作用 |
|---|---|
uniq | 去重(相邻重复行合并) |
uniq -c | 去重并显示每行出现次数 |
uniq -d | 只输出重复的行(只显示一次) |
uniq -D | 只输出重复的行(全部显示) |
uniq -u | 只输出不重复的行 |
注意:
uniq只能处理相邻的重复行,所以通常需要先sort再uniq。
附录:jps 实现原理与完整用法
实现原理
Java 程序启动后在临时目录生成 hsperfdata 文件:
$ ls /tmp/hsperfdata_hollis/
2679 # 这就是 Java 进程的 pidjps 命令的本质就是列出 /tmp/hsperfdata_{userName}/ 目录下的文件名,再解析文件获取进程参数信息。
完整使用示例
# 只显示 pid
$ jps -q
2679
11421
# 显示传给 main 方法的参数
$ jps -m
12062 JpsDemo hollis,chuang
# 显示完整包名或 jar 路径
$ jps -l
12356 sun.tools.jps.Jps
2679 /home/hollis/tools/eclipse/plugins/org.eclipse.equinox.launcher_1.3.0.jar
12329 com.JavaCommand.JpsDemo
# 显示传给 JVM 的参数
$ jps -v
2679 launcher.jar -XX:MaxPermSize=256m -Xms40m -Xmx512m
13083 JpsDemo -Dfile.encoding=UTF-8附录:jstack 死锁分析实战
场景1:线程在死循环中
public class JStackDemo1 {
public static void main(String[] args) {
while (true) {
// Do Nothing
}
}
}jstack 输出:
"main" prio=10 tid=0x00007f197800a000 nid=0x7462 runnable [0x00007f197f7e1000]
java.lang.Thread.State: RUNNABLE
at javaCommand.JStackDemo1.main(JStackDemo1.java:7)可以看到线程处于 RUNNABLE 状态,执行在第 7 行(死循环处)。
场景2:线程在等待
public class Thread1 implements Runnable {
@Override
public void run() {
while (true) {
System.out.println(1);
}
}
}对于 Reference Handler 守护线程:
"Reference Handler" daemon prio=10 ...
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000783e066e0> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133)
- locked <0x0000000783e066e0> (a java.lang.ref.Reference$Lock)解读:
- 状态:WAITING
- 锁住了:
<0x0000000783e066e0> - 同时也在等待:
<0x0000000783e066e0>
为什么同时锁住又等待同一资源?因为线程先获得了 Monitor(locked),执行 obj.wait() 后放弃了 Monitor 所有权进入 wait set(waiting on)。
死锁的典型 jstack 输出
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x000000000218f3e8
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x000000000218f4e0
which is held by "Thread-1"jstack 会直接告诉你哪两个线程死锁了,以及各自持有和等待的锁。
附录:jmap 常用排查流程
完整排查流程
# Step 1: 发现 Java 进程内存持续增长
jps -l # 找到目标进程 pid
# Step 2: 查看堆概况
jmap -heap <pid> # 查看各代使用率
# Step 3: 查看对象统计
jmap -histo <pid> | head -20 # 查看实例数 Top20
# Step 4: 导出 dump 文件(会暂停应用,生产慎用)
jmap -dump:format=b,file=heap.bin <pid>
# Step 5: 分析 dump 文件
jhat -port 5000 heap.bin # 启动 web 分析服务
# 或者用 MAT (Memory Analyzer Tool) 分析关键指标判断
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| Old 区持续增长,FGC 频繁 | 内存泄漏 | jmap -histo 对比两次结果 |
| Young 区 GC 频繁 | 短生命周期对象过多 | 检查循环中是否频繁创建对象 |
| Eden 满了但没触发 GC | 大对象直接进入 Old 区 | 检查是否有大数组/大集合 |
| Metaspace 持续增长 | 类加载器泄漏 | 检查动态代理/反射/ClassLoader 使用 |
附录:线上问题排查工具选择
| 场景 | 推荐工具 | 说明 |
|---|---|---|
| 查看 Java 进程列表 | jps | 快速定位目标进程 |
| CPU 100% 排查 | top + jstack | top 找线程,jstack 找调用栈 |
| 内存泄漏排查 | jmap + MAT | dump 后离线分析 |
| GC 问题排查 | jstat | 实时观察 GC 频率和耗时 |
| 死锁排查 | jstack | 直接输出死锁信息 |
| 方法耗时排查 | Arthas trace | 在线诊断工具 |
| 字节码分析 | javap | 验证编译器行为 |
| 线上日志分析 | grep + awk + sort | 无需额外工具 |
| 系统级监控 | ELK / Prometheus + Grafana | 长期监控和告警 |