# 《深入分析Java Web技术内幕》知识点全解 > AI生成|2026年10月28日|codebuddy|原书:许令波《深入分析Java Web技术内幕》,电子工业出版社,2012-09-01 > **我的阅读**:进度 100%|累计 10小时7分钟|开始 2017-10-31|最近 2021-04-24|读完 2020-06-02 **一句话总结**:拆开"从浏览器到 Tomcat 返回页面"链路的每一层黑盒——网络(HTTP/DNS/CDN)、语言(I/O/编码/编译/类加载/JVM)、服务端(Servlet 与容器),让排障时知道该看哪一层。 **适合谁 / 前置知识** - 适合:有 1–2 年 Java 经验、写过 Servlet/JSP、部署过 Tomcat 的后端开发;想从"会用框架"升级到"知道框架底下发生了什么"的人。 - 不适合:零基础学语法者;只想要 Spring Boot 快速上手教程的人(2012 年成书,几乎不涉及 Spring Boot、微服务、容器化)。 - 前置:Java 语法与集合、基本多线程概念、能命令行编译运行 class。 ## 一、全书地图 按"请求走过的顺序"自外向内组织,四层递进: | 层次 | 章节 | 核心问题 | 与其他章关系 | |---|---|---|---| | 网络层 | 第1章 Web请求过程 | 请求如何从浏览器到达服务器 | 全书起点 | | 语言层(数据) | 第2章 Java I/O、第3章 中文编码 | 字节怎么读写、字符怎么变字节 | 横切能力,各层都用 | | 语言层(代码) | 第4章 Javac、第5章 class文件结构 | 源码怎么变字节码、字节码长什么样 | 4 产出 5 | | 运行时层 | 第6章 ClassLoader、第7章 JVM体系结构、第8章 JVM内存管理 | 类怎么加载、字节码怎么执行、内存怎么回收 | 承接第5章,形成闭环 | | 服务端层 | 第9章 Servlet工作原理 | 容器怎么接收请求并调度到你的代码 | 把第1章的请求落到 Java 对象上 | 依赖关系:`1 → 9`(请求落地);`5 → 6 → 7 → 8`(class → 加载 → 执行 → 内存);`2、3` 横切全栈。简介提到 Tomcat/Jetty、Spring、iBatis、Velocity 的原理,但所给目录只到第 9 章,其余对应关系**待核对**。 ## 二、核心知识点 **1. B/S 请求的完整链路** 是什么:URL → 浏览器缓存 → DNS 解析 → TCP 握手 → HTTP 请求 → 服务端处理 → 响应 → 渲染。 为什么重要:任何"慢/打不开",第一步是定位卡在哪一段。 怎么用:DevTools Timing 对照 DNS / TCP / TTFB / Content Download;TTFB 高查服务端,DNS 高查解析。 常见误区:以为"接口快"等于"页面快",忽略 DNS、TLS 与静态资源并发。 **2. HTTP 报文与无状态** 是什么:请求行/状态行 + 头部 + 空行 + 实体;状态码 1xx–5xx;协议本身不保存状态。 为什么重要:框架里的 Request/Response 就是这段文本的结构化映射。 怎么用:`curl -v` 看原始报文;`Cache-Control`/`ETag` 管缓存,`keep-alive` 复用连接,`chunked` 支持流式响应。 常见误区:301 与 302 混用(301 会被长期缓存);把 Cookie 当协议特性(它只是头部字段)。 **3. DNS 解析与 CDN 调度** 是什么:递归查询(本地 DNS 代劳)与迭代查询(根 → 顶级域 → 权威)配合;A/CNAME/MX/NS 记录;CDN 用 CNAME 指向调度系统,按地理位置与运营商返回最近边缘节点 IP。 为什么重要:DNS 是"第一跳",解析慢会拖慢全部请求。 怎么用:`dig +trace` 看解析路径;上线后多地 ping 验证调度是否就近。 常见误区:改完解析就期待立刻生效(受 TTL 约束);把 CDN 当纯加速,忽略回源带宽与命中率。 **4. Java I/O 的装饰器体系** 是什么:`InputStream/OutputStream` 面向字节,`Reader/Writer` 面向字符;装饰器层层包装叠加能力。 为什么重要:包装顺序决定缓冲加在哪层、谁负责解码。 怎么用:`new BufferedReader(new InputStreamReader(new FileInputStream(f), "UTF-8"))`——字节 → 解码 → 缓冲。 常见误区:忘记 close 导致句柄泄漏(用 try-with-resources);在字节流上直接做字符操作。 **5. 适配器模式:字节流到字符流的桥** 是什么:`InputStreamReader` 把 `InputStream` 的字节接口适配成 `Reader` 的字符接口,靠 `StreamDecoder` 解码。 为什么重要:"不兼容接口之间加一层"的标准案例,也是乱码的物理发生地。 怎么用:定义目标接口 → 持有被适配者 → 在方法里转换调用。 常见误区:与装饰器混淆——适配器**改变接口**,装饰器**保持接口、只加行为**。 **6. NIO 的 Channel / Buffer / Selector** 是什么:Channel 双向读写,Buffer 是容器(position/limit/capacity),Selector 一次系统调用轮询多路就绪事件。 为什么重要:I/O 多路复用在 Java 层的落地,Netty 与 Tomcat NIO 连接器的地基。 怎么用:注册 `OP_ACCEPT/OP_READ`,`select()` 返回后遍历就绪集合,避免"一连接一线程";大文件用 `transferTo` 减少拷贝。 常见误区:以为 NIO 必然更快(连接少时更复杂更慢);忘记 `flip()` 就读不到数据;以为 `flush()` 已落盘(多数只刷到内核缓冲)。 **7. 中文编码:char 是 UTF-16,字节才是存储** 是什么:`char` 是 16 位 UTF-16 码元,`String` 内存中是 Unicode 序列;只有 `getBytes(charset)` / `new String(bytes, charset)` 才涉及具体编码。 为什么重要:绝大多数乱码是"用什么编的"与"用什么解的"不一致。 怎么用:全链路锁死 UTF-8——JSP `pageEncoding`、`response.setContentType`、`request.setCharacterEncoding`、JDBC 连接串、Tomcat `URIEncoding`。 常见误区:`new String(bytes)` 吃平台默认值;以为设了 response 编码就能解决 GET 参数乱码(GET 走 URI,要在容器层配)。 **8. Javac 的五个阶段** 是什么:词法分析(Token)→ 语法分析(AST)→ 语义分析(符号表、类型标注、校验)→ 解语法糖(擦除、foreach 展开、装箱)→ 字节码生成。 为什么重要:泛型擦除、内部类持有外部引用、字符串拼接优化,答案都在这里。 怎么用:`javap` 或反编译看解糖结果;理解 `List` 与 `List` 运行时是同一个类。 常见误区:以为泛型信息运行时还能随便取(仅签名属性保留给反射)。 **9. class 文件结构与常量池** 是什么:魔数 `0xCAFEBABE` → 版本号 → 常量池 → 访问标志 → 本类/父类/接口 → 字段表 → 方法表 → 属性表。 为什么重要:常量池是 class 的"符号仓库",运行时常量池、字符串驻留、动态调用都挂在它上面。 怎么用:`javap -v` 逐段读;描述符用 `Ljava/lang/String;` 表达类型。 常见误区:只替换 class 不清 Tomcat 工作目录,加载到旧类;版本不匹配报 `UnsupportedClassVersionError`。 **10. 双亲委派与 Tomcat 的"破坏"** 是什么:`loadClass` 先查已加载 → 委派父加载器 → 父加载不到才 `findClass`;目的是核心类不被篡改、类不重复加载。 为什么重要:这是类隔离与热部署的理论基础。 怎么用:自定义加载器重写 `findClass` 而非 `loadClass`;Tomcat 的 `WebappClassLoader` 对应用自身类优先自加载。 常见误区:以为双亲委派是强制规范(它是约定);`Class.forName` 会初始化类,`loadClass` 默认不。 **11. JVM 运行时数据区与分配策略** 是什么:线程私有——程序计数器、虚拟机栈、本地方法栈;共享——堆、方法区(书中为永久代,Java 8 后改元空间)。 为什么重要:OOM 与 StackOverflow 的区别就来自"哪块区域满了"。 怎么用:对象优先 Eden 分配,大对象直接进老年代,长期存活者晋升;`-Xmx/-Xmn/-XX:SurvivorRatio` 配 `jstat -gc` 观察。 常见误区:把 `-Xmx` 当进程总内存(还有元空间、直接内存、线程栈);堆调大导致单次 Full GC 更久。 **12. GC 算法与分代回收** 是什么:存活判定用可达性分析(GC Roots:栈引用、静态变量、常量、JNI 引用);算法有标记-清除、复制、标记-整理;分代假设"绝大多数对象朝生夕死"。 为什么重要:收集器选错会出现长停顿,直接打到接口 P99。 怎么用:新生代用复制,老年代用标记-整理或并发标记清除;重点盯晋升失败与并发模式失败。 常见误区:把 `System.gc()` 当优化手段;以为堆调小就省内存(只是把 GC 频率拉高)。 **13. Servlet 容器的工作模型** 是什么:容器解析请求 → 匹配 `url-pattern` → 组装 `ServletRequest/Response` → 调 `service()`;Servlet 单实例多线程,`init`/`destroy` 各一次。 为什么重要:线程安全、Filter 顺序、Listener 生命周期全由此推导。 怎么用:`url-pattern` 优先级为精确匹配 > 最长路径 `/foo/*` > 扩展名 `*.do` > 默认 `/`;Filter 用 `chain.doFilter` 串联。 常见误区:在实例字段里存请求数据(并发串数据);以为 `load-on-startup` 只影响启动速度。 ## 三、关键概念速查表 | 概念 | 一句话解释 | 用在哪儿 | |---|---|---| | TTFB | 首字节到达时间 | 判断慢在服务端还是网络 | | CNAME | 域名别名记录 | CDN 接入与调度 | | Buffer 三指针 | position/limit/capacity | NIO 读写状态切换 | | Selector | 单线程轮询多路就绪事件 | 高并发连接管理 | | 码元 | UTF-16 的 16 位单元 | 解释 char 与 emoji 长度 | | 语法糖 | 编译器替你展开的简写 | 泛型擦除、foreach、装箱 | | 常量池 | class 内的符号与字面量仓库 | 类加载、反射、字符串驻留 | | 双亲委派 | 先委派父加载器再自己找 | 类隔离、核心类保护 | | GC Roots | 可达性分析的起点集合 | 判断对象可否回收 | | 晋升 | 对象从新生代移到老年代 | 分析 Full GC 频繁的根因 | | url-pattern | Servlet 匹配规则 | 路由与 404 排查 | ## 四、可复用的方法/框架 **分层定位法(排障用)** 1. 定层:网络 / 容器 / 框架 / 应用代码 / JVM,一次只怀疑一层。 2. 取证:抓包、访问日志、GC 日志、线程栈,每层都要有可观测物。 3. 对照:把现象映射到对应知识点("偶发 502"→ 第1章连接超时;"Full GC 频繁"→ 第8章晋升)。 4. 验证:只改一个变量,看指标是否单向变化。 **编码四问**:谁写的字节?用什么字符集写?谁在读?用什么字符集读?四问对齐,乱码现形。 **类加载三查**:类从哪个 jar 来(`-verbose:class`)?被哪个加载器加载?父加载器能否看到它? ## 五、我的阅读轨迹 | 项目 | 数据 | |---|---| | 进度 | 100% | | 累计阅读 | 10 小时 7 分钟 | | 开始阅读 | 2017-10-31 | | 最近阅读 | 2021-04-24 | | 读完时间 | 2020-06-02 | 复习建议:已读完但跨度近四年,属"需重激活"。按信息密度分配时间——第 8 章(内存与回收,篇幅最大)、第 4 章(Javac,最长最易忘)、第 3 章(编码,实战高频)优先;第 1、2、9 章只回看速查表。每次只攻一个主题,配一次动手实验(如 `jstat` 看一轮 Young GC)。 ## 六、行动清单 - 用 `curl -v` 拆解一次自己项目的请求,标出每段耗时。 - 打开 GC 日志,用 `jstat -gc` 记录 5 分钟 Young/Old 变化。 - 检查全链路字符集配置,列出所有可能落到默认编码的位置。 - 用 `javap -v` 反编译一个自己写的类,对照常量池与指令。 - 写一个自定义 ClassLoader,验证委派被打破时的加载顺序。 - 整理 `url-pattern` 与 Filter 顺序的验证用例,确认线上路由符合预期。 ## 七、延伸阅读 1. **《深入理解Java虚拟机》(周志明)**——本书第 6–8 章的深化版,GC 与类加载更细。先读本书建全景,再读它做纵深。 2. **《HTTP权威指南》**——本书第 1 章的展开,缓存、连接管理、代理更系统。与第 1 章并行读。 3. **《深入剖析Tomcat》**——第 9 章的延伸,从连接器到容器逐层实现。读完第 9 章再读,否则易迷失。