├── .nojekyll
├── docs
├── .vuepress
│ ├── styles
│ │ ├── dist
│ │ │ ├── config.css
│ │ │ ├── palette.css
│ │ │ └── index.css
│ │ ├── config.scss
│ │ ├── index.scss
│ │ └── palette.scss
│ ├── public
│ │ ├── me.png
│ │ ├── logo.png
│ │ ├── favicon.ico
│ │ ├── assets
│ │ │ └── icon
│ │ │ │ ├── chrome-192.png
│ │ │ │ ├── chrome-512.png
│ │ │ │ ├── ms-icon-144.png
│ │ │ │ ├── apple-icon-152.png
│ │ │ │ ├── chrome-mask-192.png
│ │ │ │ ├── chrome-mask-512.png
│ │ │ │ ├── guide-maskable.png
│ │ │ │ └── guide-monochrome.png
│ │ └── logo.svg
│ ├── navbar.ts
│ ├── sidebar
│ │ └── index.ts
│ ├── config.ts
│ └── theme.ts
├── images
│ ├── TreeMap继承结构.png
│ ├── cover
│ │ ├── cs-basis.png
│ │ ├── java-jvm.png
│ │ ├── network.png
│ │ ├── java-basis.png
│ │ ├── design-pattern.png
│ │ ├── java-collection.png
│ │ ├── java-concurrent.png
│ │ └── operating-system.png
│ ├── Java-Collections.jpeg
│ ├── jdk1.8之前的内部结构-HashMap.png
│ ├── jdk1.8之后的内部结构-HashMap.png
│ ├── image-20200405151029416.png
│ ├── java8_concurrenthashmap.png
│ ├── 77c95eb733284dbd8ce4e85c9cb6b042.png
│ └── ad28e3ba-e419-4724-869c-73879e604da1.png
├── snippets
│ ├── small-advertisement.snippet.md
│ ├── article-footer.snippet.md
│ └── yuanma.snippet.md
├── interview-preparation
│ ├── self-test-of-common-interview-questions.md
│ ├── interview-experience.md
│ ├── java-roadmap.md
│ ├── internship-experience.md
│ ├── how-to-handle-interview-nerves.md
│ ├── key-points-of-interview.md
│ ├── project-experience-guide.md
│ ├── teach-you-how-to-prepare-for-the-interview-hand-in-hand.md
│ └── resume-guide.md
├── cs-basics
│ ├── data-structure.md
│ ├── algorithms.md
│ ├── operating-system.md
│ └── network.md
├── intro
│ └── faq.md
├── home.md
├── README.md
├── real-interview-experience
│ └── 2025-alibaba-taotian-1.md
├── other
│ └── test-development.md
└── java
│ └── java-jvm.md
├── merge-md.sh
├── pictures
└── logo.png
├── .gitignore
├── _coverpage.md
├── package.json
├── README.md
├── index.html
└── sw.js
/.nojekyll:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/.vuepress/styles/dist/config.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/.vuepress/styles/dist/palette.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/.vuepress/styles/config.scss:
--------------------------------------------------------------------------------
1 | $theme-color: #2980b9;
2 |
--------------------------------------------------------------------------------
/merge-md.sh:
--------------------------------------------------------------------------------
1 | rm JavaGuide.md
2 | cat ./docs/*.md >> JavaGuide.md
3 |
4 |
5 |
--------------------------------------------------------------------------------
/pictures/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/pictures/logo.png
--------------------------------------------------------------------------------
/docs/.vuepress/styles/dist/index.css:
--------------------------------------------------------------------------------
1 | @media (min-width: 1440px) {
2 | body {
3 | font-size: 16px;
4 | }
5 | }
--------------------------------------------------------------------------------
/docs/.vuepress/styles/index.scss:
--------------------------------------------------------------------------------
1 | body {
2 | @media (min-width: 1440px) {
3 | font-size: 16px;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .DS_Store
3 | /docs/.vuepress/.cache
4 | /docs/.vuepress/.temp
5 | /node_modules
6 | /.vscode
7 | /dist
8 |
--------------------------------------------------------------------------------
/docs/.vuepress/public/me.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/.vuepress/public/me.png
--------------------------------------------------------------------------------
/docs/images/TreeMap继承结构.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/images/TreeMap继承结构.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/.vuepress/public/logo.png
--------------------------------------------------------------------------------
/docs/images/cover/cs-basis.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/images/cover/cs-basis.png
--------------------------------------------------------------------------------
/docs/images/cover/java-jvm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/images/cover/java-jvm.png
--------------------------------------------------------------------------------
/docs/images/cover/network.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/images/cover/network.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/.vuepress/public/favicon.ico
--------------------------------------------------------------------------------
/docs/images/Java-Collections.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/images/Java-Collections.jpeg
--------------------------------------------------------------------------------
/docs/images/cover/java-basis.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/images/cover/java-basis.png
--------------------------------------------------------------------------------
/docs/images/cover/design-pattern.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/images/cover/design-pattern.png
--------------------------------------------------------------------------------
/docs/images/cover/java-collection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/images/cover/java-collection.png
--------------------------------------------------------------------------------
/docs/images/cover/java-concurrent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/images/cover/java-concurrent.png
--------------------------------------------------------------------------------
/docs/images/cover/operating-system.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/images/cover/operating-system.png
--------------------------------------------------------------------------------
/docs/images/jdk1.8之前的内部结构-HashMap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/images/jdk1.8之前的内部结构-HashMap.png
--------------------------------------------------------------------------------
/docs/images/jdk1.8之后的内部结构-HashMap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/images/jdk1.8之后的内部结构-HashMap.png
--------------------------------------------------------------------------------
/docs/images/image-20200405151029416.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/images/image-20200405151029416.png
--------------------------------------------------------------------------------
/docs/images/java8_concurrenthashmap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/images/java8_concurrenthashmap.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/assets/icon/chrome-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/.vuepress/public/assets/icon/chrome-192.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/assets/icon/chrome-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/.vuepress/public/assets/icon/chrome-512.png
--------------------------------------------------------------------------------
/docs/images/77c95eb733284dbd8ce4e85c9cb6b042.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/images/77c95eb733284dbd8ce4e85c9cb6b042.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/assets/icon/ms-icon-144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/.vuepress/public/assets/icon/ms-icon-144.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/assets/icon/apple-icon-152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/.vuepress/public/assets/icon/apple-icon-152.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/assets/icon/chrome-mask-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/.vuepress/public/assets/icon/chrome-mask-192.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/assets/icon/chrome-mask-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/.vuepress/public/assets/icon/chrome-mask-512.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/assets/icon/guide-maskable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/.vuepress/public/assets/icon/guide-maskable.png
--------------------------------------------------------------------------------
/docs/images/ad28e3ba-e419-4724-869c-73879e604da1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/images/ad28e3ba-e419-4724-869c-73879e604da1.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/assets/icon/guide-monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Snailclimb/JavaGuide-Interview/HEAD/docs/.vuepress/public/assets/icon/guide-monochrome.png
--------------------------------------------------------------------------------
/docs/snippets/small-advertisement.snippet.md:
--------------------------------------------------------------------------------
1 | ::: tip 这是一则或许对你有用的小广告
2 |
3 | 给自己打个小广告,如果需要面试辅导(比如简历优化、一对一模拟问答、高频考点突击资料等),欢迎了解我的[知识星球](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)。已经坚持维护六年,内容持续更新,虽白菜价(0.4元/天)但质量很高,主打一个良心!
4 |
5 | :::
6 |
--------------------------------------------------------------------------------
/_coverpage.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | JavaGuide面试突击版
5 |
6 | [常用资源](https://shimo.im/docs/MuiACIg1HlYfVxrj/)
7 | [GitHub](https://github.com/Snailclimb/JavaGuide-Interview)
8 | [开始阅读](#javaguide-interview)
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/docs/.vuepress/navbar.ts:
--------------------------------------------------------------------------------
1 | import { navbar } from "vuepress-theme-hope";
2 |
3 | export default navbar([
4 | { text: "Java 面试", icon: "java", link: "/home.md" },
5 | { text: "PDF 下载", icon: "pdf", link: "https://mp.weixin.qq.com/s/q14qXzdM4KTmawyMi5mFpg" },
6 | // { text: "后端面经", icon: "interview", link: "/system-design/design-pattern.md" },
7 | ]);
8 |
--------------------------------------------------------------------------------
/docs/.vuepress/styles/palette.scss:
--------------------------------------------------------------------------------
1 | $sidebar-width: 20rem;
2 | $sidebar-mobile-width: 16rem;
3 | $vp-font: 'Georgia, -apple-system, "Nimbus Roman No9 L", "PingFang SC", "Hiragino Sans GB", "Noto Serif SC", "Microsoft Yahei", "WenQuanYi Micro Hei", sans-serif';
4 | $vp-font-heading: 'Georgia, -apple-system, "Nimbus Roman No9 L", "PingFang SC", "Hiragino Sans GB", "Noto Serif SC", "Microsoft Yahei", "WenQuanYi Micro Hei", sans-serif';
5 |
--------------------------------------------------------------------------------
/docs/snippets/article-footer.snippet.md:
--------------------------------------------------------------------------------
1 | ## 写在最后
2 |
3 | 感谢你能看到这里,也希望这篇文章对你有点用。
4 |
5 | JavaGuide 坚持更新 6 年多,近 6000 次提交、600+ 位贡献者一起打磨。如果这些内容对你有帮助,非常欢迎点个免费的 Star 支持下(完全自愿,觉得有收获再点就好):[GitHub](https://github.com/Snailclimb/JavaGuide) | [Gitee](https://gitee.com/SnailClimb/JavaGuide)。
6 |
7 | 如果你想要付费支持/面试辅导(比如简历优化、一对一提问、高频考点突击资料等)的话,欢迎了解我的[知识星球](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)。已经坚持维护六年,内容持续更新,虽白菜价(0.4元/天)但质量很高,主打一个良心!
8 |
9 |
--------------------------------------------------------------------------------
/docs/interview-preparation/self-test-of-common-interview-questions.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 常见面试题自测(付费)
3 | category: 知识星球
4 | icon: security-fill
5 | ---
6 |
7 | 面试之前,强烈建议大家多拿常见的面试题来进行自测,检查一下自己的掌握情况,这是一种非常实用的备战技术面试的小技巧。
8 |
9 | 在 **[《Java 面试指北》](https://javaguide.cn/zhuanlan/java-mian-shi-zhi-bei.html)** 的 **「技术面试题自测篇」** ,我总结了 Java 面试中最重要的知识点的最常见的面试题并按照面试提问的方式展现出来。
10 |
11 | 
12 |
13 | 每一道用于自测的面试题我都会给出重要程度,方便大家在时间比较紧张的时候根据自身情况来选择性自测。并且,我还会给出提示,方便你回忆起对应的知识点。
14 |
15 | 在面试中如果你实在没有头绪的话,一个好的面试官也是会给你提示的。
16 |
17 | 
18 |
--------------------------------------------------------------------------------
/docs/interview-preparation/interview-experience.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 优质面经汇总(付费)
3 | category: 知识星球
4 | icon: experience
5 | ---
6 |
7 | 古人云:“**他山之石,可以攻玉**” 。善于学习借鉴别人的面试的成功经验或者失败的教训,可以让自己少走许多弯路。
8 |
9 | 在 **[《Java 面试指北》](https://javaguide.cn/zhuanlan/java-mian-shi-zhi-bei.html)** 的 **「面经篇」** ,我分享了 15+ 篇高质量的 Java 后端面经,有校招的,也有社招的,有大厂的,也有中小厂的。
10 |
11 | 如果你是非科班的同学,也能在这些文章中找到对应的非科班的同学写的面经。
12 |
13 | 
14 |
15 | 并且,[知识星球](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)还有专门分享面经和面试题的专题,里面会分享很多优质的面经和面试题。
16 |
17 | 
18 |
19 | 
20 |
21 | 
22 |
23 | 相比于牛客网或者其他网站的面经,《Java 面试指北》中整理的面经质量更高,并且,我会提供优质的参考资料。
24 |
25 | 有很多同学要说了:“为什么不直接给出具体答案呢?”。主要原因有如下两点:
26 |
27 | 1. 参考资料解释的要更详细一些,还可以顺便让你把相关的知识点复习一下。
28 | 2. 给出的参考资料基本都是我的原创,假如后续我想对面试问题的答案进行完善,就不需要挨个把之前的面经写的答案给修改了(面试中的很多问题都是比较类似的)。当然了,我的原创文章也不太可能覆盖到面试的每个点,部分面试问题的答案,我是精选的其他技术博主写的优质文章,文章质量都很高。
29 |
--------------------------------------------------------------------------------
/docs/cs-basics/data-structure.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 数据结构常见面试题总结
3 | category: 计算机基础
4 | tag:
5 | - 数据结构
6 | head:
7 | - - meta
8 | - name: keywords
9 | content: 数据结构面试突击,图DFS BFS,邻接表矩阵,红黑树详解,堆栈队列,线性结构,布隆过滤器,树遍历算法
10 | - - meta
11 | - name: description
12 | content: 数据结构面试突击:图邻接表/矩阵+DFS/BFS、红黑树/堆/栈队列、线性结构速览,一图一链全覆盖,3小时掌握大厂高频考点!
13 | ---
14 |
15 | 数据结构这部分的基础知识已经总结完成。
16 |
17 | 由于篇幅问题,这里直接放 JavaGuide 在线网站网站上的文章链接,小伙伴可以根据个人需求自行学习:
18 |
19 | - [线性数据结构 :数组、链表、栈、队列](https://javaguide.cn/cs-basics/data-structure/linear-data-structure.html)
20 | - [图](https://javaguide.cn/cs-basics/data-structure/graph.html)
21 | - [堆](https://javaguide.cn/cs-basics/data-structure/heap.html)
22 | - [树](https://javaguide.cn/cs-basics/data-structure/tree.html)
23 | - [红黑树](https://javaguide.cn/cs-basics/data-structure/red-black-tree.html)
24 | - [布隆过滤器](https://javaguide.cn/cs-basics/data-structure/bloom-filter.html)
25 |
26 | 
27 |
28 |
29 |

30 |
31 |
--------------------------------------------------------------------------------
/docs/snippets/yuanma.snippet.md:
--------------------------------------------------------------------------------
1 | [《Java 必读源码系列》](../zhuanlan/source-code-reading.md)(点击链接即可查看详细介绍)的部分内容展示如下。
2 |
3 | 
4 |
5 | 为了帮助更多同学准备 Java 面试以及学习 Java ,我创建了一个纯粹的[Java 面试知识星球](../about-the-author/zhishixingqiu-two-years.md)。虽然收费只有培训班/训练营的百分之一,但是知识星球里的内容质量更高,提供的服务也更全面,非常适合准备 Java 面试和学习 Java 的同学。
6 |
7 | **欢迎准备 Java 面试以及学习 Java 的同学加入我的 [知识星球](../about-the-author/zhishixingqiu-two-years.md),干货非常多,学习氛围也很不错!收费虽然是白菜价,但星球里的内容或许比你参加上万的培训班质量还要高。**
8 |
9 | 下面是星球提供的部分服务(点击下方图片即可获取知识星球的详细介绍):
10 |
11 | [](../about-the-author/zhishixingqiu-two-years.md)
12 |
13 | **我有自己的原则,不割韭菜,用心做内容,真心希望帮助到你!**
14 |
15 | 如果你感兴趣的话,不妨花 3 分钟左右看看星球的详细介绍:[JavaGuide 知识星球详细介绍](../about-the-author/zhishixingqiu-two-years.md) 。
16 |
17 | 这里再送一个 **30** 元的星球专属优惠券,数量有限(价格即将上调。老用户续费半价 ,微信扫码即可续费)!
18 |
19 | 
20 |
21 | 进入星球之后,记得查看 **[星球使用指南](https://t.zsxq.com/0d18KSarv)** (一定要看!!!) 和 **[星球优质主题汇总](https://t.zsxq.com/12uSKgTIm)** 。
22 |
23 | **无任何套路,无任何潜在收费项。用心做内容,不割韭菜!**
24 |
25 | 不过, **一定要确定需要再进** 。并且, **三天之内觉得内容不满意可以全额退款** 。
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "javaguide-interview",
3 | "version": "2.0.0-alpha.40",
4 | "private": true,
5 | "description": "javaguide-interview",
6 | "license": "MIT",
7 | "author": "Guide",
8 | "scripts": {
9 | "docs:build": "vuepress build docs",
10 | "docs:dev": "vuepress dev docs",
11 | "docs:clean-dev": "vuepress dev docs --clean-cache",
12 | "lint": "pnpm lint:prettier && pnpm lint:md",
13 | "lint:md": "markdownlint-cli2 '**/*.md'",
14 | "lint:prettier": "prettier --check --write .",
15 | "prepare": "husky",
16 | "update": "pnpm dlx vp-update"
17 | },
18 | "nano-staged": {
19 | "**/*": "prettier --write --ignore-unknown",
20 | ".md": "markdownlint-cli2"
21 | },
22 | "dependencies": {
23 | "@vuepress/bundler-vite": "2.0.0-rc.24",
24 | "@vuepress/plugin-feed": "2.0.0-rc.112",
25 | "@vuepress/plugin-search": "2.0.0-rc.112",
26 | "husky": "9.1.7",
27 | "markdownlint-cli2": "0.17.1",
28 | "mathjax-full": "3.2.2",
29 | "nano-staged": "0.8.0",
30 | "prettier": "3.4.2",
31 | "sass-embedded": "1.89.2",
32 | "vue": "^3.5.18",
33 | "vuepress": "2.0.0-rc.24",
34 | "vuepress-theme-hope": "2.0.0-rc.94"
35 | },
36 | "packageManager": "pnpm@10.0.0"
37 | }
38 |
--------------------------------------------------------------------------------
/docs/intro/faq.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 常见问题
3 | category: 走近项目
4 | icon: help
5 | ---
6 |
7 | ## 为什么要再单独弄一个面试突击版?
8 |
9 | JavaGuide 已经有了在线阅读版本(地址:https://javaguide.cn/),阅读体验也很不错,为什么我还要再花这么多时间单独弄一个面试突击版呢?
10 |
11 | 1. 很多同学由于某些原因比较喜欢看 PDF 电子版或者有打印的需求,[JavaGuide](https://javaguide.cn/) 原项目内容过多,不太适合整理成 PDF 版本;
12 | 2. 《JavaGuide 面试突击版》专为面试突击打造,内容相比于[JavaGuide](https://javaguide.cn/) 原项目更精简。
13 |
14 | ## 如何获取最新版本?
15 |
16 | 你可以通过我的公众号获取到 **《JavaGuide 面试突击版》** 的最新版本,后台回复“**PDF**”即可!
17 |
18 |
19 |
20 | ## 如何学习本项目?
21 |
22 | 不论是在线版本还是 PDF 版本都提供了非常详细的目录,建议可以从头到尾看一遍,如果基础不错的话也可以挑自己需要的章节查看。看的过程中自己要多思考,碰到不懂的地方,自己记得要勤搜索,需要记忆的地方也不要吝啬自己的脑子。
23 |
24 | ## 如何贡献?
25 |
26 | 大家阅读过程中如果遇到错误或者可以完善的地方,可以在 Github/Gitee 的 issue 区与我交流:
27 |
28 | - Github:https://github.com/Snailclimb/JavaGuide-Interview
29 | - Gitee:https://gitee.com/SnailClimb/JavaGuide-Interview
30 |
31 | 或者,你可以通过邮箱 _koushuangbwcx@163.com_ 与我交流。
32 |
33 | 希望大家给我提反馈的时候可以按照如下格式:
34 |
35 | > 问题:描述清楚哪一篇文章的描述存在问题。
36 | >
37 | > 改进:描述清楚如何去改进有问题的描述。
38 | >
39 | > 参考文档(可选):相关的一些参考资料比如官方文档的描述、书籍中的描述。
40 |
41 | 为了提高准确性已经不必要的时间花费,希望大家尽量确保自己想法的准确性。
42 |
43 |
44 |
45 | ------
46 |
--------------------------------------------------------------------------------
/docs/interview-preparation/java-roadmap.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Java 学习路线(最新版,4w+字)
3 | category: 面试准备
4 | icon: path
5 | ---
6 |
7 | ::: tip 重要说明
8 |
9 | 本学习路线保持**年度系统性修订**,严格同步 Java 技术生态与招聘市场的最新动态,**确保内容时效性与前瞻性**。
10 |
11 | :::
12 |
13 | 历时一个月精心打磨,笔者基于当下 Java 后端开发岗位招聘的最新要求,对既有学习路线进行了全面升级。本次升级涵盖技术栈增删、学习路径优化、配套学习资源更新等维度,力争构建出更符合 Java 开发者成长曲线的知识体系。
14 |
15 | 亮色板概览:
16 |
17 | 
18 |
19 | 暗色板概览:
20 |
21 | 
22 |
23 | 这可能是你见过的最用心、最全面的 Java 后端学习路线。这份学习路线共包含 **4w+** 字,但你完全不用担心内容过多而学不完。我会根据学习难度,划分出适合找小厂工作必学的内容,以及适合逐步提升 Java 后端开发能力的学习路径。
24 |
25 | 
26 |
27 | 对于初学者,你可以按照这篇文章推荐的学习路线和资料进行系统性的学习;对于有经验的开发者,你可以根据这篇文章更一步地深入学习 Java 后端开发,提升个人竞争力。
28 |
29 | 在看这份学习路线的过程中,建议搭配 [Java 面试重点总结(重要)](https://javaguide.cn/interview-preparation/key-points-of-interview.html),可以让你在学习过程中更有目的性。
30 |
31 | 由于这份学习路线内容太多,因此我将其整理成了 PDF 版本(共 **55** 页),方便大家阅读。这份 PDF 有黑夜和白天两种阅读版本,满足大家的不同需求。
32 |
33 | 这份学习路线的获取方法很简单:直接在公众号「**JavaGuide**」后台回复“**路线**”即可获取。
34 |
35 | 
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 项目介绍
2 |
3 | - 这是 [JavaGuide](https://javaguide.cn/) 面试突击版本,适合突击面试的小伙伴。并且,提供了 PDF 下载,方便大家离线阅读/打印,阅读体验非常高。
4 | - 如果你准备面试的时间比较充足的话,建议阅读完整版,针对重要的知识点有更详细的讲解。地址:**[javaguide.cn](https://javaguide.cn/)**。
5 | - 专属面试小册/一对一交流/简历修改/专属求职指南,欢迎加入 **[JavaGuide 知识星球](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)**(点击链接即可查看星球的详细介绍,一定确定自己真的需要再加入)。
6 |
7 | ## Java
8 |
9 | - [Java基础常见面试题总结](./docs/java/java-basis.md)
10 | - [Java集合常见面试题总结](./docs/java/java-collection.md)
11 | - [Java并发常见面试题总结](./docs/java/java-concurrent.md)
12 | - [JVM常见面试题总结](./docs/java/java-jvm.md)
13 |
14 | ## 计算机基础
15 |
16 | - [计算机网络常见面试题总结](./docs/cs-basics/network.md)
17 | - [操作系统常见面试题总结](./docs/cs-basics/operating-system.md)
18 | - [数据结构常见面试题总结](./docs/cs-basics/data-structure.md)
19 | - [算法常见面试题总结](./docs/cs-basics/algorithms.md)
20 |
21 | ## 数据库和缓存
22 |
23 | - [MySQL常见面试题总结](./docs/database/mysql.md)
24 | - [Redis常见面试题总结](./docs/database/redis.md)
25 |
26 | ## 系统设计
27 |
28 | - [Spring和Spring Boot常见面试题总结](./docs/system-design/spring.md)
29 | - [设计模式常见面试题总结](./docs/system-design/design-pattern.md)
30 |
31 | ## 测试开发
32 |
33 | - [测试开发常见面试题总结](./docs/other/test-development.md)
34 |
35 | ## 公众号
36 |
37 | 如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
38 |
39 |
40 |
--------------------------------------------------------------------------------
/docs/home.md:
--------------------------------------------------------------------------------
1 | ---
2 | icon: creative
3 | title: Java 学习&面试指南(Go、Python 后端面试通用,计算机基础面试总结)
4 | ---
5 |
6 | ## 项目介绍
7 |
8 | - 这是 [JavaGuide](https://javaguide.cn/) 面试突击版本,适合突击面试的小伙伴。并且,提供了 PDF 下载,方便大家离线阅读/打印,阅读体验非常高。
9 | - 如果你准备面试的时间比较充足的话,建议阅读完整版,针对重要的知识点有更详细的讲解。地址:**[javaguide.cn](https://javaguide.cn/)**。
10 | - 专属面试小册/一对一交流/简历修改/专属求职指南,欢迎加入 **[JavaGuide 知识星球](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)**(点击链接即可查看星球的详细介绍,一定确定自己真的需要再加入)。
11 |
12 | ## Java
13 |
14 | - [Java基础常见面试题总结](./java/java-basis.md)
15 | - [Java集合常见面试题总结](./java/java-collection.md)
16 | - [Java并发常见面试题总结](./java/java-concurrent.md)
17 | - [JVM常见面试题总结](./java/java-jvm.md)
18 |
19 | ## 计算机基础
20 |
21 | - [计算机网络常见面试题总结](./cs-basics/network.md)
22 | - [操作系统常见面试题总结](./cs-basics/operating-system.md)
23 | - [数据结构常见面试题总结](./cs-basics/data-structure.md)
24 | - [算法常见面试题总结](./cs-basics/algorithms.md)
25 |
26 | ## 数据库和缓存
27 |
28 | - [MySQL常见面试题总结](./database/mysql.md)
29 | - [Redis常见面试题总结](./database/redis.md)
30 |
31 | ## 系统设计
32 |
33 | - [Spring和Spring Boot常见面试题总结](./system-design/spring.md)
34 | - [设计模式常见面试题总结](./system-design/design-pattern.md)
35 |
36 | ## 测试开发
37 |
38 | - [测试开发常见面试题总结](./other/test-development.md)
39 |
40 | ## 公众号
41 |
42 | 如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/docs/cs-basics/algorithms.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 算法常见面试题总结
3 | category: 计算机基础
4 | tag:
5 | - 算法
6 | head:
7 | - - meta
8 | - name: keywords
9 | content: 算法面试突击,LeetCode高频题,十大排序算法,字符串算法题,链表算法题,剑指Offer编程题,数据结构经典题,算法思想总结
10 | - - meta
11 | - name: description
12 | content: 算法面试突击:十大经典排序+字符串/链表高频LeetCode、剑指Offer编程题、算法思想速览,一文附资源链接,3步刷题法+⭐️推荐合集,快速通关算法面试!
13 | ---
14 |
15 | ## 算法
16 |
17 | > [JavaGuide](https://javaguide.cn/) :「Java学习+面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识。准备 Java 面试,首选 JavaGuide!
18 |
19 | 算法这部分目前已经总结了部分基础的常见的算法面试题。
20 |
21 | 由于篇幅问题,这里直接放网站上的文章链接,小伙伴可以根据个人需求自行学习:
22 |
23 | - [经典算法思想总结(含LeetCode题目推荐)](https://javaguide.cn/cs-basics/algorithms/classical-algorithm-problems-recommendations.html)
24 | - [常见数据结构经典LeetCode题目推荐](https://javaguide.cn/cs-basics/algorithms/common-data-structures-leetcode-recommendations.html)
25 | - [几道常见的字符串算法题](https://javaguide.cn/cs-basics/algorithms/string-algorithm-problems.html)
26 | - [几道常见的链表算法题](https://javaguide.cn/cs-basics/algorithms/linkedlist-algorithm-problems.html)
27 | - [剑指offer部分编程题](https://javaguide.cn/cs-basics/algorithms/the-sword-refers-to-offer.html)
28 | - [十大经典排序算法总结](https://javaguide.cn/cs-basics/algorithms/10-classical-sorting-algorithms.html)
29 |
30 | [《Java 面试指北》](https://javaguide.cn/zhuanlan/java-mian-shi-zhi-bei.html)中有一篇文章详细介绍了如何刷算法题效率最高,效果最好,文中还总结推荐了一些高频算法题资源。
31 |
32 | 
33 |
34 |
35 |

36 |
37 |
--------------------------------------------------------------------------------
/docs/.vuepress/sidebar/index.ts:
--------------------------------------------------------------------------------
1 | import { sidebar } from "vuepress-theme-hope";
2 |
3 | export default sidebar({
4 | "/": [
5 | {
6 | text: "项目介绍",
7 | icon: "star",
8 | collapsible: true,
9 | prefix: "intro/",
10 | children: ["faq"],
11 | },
12 | {
13 | text: "面试准备(必看)",
14 | icon: "interview",
15 | collapsible: true,
16 | prefix: "interview-preparation/",
17 | children: [
18 | "teach-you-how-to-prepare-for-the-interview-hand-in-hand",
19 | "resume-guide",
20 | "key-points-of-interview",
21 | "java-roadmap",
22 | "project-experience-guide",
23 | "how-to-handle-interview-nerves",
24 | "internship-experience",
25 | ],
26 | },
27 | {
28 | text: "Java",
29 | icon: "interview",
30 | collapsible: false,
31 | prefix: "java/",
32 | children: [
33 | "java-basis",
34 | "java-collection",
35 | "java-concurrent",
36 | "java-jvm",
37 | ],
38 | },
39 | {
40 | text: "计算机基础",
41 | icon: "computer",
42 | collapsible: false,
43 | prefix: "cs-basics/",
44 | children: ["network", "operating-system", "data-structure", "algorithms"],
45 | },
46 | {
47 | text: "数据库和缓存",
48 | icon: "database",
49 | collapsible: false,
50 | prefix: "database/",
51 | children: ["mysql", "redis"],
52 | },
53 | {
54 | text: "系统设计",
55 | icon: "design",
56 | collapsible: false,
57 | prefix: "system-design/",
58 | children: ["spring", "design-pattern"],
59 | },
60 | {
61 | text: "测开",
62 | icon: "framework",
63 | collapsible: false,
64 | prefix: "other/",
65 | children: ["test-development"],
66 | },
67 | ],
68 | });
69 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | home: true
3 | icon: home
4 | title: Java 面试指南
5 | heroImage: /logo.svg
6 | heroText: JavaGuide 面试突击版
7 | tagline: Java 学习&面试指南(Go、Python 后端面试通用,计算机基础面试总结)
8 | actions:
9 | - text: 开始阅读
10 | link: /home.md
11 | type: primary
12 | - text: 知识星球
13 | link: https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html
14 | type: default
15 | footer: |-
16 | 鄂ICP备2020015769号-1 | 主题: VuePress Theme Hope
17 | ---
18 |
19 | ## 关于网站
20 |
21 | JavaGuide 已经持续维护 6 年多了,累计提交了接近 **6000** commit ,共有 **570+** 多位贡献者共同参与维护和完善。真心希望能够把这个项目做好,真正能够帮助到有需要的朋友!
22 |
23 | 如果觉得 JavaGuide 的内容对你有帮助的话,还请点个免费的 Star(绝不强制点 Star,觉得内容不错有收获再点赞就好),这是对我最大的鼓励,感谢各位一路同行,共勉!传送门:[GitHub](https://github.com/Snailclimb/JavaGuide) | [Gitee](https://gitee.com/SnailClimb/JavaGuide)。
24 |
25 | 这是 [JavaGuide](https://javaguide.cn/) 面试突击版本,适合突击面试的小伙伴。并且,提供了 PDF 下载,方便大家离线阅读/打印,阅读体验非常高。
26 |
27 | 如果你准备面试的时间比较充足的话,建议阅读完整版,针对重要的知识点有更详细的讲解。地址:**[javaguide.cn](https://javaguide.cn/)**。
28 |
29 | 专属面试小册/一对一交流/简历修改/专属求职指南,欢迎加入 **[JavaGuide 知识星球](./docs/about-the-author/zhishixingqiu-two-years.md)**(点击链接即可查看星球的详细介绍,一定确定自己真的需要再加入)。
30 |
31 | ## 关于作者
32 |
33 | - [我曾经也是网瘾少年](https://javaguide.cn/about-the-author/internet-addiction-teenager.html)
34 | - [害,毕业三年了!](https://javaguide.cn/about-the-author/my-college-life.html)
35 | - [我的知识星球 4 岁了!](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)
36 | - [坚持写技术博客六年了](https://javaguide.cn/about-the-author/writing-technology-blog-six-years.html)
37 |
38 | ## 公众号
39 |
40 | 最新更新会第一时间同步在公众号,推荐关注!另外,公众号上有很多干货不会同步在线阅读网站。
41 |
42 |
43 |
--------------------------------------------------------------------------------
/docs/.vuepress/config.ts:
--------------------------------------------------------------------------------
1 | import { viteBundler } from "@vuepress/bundler-vite";
2 | import { defineUserConfig } from "vuepress";
3 | import theme from "./theme.js";
4 |
5 | export default defineUserConfig({
6 | dest: "./dist",
7 |
8 | title: "JavaGuide(面试突击版)",
9 | description:
10 | "Java 学习&面试指南(Go、Python 后端面试通用,计算机基础面试总结)",
11 | lang: "zh-CN",
12 |
13 | head: [
14 | // meta
15 | ["meta", { name: "robots", content: "all" }],
16 | ["meta", { name: "author", content: "Guide" }],
17 | [
18 | "meta",
19 | {
20 | "http-equiv": "Cache-Control",
21 | content: "no-cache, no-store, must-revalidate",
22 | },
23 | ],
24 | ["meta", { "http-equiv": "Pragma", content: "no-cache" }],
25 | ["meta", { "http-equiv": "Expires", content: "0" }],
26 | [
27 | "meta",
28 | {
29 | name: "keywords",
30 | content:
31 | "Java,Go,Java面试,Java基础, 多线程, JVM, 虚拟机, 数据库, MySQL, Spring, Redis, MyBatis, 系统设计, 分布式, RPC, 高可用, 高并发",
32 | },
33 | ],
34 | [
35 | "meta",
36 | {
37 | name: "description",
38 | content:
39 | "「Java 突击面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识。准备 Java 面试,首选 JavaGuide!",
40 | },
41 | ],
42 | ["meta", { name: "apple-mobile-web-app-capable", content: "yes" }],
43 | // 添加百度统计
44 | [
45 | "script",
46 | {},
47 | `var _hmt = _hmt || [];
48 | (function() {
49 | var hm = document.createElement("script");
50 | hm.src = "https://hm.baidu.com/hm.js?e1bad04c60516dda715993552ed7ebb1";
51 | var s = document.getElementsByTagName("script")[0];
52 | s.parentNode.insertBefore(hm, s);
53 | })();`,
54 | ],
55 | ],
56 |
57 | bundler: viteBundler(),
58 |
59 | theme,
60 |
61 | pagePatterns: ["**/*.md", "!**/*.snippet.md", "!.vuepress", "!node_modules"],
62 |
63 | shouldPrefetch: false,
64 | shouldPreload: false,
65 | });
66 |
--------------------------------------------------------------------------------
/docs/.vuepress/theme.ts:
--------------------------------------------------------------------------------
1 | import { getDirname, path } from "vuepress/utils";
2 | import { hopeTheme } from "vuepress-theme-hope";
3 |
4 | import navbar from "./navbar.js";
5 | import sidebar from "./sidebar/index.js";
6 |
7 | const __dirname = getDirname(import.meta.url);
8 |
9 | export default hopeTheme({
10 | hostname: "https://interview.javaguide.cn/",
11 | logo: "/logo.png",
12 | favicon: "/favicon.ico",
13 |
14 | author: {
15 | name: "Guide",
16 | url: "https://javaguide.cn/article/",
17 | },
18 |
19 | repo: "https://github.com/Snailclimb/JavaGuide-Interview",
20 | docsDir: "docs",
21 | pure: true,
22 | focus: false,
23 | breadcrumb: false,
24 | navbar,
25 | sidebar,
26 | footer:
27 | '鄂ICP备2020015769号-1',
28 | displayFooter: true,
29 |
30 | pageInfo: ["Author", "Category", "Tag", "Original", "Word", "ReadingTime"],
31 |
32 | markdown: {
33 | align: true,
34 | codeTabs: true,
35 | gfm: true,
36 | include: {
37 | resolvePath: (file, cwd) => {
38 | if (file.startsWith("@"))
39 | return path.resolve(
40 | __dirname,
41 | "../snippets",
42 | file.replace("@", "./"),
43 | );
44 |
45 | return path.resolve(cwd, file);
46 | },
47 | },
48 | tasklist: true,
49 | },
50 |
51 | plugins: {
52 | blog: true,
53 |
54 | copyright: {
55 | author: "JavaGuide(javaguide.cn)",
56 | license: "MIT",
57 | triggerLength: 100,
58 | maxLength: 700,
59 | canonical: "https://javaguide.cn/",
60 | global: true,
61 | },
62 |
63 | feed: {
64 | atom: true,
65 | json: true,
66 | rss: true,
67 | },
68 |
69 | icon: {
70 | assets: "//at.alicdn.com/t/c/font_2922463_o9q9dxmps9.css",
71 | },
72 |
73 | search: {
74 | isSearchable: (page) => page.path !== "/",
75 | maxSuggestions: 10,
76 | },
77 | },
78 | });
79 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | JavaGuide
7 |
8 |
9 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
48 |
49 |
50 |
51 |
52 |
53 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/docs/interview-preparation/internship-experience.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 校招没有实习经历怎么办?
3 | category: 面试准备
4 | icon: experience
5 | ---
6 |
7 | 由于目前的面试太卷,对于犹豫是否要找实习的同学来说,个人建议不论是本科生还是研究生都应该在参加校招面试之前,争取一下不错的实习机会,尤其是大厂的实习机会,日常实习或者暑期实习都可以。当然,如果大厂实习面不上,中小厂实习也是可以接受的。
8 |
9 | 不过,现在的实习是真难找,今年有非常多的同学没有找到实习,有一部分甚至是 211/985 名校的同学。
10 |
11 | 如果实在是找不到合适的实习的话,那也没办法,我们应该多花时间去把下面这三件事情给做好:
12 |
13 | 1. 补强项目经历
14 | 2. 持续完善简历
15 | 3. 准备技术面试
16 |
17 | ## 补强项目经历
18 |
19 | 校招没有实习经历的话,找工作比较吃亏(没办法,太卷了),需要在项目经历部分多发力弥补一下。
20 |
21 | 建议你尽全力地去补强自己的项目经历,完善现有的项目或者去做更有亮点的项目,尽可能地通过项目经历去弥补一些。
22 |
23 | 你面试中的重点就是你的项目经历涉及到的知识点,如果你的项目经历比较简单的话,面试官直接不知道问啥了。另外,你的项目经历中不涉及的知识点,但在技能介绍中提到的知识点也很大概率会被问到。像 Redis 这种基本是面试 Java 后端岗位必备的技能,我觉得大部分面试官应该都会问。
24 |
25 | 推荐阅读一下网站的这篇文章:[项目经验指南](https://javaguide.cn/interview-preparation/project-experience-guide.html)。
26 |
27 | ## **完善简历**
28 |
29 | 一定一定一定要重视简历啊!建议至少花 2~3 天时间来专门完善自己的简历。并且,后续还要持续完善。
30 |
31 | 对于面试官来说,筛选简历的时候会比较看重下面这些维度:
32 |
33 | 1. **实习/工作经历**:看你是否有不错的实习经历,大厂且与面试岗位相关的实习/工作经历最佳。
34 | 2. **获奖经历**:如果有含金量比较高(知名度较高的赛事比如 ACM、阿里云天池)的获奖经历的话,也是加分点,尤其是对于校招来说,这类求职者属于是很多大厂争抢的对象(但不是说获奖了就能进大厂,还是要面试表现还可以)。对于社招来说,获奖经历作用相对较小,通常会更看重过往的工作经历和项目经验。
35 | 3. **项目经验**:项目经验对于面试来说非常重要,面试官会重点关注,同时也是有水平的面试提问的重点。
36 | 4. **技能匹配度**:看你的技能是否满足岗位的需求。在投递简历之前,一定要确认一下自己的技能介绍中是否缺少一些你要投递的对应岗位的技能要求。
37 | 5. **学历**:相对其他行业来说,程序员求职面试对于学历的包容度还是比较高的,只要你在其他方面有过人之出的话,也是可以弥补一下学历的缺陷的。你要知道,很多行业比如律师、金融,学历就是敲门砖,学历没达到要求,直接面试机会都没有。不过,由于现在面试越来越卷,一些大厂、国企和研究所也开始卡学历了,很多岗位都要求 211/985,甚至必须需要硕士学历。总之,学历很难改变,学校较差的话,就投递那些对学历没有明确要求的公司即可,努力提升自己的其他方面的硬实力。
38 |
39 | 对于大部分求职者来说,实习/工作经历、项目经验、技能匹配度更重要一些。不过,不排除一些公司会因为学历卡人。
40 |
41 | 详细的程序员简历编写指南可以参考这篇文章:[程序员简历编写指南(重要)](https://javaguide.cn/interview-preparation/resume-guide.html)。
42 |
43 | ## **准备技术面试**
44 |
45 | 面试之前一定要提前准备一下常见的面试题也就是八股文:
46 |
47 | - 自己面试中可能涉及哪些知识点、那些知识点是重点。
48 | - 面试中哪些问题会被经常问到、面试中自己该如何回答。(强烈不推荐死记硬背,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!)
49 |
50 | Java 后端面试复习的重点请看这篇文章:[Java 后端的面试重点是什么?](https://javaguide.cn/interview-preparation/key-points-of-interview.html)。
51 |
52 | 不同类型的公司对于技能的要求侧重点是不同的比如腾讯、字节可能更重视计算机基础比如网络、操作系统这方面的内容。阿里、美团这种可能更重视你的项目经历、实战能力。
53 |
54 | 一定不要抱着一种思想,觉得八股文或者基础问题的考查意义不大。如果你抱着这种思想复习的话,那效果可能不会太好。实际上,个人认为还是很有意义的,八股文或者基础性的知识在日常开发中也会需要经常用到。例如,线程池这块的拒绝策略、核心参数配置什么的,如果你不了解,实际项目中使用线程池可能就用的不是很明白,容易出现问题。而且,其实这种基础性的问题是最容易准备的,像各种底层原理、系统设计、场景题以及深挖你的项目这类才是最难的!
55 |
56 | 八股文资料首推我的 [《Java 面试指北》](https://javaguide.cn/zhuanlan/java-mian-shi-zhi-bei.html) 和 [JavaGuide](https://javaguide.cn/home.html) 。里面不仅仅是原创八股文,还有很多对实际开发有帮助的干货。除了我的资料之外,你还可以去网上找一些其他的优质的文章、视频来看。
57 |
--------------------------------------------------------------------------------
/docs/interview-preparation/how-to-handle-interview-nerves.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 面试太紧张怎么办?
3 | category: 面试准备
4 | icon: security-fill
5 | ---
6 |
7 | 很多小伙伴在第一次技术面试时都会感到紧张甚至害怕,面试结束后还会有种“懵懵的”感觉。我也经历过类似的状况,可以说是深有体会。其实,**紧张是很正常的**——它代表你对面试的重视,也来自于对未知结果的担忧。但如果过度紧张,反而会影响你的临场发挥。
8 |
9 | 下面,我就分享一些自己的心得,帮大家更好地应对面试中的紧张情绪。
10 |
11 | ## 试着接受紧张情绪,调整心态
12 |
13 | 首先要明白,紧张是正常情绪,特别是初次或前几次面试时,多少都会有点忐忑。不要过分排斥这种情绪,可以适当地“拥抱”它:
14 |
15 | - **搞清楚面试的本质**:面试本质上是一场与面试官的深入交流,是一个双向选择的过程。面试失败并不意味着你的价值和努力被否定,而可能只是因为你与目标岗位暂时不匹配,或者仅仅是一次 KPI 面试,这家公司可能压根就没有真正的招聘需求。失败的原因也可能是某些知识点、项目经验或表达方式未能充分展现出你的能力。即便这次面试未通过,也不妨碍你继续尝试其他公司,完全不慌!
16 | - **不要害怕面试官**:很多求职者平时和同学朋友交流沟通的蛮好,一到面试就害怕了。面试官和求职者双方是平等的,以后说不定就是同事关系。也不要觉得面试官就很厉害,实际上,面试官的水平也参差不齐。他们提出的问题,可能自己也没有完全理解。
17 | - **给自己积极的心理暗示**:告诉自己“有点紧张没关系,这只能让我更专注,心跳加快是我在给自己打气,我一定可以回答的很好!”。
18 |
19 | ## 提前准备,减少不确定性
20 |
21 | **不确定性越多,越容易紧张。** 如果你能够在面试前做充分的准备,很多“未知”就会消失,紧张情绪自然会减轻很多。
22 |
23 | ### 认真准备技术面试
24 |
25 | - **优先梳理核心知识点**:比如计算基础、数据库、Java 基础、Java 集合、并发编程、SpringBoot(这里以 Java 后端方向为例)等。如果时间不够,可以分轻重缓急,有重点地复习。强烈推荐阅读一下 [Java 面试重点总结(重要)](https://javaguide.cn/interview-preparation/key-points-of-interview.html)这篇文章。
26 | - **精心准备项目经历**:认真思考你简历上最重要的项目(面试以前两个项目为主,尤其是第一个),它们的技术难点、业务逻辑、架构设计,以及可能被面试官深挖的点。把你的思考总结成可能出现的面试问题,并尝试回答。
27 |
28 | ### 模拟面试和自测
29 |
30 | - **约朋友或同学互相提问**:以真实的面试场景来进行演练,并及时对回答进行诊断和反馈。
31 | - **线上练习**:很多平台都提供 AI 模拟面试,能比较真实地模拟面试官提问情境。
32 | - **面经**:平时可以多看一些前辈整理的面经,尤其是目标岗位或目标公司的面经,总结高频考点和常见问题。
33 | - **技术面试题自测**:在 [《Java 面试指北》](https://javaguide.cn/zhuanlan/java-mian-shi-zhi-bei.html) 的 「技术面试题自测篇」 ,我总结了 Java 面试中最重要的知识点的最常见的面试题并按照面试提问的方式展现出来。其中,每一个问题都有提示和重要程度说明,非常适合用来自测。
34 |
35 | [《Java 面试指北》](https://javaguide.cn/zhuanlan/java-mian-shi-zhi-bei.html) 的 「技术面试题自测篇」概览:
36 |
37 | 
38 |
39 | ### 多表达
40 |
41 | 平时要多说,多表达出来,不要只是在心里面想,不然真正面试的时候会发现想的和说的不太一样。
42 |
43 | 我前面推荐的模拟面试和自测,有一部分原因就是为了能够多多表达。
44 |
45 | ### 多面试
46 |
47 | - **先小厂后大厂**:可以先去一些规模较小或者对你来说压力没那么大的公司试试手,积累一些实战经验,增加一些信心;等熟悉了面试流程、能够更从容地回答问题后,再去挑战自己心仪的大厂或热门公司。
48 | - **积累“失败经验”**:不要怕被拒,有些时候被拒绝却能从中学到更多。多复盘,多思考到底是哪个环节出了问题,再用更好的状态迎接下一次面试。
49 |
50 | ### 保证休息
51 |
52 | - **留出充裕时间**:面试前尽量不要排太多事情,保证自己能有个好状态去参加面试。
53 | - **保证休息**:充足睡眠有助于情绪稳定,也能让你在面试时更清晰地思考问题。
54 |
55 | ## 遇到不会的问题不要慌
56 |
57 | 一场面试,不太可能面试官提的每一个问题你都能轻松应对,除非这场面试非常简单。
58 |
59 | 在面试过程中,遇到不会的问题,首先要做的是快速回顾自己过往的知识,看是否能找到突破口。如果实在没有思路的话,可以真诚地向面试要一些提示比如谈谈你对这个问题的理解以及困惑点。一定不要觉得向面试官要提示很可耻,只要沟通没问题,这其实是很正常的。最怕的就是自己不会,还乱回答一通,这样会让面试官觉得你技术态度有问题。
60 |
61 | ## 面试结束后的复盘
62 |
63 | 很多人关注面试前的准备,却忽略了面试后的复盘,这一步真的非常非常非常重要:
64 |
65 | 1. **记录面试中的问题**:无论回答得好坏,都把它们写下来。如果问到了一些没想过的问题,可以认真思考并在面试后补上答案。
66 | 2. **反思自己的表现**:有没有遇到卡壳的地方?是知识没准备到还是过于紧张导致表达混乱?下次如何改进?
67 | 3. **持续完善自己的“面试题库”**:把新的问题补充进去,不断拓展自己的知识面,也逐步降低对未知问题的恐惧感。
68 |
--------------------------------------------------------------------------------
/docs/interview-preparation/key-points-of-interview.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Java后端面试重点总结
3 | category: 面试准备
4 | icon: star
5 | ---
6 |
7 | ::: tip 友情提示
8 | 本文节选自 **[《Java 面试指北》](https://javaguide.cn/zhuanlan/java-mian-shi-zhi-bei.html)**。这是一份教你如何更高效地准备面试的专栏,内容和 JavaGuide 互补,涵盖常见八股文(系统设计、常见框架、分布式、高并发 ……)、优质面经等内容。
9 | :::
10 |
11 | ## Java 后端面试哪些知识点是重点?
12 |
13 | **准备面试的时候,具体哪些知识点是重点呢?如何把握重点?**
14 |
15 | 给你几点靠谱的建议:
16 |
17 | 1. Java 基础、集合、并发、MySQL、Redis 、Spring、Spring Boot 这些 Java 后端开发必备的知识点(MySQL + Redis >= Java > Spring + Spring Boot)。大厂以及中小厂的面试问的比较多的就是这些知识点。Spring 和 Spring Boot 这俩框架类的知识点相对前面的知识点来说重要性要稍低一些,但一般面试也会问一些,尤其是中小厂。并发知识一般中大厂提问更多也更难,尤其是大厂喜欢深挖底层,很容易把人问倒。计算机基础相关的内容会在下面提到。
18 | 2. 你的项目经历涉及到的知识点是重中之重,有水平的面试官都是会根据你的项目经历来问的。举个例子,你的项目经历使用了 Redis 来做限流,那 Redis 相关的八股文(比如 Redis 常见数据结构)以及限流相关的八股文(比如常见的限流算法)你就应该多花更多心思来搞懂吃透!你把项目经历上的知识点吃透之后,再把你简历上哪些写熟练掌握的技术给吃透,最后再去花时间准备其他知识点。
19 | 3. 针对自身找工作的需求,你又可以适当地调整复习的重点。像中小厂一般问计算机基础比较少一些,有些大厂比如字节比较重视计算机基础尤其是算法。这样的话,如果你的目标是中小厂的话,计算机基础就准备面试来说不是那么重要了。如果复习时间不够的话,可以暂时先放放,腾出时间给其他重要的知识点。
20 | 4. 一般校招的面试不会强制要求你会分布式/微服务、高并发的知识(不排除个别岗位有这方面的硬性要求),所以到底要不要掌握还是要看你个人当前的实际情况。如果你会这方面的知识的话,对面试相对来说还是会更有利一些(想要让项目经历有亮点,还是得会一些性能优化的知识。性能优化的知识这也算是高并发知识的一个小分支了)。如果你的技能介绍或者项目经历涉及到分布式/微服务、高并发的知识,那建议你尽量也要抽时间去认真准备一下,面试中很可能会被问到,尤其是项目经历用到的时候。不过,也还是主要准备写在简历上的那些知识点就好。
21 | 5. JVM 相关的知识点,一般是大厂(例如美团、阿里)和一些不错的中厂(例如携程、顺丰、招银网络)才会问到,面试国企、差一点的中厂和小厂就没必要准备了。JVM 面试中比较常问的是 [Java 内存区域](https://javaguide.cn/java/jvm/memory-area.html)、[JVM 垃圾回收](https://javaguide.cn/java/jvm/jvm-garbage-collection.html)、[类加载器和双亲委派模型](https://javaguide.cn/java/jvm/classloader.html) 以及 JVM 调优和问题排查(我之前分享过一些[常见的线上问题案例](https://t.zsxq.com/0bsAac47U),里面就有 JVM 相关的)。
22 | 6. 不同的大厂面试侧重点也会不同。比如说你要去阿里这种公司的话,项目和八股文就是重点,阿里笔试一般会有代码题,进入面试后就很少问代码题了,但是对原理性的问题问的比较深,经常会问一些你对技术的思考。再比如说你要面试字节这种公司,那计算机基础,尤其是算法是重点,字节的面试十分注重代码功底,有时候开始面试就会直接甩给你一道代码题,写出来再谈别的。也会问面试八股文,以及项目,不过,相对来说要少很多。建议你看一下这篇文章 [为了解开互联网大厂秋招内幕,我把他们全面了一遍](https://mp.weixin.qq.com/s/pBsGQNxvRupZeWt4qZReIA),了解一下常见大厂的面试题侧重点。
23 | 7. 多去找一些面经看看,尤其你目标公司或者类似公司对应岗位的面经。这样可以实现针对性的复习,还能顺便自测一波,检查一下自己的掌握情况。
24 |
25 | 看似 Java 后端八股文很多,实际把复习范围一缩小,重要的东西就是那些。考虑到时间问题,你不可能连一些比较冷门的知识点也给准备了。这没必要,主要精力先放在那些重要的知识点即可。
26 |
27 | ## 如何更高效地准备八股文?
28 |
29 | 对于技术八股文来说,尽量不要死记硬背,这种方式非常枯燥且对自身能力提升有限!但是!想要一点不背是不太现实的,只是说要结合实际应用场景和实战来理解记忆。
30 |
31 | 我一直觉得面试八股文最好是和实际应用场景和实战相结合。很多同学现在的方向都错了,上来就是直接背八股文,硬生生学成了文科,那当然无趣了。
32 |
33 | 举个例子:你的项目中需要用到 Redis 来做缓存,你对照着官网简单了解并实践了简单使用 Redis 之后,你去看了 Redis 对应的八股文。你发现 Redis 可以用来做限流、分布式锁,于是你去在项目中实践了一下并掌握了对应的八股文。紧接着,你又发现 Redis 内存不够用的情况下,还能使用 Redis Cluster 来解决,于是你就又去实践了一下并掌握了对应的八股文。
34 |
35 | **一定要记住你的主要目标是理解和记关键词,而不是像背课文一样一字一句地记下来,这样毫无意义!效率最低,对自身帮助也最小!**
36 |
37 | 还要注意适当“投机取巧”,不要单纯死记八股,有些技术方案的实现有很多种,例如分布式 ID、分布式锁、幂等设计,想要完全记住所有方案不太现实,你就重点记忆你项目的实现方案以及选择该种实现方案的原因就好了。当然,其他方案还是建议你简单了解一下,不然也没办法和你选择的方案进行对比。
38 |
39 | 想要检测自己是否搞懂或者加深印象,记录博客或者用自己的理解把对应的知识点讲给别人听也是一个不错的选择。
40 |
41 | 另外,准备八股文的过程中,强烈建议你花个几个小时去根据你的简历(主要是项目经历部分)思考一下哪些地方可能被深挖,然后把你自己的思考以面试问题的形式体现出来。面试之后,你还要根据当下的面试情况复盘一波,对之前自己整理的面试问题进行完善补充。这个过程对于个人进一步熟悉自己的简历(尤其是项目经历)部分,非常非常有用。这些问题你也一定要多花一些时间搞懂吃透,能够流畅地表达出来。面试问题可以参考 [Java 面试常见问题总结(2024 最新版)](https://t.zsxq.com/0eRq7EJPy),记得根据自己项目经历去深入拓展即可!
42 |
43 | 最后,准备技术面试的同学一定要定期复习(自测的方式非常好),不然确实会遗忘的。
44 |
--------------------------------------------------------------------------------
/sw.js:
--------------------------------------------------------------------------------
1 | /* ===========================================================
2 | * docsify sw.js
3 | * ===========================================================
4 | * Copyright 2016 @huxpro
5 | * Licensed under Apache 2.0
6 | * Register service worker.
7 | * ========================================================== */
8 |
9 | const RUNTIME = 'docsify'
10 | const HOSTNAME_WHITELIST = [
11 | self.location.hostname,
12 | 'fonts.gstatic.com',
13 | 'fonts.googleapis.com',
14 | 'unpkg.com'
15 | ]
16 |
17 | // The Util Function to hack URLs of intercepted requests
18 | const getFixedUrl = (req) => {
19 | var now = Date.now()
20 | var url = new URL(req.url)
21 |
22 | // 1. fixed http URL
23 | // Just keep syncing with location.protocol
24 | // fetch(httpURL) belongs to active mixed content.
25 | // And fetch(httpRequest) is not supported yet.
26 | url.protocol = self.location.protocol
27 |
28 | // 2. add query for caching-busting.
29 | // Github Pages served with Cache-Control: max-age=600
30 | // max-age on mutable content is error-prone, with SW life of bugs can even extend.
31 | // Until cache mode of Fetch API landed, we have to workaround cache-busting with query string.
32 | // Cache-Control-Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=453190
33 | if (url.hostname === self.location.hostname) {
34 | url.search += (url.search ? '&' : '?') + 'cache-bust=' + now
35 | }
36 | return url.href
37 | }
38 |
39 | /**
40 | * @Lifecycle Activate
41 | * New one activated when old isnt being used.
42 | *
43 | * waitUntil(): activating ====> activated
44 | */
45 | self.addEventListener('activate', event => {
46 | event.waitUntil(self.clients.claim())
47 | })
48 |
49 | /**
50 | * @Functional Fetch
51 | * All network requests are being intercepted here.
52 | *
53 | * void respondWith(Promise r)
54 | */
55 | self.addEventListener('fetch', event => {
56 | // Skip some of cross-origin requests, like those for Google Analytics.
57 | if (HOSTNAME_WHITELIST.indexOf(new URL(event.request.url).hostname) > -1) {
58 | // Stale-while-revalidate
59 | // similar to HTTP's stale-while-revalidate: https://www.mnot.net/blog/2007/12/12/stale
60 | // Upgrade from Jake's to Surma's: https://gist.github.com/surma/eb441223daaedf880801ad80006389f1
61 | const cached = caches.match(event.request)
62 | const fixedUrl = getFixedUrl(event.request)
63 | const fetched = fetch(fixedUrl, { cache: 'no-store' })
64 | const fetchedCopy = fetched.then(resp => resp.clone())
65 |
66 | // Call respondWith() with whatever we get first.
67 | // If the fetch fails (e.g disconnected), wait for the cache.
68 | // If there’s nothing in cache, wait for the fetch.
69 | // If neither yields a response, return offline pages.
70 | event.respondWith(
71 | Promise.race([fetched.catch(_ => cached), cached])
72 | .then(resp => resp || fetched)
73 | .catch(_ => { /* eat any errors */ })
74 | )
75 |
76 | // Update the cache with the version we fetched (only for ok status)
77 | event.waitUntil(
78 | Promise.all([fetchedCopy, caches.open(RUNTIME)])
79 | .then(([response, cache]) => response.ok && cache.put(event.request, response))
80 | .catch(_ => { /* eat any errors */ })
81 | )
82 | }
83 | })
--------------------------------------------------------------------------------
/docs/interview-preparation/project-experience-guide.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 项目经验指南
3 | category: 面试准备
4 | icon: project
5 | ---
6 |
7 | ::: tip 友情提示
8 | 本文节选自 **[《Java 面试指北》](https://javaguide.cn/zhuanlan/java-mian-shi-zhi-bei.html)**。这是一份教你如何更高效地准备面试的专栏,内容和 JavaGuide 互补,涵盖常见八股文(系统设计、常见框架、分布式、高并发 ……)、优质面经等内容。
9 | :::
10 |
11 | ## 没有项目经验怎么办?
12 |
13 | 没有项目经验是大部分应届生会碰到的一个问题。甚至说,有很多有工作经验的程序员,对自己在公司做的项目不满意,也想找一个比较有技术含量的项目来做。
14 |
15 | 说几种我觉得比较靠谱的获取项目经验的方式,希望能够对你有启发。
16 |
17 | ### 实战项目视频/专栏
18 |
19 | 在网上找一个符合自己能力与找工作需求的实战项目视频或者专栏,跟着老师一起做。
20 |
21 | 你可以通过慕课网、哔哩哔哩、拉勾、极客时间、培训机构(比如黑马、尚硅谷)等渠道获取到适合自己的实战项目视频/专栏。
22 |
23 | 
24 |
25 | 尽量选择一个适合自己的项目,没必要必须做分布式/微服务项目,对于绝大部分同学来说,能把一个单机项目做好就已经很不错了。
26 |
27 | 我面试过很多求职者,简历上看着有微服务的项目经验,结果随便问两个问题就知道根本不是自己做的或者说做的时候压根没认真思考。这种情况会给我留下非常不好的印象。
28 |
29 | 我在 **[《Java 面试指北》](https://javaguide.cn/zhuanlan/java-mian-shi-zhi-bei.html)** 的「面试准备篇」中也说过:
30 |
31 | > 个人认为也没必要非要去做微服务或者分布式项目,不一定对你面试有利。微服务或者分布式项目涉及的知识点太多,一般人很难吃透。并且,这类项目其实对于校招生来说稍微有一点超标了。即使你做出来,很多面试官也会认为不是你独立完成的。
32 | >
33 | > 其实,你能把一个单体项目做到极致也很好,对于个人能力提升不比做微服务或者分布式项目差。如何做到极致?代码质量这里就不提了,更重要的是你要尽量让自己的项目有一些亮点(比如你是如何提升项目性能的、如何解决项目中存在的一个痛点的),项目经历取得的成果尽量要量化一下比如我使用 xxx 技术解决了 xxx 问题,系统 qps 从 xxx 提高到了 xxx。
34 |
35 | 跟着老师做的过程中,你一定要有自己的思考,不要浅尝辄止。对于很多知识点,别人的讲解可能只是满足项目就够了,你自己想多点知识的话,对于重要的知识点就要自己学会去深入学习。
36 |
37 | ### 实战类开源项目
38 |
39 | GitHub 或者码云上面有很多实战类别项目,你可以选择一个来研究,为了让自己对这个项目更加理解,在理解原有代码的基础上,你可以对原有项目进行改进或者增加功能。
40 |
41 | 你可以参考 [Java 优质开源实战项目](https://javaguide.cn/open-source-project/practical-project.html "Java 优质开源实战项目") 上面推荐的实战类开源项目,质量都很高,项目类型也比较全面,涵盖博客/论坛系统、考试/刷题系统、商城系统、权限管理系统、快速开发脚手架以及各种轮子。
42 |
43 | 
44 |
45 | 一定要记住:**不光要做,还要改进,改善。不论是实战项目视频或者专栏还是实战类开源项目,都一定会有很多可以完善改进的地方。**
46 |
47 | ### 从头开始做
48 |
49 | 自己动手去做一个自己想完成的东西,遇到不会的东西就临时去学,现学现卖。
50 |
51 | 这个要求比较高,我建议你已经有了一个项目经验之后,再采用这个方法。如果你没有做过项目的话,还是老老实实采用上面两个方法比较好。
52 |
53 | ### 参加各种大公司组织的各种大赛
54 |
55 | 如果参加这种赛事能获奖的话,项目含金量非常高。即使没获奖也没啥,也可以写简历上。
56 |
57 | 
58 |
59 | ### 参与实际项目
60 |
61 | 通常情况下,你有如下途径接触到企业实际项目的开发:
62 |
63 | 1. 老师接的项目;
64 | 2. 自己接的私活;
65 | 3. 实习/工作接触到的项目;
66 |
67 | 老师接的项目和自己接的私活通常都是一些偏业务的项目,很少会涉及到性能优化。这种情况下,你可以考虑对项目进行改进,别怕花时间,某个时间用心做好一件事情就好比如你对项目的数据模型进行改进、引入缓存提高访问速度等等。
68 |
69 | 实习/工作接触到的项目类似,如果遇到一些偏业务的项目,也是要自己私下对项目进行改进优化。
70 |
71 | 尽量是真的对项目进行了优化,这本身也是对个人能力的提升。如果你实在是没时间去实践的话,也没关系,吃透这个项目优化手段就好,把一些面试可能会遇到的问题提前准备一下。
72 |
73 | ## 有没有还不错的项目推荐?
74 |
75 | **[《Java 面试指北》](https://javaguide.cn/zhuanlan/java-mian-shi-zhi-bei.html)** 的「面试准备篇」中有一篇文章专门整理了一些比较高质量的实战项目,包含业务项目、轮子项目、国外公开课 Lab 和视频类实战项目教程推荐,非常适合用来学习或者作为项目经验。
76 |
77 | 
78 |
79 | 这篇文章一共推荐了 15+ 个实战项目,有业务类的,也有轮子类的,有开源项目、也有视频教程。对于参加校招的小伙伴,我更建议做一个业务类项目加上一个轮子类的项目。
80 |
81 | ## 我跟着视频做的项目会被面试官嫌弃不?
82 |
83 | 很多应届生都是跟着视频做的项目,这个大部分面试官都心知肚明。
84 |
85 | 不排除确实有些面试官不吃这一套,这个也看人。不过我相信大多数面试官都是能理解的,毕竟你在学校的时候实际上是没有什么获得实际项目经验的途径的。
86 |
87 | 大部分应届生的项目经验都是自己在网上找的或者像你一样买的付费课程跟着做的,极少部分是比较真实的项目。 从你能想着做一个实战项目来说,我觉得初衷是好的,确实也能真正学到东西。 但是,究竟有多少是自己掌握了很重要。看视频最忌讳的是被动接受,自己多改进一下,多思考一下!就算是你跟着视频做的项目,也是可以优化的!
88 |
89 | **如果你想真正学到东西的话,建议不光要把项目单纯完成跑起来,还要去自己尝试着优化!**
90 |
91 | 简单说几个比较容易的优化点:
92 |
93 | 1. **全局异常处理**:很多项目这方面都做的不是很好,可以参考我的这篇文章:[《使用枚举简单封装一个优雅的 Spring Boot 全局异常处理!》](https://mp.weixin.qq.com/s/Y4Q4yWRqKG_lw0GLUsY2qw) 来做优化。
94 | 2. **项目的技术选型优化**:比如使用 Guava 做本地缓存的地方可以换成 **Caffeine** 。Caffeine 的各方面的表现要更加好!再比如 Controller 层是否放了太多的业务逻辑。
95 | 3. **数据库方面**:数据库设计可否优化?索引是否使用使用正确?SQL 语句是否可以优化?是否需要进行读写分离?
96 | 4. **缓存**:项目有没有哪些数据是经常被访问的?是否引入缓存来提高响应速度?
97 | 5. **安全**:项目是否存在安全问题?
98 | 6. ……
99 |
100 | 另外,我在星球分享过常见的性能优化方向实践案例,涉及到多线程、异步、索引、缓存等方向,强烈推荐你看看: 。
101 |
102 | 最后,**再给大家推荐一个 IDEA 优化代码的小技巧,超级实用!**
103 |
104 | 分析你的代码:右键项目-> Analyze->Inspect Code
105 |
106 | 
107 |
108 | 扫描完成之后,IDEA 会给出一些可能存在的代码坏味道比如命名问题。
109 |
110 | 
111 |
112 | 并且,你还可以自定义检查规则。
113 |
114 | 
115 |
--------------------------------------------------------------------------------
/docs/interview-preparation/teach-you-how-to-prepare-for-the-interview-hand-in-hand.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 如何高效准备Java面试?
3 | category: 知识星球
4 | icon: path
5 | ---
6 |
7 | ::: tip 友情提示
8 | 本文节选自 **[《Java 面试指北》](https://javaguide.cn/zhuanlan/java-mian-shi-zhi-bei.html)**。这是一份教你如何更高效地准备面试的专栏,内容和 JavaGuide 互补,涵盖常见八股文(系统设计、常见框架、分布式、高并发 ……)、优质面经等内容。
9 | :::
10 |
11 | 你身边是否有这样的朋友:编程能力比你强,求职结果却不如你?其实**技术好≠面试能过** —— 如今的面试早已不是 “会写代码就行”,不做准备就去面,大概率是 “撞枪口”。
12 |
13 | 我们大多是普通开发者,没有顶会论文或竞赛大奖加持,面对 “面试造火箭,工作拧螺丝钉” 的常态,只能靠扎实准备突围。但准备面试不等于耍小聪明或者死记硬背面试题。 **一定不要对面试抱有侥幸心理。打铁还需自身硬!** 千万不要觉得自己看几篇面经,看几篇面试题解析就能通过面试了。一定要静下心来深入学习!
14 |
15 | 这篇文章就从宏观视角,带你搞懂程序员该如何系统准备面试:从求职导向学习,到简历优化、面试冲刺,帮你少走弯路,高效拿下心仪 offer。
16 |
17 | ## 尽早以求职为导向来学习
18 |
19 | 我是比较建议还在学校的同学尽可能早一点以求职为导向来学习的。
20 |
21 | **这样更有针对性,并且可以大概率减少自己处在迷茫的时间,很大程度上还可以让自己少走很多弯路。**
22 |
23 | 但是!不要把“以求职为导向学习”理解为“我就不用学课堂上那些计算机基础课程了”!
24 |
25 | 我在之前的很多次分享中都强调过:**一定要用心学习计算机基础知识!操作系统、计算机组成原理、计算机网络真的不是没有实际用处的学科!!!**
26 |
27 | 你会发现大厂面试你会用到,以后工作之后你也会用到。我分别列举 2 个例子吧!
28 |
29 | - **面试中**:像字节、腾讯这些大厂的技术面试以及几乎所有公司的笔试都会考操作系统相关的问题。
30 | - **工作中**:在实际使用缓存的时候,软件层次而言的缓存思想,则是源自数据库速度、Redis(内存中间件)速度、本地内存速度之间的不匹配;而在计算机存储层次结构设计中,我们也能发现同样的问题及缓存思想的使用:内存用于解决磁盘访问速度过慢的问题,CPU 用三级缓存缓解寄存器和内存之间的速度差异。它们面临的都是同一个问题(速度不匹配)和同一个思想,那么计算机先驱者在存储层次结构设计上对缓存性能的优化措施,同样也适用于软件层次缓存的性能优化。
31 |
32 | **如何求职为导向学习呢?** 简答来说就是:根据招聘要求整理一份目标岗位的技能清单,然后按照技能清单去学习和提升。
33 |
34 | 1. 你首先搞清楚自己要找什么工作
35 | 2. 然后根据招聘岗位的要求梳理一份技能清单
36 | 3. 根据技能清单写好最终的简历
37 | 4. 最后再按照简历的要求去学习和提升。
38 |
39 | 这其实也是 **以终为始** 思想的运用。
40 |
41 | **何为以终为始?** 简单来说,以终为始就是我们可以站在结果来考虑问题,从结果出发,根据结果来确定自己要做的事情。
42 |
43 | 你会发现,其实几乎任何领域都可以用到 **以终为始** 的思想。
44 |
45 | ## 了解投递简历的黄金时间
46 |
47 | 面试之前,你肯定是先要搞清楚春招和秋招的具体时间的。
48 |
49 | 正所谓金三银四,金九银十,错过了这个时间,很多公司都没有 HC 了。
50 |
51 | **秋招一般 7 月份就开始了,大概一直持续到 9 月底。**
52 |
53 | **春招一般 3 月份就开始了,大概一直持续到 4 月底。**
54 |
55 | 很多公司(尤其大厂)到了 9 月中旬(秋招)/3 月中旬(春招),很可能就会没有 HC 了。面试的话一般都是至少是 3 轮起步,一些大厂比如阿里、字节可能会有 5 轮面试。**面试失败话的不要紧,某一面表现差的话也不要紧,调整好心态。又不是单一选择对吧?你能投这么多企业呢! 调整心态。** 今年面试的话,因为疫情原因,有些公司还是可能会还是集中在线上进行面试。然后,还是因为疫情的影响,可能会比往年更难找工作(对大厂影响较小)。
56 |
57 | ## 知道如何获取招聘信息
58 |
59 | 下面是常见的获取招聘信息的渠道:
60 |
61 | - **目标企业的官网/公众号**:最及时最权威的获取招聘信息的途径。
62 | - **招聘网站**:[BOSS 直聘](https://www.zhipin.com/)、[智联招聘](https://www.zhaopin.com/)、[拉勾招聘](https://www.lagou.com/)……。
63 | - **牛客网**:每年秋招/春招,都会有大批量的公司会到牛客网发布招聘信息,并且还会有大量的公司员工来到这里发内推的帖子。地址: 。
64 | - **超级简历**:超级简历目前整合了各大企业的校园招聘入口,地址:
65 | - **认识的朋友**:如果你有认识的朋友在目标企业工作的话,你也可以找他们了解招聘信息,并且可以让他们帮你内推。
66 | - **宣讲会**:宣讲会也是一个不错的途径,不过,好的企业通常只会去比较好的学校,可以留意一下意向公司的宣讲会安排或者直接去到一所比较好的学校参加宣讲会。像我当时校招就去参加了几场宣讲会。不过,我是在荆州上学,那边没什么比较好的学校,一般没有公司去开宣讲会。所以,我当时是直接跑到武汉来了,参加了武汉理工大学以及华中科技大学的几场宣讲会。总体感觉还是很不错的!
67 | - **其他**:校园就业信息网、学校论坛、班级 or 年级 QQ 群。
68 |
69 | 校招的话,建议以官网为准,有宣讲会的话更好。社招的话,可以多留意一下各大招聘网站比如 BOSS 直聘、拉勾上的职位信息。
70 |
71 | 不论校招和社招,如果能找到比较靠谱的内推机会的话,获得面试的机会的概率还是非常大的。而且,你可以让内推你的人定向地给你一些建议。找内推的方式有很多,首选比较熟悉的朋友、同学,还可以留意技术交流社区和公众号上的内推信息。
72 |
73 | 一般是只能投递一个岗位,不过,也有极少数投递不同部门两个岗位的情况,这个应该不会有影响,但你的前一次面试情况可能会被记录,也就是说就算你投递成功两个岗位,第一个岗位面试失败的话,对第二个岗位也会有影响,很可能直接就被 pass。
74 |
75 | ## 多花点时间完善简历
76 |
77 | 一定一定一定要重视简历啊!朋友们!至少要花 2~3 天时间来专门完善自己的简历。
78 |
79 | 最近看了很多份简历,满意的很少,我简单拿出一份来说分析一下(欢迎在评论区补充)。
80 |
81 | **1.个人介绍没太多实用的信息。**
82 |
83 | 
84 |
85 | 技术博客、GitHub 以及在校获奖经历的话,能写就尽量写在这里。 你可以参考下面 👇 的模板进行修改:
86 |
87 | 
88 |
89 | **2.项目经历过于简单,完全没有质量可言**
90 |
91 | 
92 |
93 | 每一个项目经历真的就一两句话可以描述了么?还是自己不想写?还是说不是自己做的,不敢多写。
94 |
95 | 如果有项目的话,技术面试第一步,面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑:
96 |
97 | 1. 你对项目整体设计的一个感受(面试官可能会让你画系统的架构图)
98 | 2. 你在这个项目中你负责了什么、做了什么、担任了什么角色。
99 | 3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用。
100 | 4. 你在这个项目中是否解决过什么问题?怎么解决的?收获了什么?
101 | 5. 你的项目用到了哪些技术?这些技术你吃透了没有?举个例子,你的项目经历使用了 Seata 来做分布式事务,那 Seata 相关的问题你要提前准备一下吧,比如说 Seata 支持哪些配置中心、Seata 的事务分组是怎么做的、Seata 支持哪些事务模式,怎么选择?
102 | 6. 你在这个项目中犯过的错误,最后是怎么弥补的?
103 |
104 | **3.计算机二级这个证书对于计算机专业完全不用写了,没有含金量的。**
105 |
106 | 
107 |
108 | **4.技能介绍问题太大。**
109 |
110 | 
111 |
112 | - 技术名词最好规范大小写比较好,比如 java->Java ,spring boot -> Spring Boot 。这个虽然有些面试官不会介意,但是很多面试官都会在意这个细节的。
113 | - 技能介绍太杂,没有亮点。不需要全才,某个领域做得好就行了!
114 | - 对 Java 后台开发的部分技能比如 Spring Boot 的熟悉度仅仅为了解,无法满足企业的要求。
115 |
116 | 详细的程序员简历编写指南请参考:[程序员简历到底该怎么写?](https://javaguide.cn/interview-preparation/resume-guide.html)。
117 |
118 | ## 岗位匹配度很重要
119 |
120 | 校招通常会对你的项目经历的研究方向比较宽容,即使你的项目经历和对应公司的具体业务没有关系,影响其实也并不大。
121 |
122 | 社招的话就不一样了,毕竟公司是要招聘可以直接来干活的人,你有相关的经验,公司会比较省事。社招通常会比较重视你的过往工作经历以及项目经历,HR 在筛选简历的时候会根据这两方面信息来判断你是否满足他们的招聘要求。就比如说你投递电商公司,而你之前的并没有和电商相关的工作经历以及项目经历,那 HR 在筛简历的时候很可能会直接把你 Pass 掉。
123 |
124 | 不过,这个也并不绝对,也有一些公司在招聘的时候更看重的是你的过往经历,较少地关注岗位匹配度,优秀公司的工作经历以及有亮点的项目经验都是加分项。这类公司相信你既然在某个领域(比如电商、支付)已经做的不错了,那应该也可以在另外一个领域(比如流媒体平台、社交软件)很快成为专家。这个领域指的不是技术领域,更多的是业务方向。横跨技术领域(比如后端转算法、后端转大数据)找工作,你又没有相关的经验,几乎是没办法找到的。即使找到了,也大概率会面临 HR 压薪资的问题。
125 |
126 | ## 提前准备技术面试
127 |
128 | 面试之前一定要提前准备一下常见的面试题也就是八股文:
129 |
130 | - 自己面试中可能涉及哪些知识点、那些知识点是重点。
131 | - 面试中哪些问题会被经常问到、面试中自己该如何回答。(强烈不推荐死记硬背,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!)
132 |
133 | Java 后端面试复习的重点请看这篇文章:[Java 面试重点总结(重要)](https://javaguide.cn/interview-preparation/key-points-of-interview.html)。
134 |
135 | 不同类型的公司对于技能的要求侧重点是不同的比如腾讯、字节可能更重视计算机基础比如网络、操作系统这方面的内容。阿里、美团这种可能更重视你的项目经历、实战能力。
136 |
137 | 一定不要抱着一种思想,觉得八股文或者基础问题的考查意义不大。如果你抱着这种思想复习的话,那效果可能不会太好。实际上,个人认为还是很有意义的,八股文或者基础性的知识在日常开发中也会需要经常用到。例如,线程池这块的拒绝策略、核心参数配置什么的,如果你不了解,实际项目中使用线程池可能就用的不是很明白,容易出现问题。而且,其实这种基础性的问题是最容易准备的,像各种底层原理、系统设计、场景题以及深挖你的项目这类才是最难的!
138 |
139 | 八股文资料首推我的 [《Java 面试指北》](https://javaguide.cn/zhuanlan/java-mian-shi-zhi-bei.html) (配合 JavaGuide 使用,会根据每一年的面试情况对内容进行更新完善)和 [JavaGuide](https://javaguide.cn/) 。里面不仅仅是原创八股文,还有很多对实际开发有帮助的干货。除了我的资料之外,你还可以去网上找一些其他的优质的文章、视频来看。
140 |
141 | 
142 |
143 | ## 提前准备手撕算法
144 |
145 | 很明显,国内现在的校招面试开始越来越重视算法了,尤其是像字节跳动、腾讯这类大公司。绝大部分公司的校招笔试是有算法题的,如果 AC 率比较低的话,基本就挂掉了。
146 |
147 | 社招的话,算法面试同样会有。不过,面试官可能会更看重你的工程能力,你的项目经历。如果你的其他方面都很优秀,但是算法很菜的话,不一定会挂掉。不过,还是建议刷下算法题,避免让其成为自己在面试中的短板。
148 |
149 | 社招往往是在技术面试的最后,面试官给你一个算法题目让你做。
150 |
151 | 关于如何准备算法面试[《Java 面试指北》](https://javaguide.cn/zhuanlan/java-mian-shi-zhi-bei.html) 的面试准备篇有详细介绍到。
152 |
153 | 
154 |
155 | ## 提前准备自我介绍
156 |
157 | 自我介绍一般是你和面试官的第一次面对面正式交流,换位思考一下,假如你是面试官的话,你想听到被你面试的人如何介绍自己呢?一定不是客套地说说自己喜欢编程、平时花了很多时间来学习、自己的兴趣爱好是打球吧?
158 |
159 | 我觉得一个好的自我介绍至少应该包含这几点要素:
160 |
161 | - 用简洁的话说清楚自己主要的技术栈于擅长的领域;
162 | - 把重点放在自己在行的地方以及自己的优势之处;
163 | - 重点突出自己的能力比如自己的定位的 bug 的能力特别厉害;
164 |
165 | 简单来说就是用简洁的语言突出自己的亮点,也就是推销自己嘛!
166 |
167 | - 如果你去过大公司实习,那对应的实习经历就是你的亮点。
168 | - 如果你参加过技术竞赛,那竞赛经历就是你的亮点。
169 | - 如果你大学就接触过企业级项目的开发,实战经验比较多,那这些项目经历就是你的亮点。
170 | - ……
171 |
172 | 从社招和校招两个角度来举例子吧!我下面的两个例子仅供参考,自我介绍并不需要死记硬背,记住要说的要点,面试的时候根据公司的情况临场发挥也是没问题的。另外,网上一般建议的是准备好两份自我介绍:一份对 hr 说的,主要讲能突出自己的经历,会的编程技术一语带过;另一份对技术面试官说的,主要讲自己会的技术细节和项目经验。
173 |
174 | **社招:**
175 |
176 | > 面试官,您好!我叫独秀儿。我目前有 1 年半的工作经验,熟练使用 Spring、MyBatis 等框架、了解 Java 底层原理比如 JVM 调优并且有着丰富的分布式开发经验。离开上一家公司是因为我想在技术上得到更多的锻炼。在上一个公司我参与了一个分布式电子交易系统的开发,负责搭建了整个项目的基础架构并且通过分库分表解决了原始数据库以及一些相关表过于庞大的问题,目前这个网站最高支持 10 万人同时访问。工作之余,我利用自己的业余时间写了一个简单的 RPC 框架,这个框架用到了 Netty 进行网络通信, 目前我已经将这个项目开源,在 GitHub 上收获了 2k 的 Star! 说到业余爱好的话,我比较喜欢通过博客整理分享自己所学知识,现在已经是多个博客平台的认证作者。 生活中我是一个比较积极乐观的人,一般会通过运动打球的方式来放松。我一直都非常想加入贵公司,我觉得贵公司的文化和技术氛围我都非常喜欢,期待能与你共事!
177 |
178 | **校招:**
179 |
180 | > 面试官,您好!我叫秀儿。大学时间我主要利用课外时间学习了 Java 以及 Spring、MyBatis 等框架 。在校期间参与过一个考试系统的开发,这个系统的主要用了 Spring、MyBatis 和 shiro 这三种框架。我在其中主要担任后端开发,主要负责了权限管理功能模块的搭建。另外,我在大学的时候参加过一次软件编程大赛,我和我的团队做的在线订餐系统成功获得了第二名的成绩。我还利用自己的业余时间写了一个简单的 RPC 框架,这个框架用到了 Netty 进行网络通信, 目前我已经将这个项目开源,在 GitHub 上收获了 2k 的 Star! 说到业余爱好的话,我比较喜欢通过博客整理分享自己所学知识,现在已经是多个博客平台的认证作者。 生活中我是一个比较积极乐观的人,一般会通过运动打球的方式来放松。我一直都非常想加入贵公司,我觉得贵公司的文化和技术氛围我都非常喜欢,期待能与你共事!
181 |
182 | ## 减少抱怨
183 |
184 | 就像现在的技术面试一样,大家都说内卷了,抱怨现在的面试真特么难。然而,单纯抱怨有用么?你对其他求职者说:“大家都不要刷 Leetcode 了啊!都不要再准备高并发、高可用的面试题了啊!现在都这么卷了!”
185 |
186 | 会有人听你的么?**你不准备面试,但是其他人会准备面试啊!那你是不是傻啊?还是真的厉害到不需要准备面试呢?**
187 |
188 | 因此,准备 Java 面试的第一步,我们一定要尽量减少抱怨。抱怨的声音多了之后,会十分影响自己,会让自己变得十分焦虑。
189 |
190 | ## 面试之后及时复盘
191 |
192 | 如果失败,不要灰心;如果通过,切勿狂喜。面试和工作实际上是两回事,可能很多面试未通过的人,工作能力比你强的多,反之亦然。
193 |
194 | 面试就像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!
195 |
196 | ## 总结
197 |
198 | 这篇文章内容有点多,如果这篇文章只能让你记住 7 句话,那请记住下面这 7 句:
199 |
200 | 1. 一定要提前准备面试!技术面试不同于编程,编程厉害不代表技术面试就一定能过。
201 | 2. 一定不要对面试抱有侥幸心理。打铁还需自身硬!千万不要觉得自己看几篇面经,看几篇面试题解析就能通过面试了。一定要静下心来深入学习!尤其是目标是大厂的同学,那更要深挖原理!
202 | 3. 建议大学生尽可能早一点以求职为导向来学习的。这样更有针对性,并且可以大概率减少自己处在迷茫的时间,很大程度上还可以让自己少走很多弯路。 但是,不要把“以求职为导向学习”理解为“我就不用学课堂上那些计算机基础课程了”!
203 | 4. 一定不要抱着一种思想,觉得八股文或者基础问题的考查意义不大。如果你抱着这种思想复习的话,那效果可能不会太好。实际上,个人认为还是很有意义的,八股文或者基础性的知识在日常开发中也会需要经常用到。例如,线程池这块的拒绝策略、核心参数配置什么的,如果你不了解,实际项目中使用线程池可能就用的不是很明白,容易出现问题。
204 | 5. 手撕算法是当下技术面试的标配,尽早准备!
205 | 6. 岗位匹配度很重要。校招通常会对你的项目经历的研究方向比较宽容,即使你的项目经历和对应公司的具体业务没有关系,影响其实也并不大。社招的话就不一样了,毕竟公司是要招聘可以直接来干活的人,你有相关的经验,公司会比较省事。
206 |
207 | 7. 面试之后及时复盘。面试就像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!
208 |
--------------------------------------------------------------------------------
/docs/interview-preparation/resume-guide.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 程序员简历编写指南
3 | category: 面试准备
4 | icon: jianli
5 | ---
6 |
7 | ::: tip 友情提示
8 | 本文节选自 **[《Java 面试指北》](https://javaguide.cn/zhuanlan/java-mian-shi-zhi-bei.html)**。这是一份教你如何更高效地准备面试的小册,涵盖常见八股文(系统设计、常见框架、分布式、高并发 ……)、优质面经等内容。
9 | :::
10 |
11 | ## 前言
12 |
13 | 一份好的简历可以在整个申请面试以及面试过程中起到非常重要的作用。
14 |
15 | **为什么说简历很重要呢?** 我们可以从下面几点来说:
16 |
17 | **1、简历就像是我们的一个门面一样,它在很大程度上决定了是否能够获得面试机会。**
18 |
19 | - 假如你是网申,你的简历必然会经过 HR 的筛选,一张简历 HR 可能也就花费 10 秒钟左右看一下,然后决定你能否进入面试。
20 | - 假如你是内推,如果你的简历没有什么优势的话,就算是内推你的人再用心,也无能为力。
21 |
22 | 另外,就算你通过了第一轮的筛选获得面试机会,后面的面试中,面试官也会根据你的简历来判断你究竟是否值得他花费很多时间去面试。
23 |
24 | **2、简历上的内容很大程度上决定了面试官提问的侧重点。**
25 |
26 | - 一般情况下你的简历上注明你会的东西才会被问到(Java 基础、集合、并发、MySQL、Redis 、Spring、Spring Boot 这些算是每个人必问的),比如写了你熟练使用 Redis,那面试官就很大概率会问你 Redis 的一些问题,再比如你写了你在项目中使用了消息队列,那面试官大概率问很多消息队列相关的问题。
27 | - 技能熟练度在很大程度上也决定了面试官提问的深度。
28 |
29 | 在不夸大自己能力的情况下,写出一份好的简历也是一项很棒的能力。一般情况下,技术能力和学习能力比较厉害的,写出来的简历也比较棒!
30 |
31 | ## 简历模板
32 |
33 | 简历的样式真的非常非常重要!!!如果你的简历样式丑到没朋友的话,面试官真的没有看下去的欲望。一天处理上百份的简历的痛苦,你不懂!
34 |
35 | 我这里的话,推荐大家使用 Markdown 语法写简历,然后再将 Markdown 格式转换为 PDF 格式后进行简历投递。如果你对 Markdown 语法不太了解的话,可以花半个小时简单看一下 Markdown 语法说明: 。
36 |
37 | 下面是我收集的一些还不错的简历模板:
38 |
39 | - 适合中文的简历模板收集(推荐,开源免费):
40 | - 木及简历(推荐,部分免费) :
41 | - 简单简历(推荐,部分免费):
42 | - 极简简历(免费):
43 | - Markdown 简历排版工具(开源免费):
44 | - 站长简历(收费,支持 AI 生成):
45 | - typora+markdown+css 自定义简历模板 :
46 | - 超级简历(部分收费) :
47 |
48 | 上面这些简历模板大多是只有 1 页内容,很难展现足够的信息量。如果你不是顶级大牛(比如 ACM 大赛获奖)的话,我建议还是尽可能多写一点可以突出你自己能力的内容(校招生 2 页之内,社招生 3 页之内,记得精炼语言,不要过多废话)。
49 |
50 | 再总结几点 **简历排版的注意事项**:
51 |
52 | - 尽量简洁,不要太花里胡哨。
53 | - 技术名词最好规范大小写比较好,比如 java->Java ,spring boot -> Spring Boot 。这个虽然有些面试官不会介意,但是很多面试官都会在意这个细节的。
54 | - 中文和数字英文之间加上空格的话看起来会舒服一点。
55 |
56 | 另外,知识星球里还有真实的简历模板可供参考,地址: (需加入[知识星球](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)获取)。
57 |
58 | 
59 |
60 | ## 简历内容
61 |
62 | ### 个人信息
63 |
64 | - 最基本的 :姓名(身份证上的那个)、年龄、电话、籍贯、联系方式、邮箱地址
65 | - 潜在加分项 : Github 地址、博客地址(如果技术博客和 Github 上没有什么内容的话,就不要写了)
66 |
67 | 示例:
68 |
69 | 
70 |
71 | **简历要不要放照片呢?** 很多人写简历的时候都有这个问题。
72 |
73 | 其实放不放都行,影响不大,完全不用在意这个问题。除非,你投递的岗位明确要求要放照片。 不过,如果要放的话,不要放生活照,还是应该放正规一些的照片比如证件照。
74 |
75 | ### 求职意向
76 |
77 | 你想要应聘什么岗位,希望在什么城市。另外,你也可以将求职意向放到个人信息这块写。
78 |
79 | 示例:
80 |
81 | 
82 |
83 | ### 教育经历
84 |
85 | 教育经历也不可或缺。通过教育经历的介绍,你要确保能让面试官就可以知道你的学历、专业、毕业学校以及毕业的日期。
86 |
87 | 示例:
88 |
89 | > 北京理工大学 硕士,软件工程 2019.09 - 2022.01
90 | > 湖南大学 学士,应用化学 2015.09 ~ 2019.06
91 |
92 | ### 专业技能
93 |
94 | 先问一下你自己会什么,然后看看你意向的公司需要什么。一般 HR 可能并不太懂技术,所以他在筛选简历的时候可能就盯着你专业技能的关键词来看。对于公司有要求而你不会的技能,你可以花几天时间学习一下,然后在简历上可以写上自己了解这个技能。
95 |
96 | 下面是一份最新的 Java 后端开发技能清单,你可以根据自身情况以及岗位招聘要求做动态调整,核心思想就是尽可能满足岗位招聘的所有技能要求。
97 |
98 | 
99 |
100 | 我这里再单独放一个我看过的某位同学的技能介绍,我们来找找问题。
101 |
102 | 
103 |
104 | 上图中的技能介绍存在的问题:
105 |
106 | - 技术名词最好规范大小写比较好,比如 java->Java ,spring boot -> Spring Boot 。这个虽然有些面试官不会介意,但是很多面试官都会在意这个细节的。
107 | - 技能介绍太杂,没有亮点。不需要全才,某个领域做得好就行了!
108 | - 对 Java 后台开发的部分技能比如 Spring Boot 的熟悉度仅仅为了解,无法满足企业的要求。
109 |
110 | ### 实习经历/工作经历(重要)
111 |
112 | 工作经历针对社招,实习经历针对校招。
113 |
114 | 工作经历建议采用时间倒序的方式来介绍。实习经历和工作经历都需要简单突出介绍自己在职期间主要做了什么。
115 |
116 | 示例:
117 |
118 | > **XXX 公司 (201X 年 X 月 ~ 201X 年 X 月 )**
119 | >
120 | > - **职位**:Java 后端开发工程师
121 | > - **工作内容**:主要负责 XXX
122 |
123 | ### 项目经历(重要)
124 |
125 | 简历上有一两个项目经历很正常,但是真正能把项目经历很好的展示给面试官的非常少。
126 |
127 | 很多求职者的项目经历介绍都会面临过于啰嗦、过于简单、没突出亮点等问题。
128 |
129 | 项目经历介绍模板如下:
130 |
131 | > 项目名称(字号要大一些)
132 | >
133 | > 2017-05~2018-06 淘宝 Java 后端开发工程师
134 | >
135 | > - **项目描述** : 简单描述项目是做什么的。
136 | > - **技术栈** :用了什么技术(如 Spring Boot + MySQL + Redis + Mybatis-plus + Spring Security + Oauth2)
137 | > - **工作内容/个人职责** : 简单描述自己做了什么,解决了什么问题,带来了什么实质性的改善。突出自己的能力,不要过于平淡的叙述。
138 | > - **个人收获(可选)** : 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用。通常是可以不用写个人收获的,因为你在个人职责介绍中写的东西已经表明了自己的主要收获。
139 | > - **项目成果(可选)** :简单描述这个项目取得了什么成绩。
140 |
141 | **1、项目经历应该突出自己做了什么,简单概括项目基本情况。**
142 |
143 | 项目介绍尽量压缩在两行之内,不需要介绍太多,但也不要随便几个字就介绍完了。
144 |
145 | 另外,个人收获和项目成果都是可选的,如果选择写的话,也不要花费太多篇幅,记住你的重点是介绍工作内容/个人职责。
146 |
147 | **2、技术架构直接写技术名词就行,不要再介绍技术是干嘛的了,没意义,属于无效介绍。**
148 |
149 | 
150 |
151 | **3、尽量减少纯业务的个人职责介绍,对于面试不太友好。尽量再多挖掘一些亮点(6~8 条个人职责介绍差不多了,做好筛选),最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目优化了某个模块的性能。**
152 |
153 | 即使不是你做的功能模块或者解决的问题,你只要搞懂吃透了就能拿来自己用,适当润色即可!
154 |
155 | 像性能优化方向上的亮点面试之前也比较容易准备,但也不要都是性能优化相关的,这种也算是一个极端。
156 |
157 | 另外,技术优化取得的成果尽量要量化一下:
158 |
159 | - 使用 xxx 技术解决了 xxx 问题,系统 QPS 从 xxx 提高到了 xxx。
160 | - 使用 xxx 技术了优化了 xxx 接口,系统 QPS 从 xxx 提高到了 xxx。
161 | - 使用 xxx 技术解决了 xxx 问题,查询速度优化了 xxx,系统 QPS 达到 10w+。
162 | - 使用 xxx 技术优化了 xxx 模块,响应时间从 2s 降低到 0.2s。
163 | - ……
164 |
165 | 个人职责介绍示例(这里只是举例,不要照搬,结合自己项目经历自己去写,不然面试的时候容易被问倒) :
166 |
167 | - 基于 Spring Cloud Gateway + Spring Security OAuth2 + JWT 实现微服务统一认证授权和鉴权,使用 RBAC 权限模型实现动态权限控制。
168 | - 参与项目订单模块的开发,负责订单创建、删除、查询等功能,基于 Spring 状态机实现订单状态流转。
169 | - 商品和订单搜索场景引入 Elasticsearch,并且实现了相关商品推荐以及搜索提示功能。
170 | - 整合 Canal + RabbitMQ 将 MySQL 增量数据(如商品、订单数据)同步到 Elasticsearch。
171 | - 利用 RabbitMQ 官方提供的延迟队列插件实现延时任务场景比如订单超时自动取消、优惠券过期提醒、退款处理。
172 | - 消息推送系统引入 RabbitMQ 实现异步处理、削峰填谷和服务解耦,最高推送速度 10w/s,单日最大消息量 2000 万。
173 | - 使用 MAT 工具分析 dump 文件解决了广告服务新版本上线后导致大量的服务超时告警的问题。
174 | - 排查并解决扣费模块由于扣费父任务和反作弊子任务使用同一个线程池导致的死锁问题。
175 | - 基于 EasyExcel 实现广告投放数据的导入导出,通过 MyBatis 批处理插入数据,基于任务表实现异步。
176 | - 负责用户统计模块的开发,使用 CompletableFuture 并行加载后台用户统计模块的数据信息,平均相应时间从 3.5s 降低到 1s。
177 | - 基于 Sentinel 对核心场景(如用户登入注册、收货地址查询等)进行限流、降级,保护系统,提升用户体验。
178 | - 热门数据(如首页、热门博客)使用 Redis+Caffeine 两级缓存,解决了缓存击穿和穿透问题,查询速度毫秒级,QPS 30w+。
179 | - 使用 CompletableFuture 优化购物车查询模块,对获取用户信息、商品详情、优惠券信息等异步 RPC 调用进行编排,响应时间从 2s 降低为 0.2s。
180 | - 搭建 EasyMock 服务,用于模拟第三方平台接口,方便了在网络隔离情况下的接口对接工作。
181 | - 基于 SkyWalking + Elasticsearch 搭建分布式链路追踪系统实现全链路监控。
182 |
183 | **4、如果你觉得你的项目技术比较落后的话,可以自己私下进行改进。重要的是让项目比较有亮点,通过什么方式就无所谓了。**
184 |
185 | 项目经历这部分对于简历来说非常重要,[《Java 面试指北》](https://javaguide.cn/zhuanlan/java-mian-shi-zhi-bei.html)的面试准备篇有好几篇关于优化项目经历的文章,建议你仔细阅读一下,应该会对你有帮助。
186 |
187 | 
188 |
189 | **5、避免个人职责介绍都是围绕一个技术点来写,非常不可取。**
190 |
191 | 
192 |
193 | **6、避免模糊性描述,介绍要具体(技术+场景+效果),也要注意精简语言(避免堆砌技术词,省略不必要的描述)。**
194 |
195 | 
196 |
197 | ### 荣誉奖项(可选)
198 |
199 | 如果你有含金量比较高的竞赛(比如 ACM、阿里的天池大赛)的获奖经历的话,荣誉奖项这块内容一定要写一下!并且,你还可以将荣誉奖项这块内容适当往前放,放在一个更加显眼的位置。
200 |
201 | ### 校园经历(可选)
202 |
203 | 如果有比较亮眼的校园经历的话就简单写一下,没有就不写!
204 |
205 | ### 个人评价
206 |
207 | **个人评价就是对自己的解读,一定要用简洁的语言突出自己的特点和优势,避免废话!** 像勤奋、吃苦这些比较虚的东西就不要扯了,面试官看着这种个人评价就烦。
208 |
209 | 我们可以从下面几个角度来写个人评价:
210 |
211 | - 文档编写能力、学习能力、沟通能力、团队协作能力
212 | - 对待工作的态度以及个人的责任心
213 | - 能承受的工作压力以及对待困难的态度
214 | - 对技术的追求、对代码质量的追求
215 | - 分布式、高并发系统开发或维护经验
216 |
217 | 列举 3 个实际的例子:
218 |
219 | - 学习能力较强,大三参加国家软件设计大赛的时候快速上手 Python 写了一个可配置化的爬虫系统。
220 | - 具有团队协作精神,大三参加国家软件设计大赛的时候协调项目组内 5 名开发同学,并对编码遇到困难的同学提供帮助,最终顺利在 1 个月的时间完成项目的核心功能。
221 | - 项目经验丰富,在校期间主导过多个企业级项目的开发。
222 |
223 | ## STAR 法则和 FAB 法则
224 |
225 | ### STAR 法则(Situation Task Action Result)
226 |
227 | 相信大家一定听说过 STAR 法则。对于面试,你可以将这个法则用在自己的简历以及和面试官沟通交流的过程中。
228 |
229 | STAR 法则由下面 4 个单词组成(STAR 法则的名字就是由它们的首字母组成):
230 |
231 | - **Situation:** 情景。 事情是在什么情况下发生的?
232 | - **Task:** 任务。你的任务是什么?
233 | - **Action:** 行动。你做了什么?
234 | - **Result:** 结果。最终的结果怎样?
235 |
236 | ### FAB 法则(Feature Advantage Benefit)
237 |
238 | 除了 STAR 法则,你还需要了解在销售行业经常用到的一个叫做 FAB 的法则。
239 |
240 | FAB 法则由下面 3 个单词组成(FAB 法则的名字就是由它们的首字母组成):
241 |
242 | - **Feature:** 你的特征/优势是什么?
243 | - **Advantage:** 比别人好在哪些地方;
244 | - **Benefit:** 如果雇佣你,招聘方会得到什么好处。
245 |
246 | 简单来说,**FAB 法则主要是让你的面试官知道你的优势和你能为公司带来的价值。**
247 |
248 | ## 建议
249 |
250 | ### 避免页数过多
251 |
252 | 精简表述,突出亮点。校招简历建议不要超过 2 页,社招简历建议不要超过 3 页。如果内容过多的话,不需要非把内容压缩到一页,保持排版干净整洁就可以了。
253 |
254 | 看了几千份简历,有少部分同学的简历页数都接近 10 页了,让我头皮发麻。
255 |
256 | 
257 |
258 | ### 避免语义模糊
259 |
260 | 尽量避免主观表述,少一点语义模糊的形容词。表述要简洁明了,简历结构要清晰。
261 |
262 | 举例:
263 |
264 | - 不好的表述:我在团队中扮演了很重要的角色。
265 | - 好的表述:我作为后端技术负责人,领导团队完成后端项目的设计与开发。
266 |
267 | ### 注意简历样式
268 |
269 | 简历样式同样很重要,一定要注意!不必追求花里胡哨,但要尽量保证结构清晰且易于阅读。
270 |
271 | ### 其他
272 |
273 | - 一定要使用 PDF 格式投递,不要使用 Word 或者其他格式投递。这是最基本的!
274 | - 不会的东西就不要写在简历上了。注意简历真实性,适当润色没有问题。
275 | - 工作经历建议采用时间倒序的方式来介绍,实习经历建议将最有价值的放在最前面。
276 | - 将自己的项目经历完美的展示出来非常重要,重点是突出自己做了什么(挖掘亮点),而不是介绍项目是做什么的。
277 | - 项目经历建议以时间倒序排序,另外项目经历不在于多(精选 2~3 即可),而在于有亮点。
278 | - 准备面试的过程中应该将你写在简历上的东西作为重点,尤其是项目经历上和技能介绍上的。
279 | - 面试和工作是两回事,聪明的人会把面试官往自己擅长的领域领,其他人则被面试官牵着鼻子走。虽说面试和工作是两回事,但是你要想要获得自己满意的 offer ,你自身的实力必须要强。
280 |
281 | ## 简历修改
282 |
283 | 到目前为止,我至少帮助 **6000+** 位球友提供了免费的简历修改服务。由于个人精力有限,修改简历仅限加入星球的读者,需要帮看简历的话,可以加入 [**JavaGuide 官方知识星球**](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html#%E7%AE%80%E5%8E%86%E4%BF%AE%E6%94%B9)(点击链接查看详细介绍)。
284 |
285 | 
286 |
287 | 虽然收费只有培训班/训练营的百分之一,但是知识星球里的内容质量更高,提供的服务也更全面,非常适合准备 Java 面试和学习 Java 的同学。
288 |
289 | 下面是星球提供的部分服务(点击下方图片即可获取知识星球的详细介绍):
290 |
291 | [](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)
292 |
293 | 这里再提供一份限时专属优惠卷:
294 |
295 | 
296 |
--------------------------------------------------------------------------------
/docs/real-interview-experience/2025-alibaba-taotian-1.md:
--------------------------------------------------------------------------------
1 | 过去两年,阿里的招聘策略正在悄悄发生结构性变化。
2 |
3 | 一方面,阿里整体调整组织架构,形成淘天、阿里云、夸克等多个相对独立的业务方向;另一方面,不少应届生明显感受到:**阿里开始“卡学历”了。**
4 |
5 | 从我这段时间收到的反馈来看——
6 |
7 | - 淘天、高德、蚂蚁、阿里云等核心业务线 **对双非学历的过滤明显增强**,进入难度比前几年高很多;
8 | - 社招相对宽松,但学历同样会影响初筛通过率;
9 | - 部门之间的招聘标准差异极大: **有的部门卡得很严,有的部门依旧相对友好。**
10 |
11 | 还有一个投递小技巧:阿里不同部门之间的投递 **是可以分开算的**。 同学们别傻傻只投一个 JD,被拒了连第二次机会都没有。
12 |
13 | 至于面试流程,阿里整体依然保持“技术主导”,一般是:
14 |
15 | - 两轮或三轮技术面(极少出现四轮)
16 | - 技术通过后才会约 HR 面
17 | - HR 面依旧“玄学”,也需要重视
18 |
19 | **千万不要以为:技术面过了 = 稳了。**
20 |
21 | 这是一位来自四川大学的同学分享的 **阿里淘天一面** 面经。从整体感受来看,这次面试偏轻松,面试官提问比较随意(有点像是 KPI 面试),主要分为三个部分:
22 |
23 | 1. 非技术类问题(自我介绍、实习经历等)
24 | 2. 小型笔试题(在线编程,不是 LeetCode 偏难题)
25 | 3. 基础技术八股(如 GET/POST 区别、反射应用场景、SQL 优化等)
26 |
27 | 面试时长约 **一个半小时**。整体难度比较简单,最让我意外的事竟然考察了三道笔试题,不是那种纯粹的 LeetCode 问题,偏向于考察对 Java 语言的掌握,挺简单的!
28 |
29 | 
30 |
31 | > 这篇是24届同学的面经,当时分享过,但笔试题的答案需要重新完善一下。根据我的观察来看,阿里的面试一般不会考察这么多笔试题,所以说有点像是 KPI 面试。
32 |
33 | ## 非技术问题
34 |
35 | ### 自我介绍
36 |
37 | 面试时的自我介绍,其实是你给面试官的“第一印象浓缩版”。它不需要面面俱到,但要精准、自信地展现你的核心价值和与岗位的匹配度。通常控制在 1-2 分钟内比较合适。一个好的自我介绍应该包含这几点要素:
38 |
39 | 1. 用简单的话说清楚自己主要的技术栈于擅长的领域,例如 Java 后端开发、分布式系统开发;
40 | 2. 把重点放在自己的优势上,重点突出自己的能力,最好能用一个简短的例子支撑,例如:我比较擅长定位和解决复杂问题。在[某项目/实习]中,我曾通过[简述方法,如日志分析、源码追踪、压力测试]成功解决了[某个具体问题,如一个棘手的性能瓶颈/一个偶现的 Bug],将[某个指标]提升了[百分比/具体数值]。
41 | 3. 简要提及 1-2 个最能体现你能力和与岗位要求匹配的项目经历、实习经历或竞赛成绩。不需要展开细节,目的是引出面试官后续的提问。
42 | 4. 如果时间允许,可以非常简短地表达对所申请岗位的兴趣和对公司的向往,表明你是有备而来。
43 |
44 | ### 讲一下实习经历以及遇到的难点
45 |
46 | 实习经历的描述一定要避免空谈,尽量列举出你在实习期间取得的成就和具体贡献,使用具体的数据和指标来量化你的工作成果。
47 |
48 | 示例(这里假设项目细节放在实习经历这里介绍,你也可以选择将实习经历参与的项目放到项目经历中):
49 |
50 | 1. 参与项目订单模块的开发,负责订单创建、删除、查询等功能。
51 | 2. 排查并解决扣费模块由于扣费父任务和反作弊子任务使用同一个线程池导致的死锁问题。
52 | 3. 使用 CompletableFuture 并行加载后台用户统计模块的数据信息,平均相应时间从 3.5s 降低到 1s。
53 | 4. 使用 Redis+Caffeine 多级缓存优化热门数据(如首页、热门商品)的访问,解决了缓存击穿和穿透问题,查询速度毫秒级,QPS 30w+。
54 | 5. 在实习期间,共完成了 10 个需求开发和 5 个问题修复,编写了 2000 行代码和 100 个测试用例,通过了代码评审和测试验收,上线运行稳定无故障。
55 |
56 | 关于实习经历这块再多提一点。很多同学实习期间可能接触不到什么实际的开发任务,大部分时间可能都是在熟悉和维护项目。对于这种情况,你可以适当润色这段实习经历,找一些简单的功能研究透,包装成自己做的,很多同学都是这么做的。不过,我更建议你在实习期间主动去承担一些开发任务,甚至说对原系统进行优化改造。常见的性能优化方向实践(涉及到多线程、JVM、数据库/缓存、数据结构优化这 4 个常见的性能优化方向)总结请看:https://t.zsxq.com/0c1uS7q2Y (这块内容分享在 [知识星球](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html) 里了,你也可以自己按照我的思路总结,效果是一样的)。
57 |
58 | 
59 |
60 | ### 说一下自己以后的发展发向
61 |
62 | > [工作五年之后,对技术和业务的思考](https://javaguide.cn/high-quality-technical-articles/advanced-programmer/thinking-about-technology-and-business-after-five-years-of-work.html) 这篇文章是我在两年前看到的一篇对我触动比较深的文章,介绍了作者工作五年之后,对于技术和业务的深度思考。
63 |
64 | 建议:
65 |
66 | - 如果你的想法是干个两三年就跳槽或者换行业的话,尽量不要直说,一定要体现出自己的稳定性。
67 | - 绝大部分人的职业目标都可以从技术精进、项目管理和个人影响力三个方面来回答。
68 |
69 | 参考回答:
70 |
71 | 在接下来的五年里,我的职业目标主要集中在技术精进、项目管理和个人影响力三个方面。
72 |
73 | 首先,技术上,我会深入专研 Java 后端开发,争取早日成为 Java 后端开发领域的技术专家。为此,我将不断深入学习 Java 的核心技术和最新技术进展。
74 |
75 | 其次,项目管理上,我会慢慢尝试着在工作中承担更多的项目管理职责,积累项目管理经验,争取早日能够拥有独立带领中小型项目的能力。
76 |
77 | 最后,个人影响力上,我希望通过我的专业技能对公司的核心产品做出重大贡献,解决技术难题,提升产品性能和用户体验。同时,我也计划积极参与贡献开源项目和技术社区。
78 |
79 | ## 笔试题
80 |
81 | 笔试的形式是给你的邮箱发个链接,点进去就是一个在线的编辑器。
82 |
83 | ### 写三种单例模式的实现方式
84 |
85 | **1、枚举(推荐)**:
86 |
87 | ```java
88 | public enum Singleton {
89 | INSTANCE;
90 | public void doSomething(String str) {
91 | System.out.println(str);
92 | }
93 | }
94 | ```
95 |
96 | 《Effective Java》作者推荐的一种单例实现方式,简单高效,无需加锁,线程安全,可以避免通过反射破坏枚举单例。
97 |
98 | **2、静态内部类(推荐)**:
99 |
100 | ```java
101 | public class Singleton {
102 | // 私有化构造方法
103 | private Singleton() {
104 | }
105 |
106 | // 对外提供获取实例的公共方法
107 | public static Singleton getInstance() {
108 | return SingletonInner.INSTANCE;
109 | }
110 |
111 | // 定义静态内部类
112 | private static class SingletonInner{
113 | private final static Singleton INSTANCE = new Singleton();
114 | }
115 |
116 | }
117 | ```
118 |
119 | 当外部类 `Singleton` 被加载的时候,并不会创建静态内部类 `SingletonInner` 的实例对象。只有当调用 `getInstance()` 方法时,`SingletonInner` 才会被加载,这个时候才会创建单例对象 `INSTANCE`。`INSTANCE` 的唯一性、创建过程的线程安全性,都由 JVM 来保证。
120 |
121 | 这种方式同样简单高效,无需加锁,线程安全,并且支持延时加载。
122 |
123 | **3、双重校验锁**:
124 |
125 | ```java
126 | public class Singleton {
127 |
128 | private volatile static Singleton uniqueInstance;
129 |
130 | // 私有化构造方法
131 | private Singleton() {
132 | }
133 |
134 | public static Singleton getUniqueInstance() {
135 | //先判断对象是否已经实例过,没有实例化过才进入加锁代码
136 | if (uniqueInstance == null) {
137 | //类对象加锁
138 | synchronized (Singleton.class) {
139 | if (uniqueInstance == null) {
140 | uniqueInstance = new Singleton();
141 | }
142 | }
143 | }
144 | return uniqueInstance;
145 | }
146 | }
147 | ```
148 |
149 | `uniqueInstance` 采用 `volatile` 关键字修饰也是很有必要的, `uniqueInstance = new Singleton();` 这段代码其实是分为三步执行:
150 |
151 | 1. 为 `uniqueInstance` 分配内存空间
152 | 2. 初始化 `uniqueInstance`
153 | 3. 将 `uniqueInstance` 指向分配的内存地址
154 |
155 | 但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 `getUniqueInstance`() 后发现 `uniqueInstance` 不为空,因此返回 `uniqueInstance`,但此时 `uniqueInstance` 还未被初始化。
156 |
157 | 这种方式实现起来较麻烦,但同样线程安全,支持延时加载。
158 |
159 | 推荐阅读:[Java 并发常见面试题总结(中)](https://javaguide.cn/java/concurrent/java-concurrent-questions-02.html)。
160 |
161 | ### 编号为 1-n 的循环报 1-3,报道 3 的出列,求最后一人的编号
162 |
163 | 问题描述:编号为 1-n 的循环报 1-3,报道 3 的出列,求最后一人的编号
164 |
165 | 标准的约瑟夫环问题。有 n 个人围成一个圈,从某个人开始报数,报到某个特定数字(本题中为 3 )时该人出圈,直到只剩下一个人为止。
166 |
167 | 解决约瑟夫环问题,可以分两种情况:
168 |
169 | 1. 我们要求出最后留下的那个人的编号(本题要求)。
170 | 2. 求全过程,即要算出每轮出局的人。
171 |
172 | 有多种方法可以解决约瑟夫环问题,其中一种是使用递归的方式。
173 |
174 | 本题的约瑟夫环问题的公式为: **(f(n - 1, k) + k - 1) % n + 1** 。f(n,k) 表示 n 个人报数,每次报数报到 k 的人出局,最终最后一个人的编号。
175 |
176 | 假设 n 为 10,k 为 3 ,逆推过程如下:
177 |
178 | - f(1, 3) = 1(当 n = 1 时,只有一个人,最后一人的编号就为 1);
179 | - f(2,3) =(f(1,3) + 3 -1)%2 + 1 = 3%2 + 1 = 2(当 n = 2 时,最后一人的编号为 2);
180 | - f(3,3) = (f(2,3) + 3 - 1))%3 + 1 = 4%3 + 1 = 2(当 n = 3 时,最后一人的编号为 2);
181 | - f(4,3) = (f(3,3) + 3 - 1) % 4 + 1 = 4%4 + 1 = 1(当 n = 4 时,最后一人的编号为 1);
182 | - ...
183 | - f(10,3) = 3 (当 n = 10 时,最后一人的编号为 4);
184 |
185 | 这个问题对应[剑指 Offer 62. 圆圈中最后剩下的数字](https://leetcode.cn/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/) ,两者意思是类似的,比较简单。
186 |
187 | ```java
188 | public class Josephus {
189 |
190 | // 定义递归函数
191 | public static int f(int n, int k) {
192 | // 如果只有一个人,则返回 1
193 | if (n == 1) {
194 | return 1;
195 | }
196 | return (f(n - 1, k) + k - 1) % n + 1;
197 | }
198 |
199 | public static void main(String[] args) {
200 | int n = 10;
201 | int k = 3;
202 | System.out.println("最后留下的那个人的编号是:" + f(n, k));
203 | }
204 | }
205 | ```
206 |
207 | 输出:
208 |
209 | ```plain
210 | 最后留下的那个人的编号是:4
211 | ```
212 |
213 | ### 写两个线程打印 1-n,一个线程打印奇数,一个线程打印偶数
214 |
215 | 问题描述:写两个线程打印 1-100,一个线程打印奇数,一个线程打印偶数。
216 |
217 | 这道题的实现方式还是挺多的,线程的等待/通知机制(`wait()`和`notify()`)、信号量 `Semaphore`等都可以实现。
218 |
219 | #### synchronized+wait/notify 实现
220 |
221 | 我们先定义一个类 `ParityPrinter` 用于打印奇数和偶数。
222 |
223 | ```java
224 | public class ParityPrinter {
225 | private final int max;
226 | // 从1开始计数
227 | private int count = 1;
228 | private final Object lock = new Object();
229 |
230 | public ParityPrinter(int max) {
231 | this.max = max;
232 | }
233 |
234 | public void printOdd() {
235 | print(true);
236 | }
237 |
238 | public void printEven() {
239 | print(false);
240 | }
241 |
242 | private void print(boolean isOdd) {
243 | for (int i = 1; i <= max; i += 2) {
244 | // 确保同一时间只有一个线程可以执行内部代码块
245 | synchronized (lock) {
246 | // 等待直到轮到当前线程打印
247 | // count为奇数时奇数线程打印,count为偶数时偶数线程打印
248 | while (isOdd == (count % 2 == 0)) {
249 | try {
250 | lock.wait();
251 | } catch (InterruptedException e) {
252 | Thread.currentThread().interrupt();
253 | return;
254 | }
255 | }
256 | System.out.println(Thread.currentThread().getName() + " : " + count++);
257 | // 通知等待的线程
258 | lock.notify();
259 | }
260 | }
261 | }
262 | }
263 | ```
264 |
265 | `ParityPrinter`类中的变量和方法介绍:
266 |
267 | - `max`: 最大打印数值,由构造函数传入。
268 | - `count`: 从 1 开始的计数器,用于追踪当前打印到的数字。
269 | - `lock`: 一个对象锁,用于线程间的同步控制。
270 | - `printOdd()`和`printEven()`: 分别启动打印奇数和偶数的逻辑,实际上调用了私有的`print()`方法,并传入线程名称前缀和一个布尔值表示打印奇数(`true`)还是偶数(`false`)。
271 |
272 | 接着,我们创建两个线程,一个负责打印奇数,一个负责打印偶数。
273 |
274 | ```java
275 | // 打印 1-100
276 | ParityPrinter printer = new ParityPrinter(100);
277 | // 创建打印奇数和偶数的线程
278 | Thread t1 = new Thread(printer::printOdd, "Odd");
279 | Thread t2 = new Thread(printer::printEven, "Even");
280 | t1.start();
281 | t2.start();
282 | ```
283 |
284 | 输出:
285 |
286 | ```plain
287 | Odd : 1
288 | Even : 2
289 | Odd : 3
290 | Even : 4
291 | Odd : 5
292 | ...
293 | Odd : 95
294 | Even : 96
295 | Odd : 97
296 | Even : 98
297 | Odd : 99
298 | Even : 100
299 | ```
300 |
301 | #### Semaphore 实现
302 |
303 | 如果想要把上面的代码修改为基于 `Semaphore`实现也挺简单的。
304 |
305 | ```java
306 | public class ParityPrinter {
307 | private final int max;
308 | private int count = 1;
309 | // 初始为1,奇数线程先获取
310 | private final Semaphore oddSemaphore = new Semaphore(1);
311 | // 初始为0,偶数线程等待
312 | private final Semaphore evenSemaphore = new Semaphore(0);
313 |
314 | public ParityPrinter(int max) {
315 | this.max = max;
316 | }
317 |
318 | public void printOdd() {
319 | print(oddSemaphore, evenSemaphore);
320 | }
321 |
322 | public void printEven() {
323 | print(evenSemaphore, oddSemaphore);
324 | }
325 |
326 | private void print(Semaphore currentSemaphore, Semaphore nextSemaphore) {
327 | for (int i = 1; i <= max; i += 2) {
328 | try {
329 | // 获取当前信号量
330 | currentSemaphore.acquire();
331 | System.out.println(Thread.currentThread().getName() + " : " + count++);
332 | // 释放下一个信号量
333 | nextSemaphore.release();
334 | } catch (InterruptedException e) {
335 | Thread.currentThread().interrupt();
336 | return;
337 | }
338 | }
339 | }
340 | }
341 | ```
342 |
343 | 可以看到,我们这里使用两个信号量 `oddSemaphore` 和 `evenSemaphore` 来确保两个线程交替执行。`oddSemaphore` 信号量先获取,也就是先执行奇数输出。一个线程执行完之后,就释放下一个信号量。
344 |
345 | ## 技术问题
346 |
347 | ### GET 和 POST 的区别
348 |
349 | 这个问题在知乎上被讨论的挺火热的,地址: 。
350 |
351 | 
352 |
353 | GET 和 POST 是 HTTP 协议中两种常用的请求方法,它们在不同的场景和目的下有不同的特点和用法。一般来说,可以从以下几个方面来区分它们(重点搞清两者在语义上的区别即可):
354 |
355 | - 语义(主要区别):GET 通常用于获取或查询资源,而 POST 通常用于创建或修改资源。
356 | - 幂等:GET 请求是幂等的,即多次重复执行不会改变资源的状态,而 POST 请求是不幂等的,即每次执行可能会产生不同的结果或影响资源的状态。
357 | - 格式:GET 请求的参数通常放在 URL 中,形成查询字符串(querystring),而 POST 请求的参数通常放在请求体(body)中,可以有多种编码格式,如 application/x-www-form-urlencoded、multipart/form-data、application/json 等。GET 请求的 URL 长度受到浏览器和服务器的限制,而 POST 请求的 body 大小则没有明确的限制。不过,实际上 GET 请求也可以用 body 传输数据,只是并不推荐这样做,因为这样可能会导致一些兼容性或者语义上的问题。
358 | - 缓存:由于 GET 请求是幂等的,它可以被浏览器或其他中间节点(如代理、网关)缓存起来,以提高性能和效率。而 POST 请求则不适合被缓存,因为它可能有副作用,每次执行可能需要实时的响应。
359 | - 安全性:GET 请求和 POST 请求如果使用 HTTP 协议的话,那都不安全,因为 HTTP 协议本身是明文传输的,必须使用 HTTPS 协议来加密传输数据。另外,GET 请求相比 POST 请求更容易泄露敏感数据,因为 GET 请求的参数通常放在 URL 中。
360 |
361 | 再次提示,重点搞清两者在语义上的区别即可,实际使用过程中,也是通过语义来区分使用 GET 还是 POST。不过,也有一些项目所有的请求都用 POST,这个并不是固定的,项目组达成共识即可。
362 |
363 | ### 如何优化 MySQL 查询
364 |
365 | 回答这个问题的核心是先提到开启慢查询日志和使用 EXPLAIN 进行执行计划分析。
366 |
367 | 慢查询日志捕获那些执行时间超过阈值的SQL语句,这是发现问题的起点。拿到慢SQL后,用 `EXPLAIN` 关键字分析这条SQL的执行计划,分析原因。
368 |
369 | 基于 `EXPLAIN` 的分析结果,进行针对性优化。比较常见的 SQL优化手段如下:
370 |
371 | 1. 索引优化(最常用)
372 | 2. 避免 `SELECT *`
373 | 3. 深度分页优化
374 | 4. 尽量避免多表做 join
375 | 5. 选择合适的字段类型
376 | 6. ......
377 |
378 | [《Java 面试指北》](https://mp.weixin.qq.com/s/JNJIKnUMc0MU_i2VNXb50A)的技术面试题篇总结了常见的高并发面试问题,其中包含常见的 SQL 优化手段,内容非常全面。
379 |
380 | 
381 |
382 | 推荐顺带看看下面这两篇文章:
383 |
384 | - [MySQL 高性能优化规范建议总结](https://javaguide.cn/database/mysql/mysql-high-performance-optimization-specification-recommendations.html)
385 | - [MySQL 执行计划分析](https://javaguide.cn/database/mysql/mysql-query-execution-plan.html)
386 |
387 | ### 反射及应用场景
388 |
389 | 简单来说,Java 反射 (Reflection) 是一种**在程序运行时,动态地获取类的信息并操作类或对象(方法、属性)的能力**。
390 |
391 | 通常情况下,我们写的代码在编译时类型就已经确定了,要调用哪个方法、访问哪个字段都是明确的。但反射允许我们在**运行时**才去探知一个类有哪些方法、哪些属性、它的构造函数是怎样的,甚至可以动态地创建对象、调用方法或修改属性,哪怕这些方法或属性是私有的。
392 |
393 | 正是这种在运行时“反观自身”并进行操作的能力,使得反射成为许多**通用框架和库的基石**。它让代码更加灵活,能够处理在编译时未知的类型。
394 |
395 | 我们平时写业务代码可能很少直接跟 Java 的反射(Reflection)打交道。但你可能没意识到,你天天都在享受反射带来的便利!**很多流行的框架,比如 Spring/Spring Boot、MyBatis 等,底层都大量运用了反射机制**,这才让它们能够那么灵活和强大。
396 |
397 | 下面简单列举几个最场景的场景帮助大家理解。
398 |
399 | **1.依赖注入与控制反转(IoC)**
400 |
401 | 以 Spring/Spring Boot 为代表的 IoC 框架,会在启动时扫描带有特定注解(如 `@Component`, `@Service`, `@Repository`, `@Controller`)的类,利用反射实例化对象(Bean),并通过反射注入依赖(如 `@Autowired`、构造器注入等)。
402 |
403 | **2.注解处理**
404 |
405 | 注解本身只是个“标记”,得有人去读这个标记才知道要做什么。反射就是那个“读取器”。框架通过反射检查类、方法、字段上有没有特定的注解,然后根据注解信息执行相应的逻辑。比如,看到 `@Value`,就用反射读取注解内容,去配置文件找对应的值,再用反射把值设置给字段。
406 |
407 | **3.动态代理与 AOP**
408 |
409 | 想在调用某个方法前后自动加点料(比如打日志、开事务、做权限检查)?AOP(面向切面编程)就是干这个的,而动态代理是实现 AOP 的常用手段。JDK 自带的动态代理(Proxy 和 InvocationHandler)就离不开反射。代理对象在内部调用真实对象的方法时,就是通过反射的 `Method.invoke` 来完成的。
410 |
411 | ```java
412 | public class DebugInvocationHandler implements InvocationHandler {
413 | private final Object target; // 真实对象
414 |
415 | public DebugInvocationHandler(Object target) { this.target = target; }
416 |
417 | // proxy: 代理对象, method: 被调用的方法, args: 方法参数
418 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
419 | System.out.println("切面逻辑:调用方法 " + method.getName() + " 之前");
420 | // 通过反射调用真实对象的同名方法
421 | Object result = method.invoke(target, args);
422 | System.out.println("切面逻辑:调用方法 " + method.getName() + " 之后");
423 | return result;
424 | }
425 | }
426 | ```
427 |
428 | **4.对象关系映射(ORM)**
429 |
430 | 像 MyBatis、Hibernate 这种框架,能帮你把数据库查出来的一行行数据,自动变成一个个 Java 对象。它是怎么知道数据库字段对应哪个 Java 属性的?还是靠反射。它通过反射获取 Java 类的属性列表,然后把查询结果按名字或配置对应起来,再用反射调用 setter 或直接修改字段值。反过来,保存对象到数据库时,也是用反射读取属性值来拼 SQL。
--------------------------------------------------------------------------------
/docs/other/test-development.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 测试开发常见面试题总结
3 | category: 测试开发
4 | tag:
5 | - 测试开发
6 | - 软件测试
7 | head:
8 | - - meta
9 | - name: keywords
10 | content: 测试开发面试题,测开高频问题,软件测试面试题,测试用例设计,自动化测试,压力测试,Web测试,白盒测试,黑盒测试,端到端测试,测试左移,测试类型
11 | - - meta
12 | - name: description
13 | content: 测试开发高频面试题系统总结,覆盖测试开发基础、Bug处理与协作、测试用例设计、自动化测试、压力测试、测试类型区分等实战问题与参考答案,适合冲击中大厂测开岗位的同学快速复盘与刷题。
14 | ---
15 |
16 |
17 |
18 | 很多读者选择了测开方向来冲一波中大厂。在国内当前的测开面试中,面试官不仅会考察测试领域的基础知识和实践能力,往往也会考察候选人的后端开发功底。这是因为优秀的测开工程师需要深入理解系统架构,能够编写高质量的自动化测试代码,进行性能测试分析,甚至参与到测试工具和平台的开发中。所以,像多线程、Redis、MySQL 等后端开发的核心知识点,在测开面试中被问到是常态。
19 |
20 | 我这里整理并总结了一些在测开面试中偏测试方向的高频问题,并附上详细的参考答案(如答案过长,还会提供简化版回答):
21 |
22 | 1. **测试开发基础**
23 | - 为什么想做测开?/ 为什么从开发转测开?
24 | - 你认为软件测试的核心竞争力是什么?
25 | - 什么是软件测试?为什么要做?
26 | - 软件测试的流程是怎样的?
27 | - 你如何看待测试覆盖率这个指标?
28 | - 什么是“测试左移”(Shift-Left Testing)?你在项目中是如何实践的?
29 | 2. **Bug 处理与协作**
30 | - 如果有一个 Bug 不能复现,怎么去跟开发沟通?
31 | - 你在实习/项目期间测出过什么印象深刻的 Bug?
32 | 3. **测试用例**
33 | - 什么是测试用例?一个好的测试用例需要满足什么标准?
34 | - 怎么设计测试用例?/ 常用的测试用例设计方法有哪些?
35 | - 设计 xxx 场景(例如电商直播间、充值、数据迁移系统)的测试用例
36 | 4. **测试类型**
37 | - 什么是自动化测试?为什么需要?
38 | - 什么是 Web 应用的自动化测试?怎么做?
39 | - 怎么进行压测?
40 | - 白盒测试和黑盒测试的主要区别是什么?
41 | - 什么是单元测试?集成测试?系统测试?回归测试?验收测试?
42 | - 单元测试、集成测试、系统测试、回归测试、验收测试最重要的是那一步?
43 | - 什么是端到端测试?
44 | - 移动端与 Web 端测试有什么区别?
45 | - 如何测试 Web 应用的安全性?你会关注哪些常见漏洞?
46 | - API 功能测试如何做?
47 | - 什么是正向测试和反向测试?能否举例说明
48 | - 可用性测试和用户体验测试有什么区别?
49 |
50 | ## 测试开发基础
51 |
52 | ### 为什么想做测开?/ 为什么从开发转测开?
53 |
54 | 我在之前的 Java 后端开发经历中,积累了比较扎实的编程基础和系统设计能力(可以具体提 1-2 个技术点,如熟悉微服务、数据库等)。我发现自己对保证软件质量、提升系统稳定性有浓厚的兴趣。我觉得测开岗位能很好地结合我的开发背景和对软件质量保障的关注。
55 |
56 | 我个人的话,也比较注重细节,有耐心,也乐于从用户的角度去思考问题,我觉得这些特质也挺适合做质量保障工作的。
57 |
58 | 我认为测开不仅仅是功能测试,更需要通过技术手段,比如自动化测试、性能测试、构建 CI/CD 流程中的质量门禁,来提高测试效率和深度,从源头保障软件质量。
59 |
60 | ### 你认为软件测试的核心竞争力是什么?
61 |
62 | 我认为优秀测试工程师的核心竞争力主要体现在三点:
63 |
64 | 1. **强大的质量意识和业务理解能力**,能从用户和业务视角拆解需求、识别高风险场景;
65 | 2. **系统性设计测试策略和用例的能力**,合理运用等价类、边界值、组合覆盖等方法,把有限的测试资源用在最关键的地方;
66 | 3. **工程化与自动化能力**,熟悉测试工具、脚本和 CI/CD,把测试深度嵌入开发流程,实现持续、可重复、可量化的质量保障。
67 |
68 | 简单说,就是:**懂业务、会分析、能工程化落地的质量保障能力**。
69 |
70 | ### 什么是软件测试?为什么要做?
71 |
72 | 软件测试是通过设计和执行一系列用例,在不同场景下运行软件,去发现缺陷、验证功能和质量是否符合需求与标准的系统化活动。
73 |
74 | 回答这个问题的时候需要注意:**一定不要认为软件测试就是在找 bug。**
75 |
76 | 为什么要做它?主要有三个原因:
77 |
78 | 1. **控制风险,降低成本**:在开发阶段就把 bug 找出来,远比等它在线上造成事故再修复要便宜得多。这是一种风险控制。
79 | 2. **验证业务需求**:确保我们开发的功能,确实是产品和用户想要的,**没做偏**。
80 | 3. **提供质量反馈,赋能持续交付**:在敏捷开发里,没有自动化测试作为安全网,我们根本不敢频繁发布新版本。测试是持续交付的基础。
81 |
82 | 一句话概括:**软件测试就是用有计划的方式尽早暴露问题、评估质量,让软件在交付给用户前尽可能“可预测、可依赖”。**
83 |
84 | ### 软件测试的流程是怎样的?
85 |
86 | 一个相对完整的软件测试流程,我理解大致包含这几个阶段:
87 |
88 | - **需求分析:** 首先是深入理解需求文档,分析需求的可测性,有时候还需要参与需求评审,尽早发现需求层面的问题。
89 | - **测试计划:** 然后是制定测试计划,明确测试范围、测试策略(比如哪些功能需要重点测、采用什么测试方法)、资源投入、时间安排以及风险评估。
90 | - **测试设计:** 接下来是设计测试用例(Test Case),这需要用到像等价类、边界值、场景法等方法,确保用例能有效覆盖需求。同时,可能还需要准备测试数据。
91 | - **测试执行:** 之后就是执行测试用例,记录测试结果,发现 Bug 要及时提交到缺陷管理系统(比如 Jira、禅道)。
92 | - **缺陷跟踪与管理:** 对提交的 Bug 进行跟踪,验证开发修复后的 Bug,直到 Bug 关闭。
93 | - **测试报告与评估:** 测试结束后,需要输出测试报告,总结测试情况,评估产品质量风险,给出是否可以上线的建议。
94 | - **回归测试:** 在修复 Bug 或有新版本迭代时,还需要进行回归测试,确保旧功能没问题,新改动不引入新问题。
95 |
96 | 在敏捷开发模式下,这些流程可能会更快速地迭代进行。
97 |
98 | **简化版回答:** 通常我会先和 PM、开发团队沟通需求,然后根据需求来制定测试计划和设计测试用例,之后就是执行测试用例并记录发现的问题。缺陷修复后要做回归测试,确保没有引入新问题,最后再评估版本是否满足上线标准,才会提交上线。
99 |
100 | ### 你如何看待测试覆盖率这个指标?
101 |
102 | 我认为测试覆盖率是一个**有用但有限、甚至有时带点欺骗性**的指标。
103 |
104 | **高覆盖率 ≠ 高质量测试**:覆盖率只反映"代码是否被执行",无法验证:
105 |
106 | - 断言是否充分合理
107 | - 边界条件和异常场景是否覆盖
108 | - 业务逻辑是否被正确验证(例如,一个只调用方法但不验证结果的测试也能提高覆盖率)
109 |
110 | **低覆盖率 ≈ 测试很可能不充分**:核心模块若覆盖率低于 70%(尤其是新功能),通常意味着测试深度不足,但也要结合业务风险评估(如简单 DTO 类覆盖率低可能影响不大)。
111 |
112 | **比较推荐的实践原则**:
113 |
114 | - **设定合理阈值**:核心模块要求 80%+行覆盖,关键路径 100%分支覆盖;非核心模块 60%+即可、
115 | - **关注"有意义"的覆盖率**:结合变更覆盖率(只关注本次修改的代码)和关键路径覆盖率。
116 | - **质量优先于数字**:宁可接受 70%的高质量测试,也不要 90%的"形式主义"测试。
117 | - **工具辅助但不盲从**:使用 JaCoCo/Sonar 等工具监控趋势,但不设硬性 KPI。
118 |
119 | **一句话总结**:覆盖率是发现盲区的探照灯,而非质量合格的证明书。它最有价值的用法是识别"明显缺失测试"的区域,而非证明"测试充分"。
120 |
121 | ### 什么是“测试左移”(Shift-Left Testing)?你在项目中是如何实践的?
122 |
123 | **测试左移**的本质是:**将质量保障活动尽量前移到软件生命周期的早期阶段**,通过“早发现、早修复”来降低缺陷修复成本(有研究表明,需求阶段发现缺陷的成本大约只有生产环境的 1/100)。
124 |
125 | 也就是说,不再把测试只放在“开发完之后”,而是在**需求、设计、开发阶段就开始介入质量活动**,让缺陷在最早的环节被发现和预防。
126 |
127 | **比较推荐的实践原则**:
128 |
129 | 1. **早介入需求与设计**
130 | - 测试人员参与需求评审、设计评审,
131 | - 提前梳理业务流程、异常场景,
132 | - 发现需求歧义、遗漏的边界条件。
133 | 2. **开发阶段的自测与单元测试**
134 | - 要求开发为核心业务逻辑和关键模块编写单元测试、基础集成测试;
135 | - 将单元测试通过率作为代码提交 / 合并的前置条件之一;
136 | - 倡导“先写用例,再写实现”的思路(TDD/UT 驱动,可选,国内较难实现)。
137 | 3. **CI 中尽早运行自动化检查** :
138 | - 对每次提交或合并请求自动执行:静态代码扫描(如 Sonar 等)、单元测试和关键集成测试;
139 | - 让问题在“提交当下”就暴露,而不是拖到系统测试甚至上线前。
140 | 4. **测试用例提前设计**
141 | - 在需求/设计阶段就开始梳理测试思路和关键用例,
142 | - 指导后续自动化脚本和手工测试计划。
143 |
144 | 可以总结为一句话:测试左移就是**把“发现问题”尽量变成“预防问题”**,让测试从“事后把关”变成“全流程参与”。
145 |
146 | ## Bug 处理与协作
147 |
148 | ### 如果有一个 Bug 不能复现,怎么去跟开发沟通?
149 |
150 | - **先自我排查:** 首先,我会自己再努力尝试复现几次,确保不是我操作失误或者遗漏了什么前提条件。我会尝试更换不同的测试环境、不同的测试账号或数据,看看问题是否在特定条件下出现。
151 | - **收集详细信息:** 如果确实难以稳定复现,我会尽可能详细地记录下当时发现问题的场景信息,比如:
152 | - **具体的操作步骤:** 尽可能回忆并描述清楚每一步操作。
153 | - **环境信息:** 包括操作系统、浏览器版本(如果是 Web 端)、App 版本(如果是移动端)、网络状况、测试设备型号等。
154 | - **测试数据:** 当时使用的具体账号或关键数据是什么。
155 | - **日志信息:** 查看并附上当时客户端(浏览器控制台、App 日志)和服务器端的相关日志,特别是报错信息或异常堆栈。
156 | - **截图或录屏:** 如果有当时问题现象的截图或录屏是最好的。
157 | - **出现频率:** 大概尝试了多少次,出现了几次,或者是在什么特定时间段出现的。
158 | - **有效沟通:** 然后,我会整理好这些信息,找到对应的开发同学,不是直接去质问,而是客观地描述:‘我遇到了这样一个现象(描述现象),尝试了这些方法(描述排查步骤)后暂时无法稳定复现,但这里有当时收集到的信息(展示日志、截图等),你看这些信息对你定位问题有没有帮助?或者我们是不是可以一起看看,或者你那边有没有更详细的日志能查到当时的请求?’
159 | - **协作解决:** 目的是提供尽可能多的线索,协助开发去分析可能的原因,比如是不是偶发的环境问题、并发问题、脏数据问题等,共同把问题解决掉。
160 |
161 | **简化版回答:** 我自己会先在不同的情况下,多次复现尝试。如果确实难以稳定复现,我会尽量给开发提供当时的详细操作步骤、环境信息、相关日志和截图,甚至录屏等必要信息,这可以帮助开发更好地排查和定位。同时,我也会协助开发去分析可能的原因。
162 |
163 | ### 你在实习/项目期间测出过什么印象深刻的 Bug?
164 |
165 | 一定要讲清楚具体场景(例如优惠卷计算错误、重复点击多次提交订单) + 原因(分析一下导致这个问题的原因) + 修复过程(这个问题最重视如何修复的,后续如何避免)。
166 |
167 | 最好是能够体现个人积极思考和追根究底的态度。
168 |
169 | ## 测试用例
170 |
171 | ### 什么是测试用例?一个好的测试用例需要满足什么标准?
172 |
173 | 测试用例是用来验证软件是否符合需求的一组明确的测试步骤和结果,它相当于一个计划书,指导你在特定条件下执行测试,并观察系统的表现是否与预期一致。简单来说,测试用例就是一个操作指南,告诉你:
174 |
175 | - 在什么条件下测试(前置条件)。
176 | - 做什么操作(输入或操作步骤)。
177 | - 期望看到什么结果(预期输出)。
178 |
179 | 例如,测一个登录功能,一个测试用例可能就是:输入正确的用户名和密码,点击登录,预期结果是成功登录并跳转到主页。另一个用例可能是:输入错误的密码,点击登录,预期结果是提示密码错误。
180 |
181 | 一个好的测试用例,不是单纯指它能发现问题,而是它能帮助你全面、精准地验证软件的功能和质量。打个比方,如果把软件看作一个池塘,软件缺陷是池塘里的鱼,那么“好的”测试用例就是一张编织得紧密、覆盖全面的渔网——只要池塘里有鱼,这张网就能捞上来;而如果捞不到鱼,就可以确定池塘里没有鱼。
182 |
183 | 所以,好的测试用例的关键不在于发现了多少缺陷,而是它是否全面、科学,能确保测试需求被完整覆盖。具体来说,一个好的测试用例应该满足以下几个标准:
184 |
185 | - **覆盖全面**:把该测的需求、功能点都包含了。比如测试一个表单输入框,测试用例集合需要覆盖正常输入、特殊字符输入、空输入、超长输入、非法输入等所有可能的场景,而不能只测试一种输入情况。
186 | - **分组精准**:通过等价类划分,减少冗余测试,确保效率和准确性。比如测试一个年龄输入框,18-60 的范围可以作为一个等价类,测试一个值(比如 25)即可,而不需要测试每个值。
187 | - **边界清晰**:把所有可能的边界、异常情况都考虑并测试到了。比如测试一个输入要求“1-100”的数值框时,边界值测试包括 0、1、100、101,异常情况测试包括负数、字符串输入、空值等。
188 |
189 | **简化版回答:** 简单来说,测试用例就是测试说明书,它告诉测试人员:"在什么条件下,怎么操作,应该得到什么结果"。一个好的测试用例应该满足覆盖全面、分组精准和边界清晰这三个条件。
190 |
191 | ### 怎么设计测试用例?/ 常用的测试用例设计方法有哪些?
192 |
193 | 设计测试用例的目标是全面覆盖软件需求,同时确保测试效率。我通常会结合多种方法,以达到最佳的测试覆盖率:
194 |
195 | - **等价类划分法:** 将所有可能的输入数据划分为若干个互不相交的“等价类”,然后从每个等价类中选取一个或少量具有代表性的数据作为测试用例。例如,测试一个处理用户注册的模块,针对“用户名”输入框,可以划分等价类为:有效用户名(3-20 个字符)、无效用户名(小于 3 个字符、大于 20 个字符、包含非法字符)。
196 | - **边界值分析法:** 作为等价类划分的补充,它专注于测试等价类边界上的值,因为错误往往更容易发生在边界处。例如,对于上述用户名长度的例子,我们需要测试 3 个字符、20 个字符,以及 2 个字符、21 个字符这些边界值。
197 | - **判定表法(决策表法):** 当存在多个输入条件,且这些条件的组合会产生不同的输出结果时,使用判定表法可以清晰地展示各种情况。例如,一个机票预订系统,影响预订价格的因素可能有:是否是会员、是否是节假日、提前预订天数等。通过判定表,可以列出所有条件组合及其对应的价格计算规则。
198 | - **场景法(流程图法):** 模拟用户在实际使用软件时的各种场景和操作流程来设计测试用例。通过绘制流程图,可以清晰地展示用户可能的操作路径,包括主流程和各种异常分支流程。例如,测试一个在线购物的流程,可以模拟用户从浏览商品、加入购物车、提交订单、支付、确认收货等一系列操作。
199 | - **错误推测法:** 凭借测试人员的经验、知识和直觉,推测程序中可能存在的错误,并以此设计测试用例。例如,针对文件上传功能,可以推测可能出现的错误包括:上传空文件、上传超大文件、上传病毒文件、上传与指定格式不符的文件等。
200 | - **状态迁移法:** 适用于测试具有多种状态且状态会发生变化的对象。例如,测试一个电梯控制系统,电梯有静止、上升、下降等状态,状态之间会根据用户的操作(如按下楼层按钮)发生迁移。需要测试在各种状态下,电梯的行为是否符合预期,以及状态迁移是否正确。
201 |
202 | 在实际工作中,通常需要根据被测功能的特点,灵活地组合使用这些方法,以达到最佳的测试效果,发现潜在的缺陷。同时,测试用例的设计也需要不断地评审和更新,以适应软件的迭代和变化。
203 |
204 | **简化版回答:** 设计测试用例的目标是全面覆盖需求,同时保证效率。我通常会结合几种方法,比如等价类划分,将输入数据分成不同的“等价类”,从每个类中选代表性的数据测试;边界值分析,重点测试等价类边界上的值;以及错误推测,基于经验,推测程序可能出错的地方来设计用例。
205 |
206 | ### 设计 xxx 场景(例如电商直播间、充值、数据迁移系统)的测试用例
207 |
208 | 回答这类问题一般不需要详细列出所有用例,关键是展现你的分析思路和覆盖维度。
209 |
210 | 这里以**电商直播间**为例,分享一下回答思路:
211 |
212 | - **功能测试:** 这是基础。需要覆盖直播间的所有核心功能点,正常场景和异常场景都需要考虑,包括:
213 | - **主播端:** 创建/开始直播、推流稳定性、添加/讲解商品、发优惠券、互动(评论、点赞、连麦)、结束直播、查看数据统计等。
214 | - **用户端:** 进入/退出直播间、观看流畅度(清晰度切换、卡顿情况)、浏览/搜索商品列表、查看商品详情、领取优惠券、加购/下单流程、评论/点赞/送礼互动、分享直播间、关注主播等。
215 | - **后台管理:** 直播间管理、内容审核、禁言/踢人、商品管理、订单管理、数据报表等。
216 | - **兼容性测试:** 需要考虑在不同平台和设备上的表现:
217 | - **移动端:** 不同操作系统版本(iOS/Android)、不同手机品牌和型号、不同屏幕分辨率、App 版本兼容性。
218 | - **Web 端/PC 端:** 不同浏览器(Chrome, Firefox, Safari, Edge 等)及其版本、不同操作系统(Windows/Mac)。
219 | - **性能测试:** 直播间对性能要求很高:
220 | - **压力测试:** 模拟大量用户同时在线观看、评论、下单,看服务器的响应时间(RT)、吞吐量(TPS)、错误率、CPU/内存/带宽使用率等指标。
221 | - **稳定性测试:** 长时间运行直播,观察系统资源是否有泄漏、服务是否稳定。
222 | - **客户端性能:** App 的启动速度、进入直播间速度、滑动流畅度、CPU/内存/电量消耗等。
223 | - **网络测试:** 模拟不同网络环境下的表现,如 WiFi、4G、5G、弱网(高延迟、丢包)下的音视频流畅度、互动响应速度等。
224 | - **安全测试:** 考虑潜在的安全风险,比如用户信息泄露、接口是否可以被恶意调用、防刷(评论、点赞、下单)、支付安全等。
225 | - **UI/UX 测试:** 界面布局是否合理美观、交互是否符合用户习惯、提示信息是否清晰友好等。
226 |
227 | 针对每个维度,再结合具体的功能点,使用前面提到的测试用例设计方法(如边界值、场景法)来生成具体的测试用例。
228 |
229 | **简化版回答:** 首先是功能测试,我会覆盖直播间核心功能,包括主播端(如开播、推流、商品添加)和用户端(如进入观看、互动、下单)的各种场景,确保功能正常。其次是兼容性测试,考虑在不同平台和设备上的表现,比如 iOS/Android 手机、不同浏览器等。第三是性能测试,模拟大量用户并发,关注服务器的响应时间、稳定性以及客户端的流畅度。第四是网络测试,模拟弱网环境,确保音视频和互动流畅。最后,我还会考虑安全测试,比如用户信息保护和支付安全。针对每个维度,我会结合具体功能点,运用等价类划分、边界值分析等方法来设计测试用例,确保覆盖到各种潜在问题。
230 |
231 | ## 测试类型
232 |
233 | ### 什么是自动化测试?为什么需要?
234 |
235 | 简单来说,自动化测试就是用代码或脚本让电脑自动运行测试,而不是让人工手动点击。你可以把它理解为编写代码来测试其他代码。
236 |
237 | 自动化测试的优势在于:
238 |
239 | - **搞定重复劳动:** 它擅长运行重复性的任务(比如更新后检查旧功能——回归测试),比人工更快、更稳定。再也不用让人痛苦地点击几个小时了。
240 | - **解放脑力:** 通过自动化那些枯燥的事情,测试人员可以专注于更聪明的工作:设计更好的测试、探索棘手的边界情况、检查可用性——这些都需要人类的直觉。
241 | - **解决难题:** 它使困难或不切实际的手动测试成为可能,比如模拟成千上万的用户同时访问网站(性能/负载测试)或连续几天不停地运行检查(稳定性测试)。
242 | - **随时运行:** 你可以安排这些测试频繁运行,甚至在夜间或周末,无需人员在场就能提供更快的反馈。
243 | - **一致性是关键:** 机器每次都精确地按照指令执行,消除了人为错误,比如忘记步骤或误解结果。
244 |
245 | 不过,维护自动化测试需要花费大量时间和精力。当你发现自动化测试用例的维护成本高于其节省的测试成本时,自动化测试就失去了价值与意义,你也就需要在是否使用自动化测试上权衡取舍了。
246 |
247 | ### 什么是 Web 应用的自动化测试?怎么做?
248 |
249 | Web 应用自动化测试挺深的,面试中问到抓住几个关键点聊就好了。
250 |
251 | Web 应用的自动化测试,简单说就是**用代码(脚本)来控制工具,模拟真实用户操作浏览器,自动执行预设的测试步骤并验证结果**。比如自动打开网页、点击按钮、输入文字、检查页面显示是否正确等等。
252 |
253 | 实践中,Web 应用自动化测试通常这样做:
254 |
255 | 1. **选择合适的工具/框架**:市面上有很多选择,比如老牌的 **Selenium**(支持多语言、多浏览器)、现代化的 **Cypress** 或 **Playwright**(通常更快、更稳定,调试友好)。选择时主要考虑项目技术栈、团队熟悉程度和测试需求。
256 | 2. **编写测试脚本**:针对应用的核心功能和关键业务流程编写自动化脚本。
257 | - **重点是功能和回归测试**:确保核心流程(如登录、购物、关键操作)能正常工作,并且在新代码提交后这些功能没有被意外破坏。
258 | - **适当进行 UI 验证**:可以检查关键元素是否存在、是否可见,但过于精细的像素级 UI 对比通常投入产出比较低,容易失败。
259 | - **考虑兼容性**:同一套脚本可以在不同浏览器(Chrome, Firefox, Safari 等)或不同环境配置下运行,以检查兼容性。
260 | 3. **集成到 CI/CD 流程**:这是发挥自动化价值的关键一步。把自动化测试加入到持续集成/持续部署(CI/CD)管道中(例如使用 Jenkins, GitLab CI, GitHub Actions)。这样,每次开发者提交代码后,测试就会自动运行,快速反馈结果。通过清晰的测试报告,团队能迅速定位并修复失败的测试,保证代码质量。
261 |
262 | ### 怎么进行压测?
263 |
264 | 压测,全称压力测试,是指通过模拟大量用户并发访问,或者在极端条件下运行系统,来评估系统在负载下的性能、稳定性和可靠性。 简单来说,就是给系统施加压力,看看它能不能扛得住。 压测可以帮助我们发现系统的瓶颈,比如 CPU、内存、数据库连接数等,从而进行优化,提高系统的性能和稳定性。
265 |
266 | 压测的步骤大致如下:
267 |
268 | 1. **确定压测目标:** 你想通过压测了解什么?是想看系统能否支撑 1000 用户在线?还是想知道下单接口的最大 TPS 是多少?响应时间要求低于 500ms?先把这些具体的、可量化的性能指标定下来。同时确定压测的范围,是测试整个系统,还是某个核心模块或接口?
269 | 2. **准备测试环境:** 准备一套尽可能接近生产环境的测试环境(硬件配置、网络、数据量等),结果才更有参考价值。最好是隔离的环境,避免影响线上用户。
270 | 3. **选择合适的压测工具:** JMeter 是最常用、免费开源的选择,功能强大。其他流行的还有 k6 (现代化的,用 JavaScript 写脚本)、LoadRunner (商业,功能全面)、Gatling (用 Scala 写脚本,性能较好)。根据团队技术栈和需求选择。
271 | 4. **编写/录制测试脚本:** 使用选定的工具,根据设计的测试场景,编写或录制模拟用户操作的请求脚本。
272 | 5. **配置压测工具并执行测试:** 设置并发用户数、请求频率等参数。配置完成后,执行测试。
273 | 6. **监控和分析结果:** 密切关注压测工具的指标(TPS、响应时间、错误率)和服务器端的性能指标(CPU、内存、磁盘 I/O、网络、数据库连接数、中间件指标等)。找出系统的瓶颈,比如数据库连接数、CPU 利用率等。
274 | 7. **调优和再测试:** 根据分析结果,定位瓶颈并进行优化(代码优化、配置调整、硬件升级等)。优化后,重复执行之前的测试,验证优化效果,看性能指标是否有提升。这是一个迭代的过程。
275 |
276 | **简化版回答:** 首先确定压测目标和范围,然后准备接近生产环境的测试环境。选择合适的压测工具(如 JMeter),编写模拟用户操作的脚本,配置工具并执行测试。密切监控压测工具和服务器端的性能指标,找出瓶颈并进行优化,最后重复测试验证优化效果,直至达到性能目标。
277 |
278 | ### 白盒测试和黑盒测试的主要区别是什么?
279 |
280 | 白盒测试和黑盒测试最主要的区别在于**是否关注被测对象的内部结构和实现逻辑:**
281 |
282 | - 黑盒测试把被测系统看作一个‘黑盒子’,完全不关心它内部是怎么实现的,主要是针对系统对外功能进行验证,比如给定输入,是否得到预期输出。测试人员站在用户的角度,根据需求文档或规格说明,检查系统功能是否符合预期。常用的方法就是前面提到的等价类、边界值、场景法等。”
283 | - 白盒测试则需要了解被测对象的内部代码逻辑、结构和算法。测试的目的是检查代码内部路径是否都能按预期执行、逻辑判断是否正确、是否存在潜在的代码缺陷等。
284 |
285 | ### 什么是单元测试?集成测试?系统测试?回归测试?验收测试?
286 |
287 | - **单元测试(Unit Test)**
288 | - 针对:最小可测试单元(函数、类、模块)。
289 | - 目标:验证该单元的功能和逻辑是否正确,边界、异常是否处理到位。
290 | - 特点:依赖少(外部依赖多用 Mock),执行快,通常由开发编写并自动化执行。
291 | - **集成测试(Integration Test)**
292 | - 针对:多个模块 / 服务之间的组合和交互。
293 | - 目标:验证接口、数据传递、依赖关系是否正确,模块间能否协同工作。
294 | - 特点:会使用真实或接近真实的数据库、接口等,关注的是“模块之间”。
295 | - **系统测试(System Test)**
296 | - 针对:完整系统(所有模块与外部接口集成后)。
297 | - 目标:从整体上验证系统是否满足需求规格,包括功能、性能、安全、兼容性等。
298 | - 特点:由测试团队执行,从“系统视角”验证,不关注具体代码实现细节。
299 | - **回归测试(Regression Test)**
300 | - 针对:每次修改代码后的系统(修 bug、新功能、重构之后)。
301 | - 目标:确认本次改动没有破坏已有功能,老功能依然按原来方式工作。
302 | - 特点:高度依赖自动化用例,常在每次迭代、发布前重复执行。
303 | - **验收测试(Acceptance Test / UAT)**
304 | - 针对:接近上线的完整系统。
305 | - 目标:由客户 / 业务方 / 产品代表确认系统是否满足业务需求、合同约定,是否可以交付上线。
306 | - 特点:贴近真实业务场景和生产环境,更关注“是否满足业务和用户预期”。
307 |
308 | 实际项目中,一般遵循“测试金字塔”:单元测试自动化率最高(>90%),其上依次是集成测试(60–80%)和少量关键系统/UI 自动化(20–40%),回归尽量依赖自动化,而验收测试则以人工探索和真实业务场景为主。
309 |
310 | ### 单元测试、集成测试、系统测试、回归测试、验收测试最重要的是那一步?
311 |
312 | 严格来说,这几个阶段**都重要、互相补充**,但如果面试官逼问“最重要是哪一步”,可以这样回答(重点是**理由**):
313 |
314 | - **单元测试**:是质量的第一道防线,越早发现问题,修复成本越低;它保证代码构件本身是可靠的。
315 | - **集成测试 & 系统测试**:从整体角度保证“拼在一起能不能用”、“业务链路能不能跑通”,避免线上出现大面积故障。
316 | - **回归测试**:保证迭代过程中老功能不被破坏,是持续交付、敏捷开发中非常关键的一环。
317 | - **验收测试**:从业务和用户角度做最终把关,决定能不能交付上线。
318 |
319 | 如果必须选一个**对业务结果影响最大**的阶段,可以说是:
320 | **系统测试 / 验收测试更关键**,因为它们直接决定系统整体是否可用、能否交付给用户;
321 |
322 | 但从“工程实践”和“长期质量保障”来看,**单元测试和回归测试**是最值得投入的,它们决定了团队能否稳定、可持续地迭代。
323 |
324 | ### 什么是端到端测试?
325 |
326 | **端到端测试**(End-to-End Test,E2E)是指**从用户交互入口到系统最终输出结果**,按照真实业务场景,将前端界面、后端服务、数据库、缓存、消息队列、第三方依赖等全部组件串联起来进行的**全流程集成验证**。它模拟真实用户行为,验证整个系统在近生产环境条件下是否满足业务需求。
327 |
328 | **特点**:
329 |
330 | - 覆盖“完整业务链路”,例如:注册 → 登录 → 下单 → 支付 → 查询订单状态;
331 | - 尽量使用与生产环境相似的配置和数据;
332 | - 更关注系统整体可用性和业务流是否跑通,而不是某个单点模块的细节;
333 | - 数量相对较少,但每条用例价值高,适合作为**关键路径回归**和**上线前的最后验证**。
334 |
335 | ### 移动端与 Web 端测试有什么区别?
336 |
337 | | 维度 | 移动端 App 测试 | Web 端测试 |
338 | | ----------- | -------------------------------------------- | -------------------------------------- |
339 | | 架构 & 入口 | C/S 架构,需要安装 App | B/S 架构,通过浏览器访问 |
340 | | 运行环境 | 受机型、系统版本、厂商 ROM 影响大 | 受浏览器内核/版本、操作系统影响 |
341 | | 主要兼容性 | 不同机型、分辨率、iOS/Android 各版本 | 不同浏览器(Chrome/Firefox/Safari 等) |
342 | | 网络相关 | 2G/3G/4G/5G/Wi‑Fi 切换、弱网/无网、前后台 | 不同带宽下页面加载、缓存策略、断网降级 |
343 | | 中断场景 | 来电、短信、通知、锁屏、前后台切换 | 标签页切换、刷新,系统中断较少 |
344 | | UI/交互 | 手势操作、软键盘弹出/收起、全面屏安全区域 | 响应式布局、鼠标键盘操作、窗口缩放 |
345 | | 性能关注点 | 启动时间、流畅度、CPU/内存、耗电、流量 | 首屏时间、资源体积、前端性能、后端响应 |
346 | | 发布方式 | 应用市场发版,有审核;灰度、热更新受平台限制 | 服务器部署,上线/回滚更快,灰度灵活 |
347 | | 典型难点 | 机型/OS 碎片化、网络复杂、功耗与流量 | 浏览器兼容性、复杂前端与多终端适配 |
348 |
349 | ### 如何测试 Web 应用的安全性?你会关注哪些常见漏洞?
350 |
351 | 重点关注 **OWASP Top 10** 类常见漏洞,针对 Web 端典型场景设计测试:
352 |
353 | **1. XSS(跨站脚本攻击)**
354 |
355 | - 测试位置:输入框、URL 参数、富文本编辑器、评论区
356 | - 测试用例:`、" onmouseover="alert(1)、
`
357 | - 验证点:页面是否执行脚本、是否正确转义/过滤
358 |
359 | **2. SQL 注入**
360 |
361 | - 测试位置:登录、搜索、筛选、排序等数据库查询场景
362 | - 测试用例:`' OR '1'='1 --`(登录绕过)、`' UNION SELECT database(),user() --`(信息泄露);时间盲注:`AND SLEEP(5)`、布尔盲注:`AND (SELECT COUNT(*) FROM users)>0`
363 | - 验证点:是否使用参数化查询、是否暴露数据库错误信息、是否对输入做类型校验
364 |
365 | **3. CSRF(跨站请求伪造)**
366 |
367 | - 测试场景:修改密码、转账、删除数据等敏感操作
368 | - 测试方法:检查是否有 CSRF Token,尝试第三方页面构造伪造请求
369 | - 验证点:Token 验证机制、Referer / Origin 校验
370 |
371 | **4. 身份认证与会话管理**
372 |
373 | - 测试位置:登录/退出流程、会话保持
374 | - 测试方法:Session 固定攻击测试,退出后 Token 有效性验证
375 | - 验证点:会话是否及时失效,是否允许暴力破解
376 |
377 | **5. 敏感信息泄露**
378 |
379 | - 测试位置:API 响应、错误页面、前端代码
380 | - 测试方法:密码/密钥/内部路径是否暴露,错误信息是否过度详细
381 | - 验证点:敏感数据是否脱敏,错误信息是否通用化
382 |
383 | **6. 访问控制失效**
384 |
385 | - 测试位置:用户资料查看、订单数据、文件下载、管理员接口、导出接口
386 | - 测试方法:越权访问,提升权限
387 | - 验证点:是否返回他人数据;RBAC(角色访问控制)是否生效
388 |
389 | ### API 功能测试如何做?
390 |
391 | API 功能测试的核心目的其实很简单:**确认你的 API 是否按照设计(例如 API 文档或需求)准确工作**。它并不在乎界面的美观程度,而是关心当我们调用该 API 时,能否正确接收请求、处理业务逻辑并返回预期的响应,以及是否会产生相应的“副作用”(例如对数据库记录的增删改)。
392 |
393 | 1. **仔细阅读 API 文档(如 Swagger/OpenAPI)** :了解请求的 URL 及方法(GET、POST 等),参数类型(路径参数、查询参数)、请求体格式(JSON、XML)以及成功/失败时对应的状态码等信息。
394 | 2. **设计覆盖多种场景的测试用例** :在充分理解 API 的基础上,编写不同维度的用例,包括正确输入、无效输入、非法输入以及边界值等,以确保各种情况得到测试。
395 | 3. **准备相关数据与认证信息** :可能需要在数据库中预先插入初始数据或异常数据。根据场景需求,还要配置 API Key、Token 等认证信息,以便顺利调用接口。
396 | 4. **选用合适的测试工具并执行测试** :可使用 Postman、Reqable、Insomnia 等工具。根据测试用例构造 HTTP 请求(设置方法、URL、Headers、Body 等),执行后得到实际响应(状态码、响应头、响应体)并进行校验。
397 | 5. **对比实际响应与预期结果** :关注状态码是否正确,返回数据是否与预期一致,数据库记录是否跟随请求生效等。任何偏差都可能意味着缺陷或需求不匹配。
398 | 6. **记录测试结果并报告问题(Bug)** :对于用例执行失败的情况,需要在报告中包括完整的请求信息、预期结果、实际结果以及重现步骤,方便团队排查修复。
399 |
400 | **简化版回答:** API 功能测试的核心是确认接口是否按设计正确工作,主要做法是先阅读文档,弄清楚请求方法、参数和返回值,然后设计覆盖正常、异常与边界场景的用例,准备好测试环境和认证信息,再利用 Postman 等工具发起请求并查看状态码和响应体,接着核对数据库等是否与预期一致,最后将失败用例的请求、预期和实际结果记录下来,方便快速定位并修复问题。
401 |
402 | ### 什么是正向测试和反向测试?能否举例说明
403 |
404 | **解释**:
405 |
406 | - 正向测试(Positive Testing):使用**合法、合理、期望的输入**,验证系统在正常使用场景下是否按需求工作。
407 | - 反向测试(Negative Testing):使用**非法、异常、边界或恶意的输入/操作**,验证系统对异常情况的处理能力和鲁棒性。
408 |
409 | **例子(登录功能)**:
410 |
411 | - 正向测试:输入正确账号 + 正确密码 → 登录成功。
412 | - 反向测试:账号为空 / 密码为空、密码错误、输入超长字符串、注入脚本、多次输错触发锁定/验证码。
413 |
414 | ### 可用性测试和用户体验测试有什么区别?
415 |
416 | 可用性测试评估系统是否易学、易用、易理解,关注点在于:操作流程是否顺畅、提示是否清晰、错误率高低。
417 |
418 | 用户体验测试(UX)评估整体主观感受,关注点在于:用户情绪、满意度、视觉美感、品牌感知等。
419 |
420 | | **维度** | **可用性测试 (Usability Testing)** | **用户体验测试 (UX Testing)** |
421 | | ------------ | ------------------------------------------ | ---------------------------------------------------- |
422 | | **核心目标** | 产品能否被高效、准确地使用 | 使用过程是否愉悦、有价值、令人满意 |
423 | | **关注范围** | 任务执行效率(交互流程、认知负荷、容错性) | 完整体验旅程(使用前期待 + 使用中感受 + 使用后回忆) |
424 | | **测试维度** | 功能层面 | 情感+功能+审美全方位 |
425 | | **衡量指标** | 任务完成率、时间、错误数 | 满意度评分、NPS、情感反馈 |
426 | | **相互关系** | 是 UX 的基础子集 | 包含可用性 + 情感/价值层面 |
427 | | **测试时机** | 常在开发中后期进行功能验证 | 贯穿产品全生命周期(概念验证到迭代优化) |
428 |
429 |
430 |
--------------------------------------------------------------------------------
/docs/.vuepress/public/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/java/java-jvm.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: JVM常见面试题总结
3 | category: Java
4 | tag:
5 | - JVM
6 | head:
7 | - - meta
8 | - name: keywords
9 | content: JVM内存区域,JVM垃圾回收,类加载机制,双亲委派模型,GC算法,G1,ZGC,OutOfMemoryError,Heap Dump,性能优化,JVM参数
10 | - - meta
11 | - name: description
12 | content: JVM核心知识点与高频面试题精炼总结:内存区域、垃圾回收、类加载、双亲委派、G1/ZGC、OOM排查、Heap Dump、性能参数等,含图解与实战案例。
13 | ---
14 |
15 | ------
16 |
17 | 
18 |
19 | ------
20 |
21 | ## 前言
22 |
23 | 由于很多读者都有突击面试的需求,所以我在几年前就弄了 **JavaGuide 面试突击版本**(JavaGuide 内容精简版,只保留重点),并持续完善跟进。对于喜欢纸质阅读的朋友来说,也可以打印出来,整体阅读体验非常高!
24 |
25 | 除了只保留最常问的面试题之外,我还进一步对重点中的重点进行了⭐️标注。并且,有亮色(白天)和暗色(夜间)两个主题选择,需要打印出来的朋友记得选择亮色版本。
26 |
27 | 对于时间比较充裕的朋友,我个人还是更推荐 [JavaGuide](https://javaguide.cn/) 网站系统学习,内容更全面,更深入。
28 |
29 | JavaGuide 已经持续维护 6 年多了,累计提交了接近 **6000** commit ,共有 **570+** 多位贡献者共同参与维护和完善。用心做原创优质内容,如果觉得有帮助的话,欢迎点赞分享!传送门:[GitHub](https://github.com/Snailclimb/JavaGuide) | [Gitee](https://gitee.com/SnailClimb/JavaGuide)。
30 |
31 | 对于需要更进一步面试辅导服务的读者,欢迎加入 **[JavaGuide 官方知识星球](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)**(技术专栏/一对一提问/简历修改/求职指南/面试打卡),绝对物超所值!
32 |
33 | 面试突击最新版本可以在我的公众号回复“**PDF**”获取([JavaGuide 官方知识星球](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)会提前同步最新版,针对球友的一个小福利)。
34 |
35 | 
36 |
37 | 这部分内容摘自 [JavaGuide](https://javaguide.cn/) 下面几篇文章中的重点:
38 |
39 | 1. [Java 内存区域详解](https://javaguide.cn/java/jvm/memory-area.html)
40 | 2. [JVM 垃圾回收详解](https://javaguide.cn/java/jvm/jvm-garbage-collection.html)
41 | 3. [类文件结构详解](https://javaguide.cn/java/jvm/class-file-structure.html)
42 | 4. [类加载过程详解](https://javaguide.cn/java/jvm/class-loading-process.html)
43 | 5. [类加载器详解](https://javaguide.cn/java/jvm/classloader.html)
44 |
45 | ## Java 内存区域
46 |
47 | ### ⭐️Java 内存区域(运行时数据区)的组成
48 |
49 | Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。
50 |
51 | JDK 1.8 和之前的版本略有不同,我们这里以 JDK 1.7 和 JDK 1.8 这两个版本为例介绍。
52 |
53 | **JDK 1.7**:
54 |
55 | 
56 |
57 | **JDK 1.8**:
58 |
59 | 
60 |
61 | **线程私有的:**
62 |
63 | - 程序计数器
64 | - 虚拟机栈
65 | - 本地方法栈
66 |
67 | **线程共享的:**
68 |
69 | - 堆
70 | - 方法区
71 | - 直接内存 (非运行时数据区的一部分)
72 |
73 | Java 虚拟机规范对于运行时数据区域的规定是相当宽松的。以堆为例:堆可以是连续空间,也可以不连续。堆的大小可以固定,也可以在运行时按需扩展 。虚拟机实现者可以使用任何垃圾回收算法管理堆,甚至完全不进行垃圾收集也是可以的。
74 |
75 | ### ⭐️哪个区域不会出现 OutOfMemoryError?
76 |
77 | 程序计数器是唯一一个不会出现 `OutOfMemoryError` 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。
78 |
79 | 程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。
80 |
81 | 另外,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
82 |
83 | 从上面的介绍中我们知道了程序计数器主要有两个作用:
84 |
85 | - 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
86 | - 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
87 |
88 | ### ⭐️哪些情况可能出现堆溢出?
89 |
90 | 堆溢出,也就是我们常说的 `OutOfMemoryError: Java heap space`,是 Java 开发中非常常见的一种严重错误。它的根本原因就是 JVM 在尝试为新对象分配内存时,堆中已经没有足够的连续空间了,并且经过垃圾回收后,也无法腾出足够的空间。
91 |
92 | 导致堆溢出的场景主要可以分为两类:
93 |
94 | 1. **内存泄漏**:对象用完了但没被释放,比如 `static` 集合无限增长、 `ThreadLocal` 没调用 `remove()` 。
95 | 2. **内存膨胀**:短时间内创建了太多对象,比如一次性从数据库查了几百万条数据到 List 里,或者直接把一个大文件整个读进内存。
96 |
97 | ### 程序运行中栈可能会出现什么错误?
98 |
99 | - **`StackOverFlowError`:** 如果栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 `StackOverFlowError` 错误。
100 | - **`OutOfMemoryError`:** 如果栈的内存大小可以动态扩展, 那么当虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出`OutOfMemoryError`异常。
101 |
102 | 
103 |
104 | ### 堆内存的作用和组成
105 |
106 | Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。**此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。**
107 |
108 | Java 世界中“几乎”所有的对象都在堆中分配,但是,随着 JIT 编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。从 JDK 1.7 开始已经默认开启逃逸分析,如果某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去),那么对象可以直接在栈上分配内存。
109 |
110 | Java 堆是垃圾收集器管理的主要区域,因此也被称作 **GC 堆(Garbage Collected Heap)**。从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代;再细致一点有:Eden、Survivor、Old 等空间。进一步划分的目的是更好地回收内存,或者更快地分配内存。
111 |
112 | 在 JDK 7 版本及 JDK 7 版本之前,堆内存被通常分为下面三部分:
113 |
114 | 1. 新生代内存(Young Generation)
115 | 2. 老生代(Old Generation)
116 | 3. 永久代(Permanent Generation)
117 |
118 | 下图所示的 Eden 区、两个 Survivor 区 S0 和 S1 都属于新生代,中间一层属于老年代,最下面一层属于永久代。
119 |
120 | 
121 |
122 | **JDK 8 版本之后 PermGen(永久代) 已被 Metaspace(元空间) 取代,元空间使用的是本地内存。** (我会在方法区这部分内容详细介绍到)。
123 |
124 | 大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 S0 或者 S1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。不过,设置的值应该在 0-15,否则会爆出以下错误:
125 |
126 | ```bash
127 | MaxTenuringThreshold of 20 is invalid; must be between 0 and 15
128 | ```
129 |
130 | ### ⭐️程序运行中堆可能会出现什么错误?
131 |
132 | 堆这里最容易出现的就是 `OutOfMemoryError` 错误,并且出现这种错误之后的表现形式还会有几种,比如:
133 |
134 | 1. **`java.lang.OutOfMemoryError: GC Overhead Limit Exceeded`**:当 JVM 花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误。
135 | 2. **`java.lang.OutOfMemoryError: Java heap space`** :假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发此错误。(和配置的最大堆内存有关,且受制于物理内存大小。最大堆内存可通过`-Xmx`参数配置,若没有特别配置,将会使用默认值,详见:[Default Java 8 max heap size](https://stackoverflow.com/questions/28272923/default-xmxsize-in-java-8-max-heap-size))
136 | 3. ……
137 |
138 | ### ⭐️为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?
139 |
140 | 下图来自《深入理解 Java 虚拟机》第 3 版 2.2.5
141 |
142 | 
143 |
144 | 1、整个永久代有一个 JVM 本身设置的固定大小上限,无法进行调整(也就是受到 JVM 内存的限制),而元空间使用的是本地内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小。
145 |
146 | > 当元空间溢出时会得到如下错误:`java.lang.OutOfMemoryError: MetaSpace`
147 |
148 | 你可以使用 `-XX:MaxMetaspaceSize` 标志设置最大元空间大小,默认值为 unlimited,这意味着它只受系统内存的限制。`-XX:MetaspaceSize` 调整标志定义元空间的初始大小如果未指定此标志,则 Metaspace 将根据运行时的应用程序需求动态地重新调整大小。
149 |
150 | 2、元空间里面存放的是类的元数据,这样加载多少类的元数据就不由 `MaxPermSize` 控制了, 而由系统的实际可用空间来控制,这样能加载的类就更多了。
151 |
152 | 3、在 JDK8,合并 HotSpot 和 JRockit 的代码时, JRockit 从来没有一个叫永久代的东西, 合并之后就没有必要额外的设置这么一个永久代的地方了。
153 |
154 | 4、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
155 |
156 | ### 方法区常用参数有哪些?
157 |
158 | JDK 1.8 之前永久代还没被彻底移除的时候通常通过下面这些参数来调节方法区大小。
159 |
160 | ```java
161 | -XX:PermSize=N //方法区 (永久代) 初始大小
162 | -XX:MaxPermSize=N //方法区 (永久代) 最大大小,超过这个值将会抛出 OutOfMemoryError 异常:java.lang.OutOfMemoryError: PermGen
163 | ```
164 |
165 | 相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。
166 |
167 | JDK 1.8 的时候,方法区(HotSpot 的永久代)被彻底移除了(JDK1.7 就已经开始了),取而代之是元空间,元空间使用的是本地内存。下面是一些常用参数:
168 |
169 | ```java
170 | -XX:MetaspaceSize=N //设置 Metaspace 的初始(和最小大小)
171 | -XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小
172 | ```
173 |
174 | 与永久代很大的不同就是,如果不指定大小的话,随着更多类的创建,虚拟机会耗尽所有可用的系统内存。
175 |
176 | ### ⭐️字符串常量池的作用是?
177 |
178 | **字符串常量池** 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
179 |
180 | ```java
181 | // 在字符串常量池中创建字符串对象 ”ab“
182 | // 将字符串对象 ”ab“ 的引用赋值给给 aa
183 | String aa = "ab";
184 | // 直接返回字符串常量池中字符串对象 ”ab“,赋值给引用 bb
185 | String bb = "ab";
186 | System.out.println(aa==bb); // true
187 | ```
188 |
189 | HotSpot 虚拟机中字符串常量池的实现是 `src/hotspot/share/classfile/stringTable.cpp` ,`StringTable` 可以简单理解为一个固定大小的`HashTable` ,容量为 `StringTableSize`(可以通过 `-XX:StringTableSize` 参数来设置),保存的是字符串(key)和 字符串对象的引用(value)的映射关系,字符串对象的引用指向堆中的字符串对象。
190 |
191 | JDK1.7 之前,字符串常量池存放在永久代。JDK1.7 字符串常量池和静态变量从永久代移动到了 Java 堆中。
192 |
193 | 
194 |
195 | 
196 |
197 | ### JDK 1.7 为什么要将字符串常量池移动到堆中?
198 |
199 | 主要是因为永久代(方法区实现)的 GC 回收效率太低,只有在整堆收集 (Full GC)的时候才会被执行 GC。Java 程序中通常会有大量的被创建的字符串等待回收,将字符串常量池放到堆中,能够更高效及时地回收字符串内存。
200 |
201 | ### 直接内存的作用是?
202 |
203 | 直接内存是一种特殊的内存缓冲区,并不在 Java 堆或方法区中分配的,而是通过 JNI 的方式在本地内存上分配的。
204 |
205 | 直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 `OutOfMemoryError` 错误出现。
206 |
207 | JDK1.4 中新加入的 **NIO(Non-Blocking I/O,也被称为 New I/O)**,引入了一种基于**通道(Channel)**与**缓存区(Buffer)**的 I/O 方式,它可以直接使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为**避免了在 Java 堆和 Native 堆之间来回复制数据**。
208 |
209 | 直接内存的分配不会受到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。
210 |
211 | 类似的概念还有 **堆外内存** 。在一些文章中将直接内存等价于堆外内存,个人觉得不是特别准确。
212 |
213 | 堆外内存就是把内存对象分配在堆外的内存,这些内存直接受操作系统管理(而不是虚拟机),这样做的结果就是能够在一定程度上减少垃圾回收对应用程序造成的影响。
214 |
215 | ### Java 对象的创建过程
216 |
217 | JVM(HotSpot 虚拟机)中对象的创建过程主要分为以下五步:
218 |
219 | 1. **类加载检查**:虚拟机执行 new 指令时,先检查常量池中对应类的符号引用是否已加载、解析和初始化,未完成则先执行类加载过程。
220 | 2. **分配内存**:类加载通过后,根据类加载确定的对象大小从 Java 堆划分内存,分配方式有 “指针碰撞”(适用于堆内存规整,如 Serial/ParNew 收集器)和 “空闲列表”(适用于堆内存不规整,如 CMS 收集器);为保证线程安全,采用 CAS + 失败重试或 TLAB(线程本地分配缓冲)机制。
221 | 3. **初始化零值**:将分配的内存空间(除对象头外)初始化为零值,确保 Java 代码中未赋初始值的实例字段可直接使用对应类型的零值。
222 | 4. **设置对象头**:在对象头中记录类元数据信息、哈希码、GC 分代年龄、锁状态等必要信息,具体设置依虚拟机运行状态(如是否启用偏向锁)而定。
223 | 5. **执行 init 方法**:虚拟机视角下对象已创建,但需执行``方法按程序员定义完成初始化,最终生成可用对象。
224 |
225 | ### ⭐️对象访问定位的方式有哪些?
226 |
227 | 建立对象就是为了使用对象,我们的 Java 程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式由虚拟机实现而定,目前主流的访问方式有:**使用句柄**、**直接指针**。
228 |
229 | #### 句柄
230 |
231 | 如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与对象类型数据各自的具体地址信息。
232 |
233 | 
234 |
235 | #### 直接指针
236 |
237 | 如果使用直接指针访问,reference 中存储的直接就是对象的地址。
238 |
239 | 
240 |
241 | 这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。
242 |
243 | HotSpot 虚拟机主要使用的就是这种方式来进行对象访问。
244 |
245 | ## JVM 垃圾回收
246 |
247 | ### ⭐️如何判断对象是否死亡
248 |
249 | 堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断哪些对象已经死亡(即不能再被任何途径使用的对象)。
250 |
251 | #### 引用计数法
252 |
253 | 给对象中添加一个引用计数器:
254 |
255 | - 每当有一个地方引用它,计数器就加 1;
256 | - 当引用失效,计数器就减 1;
257 | - 任何时候计数器为 0 的对象就是不可能再被使用的。
258 |
259 | **这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间循环引用的问题。**
260 |
261 | 
262 |
263 | 所谓对象之间的相互引用问题,如下面代码所示:除了对象 `objA` 和 `objB` 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为 0,于是引用计数算法无法通知 GC 回收器回收他们。
264 |
265 | ```java
266 | public class ReferenceCountingGc {
267 | Object instance = null;
268 | public static void main(String[] args) {
269 | ReferenceCountingGc objA = new ReferenceCountingGc();
270 | ReferenceCountingGc objB = new ReferenceCountingGc();
271 | objA.instance = objB;
272 | objB.instance = objA;
273 | objA = null;
274 | objB = null;
275 | }
276 | }
277 | ```
278 |
279 | #### 可达性分析算法
280 |
281 | 这个算法的基本思想就是通过一系列的称为 **“GC Roots”** 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的,需要被回收。
282 |
283 | 下图中的 `Object 6 ~ Object 10` 之间虽有引用关系,但它们到 GC Roots 不可达,因此为需要被回收的对象。
284 |
285 | 
286 |
287 | **哪些对象可以作为 GC Roots 呢?**
288 |
289 | - 虚拟机栈(栈帧中的局部变量表)中引用的对象
290 | - 本地方法栈(Native 方法)中引用的对象
291 | - 方法区中类静态属性引用的对象
292 | - 方法区中常量引用的对象
293 | - 所有被同步锁持有的对象
294 | - JNI(Java Native Interface)引用的对象
295 |
296 | **对象可以被回收,就代表一定会被回收吗?**
297 |
298 | 即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 `finalize` 方法。当对象没有覆盖 `finalize` 方法,或 `finalize` 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。
299 |
300 | 被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。
301 |
302 | > `Object` 类中的 `finalize` 方法一直被认为是一个糟糕的设计,成为了 Java 语言的负担,影响了 Java 语言的安全和 GC 的性能。JDK9 版本及后续版本中各个类中的 `finalize` 方法会被逐渐弃用移除。忘掉它的存在吧!
303 | >
304 | > 参考:
305 | >
306 | > - [JEP 421: Deprecate Finalization for Removal](https://openjdk.java.net/jeps/421)
307 | > - [是时候忘掉 finalize 方法了](https://mp.weixin.qq.com/s/LW-paZAMD08DP_3-XCUxmg)
308 |
309 | ### 常见的引用类型有哪些?
310 |
311 | 无论是通过引用计数法判断对象引用数量,还是通过可达性分析法判断对象的引用链是否可达,判定对象的存活都与“引用”有关。
312 |
313 | JDK1.2 之前,Java 中引用的定义很传统:如果 reference 类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用。
314 |
315 | JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱),强引用就是 Java 中普通的对象,而软引用、弱引用、虚引用在 JDK 中定义的类分别是 `SoftReference`、`WeakReference`、`PhantomReference`。
316 |
317 | 
318 |
319 | **1.强引用(StrongReference)**
320 |
321 | 强引用实际上就是程序代码中普遍存在的引用赋值,这是使用最普遍的引用,其代码如下
322 |
323 | ```java
324 | String strongReference = new String("abc");
325 | ```
326 |
327 | 如果一个对象具有强引用,那就类似于**必不可少的生活用品**,垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
328 |
329 | **2.软引用(SoftReference)**
330 |
331 | 如果一个对象只具有软引用,那就类似于**可有可无的生活用品**。软引用代码如下
332 |
333 | ```java
334 | // 软引用
335 | String str = new String("abc");
336 | SoftReference softReference = new SoftReference(str);
337 | ```
338 |
339 | 如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
340 |
341 | 软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中。
342 |
343 | **3.弱引用(WeakReference)**
344 |
345 | 如果一个对象只具有弱引用,那就类似于**可有可无的生活用品**。弱引用代码如下:
346 |
347 | ```java
348 | String str = new String("abc");
349 | WeakReference weakReference = new WeakReference<>(str);
350 | str = null; //str变成软引用,可以被收集
351 | ```
352 |
353 | 弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
354 |
355 | 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。
356 |
357 | **4.虚引用(PhantomReference)**
358 |
359 | "虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用代码如下:
360 |
361 | ```java
362 | String str = new String("abc");
363 | ReferenceQueue queue = new ReferenceQueue();
364 | // 创建虚引用,要求必须与一个引用队列关联
365 | PhantomReference pr = new PhantomReference(str, queue);
366 | ```
367 |
368 | **虚引用主要用来跟踪对象被垃圾回收的活动**。
369 |
370 | **虚引用与软引用和弱引用的一个区别在于:** 虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
371 |
372 | 特别注意,在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为**软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生**。
373 |
374 | ### 如何判断一个类是无用的类?
375 |
376 | 方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?
377 |
378 | 判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面 3 个条件才能算是 **“无用的类”**:
379 |
380 | - 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
381 | - 加载该类的 `ClassLoader` 已经被回收。
382 | - 该类对应的 `java.lang.Class` 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
383 |
384 | 虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。
385 |
386 | ### ⭐️垃圾回收算法有哪些?
387 |
388 | #### 标记-清除算法
389 |
390 | 标记-清除(Mark-and-Sweep)算法分为“标记(Mark)”和“清除(Sweep)”阶段:首先标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。
391 |
392 | 它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题:
393 |
394 | 1. **效率问题**:标记和清除两个过程效率都不高。
395 | 2. **空间问题**:标记清除后会产生大量不连续的内存碎片。
396 |
397 | 
398 |
399 | 关于具体是标记可回收对象还是不可回收对象,众说纷纭,两种说法其实都没问题,我个人更倾向于是前者。
400 |
401 | 如果按照前者的理解,整个标记-清除过程大致是这样的:
402 |
403 | 1. 当一个对象被创建时,给一个标记位,假设为 0 (false);
404 | 2. 在标记阶段,我们将所有可达对象(或用户可以引用的对象)的标记位设置为 1 (true);
405 | 3. 扫描阶段清除的就是标记位为 0 (false)的对象。
406 |
407 | #### 复制算法
408 |
409 | 为了解决标记-清除算法的效率和内存碎片问题,复制(Copying)收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
410 |
411 | 
412 |
413 | 虽然改进了标记-清除算法,但依然存在下面这些问题:
414 |
415 | - **可用内存变小**:可用内存缩小为原来的一半。
416 | - **不适合老年代**:如果存活对象数量比较大,复制性能会变得很差。
417 |
418 | #### 标记-整理算法
419 |
420 | 标记-整理(Mark-and-Compact)算法是根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
421 |
422 | 
423 |
424 | 由于多了整理这一步,因此效率也不高,适合老年代这种垃圾回收频率不是很高的场景。
425 |
426 | #### 分代收集算法
427 |
428 | 当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将 Java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
429 |
430 | 比如在新生代中,每次收集都会有大量对象死去,所以可以选择”标记-复制“算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。
431 |
432 | **延伸面试问题:** HotSpot 为什么要分为新生代和老年代?
433 |
434 | 根据上面的对分代收集算法的介绍回答。
435 |
436 | ### ⭐️JDK 1.8 的默认垃圾回收器是?JDK1.9 之后呢?
437 |
438 | - **JDK 1.8 默认垃圾回收器**:Parallel Scanvenge(新生代)+ Parallel Old(老年代)。 这个组合也被称为 Parallel GC 或 Throughput GC,侧重于吞吐量。
439 | - **JDK 1.9 及以后默认垃圾回收器**:G1 GC (Garbage-First Garbage Collector)。 G1 GC 是一个更现代化的垃圾回收器,旨在平衡吞吐量和停顿时间,尤其适用于堆内存较大的应用。
440 |
441 | ### ⭐️G1 垃圾回收的过程
442 |
443 | G1(Garbage-First)垃圾收集器在 JDK 7 中首次引入,作为一种试验性的垃圾收集器。到了 JDK 8,G1 得到了进一步的完善和改进,功能基本已经完全实现,成为一个稳定、可用于生产环境的垃圾收集器。
444 |
445 | G1 收集器的运作大致分为以下几个步骤:
446 |
447 | - **初始标记**: 短暂停顿(Stop-The-World,STW),标记从 GC Roots 可直接引用的对象,即标记所有直接可达的活跃对象
448 | - **并发标记**:与应用并发运行,标记所有可达对象。 这一阶段可能持续较长时间,取决于堆的大小和对象的数量。
449 | - **最终标记**: 短暂停顿(STW),处理并发标记阶段结束后残留的少量未处理的引用变更。
450 | - **筛选回收**:根据标记结果,选择回收价值高的区域,复制存活对象到新区域,回收旧区域内存。这一阶段包含一个或多个停顿(STW),具体取决于回收的复杂度。
451 |
452 | 
453 |
454 | **G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来)** 。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。
455 |
456 | ### ⭐️ZGC 有哪些改进?
457 |
458 | 与 CMS、ParNew 和 G1 类似,ZGC 也采用标记-复制算法,不过 ZGC 对该算法做了重大改进。
459 |
460 | ZGC 可以将暂停时间控制在几毫秒以内,且暂停时间不受堆内存大小的影响,出现 Stop The World 的情况会更少,但代价是牺牲了一些吞吐量。ZGC 最大支持 16TB 的堆内存。
461 |
462 | ZGC 在 Java11 中引入,处于试验阶段。经过多个版本的迭代,不断的完善和修复问题,ZGC 在 Java15 已经可以正式使用了。
463 |
464 | 不过,默认的垃圾回收器依然是 G1。你可以通过下面的参数启用 ZGC:
465 |
466 | ```bash
467 | java -XX:+UseZGC className
468 | ```
469 |
470 | 在 Java21 中,引入了分代 ZGC,暂停时间可以缩短到 1 毫秒以内。
471 |
472 | 你可以通过下面的参数启用分代 ZGC:
473 |
474 | ```bash
475 | java -XX:+UseZGC -XX:+ZGenerational className
476 | ```
477 |
478 | 关于 ZGC 收集器的详细介绍推荐看看这几篇文章:
479 |
480 | - [从历代 GC 算法角度剖析 ZGC - 京东技术](https://mp.weixin.qq.com/s/ExkB40cq1_Z0ooDzXn7CVw)
481 | - [新一代垃圾回收器 ZGC 的探索与实践 - 美团技术团队](https://tech.meituan.com/2020/08/06/new-zgc-practice-in-meituan.html)
482 | - [极致八股文之 JVM 垃圾回收器 G1&ZGC 详解 - 阿里云开发者](https://mp.weixin.qq.com/s/Ywj3XMws0IIK-kiUllN87Q)
483 |
484 | ## ⭐️双亲委派模型
485 |
486 | ### 双亲委派模型指的是?
487 |
488 | 类加载器有很多种,当我们想要加载一个类的时候,具体是哪个类加载器加载呢?这就需要提到双亲委派模型了。
489 |
490 | 根据官网介绍:
491 |
492 | > The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself. The virtual machine's built-in class loader, called the "bootstrap class loader", does not itself have a parent but may serve as the parent of a ClassLoader instance.
493 |
494 | 翻译过来大概的意思是:
495 |
496 | > `ClassLoader` 类使用委托模型来搜索类和资源。每个 `ClassLoader` 实例都有一个相关的父类加载器。需要查找类或资源时,`ClassLoader` 实例会在试图亲自查找类或资源之前,将搜索类或资源的任务委托给其父类加载器。
497 | > 虚拟机中被称为 "bootstrap class loader"的内置类加载器本身没有父类加载器,但是可以作为 `ClassLoader` 实例的父类加载器。
498 |
499 | 从上面的介绍可以看出:
500 |
501 | - `ClassLoader` 类使用委托模型来搜索类和资源。
502 | - 双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。
503 | - `ClassLoader` 实例会在试图亲自查找类或资源之前,将搜索类或资源的任务委托给其父类加载器。
504 |
505 | 下图展示的各种类加载器之间的层次关系被称为类加载器的“**双亲委派模型(Parents Delegation Model)**”。
506 |
507 | 
508 |
509 | 注意 ⚠️:双亲委派模型并不是一种强制性的约束,只是 JDK 官方推荐的一种方式。如果我们因为某些特殊需求想要打破双亲委派模型,也是可以的,后文会介绍具体的方法。
510 |
511 | 其实这个双亲翻译的容易让别人误解,我们一般理解的双亲都是父母,这里的双亲更多地表达的是“父母这一辈”的人而已,并不是说真的有一个 `MotherClassLoader` 和一个`FatherClassLoader` 。个人觉得翻译成单亲委派模型更好一些,不过,国内既然翻译成了双亲委派模型并流传了,按照这个来也没问题,不要被误解了就好。
512 |
513 | 另外,类加载器之间的父子关系一般不是以继承的关系来实现的,而是通常使用组合关系来复用父加载器的代码。
514 |
515 | ```java
516 | public abstract class ClassLoader {
517 | ...
518 | // 组合
519 | private final ClassLoader parent;
520 | protected ClassLoader(ClassLoader parent) {
521 | this(checkCreateClassLoader(), parent);
522 | }
523 | ...
524 | }
525 | ```
526 |
527 | 在面向对象编程中,有一条非常经典的设计原则:**组合优于继承,多用组合少用继承。**
528 |
529 | ### 如何打破打破双亲委派模型?
530 |
531 | 定义加载器的话,需要继承 `ClassLoader` 。如果我们不想打破双亲委派模型,就重写 `ClassLoader` 类中的 `findClass()` 方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。但是,如果想打破双亲委派模型则需要重写 `loadClass()` 方法。
532 |
533 | 为什么是重写 `loadClass()` 方法打破双亲委派模型呢?双亲委派模型的执行流程已经解释了:
534 |
535 | > 类加载器在进行类加载的时候,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成(调用父加载器 `loadClass()`方法来加载类)。
536 |
537 | 重写 `loadClass()`方法之后,我们就可以改变传统双亲委派模型的执行流程。例如,子类加载器可以在委派给父类加载器之前,先自己尝试加载这个类,或者在父类加载器返回之后,再尝试从其他地方加载这个类。具体的规则由我们自己实现,根据项目需求定制化。
538 |
539 | 我们比较熟悉的 Tomcat 服务器为了能够优先加载 Web 应用目录下的类,然后再加载其他目录下的类,就自定义了类加载器 `WebAppClassLoader` 来打破双亲委托机制。这也是 Tomcat 下 Web 应用之间的类实现隔离的具体原理。
540 |
541 | Tomcat 的类加载器的层次结构如下:
542 |
543 | 
544 |
545 | Tomcat 这四个自定义的类加载器对应的目录如下:
546 |
547 | - `CommonClassLoader`对应`/common/*`
548 | - `CatalinaClassLoader`对应`/server/*`
549 | - `SharedClassLoader`对应 `/shared/*`
550 | - `WebAppClassloader`对应 `/webapps//WEB-INF/*`
551 |
552 | 从图中的委派关系中可以看出:
553 |
554 | - `CommonClassLoader`作为 `CatalinaClassLoader` 和 `SharedClassLoader` 的父加载器。`CommonClassLoader` 能加载的类都可以被 `CatalinaClassLoader` 和 `SharedClassLoader` 使用。因此,`CommonClassLoader` 是为了实现公共类库(可以被所有 Web 应用和 Tomcat 内部组件使用的类库)的共享和隔离。
555 | - `CatalinaClassLoader` 和 `SharedClassLoader` 能加载的类则与对方相互隔离。`CatalinaClassLoader` 用于加载 Tomcat 自身的类,为了隔离 Tomcat 本身的类和 Web 应用的类。`SharedClassLoader` 作为 `WebAppClassLoader` 的父加载器,专门来加载 Web 应用之间共享的类比如 Spring、Mybatis。
556 | - 每个 Web 应用都会创建一个单独的 `WebAppClassLoader`,并在启动 Web 应用的线程里设置线程线程上下文类加载器为 `WebAppClassLoader`。各个 `WebAppClassLoader` 实例之间相互隔离,进而实现 Web 应用之间的类隔。
557 |
558 | 单纯依靠自定义类加载器没办法满足某些场景的要求,例如,有些情况下,高层的类加载器需要加载低层的加载器才能加载的类。
559 |
560 | 比如,SPI 中,SPI 的接口(如 `java.sql.Driver`)是由 Java 核心库提供的,由`BootstrapClassLoader` 加载。而 SPI 的实现(如`com.mysql.cj.jdbc.Driver`)是由第三方供应商提供的,它们是由应用程序类加载器或者自定义类加载器来加载的。默认情况下,一个类及其依赖类由同一个类加载器加载。所以,加载 SPI 的接口的类加载器(`BootstrapClassLoader`)也会用来加载 SPI 的实现。按照双亲委派模型,`BootstrapClassLoader` 是无法找到 SPI 的实现类的,因为它无法委托给子类加载器去尝试加载。
561 |
562 | 再比如,假设我们的项目中有 Spring 的 jar 包,由于其是 Web 应用之间共享的,因此会由 `SharedClassLoader` 加载(Web 服务器是 Tomcat)。我们项目中有一些用到了 Spring 的业务类,比如实现了 Spring 提供的接口、用到了 Spring 提供的注解。所以,加载 Spring 的类加载器(也就是 `SharedClassLoader`)也会用来加载这些业务类。但是业务类在 Web 应用目录下,不在 `SharedClassLoader` 的加载路径下,所以 `SharedClassLoader` 无法找到业务类,也就无法加载它们。
563 |
564 | 如何解决这个问题呢? 这个时候就需要用到 **线程上下文类加载器(`ThreadContextClassLoader`)** 了。
565 |
566 | 拿 Spring 这个例子来说,当 Spring 需要加载业务类的时候,它不是用自己的类加载器,而是用当前线程的上下文类加载器。还记得我上面说的吗?每个 Web 应用都会创建一个单独的 `WebAppClassLoader`,并在启动 Web 应用的线程里设置线程线程上下文类加载器为 `WebAppClassLoader`。这样就可以让高层的类加载器(`SharedClassLoader`)借助子类加载器( `WebAppClassLoader`)来加载业务类,破坏了 Java 的类加载委托机制,让应用逆向使用类加载器。
567 |
568 | 线程上下文类加载器的原理是将一个类加载器保存在线程私有数据里,跟线程绑定,然后在需要的时候取出来使用。这个类加载器通常是由应用程序或者容器(如 Tomcat)设置的。
569 |
570 | `Java.lang.Thread` 中的`getContextClassLoader()`和 `setContextClassLoader(ClassLoader cl)`分别用来获取和设置线程的上下文类加载器。如果没有通过`setContextClassLoader(ClassLoader cl)`进行设置的话,线程将继承其父线程的上下文类加载器。
571 |
572 | Spring 获取线程线程上下文类加载器的代码如下:
573 |
574 | ```java
575 | cl = Thread.currentThread().getContextClassLoader();
576 | ```
577 |
578 | 感兴趣的小伙伴可以自行深入研究一下 Tomcat 打破双亲委派模型的原理,推荐资料:[《深入拆解 Tomcat & Jetty》](http://gk.link/a/10Egr)。
579 |
580 | ## ⭐️问题排查
581 |
582 | ### 你知道哪些 Java 性能优化和问题排查工具?
583 |
584 | JDK 自带的可视化分析工具:
585 |
586 | - **JConsole** :基于 JMX 的可视化监视、管理工具,可以用于查看应用程序的运行概况、内存、线程、类、VM 概括、MBean 等信息。
587 | - **VisualVM**:基于 NetBeans 平台开发,具备了插件扩展功能的特性。利用它不仅能够监控服务的 CPU、内存、线程、类等信息,还可以捕获有关 JVM 软件实例的数据,并将该数据保存到本地系统,以供后期查看或与其他用户共享。根据《深入理解 Java 虚拟机》介绍:“VisualVM 的性能分析功能甚至比起 JProfiler、YourKit 等专业且收费的 Profiling 工具都不会逊色多少,而且 VisualVM 还有一个很大的优点:不需要被监视的程序基于特殊 Agent 运行,因此他对应用程序的实际性能的影响很小,使得他可以直接应用在生产环境中。这个优点是 JProfiler、YourKit 等工具无法与之媲美的”。
588 |
589 | JDK 自带的命令行工具:
590 |
591 | - **`jps`** (JVM Process Status): 类似 UNIX 的 `ps` 命令。用于查看所有 Java 进程的启动类、传入参数和 Java 虚拟机参数等信息;
592 | - **`jstat`**(JVM Statistics Monitoring Tool): 用于收集 HotSpot 虚拟机各方面的运行数据;
593 | - **`jinfo`** (Configuration Info for Java) : Configuration Info for Java,显示虚拟机配置信息;
594 | - **`jmap`** (Memory Map for Java) : 生成堆转储快照;
595 | - **`jhat`** (JVM Heap Dump Browser) : 用于分析 heapdump 文件,它会建立一个 HTTP/HTML 服务器,让用户可以在浏览器上查看分析结果。JDK9 移除了 jhat;
596 | - **`jstack`** (Stack Trace for Java) : 生成虚拟机当前时刻的线程快照,线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合。
597 |
598 | 第三方工具:
599 |
600 | - **MAT**:一款功能强大的 Java 堆内存分析器,可以用于查找内存泄漏以及查看内存消耗情况,用户可以利用 VisualVM 或者是 `jmap` 命令生产堆文件,然后导入工具中进行分析。
601 | - **GCeasy**:一款在线的 GC 日志分析器,使用起来非常方便,用户可以通过它的 Web 网站导入 GC 日志,实时进行内存泄漏检测、GC 暂停原因分析、JVM 配置建议优化等功能。网站地址: 。
602 | - **GCViewer**:一款非常强大的 GC 日志可视化分析工具,功能强大而且完全免费。
603 | - **JProfiler**:一款商用的性能分析利器,功能强大,但需要付费使用。 它提供更深入的性能分析功能,例如方法调用分析、内存分配分析等。
604 | - **Arthas**:阿里开源的一款线上监控诊断工具,可以查看应用负载、内存、gc、线程等信息。
605 |
606 | ### 如何查看服务器上运行的 Java 进程?
607 |
608 | JDK 自带的 `jps` (JVM Process Status) 命令专门用于列出当前用户下所有正在运行的 JVM 实例。
609 |
610 | `jps` 的基础用法和几个核心参数如下:
611 |
612 | - **`jps`**:这是最基础的用法,它会列出 Java 进程的 **LVMID**(本地虚拟机唯一 ID,通常就是操作系统的进程号 PID)和**主类名**(或 Jar 包名)。
613 | - **`jps -l`**:这是我最常用的参数之一。它会输出主类的**完整包名**,或者如果应用是通过 Jar 包运行的,会输出 Jar 包的**完整路径**。这在同一台机器上部署了多个来自不同项目的 Java 应用时,能非常清晰地区分它们。
614 | - **`jps -v`**:这个参数也非常实用,尤其是在排查配置问题时。它会显示传递给 JVM 的参数,例如 `-Xmx`、`-Xms`、`-XX:+UseG1GC` 等。通过它,我可以快速确认应用的内存配置、GC 策略等是否符合预期。
615 | - **`jps -m`**:这个参数用于查看传递给主函数 `main()` 的参数。当我们需要确认程序启动时传入的业务参数是否正确时,它非常有用。
616 |
617 | 在某些情况下,`jps` 命令可能无法满足需求,这时我会采用标准的操作系统命令:
618 |
619 | 1. **权限问题**:jps 默认只能看到由**当前用户**启动的 Java 进程。如果需要查看服务器上所有用户(如 root 或其他业务用户)的 Java 进程,jps 就会受限。
620 | 2. **环境问题**:在一些极简的生产环境或 Docker 容器中,可能只安装了 JRE 而没有完整的 JDK,此时 jps 命令可能不存在。
621 |
622 | 在这些情况下,我会使用 ps 命令来查找,例如:
623 |
624 | ```bash
625 | # 列出所有进程,然后通过 grep 过滤出包含 "java" 关键字的进程
626 | ps -ef | grep java
627 | ```
628 |
629 | ### 堆内存相关的 JVM 参数有哪些?
630 |
631 | **堆内存大小控制**:
632 |
633 | 1. **`-Xms`** :设置 JVM 初始堆内存大小(如`-Xms512m`表示初始堆为 512MB)。
634 | 2. **`-Xmx`** :设置 JVM 最大堆内存大小(如`-Xmx1g`表示最大堆为 1GB)。
635 |
636 | 在生产环境中,强烈建议将 `-Xms` 和 `-Xmx` 设置为相同的值。这样做可以避免 JVM 在运行时根据负载情况动态地收缩和扩展堆内存,这个过程会引发不必要的 Full GC 和性能抖动,从而提高服务的稳定性和响应速度。
637 |
638 | **新生代与老年代**:
639 |
640 | 1. **`-Xmn`**:这是最直接控制新生代大小的方式,优先级高于 -`XX:NewRatio`。设置后,老年代的大小就是 `-Xmx` 减去 `-Xmn`。当我们对应用的对象生命周期有明确的判断时(例如,有大量的短生命周期对象),可以直接给新生代一个合适的大小,以达到更好的 GC 性能。
641 | 2. **`-XX:NewRatio`**:这是另一种调节新生代大小的方式,默认值为 2,表示老年代:新生代 = 2:1。因此,新生代默认占整个堆的 1/3。如果设置为 3,则新生代占堆的 1/4。通常在 `-Xmn` 和 `-XX:NewRatio`中选择一个使用即可。
642 | 3. **`-XX:SurvivorRatio`**:设置新生代中 Eden 区与单个 Survivor 区的比例。默认值为 8,表示 Eden : From Survivor : To Survivor = 8:1:1。所以 Eden 区占整个新生代的 8/10。这个比例会影响对象能否在新生代中“存活”足够长的时间。如果 Survivor 区太小(即 `-XX:SurvivorRatio` 值过大),Minor GC 后存活的对象可能因为放不下而被迫提前进入老年代,增加 Full GC 的压力。
643 |
644 | **堆内存溢出相关参数**:
645 |
646 | 1. **`-XX:+HeapDumpOnOutOfMemoryError`** :当发生`OutOfMemoryError`(OOM)时,自动生成堆转储文件(`.hprof`),记录堆内存对象状态。
647 | 2. **`-XX:HeapDumpPath`** :指定 OOM 时堆转储文件的保存路径(如`-XX:HeapDumpPath=/logs/heapdump.hprof`),默认生成在程序运行目录。
648 |
649 | 最重要的 JVM 参数可以参考这篇文章:[最重要的 JVM 参数总结](https://javaguide.cn/java/jvm/jvm-parameters-intro.html)。
650 |
651 | ### 如何检测死锁?
652 |
653 | - 使用`jmap`、`jstack`等命令查看 JVM 线程栈和堆内存的情况。如果有死锁,`jstack` 的输出中通常会有 `Found one Java-level deadlock:`的字样,后面会跟着死锁相关的线程信息。另外,实际项目中还可以搭配使用`top`、`df`、`free`等命令查看操作系统的基本情况,出现死锁可能会导致 CPU、内存等资源消耗过高。
654 | - 采用 VisualVM、JConsole 等工具进行排查。
655 |
656 | 这里以 JConsole 工具为例进行演示。
657 |
658 | 首先,我们要找到 JDK 的 bin 目录,找到 jconsole 并双击打开。
659 |
660 | 
661 |
662 | 对于 MAC 用户来说,可以通过 `/usr/libexec/java_home -V`查看 JDK 安装目录,找到后通过 `open . + 文件夹地址`打开即可。例如,我本地的某个 JDK 的路径是:
663 |
664 | ```bash
665 | open . /Users/guide/Library/Java/JavaVirtualMachines/corretto-1.8.0_252/Contents/Home
666 | ```
667 |
668 | 打开 jconsole 后,连接对应的程序,然后进入线程界面选择检测死锁即可!
669 |
670 | 
671 |
672 | 
673 |
674 | 详细介绍可以查看这篇文章的死锁部分内容:[Java 并发常见面试题总结(上)](https://javaguide.cn/java/concurrent/java-concurrent-questions-01.html)。
675 |
676 | ### 什么是 Heap Dump 文件?如何生成 Heap Dump 文件?
677 |
678 | Heap Dump(堆转储文件)是 Java 虚拟机(JVM)在某个特定时间点,对整个 Java **堆内存**的快照。它是一个二进制文件,包含了快照时刻堆中所有对象的信息,例如:
679 |
680 | - **对象实例**:每个对象的数据。
681 | - **类信息**:对象的类名、父类、静态字段等。
682 | - **引用关系**:对象之间复杂的引用链,即谁持有了谁。
683 | - **线程信息**:堆栈信息,特别是与 GC Roots 相关的线程栈。
684 |
685 | 简单来说,Heap Dump 就是 Java 进程在某一刻的“内存 X 光片”,是诊断内存问题的最核心、最权威的依据。
686 |
687 | #### 自动生成
688 |
689 | 在 JVM 启动参数中加入以下配置,这是生产环境排查 OOM 问题的首选方案。
690 |
691 | ```bash
692 | # 当发生 OutOfMemoryError 时,自动生成 Heap Dump 文件
693 | -XX:+HeapDumpOnOutOfMemoryError
694 |
695 | # 指定 Heap Dump 文件的生成路径,例如:/home/app/dumps/
696 | -XX:HeapDumpPath=
697 | ```
698 |
699 | #### 手动生成
700 |
701 | 当应用出现内存疑似异常(如内存持续升高、GC 频繁)但未崩溃时,可以手动生成快照进行分析。
702 |
703 | 1. **jmap** :JDK 自带的命令行工具,专门用于生成堆快照。使用示例:`jmap -dump:format=b,file=heapdump.hprof `。在执行时会触发 STW ,导致 Java 进程短暂停顿,对生产环境有一定影响。在高版本 JDK 中已不推荐直接使用。
704 | 2. **jcmd** :JDK 7 之后引入的多功能命令行工具,功能比 jmap 更强大一些,可用来替代 jmap,侵入性更小。使用示例:`jcmd GC.heap_dump /path/to/heapdump.hprof`。
705 | 3. **Arthas**:阿里巴巴开源的 Java 诊断神器,对应用无侵入,功能强大,可在不重启服务的情况下动态分析。使用示例:`heapdump /tmp/heapdump.hprof`。
706 | 4. **可视化工具**:如 JVisualVM、JProfiler、YourKit 等,都提供了图形化界面,点击按钮即可生成 Heap Dump 文件,并能直接进行分析,非常方便。
707 |
708 | ### 遇到 OutOfMemoryError 怎么排查解决?
709 |
710 | 我们可以通过 MAT、JVisualVM 等工具分析 Heap Dump 找到导致`OutOfMemoryError` 的原因。
711 |
712 | 以 MAT 为例,其提供的泄漏嫌疑(Leak Suspects)报告是 MAT 最强大的功能之一。它会基于启发式算法自动分析整个堆,直接指出最可疑的内存泄漏点,并给出详细的报告,包括问题组件、累积点(Accumulation Point)和引用链的图示。
713 |
714 | 如果“泄漏嫌疑”报告不够明确,或者想要分析的是内存占用过高(而非泄漏)问题,可以切换到**支配树(Dominator Tree)**视图。这个视图将内存对象关系组织成一棵树,父节点“支配”子节点(即父节点被回收,子节点也必被回收)。
715 |
716 | 下面是一段模拟出现 `OutOfMemoryError`的代码:
717 |
718 | ```java
719 | import java.util.ArrayList;
720 | import java.util.List;
721 |
722 | public class SimpleLeak {
723 |
724 | // 静态集合,生命周期与应用程序一样长
725 | public static List staticList = new ArrayList<>();
726 |
727 | public void leakMethod() {
728 | // 每次调用都向静态集合中添加一个 1MB 的字节数组
729 | staticList.add(new byte[1024 * 1024]); // 1MB
730 | }
731 |
732 | public static void main(String[] args) throws InterruptedException {
733 | SimpleLeak leak = new SimpleLeak();
734 | System.out.println("Starting leak simulation...");
735 |
736 | // 循环添加对象,模拟内存泄漏过程
737 | for (int i = 0; i < 200; i++) {
738 | leak.leakMethod();
739 | System.out.println("Added " + (i + 1) + " MB to the list.");
740 | Thread.sleep(200); // 稍微延时,方便观察
741 | }
742 |
743 | System.out.println("Leak simulation finished. Keeping process alive for Heap Dump.");
744 | // 保持进程存活,以便我们有时间生成 Heap Dump
745 | Thread.sleep(Long.MAX_VALUE);
746 | }
747 | }
748 | ```
749 |
750 | 为了更快让程序出现 `OutOfMemoryError` 问题,我们可以故意设置一个较小的堆 `-Xmx256m`。
751 |
752 | IDEA 设置 VM 参数的方式如下图所示:
753 |
754 | 
755 |
756 | 具体设置的 VM 参数是:`-Xmx128m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=simple_leak.hprof`,其中:
757 |
758 | - `-Xmx128m`:设置 JVM 最大堆内存为 128MB。
759 | - `-XX:+HeapDumpOnOutOfMemoryError`:当 JVM 发生 `OutOfMemoryError` 时,自动生成堆转储文件(`.hprof`)。
760 | - `-XX:HeapDumpPath=simple_leak.hprof`:指定 OOM 时生成的堆转储文件路径及文件名(这里是 `simple_leak.hprof`)。
761 |
762 | 运行程序之后,会出现 `OutOfMemoryError`并自动生成了 Heap Dump 文件。
763 |
764 | ```bash
765 | Starting leak simulation...
766 | Added 1 MB to the list.
767 | Added 2 MB to the list.
768 | Added 3 MB to the list.
769 | ......
770 | Added 113 MB to the list.
771 | Added 114 MB to the list.
772 | Added 115 MB to the list.
773 | java.lang.OutOfMemoryError: Java heap space
774 | Dumping heap to simple_leak.hprof ...
775 | Heap dump file created [124217346 bytes in 0.121 secs]
776 | ```
777 |
778 | 我们将 `.hprof` 文件导入 MAT 后,它会首先进行解析和索引。完成后,可以查看它的 **“泄漏嫌疑报告” (Leak Suspects Report)**。
779 |
780 | 
781 |
782 | 下图中的 Problem Suspect 1 就是可能出现内存泄露的问题分析:
783 |
784 | 
785 |
786 | - `cn.javaguide.SimpleLeak` 类由 `sun.misc.Launcher$AppClassLoader` 加载,占用 **120,589,040 字节(约 115MB,占堆 98.80%)**,是内存占用的核心。
787 | - 内存主要被 **`java.lang.Object[]` 数组** 占用(120,588,752 字节),说明 `SimpleLeak` 中可能存在大量 `Object` 数组未释放,触发内存泄漏。
788 |
789 | Problem Suspect 1 的可以看到有一个 **Details**,点进去即可看到内存泄漏的关键路径和对象占比:
790 |
791 | 
792 |
793 | 可以看到:`SimpleLeak` 中的**静态集合 `staticList`** 是内存泄漏的 “根源”,因为静态变量生命周期与类一致,若持续向其中添加对象且不清理,会导致对象无法被 GC 回收。
794 |
795 | ### 遇到过 GC 问题吗?怎么分析和解决的?
796 |
797 | 美团技术团队的 [Java 中 9 种常见的 CMS GC 问题分析与解决](https://tech.meituan.com/2020/11/12/java-9-cms-gc.html)这篇文章共 2w+ 字,详细介绍了 GC 基础,总结了 CMS GC 的一些常见问题分析与解决办法。
798 |
--------------------------------------------------------------------------------
/docs/cs-basics/operating-system.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 操作系统常见面试题总结
3 | category: 计算机基础
4 | tag:
5 | - 操作系统
6 | head:
7 | - - meta
8 | - name: keywords
9 | content: 操作系统面试突击,用户态内核态,系统调用,进程线程区别,多线程同步,死锁预防避免,虚拟内存分页,TLB快表,页面置换算法,硬链接软链接,磁盘调度算法
10 | - - meta
11 | - name: description
12 | content: 最新操作系统高频面试题总结:用户态/内核态+系统调用、进程/线程/死锁高频题、虚拟内存分页分段对比、TLB+页缺失+LRU置换、文件系统&磁盘调度,一图一表速记,快速通关后端面试!
13 | ---
14 |
15 | ------
16 |
17 | 
18 |
19 | ------
20 |
21 | ## 前言
22 |
23 | 由于很多读者都有突击面试的需求,所以我在几年前就弄了 **JavaGuide 面试突击版本**(JavaGuide 内容精简版,只保留重点),并持续完善跟进。对于喜欢纸质阅读的朋友来说,也可以打印出来,整体阅读体验非常高!
24 |
25 | 除了只保留最常问的面试题之外,我还进一步对重点中的重点进行了⭐️标注。并且,有亮色(白天)和暗色(夜间)两个主题选择,需要打印出来的朋友记得选择亮色版本。
26 |
27 | 对于时间比较充裕的朋友,我个人还是更推荐 [JavaGuide](https://javaguide.cn/) 网站系统学习,内容更全面,更深入。
28 |
29 | JavaGuide 已经持续维护 6 年多了,累计提交了接近 **6000** commit ,共有 **570+** 多位贡献者共同参与维护和完善。用心做原创优质内容,如果觉得有帮助的话,欢迎点赞分享!传送门:[GitHub](https://github.com/Snailclimb/JavaGuide) | [Gitee](https://gitee.com/SnailClimb/JavaGuide)。
30 |
31 | 对于需要更进一步面试辅导服务的读者,欢迎加入 **[JavaGuide 官方知识星球](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)**(技术专栏/一对一提问/简历修改/求职指南/面试打卡),绝对物超所值!
32 |
33 | 面试突击最新版本可以在我的公众号回复“**PDF**”获取([JavaGuide 官方知识星球](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)会提前同步最新版,针对球友的一个小福利)。
34 |
35 | 
36 |
37 | 这部分内容摘自 [JavaGuide](https://javaguide.cn/) 下面几篇文章中的重点:
38 |
39 | - [操作系统常见面试题总结(上)](https://javaguide.cn/cs-basics/operating-system/operating-system-basic-questions-01.html)(操作系统基础、进程和线程、死锁)
40 | - [操作系统常见面试题总结(下)](https://javaguide.cn/cs-basics/operating-system/operating-system-basic-questions-02.html)(内存管理、文件系统)
41 |
42 | ## 操作系统基础
43 |
44 | 
45 |
46 | ### 什么是操作系统?
47 |
48 | 通过以下四点可以概括操作系统到底是什么:
49 |
50 | 1. 操作系统(Operating System,简称 OS)是管理计算机硬件与软件资源的程序,是计算机的基石。
51 | 2. 操作系统本质上是一个运行在计算机上的软件程序 ,主要用于管理计算机硬件和软件资源。 举例:运行在你电脑上的所有应用程序都通过操作系统来调用系统内存以及磁盘等等硬件。
52 | 3. 操作系统存在屏蔽了硬件层的复杂性。 操作系统就像是硬件使用的负责人,统筹着各种相关事项。
53 | 4. 操作系统的内核(Kernel)是操作系统的核心部分,它负责系统的内存管理,硬件设备的管理,文件系统的管理以及应用程序的管理。 内核是连接应用程序和硬件的桥梁,决定着系统的性能和稳定性。
54 |
55 | 很多人容易把操作系统的内核(Kernel)和中央处理器(CPU,Central Processing Unit)弄混。你可以简单从下面两点来区别:
56 |
57 | 1. 操作系统的内核(Kernel)属于操作系统层面,而 CPU 属于硬件。
58 | 2. CPU 主要提供运算,处理各种指令的能力。内核(Kernel)主要负责系统管理比如内存管理,它屏蔽了对硬件的操作。
59 |
60 | 下图清晰说明了应用程序、内核、CPU 这三者的关系。
61 |
62 | 
63 |
64 | ### 操作系统主要有哪些功能?
65 |
66 | 从资源管理的角度来看,操作系统有 6 大功能:
67 |
68 | 1. **进程和线程的管理**:进程的创建、撤销、阻塞、唤醒,进程间的通信等。
69 | 2. **存储管理**:内存的分配和管理、外存(磁盘等)的分配和管理等。
70 | 3. **文件管理**:文件的读、写、创建及删除等。
71 | 4. **设备管理**:完成设备(输入输出设备和外部存储设备等)的请求或释放,以及设备启动等功能。
72 | 5. **网络管理**:操作系统负责管理计算机网络的使用。网络是计算机系统中连接不同计算机的方式,操作系统需要管理计算机网络的配置、连接、通信和安全等,以提供高效可靠的网络服务。
73 | 6. **安全管理**:用户的身份认证、访问控制、文件加密等,以防止非法用户对系统资源的访问和操作。
74 |
75 | ### ⭐️用户态和内核态
76 |
77 | #### 什么是用户态和内核态?
78 |
79 | 根据进程访问资源的特点,我们可以把进程在系统上的运行分为两个级别:
80 |
81 | - **用户态(User Mode)** : 用户态运行的进程可以直接读取用户程序的数据,拥有较低的权限。当应用程序需要执行某些需要特殊权限的操作,例如读写磁盘、网络通信等,就需要向操作系统发起系统调用请求,进入内核态。
82 | - **内核态(Kernel Mode)**:内核态运行的进程几乎可以访问计算机的任何资源包括系统的内存空间、设备、驱动程序等,不受限制,拥有非常高的权限。当操作系统接收到进程的系统调用请求时,就会从用户态切换到内核态,执行相应的系统调用,并将结果返回给进程,最后再从内核态切换回用户态。
83 |
84 | 
85 |
86 | 内核态相比用户态拥有更高的特权级别,因此能够执行更底层、更敏感的操作。不过,由于进入内核态需要付出较高的开销(需要进行一系列的上下文切换和权限检查),应该尽量减少进入内核态的次数,以提高系统的性能和稳定性。
87 |
88 | #### 为什么要有用户态和内核态?只有一个内核态不行么?
89 |
90 | - 在 CPU 的所有指令中,有一些指令是比较危险的比如内存分配、设置时钟、IO 处理等,如果所有的程序都能使用这些指令的话,会对系统的正常运行造成灾难性地影响。因此,我们需要限制这些危险指令只能内核态运行。这些只能由操作系统内核态执行的指令也被叫做 **特权指令** 。
91 | - 如果计算机系统中只有一个内核态,那么所有程序或进程都必须共享系统资源,例如内存、CPU、硬盘等,这将导致系统资源的竞争和冲突,从而影响系统性能和效率。并且,这样也会让系统的安全性降低,毕竟所有程序或进程都具有相同的特权级别和访问权限。
92 |
93 | 因此,同时具有用户态和内核态主要是为了保证计算机系统的安全性、稳定性和性能。
94 |
95 | #### 用户态和内核态是如何切换的?
96 |
97 | 
98 |
99 | 用户态切换到内核态的 3 种方式:
100 |
101 | 1. **系统调用(Trap)**:用户态进程 **主动** 要求切换到内核态的一种方式,主要是为了使用内核态才能做的事情比如读取磁盘资源。系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现。
102 | 2. **中断(Interrupt)**:当外围设备完成用户请求的操作后,会向 CPU 发出相应的中断信号,这时 CPU 会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
103 | 3. **异常(Exception)**:当 CPU 在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
104 |
105 | 在系统的处理上,中断和异常类似,都是通过中断向量表来找到相应的处理程序进行处理。区别在于,中断来自处理器外部,不是由任何一条专门的指令造成,而异常是执行当前指令的结果。
106 |
107 | ### ⭐️系统调用
108 |
109 | #### 什么是系统调用?
110 |
111 | 我们运行的程序基本都是运行在用户态,如果我们调用操作系统提供的内核态级别的子功能咋办呢?那就需要系统调用了!
112 |
113 | 也就是说在我们运行的用户程序中,凡是与系统态级别的资源有关的操作(如文件管理、进程控制、内存管理等),都必须通过系统调用方式向操作系统提出服务请求,并由操作系统代为完成。
114 |
115 | 
116 |
117 | 这些系统调用按功能大致可分为如下几类:
118 |
119 | - 设备管理:完成设备(输入输出设备和外部存储设备等)的请求或释放,以及设备启动等功能。
120 | - 文件管理:完成文件的读、写、创建及删除等功能。
121 | - 进程管理:进程的创建、撤销、阻塞、唤醒,进程间的通信等功能。
122 | - 内存管理:完成内存的分配、回收以及获取作业占用内存区大小及地址等功能。
123 |
124 | 系统调用和普通库函数调用非常相似,只是系统调用由操作系统内核提供,运行于内核态,而普通的库函数调用由函数库或用户自己提供,运行于用户态。
125 |
126 | 总结:系统调用是应用程序与操作系统之间进行交互的一种方式,通过系统调用,应用程序可以访问操作系统底层资源例如文件、设备、网络等。
127 |
128 | #### 系统调用的过程了解吗?
129 |
130 | 系统调用的过程可以简单分为以下几个步骤:
131 |
132 | 1. 用户态的程序发起系统调用,因为系统调用中涉及一些特权指令(只能由操作系统内核态执行的指令),用户态程序权限不足,因此会中断执行,也就是 Trap(Trap 是一种中断)。
133 | 2. 发生中断后,当前 CPU 执行的程序会中断,跳转到中断处理程序。内核程序开始执行,也就是开始处理系统调用。
134 | 3. 当系统调用处理完成后,操作系统使用特权指令(如 `iret`、`sysret` 或 `eret`)切换回用户态,恢复用户态的上下文,继续执行用户程序。
135 |
136 | 
137 |
138 | ## ⭐️进程和线程
139 |
140 | ### 什么是进程和线程?
141 |
142 | - **进程(Process)** 是指计算机中正在运行的一个程序实例。举例:你打开的微信就是一个进程。
143 | - **线程(Thread)** 也被称为轻量级进程,更加轻量。多个线程可以在同一个进程中同时执行,并且共享进程的资源比如内存空间、文件句柄、网络连接等。举例:你打开的微信里就有一个线程专门用来拉取别人发你的最新的消息。
144 |
145 | ### 进程和线程的区别是什么?
146 |
147 | 下图是 Java 内存区域,我们从 JVM 的角度来说一下线程和进程之间的关系吧!
148 |
149 | 
150 |
151 | 从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的**堆**和**方法区 (JDK1.8 之后的元空间)**资源,但是每个线程有自己的**程序计数器**、**虚拟机栈** 和 **本地方法栈**。
152 |
153 | **总结:**
154 |
155 | - 线程是进程划分成的更小的运行单位,一个进程在其执行的过程中可以产生多个线程。
156 | - 线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。
157 | - 线程执行开销小,但不利于资源的管理和保护;而进程正相反。
158 |
159 | ### 有了进程为什么还需要线程?
160 |
161 | - 进程切换是一个开销很大的操作,线程切换的成本较低。
162 | - 线程更轻量,一个进程可以创建多个线程。
163 | - 多个线程可以并发处理不同的任务,更有效地利用了多处理器和多核计算机。而进程只能在一个时间干一件事,如果在执行过程中遇到阻塞问题比如 IO 阻塞就会挂起直到结果返回。
164 | - 同一进程内的线程共享内存和文件,因此它们之间相互通信无须调用内核。
165 |
166 | ### 为什么要使用多线程?
167 |
168 | 先从总体上来说:
169 |
170 | - **从计算机底层来说:** 线程可以比作是轻量级的进程,是程序执行的最小单位,线程间的切换和调度的成本远远小于进程。另外,多核 CPU 时代意味着多个线程可以同时运行,这减少了线程上下文切换的开销。
171 | - **从当代互联网发展趋势来说:** 现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。
172 |
173 | 再深入到计算机底层来探讨:
174 |
175 | - **单核时代**:在单核时代多线程主要是为了提高单进程利用 CPU 和 IO 系统的效率。 假设只运行了一个 Java 进程的情况,当我们请求 IO 的时候,如果 Java 进程中只有一个线程,此线程被 IO 阻塞则整个进程被阻塞。CPU 和 IO 设备只有一个在运行,那么可以简单地说系统整体效率只有 50%。当使用多线程的时候,一个线程被 IO 阻塞,其他线程还可以继续使用 CPU。从而提高了 Java 进程利用系统资源的整体效率。
176 | - **多核时代**: 多核时代多线程主要是为了提高进程利用多核 CPU 的能力。举个例子:假如我们要计算一个复杂的任务,我们只用一个线程的话,不论系统有几个 CPU 核心,都只会有一个 CPU 核心被利用到。而创建多个线程,这些线程可以被映射到底层多个 CPU 上执行,在任务中的多个线程没有资源竞争的情况下,任务执行的效率会有显著性的提高,约等于(单核时执行时间/CPU 核心数)。
177 |
178 | ### 线程间的同步的方式有哪些?
179 |
180 | 线程同步是两个或多个共享关键资源的线程的并发执行。应该同步线程以避免关键的资源使用冲突。
181 |
182 | 下面是几种常见的线程同步的方式:
183 |
184 | 1. **互斥锁(Mutex)** :采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。比如 Java 中的 `synchronized` 关键词和各种 `Lock` 都是这种机制。
185 | 2. **读写锁(Read-Write Lock)** :允许多个线程同时读取共享资源,但只有一个线程可以对共享资源进行写操作。
186 | 3. **信号量(Semaphore)** :它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量。
187 | 4. **屏障(Barrier)** :屏障是一种同步原语,用于等待多个线程到达某个点再一起继续执行。当一个线程到达屏障时,它会停止执行并等待其他线程到达屏障,直到所有线程都到达屏障后,它们才会一起继续执行。比如 Java 中的 `CyclicBarrier` 是这种机制。
188 | 5. **事件(Event)** :Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作。
189 |
190 | ### PCB 是什么?包含哪些信息?
191 |
192 | **PCB(Process Control Block)** 即进程控制块,是操作系统中用来管理和跟踪进程的数据结构,每个进程都对应着一个独立的 PCB。你可以将 PCB 视为进程的大脑。
193 |
194 | 当操作系统创建一个新进程时,会为该进程分配一个唯一的进程 ID,并且为该进程创建一个对应的进程控制块。当进程执行时,PCB 中的信息会不断变化,操作系统会根据这些信息来管理和调度进程。
195 |
196 | PCB 主要包含下面几部分的内容:
197 |
198 | - 进程的描述信息,包括进程的名称、标识符等等;
199 | - 进程的调度信息,包括进程阻塞原因、进程状态(就绪、运行、阻塞等)、进程优先级(标识进程的重要程度)等等;
200 | - 进程对资源的需求情况,包括 CPU 时间、内存空间、I/O 设备等等。
201 | - 进程打开的文件信息,包括文件描述符、文件类型、打开模式等等。
202 | - 处理机的状态信息(由处理机的各种寄存器中的内容组成的),包括通用寄存器、指令计数器、程序状态字 PSW、用户栈指针。
203 | - ……
204 |
205 | ### 进程有哪几种状态?
206 |
207 | 我们一般把进程大致分为 5 种状态,这一点和线程很像!
208 |
209 | - **创建状态(new)**:进程正在被创建,尚未到就绪状态。
210 | - **就绪状态(ready)**:进程已处于准备运行状态,即进程获得了除了处理器之外的一切所需资源,一旦得到处理器资源(处理器分配的时间片)即可运行。
211 | - **运行状态(running)**:进程正在处理器上运行(单核 CPU 下任意时刻只有一个进程处于运行状态)。
212 | - **阻塞状态(waiting)**:又称为等待状态,进程正在等待某一事件而暂停运行如等待某资源为可用或等待 IO 操作完成。即使处理器空闲,该进程也不能运行。
213 | - **结束状态(terminated)**:进程正在从系统中消失。可能是进程正常结束或其他原因中断退出运行。
214 |
215 | 
216 |
217 | ### 进程间的通信方式有哪些?
218 |
219 | > 下面这部分总结参考了:[《进程间通信 IPC (InterProcess Communication)》](https://www.jianshu.com/p/c1015f5ffa74) 这篇文章,推荐阅读,总结的非常不错。
220 |
221 | 1. **管道/匿名管道(Pipes)** :用于具有亲缘关系的父子进程间或者兄弟进程之间的通信。
222 | 2. **有名管道(Named Pipes)** : 匿名管道由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循 **先进先出(First In First Out)** 。有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信。
223 | 3. **信号(Signal)** :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生;
224 | 4. **消息队列(Message Queuing)** :消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。管道和消息队列的通信数据都是先进先出的原则。与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显式地删除一个消息队列时,该消息队列才会被真正的删除。消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比 FIFO 更有优势。消息队列克服了信号承载信息量少,管道只能承载无格式字 节流以及缓冲区大小受限等缺点。
225 | 5. **信号量(Semaphores)** :信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。
226 | 6. **共享内存(Shared memory)** :使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。
227 | 7. **套接字(Sockets)** : 此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持 TCP/IP 的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
228 |
229 | ### 进程的调度算法有哪些?
230 |
231 | 
232 |
233 | 这是一个很重要的知识点!为了确定首先执行哪个进程以及最后执行哪个进程以实现最大 CPU 利用率,计算机科学家已经定义了一些算法,它们是:
234 |
235 | - **先到先服务调度算法(FCFS,First Come, First Served)** : 从就绪队列中选择一个最先进入该队列的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
236 | - **短作业优先的调度算法(SJF,Shortest Job First)** : 从就绪队列中选出一个估计运行时间最短的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
237 | - **时间片轮转调度算法(RR,Round-Robin)** : 时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
238 | - **多级反馈队列调度算法(MFQ,Multi-level Feedback Queue)**:前面介绍的几种进程调度的算法都有一定的局限性。如**短进程优先的调度算法,仅照顾了短进程而忽略了长进程** 。多级反馈队列调度算法既能使高优先级的作业得到响应又能使短作业(进程)迅速完成,因而它是目前**被公认的一种较好的进程调度算法**,UNIX 操作系统采取的便是这种调度算法。
239 | - **优先级调度算法(Priority)**:为每个流程分配优先级,首先执行具有最高优先级的进程,依此类推。具有相同优先级的进程以 FCFS 方式执行。可以根据内存要求,时间要求或任何其他资源要求来确定优先级。
240 |
241 | ### 什么是僵尸进程和孤儿进程?
242 |
243 | 在 Unix/Linux 系统中,子进程通常是通过 fork()系统调用创建的,该调用会创建一个新的进程,该进程是原有进程的一个副本。子进程和父进程的运行是相互独立的,它们各自拥有自己的 PCB,即使父进程结束了,子进程仍然可以继续运行。
244 |
245 | 当一个进程调用 exit()系统调用结束自己的生命时,内核会释放该进程的所有资源,包括打开的文件、占用的内存等,但是该进程对应的 PCB 依然存在于系统中。这些信息只有在父进程调用 wait()或 waitpid()系统调用时才会被释放,以便让父进程得到子进程的状态信息。
246 |
247 | 这样的设计可以让父进程在子进程结束时得到子进程的状态信息,并且可以防止出现“僵尸进程”(即子进程结束后 PCB 仍然存在但父进程无法得到状态信息的情况)。
248 |
249 | - **僵尸进程**:子进程已经终止,但是其父进程仍在运行,且父进程没有调用 wait()或 waitpid()等系统调用来获取子进程的状态信息,释放子进程占用的资源,导致子进程的 PCB 依然存在于系统中,但无法被进一步使用。这种情况下,子进程被称为“僵尸进程”。避免僵尸进程的产生,父进程需要及时调用 wait()或 waitpid()系统调用来回收子进程。
250 | - **孤儿进程**:一个进程的父进程已经终止或者不存在,但是该进程仍在运行。这种情况下,该进程就是孤儿进程。孤儿进程通常是由于父进程意外终止或未及时调用 wait()或 waitpid()等系统调用来回收子进程导致的。为了避免孤儿进程占用系统资源,操作系统会将孤儿进程的父进程设置为 init 进程(进程号为 1),由 init 进程来回收孤儿进程的资源。
251 |
252 | ### 如何查看是否有僵尸进程?
253 |
254 | Linux 下可以使用 Top 命令查找,`zombie` 值表示僵尸进程的数量,为 0 则代表没有僵尸进程。
255 |
256 | 
257 |
258 | 下面这个命令可以定位僵尸进程以及该僵尸进程的父进程:
259 |
260 | ```
261 | ps -A -ostat,ppid,pid,cmd |grep -e '^[Zz]'
262 | ```
263 |
264 | ## ⭐️死锁
265 |
266 | ### 什么是死锁?
267 |
268 | 死锁(Deadlock)描述的是这样一种情况:多个进程/线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于进程/线程被无限期地阻塞,因此程序不可能正常终止。
269 |
270 | ### 能列举一个操作系统发生死锁的例子吗?
271 |
272 | 假设有两个进程 A 和 B,以及两个资源 X 和 Y,它们的分配情况如下:
273 |
274 | | 进程 | 占用资源 | 需求资源 |
275 | | ---- | -------- | -------- |
276 | | A | X | Y |
277 | | B | Y | X |
278 |
279 | 此时,进程 A 占用资源 X 并且请求资源 Y,而进程 B 已经占用了资源 Y 并请求资源 X。两个进程都在等待对方释放资源,无法继续执行,陷入了死锁状态。
280 |
281 | ### 产生死锁的四个必要条件是什么?
282 |
283 | 1. **互斥**:资源必须处于非共享模式,即一次只有一个进程可以使用。如果另一进程申请该资源,那么必须等待直到该资源被释放为止。
284 | 2. **占有并等待**:一个进程至少应该占有一个资源,并等待另一资源,而该资源被其他进程所占有。
285 | 3. **非抢占**:资源不能被抢占。只能在持有资源的进程完成任务后,该资源才会被释放。
286 | 4. **循环等待**:有一组等待进程 `{P0, P1,..., Pn}`, `P0` 等待的资源被 `P1` 占有,`P1` 等待的资源被 `P2` 占有,……,`Pn-1` 等待的资源被 `Pn` 占有,`Pn` 等待的资源被 `P0` 占有。
287 |
288 | **注意 ⚠️**:这四个条件是产生死锁的 **必要条件** ,也就是说只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
289 |
290 | 下面是百度百科对必要条件的解释:
291 |
292 | > 如果没有事物情况 A,则必然没有事物情况 B,也就是说如果有事物情况 B 则一定有事物情况 A,那么 A 就是 B 的必要条件。从逻辑学上看,B 能推导出 A,A 就是 B 的必要条件,等价于 B 是 A 的充分条件。
293 |
294 | ### 能写一个模拟产生死锁的代码吗?
295 |
296 | 下面通过一个实际的例子来模拟下图展示的线程死锁:
297 |
298 | 
299 |
300 | ```
301 | public class DeadLockDemo {
302 | private static Object resource1 = new Object();//资源 1
303 | private static Object resource2 = new Object();//资源 2
304 |
305 | public static void main(String[] args) {
306 | new Thread(() -> {
307 | synchronized (resource1) {
308 | System.out.println(Thread.currentThread() + "get resource1");
309 | try {
310 | Thread.sleep(1000);
311 | } catch (InterruptedException e) {
312 | e.printStackTrace();
313 | }
314 | System.out.println(Thread.currentThread() + "waiting get resource2");
315 | synchronized (resource2) {
316 | System.out.println(Thread.currentThread() + "get resource2");
317 | }
318 | }
319 | }, "线程 1").start();
320 |
321 | new Thread(() -> {
322 | synchronized (resource2) {
323 | System.out.println(Thread.currentThread() + "get resource2");
324 | try {
325 | Thread.sleep(1000);
326 | } catch (InterruptedException e) {
327 | e.printStackTrace();
328 | }
329 | System.out.println(Thread.currentThread() + "waiting get resource1");
330 | synchronized (resource1) {
331 | System.out.println(Thread.currentThread() + "get resource1");
332 | }
333 | }
334 | }, "线程 2").start();
335 | }
336 | }
337 | ```
338 |
339 | Output
340 |
341 | ```
342 | Thread[线程 1,5,main]get resource1
343 | Thread[线程 2,5,main]get resource2
344 | Thread[线程 1,5,main]waiting get resource2
345 | Thread[线程 2,5,main]waiting get resource1
346 | ```
347 |
348 | 线程 A 通过 `synchronized (resource1)` 获得 `resource1` 的监视器锁,然后通过`Thread.sleep(1000);`让线程 A 休眠 1s 为的是让线程 B 得到执行然后获取到 `resource2` 的监视器锁。线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源,然后这两个线程就会陷入互相等待的状态,这也就产生了死锁。
349 |
350 | ### 解决死锁的方法
351 |
352 | 解决死锁的方法可以从多个角度去分析,一般的情况下,有**预防,避免,检测和解除四种**。
353 |
354 | - **预防** 是采用某种策略,**限制并发进程对资源的请求**,从而使得死锁的必要条件在系统执行的任何时间上都不满足。
355 | - **避免**则是系统在分配资源时,根据资源的使用情况**提前做出预测**,从而**避免死锁的发生**
356 | - **检测**是指系统设有**专门的机构**,当死锁发生时,该机构能够检测死锁的发生,并精确地确定与死锁有关的进程和资源。
357 | - **解除** 是与检测相配套的一种措施,用于**将进程从死锁状态下解脱出来**。
358 |
359 | #### 死锁的预防
360 |
361 | 死锁四大必要条件上面都已经列出来了,很显然,只要破坏四个必要条件中的任何一个就能够预防死锁的发生。
362 |
363 | 破坏第一个条件 **互斥条件**:使得资源是可以同时访问的,这是种简单的方法,磁盘就可以用这种方法管理,但是我们要知道,有很多资源 **往往是不能同时访问的** ,所以这种做法在大多数的场合是行不通的。
364 |
365 | 破坏第三个条件 **非抢占**:也就是说可以采用 **剥夺式调度算法**,但剥夺式调度方法目前一般仅适用于 **主存资源** 和 **处理器资源** 的分配,并不适用于所有的资源,会导致 **资源利用率下降**。
366 |
367 | 所以一般比较实用的 **预防死锁的方法**,是通过考虑破坏第二个条件和第四个条件。
368 |
369 | **1、静态分配策略**
370 |
371 | 静态分配策略可以破坏死锁产生的第二个条件(占有并等待)。所谓静态分配策略,就是指一个进程必须在执行前就申请到它所需要的全部资源,并且知道它所要的资源都得到满足之后才开始执行。进程要么占有所有的资源然后开始执行,要么不占有资源,不会出现占有一些资源等待一些资源的情况。
372 |
373 | 静态分配策略逻辑简单,实现也很容易,但这种策略 **严重地降低了资源利用率**,因为在每个进程所占有的资源中,有些资源是在比较靠后的执行时间里采用的,甚至有些资源是在额外的情况下才使用的,这样就可能造成一个进程占有了一些 **几乎不用的资源而使其他需要该资源的进程产生等待** 的情况。
374 |
375 | **2、层次分配策略**
376 |
377 | 层次分配策略破坏了产生死锁的第四个条件(循环等待)。在层次分配策略下,所有的资源被分成了多个层次,一个进程得到某一次的一个资源后,它只能再申请较高一层的资源;当一个进程要释放某层的一个资源时,必须先释放所占用的较高层的资源,按这种策略,是不可能出现循环等待链的,因为那样的话,就出现了已经申请了较高层的资源,反而去申请了较低层的资源,不符合层次分配策略,证明略。
378 |
379 | #### 死锁的避免
380 |
381 | 上面提到的 **破坏** 死锁产生的四个必要条件之一就可以成功 **预防系统发生死锁** ,但是会导致 **低效的进程运行** 和 **资源使用率** 。而死锁的避免相反,它的角度是允许系统中**同时存在四个必要条件** ,只要掌握并发进程中与每个进程有关的资源动态申请情况,做出 **明智和合理的选择** ,仍然可以避免死锁,因为四大条件仅仅是产生死锁的必要条件。
382 |
383 | 我们将系统的状态分为 **安全状态** 和 **不安全状态** ,每当在为申请者分配资源前先测试系统状态,若把系统资源分配给申请者会产生死锁,则拒绝分配,否则接受申请,并为它分配资源。
384 |
385 | > 如果操作系统能够保证所有的进程在有限的时间内得到需要的全部资源,则称系统处于安全状态,否则说系统是不安全的。很显然,系统处于安全状态则不会发生死锁,系统若处于不安全状态则可能发生死锁。
386 |
387 | 那么如何保证系统保持在安全状态呢?通过算法,其中最具有代表性的 **避免死锁算法** 就是 Dijkstra 的银行家算法,银行家算法用一句话表达就是:当一个进程申请使用资源的时候,**银行家算法** 通过先 **试探** 分配给该进程资源,然后通过 **安全性算法** 判断分配后系统是否处于安全状态,若不安全则试探分配作废,让该进程继续等待,若能够进入到安全的状态,则就 **真的分配资源给该进程**。
388 |
389 | 银行家算法详情可见:[《一句话+一张图说清楚——银行家算法》](https://blog.csdn.net/qq_33414271/article/details/80245715) 。
390 |
391 | 操作系统教程书中讲述的银行家算法也比较清晰,可以一看.
392 |
393 | 死锁的避免(银行家算法)改善了 **资源使用率低的问题** ,但是它要不断地检测每个进程对各类资源的占用和申请情况,以及做 **安全性检查** ,需要花费较多的时间。
394 |
395 | #### 死锁的检测
396 |
397 | 对资源的分配加以限制可以 **预防和避免** 死锁的发生,但是都不利于各进程对系统资源的**充分共享**。解决死锁问题的另一条途径是 **死锁检测和解除** (这里突然联想到了乐观锁和悲观锁,感觉死锁的检测和解除就像是 **乐观锁** ,分配资源时不去提前管会不会发生死锁了,等到真的死锁出现了再来解决嘛,而 **死锁的预防和避免** 更像是悲观锁,总是觉得死锁会出现,所以在分配资源的时候就很谨慎)。
398 |
399 | 这种方法对资源的分配不加以任何限制,也不采取死锁避免措施,但系统 **定时地运行一个 “死锁检测”** 的程序,判断系统内是否出现死锁,如果检测到系统发生了死锁,再采取措施去解除它。
400 |
401 | ##### 进程-资源分配图
402 |
403 | 操作系统中的每一刻时刻的**系统状态**都可以用**进程-资源分配图**来表示,进程-资源分配图是描述进程和资源申请及分配关系的一种有向图,可用于**检测系统是否处于死锁状态**。
404 |
405 | 用一个方框表示每一个资源类,方框中的黑点表示该资源类中的各个资源,用一个圆圈表示每一个进程,用 **有向边** 来表示**进程申请资源和资源被分配的情况**。
406 |
407 | 图中 2-21 是**进程-资源分配图**的一个例子,其中共有三个资源类,每个进程的资源占有和申请情况已清楚地表示在图中。在这个例子中,由于存在 **占有和等待资源的环路** ,导致一组进程永远处于等待资源的状态,发生了 **死锁**。
408 |
409 | 
410 |
411 | 进程-资源分配图中存在环路并不一定是发生了死锁。因为循环等待资源仅仅是死锁发生的必要条件,而不是充分条件。图 2-22 便是一个有环路而无死锁的例子。虽然进程 P1 和进程 P3 分别占用了一个资源 R1 和一个资源 R2,并且因为等待另一个资源 R2 和另一个资源 R1 形成了环路,但进程 P2 和进程 P4 分别占有了一个资源 R1 和一个资源 R2,它们申请的资源得到了满足,在有限的时间里会归还资源,于是进程 P1 或 P3 都能获得另一个所需的资源,环路自动解除,系统也就不存在死锁状态了。
412 |
413 | ##### 死锁检测步骤
414 |
415 | 知道了死锁检测的原理,我们可以利用下列步骤编写一个 **死锁检测** 程序,检测系统是否产生了死锁。
416 |
417 | 1. 如果进程-资源分配图中无环路,则此时系统没有发生死锁
418 | 2. 如果进程-资源分配图中有环路,且每个资源类仅有一个资源,则系统中已经发生了死锁。
419 | 3. 如果进程-资源分配图中有环路,且涉及到的资源类有多个资源,此时系统未必会发生死锁。如果能在进程-资源分配图中找出一个 **既不阻塞又非独立的进程** ,该进程能够在有限的时间内归还占有的资源,也就是把边给消除掉了,重复此过程,直到能在有限的时间内 **消除所有的边** ,则不会发生死锁,否则会发生死锁。(消除边的过程类似于 **拓扑排序**)
420 |
421 | #### 死锁的解除
422 |
423 | 当死锁检测程序检测到存在死锁发生时,应设法让其解除,让系统从死锁状态中恢复过来,常用的解除死锁的方法有以下四种:
424 |
425 | 1. **立即结束所有进程的执行,重新启动操作系统**:这种方法简单,但以前所在的工作全部作废,损失很大。
426 | 2. **撤销涉及死锁的所有进程,解除死锁后继续运行**:这种方法能彻底打破**死锁的循环等待**条件,但将付出很大代价,例如有些进程可能已经计算了很长时间,由于被撤销而使产生的部分结果也被消除了,再重新执行时还要再次进行计算。
427 | 3. **逐个撤销涉及死锁的进程,回收其资源直至死锁解除。**
428 | 4. **抢占资源**:从涉及死锁的一个或几个进程中抢占资源,把夺得的资源再分配给涉及死锁的进程直至死锁解除。
429 |
430 | ## 内存管理
431 |
432 | ### 内存管理主要做了什么?
433 |
434 | 
435 |
436 | 操作系统的内存管理非常重要,主要负责下面这些事情:
437 |
438 | - **内存的分配与回收**:对进程所需的内存进行分配和释放,malloc 函数:申请内存,free 函数:释放内存。
439 | - **地址转换**:将程序中的虚拟地址转换成内存中的物理地址。
440 | - **内存扩充**:当系统没有足够的内存时,利用虚拟内存技术或自动覆盖技术,从逻辑上扩充内存。
441 | - **内存映射**:将一个文件直接映射到进程的进程空间中,这样可以通过内存指针用读写内存的办法直接存取文件内容,速度更快。
442 | - **内存优化**:通过调整内存分配策略和回收算法来优化内存使用效率。
443 | - **内存安全**:保证进程之间使用内存互不干扰,避免一些恶意程序通过修改内存来破坏系统的安全性。
444 | - ……
445 |
446 | ### 什么是内存碎片?
447 |
448 | 内存碎片是由内存的申请和释放产生的,通常分为下面两种:
449 |
450 | - **内部内存碎片(Internal Memory Fragmentation,简称为内存碎片)**:已经分配给进程使用但未被使用的内存。导致内部内存碎片的主要原因是,当采用固定比例比如 2 的幂次方进行内存分配时,进程所分配的内存可能会比其实际所需要的大。举个例子,一个进程只需要 65 字节的内存,但为其分配了 128(2^7) 大小的内存,那 63 字节的内存就成为了内部内存碎片。
451 | - **外部内存碎片(External Memory Fragmentation,简称为外部碎片)**:由于未分配的连续内存区域太小,以至于不能满足任意进程所需要的内存分配请求,这些小片段且不连续的内存空间被称为外部碎片。也就是说,外部内存碎片指的是那些并未分配给进程但又不能使用的内存。我们后面介绍的分段机制就会导致外部内存碎片。
452 |
453 | 
454 |
455 | 内存碎片会导致内存利用率下降,如何减少内存碎片是内存管理要非常重视的一件事情。
456 |
457 | ### ⭐️常见的内存管理方式有哪些?
458 |
459 | 内存管理方式可以简单分为下面两种:
460 |
461 | - **连续内存管理**:为一个用户程序分配一个连续的内存空间,内存利用率一般不高。
462 | - **非连续内存管理**:允许一个程序使用的内存分布在离散或者说不相邻的内存中,相对更加灵活一些。
463 |
464 | #### 连续内存管理
465 |
466 | **块式管理** 是早期计算机操作系统的一种连续内存管理方式,存在严重的内存碎片问题。块式管理会将内存分为几个固定大小的块,每个块中只包含一个进程。如果程序运行需要内存的话,操作系统就分配给它一块,如果程序运行只需要很小的空间的话,分配的这块内存很大一部分几乎被浪费了。这些在每个块中未被利用的空间,我们称之为内部内存碎片。除了内部内存碎片之外,由于两个内存块之间可能还会有外部内存碎片,这些不连续的外部内存碎片由于太小了无法再进行分配。
467 |
468 | 在 Linux 系统中,连续内存管理采用了 **伙伴系统(Buddy System)算法** 来实现,这是一种经典的连续内存分配算法,可以有效解决外部内存碎片的问题。伙伴系统的主要思想是将内存按 2 的幂次划分(每一块内存大小都是 2 的幂次比如 2^6=64 KB),并将相邻的内存块组合成一对伙伴(注意:**必须是相邻的才是伙伴**)。
469 |
470 | 当进行内存分配时,伙伴系统会尝试找到大小最合适的内存块。如果找到的内存块过大,就将其一分为二,分成两个大小相等的伙伴块。如果还是大的话,就继续切分,直到到达合适的大小为止。
471 |
472 | 假设两块相邻的内存块都被释放,系统会将这两个内存块合并,进而形成一个更大的内存块,以便后续的内存分配。这样就可以减少内存碎片的问题,提高内存利用率。
473 |
474 | 
475 |
476 | 虽然解决了外部内存碎片的问题,但伙伴系统仍然存在内存利用率不高的问题(内部内存碎片)。这主要是因为伙伴系统只能分配大小为 2^n 的内存块,因此当需要分配的内存大小不是 2^n 的整数倍时,会浪费一定的内存空间。举个例子:如果要分配 65 大小的内存快,依然需要分配 2^7=128 大小的内存块。
477 |
478 | 
479 |
480 | 对于内部内存碎片的问题,Linux 采用 **SLAB** 进行解决。由于这部分内容不是本篇文章的重点,这里就不详细介绍了。
481 |
482 | #### 非连续内存管理
483 |
484 | 非连续内存管理存在下面 3 种方式:
485 |
486 | - **段式管理**:以段(一段连续的物理内存)的形式管理/分配物理内存。应用程序的虚拟地址空间被分为大小不等的段,段是有实际意义的,每个段定义了一组逻辑信息,例如有主程序段 MAIN、子程序段 X、数据段 D 及栈段 S 等。
487 | - **页式管理**:把物理内存分为连续等长的物理页,应用程序的虚拟地址空间也被划分为连续等长的虚拟页,是现代操作系统广泛使用的一种内存管理方式。
488 | - **段页式管理机制**:结合了段式管理和页式管理的一种内存管理机制,把物理内存先分成若干段,每个段又继续分成若干大小相等的页。
489 |
490 | ### ⭐️虚拟内存
491 |
492 | #### 什么是虚拟内存?有什么用?
493 |
494 | **虚拟内存(Virtual Memory)** 是计算机系统内存管理非常重要的一个技术,本质上来说它只是逻辑存在的,是一个假想出来的内存空间,主要作用是作为进程访问主存(物理内存)的桥梁并简化内存管理。
495 |
496 | 
497 |
498 | 总结来说,虚拟内存主要提供了下面这些能力:
499 |
500 | - **隔离进程**:物理内存通过虚拟地址空间访问,虚拟地址空间与进程一一对应。每个进程都认为自己拥有了整个物理内存,进程之间彼此隔离,一个进程中的代码无法更改正在由另一进程或操作系统使用的物理内存。
501 | - **提升物理内存利用率**:有了虚拟地址空间后,操作系统只需要将进程当前正在使用的部分数据或指令加载入物理内存。
502 | - **简化内存管理**:进程都有一个一致且私有的虚拟地址空间,程序员不用和真正的物理内存打交道,而是借助虚拟地址空间访问物理内存,从而简化了内存管理。
503 | - **多个进程共享物理内存**:进程在运行过程中,会加载许多操作系统的动态库。这些库对于每个进程而言都是公用的,它们在内存中实际只会加载一份,这部分称为共享内存。
504 | - **提高内存使用安全性**:控制进程对物理内存的访问,隔离不同进程的访问权限,提高系统的安全性。
505 | - **提供更大的可使用内存空间**:可以让程序拥有超过系统物理内存大小的可用内存空间。这是因为当物理内存不够用时,可以利用磁盘充当,将物理内存页(通常大小为 4 KB)保存到磁盘文件(会影响读写速度),数据或代码页会根据需要在物理内存与磁盘之间移动。
506 |
507 | #### 没有虚拟内存有什么问题?
508 |
509 | 如果没有虚拟内存的话,程序直接访问和操作的都是物理内存,看似少了一层中介,但多了很多问题。
510 |
511 | **具体有什么问题呢?** 这里举几个例子说明(参考虚拟内存提供的能力回答这个问题):
512 |
513 | 1. 用户程序可以访问任意物理内存,可能会不小心操作到系统运行必需的内存,进而造成操作系统崩溃,严重影响系统的安全。
514 | 2. 同时运行多个程序容易崩溃。比如你想同时运行一个微信和一个 QQ 音乐,微信在运行的时候给内存地址 1xxx 赋值后,QQ 音乐也同样给内存地址 1xxx 赋值,那么 QQ 音乐对内存的赋值就会覆盖微信之前所赋的值,这就可能会造成微信这个程序会崩溃。
515 | 3. 程序运行过程中使用的所有数据或指令都要载入物理内存,根据局部性原理,其中很大一部分可能都不会用到,白白占用了宝贵的物理内存资源。
516 | 4. ……
517 |
518 | #### 什么是虚拟地址和物理地址?
519 |
520 | **物理地址(Physical Address)** 是真正的物理内存中地址,更具体点来说是内存地址寄存器中的地址。程序中访问的内存地址不是物理地址,而是 **虚拟地址(Virtual Address)** 。
521 |
522 | 也就是说,我们编程开发的时候实际就是在和虚拟地址打交道。比如在 C 语言中,指针里面存储的数值就可以理解成为内存里的一个地址,这个地址也就是我们说的虚拟地址。
523 |
524 | 操作系统一般通过 CPU 芯片中的一个重要组件 **MMU(Memory Management Unit,内存管理单元)** 将虚拟地址转换为物理地址,这个过程被称为 **地址翻译/地址转换(Address Translation)** 。
525 |
526 | 
527 |
528 | 通过 MMU 将虚拟地址转换为物理地址后,再通过总线传到物理内存设备,进而完成相应的物理内存读写请求。
529 |
530 | MMU 将虚拟地址翻译为物理地址的主要机制有两种: **分段机制** 和 **分页机制** 。
531 |
532 | #### 什么是虚拟地址空间和物理地址空间?
533 |
534 | - 虚拟地址空间是虚拟地址的集合,是虚拟内存的范围。每一个进程都有一个一致且私有的虚拟地址空间。
535 | - 物理地址空间是物理地址的集合,是物理内存的范围。
536 |
537 | #### 虚拟地址与物理内存地址是如何映射的?
538 |
539 | MMU 将虚拟地址翻译为物理地址的主要机制有 3 种:
540 |
541 | 1. 分段机制
542 | 2. 分页机制
543 | 3. 段页机制
544 |
545 | 其中,现代操作系统广泛采用分页机制,需要重点关注!
546 |
547 | ### ⭐️分段机制
548 |
549 | **分段机制(Segmentation)** 以段(一段 **连续** 的物理内存)的形式管理/分配物理内存。应用程序的虚拟地址空间被分为大小不等的段,段是有实际意义的,每个段定义了一组逻辑信息,例如有主程序段 MAIN、子程序段 X、数据段 D 及栈段 S 等。
550 |
551 | #### 段表有什么用?地址翻译过程是怎样的?
552 |
553 | 分段管理通过 **段表(Segment Table)** 映射虚拟地址和物理地址。
554 |
555 | 分段机制下的虚拟地址由两部分组成:
556 |
557 | - **段号**:标识着该虚拟地址属于整个虚拟地址空间中的哪一个段。
558 | - **段内偏移量**:相对于该段起始地址的偏移量。
559 |
560 | 具体的地址翻译过程如下:
561 |
562 | 1. MMU 首先解析得到虚拟地址中的段号;
563 | 2. 通过段号去该应用程序的段表中取出对应的段信息(找到对应的段表项);
564 | 3. 从段信息中取出该段的起始地址(物理地址)加上虚拟地址中的段内偏移量得到最终的物理地址。
565 |
566 | 
567 |
568 | 段表中还存有诸如段长(可用于检查虚拟地址是否超出合法范围)、段类型(该段的类型,例如代码段、数据段等)等信息。
569 |
570 | **通过段号一定要找到对应的段表项吗?得到最终的物理地址后对应的物理内存一定存在吗?**
571 |
572 | 不一定。段表项可能并不存在:
573 |
574 | - **段表项被删除**:软件错误、软件恶意行为等情况可能会导致段表项被删除。
575 | - **段表项还未创建**:如果系统内存不足或者无法分配到连续的物理内存块就会导致段表项无法被创建。
576 |
577 | #### 分段机制为什么会导致内存外部碎片?
578 |
579 | 分段机制容易出现外部内存碎片,即在段与段之间留下碎片空间(不足以映射给虚拟地址空间中的段)。从而造成物理内存资源利用率的降低。
580 |
581 | 举个例子:假设可用物理内存为 5G 的系统使用分段机制分配内存。现在有 4 个进程,每个进程的内存占用情况如下:
582 |
583 | - 进程 1:0~1G(第 1 段)
584 | - 进程 2:1~3G(第 2 段)
585 | - 进程 3:3~4.5G(第 3 段)
586 | - 进程 4:4.5~5G(第 4 段)
587 |
588 | 此时,我们关闭了进程 1 和进程 4,则第 1 段和第 4 段的内存会被释放,空闲物理内存还有 1.5G。由于这 1.5G 物理内存并不是连续的,导致没办法将空闲的物理内存分配给一个需要 1.5G 物理内存的进程。
589 |
590 | 
591 |
592 | ### ⭐️分页机制
593 |
594 | **分页机制(Paging)** 把主存(物理内存)分为连续等长的物理页,应用程序的虚拟地址空间划也被分为连续等长的虚拟页。现代操作系统广泛采用分页机制。
595 |
596 | **注意:这里的页是连续等长的,不同于分段机制下不同长度的段。**
597 |
598 | 在分页机制下,应用程序虚拟地址空间中的任意虚拟页可以被映射到物理内存中的任意物理页上,因此可以实现物理内存资源的离散分配。分页机制按照固定页大小分配物理内存,使得物理内存资源易于管理,可有效避免分段机制中外部内存碎片的问题。
599 |
600 | #### 页表有什么用?地址翻译过程是怎样的?
601 |
602 | 分页管理通过 **页表(Page Table)** 映射虚拟地址和物理地址。我这里画了一张基于单级页表进行地址翻译的示意图。
603 |
604 | 
605 |
606 | 在分页机制下,每个进程都会有一个对应的页表。
607 |
608 | 分页机制下的虚拟地址由两部分组成:
609 |
610 | - **页号**:通过虚拟页号可以从页表中取出对应的物理页号;
611 | - **页内偏移量**:物理页起始地址+页内偏移量=物理内存地址。
612 |
613 | 具体的地址翻译过程如下:
614 |
615 | 1. MMU 首先解析得到虚拟地址中的虚拟页号;
616 | 2. 通过虚拟页号去该应用程序的页表中取出对应的物理页号(找到对应的页表项);
617 | 3. 用该物理页号对应的物理页起始地址(物理地址)加上虚拟地址中的页内偏移量得到最终的物理地址。
618 |
619 | 
620 |
621 | 页表中还存有诸如访问标志(标识该页面有没有被访问过)、脏数据标识位等信息。
622 |
623 | **通过虚拟页号一定要找到对应的物理页号吗?找到了物理页号得到最终的物理地址后对应的物理页一定存在吗?**
624 |
625 | 不一定!可能会存在 **页缺失** 。也就是说,物理内存中没有对应的物理页或者物理内存中有对应的物理页但虚拟页还未和物理页建立映射(对应的页表项不存在)。关于页缺失的内容,后面会详细介绍到。
626 |
627 | #### 单级页表有什么问题?为什么需要多级页表?
628 |
629 | 以 32 位的环境为例,虚拟地址空间范围共有 2^32(4G)。假设 一个页的大小是 2^12(4KB),那页表项共有 4G / 4K = 2^20 个。每个页表项为一个地址,占用 4 字节,`2^20 * 2^2 / 1024 * 1024= 4MB`。也就是说一个程序啥都不干,页表大小就得占用 4M。
630 |
631 | 系统运行的应用程序多起来的话,页表的开销还是非常大的。而且,绝大部分应用程序可能只能用到页表中的几项,其他的白白浪费了。
632 |
633 | 为了解决这个问题,操作系统引入了 **多级页表** ,多级页表对应多个页表,每个页表与前一个页表相关联。32 位系统一般为二级页表,64 位系统一般为四级页表。
634 |
635 | 这里以二级页表为例进行介绍:二级列表分为一级页表和二级页表。一级页表共有 1024 个页表项,一级页表又关联二级页表,二级页表同样共有 1024 个页表项。二级页表中的一级页表项是一对多的关系,二级页表按需加载(只会用到很少一部分二级页表),进而节省空间占用。
636 |
637 | 假设只需要 2 个二级页表,那两级页表的内存占用情况为: 4KB(一级页表占用) + 4KB \* 2(二级页表占用) = 12 KB。
638 |
639 | 
640 |
641 | 多级页表属于时间换空间的典型场景,利用增加页表查询的次数减少页表占用的空间。
642 |
643 | #### TLB 有什么用?使用 TLB 之后的地址翻译流程是怎样的?
644 |
645 | 为了提高虚拟地址到物理地址的转换速度,操作系统在 **页表方案** 基础之上引入了 **转址旁路缓存(Translation Lookaside Buffer,TLB,也被称为快表)** 。
646 |
647 | 
648 |
649 | 在主流的 AArch64 和 x86-64 体系结构下,TLB 属于 (Memory Management Unit,内存管理单元) 内部的单元,本质上就是一块高速缓存(Cache),缓存了虚拟页号到物理页号的映射关系,你可以将其简单看作是存储着键(虚拟页号)值(物理页号)对的哈希表。
650 |
651 | 使用 TLB 之后的地址翻译流程是这样的:
652 |
653 | 1. 用虚拟地址中的虚拟页号作为 key 去 TLB 中查询;
654 | 2. 如果能查到对应的物理页的话,就不用再查询页表了,这种情况称为 TLB 命中(TLB hit)。
655 | 3. 如果不能查到对应的物理页的话,还是需要去查询主存中的页表,同时将页表中的该映射表项添加到 TLB 中,这种情况称为 TLB 未命中(TLB miss)。
656 | 4. 当 TLB 填满后,又要登记新页时,就按照一定的淘汰策略淘汰掉快表中的一个页。
657 |
658 | 
659 |
660 | 由于页表也在主存中,因此在没有 TLB 之前,每次读写内存数据时 CPU 要访问两次主存。有了 TLB 之后,对于存在于 TLB 中的页表数据只需要访问一次主存即可。
661 |
662 | TLB 的设计思想非常简单,但命中率往往非常高,效果很好。这就是因为被频繁访问的页就是其中的很小一部分。
663 |
664 | 看完了之后你会发现快表和我们平时经常在开发系统中使用的缓存(比如 Redis)很像,的确是这样的,操作系统中的很多思想、很多经典的算法,你都可以在我们日常开发使用的各种工具或者框架中找到它们的影子。
665 |
666 | #### 换页机制有什么用?
667 |
668 | 换页机制的思想是当物理内存不够用的时候,操作系统选择将一些物理页的内容放到磁盘上去,等要用到的时候再将它们读取到物理内存中。也就是说,换页机制利用磁盘这种较低廉的存储设备扩展的物理内存。
669 |
670 | 这也就解释了一个日常使用电脑常见的问题:为什么操作系统中所有进程运行所需的物理内存即使比真实的物理内存要大一些,这些进程也是可以正常运行的,只是运行速度会变慢。
671 |
672 | 这同样是一种时间换空间的策略,你用 CPU 的计算时间,页的调入调出花费的时间,换来了一个虚拟的更大的物理内存空间来支持程序的运行。
673 |
674 | #### 什么是页缺失?
675 |
676 | 根据维基百科:
677 |
678 | > 页缺失(Page Fault,又名硬错误、硬中断、分页错误、寻页缺失、缺页中断、页故障等)指的是当软件试图访问已映射在虚拟地址空间中,但是目前并未被加载在物理内存中的一个分页时,由 MMU 所发出的中断。
679 |
680 | 常见的页缺失有下面这两种:
681 |
682 | - **硬性页缺失(Hard Page Fault)**:物理内存中没有对应的物理页。于是,Page Fault Handler 会指示 CPU 从已经打开的磁盘文件中读取相应的内容到物理内存,而后交由 MMU 建立相应的虚拟页和物理页的映射关系。
683 | - **软性页缺失(Soft Page Fault)**:物理内存中有对应的物理页,但虚拟页还未和物理页建立映射。于是,Page Fault Handler 会指示 MMU 建立相应的虚拟页和物理页的映射关系。
684 |
685 | 发生上面这两种缺页错误的时候,应用程序访问的是有效的物理内存,只是出现了物理页缺失或者虚拟页和物理页的映射关系未建立的问题。如果应用程序访问的是无效的物理内存的话,还会出现 **无效缺页错误(Invalid Page Fault)** 。
686 |
687 | #### 常见的页面置换算法有哪些?
688 |
689 | 当发生硬性页缺失时,如果物理内存中没有空闲的物理页面可用的话。操作系统就必须将物理内存中的一个物理页淘汰出去,这样就可以腾出空间来加载新的页面了。
690 |
691 | 用来选择淘汰哪一个物理页的规则叫做 **页面置换算法** ,我们可以把页面置换算法看成是淘汰物物理页的规则。
692 |
693 | 页缺失太频繁的发生会非常影响性能,一个好的页面置换算法应该是可以减少页缺失出现的次数。
694 |
695 | 常见的页面置换算法有下面这 5 种(其他还有很多页面置换算法都是基于这些算法改进得来的):
696 |
697 | 
698 |
699 | 1. **最佳页面置换算法(OPT,Optimal)**:优先选择淘汰的页面是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证获得最低的缺页率。但由于人们目前无法预知进程在内存下的若干页面中哪个是未来最长时间内不再被访问的,因而该算法无法实现,只是理论最优的页面置换算法,可以作为衡量其他置换算法优劣的标准。
700 | 2. **先进先出页面置换算法(FIFO,First In First Out)** : 最简单的一种页面置换算法,总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面进行淘汰。该算法易于实现和理解,一般只需要通过一个 FIFO 队列即可满足需求。不过,它的性能并不是很好。
701 | 3. **最近最久未使用页面置换算法(LRU ,Least Recently Used)**:LRU 算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 T,当须淘汰一个页面时,选择现有页面中其 T 值最大的,即最近最久未使用的页面予以淘汰。LRU 算法是根据各页之前的访问情况来实现,因此是易于实现的。OPT 算法是根据各页未来的访问情况来实现,因此是不可实现的。
702 | 4. **最少使用页面置换算法(LFU,Least Frequently Used)** : 和 LRU 算法比较像,不过该置换算法选择的是之前一段时间内使用最少的页面作为淘汰页。
703 | 5. **时钟页面置换算法(Clock)**:可以认为是一种最近未使用算法,即逐出的页面都是最近没有使用的那个。
704 |
705 | **FIFO 页面置换算法性能为何不好?**
706 |
707 | 主要原因主要有二:
708 |
709 | 1. **经常访问或者需要长期存在的页面会被频繁调入调出**:较早调入的页往往是经常被访问或者需要长期存在的页,这些页会被反复调入和调出。
710 | 2. **存在 Belady 现象**:被置换的页面并不是进程不会访问的,有时就会出现分配的页面数增多但缺页率反而提高的异常现象。出现该异常的原因是因为 FIFO 算法只考虑了页面进入内存的顺序,而没有考虑页面访问的频率和紧迫性。
711 |
712 | **哪一种页面置换算法实际用的比较多?**
713 |
714 | LRU 算法是实际使用中应用的比较多,也被认为是最接近 OPT 的页面置换算法。
715 |
716 | 不过,需要注意的是,实际应用中这些算法会被做一些改进,就比如 InnoDB Buffer Pool( InnoDB 缓冲池,MySQL 数据库中用于管理缓存页面的机制)就改进了传统的 LRU 算法,使用了一种称为"Adaptive LRU"的算法(同时结合了 LRU 和 LFU 算法的思想)。
717 |
718 | ### 分页机制和分段机制有哪些共同点和区别?
719 |
720 | **共同点**:
721 |
722 | - 都是非连续内存管理的方式。
723 | - 都采用了地址映射的方法,将虚拟地址映射到物理地址,以实现对内存的管理和保护。
724 |
725 | **区别**:
726 |
727 | - 分页机制以页面为单位进行内存管理,而分段机制以段为单位进行内存管理。页的大小是固定的,由操作系统决定,通常为 2 的幂次方。而段的大小不固定,取决于我们当前运行的程序。
728 | - 页是物理单位,即操作系统将物理内存划分成固定大小的页面,每个页面的大小通常是 2 的幂次方,例如 4KB、8KB 等等。而段则是逻辑单位,是为了满足程序对内存空间的逻辑需求而设计的,通常根据程序中数据和代码的逻辑结构来划分。
729 | - 分段机制容易出现外部内存碎片,即在段与段之间留下碎片空间(不足以映射给虚拟地址空间中的段)。分页机制解决了外部内存碎片的问题,但仍然可能会出现内部内存碎片。
730 | - 分页机制采用了页表来完成虚拟地址到物理地址的映射,页表通过一级页表和二级页表来实现多级映射;而分段机制则采用了段表来完成虚拟地址到物理地址的映射,每个段表项中记录了该段的起始地址和长度信息。
731 | - 分页机制对程序没有任何要求,程序只需要按照虚拟地址进行访问即可;而分段机制需要程序员将程序分为多个段,并且显式地使用段寄存器来访问不同的段。
732 |
733 | ### 段页机制
734 |
735 | 结合了段式管理和页式管理的一种内存管理机制。程序视角中,内存被划分为多个逻辑段,每个逻辑段进一步被划分为固定大小的页。
736 |
737 | 在段页式机制下,地址翻译的过程分为两个步骤:
738 |
739 | 1. **段式地址映射(虚拟地址 → 线性地址):**
740 | - 虚拟地址 = 段选择符(段号)+ 段内偏移。
741 | - 根据段号查段表,找到段基址,加上段内偏移得到线性地址。
742 | 2. **页式地址映射(线性地址 → 物理地址):**
743 | - 线性地址 = 页号 + 页内偏移。
744 | - 根据页号查页表,找到物理页框号,加上页内偏移得到物理地址。
745 |
746 | ### ⭐️局部性原理
747 |
748 | 要想更好地理解虚拟内存技术,必须要知道计算机中著名的 **局部性原理(Locality Principle)**。另外,局部性原理既适用于程序结构,也适用于数据结构,是非常重要的一个概念。
749 |
750 | 局部性原理是指在程序执行过程中,数据和指令的访问存在一定的空间和时间上的局部性特点。其中,时间局部性是指一个数据项或指令在一段时间内被反复使用的特点,空间局部性是指一个数据项或指令在一段时间内与其相邻的数据项或指令被反复使用的特点。
751 |
752 | 在分页机制中,页表的作用是将虚拟地址转换为物理地址,从而完成内存访问。在这个过程中,局部性原理的作用体现在两个方面:
753 |
754 | - **时间局部性**:由于程序中存在一定的循环或者重复操作,因此会反复访问同一个页或一些特定的页,这就体现了时间局部性的特点。为了利用时间局部性,分页机制中通常采用缓存机制来提高页面的命中率,即将最近访问过的一些页放入缓存中,如果下一次访问的页已经在缓存中,就不需要再次访问内存,而是直接从缓存中读取。
755 | - **空间局部性**:由于程序中数据和指令的访问通常是具有一定的空间连续性的,因此当访问某个页时,往往会顺带访问其相邻的一些页。为了利用空间局部性,分页机制中通常采用预取技术来预先将相邻的一些页读入内存缓存中,以便在未来访问时能够直接使用,从而提高访问速度。
756 |
757 | 总之,局部性原理是计算机体系结构设计的重要原则之一,也是许多优化算法的基础。在分页机制中,利用时间局部性和空间局部性,采用缓存和预取技术,可以提高页面的命中率,从而提高内存访问效率
758 |
759 | ## 文件系统
760 |
761 | ### 文件系统主要做了什么?
762 |
763 | 文件系统主要负责管理和组织计算机存储设备上的文件和目录,其功能包括以下几个方面:
764 |
765 | 1. **存储管理**:将文件数据存储到物理存储介质中,并且管理空间分配,以确保每个文件都有足够的空间存储,并避免文件之间发生冲突。
766 | 2. **文件管理**:文件的创建、删除、移动、重命名、压缩、加密、共享等等。
767 | 3. **目录管理**:目录的创建、删除、移动、重命名等等。
768 | 4. **文件访问控制**:管理不同用户或进程对文件的访问权限,以确保用户只能访问其被授权访问的文件,以保证文件的安全性和保密性。
769 |
770 | ### ⭐️硬链接和软链接有什么区别?
771 |
772 | 在 Linux/类 Unix 系统上,文件链接(File Link)是一种特殊的文件类型,可以在文件系统中指向另一个文件。常见的文件链接类型有两种:
773 |
774 | **1、硬链接(Hard Link)**
775 |
776 | - 在 Linux/类 Unix 文件系统中,每个文件和目录都有一个唯一的索引节点(inode)号,用来标识该文件或目录。硬链接通过 inode 节点号建立连接,硬链接和源文件的 inode 节点号相同,两者对文件系统来说是完全平等的(可以看作是互为硬链接,源头是同一份文件),删除其中任何一个对另外一个没有影响,可以通过给文件设置硬链接文件来防止重要文件被误删。
777 | - 只有删除了源文件和所有对应的硬链接文件,该文件才会被真正删除。
778 | - 硬链接具有一些限制,不能对目录以及不存在的文件创建硬链接,并且,硬链接也不能跨越文件系统。
779 | - `ln` 命令用于创建硬链接。
780 |
781 | **2、软链接(Symbolic Link 或 Symlink)**
782 |
783 | - 软链接和源文件的 inode 节点号不同,而是指向一个文件路径。
784 | - 源文件删除后,软链接依然存在,但是指向的是一个无效的文件路径。
785 | - 软连接类似于 Windows 系统中的快捷方式。
786 | - 不同于硬链接,可以对目录或者不存在的文件创建软链接,并且,软链接可以跨越文件系统。
787 | - `ln -s` 命令用于创建软链接。
788 |
789 | ### 硬链接为什么不能跨文件系统?
790 |
791 | 我们之前提到过,硬链接是通过 inode 节点号建立连接的,而硬链接和源文件共享相同的 inode 节点号。
792 |
793 | 然而,每个文件系统都有自己的独立 inode 表,且每个 inode 表只维护该文件系统内的 inode。如果在不同的文件系统之间创建硬链接,可能会导致 inode 节点号冲突的问题,即目标文件的 inode 节点号已经在该文件系统中被使用。
794 |
795 | ### 提高文件系统性能的方式有哪些?
796 |
797 | - **优化硬件**:使用高速硬件设备(如 SSD、NVMe)替代传统的机械硬盘,使用 RAID(Redundant Array of Inexpensive Disks)等技术提高磁盘性能。
798 | - **选择合适的文件系统选型**:不同的文件系统具有不同的特性,对于不同的应用场景选择合适的文件系统可以提高系统性能。
799 | - **运用缓存**:访问磁盘的效率比较低,可以运用缓存来减少磁盘的访问次数。不过,需要注意缓存命中率,缓存命中率过低的话,效果太差。
800 | - **避免磁盘过度使用**:注意磁盘的使用率,避免将磁盘用满,尽量留一些剩余空间,以免对文件系统的性能产生负面影响。
801 | - **对磁盘进行合理的分区**:合理的磁盘分区方案,能够使文件系统在不同的区域存储文件,从而减少文件碎片,提高文件读写性能。
802 |
803 | ### ⭐️常见的磁盘调度算法有哪些?
804 |
805 | 磁盘调度算法是操作系统中对磁盘访问请求进行排序和调度的算法,其目的是提高磁盘的访问效率。
806 |
807 | 一次磁盘读写操作的时间由磁盘寻道/寻找时间、延迟时间和传输时间决定。磁盘调度算法可以通过改变到达磁盘请求的处理顺序,减少磁盘寻道时间和延迟时间。
808 |
809 | 常见的磁盘调度算法有下面这 6 种(其他还有很多磁盘调度算法都是基于这些算法改进得来的):
810 |
811 | 
812 |
813 | 1. **先来先服务算法(First-Come First-Served,FCFS)**:按照请求到达磁盘调度器的顺序进行处理,先到达的请求的先被服务。FCFS 算法实现起来比较简单,不存在算法开销。不过,由于没有考虑磁头移动的路径和方向,平均寻道时间较长。同时,该算法容易出现饥饿问题,即一些后到的磁盘请求可能需要等待很长时间才能得到服务。
814 | 2. **最短寻道时间优先算法(Shortest Seek Time First,SSTF)**:也被称为最佳服务优先(Shortest Service Time First,SSTF)算法,优先选择距离当前磁头位置最近的请求进行服务。SSTF 算法能够最小化磁头的寻道时间,但容易出现饥饿问题,即磁头附近的请求不断被服务,远离磁头的请求长时间得不到响应。实际应用中,需要优化一下该算法的实现,避免出现饥饿问题。
815 | 3. **扫描算法(SCAN)**:也被称为电梯(Elevator)算法,基本思想和电梯非常类似。磁头沿着一个方向扫描磁盘,如果经过的磁道有请求就处理,直到到达磁盘的边界,然后改变移动方向,依此往复。SCAN 算法能够保证所有的请求得到服务,解决了饥饿问题。但是,如果磁头从一个方向刚扫描完,请求才到的话。这个请求就需要等到磁头从相反方向过来之后才能得到处理。
816 | 4. **循环扫描算法(Circular Scan,C-SCAN)**:SCAN 算法的变体,只在磁盘的一侧进行扫描,并且只按照一个方向扫描,直到到达磁盘边界,然后回到磁盘起点,重新开始循环。
817 | 5. **边扫描边观察算法(LOOK)**:SCAN 算法中磁头到了磁盘的边界才改变移动方向,这样可能会做很多无用功,因为磁头移动方向上可能已经没有请求需要处理了。LOOK 算法对 SCAN 算法进行了改进,如果磁头移动方向上已经没有别的请求,就可以立即改变磁头移动方向,依此往复。也就是边扫描边观察指定方向上还有无请求,因此叫 LOOK。
818 | 6. **均衡循环扫描算法(C-LOOK)**:C-SCAN 只有到达磁盘边界时才能改变磁头移动方向,并且磁头返回时也需要返回到磁盘起点,这样可能会做很多无用功。C-LOOK 算法对 C-SCAN 算法进行了改进,如果磁头移动的方向上已经没有磁道访问请求了,就可以立即让磁头返回,并且磁头只需要返回到有磁道访问请求的位置即可。
819 |
820 |
821 |
--------------------------------------------------------------------------------
/docs/cs-basics/network.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 计算机网络常见面试题总结
3 | category: 计算机基础
4 | tag:
5 | - 计算机网络
6 | head:
7 | - - meta
8 | - name: keywords
9 | content: 计算机网络面试题,OSI七层模型,TCP/IP四层模型,HTTP vs HTTPS,HTTP/1.1 vs HTTP/2,HTTP/3 QUIC,GET vs POST,WebSocket vs HTTP,DNS解析过程,应用层面试,网络基础高频题,URL到页面展示,队头阻塞,Session vs Cookie
10 | - - meta
11 | - name: description
12 | content: 最新计算机网络高频面试题总结:OSI/TCP/IP模型详解、HTTP全版本对比、GET/POST区别、WebSocket实时通信、DNS解析流程,附图表+⭐️重点标注,一文搞定网络基础&应用层核心考点,快速备战后端面试!
13 | ---
14 |
15 | ------
16 |
17 | 
18 |
19 | ------
20 |
21 | ## 前言
22 |
23 | 由于很多读者都有突击面试的需求,所以我在几年前就弄了 **JavaGuide 面试突击版本**(JavaGuide 内容精简版,只保留重点),并持续完善跟进。对于喜欢纸质阅读的朋友来说,也可以打印出来,整体阅读体验非常高!
24 |
25 | 除了只保留最常问的面试题之外,我还进一步对重点中的重点进行了⭐️标注。并且,有亮色(白天)和暗色(夜间)两个主题选择,需要打印出来的朋友记得选择亮色版本。
26 |
27 | 对于时间比较充裕的朋友,我个人还是更推荐 [JavaGuide](https://javaguide.cn/) 网站系统学习,内容更全面,更深入。
28 |
29 | JavaGuide 已经持续维护 6 年多了,累计提交了接近 **6000** commit ,共有 **570+** 多位贡献者共同参与维护和完善。用心做原创优质内容,如果觉得有帮助的话,欢迎点赞分享!传送门:[GitHub](https://github.com/Snailclimb/JavaGuide) | [Gitee](https://gitee.com/SnailClimb/JavaGuide)。
30 |
31 | 对于需要更进一步面试辅导服务的读者,欢迎加入 **[JavaGuide 官方知识星球](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)**(技术专栏/一对一提问/简历修改/求职指南/面试打卡),绝对物超所值!
32 |
33 | 面试突击最新版本可以在我的公众号回复“**PDF**”获取([JavaGuide 官方知识星球](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)会提前同步最新版,针对球友的一个小福利)。
34 |
35 | 
36 |
37 | 这部分内容摘自 [JavaGuide](https://javaguide.cn/) 下面几篇文章中的重点:
38 |
39 | - [计算机网络常见面试题总结(上)](https://javaguide.cn/cs-basics/network/other-network-questions.html)(网络分层模型、常见网路协议总结、HTTP、WebSocket、DNS 等)
40 | - [计算机网络常见面试题总结(下)](https://javaguide.cn/cs-basics/network/other-network-questions2.html)(TCP 和 UDP、IP、ARP 等)
41 |
42 | ## 计算机网络基础
43 |
44 | ### ⭐️TCP/IP 四层模型是什么?每一层的作用是什么?
45 |
46 | **TCP/IP 四层模型** 是目前被广泛采用的一种模型,我们可以将 TCP / IP 模型看作是 OSI 七层模型的精简版本,由以下 4 层组成:
47 |
48 | 1. 应用层
49 | 2. 传输层
50 | 3. 网络层
51 | 4. 网络接口层
52 |
53 | 需要注意的是,我们并不能将 TCP/IP 四层模型 和 OSI 七层模型完全精确地匹配起来,不过可以简单将两者对应起来,如下图所示:
54 |
55 | 
56 |
57 | 关于每一层作用的详细介绍,请看 [OSI 和 TCP/IP 网络分层模型详解(基础)](https://javaguide.cn/cs-basics/network/osi-and-tcp-ip-model.html) 这篇文章。
58 |
59 | ### 为什么网络要分层?
60 |
61 | 说到分层,我们先从我们平时使用框架开发一个后台程序来说,我们往往会按照每一层做不同的事情的原则将系统分为三层(复杂的系统分层会更多):
62 |
63 | 1. Repository(数据库操作)
64 | 2. Service(业务操作)
65 | 3. Controller(前后端数据交互)
66 |
67 | **复杂的系统需要分层,因为每一层都需要专注于一类事情。网络分层的原因也是一样,每一层只专注于做一类事情。**
68 |
69 | 好了,再来说回:“为什么网络要分层?”。我觉得主要有 3 方面的原因:
70 |
71 | 1. **各层之间相互独立**:各层之间相互独立,各层之间不需要关心其他层是如何实现的,只需要知道自己如何调用下层提供好的功能就可以了(可以简单理解为接口调用)**。这个和我们对开发时系统进行分层是一个道理。**
72 | 2. **提高了灵活性和可替换性**:每一层都可以使用最适合的技术来实现,你只需要保证你提供的功能以及暴露的接口的规则没有改变就行了。并且,每一层都可以根据需要进行修改或替换,而不会影响到整个网络的结构。**这个和我们平时开发系统的时候要求的高内聚、低耦合的原则也是可以对应上的。**
73 | 3. **大问题化小**:分层可以将复杂的网络问题分解为许多比较小的、界线比较清晰简单的小问题来处理和解决。这样使得复杂的计算机网络系统变得易于设计,实现和标准化。 **这个和我们平时开发的时候,一般会将系统功能分解,然后将复杂的问题分解为容易理解的更小的问题是相对应的,这些较小的问题具有更好的边界(目标和接口)定义。**
74 |
75 | 我想到了计算机世界非常非常有名的一句话,这里分享一下:
76 |
77 | > 计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决,计算机整个体系从上到下都是按照严格的层次结构设计的。
78 |
79 | ### 应用层有哪些常见的协议?
80 |
81 | 
82 |
83 | - **HTTP(Hypertext Transfer Protocol,超文本传输协议)**:基于 TCP 协议,是一种用于传输超文本和多媒体内容的协议,主要是为 Web 浏览器与 Web 服务器之间的通信而设计的。当我们使用浏览器浏览网页的时候,我们网页就是通过 HTTP 请求进行加载的。
84 | - **SMTP(Simple Mail Transfer Protocol,简单邮件发送协议)**:基于 TCP 协议,是一种用于发送电子邮件的协议。注意 ⚠️:SMTP 协议只负责邮件的发送,而不是接收。要从邮件服务器接收邮件,需要使用 POP3 或 IMAP 协议。
85 | - **POP3/IMAP(邮件接收协议)**:基于 TCP 协议,两者都是负责邮件接收的协议。IMAP 协议是比 POP3 更新的协议,它在功能和性能上都更加强大。IMAP 支持邮件搜索、标记、分类、归档等高级功能,而且可以在多个设备之间同步邮件状态。几乎所有现代电子邮件客户端和服务器都支持 IMAP。
86 | - **FTP(File Transfer Protocol,文件传输协议)** : 基于 TCP 协议,是一种用于在计算机之间传输文件的协议,可以屏蔽操作系统和文件存储方式。注意 ⚠️:FTP 是一种不安全的协议,因为它在传输过程中不会对数据进行加密。建议在传输敏感数据时使用更安全的协议,如 SFTP。
87 | - **Telnet(远程登陆协议)**:基于 TCP 协议,用于通过一个终端登陆到其他服务器。Telnet 协议的最大缺点之一是所有数据(包括用户名和密码)均以明文形式发送,这有潜在的安全风险。这就是为什么如今很少使用 Telnet,而是使用一种称为 SSH 的非常安全的网络传输协议的主要原因。
88 | - **SSH(Secure Shell Protocol,安全的网络传输协议)**:基于 TCP 协议,通过加密和认证机制实现安全的访问和文件传输等业务
89 | - **RTP(Real-time Transport Protocol,实时传输协议)**:通常基于 UDP 协议,但也支持 TCP 协议。它提供了端到端的实时传输数据的功能,但不包含资源预留存、不保证实时传输质量,这些功能由 WebRTC 实现。
90 | - **DNS(Domain Name System,域名管理系统)**: 基于 UDP 协议,用于解决域名和 IP 地址的映射问题。
91 |
92 | 关于这些协议的详细介绍请看 [应用层常见协议总结(应用层)](https://javaguide.cn/cs-basics/network/application-layer-protocol.html) 这篇文章。
93 |
94 | ## HTTP
95 |
96 | ### ⭐️从输入 URL 到页面展示到底发生了什么?(非常重要)
97 |
98 | > 类似的问题:打开一个网页,整个过程会使用哪些协议?
99 |
100 | 先来看一张图(来源于《图解 HTTP》):
101 |
102 |
103 |
104 | 上图有一个错误需要注意:是 OSPF 不是 OPSF。 OSPF(Open Shortest Path First,ospf)开放最短路径优先协议, 是由 Internet 工程任务组开发的路由选择协议
105 |
106 | 总体来说分为以下几个步骤:
107 |
108 | 1. 在浏览器中输入指定网页的 URL。
109 | 2. 浏览器通过 DNS 协议,获取域名对应的 IP 地址。
110 | 3. 浏览器根据 IP 地址和端口号,向目标服务器发起一个 TCP 连接请求。
111 | 4. 浏览器在 TCP 连接上,向服务器发送一个 HTTP 请求报文,请求获取网页的内容。
112 | 5. 服务器收到 HTTP 请求报文后,处理请求,并返回 HTTP 响应报文给浏览器。
113 | 6. 浏览器收到 HTTP 响应报文后,解析响应体中的 HTML 代码,渲染网页的结构和样式,同时根据 HTML 中的其他资源的 URL(如图片、CSS、JS 等),再次发起 HTTP 请求,获取这些资源的内容,直到网页完全加载显示。
114 | 7. 浏览器在不需要和服务器通信时,可以主动关闭 TCP 连接,或者等待服务器的关闭请求。
115 |
116 | 详细介绍可以查看这篇文章:[访问网页的全过程(知识串联)](https://javaguide.cn/cs-basics/network/the-whole-process-of-accessing-web-pages.html)(强烈推荐)。
117 |
118 | ### ⭐️HTTP 状态码有哪些?
119 |
120 | HTTP 状态码用于描述 HTTP 请求的结果,比如 2xx 就代表请求被成功处理。
121 |
122 | 
123 |
124 | 关于 HTTP 状态码更详细的总结,可以看我写的这篇文章:[HTTP 常见状态码总结(应用层)](https://javaguide.cn/cs-basics/network/http-status-codes.html)。
125 |
126 | ### ⭐️HTTP 和 HTTPS 有什么区别?(重要)
127 |
128 | 
129 |
130 | - **端口号**:HTTP 默认是 80,HTTPS 默认是 443。
131 | - **URL 前缀**:HTTP 的 URL 前缀是 `http://`,HTTPS 的 URL 前缀是 `https://`。
132 | - **安全性和资源消耗**:HTTP 协议运行在 TCP 之上,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。HTTPS 是运行在 SSL/TLS 之上的 HTTP 协议,SSL/TLS 运行在 TCP 之上。所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密。所以说,HTTP 安全性没有 HTTPS 高,但是 HTTPS 比 HTTP 耗费更多服务器资源。
133 | - **SEO(搜索引擎优化)**:搜索引擎通常会更青睐使用 HTTPS 协议的网站,因为 HTTPS 能够提供更高的安全性和用户隐私保护。使用 HTTPS 协议的网站在搜索结果中可能会被优先显示,从而对 SEO 产生影响。
134 |
135 | 关于 HTTP 和 HTTPS 更详细的对比总结,可以看我写的这篇文章:[HTTP vs HTTPS(应用层)](https://javaguide.cn/cs-basics/network/http-vs-https.html) 。
136 |
137 | ### HTTP/1.0 和 HTTP/1.1 有什么区别?
138 |
139 | 
140 |
141 | - **连接方式** : HTTP/1.0 为短连接,HTTP/1.1 支持长连接。HTTP 协议的长连接和短连接,实质上是 TCP 协议的长连接和短连接。
142 | - **状态响应码** : HTTP/1.1 中新加入了大量的状态码,光是错误响应状态码就新增了 24 种。比如说,`100 (Continue)`——在请求大资源前的预热请求,`206 (Partial Content)`——范围请求的标识码,`409 (Conflict)`——请求与当前资源的规定冲突,`410 (Gone)`——资源已被永久转移,而且没有任何已知的转发地址。
143 | - **缓存机制** : 在 HTTP/1.0 中主要使用 Header 里的 If-Modified-Since,Expires 来做为缓存判断的标准,HTTP/1.1 则引入了更多的缓存控制策略例如 Entity tag,If-Unmodified-Since, If-Match, If-None-Match 等更多可供选择的缓存头来控制缓存策略。
144 | - **带宽**:HTTP/1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP/1.1 则在请求头引入了 range 头域,它允许只请求资源的某个部分,即返回码是 206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
145 | - **Host 头(Host Header)处理** :HTTP/1.1 引入了 Host 头字段,允许在同一 IP 地址上托管多个域名,从而支持虚拟主机的功能。而 HTTP/1.0 没有 Host 头字段,无法实现虚拟主机。
146 |
147 | 关于 HTTP/1.0 和 HTTP/1.1 更详细的对比总结,可以看我写的这篇文章:[HTTP/1.0 vs HTTP/1.1(应用层)](https://javaguide.cn/cs-basics/network/http1.0-vs-http1.1.html) 。
148 |
149 | ### ⭐️HTTP/1.1 和 HTTP/2.0 有什么区别?
150 |
151 | 
152 |
153 | - **多路复用(Multiplexing)**:HTTP/2.0 在同一连接上可以同时传输多个请求和响应(可以看作是 HTTP/1.1 中长链接的升级版本),互不干扰。HTTP/1.1 则使用串行方式,每个请求和响应都需要独立的连接,而浏览器为了控制资源会有 6-8 个 TCP 连接的限制。这使得 HTTP/2.0 在处理多个请求时更加高效,减少了网络延迟和提高了性能。
154 | - **二进制帧(Binary Frames)**:HTTP/2.0 使用二进制帧进行数据传输,而 HTTP/1.1 则使用文本格式的报文。二进制帧更加紧凑和高效,减少了传输的数据量和带宽消耗。
155 | - **队头阻塞**:HTTP/2 引入了多路复用技术,允许多个请求和响应在单个 TCP 连接上并行交错传输,解决了 HTTP/1.1 应用层的队头阻塞问题,但 HTTP/2 依然受到 TCP 层队头阻塞 的影响。
156 | - **头部压缩(Header Compression)**:HTTP/1.1 支持`Body`压缩,`Header`不支持压缩。HTTP/2.0 支持对`Header`压缩,使用了专门为`Header`压缩而设计的 HPACK 算法,减少了网络开销。
157 | - **服务器推送(Server Push)**:HTTP/2.0 支持服务器推送,可以在客户端请求一个资源时,将其他相关资源一并推送给客户端,从而减少了客户端的请求次数和延迟。而 HTTP/1.1 需要客户端自己发送请求来获取相关资源。
158 |
159 | HTTP/2.0 多路复用效果图(图源: [HTTP/2 For Web Developers](https://blog.cloudflare.com/http-2-for-web-developers/)):
160 |
161 | 
162 |
163 | 可以看到,HTTP/2 的多路复用机制允许多个请求和响应共享一个 TCP 连接,从而避免了 HTTP/1.1 在应对并发请求时需要建立多个并行连接的情况,减少了重复连接建立和维护的额外开销。而在 HTTP/1.1 中,尽管支持持久连接,但为了缓解队头阻塞问题,浏览器通常会为同一域名建立多个并行连接。
164 |
165 | ### HTTP/2.0 和 HTTP/3.0 有什么区别?
166 |
167 | 
168 |
169 | - **传输协议**:HTTP/2.0 是基于 TCP 协议实现的,HTTP/3.0 新增了 QUIC(Quick UDP Internet Connections) 协议来实现可靠的传输,提供与 TLS/SSL 相当的安全性,具有较低的连接和传输延迟。你可以将 QUIC 看作是 UDP 的升级版本,在其基础上新增了很多功能比如加密、重传等等。HTTP/3.0 之前名为 HTTP-over-QUIC,从这个名字中我们也可以发现,HTTP/3 最大的改造就是使用了 QUIC。
170 | - **连接建立**:HTTP/2.0 需要经过经典的 TCP 三次握手过程(由于安全的 HTTPS 连接建立还需要 TLS 握手,共需要大约 3 个 RTT)。由于 QUIC 协议的特性(TLS 1.3,TLS 1.3 除了支持 1 个 RTT 的握手,还支持 0 个 RTT 的握手)连接建立仅需 0-RTT 或者 1-RTT。这意味着 QUIC 在最佳情况下不需要任何的额外往返时间就可以建立新连接。
171 | - **头部压缩**:HTTP/2.0 使用 HPACK 算法进行头部压缩,而 HTTP/3.0 使用更高效的 QPACK 头压缩算法。
172 | - **队头阻塞**:HTTP/2.0 多请求复用一个 TCP 连接,一旦发生丢包,就会阻塞住所有的 HTTP 请求。由于 QUIC 协议的特性,HTTP/3.0 在一定程度上解决了队头阻塞(Head-of-Line blocking, 简写:HOL blocking)问题,一个连接建立多个不同的数据流,这些数据流之间独立互不影响,某个数据流发生丢包了,其数据流不受影响(本质上是多路复用+轮询)。
173 | - **连接迁移**:HTTP/3.0 支持连接迁移,因为 QUIC 使用 64 位 ID 标识连接,只要 ID 不变就不会中断,网络环境改变时(如从 Wi-Fi 切换到移动数据)也能保持连接。而 TCP 连接是由(源 IP,源端口,目的 IP,目的端口)组成,这个四元组中一旦有一项值发生改变,这个连接也就不能用了。
174 | - **错误恢复**:HTTP/3.0 具有更好的错误恢复机制,当出现丢包、延迟等网络问题时,可以更快地进行恢复和重传。而 HTTP/2.0 则需要依赖于 TCP 的错误恢复机制。
175 | - **安全性**:在 HTTP/2.0 中,TLS 用于加密和认证整个 HTTP 会话,包括所有的 HTTP 头部和数据负载。TLS 的工作是在 TCP 层之上,它加密的是在 TCP 连接中传输的应用层的数据,并不会对 TCP 头部以及 TLS 记录层头部进行加密,所以在传输的过程中 TCP 头部可能会被攻击者篡改来干扰通信。而 HTTP/3.0 的 QUIC 对整个数据包(包括报文头和报文体)进行了加密与认证处理,保障安全性。
176 |
177 | HTTP/1.0、HTTP/2.0 和 HTTP/3.0 的协议栈比较:
178 |
179 | 
180 |
181 | 下图是一个更详细的 HTTP/2.0 和 HTTP/3.0 对比图:
182 |
183 | 
184 |
185 | 从上图可以看出:
186 |
187 | - **HTTP/2.0**:使用 TCP 作为传输协议、使用 HPACK 进行头部压缩、依赖 TLS 进行加密。
188 | - **HTTP/3.0**:使用基于 UDP 的 QUIC 协议、使用更高效的 QPACK 进行头部压缩、在 QUIC 中直接集成了 TLS。QUIC 协议具备连接迁移、拥塞控制与避免、流量控制等特性。
189 |
190 | 关于 HTTP/1.0 -> HTTP/3.0 更详细的演进介绍,推荐阅读 [HTTP1 到 HTTP3 的工程优化](https://dbwu.tech/posts/http_evolution/)。
191 |
192 | ### HTTP/1.1 和 HTTP/2.0 的队头阻塞有什么不同?
193 |
194 | HTTP/1.1 队头阻塞的主要原因是无法多路复用:
195 |
196 | - 在一个 TCP 连接中,资源的请求和响应是按顺序处理的。如果一个大的资源(如一个大文件)正在传输,后续的小资源(如较小的 CSS 文件)需要等待前面的资源传输完成后才能被发送。
197 | - 如果浏览器需要同时加载多个资源(如多个 CSS、JS 文件等),它通常会开启多个并行的 TCP 连接(一般限制为 6 个)。但每个连接仍然受限于顺序的请求-响应机制,因此仍然会发生 **应用层的队头阻塞**。
198 |
199 | 虽然 HTTP/2.0 引入了多路复用技术,允许多个请求和响应在单个 TCP 连接上并行交错传输,解决了 **HTTP/1.1 应用层的队头阻塞问题**,但 HTTP/2.0 依然受到 **TCP 层队头阻塞** 的影响:
200 |
201 | - HTTP/2.0 通过帧(frame)机制将每个资源分割成小块,并为每个资源分配唯一的流 ID,这样多个资源的数据可以在同一 TCP 连接中交错传输。
202 | - TCP 作为传输层协议,要求数据按顺序交付。如果某个数据包在传输过程中丢失,即使后续的数据包已经到达,也必须等待丢失的数据包重传后才能继续处理。这种传输层的顺序性导致了 **TCP 层的队头阻塞**。
203 | - 举例来说,如果 HTTP/2 的一个 TCP 数据包中携带了多个资源的数据(例如 JS 和 CSS),而该数据包丢失了,那么后续数据包中的所有资源数据都需要等待丢失的数据包重传回来,导致所有流(streams)都被阻塞。
204 |
205 | 最后,来一张表格总结补充一下:
206 |
207 | | **方面** | **HTTP/1.1 的队头阻塞** | **HTTP/2.0 的队头阻塞** |
208 | | -------------- | ---------------------------------------- | ------------------------------------------------------------ |
209 | | **层级** | 应用层(HTTP 协议本身的限制) | 传输层(TCP 协议的限制) |
210 | | **根本原因** | 无法多路复用,请求和响应必须按顺序传输 | TCP 要求数据包按顺序交付,丢包时阻塞整个连接 |
211 | | **受影响范围** | 单个 HTTP 请求/响应会阻塞后续请求/响应。 | 单个 TCP 包丢失会影响所有 HTTP/2.0 流(依赖于同一个底层 TCP 连接) |
212 | | **缓解方法** | 开启多个并行的 TCP 连接 | 减少网络掉包或者使用基于 UDP 的 QUIC 协议 |
213 | | **影响场景** | 每次都会发生,尤其是大文件阻塞小文件时。 | 丢包率较高的网络环境下更容易发生。 |
214 |
215 | ### ⭐️HTTP 是不保存状态的协议, 如何保存用户状态?
216 |
217 | HTTP 协议本身是 **无状态的 (stateless)** 。这意味着服务器默认情况下无法区分两个连续的请求是否来自同一个用户,或者同一个用户之前的操作是什么。这就像一个“健忘”的服务员,每次你跟他说话,他都不知道你是谁,也不知道你之前点过什么菜。
218 |
219 | 但在实际的 Web 应用中,比如网上购物、用户登录等场景,我们显然需要记住用户的状态(例如购物车里的商品、用户的登录信息)。为了解决这个问题,主要有以下几种常用机制:
220 |
221 | **方案一:Session (会话) 配合 Cookie (主流方式):**
222 |
223 | 
224 |
225 | 这可以说是最经典也是最常用的方法了。基本流程是这样的:
226 |
227 | 1. 用户向服务器发送用户名、密码、验证码用于登陆系统。
228 | 2. 服务器验证通过后,会为这个用户创建一个专属的 Session 对象(可以理解为服务器上的一块内存,存放该用户的状态数据,如购物车、登录信息等)存储起来,并给这个 Session 分配一个唯一的 `SessionID`。
229 | 3. 服务器通过 HTTP 响应头中的 `Set-Cookie` 指令,把这个 `SessionID` 发送给用户的浏览器。
230 | 4. 浏览器接收到 `SessionID` 后,会将其以 Cookie 的形式保存在本地。当用户保持登录状态时,每次向该服务器发请求,浏览器都会自动带上这个存有 `SessionID` 的 Cookie。
231 | 5. 服务器收到请求后,从 Cookie 中拿出 `SessionID`,就能找到之前保存的那个 Session 对象,从而知道这是哪个用户以及他之前的状态了。
232 |
233 | 使用 Session 的时候需要注意下面几个点:
234 |
235 | - **客户端 Cookie 支持**:依赖 Session 的核心功能要确保用户浏览器开启了 Cookie。
236 | - **Session 过期管理**:合理设置 Session 的过期时间,平衡安全性和用户体验。
237 | - **Session ID 安全**:为包含 `SessionID` 的 Cookie 设置 `HttpOnly` 标志可以防止客户端脚本(如 JavaScript)窃取,设置 Secure 标志可以保证 `SessionID` 只在 HTTPS 连接下传输,增加安全性。
238 |
239 | Session 数据本身存储在服务器端。常见的存储方式有:
240 |
241 | - **服务器内存**:实现简单,访问速度快,但服务器重启数据会丢失,且不利于多服务器间的负载均衡。这种方式适合简单且用户量不大的业务场景。
242 | - **数据库 (如 MySQL, PostgreSQL)**:数据持久化,但读写性能相对较低,一般不会使用这种方式。
243 | - **分布式缓存 (如 Redis)**:性能高,支持分布式部署,是目前大规模应用中非常主流的方案。
244 |
245 | **方案二:当 Cookie 被禁用时:URL 重写 (URL Rewriting)**
246 |
247 | 如果用户的浏览器禁用了 Cookie,或者某些情况下不便使用 Cookie,还有一种备选方案是 URL 重写。这种方式会将 `SessionID` 直接附加到 URL 的末尾,作为参数传递。例如:。服务器端会解析 URL 中的 `sessionid` 参数来获取 `SessionID`,进而找到对应的 Session 数据。
248 |
249 | 这种方法一般不会使用,存在以下缺点:
250 |
251 | - URL 会变长且不美观;
252 | - `SessionID` 暴露在 URL 中,安全性较低(容易被复制、分享或记录在日志中);
253 | - 对搜索引擎优化 (SEO) 可能不友好。
254 |
255 | **方案三:Token-based 认证 (如 JWT - JSON Web Tokens)**
256 |
257 | 这是一种越来越流行的无状态认证方式,尤其适用于前后端分离的架构和微服务。
258 |
259 | 
260 |
261 | 以 JWT 为例(普通 Token 方案也可以),简化后的步骤如下
262 |
263 | 1. 用户向服务器发送用户名、密码以及验证码用于登陆系统;
264 | 2. 如果用户用户名、密码以及验证码校验正确的话,服务端会返回已经签名的 Token,也就是 JWT;
265 | 3. 客户端收到 Token 后自己保存起来(比如浏览器的 `localStorage` );
266 | 4. 用户以后每次向后端发请求都在 Header 中带上这个 JWT ;
267 | 5. 服务端检查 JWT 并从中获取用户相关信息。
268 |
269 | JWT 详细介绍可以查看这两篇文章:
270 |
271 | - [JWT 基础概念详解](https://javaguide.cn/system-design/security/jwt-intro.html)
272 | - [JWT 身份认证优缺点分析](https://javaguide.cn/system-design/security/advantages-and-disadvantages-of-jwt.html)
273 |
274 | 总结来说,虽然 HTTP 本身是无状态的,但通过 Cookie + Session、URL 重写或 Token 等机制,我们能够有效地在 Web 应用中跟踪和管理用户状态。其中,**Cookie + Session 是最传统也最广泛使用的方式,而 Token-based 认证则在现代 Web 应用中越来越受欢迎。**
275 |
276 | ### URI 和 URL 的区别是什么?
277 |
278 | - URI(Uniform Resource Identifier) 是统一资源标志符,可以唯一标识一个资源。
279 | - URL(Uniform Resource Locator) 是统一资源定位符,可以提供该资源的路径。它是一种具体的 URI,即 URL 可以用来标识一个资源,而且还指明了如何 locate 这个资源。
280 |
281 | URI 的作用像身份证号一样,URL 的作用更像家庭住址一样。URL 是一种具体的 URI,它不仅唯一标识资源,而且还提供了定位该资源的信息。
282 |
283 | ### Cookie 和 Session 有什么区别?
284 |
285 | 准确点来说,这个问题属于认证授权的范畴,你可以在 [认证授权基础概念详解](https://javaguide.cn/system-design/security/basis-of-authority-certification.html) 这篇文章中找到详细的答案。
286 |
287 | ### ⭐️GET 和 POST 的区别
288 |
289 | 这个问题在知乎上被讨论的挺火热的,地址: 。
290 |
291 | 
292 |
293 | GET 和 POST 是 HTTP 协议中两种常用的请求方法,它们在不同的场景和目的下有不同的特点和用法。一般来说,可以从以下几个方面来区分二者(重点搞清两者在语义上的区别即可):
294 |
295 | - 语义(主要区别):GET 通常用于获取或查询资源,而 POST 通常用于创建或修改资源。
296 | - 幂等:GET 请求是幂等的,即多次重复执行不会改变资源的状态,而 POST 请求是不幂等的,即每次执行可能会产生不同的结果或影响资源的状态。
297 | - 格式:GET 请求的参数通常放在 URL 中,形成查询字符串(querystring),而 POST 请求的参数通常放在请求体(body)中,可以有多种编码格式,如 application/x-www-form-urlencoded、multipart/form-data、application/json 等。GET 请求的 URL 长度受到浏览器和服务器的限制,而 POST 请求的 body 大小则没有明确的限制。不过,实际上 GET 请求也可以用 body 传输数据,只是并不推荐这样做,因为这样可能会导致一些兼容性或者语义上的问题。
298 | - 缓存:由于 GET 请求是幂等的,它可以被浏览器或其他中间节点(如代理、网关)缓存起来,以提高性能和效率。而 POST 请求则不适合被缓存,因为它可能有副作用,每次执行可能需要实时的响应。
299 | - 安全性:GET 请求和 POST 请求如果使用 HTTP 协议的话,那都不安全,因为 HTTP 协议本身是明文传输的,必须使用 HTTPS 协议来加密传输数据。另外,GET 请求相比 POST 请求更容易泄露敏感数据,因为 GET 请求的参数通常放在 URL 中。
300 |
301 | 再次提示,重点搞清两者在语义上的区别即可,实际使用过程中,也是通过语义来区分使用 GET 还是 POST。不过,也有一些项目所有的请求都用 POST,这个并不是固定的,项目组达成共识即可。
302 |
303 | ## WebSocket
304 |
305 | ### 什么是 WebSocket?
306 |
307 | WebSocket 是一种基于 TCP 连接的全双工通信协议,即客户端和服务器可以同时发送和接收数据。
308 |
309 | WebSocket 协议在 2008 年诞生,2011 年成为国际标准,几乎所有主流较新版本的浏览器都支持该协议。不过,WebSocket 不只能在基于浏览器的应用程序中使用,很多编程语言、框架和服务器都提供了 WebSocket 支持。
310 |
311 | WebSocket 协议本质上是应用层的协议,用于弥补 HTTP 协议在持久通信能力上的不足。客户端和服务器仅需一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
312 |
313 | 
314 |
315 | 下面是 WebSocket 的常见应用场景:
316 |
317 | - 视频弹幕
318 | - 实时消息推送,详见[Web 实时消息推送详解](https://javaguide.cn/system-design/web-real-time-message-push.html)这篇文章
319 | - 实时游戏对战
320 | - 多用户协同编辑
321 | - 社交聊天
322 | - ……
323 |
324 | ### ⭐️WebSocket 和 HTTP 有什么区别?
325 |
326 | WebSocket 和 HTTP 两者都是基于 TCP 的应用层协议,都可以在网络中传输数据。
327 |
328 | 下面是二者的主要区别:
329 |
330 | - WebSocket 是一种双向实时通信协议,而 HTTP 是一种单向通信协议。并且,HTTP 协议下的通信只能由客户端发起,服务器无法主动通知客户端。
331 | - WebSocket 使用 ws:// 或 wss://(使用 SSL/TLS 加密后的协议,类似于 HTTP 和 HTTPS 的关系) 作为协议前缀,HTTP 使用 http:// 或 https:// 作为协议前缀。
332 | - WebSocket 可以支持扩展,用户可以扩展协议,实现部分自定义的子协议,如支持压缩、加密等。
333 | - WebSocket 通信数据格式比较轻量,用于协议控制的数据包头部相对较小,网络开销小,而 HTTP 通信每次都要携带完整的头部,网络开销较大(HTTP/2.0 使用二进制帧进行数据传输,还支持头部压缩,减少了网络开销)。
334 |
335 | ### WebSocket 的工作过程是什么样的?
336 |
337 | WebSocket 的工作过程可以分为以下几个步骤:
338 |
339 | 1. 客户端向服务器发送一个 HTTP 请求,请求头中包含 `Upgrade: websocket` 和 `Sec-WebSocket-Key` 等字段,表示要求升级协议为 WebSocket;
340 | 2. 服务器收到这个请求后,会进行升级协议的操作,如果支持 WebSocket,它将回复一个 HTTP 101 状态码,响应头中包含 ,`Connection: Upgrade`和 `Sec-WebSocket-Accept: xxx` 等字段、表示成功升级到 WebSocket 协议。
341 | 3. 客户端和服务器之间建立了一个 WebSocket 连接,可以进行双向的数据传输。数据以帧(frames)的形式进行传送,WebSocket 的每条消息可能会被切分成多个数据帧(最小单位)。发送端会将消息切割成多个帧发送给接收端,接收端接收消息帧,并将关联的帧重新组装成完整的消息。
342 | 4. 客户端或服务器可以主动发送一个关闭帧,表示要断开连接。另一方收到后,也会回复一个关闭帧,然后双方关闭 TCP 连接。
343 |
344 | 另外,建立 WebSocket 连接之后,通过心跳机制来保持 WebSocket 连接的稳定性和活跃性。
345 |
346 | ### ⭐️WebSocket 与短轮询、长轮询的区别
347 |
348 | 这三种方式,都是为了解决“**客户端如何及时获取服务器最新数据,实现实时更新**”的问题。它们的实现方式和效率、实时性差异较大。
349 |
350 | **1.短轮询(Short Polling)**
351 |
352 | - **原理**:客户端每隔固定时间(如 5 秒)发起一次 HTTP 请求,询问服务器是否有新数据。服务器收到请求后立即响应。
353 | - **优点**:实现简单,兼容性好,直接用常规 HTTP 请求即可。
354 | - **缺点**:
355 | - **实时性一般**:消息可能在两次轮询间到达,用户需等到下次请求才知晓。
356 | - **资源浪费大**:反复建立/关闭连接,且大多数请求收到的都是“无新消息”,极大增加服务器和网络压力。
357 |
358 | **2.长轮询(Long Polling)**
359 |
360 | - **原理**:客户端发起请求后,若服务器暂时无新数据,则会保持连接,直到有新数据或超时才响应。客户端收到响应后立即发起下一次请求,实现“伪实时”。
361 | - **优点**:
362 | - **实时性较好**:一旦有新数据可立即推送,无需等待下次定时请求。
363 | - **空响应减少**:减少了无效的空响应,提升了效率。
364 | - **缺点**:
365 | - **服务器资源占用高**:需长时间维护大量连接,消耗服务器线程/连接数。
366 | - **资源浪费大**:每次响应后仍需重新建立连接,且依然基于 HTTP 单向请求-响应机制。
367 |
368 | **3. WebSocket**
369 |
370 | - **原理**:客户端与服务器通过一次 HTTP Upgrade 握手后,建立一条持久的 TCP 连接。之后,双方可以随时、主动地发送数据,实现真正的全双工、低延迟通信。
371 | - **优点**:
372 | - **实时性强**:数据可即时双向收发,延迟极低。
373 | - **资源效率高**:连接持续,无需反复建立/关闭,减少资源消耗。
374 | - **功能强大**:支持服务端主动推送消息、客户端主动发起通信。
375 | - **缺点**:
376 | - **使用限制**:需要服务器和客户端都支持 WebSocket 协议。对连接管理有一定要求(如心跳保活、断线重连等)。
377 | - **实现麻烦**:实现起来比短轮询和长轮询要更麻烦一些。
378 |
379 | 
380 |
381 | ### ⭐️SSE 与 WebSocket 有什么区别?
382 |
383 | SSE (Server-Sent Events) 和 WebSocket 都是用来实现服务器向浏览器实时推送消息的技术,让网页内容能自动更新,而不需要用户手动刷新。虽然目标相似,但它们在工作方式和适用场景上有几个关键区别:
384 |
385 | 1. **通信方式:**
386 | - **SSE:** **单向通信**。只有服务器能向客户端(浏览器)发送数据。客户端不能通过同一个连接向服务器发送数据(需要发起新的 HTTP 请求)。
387 | - **WebSocket:** **双向通信 (全双工)**。客户端和服务器可以随时互相发送消息,实现真正的实时交互。
388 | 2. **底层协议:**
389 | - **SSE:** 基于**标准的 HTTP/HTTPS 协议**。它本质上是一个“长连接”的 HTTP 请求,服务器保持连接打开并持续发送事件流。不需要特殊的服务器或协议支持,现有的 HTTP 基础设施就能用。
390 | - **WebSocket:** 使用**独立的 ws:// 或 wss:// 协议**。它需要通过一个特定的 HTTP "Upgrade" 请求来建立连接,并且服务器需要明确支持 WebSocket 协议来处理连接和消息帧。
391 | 3. **实现复杂度和成本:**
392 | - **SSE:** **实现相对简单**,主要在服务器端处理。浏览器端有标准的 EventSource API,使用方便。开发和维护成本较低。
393 | - **WebSocket:** **稍微复杂一些**。需要服务器端专门处理 WebSocket 连接和协议,客户端也需要使用 WebSocket API。如果需要考虑兼容性、心跳、重连等,开发成本会更高。
394 | 4. **断线重连:**
395 | - **SSE:** **浏览器原生支持**。EventSource API 提供了自动断线重连的机制。
396 | - **WebSocket:** **需要手动实现**。开发者需要自己编写逻辑来检测断线并进行重连尝试。
397 | 5. **数据类型:**
398 | - **SSE:** **主要设计用来传输文本** (UTF-8 编码)。如果需要传输二进制数据,需要先进行 Base64 等编码转换成文本。
399 | - **WebSocket:** **原生支持传输文本和二进制数据**,无需额外编码。
400 |
401 | 为了提供更好的用户体验和利用其简单、高效、基于标准 HTTP 的特性,**Server-Sent Events (SSE) 是目前大型语言模型 API(如 OpenAI、DeepSeek 等)实现流式响应的常用甚至可以说是标准的技木选择**。
402 |
403 | 这里以 DeepSeek 为例,我们发送一个请求并打开浏览器控制台验证一下:
404 |
405 | 
406 |
407 | 
408 |
409 | 可以看到,响应头应里包含了 `text/event-stream`,说明使用的确实是SSE。并且,响应数据也确实是持续分块传输。
410 |
411 | ## PING
412 |
413 | ### PING 命令的作用是什么?
414 |
415 | PING 命令是一种常用的网络诊断工具,经常用来测试网络中主机之间的连通性和网络延迟。
416 |
417 | 这里简单举一个例子,我们来 PING 一下百度。
418 |
419 | ```bash
420 | # 发送4个PING请求数据包到 www.baidu.com
421 | ❯ ping -c 4 www.baidu.com
422 |
423 | PING www.a.shifen.com (14.119.104.189): 56 data bytes
424 | 64 bytes from 14.119.104.189: icmp_seq=0 ttl=54 time=27.867 ms
425 | 64 bytes from 14.119.104.189: icmp_seq=1 ttl=54 time=28.732 ms
426 | 64 bytes from 14.119.104.189: icmp_seq=2 ttl=54 time=27.571 ms
427 | 64 bytes from 14.119.104.189: icmp_seq=3 ttl=54 time=27.581 ms
428 |
429 | --- www.a.shifen.com ping statistics ---
430 | 4 packets transmitted, 4 packets received, 0.0% packet loss
431 | round-trip min/avg/max/stddev = 27.571/27.938/28.732/0.474 ms
432 | ```
433 |
434 | PING 命令的输出结果通常包括以下几部分信息:
435 |
436 | 1. **ICMP Echo Request(请求报文)信息**:序列号、TTL(Time to Live)值。
437 | 2. **目标主机的域名或 IP 地址**:输出结果的第一行。
438 | 3. **往返时间(RTT,Round-Trip Time)**:从发送 ICMP Echo Request(请求报文)到接收到 ICMP Echo Reply(响应报文)的总时间,用来衡量网络连接的延迟。
439 | 4. **统计结果(Statistics)**:包括发送的 ICMP 请求数据包数量、接收到的 ICMP 响应数据包数量、丢包率、往返时间(RTT)的最小、平均、最大和标准偏差值。
440 |
441 | 如果 PING 对应的目标主机无法得到正确的响应,则表明这两个主机之间的连通性存在问题(有些主机或网络管理员可能禁用了对 ICMP 请求的回复,这样也会导致无法得到正确的响应)。如果往返时间(RTT)过高,则表明网络延迟过高。
442 |
443 | ### PING 命令的工作原理是什么?
444 |
445 | PING 基于网络层的 **ICMP(Internet Control Message Protocol,互联网控制报文协议)**,其主要原理就是通过在网络上发送和接收 ICMP 报文实现的。
446 |
447 | ICMP 报文中包含了类型字段,用于标识 ICMP 报文类型。ICMP 报文的类型有很多种,但大致可以分为两类:
448 |
449 | - **查询报文类型**:向目标主机发送请求并期望得到响应。
450 | - **差错报文类型**:向源主机发送错误信息,用于报告网络中的错误情况。
451 |
452 | PING 用到的 ICMP Echo Request(类型为 8 ) 和 ICMP Echo Reply(类型为 0) 属于查询报文类型 。
453 |
454 | - PING 命令会向目标主机发送 ICMP Echo Request。
455 | - 如果两个主机的连通性正常,目标主机会返回一个对应的 ICMP Echo Reply。
456 |
457 | ## DNS
458 |
459 | ### DNS 的作用是什么?
460 |
461 | DNS(Domain Name System)域名管理系统,是当用户使用浏览器访问网址之后,使用的第一个重要协议。DNS 要解决的是**域名和 IP 地址的映射问题**。
462 |
463 | 
464 |
465 | 在一台电脑上,可能存在浏览器 DNS 缓存,操作系统 DNS 缓存,路由器 DNS 缓存。如果以上缓存都查询不到,那么 DNS 就闪亮登场了。
466 |
467 | 目前 DNS 的设计采用的是分布式、层次数据库结构,**DNS 是应用层协议,它可以在 UDP 或 TCP 协议之上运行,端口为 53** 。
468 |
469 | ### DNS 服务器有哪些?根服务器有多少个?
470 |
471 | DNS 服务器自底向上可以依次分为以下几个层级(所有 DNS 服务器都属于以下四个类别之一):
472 |
473 | - 根 DNS 服务器。根 DNS 服务器提供 TLD 服务器的 IP 地址。目前世界上只有 13 组根服务器,我国境内目前仍没有根服务器。
474 | - 顶级域 DNS 服务器(TLD 服务器)。顶级域是指域名的后缀,如`com`、`org`、`net`和`edu`等。国家也有自己的顶级域,如`uk`、`fr`和`ca`。TLD 服务器提供了权威 DNS 服务器的 IP 地址。
475 | - 权威 DNS 服务器。在因特网上具有公共可访问主机的每个组织机构必须提供公共可访问的 DNS 记录,这些记录将这些主机的名字映射为 IP 地址。
476 | - 本地 DNS 服务器。每个 ISP(互联网服务提供商)都有一个自己的本地 DNS 服务器。当主机发出 DNS 请求时,该请求被发往本地 DNS 服务器,它起着代理的作用,并将该请求转发到 DNS 层次结构中。严格说来,不属于 DNS 层级结构
477 |
478 | 世界上并不是只有 13 台根服务器,这是很多人普遍的误解,网上很多文章也是这么写的。实际上,现在根服务器数量远远超过这个数量。最初确实是为 DNS 根服务器分配了 13 个 IP 地址,每个 IP 地址对应一个不同的根 DNS 服务器。然而,由于互联网的快速发展和增长,这个原始的架构变得不太适应当前的需求。为了提高 DNS 的可靠性、安全性和性能,目前这 13 个 IP 地址中的每一个都有多个服务器,截止到 2023 年底,所有根服务器之和达到了 1700 多台,未来还会继续增加。
479 |
480 | ### DNS 解析的过程是什么样的?
481 |
482 | 整个过程的步骤比较多,我单独写了一篇文章详细介绍:[DNS 域名系统详解(应用层)](https://javaguide.cn/cs-basics/network/dns.html) 。
483 |
484 | ### DNS 劫持了解吗?如何应对?
485 |
486 | DNS 劫持是一种网络攻击,它通过修改 DNS 服务器的解析结果,使用户访问的域名指向错误的 IP 地址,从而导致用户无法访问正常的网站,或者被引导到恶意的网站。DNS 劫持有时也被称为 DNS 重定向、DNS 欺骗或 DNS 污染。
487 |
488 | ## TCP 与 UDP
489 |
490 | ### ⭐️TCP 与 UDP 的区别(重要)
491 |
492 | 1. **是否面向连接**:
493 | - TCP 是面向连接的。在传输数据之前,必须先通过“三次握手”建立连接;数据传输完成后,还需要通过“四次挥手”来释放连接。这保证了双方都准备好通信。
494 | - UDP 是无连接的。发送数据前不需要建立任何连接,直接把数据包(数据报)扔出去。
495 | 2. **是否是可靠传输**:
496 | - TCP 提供可靠的数据传输服务。它通过序列号、确认应答 (ACK)、超时重传、流量控制、拥塞控制等一系列机制,来确保数据能够无差错、不丢失、不重复且按顺序地到达目的地。
497 | - UDP 提供不可靠的传输。它尽最大努力交付 (best-effort delivery),但不保证数据一定能到达,也不保证到达的顺序,更不会自动重传。收到报文后,接收方也不会主动发确认。
498 | 3. **是否有状态**:
499 | - TCP 是有状态的。因为要保证可靠性,TCP 需要在连接的两端维护连接状态信息,比如序列号、窗口大小、哪些数据发出去了、哪些收到了确认等。
500 | - UDP 是无状态的。它不维护连接状态,发送方发出数据后就不再关心它是否到达以及如何到达,因此开销更小(**这很“渣男”!**)。
501 | 4. **传输效率**:
502 | - TCP 因为需要建立连接、发送确认、处理重传等,其开销较大,传输效率相对较低。
503 | - UDP 结构简单,没有复杂的控制机制,开销小,传输效率更高,速度更快。
504 | 5. **传输形式**:
505 | - TCP 是面向字节流 (Byte Stream) 的。它将应用程序交付的数据视为一连串无结构的字节流,可能会对数据进行拆分或合并。
506 | - UDP 是面向报文 (Message Oriented) 的。应用程序交给 UDP 多大的数据块,UDP 就照样发送,既不拆分也不合并,保留了应用程序消息的边界。
507 | 6. **首部开销**:
508 | - TCP 的头部至少需要 20 字节,如果包含选项字段,最多可达 60 字节。
509 | - UDP 的头部非常简单,固定只有 8 字节。
510 | 7. **是否提供广播或多播服务**:
511 | - TCP 只支持点对点 (Point-to-Point) 的单播通信。
512 | - UDP 支持一对一 (单播)、一对多 (多播/Multicast) 和一对所有 (广播/Broadcast) 的通信方式。
513 | 8. ……
514 |
515 | 为了更直观地对比,可以看下面这个表格:
516 |
517 | | 特性 | TCP | UDP |
518 | | ------------ | -------------------------- | ----------------------------------- |
519 | | **连接性** | 面向连接 | 无连接 |
520 | | **可靠性** | 可靠 | 不可靠 (尽力而为) |
521 | | **状态维护** | 有状态 | 无状态 |
522 | | **传输效率** | 较低 | 较高 |
523 | | **传输形式** | 面向字节流 | 面向数据报 (报文) |
524 | | **头部开销** | 20 - 60 字节 | 8 字节 |
525 | | **通信模式** | 点对点 (单播) | 单播、多播、广播 |
526 | | **常见应用** | HTTP/HTTPS, FTP, SMTP, SSH | DNS, DHCP, SNMP, TFTP, VoIP, 视频流 |
527 |
528 | ### ⭐️什么时候选择 TCP,什么时候选 UDP?
529 |
530 | 选择 TCP 还是 UDP,主要取决于你的应用**对数据传输的可靠性要求有多高,以及对实时性和效率的要求有多高**。
531 |
532 | 当**数据准确性和完整性至关重要,一点都不能出错**时,通常选择 TCP。因为 TCP 提供了一整套机制(三次握手、确认应答、重传、流量控制等)来保证数据能够可靠、有序地送达。典型应用场景如下:
533 |
534 | - **Web 浏览 (HTTP/HTTPS):** 网页内容、图片、脚本必须完整加载才能正确显示。
535 | - **文件传输 (FTP, SCP):** 文件内容不允许有任何字节丢失或错序。
536 | - **邮件收发 (SMTP, POP3, IMAP):** 邮件内容需要完整无误地送达。
537 | - **远程登录 (SSH, Telnet):** 命令和响应需要准确传输。
538 | - ......
539 |
540 | 当**实时性、速度和效率优先,并且应用能容忍少量数据丢失或乱序**时,通常选择 UDP。UDP 开销小、传输快,没有建立连接和保证可靠性的复杂过程。典型应用场景如下:
541 |
542 | - **实时音视频通信 (VoIP, 视频会议, 直播):** 偶尔丢失一两个数据包(可能导致画面或声音短暂卡顿)通常比因为等待重传(TCP 机制)导致长时间延迟更可接受。应用层可能会有自己的补偿机制。
543 | - **在线游戏:** 需要快速传输玩家位置、状态等信息,对实时性要求极高,旧的数据很快就没用了,丢失少量数据影响通常不大。
544 | - **DHCP (动态主机配置协议):** 客户端在请求 IP 时自身没有 IP 地址,无法满足 TCP 建立连接的前提条件,并且 DHCP 有广播需求、交互模式简单以及自带可靠性机制。
545 | - **物联网 (IoT) 数据上报:** 某些场景下,传感器定期上报数据,丢失个别数据点可能不影响整体趋势分析。
546 | - ......
547 |
548 | ### HTTP 基于 TCP 还是 UDP?
549 |
550 | ~~**HTTP 协议是基于 TCP 协议的**,所以发送 HTTP 请求之前首先要建立 TCP 连接也就是要经历 3 次握手。~~
551 |
552 | 🐛 修正(参见 [issue#1915](https://github.com/Snailclimb/JavaGuide/issues/1915)):
553 |
554 | HTTP/3.0 之前是基于 TCP 协议的,而 HTTP/3.0 将弃用 TCP,改用 **基于 UDP 的 QUIC 协议** :
555 |
556 | - **HTTP/1.x 和 HTTP/2.0**:这两个版本的 HTTP 协议都明确建立在 TCP 之上。TCP 提供了可靠的、面向连接的传输,确保数据按序、无差错地到达,这对于网页内容的正确展示非常重要。发送 HTTP 请求前,需要先通过 TCP 的三次握手建立连接。
557 | - **HTTP/3.0**:这是一个重大的改变。HTTP/3 弃用了 TCP,转而使用 QUIC 协议,而 QUIC 是构建在 UDP 之上的。
558 |
559 | 
560 |
561 | **为什么 HTTP/3 要做这个改变呢?主要有两大原因:**
562 |
563 | 1. 解决队头阻塞 (Head-of-Line Blocking,简写:HOL blocking) 问题。
564 | 2. 减少连接建立的延迟。
565 |
566 | 下面我们来详细介绍这两大优化。
567 |
568 | 在 HTTP/2 中,虽然可以在一个 TCP 连接上并发传输多个请求/响应流(多路复用),但 TCP 本身的特性(保证有序、可靠)意味着如果其中一个流的某个 TCP 报文丢失或延迟,整个 TCP 连接都会被阻塞,等待该报文重传。这会导致所有在这个 TCP 连接上的 HTTP/2 流都受到影响,即使其他流的数据包已经到达。**QUIC (运行在 UDP 上) 解决了这个问题**。QUIC 内部实现了自己的多路复用和流控制机制。不同的 HTTP 请求/响应流在 QUIC 层面是真正独立的。如果一个流的数据包丢失,它只会阻塞该流,而不会影响同一 QUIC 连接上的其他流(本质上是多路复用+轮询),大大提高了并发传输的效率。
569 |
570 | 除了解决队头阻塞问题,HTTP/3.0 还可以减少握手过程的延迟。在 HTTP/2.0 中,如果要建立一个安全的 HTTPS 连接,需要经过 TCP 三次握手和 TLS 握手:
571 |
572 | 1. TCP 三次握手:客户端和服务器交换 SYN 和 ACK 包,建立一个 TCP 连接。这个过程需要 1.5 个 RTT(round-trip time),即一个数据包从发送到接收的时间。
573 | 2. TLS 握手:客户端和服务器交换密钥和证书,建立一个 TLS 加密层。这个过程需要至少 1 个 RTT(TLS 1.3)或者 2 个 RTT(TLS 1.2)。
574 |
575 | 所以,HTTP/2.0 的连接建立就至少需要 2.5 个 RTT(TLS 1.3)或者 3.5 个 RTT(TLS 1.2)。而在 HTTP/3.0 中,使用的 QUIC 协议(TLS 1.3,TLS 1.3 除了支持 1 个 RTT 的握手,还支持 0 个 RTT 的握手)连接建立仅需 0-RTT 或者 1-RTT。这意味着 QUIC 在最佳情况下不需要任何的额外往返时间就可以建立新连接。
576 |
577 | 相关证明可以参考下面这两个链接:
578 |
579 | -
580 | -
581 |
582 | ### 你知道哪些基于 TCP/UDP 的协议?
583 |
584 | TCP (传输控制协议) 和 UDP (用户数据报协议) 是互联网传输层的两大核心协议,它们为各种应用层协议提供了基础的通信服务。以下是一些常见的、分别构建在 TCP 和 UDP 之上的应用层协议:
585 |
586 | **运行于 TCP 协议之上的协议 (强调可靠、有序传输):**
587 |
588 | | 中文全称 (缩写) | 英文全称 | 主要用途 | 说明与特性 |
589 | | -------------------------- | ---------------------------------- | ---------------------------- | ------------------------------------------------------------ |
590 | | 超文本传输协议 (HTTP) | HyperText Transfer Protocol | 传输网页、超文本、多媒体内容 | **HTTP/1.x 和 HTTP/2 基于 TCP**。早期版本不加密,是 Web 通信的基础。 |
591 | | 安全超文本传输协议 (HTTPS) | HyperText Transfer Protocol Secure | 加密的网页传输 | 在 HTTP 和 TCP 之间增加了 SSL/TLS 加密层,确保数据传输的机密性和完整性。 |
592 | | 文件传输协议 (FTP) | File Transfer Protocol | 文件传输 | 传统的 FTP **明文传输**,不安全。推荐使用其安全版本 **SFTP (SSH File Transfer Protocol)** 或 **FTPS (FTP over SSL/TLS)** 。 |
593 | | 简单邮件传输协议 (SMTP) | Simple Mail Transfer Protocol | **发送**电子邮件 | 负责将邮件从客户端发送到服务器,或在邮件服务器之间传递。可通过 **STARTTLS** 升级到加密传输。 |
594 | | 邮局协议第 3 版 (POP3) | Post Office Protocol version 3 | **接收**电子邮件 | 通常将邮件从服务器**下载到本地设备后删除服务器副本** (可配置保留)。**POP3S** 是其 SSL/TLS 加密版本。 |
595 | | 互联网消息访问协议 (IMAP) | Internet Message Access Protocol | **接收和管理**电子邮件 | 邮件保留在服务器,支持多设备同步邮件状态、文件夹管理、在线搜索等。**IMAPS** 是其 SSL/TLS 加密版本。现代邮件服务首选。 |
596 | | 远程终端协议 (Telnet) | Teletype Network | 远程终端登录 | **明文传输**所有数据 (包括密码),安全性极差,基本已被 SSH 完全替代。 |
597 | | 安全外壳协议 (SSH) | Secure Shell | 安全远程管理、加密数据传输 | 提供了加密的远程登录和命令执行,以及安全的文件传输 (SFTP) 等功能,是 Telnet 的安全替代品。 |
598 |
599 | **运行于 UDP 协议之上的协议 (强调快速、低开销传输):**
600 |
601 | | 中文全称 (缩写) | 英文全称 | 主要用途 | 说明与特性 |
602 | | ----------------------- | ------------------------------------- | -------------------------- | ------------------------------------------------------------ |
603 | | 超文本传输协议 (HTTP/3) | HyperText Transfer Protocol version 3 | 新一代网页传输 | 基于 **QUIC** 协议 (QUIC 本身构建于 UDP 之上),旨在减少延迟、解决 TCP 队头阻塞问题,支持 0-RTT 连接建立。 |
604 | | 动态主机配置协议 (DHCP) | Dynamic Host Configuration Protocol | 动态分配 IP 地址及网络配置 | 客户端从服务器自动获取 IP 地址、子网掩码、网关、DNS 服务器等信息。 |
605 | | 域名系统 (DNS) | Domain Name System | 域名到 IP 地址的解析 | **通常使用 UDP** 进行快速查询。当响应数据包过大或进行区域传送 (AXFR) 时,会**切换到 TCP** 以保证数据完整性。 |
606 | | 实时传输协议 (RTP) | Real-time Transport Protocol | 实时音视频数据流传输 | 常用于 VoIP、视频会议、直播等。追求低延迟,允许少量丢包。通常与 RTCP 配合使用。 |
607 | | RTP 控制协议 (RTCP) | RTP Control Protocol | RTP 流的质量监控和控制信息 | 配合 RTP 工作,提供丢包、延迟、抖动等统计信息,辅助流量控制和拥塞管理。 |
608 | | 简单文件传输协议 (TFTP) | Trivial File Transfer Protocol | 简化的文件传输 | 功能简单,常用于局域网内无盘工作站启动、网络设备固件升级等小文件传输场景。 |
609 | | 简单网络管理协议 (SNMP) | Simple Network Management Protocol | 网络设备的监控与管理 | 允许网络管理员查询和修改网络设备的状态信息。 |
610 | | 网络时间协议 (NTP) | Network Time Protocol | 同步计算机时钟 | 用于在网络中的计算机之间同步时间,确保时间的一致性。 |
611 |
612 | **总结一下:**
613 |
614 | - **TCP** 更适合那些对数据**可靠性、完整性和顺序性**要求高的应用,如网页浏览 (HTTP/HTTPS)、文件传输 (FTP/SFTP)、邮件收发 (SMTP/POP3/IMAP)。
615 | - **UDP** 则更适用于那些对**实时性要求高、能容忍少量数据丢失**的应用,如域名解析 (DNS)、实时音视频 (RTP)、在线游戏、网络管理 (SNMP) 等。
616 |
617 | ### ⭐️TCP 三次握手和四次挥手(非常重要)
618 |
619 | **相关面试题**:
620 |
621 | - 为什么要三次握手?
622 | - 第 2 次握手传回了 ACK,为什么还要传回 SYN?
623 | - 为什么要四次挥手?
624 | - 为什么不能把服务器发送的 ACK 和 FIN 合并起来,变成三次挥手?
625 | - 如果第二次挥手时服务器的 ACK 没有送达客户端,会怎样?
626 | - 为什么第四次挥手客户端需要等待 2\*MSL(报文段最长寿命)时间后才进入 CLOSED 状态?
627 |
628 | **参考答案**:[TCP 三次握手和四次挥手(传输层)](https://javaguide.cn/cs-basics/network/tcp-connection-and-disconnection.html) 。
629 |
630 | ### ⭐️TCP 如何保证传输的可靠性?(重要)
631 |
632 | [TCP 传输可靠性保障(传输层)](https://javaguide.cn/cs-basics/network/tcp-reliability-guarantee.html)
633 |
634 | ## IP
635 |
636 | ### IP 协议的作用是什么?
637 |
638 | **IP(Internet Protocol,网际协议)** 是 TCP/IP 协议中最重要的协议之一,属于网络层的协议,主要作用是定义数据包的格式、对数据包进行路由和寻址,以便它们可以跨网络传播并到达正确的目的地。
639 |
640 | 目前 IP 协议主要分为两种,一种是过去的 IPv4,另一种是较新的 IPv6,目前这两种协议都在使用,但后者已经被提议来取代前者。
641 |
642 | ### 什么是 IP 地址?IP 寻址如何工作?
643 |
644 | 每个连入互联网的设备或域(如计算机、服务器、路由器等)都被分配一个 **IP 地址(Internet Protocol address)**,作为唯一标识符。每个 IP 地址都是一个字符序列,如 192.168.1.1(IPv4)、2001:0db8:85a3:0000:0000:8a2e:0370:7334(IPv6) 。
645 |
646 | 当网络设备发送 IP 数据包时,数据包中包含了 **源 IP 地址** 和 **目的 IP 地址** 。源 IP 地址用于标识数据包的发送方设备或域,而目的 IP 地址则用于标识数据包的接收方设备或域。这类似于一封邮件中同时包含了目的地地址和回邮地址。
647 |
648 | 网络设备根据目的 IP 地址来判断数据包的目的地,并将数据包转发到正确的目的地网络或子网络,从而实现了设备间的通信。
649 |
650 | 这种基于 IP 地址的寻址方式是互联网通信的基础,它允许数据包在不同的网络之间传递,从而实现了全球范围内的网络互联互通。IP 地址的唯一性和全局性保证了网络中的每个设备都可以通过其独特的 IP 地址进行标识和寻址。
651 |
652 | 
653 |
654 | ### 什么是 IP 地址过滤?
655 |
656 | **IP 地址过滤(IP Address Filtering)** 简单来说就是限制或阻止特定 IP 地址或 IP 地址范围的访问。例如,你有一个图片服务突然被某一个 IP 地址攻击,那我们就可以禁止这个 IP 地址访问图片服务。
657 |
658 | IP 地址过滤是一种简单的网络安全措施,实际应用中一般会结合其他网络安全措施,如认证、授权、加密等一起使用。单独使用 IP 地址过滤并不能完全保证网络的安全。
659 |
660 | ### ⭐️IPv4 和 IPv6 有什么区别?
661 |
662 | **IPv4(Internet Protocol version 4)** 是目前广泛使用的 IP 地址版本,其格式是四组由点分隔的数字,例如:123.89.46.72。IPv4 使用 32 位地址作为其 Internet 地址,这意味着共有约 42 亿( 2^32)个可用 IP 地址。
663 |
664 | 
665 |
666 | 这么少当然不够用啦!为了解决 IP 地址耗尽的问题,最根本的办法是采用具有更大地址空间的新版本 IP 协议 - **IPv6(Internet Protocol version 6)**。IPv6 地址使用更复杂的格式,该格式使用由单或双冒号分隔的一组数字和字母,例如:2001:0db8:85a3:0000:0000:8a2e:0370:7334 。IPv6 使用 128 位互联网地址,这意味着越有 2^128(3 开头的 39 位数字,恐怖如斯) 个可用 IP 地址。
667 |
668 | 
669 |
670 | 除了更大的地址空间之外,IPv6 的优势还包括:
671 |
672 | - **无状态地址自动配置(Stateless Address Autoconfiguration,简称 SLAAC)**:主机可以直接通过根据接口标识和网络前缀生成全局唯一的 IPv6 地址,而无需依赖 DHCP(Dynamic Host Configuration Protocol)服务器,简化了网络配置和管理。
673 | - **NAT(Network Address Translation,网络地址转换) 成为可选项**:IPv6 地址资源充足,可以给全球每个设备一个独立的地址。
674 | - **对标头结构进行了改进**:IPv6 标头结构相较于 IPv4 更加简化和高效,减少了处理开销,提高了网络性能。
675 | - **可选的扩展头**:允许在 IPv6 标头中添加不同的扩展头(Extension Headers),用于实现不同类型的功能和选项。
676 | - **ICMPv6(Internet Control Message Protocol for IPv6)**:IPv6 中的 ICMPv6 相较于 IPv4 中的 ICMP 有了一些改进,如邻居发现、路径 MTU 发现等功能的改进,从而提升了网络的可靠性和性能。
677 | - ……
678 |
679 | ### 如何获取客户端真实 IP?
680 |
681 | 获取客户端真实 IP 的方法有多种,主要分为应用层方法、传输层方法和网络层方法。
682 |
683 | **应用层方法** :
684 |
685 | 通过 [X-Forwarded-For](https://en.wikipedia.org/wiki/X-Forwarded-For) 请求头获取,简单方便。不过,这种方法无法保证获取到的是真实 IP,这是因为 X-Forwarded-For 字段可能会被伪造。如果经过多个代理服务器,X-Forwarded-For 字段可能会有多个值(附带了整个请求链中的所有代理服务器 IP 地址)。并且,这种方法只适用于 HTTP 和 SMTP 协议。
686 |
687 | **传输层方法**:
688 |
689 | 利用 TCP Options 字段承载真实源 IP 信息。这种方法适用于任何基于 TCP 的协议,不受应用层的限制。不过,这并非是 TCP 标准所支持的,所以需要通信双方都进行改造。也就是:对于发送方来说,需要有能力把真实源 IP 插入到 TCP Options 里面。对于接收方来说,需要有能力把 TCP Options 里面的 IP 地址读取出来。
690 |
691 | 也可以通过 Proxy Protocol 协议来传递客户端 IP 和 Port 信息。这种方法可以利用 Nginx 或者其他支持该协议的反向代理服务器来获取真实 IP 或者在业务服务器解析真实 IP。
692 |
693 | **网络层方法**:
694 |
695 | 隧道 +DSR 模式。这种方法可以适用于任何协议,就是实施起来会比较麻烦,也存在一定限制,实际应用中一般不会使用这种方法。
696 |
697 | ### NAT 的作用是什么?
698 |
699 | **NAT(Network Address Translation,网络地址转换)** 主要用于在不同网络之间转换 IP 地址。它允许将私有 IP 地址(如在局域网中使用的 IP 地址)映射为公有 IP 地址(在互联网中使用的 IP 地址)或者反向映射,从而实现局域网内的多个设备通过单一公有 IP 地址访问互联网。
700 |
701 | NAT 不光可以缓解 IPv4 地址资源短缺的问题,还可以隐藏内部网络的实际拓扑结构,使得外部网络无法直接访问内部网络中的设备,从而提高了内部网络的安全性。
702 |
703 | 
704 |
705 | 相关阅读:[NAT 协议详解(网络层)](https://javaguide.cn/cs-basics/network/nat.html)。
706 |
707 | ## ARP
708 |
709 | ### 什么是 Mac 地址?
710 |
711 | MAC 地址的全称是 **媒体访问控制地址(Media Access Control Address)**。如果说,互联网中每一个资源都由 IP 地址唯一标识(IP 协议内容),那么一切网络设备都由 MAC 地址唯一标识。
712 |
713 | 
714 |
715 | 可以理解为,MAC 地址是一个网络设备真正的身份证号,IP 地址只是一种不重复的定位方式(比如说住在某省某市某街道的张三,这种逻辑定位是 IP 地址,他的身份证号才是他的 MAC 地址),也可以理解为 MAC 地址是身份证号,IP 地址是邮政地址。MAC 地址也有一些别称,如 LAN 地址、物理地址、以太网地址等。
716 |
717 | > 还有一点要知道的是,不仅仅是网络资源才有 IP 地址,网络设备也有 IP 地址,比如路由器。但从结构上说,路由器等网络设备的作用是组成一个网络,而且通常是内网,所以它们使用的 IP 地址通常是内网 IP,内网的设备在与内网以外的设备进行通信时,需要用到 NAT 协议。
718 |
719 | MAC 地址的长度为 6 字节(48 比特),地址空间大小有 280 万亿之多( $2^{48}$ ),MAC 地址由 IEEE 统一管理与分配,理论上,一个网络设备中的网卡上的 MAC 地址是永久的。不同的网卡生产商从 IEEE 那里购买自己的 MAC 地址空间(MAC 的前 24 比特),也就是前 24 比特由 IEEE 统一管理,保证不会重复。而后 24 比特,由各家生产商自己管理,同样保证生产的两块网卡的 MAC 地址不会重复。
720 |
721 | MAC 地址具有可携带性、永久性,身份证号永久地标识一个人的身份,不论他到哪里都不会改变。而 IP 地址不具有这些性质,当一台设备更换了网络,它的 IP 地址也就可能发生改变,也就是它在互联网中的定位发生了变化。
722 |
723 | 最后,记住,MAC 地址有一个特殊地址:FF-FF-FF-FF-FF-FF(全 1 地址),该地址表示广播地址。
724 |
725 | ### ⭐️ARP 协议解决了什么问题?
726 |
727 | ARP 协议,全称 **地址解析协议(Address Resolution Protocol)**,它解决的是网络层地址和链路层地址之间的转换问题。因为一个 IP 数据报在物理上传输的过程中,总是需要知道下一跳(物理上的下一个目的地)该去往何处,但 IP 地址属于逻辑地址,而 MAC 地址才是物理地址,ARP 协议解决了 IP 地址转 MAC 地址的一些问题。
728 |
729 | ### ARP 协议的工作原理?
730 |
731 | [ARP 协议详解(网络层)](https://javaguide.cn/cs-basics/network/arp.html)
732 |
733 |
734 |
--------------------------------------------------------------------------------