├── .gitignore ├── README.md ├── _config.yml └── notes ├── JVM ├── media │ ├── 0ae87bbe6a0cfb8961b87fe5897ff83c.png │ ├── 19b5414762ecb587da68c6231c246aa4.png │ ├── 29eee2766457155d4c29cf60c359e42f.jpg │ ├── 2a67943e48b2b125ab942615a2ba7fd5.png │ ├── 4664caa1031e24e57434feeb5fa528f9.jpg │ ├── 4c36c2cdc1b8256201d6c9be8e6cac6c.png │ ├── 4f883fb0edf9b8876c945a1e7ba702d3.png │ ├── 57c3661f2fda61a83c80430a6220f93f.png │ ├── 67dc710ec041a9b83949ea068000a55b.jpg │ ├── 68c96152320fc0dcf16b54856b9a4318.png │ ├── 7c45caf3842f62a36b29a58b481bb983.png │ ├── 82feee6ec1556e003f19106bf0544c16.png │ ├── 8a3ccf092d0e3a55a649ffcf76341246.png │ ├── cbd775203253123eceb4fb14bd9b1984.png │ ├── cef317d6850feac848ee1d66b2fc6691.png │ ├── dbe4acc646b8383a307fa50c842b9a41.jpg │ ├── e3685e42bfc07a6cd7689a7c95bf9e61.png │ └── ee8964bfa715e52290b49f632e3e46af.png ├── 内存分区与对象创建.md ├── 内存回收与垃圾收集器.md └── 类加载与对象分配.md ├── Java ├── JUC包.md ├── Java中的锁.md ├── Java基础.md ├── Java多线程.md ├── Java的集合类.md └── media │ ├── 1258jhvsavgkvbkgk433.png │ ├── 208dc9874b6f917427562c8a72e39788.png │ ├── 3974bdbfd0b225c61a1d9772bc7c854e.png │ ├── 41e2624139de35352255a2b1c7a0b8a7.png │ ├── 4aedb9d9d47f02ec77420699a86f22a7.jpg │ ├── 54082c5bf8e8163e4fb1072c84687e85.png │ ├── 6286448d2f5f2cb602cb7113f3bfe545.emf │ ├── 6286448d2f5f2cb602cb7113f3bfe545.png │ ├── 753f5a40efd070b30d36e396d96e9645.png │ ├── 764c4cb9f6d7115ca743ab9f9583e6d1.jpg │ ├── 7b02d3d12c1f34841dc96a2c0a25f592.png │ ├── 7b663471926809bfa3123ab57f227638.png │ ├── 8802ffa13051fd93161f3a2ee6f2eb8e.jpg │ ├── 93ef7634eaf36d4c9a8ad277e23d14c4.jpg │ ├── 9a2eb3a0f7caca0fa4e33717087884ac.png │ ├── 9ab28550cbc32507a85cea984c74dfd1.png │ ├── 9e2974141f1be5d88523a1aff1eacb41.jpg │ ├── 9f373407f86d3dac6f0245ef34171ee5.png │ ├── a5379cb3f718cd7763573726ce52ed64.png │ ├── a5822525881f9cbbdff2cd538538ac99.png │ ├── b7bf23f28c7de6d6e7867cbaac6e29d4.emf │ ├── b7bf23f28c7de6d6e7867cbaac6e29d4.png │ ├── b7f94146622574758fbe49f9785549ce.jpg │ ├── bbc18b5eea9f2d4b2b79d77a6776a839.png │ ├── cf1372bc829900ee9ebe3b98521c34d4.png │ ├── d10508a4655d6acc7e26bc1e50ac7cfc.png │ ├── d492ad6909c09905de51b046c99b027b.jpg │ ├── dbcd6216766296a1a9b5053ff1894251.jpg │ └── ee303233eb0cf4de50329461cdff508c.png ├── 操作系统 ├── media │ ├── 3a0d08e33dda0f01697fe7f2f9ea8f5e.png │ ├── 4f19a1f675d490c4dc07b767ba3ebd45.png │ ├── 57bc457a739f100c896db967128230df.jpg │ ├── 5ba1a623955657a7ffc9328fb09097d7.png │ ├── 834250de1e7d3307cc47f33e0f74682a.png │ ├── 93a92127842011539d88efb7b88d8125.png │ ├── a25008f2f0b4ef9bb9b66f88119a6967.jpg │ ├── b423c1f3dfa24f50752341f07fbf033b.png │ ├── e51457ba11bd8cc822654621ab142b61.png │ └── e95ea1ecb82227738c88286e31349b7b.jpg └── 操作系统.md ├── 数据库 ├── MySql.md ├── Redis.md ├── Redis面试题.md └── media │ ├── 221cc78a1d285ec5c774d2c7087efffd.png │ ├── 2a7316dccbdedd2fb8fec00b36daf09c.png │ ├── 3ce21bb5c36a79cdd965f2a5b564efc8.png │ ├── 547d487238dbe2d2fcc64c67876bfcfc.png │ ├── 734a962da6997171b7f295dc2f6a1655.png │ ├── 787dd3d1a08f120c8118d6554cac2fda.png │ ├── 7ee11229e2d1d4ef3c2396359d38ce0a.png │ ├── 8b4b518fa85afcb8a7dd7d1353dbd4f9.png │ ├── 9fc4ef4042e6927fb20e0b60a9281740.png │ ├── ab49f5fe6265ebca35624d11125dccdf.png │ ├── c9beb8350bc758c62d845c38a5a497b7.png │ ├── d802bdad2d733857287cf627ea12efc2.jpg │ ├── ddedd0b3683e99c97ac56be9fb1971d7.png │ ├── de254d956041a06f12d467c39dbc77e1.png │ └── df9a8160b6b13dbe46660b46cb9be344.png ├── 框架 ├── Netty.md ├── Netty.pdf ├── Spring.md └── media │ ├── 0bd3287a63d24f7eafa5cd0efeb0e552.png │ ├── 7bb35a1120e5eb25745317d009811937.png │ └── b8c67a9a399aab7ab0d03f39605e5544.png ├── 算法与数据结构 ├── 海量数据.md └── 设计模式.md └── 计算机网络 ├── HTTP2 详解.md ├── HTTP与HTTPS.md ├── TCP与UDP.md ├── gh-md-toc.exe ├── media ├── 063eafbf58d7323928914758dfe636b4.png ├── 328c8413e932880c1e7978b79c72cf41.jpg ├── 37790def0829cfd904ce2ebf003c0a44.png ├── 483a269b059d43ecc09825bbd447a0a8.png ├── 4a7176622d55c07324e96b531e1b8f78.png ├── 511b0157a8f0b6ad5c37d379e35c6410.png ├── 558215a64084e2ed0294d397ec9a40a8.png ├── 611b769803b9c47bf7ce2f6303b8ce4f.png ├── 651c2597c261a59847623aeb681fc045.png ├── 66ab9464b9680755d298336b633ba6dd.png ├── 707486bd9bb00900cae36ebb808bae6b.png ├── 7730838766deeccbcfb552dececdc0df.png ├── 7e8edd74d94c92974d01ddd1fccd3044.png ├── 8d0dca7af83c0c07123853bd4e03856e.jpg ├── 92e8ffd5026d530e630b6ba90c23236f.png ├── 954762ad809395ecb6f54e28b24b3ff2.png ├── 9e9dac1cdbd06c2c5c8ce5fc7d7bc586.png ├── af7a0dc07fbdc659d1dc87465854e7d3.png ├── af8028eff4358aa8a1aa927457c5c07b.png ├── c6f8db684870294ee2d10b9c1e0e7379.png ├── d8c9808d010f264e6b251f719138a80a.png ├── dc4f98a2afa7adc89bf1377e18d4e78d.png ├── dd5683b920fb4ac02ba9cb22438007e0.png ├── e0788460db77c061553b23598aa03ecc.png ├── e294ecec380425a71209c1a7b464a359.png ├── ecb3bf5299da01ef5a996f263d0dc8fe.jpg └── ecefe493cd15f8516606a66f0cc5d599.png ├── 安全加密与网站攻击.md └── 计算机网络分层模型.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/* 2 | package.json 3 | package-lock.json 4 | used.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RunJava 2 | 总结Java开发基础的知识 3 | 4 | 5 | ## Java 6 | 7 | - [Java 基础](https://github.com/LLLRS/RunJava/blob/master/notes/Java/Java%E5%9F%BA%E7%A1%80.md) 8 | - [Java 中的锁](https://github.com/LLLRS/RunJava/blob/master/notes/Java/Java%E4%B8%AD%E7%9A%84%E9%94%81.md) 9 | - [Java 多线程](https://github.com/LLLRS/RunJava/blob/master/notes/Java/Java%E5%A4%9A%E7%BA%BF%E7%A8%8B.md) 10 | - [Java 集合类](https://github.com/LLLRS/RunJava/blob/master/notes/Java/Java%E7%9A%84%E9%9B%86%E5%90%88%E7%B1%BB.md) 11 | - [JUC包](https://github.com/LLLRS/RunJava/blob/master/notes/Java/JUC%E5%8C%85.md) 12 | 13 | ## JVM 14 | - [内存分区与对象创建](https://github.com/LLLRS/RunJava/blob/master/notes/JVM/%E5%86%85%E5%AD%98%E5%88%86%E5%8C%BA%E4%B8%8E%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA.md) 15 | - [内存回收与垃圾收集器](https://github.com/LLLRS/RunJava/blob/master/notes/JVM/%E5%86%85%E5%AD%98%E5%9B%9E%E6%94%B6%E4%B8%8E%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E5%99%A8.md) 16 | - [类加载与对象分配](https://github.com/LLLRS/RunJava/blob/master/notes/JVM/%E7%B1%BB%E5%8A%A0%E8%BD%BD%E4%B8%8E%E5%AF%B9%E8%B1%A1%E5%88%86%E9%85%8D.md) 17 | 18 | ## 数据库 19 | - [MySql](https://github.com/LLLRS/RunJava/blob/master/notes/%E6%95%B0%E6%8D%AE%E5%BA%93/MySql.md) 20 | - [Redis](https://github.com/LLLRS/RunJava/blob/master/notes/%E6%95%B0%E6%8D%AE%E5%BA%93/Redis.md) 21 | 22 | ## 框架 23 | - [Spring](https://github.com/LLLRS/RunJava/blob/master/notes/%E6%A1%86%E6%9E%B6/Spring.md) 24 | - [Netty](https://github.com/LLLRS/RunJava/blob/master/notes/%E6%A1%86%E6%9E%B6/Netty.md) 25 | 26 | ## 算法与数据结构 27 | - [海量数据](https://github.com/LLLRS/RunJava/blob/master/notes/%E7%AE%97%E6%B3%95%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E6%B5%B7%E9%87%8F%E6%95%B0%E6%8D%AE.md) 28 | - [设计模式](https://github.com/LLLRS/RunJava/blob/master/notes/%E7%AE%97%E6%B3%95%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md) 29 | 30 | ## 计算机网络 31 | - [计算机网络分层模型](https://github.com/LLLRS/RunJava/blob/master/notes/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%88%86%E5%B1%82%E6%A8%A1%E5%9E%8B.md) 32 | - [HTTP与HTTPS](https://github.com/LLLRS/RunJava/blob/master/notes/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/HTTP%E4%B8%8EHTTPS.md) 33 | - [TCP与UDP](https://github.com/LLLRS/RunJava/blob/master/notes/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/TCP%E4%B8%8EUDP.md) 34 | - [安全加密与网站攻击](https://github.com/LLLRS/RunJava/blob/master/notes/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/%E5%AE%89%E5%85%A8%E5%8A%A0%E5%AF%86%E4%B8%8E%E7%BD%91%E7%AB%99%E6%94%BB%E5%87%BB.md) 35 | 36 | 37 | ## 操作系统 38 | - [操作系统](https://github.com/LLLRS/RunJava/blob/master/notes/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md) 39 | 40 | ## 面试 41 | - [10个最难回答的Java面试题](https://segmentfault.com/a/1190000019962661) 42 | - [8月份21道最新Java面试题剖析](https://juejin.im/post/5d69055ee51d4561a91850b4) 43 | 44 | ## 链接 45 | - [CS-Notes](https://github.com/CyC2018/CS-Notes) 46 | - [JavaGuide](https://github.com/Snailclimb/JavaGuide) 47 | - [Java-Notes](https://github.com/PansonPanson/Java-Notes) 48 | - [KnowledgeBase](https://github.com/TransientWang/KnowledgeBase) 49 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /notes/JVM/media/0ae87bbe6a0cfb8961b87fe5897ff83c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/JVM/media/0ae87bbe6a0cfb8961b87fe5897ff83c.png -------------------------------------------------------------------------------- /notes/JVM/media/19b5414762ecb587da68c6231c246aa4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/JVM/media/19b5414762ecb587da68c6231c246aa4.png -------------------------------------------------------------------------------- /notes/JVM/media/29eee2766457155d4c29cf60c359e42f.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/JVM/media/29eee2766457155d4c29cf60c359e42f.jpg -------------------------------------------------------------------------------- /notes/JVM/media/2a67943e48b2b125ab942615a2ba7fd5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/JVM/media/2a67943e48b2b125ab942615a2ba7fd5.png -------------------------------------------------------------------------------- /notes/JVM/media/4664caa1031e24e57434feeb5fa528f9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/JVM/media/4664caa1031e24e57434feeb5fa528f9.jpg -------------------------------------------------------------------------------- /notes/JVM/media/4c36c2cdc1b8256201d6c9be8e6cac6c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/JVM/media/4c36c2cdc1b8256201d6c9be8e6cac6c.png -------------------------------------------------------------------------------- /notes/JVM/media/4f883fb0edf9b8876c945a1e7ba702d3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/JVM/media/4f883fb0edf9b8876c945a1e7ba702d3.png -------------------------------------------------------------------------------- /notes/JVM/media/57c3661f2fda61a83c80430a6220f93f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/JVM/media/57c3661f2fda61a83c80430a6220f93f.png -------------------------------------------------------------------------------- /notes/JVM/media/67dc710ec041a9b83949ea068000a55b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/JVM/media/67dc710ec041a9b83949ea068000a55b.jpg -------------------------------------------------------------------------------- /notes/JVM/media/68c96152320fc0dcf16b54856b9a4318.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/JVM/media/68c96152320fc0dcf16b54856b9a4318.png -------------------------------------------------------------------------------- /notes/JVM/media/7c45caf3842f62a36b29a58b481bb983.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/JVM/media/7c45caf3842f62a36b29a58b481bb983.png -------------------------------------------------------------------------------- /notes/JVM/media/82feee6ec1556e003f19106bf0544c16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/JVM/media/82feee6ec1556e003f19106bf0544c16.png -------------------------------------------------------------------------------- /notes/JVM/media/8a3ccf092d0e3a55a649ffcf76341246.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/JVM/media/8a3ccf092d0e3a55a649ffcf76341246.png -------------------------------------------------------------------------------- /notes/JVM/media/cbd775203253123eceb4fb14bd9b1984.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/JVM/media/cbd775203253123eceb4fb14bd9b1984.png -------------------------------------------------------------------------------- /notes/JVM/media/cef317d6850feac848ee1d66b2fc6691.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/JVM/media/cef317d6850feac848ee1d66b2fc6691.png -------------------------------------------------------------------------------- /notes/JVM/media/dbe4acc646b8383a307fa50c842b9a41.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/JVM/media/dbe4acc646b8383a307fa50c842b9a41.jpg -------------------------------------------------------------------------------- /notes/JVM/media/e3685e42bfc07a6cd7689a7c95bf9e61.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/JVM/media/e3685e42bfc07a6cd7689a7c95bf9e61.png -------------------------------------------------------------------------------- /notes/JVM/media/ee8964bfa715e52290b49f632e3e46af.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/JVM/media/ee8964bfa715e52290b49f632e3e46af.png -------------------------------------------------------------------------------- /notes/JVM/内存分区与对象创建.md: -------------------------------------------------------------------------------- 1 | * [内存分区](#%E5%86%85%E5%AD%98%E5%88%86%E5%8C%BA) 2 | * [程序计数器](#%E7%A8%8B%E5%BA%8F%E8%AE%A1%E6%95%B0%E5%99%A8) 3 | * [虚拟机栈](#%E8%99%9A%E6%8B%9F%E6%9C%BA%E6%A0%88) 4 | * [本地方法栈](#%E6%9C%AC%E5%9C%B0%E6%96%B9%E6%B3%95%E6%A0%88) 5 | * [堆](#%E5%A0%86) 6 | * [方法区](#%E6%96%B9%E6%B3%95%E5%8C%BA) 7 | * [运行时常量池](#%E8%BF%90%E8%A1%8C%E6%97%B6%E5%B8%B8%E9%87%8F%E6%B1%A0) 8 | * [直接内存](#%E7%9B%B4%E6%8E%A5%E5%86%85%E5%AD%98) 9 | * [对象在内存中的初始化过程](#%E5%AF%B9%E8%B1%A1%E5%9C%A8%E5%86%85%E5%AD%98%E4%B8%AD%E7%9A%84%E5%88%9D%E5%A7%8B%E5%8C%96%E8%BF%87%E7%A8%8B) 10 | * [对象的内存布局](#%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%86%85%E5%AD%98%E5%B8%83%E5%B1%80) 11 | * [对象的强、软、弱和虚引用](#%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%BC%BA%E8%BD%AF%E5%BC%B1%E5%92%8C%E8%99%9A%E5%BC%95%E7%94%A8) 12 | * [内存溢出原因](#%E5%86%85%E5%AD%98%E6%BA%A2%E5%87%BA%E5%8E%9F%E5%9B%A0) 13 | * [内存泄漏](#%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F) 14 | 15 | 16 | ## 内存分区 17 | > 在Java运行时的数据区里,由JVM管理的内存区域分为下图几个模块: 18 | 19 | ![](media/4664caa1031e24e57434feeb5fa528f9.jpg) 20 | 21 | ### 程序计数器 22 | 23 | 程序计数器(Program Counter Register)是一个比较小的内存区域,用于指示当前线程所执行的字节码执行到了第几行,可以理解为是当前线程的行号指示器。字节码解释器在工作时,会通过改变这个计数器的值来取下一条语句指令。 24 | 25 | 每个程序计数器只用来记录一个线程的行号,所以它是线程私有(一个线程就有一个程序计数器)的。 26 | 27 | 如果程序执行的是一个Java方法,则计数器记录的是正在执行的虚拟机字节码指令地址;如果正在执行的是一个本地(native,由C语言编写完成)方法,则计数器的值为Undefined,由于程序计数器只是记录当前指令地址,所以不存在内存溢出的情况,因此,程序计数器也是所有JVM内存区域中唯一一个没有定义OutOfMemoryError的区域。 28 | 29 | ### 虚拟机栈 30 | 虚拟机栈是一个线程的每个方法在执行的同时,都会创建一个栈帧(Statck 31 | Frame),栈帧中存储的有局部变量表、操作站、动态链接、方法出口等,当方法被调用时,栈帧在JVM栈中入栈,当方法执行完成时,栈帧出栈。 32 | 33 | 局部变量表中存储着方法的相关局部变量,包括各种基本数据类型,对象的引用,返回地址等。在局部变量表中,只有long和double类型会占用2个局部变量空间(Slot,对于32位机器,一个Slot就是32个bit),其它都是1个Slot。需要注意的是,**局部变量表是在编译时就已经确定好的,方法运行所需要分配的空间在栈帧中是完全确定的,在方法的生命周期内都不会改变**。 34 | 35 | 虚拟机栈中定义了两种异常,如果线程调用的栈深度大于虚拟机允许的最大深度,则抛出StatckOverFlowError(栈溢出)。不过多数Java虚拟机都允许动态扩展虚拟机栈的大小(有少部分是固定长度的),所以线程可以一直申请栈,直到内存不足,此时,会抛出OutOfMemoryError(内存溢出)。 36 | 37 | 每个线程对应着一个虚拟机栈,因此虚拟机栈也是线程私有的。 38 | 39 | ### 本地方法栈 40 | 本地方法栈在作用,运行机制,异常类型等方面都与虚拟机栈相同,唯一的区别是:虚拟机栈是执行Java方法的,而本地方法栈是用来执行native方法的,在很多虚拟机中(如Sun的JDK默认的HotSpot虚拟机),会将本地方法栈与虚拟机栈放在一起使用。本地方法栈也是线程私有的。 41 | 42 | ### 堆 43 | 44 | 堆区(Heap)是理解JavaGC机制最重要的区域。**在JVM所管理的内存中,堆区是最大的一块**,堆区也是JavaGC机制所管理的主要内存区域,堆区由所有线程共享,在虚拟机启动时创建。堆区的存在是为了存储对象实例,原则上讲,所有的对象都在堆区上分配内存(不过现代技术里,也不是这么绝对的,也有栈上直接分配的)。 45 | 46 | 一般的,根据Java虚拟机规范规定,堆内存需要在逻辑上是连续的(在物理上不需要),在实现时,可以是固定大小的,也可以是可扩展的,目前主流的虚拟机都是可扩展的。如果在执行垃圾回收之后,仍没有足够的内存分配,也不能再扩展,将会抛出OutOfMemoryError:Java heap space异常。 47 | 48 | ### 方法区 49 | 50 | 方法区(Method Area):在Java虚拟机规范中,将方法区作为堆的一个逻辑部分来对待,但事实上,方法区并不是堆(Non-Heap)。在HotSpot Java虚拟机的实现方式中,**将分代收集的思想扩展到了方法区,并将方法区设计成了永久代。不过,除HotSpot之外的多数虚拟机,并不将方法区当做永久代,HotSpot本身,也计划取消永久代**。 51 | 52 | 方法区是各个线程共享的区域,用于存储已经被虚拟机加载的类信息(即加载类时需要加载的信息,包括版本、field、方法、接口等信息)、final常量、静态变量、编译器即时编译的代码等。 53 | 54 | 方法区在物理上也不需要是连续的,可以选择固定大小或可扩展大小,并且方法区比堆还多了一个限制:可以选择是否执行垃圾收集。一般的,方法区上执行的垃圾收集是很少的,这也是方法区被称为永久代的原因之一(HotSpot),但这也不代表着在方法区上完全没有垃圾收集,其上的垃圾收集主要是针对常量池的内存回收和对已加载类的卸载。 55 | 56 | 在方法区上定义了OutOfMemoryError:PermGen space异常,在内存不足时抛出。 57 | 58 | ### 运行时常量池 59 | 运行时常量池(Runtime Constant Pool)是方法区的一部分,用于存储编译期就生成的字面常量、符号引用、翻译出来的直接引用(符号引用就是编码是用字符串表示某个变量、接口的位置,直接引用就是根据符号引用翻译出来的地址,将在类链接阶段完成翻译)。 60 | 61 | 运行时常量池除了存储编译期常量外,也可以存储在运行时间产生的常量(比如String类的intern()方法,作用是String维护了一个常量池,如果调用的字符“abc”已经在常量池中,则返回池中的字符串地址,否则,新建一个常量加入池中,并返回地址)。 62 | 63 | ### 直接内存 64 | 65 | 直接内存(Direct Memory)并不是JVM管理的内存,可以这样理解,直接内存,就是JVM以外的机器内存,比如有4G的内存,JVM占用了1G,则其余的3G就是直接内存。JDK中有一种基于通道(Channel)和缓冲区(Buffer)的内存分配方式,将由C语言实现的native函数库分配在直接内存中,用存储在JVM堆中的DirectByteBuffer来引用。由于直接内存收到本机器内存的限制,所以也可能出现OutOfMemoryError的异常。 66 | 67 | ## 对象在内存中的初始化过程 68 | 69 | 下面详细介绍Java程序中new一个普通对象时,HotSpot虚拟机是怎么样创建这个对象的,包括5个步骤:相应类加载检查过程、在Java堆中为对象分配内存、分配后内存初始化为零、对对象进行必要的设置、以及执行对象实例方法\。 70 | 71 | **1、相应类加载检查过程** 72 | 73 | Java程序中的“new”操作会转换为Class文件中方法的“new”字节码指令。遇到new指令时,先检查指令参数是否能在常量池中定位到一个类的符号引用: 74 | 75 | A)如果能定位到,检查这个符号引用代表的类是否已被加载、解析和初始化过。 76 | 77 | B)如果不能定位到,或没有检查到,就先执行相应的类加载过程。 78 | 79 | **2、为对象分配内存** 80 | 81 | 对象所需内存的大小在类加载完成后便完全确定(JVM可以通过普通Java对象的类元数据信息确定对象大小),为对象分配内存相当于把一块确定大小的内存从Java堆里划分出来。 82 | 83 | **分配方式有两种:** 84 | 85 | I)指针碰撞:如果Java堆是绝对规整的:一边是用过的内存,一边是空闲的内存,中间一个指针作为边界指示器,分配内存只需向空闲那边移动指针,这种分配方式称为"指针碰撞"(Bump 86 | the Pointer)。 87 | 88 | II)空闲列表:如果Java堆不是规整的:用过的和空闲的内存相互交错。需要维护一个列表,记录哪些内存可用,分配内存时查表找到一个足够大的内存,并更新列表,这种分配方式称为"空闲列表"(Free 89 | List); 90 | 91 | **Java堆是否规整由JVM采用的垃圾收集器是否带有压缩功能决定的**。所以,使用Serial、ParNew等带Compact过程的收集器时,JVM采用指针碰撞方式分配内存;而使用CMS这 92 | 93 | 种基于标记-清除(Mark-Sweep)算法的收集器时,采用空闲列表方式; 94 | 95 | **线程安全问题**:并发时,上面两种方式分配内存的操作都不是线程安全的,有两种解决方案: 96 | 97 | I)同步处理:对分配内存的动作进行同步处理。JVM采用CAS(Compare and 98 | Swap)机制加上失败重试的方式,保证更新操作的原子性; 99 | 100 | II)本地线程分配缓冲区:把分配内存的动作按照线程划分在不同的空间中进行。在每个线程在Java堆预先分配一小块内存,称为本地线程分配缓冲区(Thread 101 | Local Allocation 102 | Buffer,TLAB),哪个线程需要分配内存就从哪个线程的TLAB上分配,只有TLAB用完需要分配新的TLAB时,才需要同步处理。 103 | 104 | **3、对象内存初始化为零** 105 | 106 | > 对象内存初始化为零,但不包括对象头。 107 | > 如果使用TLAB,提前至分配TLAB时。 108 | > 这保证了程序中对象(及实例变量)不显式初始赋零值,程序也能访问到零值。 109 | 110 | **4、对象内存初始化为零** 111 | 112 | 主要设置对象头信息,包括类元数据引用、对象的哈希码、对象的GC分代年龄等。 113 | 114 | **5、执行对象实例方法\** 115 | 116 | 该方法把对象(实例变量)按照程序中定义的初始赋值进行初始化。 117 | 118 | > **以Student s = new Student()为例**: 119 | > 1.首先查看类的符号引用,看是否已经在常量池中,在说明已经加载过了,不在的话需要进行类的加载,验证,准备,解析,初始化的过程。 120 | >2.上诉过程执行完毕以后,又将Student加载进内存,也就是存储Student.class的字段信息和方法信息,存储到方法区中。 121 | >字段信息:存放类中声明的每一个字段的信息,包括字段的名、类型、修饰符。 122 | >方法信息:类中声明的每一个方法的信息,包括方法名、返回值类型、参数类型、修饰符、异常、方法的字节码。 123 | >3.然后在自己的线程私有的虚拟机栈中,存储该引用,然后在每个线程的私有空间里面去分配空间存储new 124 | Student(),如果空间不足在eden区域进行分配空间。 125 | >4.对类中的成员变量进行默认初始化。 126 | >5.对类中的成员变量进行显示初始化。 127 | >6.有构造代码块就先执行构造代码块,如果没有,则省略。 128 | >7.执行构造方法,通过构造方法对对对象数据进行初始化。 129 | >8.堆内存中的数据初始化完毕,把内存值复制给 s 变量 130 | 131 | 132 | ## 对象的内存布局 133 | 在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)、和对齐填充(Padding)。 134 | 135 | HotSpot 虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希吗,GC分代年龄标志、锁状态年龄标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32bit和64bit,官方称它为“Mark Word”。对象需要存储的运行时数据很多,其实已经超出了32位、64位 BitMap结构所能记录的限度,但是对象头信息时与对象自身定义无关的额外存储成本,考虑到虚拟机的空间效率,Mark Work被设计成一个非固定的数据结构以便在极小的空间内存储更多的信息。它会根据对象的状态复用自己的空间。例如,在32位的HotSpot虚拟机中,如果对象处于未被锁定的状态下,那么 Mark Word 的32bit 空间中的25bit用于存储对象Hash码,4比特用于存储对象分代年龄,2bit用于存储锁标志位,1bit固定为0,而在其他状态(轻量级锁定,重量级锁定,GC标记,可偏向)见下表: 136 | 137 | 138 | | 存储内容 | 标志位 | 状态 | 139 | | ------------------------------------ | ------ | ------------------ | 140 | | 对象哈希码、对象分代年龄 | 01 | 未锁定 | 141 | | 指向锁记录的指针 | 00 | 轻量级锁定 | 142 | | 指向重量级记录的指针 | 10 | 膨胀(重量级锁定) | 143 | | 空,不需要记录信息 | 11 | GC标记 | 144 | | 偏向线程ID、偏向时间戳、对象分代年龄 | 01 | 可偏向 | 145 | 146 | 147 | 148 | 对象头的另外一部分是类型指针,即对象指向他的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪一个类的实例。并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说,查找对象的元数据信息不一定要经过对象本身。另外如果对象是一个java数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通java 对象的元数据信息确定java 对象的大小,但是从java 数组中的元数据中确无法确定数组的大小。 149 | 150 | 接下来的实例数据部分是对象真正存储的有效信息,也是是在程序代码中所定义的各种字段内容。无论是从父类继承下来的还是从子类定义的,都需要记录下来。这部分的存储顺序会受到虚拟机分配参数策略(FieldAllocationStyle)和字段在java源码中定义顺序的影响。HotSpot虚拟机默认的分配策略为 longs/doubles 、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers),从分配策略中可以看出,相同宽度的字段总是被分配到一起。在满足这个前提条件的情况下,父类中定义的变量会出现在子类之前。如果CompactFields参数值为true,那么子类中较窄的变量也可能会插入到父类变量的空隙只中。 151 | 152 | 第三部分对齐填充不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求起始对象的起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数,因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。 153 | 154 | ## 对象的强、软、弱和虚引用 155 | 156 | **强引用(StrongReference)**:强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。 157 | 158 | **软引用(SoftReference)**:如果一个对象只具有软引用,则内存空间足够,垃圾回收器 159 | 160 | 就不会回收它;如果内存空间不足了,就会回收这些对象的内存。 161 | 162 | **弱引用(WeakReference)**:弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。 163 | 164 | **虚引用(PhantomReference)**:虚引用顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。 165 | 166 | 167 | ## 内存溢出原因 168 | 169 | 对于Java运行时数据区来说,都会因为申请内存失败而抛出OutOfMemoryError 170 | 171 | * Java堆溢出:只要不断的创建对象,并保证GC Roots到对象之间有可达路径来避免垃圾收集器回收这些对象,那么在对象数量达到最大堆的容量限制之后就会抛出OutOfMemoryError。 172 | * 虚拟机栈和本地方法栈溢出:一般情况下在线程过多的时候,每个线程分配的栈深度不够的时候有可能抛出OutOfMemoryError。 173 | * 方法区和运行时常量池溢出:运行时生成大量类。 174 | * 对于CMS收集器来说:如果在垃圾收集中花费了太多时间,CMS收集器会抛出OutOfMemoryError:如果超过98%的总时间花在垃圾收集上,并且回收的堆少于2%,那么抛出OutOfMemoryError 。 175 | 176 | ### 内存泄漏 177 | 178 | Java中的内存泄露,广义并通俗的说,就是:**不再会被使用的对象的内存不能被回收,就是内存泄露**。 179 | 180 | Java的内存管理就是对象的分配和释放问题。在 Java 中,程序员需要通过关键字 new 为每个对象申请内存空间 (基本类型除外),所有的对象都在堆 (Heap)中分配空间。另外,对象的释放是由 GC 决定和执行的。在 Java 中,内存的分配是由程序完成的,而内存的释放是由 GC 完成的,这种收支两条线的方法确实简化了程序员的工作。但同时,它也加重了JVM的工作。这也是 Java 程序运行速度较慢的原因之一。因为,GC 为了能够正确释放对象,GC 必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC 都需要进行监控。监视对象状态是为了更加准确地、及时地释放对象,而**释放对象的根本原则就是该对象不再被引用**。Java使用有向图的方式进行内存管理,可以消除引用循环的问题,例如有三个对象,相互引用,只要它们和根进程不可达的,那么GC也是可以回收它们的。这种方式的优点是管理内存的精度很高,但是效率较低。 181 | 182 | Java内存泄漏的根本原因是:**长生命周期的对象持有短生命周期对象的引用**就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是Java中内存泄漏的发生场景。 183 | 具体主要有如下几大类: 184 | 185 | #### 静态集合类引起内存泄漏 186 | 像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。在下面的个例子中,循环申请Object 对象,并将所申请的对象放入一个Vector 中,如果仅仅释放引用本身(o=null),那么Vector 仍然引用该对象,所以这个对象对GC 来说是不可回收的。因此,如果对象加入到Vector 后,还必须从Vector 中删除,最简单的方法就是将Vector对象设置为null。 187 | 188 | ``` 189 | Static Vector v = new Vector(10); 190 | 191 | for (int i = 0; i < 100; i++) { 192 | Object o = new Object(); 193 | v.add(o); 194 | o = null; 195 | } 196 | ``` 197 | 198 | #### 各种连接 199 | 比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close() 方法将其连接关闭,否则是不会自动被GC 回收的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。这种情况下一般都会在try 里面去的连接,在finally里面释放连接。 200 | 201 | #### 内部类和外部模块的引用 202 | 内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。此外程序员还要小心外部模块不经意的引用,例如程序员A 负责A 模块,调用了B 模块的一个方法如: 203 | 204 | >public void registerMsg(Object b); 205 | 206 | 这种调用就要非常小心了,传入了一个对象,很可能模块B就保持了对该对象的引用,这时候就需要注意模块B 是否提供相应的操作去除引用。 207 | 208 | #### 单例模式 209 | 210 | 不正确使用单例模式是引起内存泄漏的一个常见问题,单例对象在初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部的引用,那么这个对象将不能被JVM正常回收,导致内存泄漏。 211 | 212 | 在开发中不能完全避免内存泄漏,关键要在发现有内存泄漏的时候能用好的测试工具迅速定位问题的所在。市场上已有几种专业检查 Java 内存泄漏的工具,它们的基本工作原理大同小异,都是通过监测 Java 程序运行时,所有对象的申请、释放等动作,将内存管理的所有信息进行统计、分析、可视化。开发人员将根据这些信息判断程序是否有内存泄漏问题。这些工具包括 Optimizeit Profiler、JProbe Profiler、JinSight、Rational 公司的 Purify 等。 213 | 214 | 215 | 此外,最基本的建议就是尽早释放无用对象的引用,大多数程序员在使用临时变量的时候,都是让引用变量在退出活动域后,自动设置为 null 。在使用这种方式时候,必须特别注意一些复杂的对象图,例如数组、列、树、图等,这些对象之间有相互引用关系较为复杂。对于这类对象,GC 回收它们一般效率较低。如果程序允许,尽早将不用的引用对象赋为null。另外在确认一个对象无用后,将其所有引用显式的置为null。 216 | 217 | 218 | -------------------------------------------------------------------------------- /notes/JVM/内存回收与垃圾收集器.md: -------------------------------------------------------------------------------- 1 | 2 | * [为什么要进行垃圾回收](#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E8%BF%9B%E8%A1%8C%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6) 3 | * [什么样的对象需要回收](#%E4%BB%80%E4%B9%88%E6%A0%B7%E7%9A%84%E5%AF%B9%E8%B1%A1%E9%9C%80%E8%A6%81%E5%9B%9E%E6%94%B6) 4 | * [典型的垃圾回收算法。](#%E5%85%B8%E5%9E%8B%E7%9A%84%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E7%AE%97%E6%B3%95) 5 | * [Mark\-Sweep(标记\-清除)算法](#mark-sweep%E6%A0%87%E8%AE%B0-%E6%B8%85%E9%99%A4%E7%AE%97%E6%B3%95) 6 | * [复制算法](#%E5%A4%8D%E5%88%B6%E7%AE%97%E6%B3%95) 7 | * [Mark\-Compact(标记\-整理)算法](#mark-compact%E6%A0%87%E8%AE%B0-%E6%95%B4%E7%90%86%E7%AE%97%E6%B3%95) 8 | * [Generational Collection(分代收集)算法](#generational-collection%E5%88%86%E4%BB%A3%E6%94%B6%E9%9B%86%E7%AE%97%E6%B3%95) 9 | * [垃圾收集器](#%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E5%99%A8) 10 | * [Serial/Serial Old](#serialserial-old) 11 | * [ParNew](#parnew) 12 | * [Parallel Scavenge](#parallel-scavenge) 13 | * [Parallel Old](#644-parallel-old) 14 | * [CMS](#645-cms) 15 | * [G1收集器](#g1%E6%94%B6%E9%9B%86%E5%99%A8) 16 | * [如何减少GC的次数](#%E5%A6%82%E4%BD%95%E5%87%8F%E5%B0%91gc%E7%9A%84%E6%AC%A1%E6%95%B0) 17 | * [触发GC(Garbage Collector)的条件](#%E8%A7%A6%E5%8F%91gcgarbage-collector%E7%9A%84%E6%9D%A1%E4%BB%B6) 18 | 19 | 20 | 21 | GC是垃圾收集的意思(Gabage Collection),Java提供的GC功能可以自动也只能自动地回收堆内存中不再使用的对象,释放资源,但是Java语言没有提供释放已分配内存的显式操作方法(GC方法只是通知,不是立即执行)。 22 | 23 | 垃圾回收是一种动态存储管理技术,它自动地释放不再被程序引用的对象,当一个对象不再被引用的时候,按照特定的垃圾收集算法来实现资源自动回收的功能。 24 | 25 | ### 为什么要进行垃圾回收 26 | 27 | 在Java中,当没有对象引用指向原先分配给 某个对象的内存时,该内存便成为垃圾。 28 | 垃圾回收能自动释放内存空间,减轻编程的负担,JVM的一个系统级线程会自动释放该内存块。垃圾回收意味着程序不再需要的对象是"无用信息",这些信息将被丢弃。当一个对象不再被引用的时候,内存回收它占领的空间,以便空间被后来的新对象使用。 29 | 事实上,除了释放没用的对象,垃圾回收也可以清除内存记录碎片。由于创建对象和垃圾回收器释放丢弃对象所占的内存空间,内存会出现碎片。碎片是分配给对象的内存块之间的空闲内存洞。碎片整理将所占用的堆内存移到堆的一端,JVM将整理出的内存分配给新的对象。 30 | 31 | 32 | ### 什么样的对象需要回收 33 | 34 | **引用计数法** :每个对象上都有一个引用计数,对象每被引用一次,引用计数器就+1,对象引用被释放,引用计数器-1,直到对象的引用计数为0,对象就标识可以回收。但是这个算法有明显的缺陷,对于循环引用的情况下,循环引用的对象就不会被回收。 35 | 36 | **可达性分析法** :可达性算法是通过一个称为 GC Roots的对象向下搜索,整个搜索路径就称为引用链,当一个对象到 GC Roots 没有任何引用链JVM就认为该对象是可以被回收的。[可达性分析的问题](https://blog.csdn.net/tjiyu/article/details/53982412)主要在于消耗大量时间和GC停顿。 37 | 38 | **可以作为GC Roots的对象:** 39 | 40 | > 被启动类(bootstrap加载器)加载的类和创建的对象 41 | 42 | > JVM运行时方法区类静态变量(static)引用的对象 43 | 44 | > JVM运行时方法去常量池引用的对象 45 | 46 | > JVM当前运行线程中的虚拟机栈变量表引用的对象 47 | 48 | > 本地方法栈中(JNI)引用的对象 49 | 50 | ## 典型的垃圾回收算法。 51 | 52 | ### Mark-Sweep(标记-清除)算法 53 | 54 | Mark-Sweep(标记-清除)算法是最基础的垃圾回收算法,它分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。具体过程如下图所示: 55 | 56 | ![](media/29eee2766457155d4c29cf60c359e42f.jpg) 57 | 58 | 从图中可以很容易看出标记-清除算法实现起来比较容易,但是有一个比较严重的问题就是容易产生**内存碎片**,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。 59 | 60 | ### 复制算法 61 | 62 | 为了解决Mark-Sweep算法的缺陷,复制算法就被提了出来。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。具体过程如下图所示: 63 | 64 | ![](media/67dc710ec041a9b83949ea068000a55b.jpg) 65 | 66 | 这种算法虽然实现简单,运行高效且不容易产生内存碎片,但是却对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。很显然,复制算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么复制算法的效率将会大大降低。 67 | 68 | ### Mark-Compact(标记-整理)算法 69 | 70 | 为了解决复制算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。具体过程如下图所示: 71 | 72 | ![](media/dbe4acc646b8383a307fa50c842b9a41.jpg) 73 | 74 | ### Generational Collection(分代收集)算法 75 | 76 | 分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。 77 | 78 | 在新生代中,使用“停止-复制”算法进行清理,将新生代内存分为2部分,1部分 79 | Eden区较大,1部分Survivor比较小,并被划分为两个等量的部分。每次进行清理时,将Eden区和一个Survivor中仍然存活的对象拷贝到另一个Survivor中,然后清理掉Eden和刚才的Survivor。这里也可以发现,停止复制算法中,用来复制的两部分并不总是相等的(传统的停止复制算法两部分内存相等,但新生代中使用1个大的Eden区和2个小的Survivor区来避免这个问题) 80 | 由于绝大部分的对象都是短命的,甚至存活不到Survivor中,所以,Eden区与Survivor的比例较大,HotSpot默认是8:1,即分别占新生代的80%,10%,10%。如果一次回收中,Survivor+Eden中存活下来的内存超过了10%,则需要将一部分对象分配到老年代。用-XX:SurvivorRatio参数来配置Eden区域Survivor区的容量比值,默认是8,代表Eden:Survivor1:Survivor2=8:1:1。 81 | 82 | 老年代存储的对象比年轻代多得多,而且不乏大对象,对老年代进行内存清理时,如果使用停止-复制算法,则相当低效。一般,老年代用的算法是标记-整理算法,即:标记出仍然存活的对象(存在引用的),将所有存活的对象向一端移动,以保证内存的连续。 83 | 84 | 在发生MinorGC时,虚拟机会检查每次晋升进入老年代的大小是否大于老年代的剩余空间大小,如果大于,则直接触发一次FullGC,否则,就查看是否设置了-XX:+HandlePromotionFailure(允许担保失败),如果允许,则只会进行MinorGC,此时可以容忍内存分配失败;如果不允许,则仍然进行FullGC(这代表着如果设置-XX:+Handle PromotionFailure,则触发MinorGC就会同时触发FullGC,哪怕老年代还有很多内存,所以,最好不要这样做)。 85 | 86 | 注意,在堆区之外还有一个代就是永久代(PermanetGeneration),它用来存储class类、常量、方法描述等。永久代的回收并不是必须的,可以通过参数来设置是否对类进行回收。对永久代的回收主要回收两部分内容:废弃常量和无用的类。常量的回收很简单,没有引用了就可以被回收。对于无用的类进行回收,必须保证3点: 87 | 88 | > 类的所有实例都已经被回收 89 | > 加载类的ClassLoader已经被回收 90 | > 类对象的Class对象没有被引用(即没有通过反射引用该类的地方) 91 | 92 | 垃圾收集器 93 | -------------- 94 | 95 | 在GC机制中,起重要作用的是垃圾收集器,垃圾收集器是GC算法的具体实现,Java虚拟机规范中对于垃圾收集器没有任何规定,所以不同厂商实现的垃圾收集器各不相同,这里需要明确一点,就是在新生代采用的停止复制算法中,“停止(Stop-the-world)”的意义是在回收内存时,需要暂停其他所有线程的执行。这个是很低效的,现在的各种新生代收集器越来越优化这一点,但仍然只是将停止的时间变短,并未彻底取消停止。 96 | 97 | JDK7/8后,HotSpot虚拟机所有收集器及组合(连线),如下图: 98 | 99 | ![](media/cbd775203253123eceb4fb14bd9b1984.png) 100 | 101 | **并发垃圾收集和并行垃圾收集的区别**: 102 | 103 | **并行(Parallel)收集**指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。如ParNew、Parallel Scavenge、Parallel Old。 104 | 105 | **并发(Concurrent)收集**指用户线程与GC线程同时执行(不一定是并行,可能交替,但总体上是在同时执行的),不需要停顿用户线程(其实在CMS中用户线程还是需要停顿的,只是非常短,GC线程在另一个CPU上执行)。 106 | 107 | 垃圾收集器期望的目标:**停顿时间:**停顿时间越短就适合需要与用户交互的程序,良好的响应速度能提升用户体验。**吞吐量**:高吞吐量则可以高效率地利用CPU时间,尽快完成运算的任务,主要适合在后台计算而不需要太多交互的任务。 108 | 109 | ### Serial/Serial Old 110 | 111 | Serial/SerialOld收集器是最基本最古老的收集器,是HotSpot在Client模式下默认的新生代收集器,它是一个单线程收集器,并且在它进行垃圾收集时,必须暂停所有用户线程。Serial收集器是针对新生代的收集器,采用的是Copying算法。 112 | 113 | SerialOld收集器是针对老年代的收集器,采用的是Mark-Compact算法。它的优点是现简单高效,但是缺点是会给用户带来停顿(Stop The World:JVM在后台自动发起和自动完成的,在用户不可见的情况下,把用户正常的工作线程全部停掉)。 114 | 115 | ![](media/8a3ccf092d0e3a55a649ffcf76341246.png) 116 | 117 | ### ParNew 118 | 119 | ParNew垃圾收集器是Serial收集器的多线程版本。ParNew/Serial Old组合收集器运行示意图如下: 120 | 121 | ![](media/4c36c2cdc1b8256201d6c9be8e6cac6c.png) 122 | 123 | 在**Server模式下**,ParNew收集器是一个非常重要的收集器,因为除Serial外,目前只有它能与CMS收集器配合工作。但在单个CPU环境中,不会比Serail收集器有更好的效果,因为存在线程交互开销。 124 | 125 | ### Parallel Scavenge 126 | 127 | Parallel Scavenge收集器是一个新生代的多线程收集器(并行收集器),它在回收期间不需要暂停其他用户线程,其采用的是Copying算法。 128 | 129 | Parallel Scavenge收集器的目标是达一个可控制的吞吐量(Throughput),即减少垃圾收集时间,让用户代码获得更长的运行时间;,而CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间。 130 | 131 | Parallel Scavenge关注CPU吞吐量,即CPU用于运行用户代码的时间与CPU总消耗时间的比值,比如:JVM运行100分钟,其中运行用户代码99分钟,垃圾收集1分钟,则吞吐量是99%,这种收集器能最高效率的利用CPU,适合运行后台运算。 132 | 133 | ### Parallel Old 134 | 135 | Parallel Old是Parallel Scavenge收集器的老年代版本(并行收集器),使用多线程和Mark-Compact算法(与Serial Old不同,这里的整理是Summary(汇总)和Compact(压缩),汇总的意思就是将幸存的对象复制到预先准备好的区域,而不是像Sweep(清理)那样清理废弃的对象)算法,在Parallel Old执行时,仍然需要暂停其它线程。 136 | 137 | Parallel Old在多核计算中很有用。Parallel Old出现后(JDK 1.6),与Parallel Scavenge配合有很好的效果,充分体现Parallel Scavenge收集器吞吐量优先的效果。 138 | 139 | ### CMS 140 | 141 | CMS(Concurrent Mark Sweep)收集器:老年代收集器,致力于**获取最短回收停顿时间(即缩短垃圾回收的时间)**,使用标记清除算法,多线程,优点是并发收集(用户线程可以和GC线程同时工作),停顿小。 142 | 143 | **CMS收集器运作过程分为四步:** 144 | 145 | >1.初始标记(CMS initial mark):仅标记一下GC Roots能直接关联到的对象,速度很快,但需要"Stop The World"。 146 | 147 | >2.并发标记(CMS concurrent mark):进行GC Roots Tracing的过程,刚才产生的集合中标记出存活对象,应用程序也在运行,并不能保证可以标记出所有的存活对象 148 | 149 | >3.重新标记(CMS remark):为了修正并发标记期间因用户程序继续运作而导致标记变动的那一部分对象的标记记录,需要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短,采用多线程并行执行来提升效率 150 | 151 | > 4.并发清除(CMS concurrent sweep):回收所有的垃圾对象 152 | 153 | 整个过程中耗时最长的并发标记和并发清除都可以与用户线程一起工作,所以总体上说,CMS收集器的内存回收过程与用户线程一起并发执行.CMS收集器运行示意图如下: 154 | 155 | ![https://img-blog.csdn.net/20170102225017372](media/e3685e42bfc07a6cd7689a7c95bf9e61.png) 156 | 157 | **CMS收集器3个明显的缺点** 158 | 159 | > **对CPU资源非常敏感**:并发收集虽然不会暂停用户线程,但因为占用一部分CPU资源,还是会导致应用程序变慢,总吞吐量降低。CMS的默认收集线程数量是=(CPU数量+3)/4,当CPU数量多于4个,收集线程占用的CPU资源多于25%,对用户程序影响可能较大;不足4个时,影响更大,可能无法接受。 160 | 161 | > **无法处理浮动垃圾**:浮动垃圾(Floating Garbage)是指在并发清除时,用户线程新产生的垃圾,称为浮动垃圾。这使得并发清除时需要预留一定的内存空间,不能像其他收集器在老年代几乎填满再进行收集,也要可以认为CMS所需要的空间比其他垃圾收集器大 162 | 163 | >**可能出现"Concurrent Mode Failure"失败** : "ConcurrentModeFailure"失败是指如果CMS预留内存空间无法满足程序需要,就会出现一次"ConcurrentModeFailure"失败,这时JVM启用后备预案:临时启用SerailOld收集器,而导致另一次Full GC的产生 164 | 165 | > **产生大量内存碎片**:由于CMS基于"标记-清除"算法,清除后不进行压缩操作,产生大量不连续的内存碎片会导致分配大内存对象时,无法找到足够的连续内存,从而需要提前触发另一次Full GC动作 166 | 167 | 168 | 使得CMS出现上面这种情况时不进行FullGC,而开启内存碎片的合并整理过程,但合并整理过程无法并发,停顿时间会变长。默认开启(但不会进行,结合下面的CMSFullGsBeforeCompaction)。 169 | 170 | > **"-XX:+CMSFullGCsBeforeCompaction"** 171 | 172 | 设置执行多少次不压缩的FullGC后,来一次压缩整理;为减少合并整理过程的停顿时间,默认为0,也就是说每次都执行FullGC,不会进行压缩整理; 173 | 174 | 总体来看,与ParallelOld垃圾收集器相比,CMS减少了执行老年代垃圾收集时应用暂停的时间,但却增加了新生代垃圾收集时应用暂停的时间、降低了吞吐量而且需要占用更大的堆空间。 175 | 176 | ### G1收集器 177 | 178 | G1(Garbagefirst)收集器是最先进的收集器之一,是面向服务端的垃圾收集器针对具有大内存、多处理器的机器,最主要的应用是为需要低GC延迟,并具有大堆的应用程序提供解决方案。与其他收集器相比,G1收集器有如下优点: 179 | 180 | **并行与并发**:有些收集器需要停顿的过程G1仍然可以通过并发的方式让用户程序继续执行 181 | 182 | **分代收集**:可以不使用其他收集器配合管理整个Java堆 183 | 184 | **空间整合**:结合多种垃圾收集算法,空间整合,不产生碎片。从整体看,是基于标记-整理算法,从局部(两个Region间)看,是基于复制算法,这是一种类似火车算法的实现 185 | 186 | **可预测的停顿**:低停顿的同时实现高吞吐量,G1除了降低停顿外,还能建立可预测的停顿时间模型,可以明确指定M毫秒时间片内,垃圾收集消耗的时间不超过N毫秒。 187 | 188 | G1中也有分代的概念,不过使用G1收集器时,Java堆的内存布局与其他收集器有很大的差别,它将整个Java堆划分为多个大小相等的独立区域(Region),G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划的避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里垃圾堆积的价值大小(回收所获得的空间大小以及回收所需要的时间的经验值),在后台维护一个优先列表,每次优先收集价值最大的那个Region。这样就保证了在有限的时间内尽可能提高效率。 189 | 190 | 191 | G1收集器运行示意图如下: 192 | ![https://img-blog.csdn.net/20170102225017799](media/68c96152320fc0dcf16b54856b9a4318.png) 193 | 194 | **G1可以建立可预测的停顿时间模型,是因为**以下几点: 195 | 196 | > 可以有计划地避免在Java堆的进行全区域的垃圾收集。 197 | 198 | > G1跟踪各个Region获得其收集价值大小,在后台维护一个优先列表。 199 | 200 | > 每次根据允许的收集时间,优先回收价值最大的Region(名称Garbage-First的由来)。 201 | 202 | 203 | 204 | **一个对象被不同区域引用的问题**:一个Region不可能是孤立的,一个Region中的对象可能被其他任意Region中对象引用,判断对象存活时,是否需要扫描整个Java堆才能保证准确。在其他的分代收集器,也存在这样的问题(而G1更突出):回收新生代也不得不同时扫描老年代?这样的话会降低MinorGC的效率; 205 | 解决方法:无论G1还是其他分代收集器,JVM都是使用Remembered Set来避免全局扫描:每个Region都有一个对应的Remembered Set。每次Reference类型数据写操作时,都会产生一个Write Barrier暂时中断操作。然后检查将要写入的引用指向的对象是否和该Reference类型数据在不同的Region(其他收集器:检查老年代对象是否引用了新生代对象)。如果不同,通过CardTable把相关引用信息记录到引用指向对象的所在Region对应的RememberedSet中。当进行垃圾收集时,在GC根节点的枚举范围加入RememberedSet,就可以保证不进行全局扫描,也不会有遗漏。 206 | 207 | 详情参考:https://blog.csdn.net/tjiyu/article/details/53983650 208 | 209 | 210 | 211 | ### 如何减少GC的次数 212 | 213 | **对象不用时最好显示置为NULL**:一般而言,为NULL的对象都会被作为垃圾处理,所以将不用的对象置为NULL,有利于GC收集器判定垃圾,从而提高了GC的效率 214 | 215 | **尽量少使用System.gc()**:此函数建议JVM进行主GC,会增加主GC的频率,增加了间接性停顿的次数 216 | 217 | **尽量少使用静态变量**:静态变量属于全局变量,不会被GC回收,会一直占用内存 218 | 219 | **尽量使用StringBuffer,而不使用String来累加字符串** 220 | 221 | **分散对象创建或删除的时间**:集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM在这种情况下只能进行主GC以回收内存,从而增加主GC的频率 222 | 223 | **能用基本类型入int就不用对象Intege** 224 | 225 | 226 | ### 触发GC(Garbage Collector)的条件 227 | 228 | 1.当应用程序空闲时,即没有应用线程在运行时,GC会被调用。因为GC在优先级最低的线程中进行,所以当应用忙时,GC线程就不会被调用,但以下条件除外。 229 | 230 | 2.Java堆内存不足时,GC会被调用。当应用线程在运行,并在运行过程中创建新对象,若这时内存空间不足,JVM就会强制地调用GC线程,以便回收内存用于新的分配。若GC一次之后仍不能满足内存分配的要求,JVM会再进行两次GC作进一步的尝试,若仍无法满足要求,则 JVM将报“out of memory”的错误,Java应用将停止。 231 | 232 | 注意:GC的回收时间是不确定的,即使你显示的调用的System.gc()。因为和线程优先级有关。使用了finalize()方法之后,GC是在这个方法执行之后的下一次进行垃圾的回收。 233 | -------------------------------------------------------------------------------- /notes/JVM/类加载与对象分配.md: -------------------------------------------------------------------------------- 1 | * [Java内存分配机制](#java%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%E6%9C%BA%E5%88%B6) 2 | * [年轻代](#%E5%B9%B4%E8%BD%BB%E4%BB%A3) 3 | * [年老代](#%E5%B9%B4%E8%80%81%E4%BB%A3) 4 | * [内存分配与回收策略](#%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%E4%B8%8E%E5%9B%9E%E6%94%B6%E7%AD%96%E7%95%A5) 5 | * [Java类加载机制](#java%E7%B1%BB%E5%8A%A0%E8%BD%BD%E6%9C%BA%E5%88%B6) 6 | * [类加载过程](#%E7%B1%BB%E5%8A%A0%E8%BD%BD%E8%BF%87%E7%A8%8B) 7 | * [加载](#%E5%8A%A0%E8%BD%BD) 8 | * [验证](#%E9%AA%8C%E8%AF%81) 9 | * [准备](#%E5%87%86%E5%A4%87) 10 | * [解析阶段](#%E8%A7%A3%E6%9E%90%E9%98%B6%E6%AE%B5) 11 | * [双亲委派机制](#%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E6%9C%BA%E5%88%B6) 12 | 13 | 14 | 15 | Java内存分配机制 16 | -------------------- 17 | 这里所说的内存分配,主要指的是在堆上的分配。Java内存分配和回收的机制概括的说,就是:分代分配,分代回收。对象将根据存活的时间被分为:年轻代(Young Generation)、年老代(Old Generation)、永久代(Permanent Generation,也就是方法区)。这样分配的依据主要有两点:**大多数对象会很快变得不可达以及只有很少的由老对象(创建时间较长的对象)指向新生对象的引用** 18 | 19 | ### 年轻代 20 | 21 | **年轻代(Young Generation)**:对象被创建时,内存的分配首先发生在年轻代(大对象可以直接被创建在年老代),大部分的对象在创建后很快就不再使用,因此很快变得不可达,于是被年轻代的GC机制清理掉(IBM的研究表明,98%的对象都是很快消亡的),这个GC机制被称为MinorGC或叫Young GC。注意,MinorGC并不代表年轻代内存不足,它事实上只表示在Eden区上的GC。 22 | 23 | 年轻代上的内存分配是这样的,年轻代可以分为3个区域:Eden区(用来表示内存首次分配的区域)和两个存活区(Survivor0 、Survivor1)。内存分配过程如下: 24 | 25 | ![http://www.importnew.com/wp-content/uploads/2012/12/Figure-3-Before-After-a-GC.png](media/82feee6ec1556e003f19106bf0544c16.png) 26 | 27 | 绝大多数刚创建的对象会被分配在Eden区,其中的大多数对象很快就会消亡。Eden区是连续的内存空间。 28 | 29 | 最初一次,当Eden区满的时候,执行MinorGC,将消亡的对象清理掉,并将剩余的对象复制到一个存活区Survivor0(此时,Survivor1是空白的,两个Survivor总有一个是空白的。 30 | 31 | 紧接着等到下次Eden区满了,再执行一次MinorGC,将消亡的对象清理掉,将存活的对象复制到Survivor1中,然后清空Eden区。同时将Survivor0中消亡的对象清理掉,将其中可以晋级的对象晋级到Old区,将存活的对象也复制到Survivor1区,然后清空Survivor0区。 32 | 33 | 当两个存活区切换了几次(**HotSpot虚拟机默认15次**,用-XX:MaxTenuringThreshold控制,大于该值进入老年代,但这只是个最大值,并不代表一定是这个值)之后,仍然存活的对象(一小部分),将被复制到老年代。 34 | 35 | 从上面的过程可以看出,Eden区是连续的空间,且Survivor总有一个为空。经过一次GC和复制,一个Survivor中保存着当前还活着的对象,而Eden区和另一个Survivor区的内容都不再需要了,可以直接清空,到下一次GC时,两个Survivor的角色再互换。因此,这种方式分配内存和清理内存的效率都极高。 36 | 37 | 在Eden区,HotSpot虚拟机使用了两种技术来加快内存分配。分别是bump-the-pointer和TLAB(Thread-LocalAllocationBuffers),这两种技术的做法分别是:由于Eden区是连续的,因此bump-the-pointer技术的核心就是跟踪最后创建的一个对象,在 38 | 39 | 对象创建时,只需要检查最后一个对象后面是否有足够的内存即可,从而大大加快内存分配速度。而对于TLAB技术是对于多线程而言的,将Eden区分为若干段,每个线程使用独立的一段,避免相互影响。TLAB结合bump-the-pointer技术,将保证每个线程都使用Eden区的一段,并快速的分配内存。 40 | 41 | ### 年老代 42 | 43 | **年老代(OldGeneration)**:对象如果在年轻代存活了足够长的时间而没有被清理掉(即在几次YoungGC后存活了下来),则会被复制到年老代,年老代的空间一般比年轻代大,能存放更多的对象,在年老代上发生的GC次数也比年轻代少。当年老代内存不足时,将执行MajorGC,也叫 FullGC。可以使用-XX:+UseAdaptiveSizePolicy开关来控制是否采用动态控制策略,如果动态控制,则动态调整Java堆中各个区域的大小以及进入老年代的年龄。 44 | 45 | 如果对象比较大(比如长字符串或大数组),Young空间不足,则大对象会直接分配到老年代上(大对象可能触发提前GC,应少用,更应避免使用短命的大对象)。用-XX:PretenureSizeThreshold来控制直接升入老年代的对象大小,大于这个值的对象会直接分配在老年代上。 46 | 47 | 可能存在年老代对象引用新生代对象的情况,如果需要执行YoungGC,则可能需要查询整个老年代以确定是否可以清理回收,这显然是低效的。解决的方法是,年老代中维护一个512byte的块(cardtable),所有老年代对象引用新生代对象的记录都记录在这里。YoungGC时,只要查这里即可,不用再去查全部老年代,因此性能大大提高。 48 | 49 | 50 | 51 | ### 内存分配与回收策略 52 | 53 | 1.对象优先在Eden分配 54 | 55 | 2.大对象直接进入老年代。 56 | ![](media/ee8964bfa715e52290b49f632e3e46af.png) 57 | 58 | 3.长期存活的对象将进入老年代 59 | ![](media/2a67943e48b2b125ab942615a2ba7fd5.png) 60 | 61 | 4.动态对象年龄判定 62 | 如果在Survivor空间中相同年龄所有对象大小总和大于Survivor空间的一半,(比如说Survivor空间大小为1M,而有两个年龄为1的对象大小和是大于512K的),那么年龄大于等于该年龄的对象都可以直接进入到老年代。 63 | 64 | 5.空间分配担保 65 | 在进行MinorGC前,虚拟机会查看HandlePromotionFailure设置值是否为True,那么说明允许担保失败(会检查虚拟机老年代剩余空间的大小与平均晋升到老年代空间的大小,如果大于说明“可能”是安全的),为True那么进行一次MinorGC,如果此时刻发现进入到老年代的新对象的大小是大于老年代的剩余空间,说明担保失败了,只能进行一次FullGC清除老年代的剩余空间。 66 | 67 | 68 | 69 | 70 | Java类加载机制 71 | ------------------ 72 | 73 | ### 类加载过程 74 | 75 | **类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。它们开始的顺序如下图所示**: 76 | 77 | ![https://img-blog.csdn.net/20140105211344671](media/4f883fb0edf9b8876c945a1e7ba702d3.png) 78 | 79 | 其中**类加载的过程包括了加载、验证、准备、解析、初始化五个**阶段。在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的**运行时绑定**(也成为动态绑定或晚期绑定)。另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。 80 | 81 | Java中的绑定:绑定指的是把一个方法的调用与方法所在的类(方法主体)关联起来,对java来说,绑定分为静态绑定和动态绑定。 82 | 83 | **静态绑定**:即前期绑定。在程序执行前方法已经被绑定,此时由编译器或其它连接程序实现。针对java,简单的可以理解为程序编译期的绑定。java当中的方法只有final,static,private和构造方法是前期绑定的。 84 | 85 | **动态绑定**:即晚期绑定,也叫运行时绑定。在运行时根据具体对象的类型进行绑定。在java中,几乎所有的方法都是后期绑定的。 86 | 87 | ### 加载 88 | 加载时类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情: 89 | 90 | * 通过一个类的全限名来获取定义此类的二进制节流。 91 | * 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 92 | * 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。 93 | 94 | 加载阶段完成后,**虚拟机外部的二进制字节流**就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据。 95 | 96 | ### 验证 97 | 验证的目的是为了确保Class文件中的字节流包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身的安全。不同的虚拟机对类验证的实现可能会有所不同,但大致都会完成以下四个阶段的验证:文件格式的验证、元数据的验证、字节码验证和符号引用验证。 98 | 99 | **文件格式的验证**:验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理,该验证的主要目的是保证输入的字节流能正确地解析并存储于方法区之内。经过该阶段的验证后,字节流才会进入内存的方法区中进行存储,后面的三个验证都是基于方法区的存储结构进行的。 100 | 101 | – [x] 是否以魔数0xCAFEBABE开头 102 | – [x] 主次版本号是否在当前虚拟机处理范围之内 103 | – [x] 常量池中的常量是否有不被支持的常量类型 104 | – [x] 指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量 105 | – [x] CONSTANT*Utf8*info型的常量中是否有不符合UTF8编码的数据 106 | – [x] Class文件中各个部分及文件本身是否有被删除的或附加的其他信息 107 | 108 | **元数据验证**:对类的元数据信息进行语义校验(其实就是对类中的各数据类型进行语法校验),保证不存在不符合Java语法规范的元数据信息。 109 | 110 | – [x] 这个类是否有父类 111 | – [x] 这个类的父类是否继承了不准许被继承的类 112 | – [x] 如果这个类不是抽象类,是否实现了其父类或者接口之中要求实现的所有方法 113 | – [x] 类中的字段方法是否与父类产生矛盾 114 | 115 | **字节码验证**:该阶段验证的主要工作是进行数据流和控制流分析,对类的方法体进行校验分析,以保证被校验的类的方法在运行时不会做出危害虚拟机安全的行为。 116 | 117 | – [x] 保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作 118 | – [x] 保证跳转指令不会跳转到方法体以外的字节码指令上 119 | – [x] 保证方法体重的类型转换是有效的 120 | 121 | **符号引用验证**:这是最后一个阶段的验证,它发生在虚拟机将符号引用转化为直接引用的时候(解析阶段中发生该转化,后面会有讲解),主要是对类自身以外的信息(常量池中的各种符号引用)进行匹配性的校验。 122 | 123 | – [x] 符号引用中通过字符串描述的全限定名是否找到相应的类 124 | – [x] 在指定的类中是否存在符合方法的字段描述符以及简单名称说描述的方法和字段 125 | – [x] 符号引用中的类、字段、方法的访问性是否被当前类访问 126 | 127 | ### 准备 128 | 准备阶段是正式为类变量分配内存并设置类变量初始值(被static修饰的变量)的阶段,这些变量所使用的内存都将在方法区中进行分配。 129 | 130 | 这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。 131 | 132 | 这里还需要注意如下几点: 133 | 134 | * 对基本数据类型来说,对于类变量(static)和全局变量,如果不显式地对其赋值而直接使用,则系统会为其赋予默认的零值,而对于局部变量来说,在使用前必须显式地为其赋值,否则编译时不通过。 135 | 136 | * 对于同时被static和final修饰的常量,必须在声明的时候就为其显式地赋值,否则编译时不通过;而只被final修饰的常量则既可以在声明时显式地为其赋值,也可以在类初始化时显式地为其赋值,总之,在使用前必须为其显式地赋值,系统不会为其赋予默认零值。 137 | 138 | * 对于引用数据类型reference来说,如数组引用、对象引用等,如果没有对其进行显式地赋值而直接使用,系统都会为其赋予默认的零值,即null。 139 | 140 | * 如果在数组初始化时没有对数组中的各元素赋值,那么其中的元素将根据对应的数据类型而被赋予默认的零值。 141 | 142 | ### 解析阶段 143 | 解析阶段是虚拟机将常量池中的符号引用转化为直接引用的过程。在Class类文件结构一文中已经比较过了符号引用和直接引用的区别和关联,这里不再赘述。前面说解析阶段可能开始于初始化之前,也可能在初始化之后开始,虚拟机会根据需要来判断,到底是在类被加载器加载时就对常量池中的符号引用进行解析(初始化之前),还是等到一个符号引用将要被使用前才去解析它(初始化之后)。 144 | 145 | 对同一个符号引用进行多次解析请求时很常见的事情,虚拟机实现可能会对第一次解析的结果进行缓存(在运行时常量池中记录直接引用,并把常量标示为已解析状态),从而避免解析动作重复进行。 146 | 147 | 解析动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行: 148 | 149 | **类或接口的解析**:判断所要转化成的直接引用是对数组类型,还是普通的对象类型的引用,从而进行不同的解析。 150 | 151 | **字段解析**:对字段进行解析时,会先在本类中查找是否包含有简单名称和字段描述符都与目标相匹配的字段,如果有,则查找结束;如果没有,则会按照继承关系从上往下递归搜索该类所实现的各个接口和它们的父接口,还没有,则按照继承关系从上往下递归搜索其父类,直至查找结束。 152 | 153 | ![https://img-blog.csdn.net/20140105220608531](media/7c45caf3842f62a36b29a58b481bb983.png) 154 | 155 | **类方法解析**:对类方法的解析与对字段解析的搜索步骤差不多,只是多了判断该方法所处的是类还是接口的步骤,而且对类方法的匹配搜索,是先搜索父类,再搜索接口。 156 | 157 | **接口方法解析**:与类方法解析步骤类似,只是接口不会有父类,因此,只递归向上搜索父接口就行了。 158 | 159 | 160 | 161 | **总结** :整个类加载过程中,除了在加载阶段用户应用程序可以自定义类加载器参与之外,其余所有的动作完全由虚拟机主导和控制。到了初始化才开始执行类中定义的Java程序代码(亦及字节码),但这里的执行代码只是个开端,它仅限于\()方法。类加载过程中主要是将Class文件(准确地讲,应该是类的二进制字节流)加载到虚拟机内存中,真正执行字节码的操作,在加载完成后才真正开始。 162 | 163 | 双亲委派机制 164 | ------------------ 165 | 166 | 双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。 167 | 168 | ![https://img-blog.csdn.net/20140105211242593](media/0ae87bbe6a0cfb8961b87fe5897ff83c.png) 169 | 170 | 类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远远不限于类的加载阶段。对于任意一个类,都需要由它的类加载器和这个类本身一同确定其在就Java虚拟机中的唯一性,也就是说,即使两个类来源于同一个Class文件,只要加载它们的类加载器不同,那这两个类就必定不相等。 171 | 172 | 站在Java虚拟机的角度来讲,只存在两种不同的类加载器: 173 | 174 | **启动类加载器**:它使用C++实现(这里仅限于Hotspot,也就是JDK1.5之后默认的虚拟机,有很多其他的虚拟机是用Java语言实现的),是虚拟机自身的一部分。 175 | 176 | **所有其他的类加载器**:这些类加载器都由Java语言实现,独立于虚拟机之外,并且全部继承自抽象类java.lang.ClassLoader,这些类加载器需要由启动类加载器加载到内存中之后才能去加载其他的类。 177 | 178 | 站在Java开发人员的角度来看,类加载器可以大致划分为以下三类: 179 | 180 | **启动类加载器**:Bootstrap 181 | ClassLoader,跟上面相同。它负责加载存放在JDK\\jre\\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.\*开头的类均被Bootstrap 182 | ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。 183 | 184 | **扩展类加载器**:Extension 185 | ClassLoader,该加载器由sun.misc.Launcher\$ExtClassLoader实现,它负责加载JDK\\jre\\lib\\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有 186 | 187 | 类库(如javax.\*开头的类),开发者可以直接使用扩展类加载器。 188 | 189 | **应用程序类加载器**:Application 190 | ClassLoader,该类加载器由sun.misc.Launcher\$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器(继承ClassLoader类并复写findClass方法),一般情况下这个就是程序中默认的类加载器。 191 | 192 | > 193 | **用户定制自己的ClassLoader可以实现以下的一些应用:** 194 | 1.自定义路径下查找自定义的class类文件,也许我们需要的class文件并不总是在已经设置好的Classpath下面,那么我们必须想办法来找到这个类,在这种清理下我们需要自己实现一个ClassLoader。 195 | 2.确保安全性:Java字节码很容易被反编译,对我们自己的要加载的类做特殊处理,如保证通过网络传输的类的安全性,可以将类经过加密后再传输,在加密到JVM之前需要对类的字节码在解密,这个过程就可以在自定义的ClassLoader中实现。 196 | 3.实现类的热部署:可以定义类的实现机制,如果我们可以检查已经加载的class文件是否被修改,如果修改了,可以重新加载这个类。 197 | 198 | 使用双亲委派模型来组织类加载器之间的关系,有一个很明显的好处,就是Java类随着它的类加载器(说白了,就是它所在的目录)一起具备了一种带有优先级的层次关系,这对于保证Java程序的稳定运作很重要。例如,类java.lang.Object类存放在JDK\\jre\\lib下的rt.jar之中,因此无论是哪个类加载器要加载此类,最终都会委派给启动类加载器进行加载,这边保证了Object类在程序中的各种类加载器中都是同一个类。 199 | 200 | 201 | -------------------------------------------------------------------------------- /notes/Java/Java中的锁.md: -------------------------------------------------------------------------------- 1 | * [锁的基本概念](#%E9%94%81%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5) 2 | * [乐观锁 vs 悲观锁](#%E4%B9%90%E8%A7%82%E9%94%81-vs-%E6%82%B2%E8%A7%82%E9%94%81) 3 | * [公平锁 vs 非公平锁](#%E5%85%AC%E5%B9%B3%E9%94%81-vs-%E9%9D%9E%E5%85%AC%E5%B9%B3%E9%94%81) 4 | * [独享锁 vs 共享锁](#%E7%8B%AC%E4%BA%AB%E9%94%81-vs-%E5%85%B1%E4%BA%AB%E9%94%81) 5 | * [分段锁 vs 可重入锁](#%E5%88%86%E6%AE%B5%E9%94%81-vs-%E5%8F%AF%E9%87%8D%E5%85%A5%E9%94%81) 6 | * [synchronized](#synchronized) 7 | * [对象头](#%E5%AF%B9%E8%B1%A1%E5%A4%B4) 8 | * [monitor](#monitor) 9 | * [synchronized底层原理](#synchronized%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86) 10 | * [自旋锁/偏向锁/轻量级锁/重量级锁](#%E8%87%AA%E6%97%8B%E9%94%81%E5%81%8F%E5%90%91%E9%94%81%E8%BD%BB%E9%87%8F%E7%BA%A7%E9%94%81%E9%87%8D%E9%87%8F%E7%BA%A7%E9%94%81) 11 | * [ReentrantLock](#reentrantlock) 12 | * [ReentrantLock](#reentrantlock-1) 13 | * [多个线程交替打印](#%E5%A4%9A%E4%B8%AA%E7%BA%BF%E7%A8%8B%E4%BA%A4%E6%9B%BF%E6%89%93%E5%8D%B0) 14 | * [Volatile](#volatile) 15 | * [Volatile关键字的两层语义](#volatile%E5%85%B3%E9%94%AE%E5%AD%97%E7%9A%84%E4%B8%A4%E5%B1%82%E8%AF%AD%E4%B9%89) 16 | * [volatile的原理和实现机制](#volatile%E7%9A%84%E5%8E%9F%E7%90%86%E5%92%8C%E5%AE%9E%E7%8E%B0%E6%9C%BA%E5%88%B6) 17 | * [volatile关键字的使用场景](#volatile%E5%85%B3%E9%94%AE%E5%AD%97%E7%9A%84%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF) 18 | 19 | 20 | 锁的基本概念 21 | ---------------- 22 | 23 | ### 乐观锁 vs 悲观锁 24 | 25 | **乐观锁** 26 | 27 | 使用乐观锁时,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。 28 | 29 | 乐观锁**适用于多读**的应用类型,乐观锁在Java中是通过使用无锁编程来实现,最常采用的是**CAS算法**,java.util.concurrent包中的原子类中的递增操作就通过CAS自旋实现的。 30 | 31 | **CAS全称 Compare And 32 | Swap(比较与交换)**,是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。 33 | 34 | 简单来说,CAS算法有3个三个操作数:**需要读写的内存值 V、进行比较的值 35 | A。要写入的新值B**。 36 | 37 | **当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则返回V。** 38 | 39 | **Java 40 | 8推出了一个新的类,LongAdder,他就是尝试使用分段CAS以及自动分段迁移的方式来大幅度提升多线程高并发执行CAS操作的性能!** 41 | 42 | ![](media/9a2eb3a0f7caca0fa4e33717087884ac.png) 43 | 44 | **悲观锁** 45 | 46 | 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都**会先上锁**,这样别人想拿这个数据就会阻塞直到它拿到锁**。Java的同步synchronized关键字的实现就是典型的悲观锁。** 47 | 48 | **综上:悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。** 49 | 50 | ### 公平锁 vs 非公平锁 51 | 52 | 公平与非公平指的是线程获取锁的方式。公平模式下,线程在同步队列中通过 FIFO 53 | 的方式获取锁,每个线程最终都能获取锁。在非公平模式下,线程会通过“插队”的方式去抢占锁,抢不到的则进入同步队列进行排队。 54 | 55 | 公平模式下,可保证每个线程最终都能获得锁,但效率相对比较较低。非公平模式下,效率比较高,但可能会导致线程出现饥饿的情况。即一些线程迟迟得不到锁,每次即将到手的锁都有可能被其他线程抢了。**原因如下**: 56 | 57 | 在激烈竞争的情况下,非公平锁的性能高于公平锁的性能的一个原因是:在恢复一个被挂起的线程与该线程真正开始运行之间存在着严重的延迟。假设线程 58 | A 持有一个锁,并且线程 B 请求这个锁。由于这个线程已经被线程 A 持有,因此 B 59 | 将被挂起。当 A 释放锁时,B 将被唤醒,因此会再次尝试获取锁。与此同时,如果 C 60 | 也请求这个锁,那么 C 很有可能会在 B 61 | 被完全唤醒前获得、使用以及释放这个锁。这样的情况时一种“双赢”的局面:B 62 | 获得锁的时刻并没有推迟,C 更早的获得了锁,并且吞吐量也获得了提高。 63 | 64 | ![https://blog-pictures.oss-cn-shanghai.aliyuncs.com/15255931517868.jpg](media/93ef7634eaf36d4c9a8ad277e23d14c4.jpg) 65 | 66 | 另一个可能的原因是公平锁线程切换次数要比非公平锁线程切换次数多得多,因此效率上要低一些。 67 | 68 | 综上:如果线程持锁时间短,则应使用非公平锁,可通过“插队”提升效率。如果线程持锁时间长,“插队”带来的效率提升可能会比较小,此时应使用公平锁。 69 | 70 | ### 独享锁 vs 共享锁 71 | 72 | >独享锁(互斥锁是指该锁一次只能被一个线程所持有。 73 | 74 | >共享锁(读写锁)是指该锁可被多个线程所持有。 75 | 76 | 对于Java 77 | ReentrantLock而言,其是独享锁。但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。读锁的共享锁可保证并发读是非常高效的,读写,写读 78 | ,写写的过程是互斥的。 79 | 80 | 独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。 81 | 82 | ### 分段锁 vs 可重入锁 83 | 84 | 分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。 85 | 86 | ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。 87 | 88 | 当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。 89 | 90 | 但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。 91 | 92 | 分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。 93 | 94 | 可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。对于ReentrantLock和 95 | Synchronized都是是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。 96 | 97 | >注意:受保护资源和锁之间合理的关联关系应该是 N:1 的关系,也就是说可以用一把锁来保护多个资源,但是不能用多把锁来保护一个资源。 98 | 保护没有关联关系的多个资源可以用多个锁来实现,但是保护有关联关系的多个资源最好用相同的锁。 99 | 100 | ### 死锁 101 | 102 | 死锁是一组互相竞争资源的线程因互相等待,导致“永久”阻塞的现象。只有以下这四个条件都发生时才会出现死锁: 103 | * 互斥,共享资源 X 和 Y 只能被一个线程占用 104 | * 占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X 105 | * 不可抢占,其他线程不能强行抢占线程 T1 占有的资源 106 | * 循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待 107 | 108 | 109 | 反过来分析,也就是说只要我们破坏其中一个,就可以成功避免死锁的发生。其中,互斥这个条件我们没有办法破坏,因为我们用锁为的就是互斥。不过其他三个条件都是有办法破坏掉的: 110 | * 对于“占用且等待”这个条件,我们可以一次性申请所有的资源,这样就不存在等待了 111 | * 对于“不可抢占”这个条件,占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源,这样不可抢占这个条件就破坏掉了。 112 | * 对于“循环等待”这个条件,可以靠按序申请资源来预防。所谓按序申请,是指资源是有线性顺序的,申请的时候可以先申请资源序号小的,再申请资源序号大的,这样线性化后自然就不存在循环了 113 | 114 | 115 | synchronized 116 | ---------------- 117 | 118 | synrhronized关键字简洁、清晰、语义明确,其应用层的语义是可以把任何一个非null对象作为"锁"。synchronized关键字最主要有以下3种应用方式: 119 | 120 | **修饰实例方法**,作用于当前实例加锁,进入同步代码前要获得当前实例的锁 121 | 122 | **修饰静态方法**,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁 123 | 124 | **修饰代码块**,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。 125 | 126 | ### 对象头 127 | 128 | HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance 129 | Data)和对齐填充(Padding)。HotSpot虚拟机的对象头(Object Header)包括两部分信息: 130 | 131 | 第一部分"Mark Word": 用于存储对象自身的运行时数据, 132 | 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等. 133 | 134 | 第二部分"Klass 135 | Pointer":对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。 136 | 137 | 32位的HotSpot虚拟机对象头存储结构如下: 138 | 139 | ![https://images2015.cnblogs.com/blog/584866/201704/584866-20170420091115212-1624858175.jpg](media/9e2974141f1be5d88523a1aff1eacb41.jpg) 140 | 141 | ### monitor 142 | 143 | 每个对象都存在着一个 monitor(管程) 144 | 与之关联(monitor对象存在于每个Java对象的对象头中),对象与其 monitor 145 | 之间的关系有存在多种实现方式,如monitor可以与对象一起创建销毁或当线程试图获取对象锁时自动生成,但当一个 146 | monitor 147 | 被某个线程持有后,它便处于锁定状态。在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的,其主要数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现的): 148 | 149 | ![](media/208dc9874b6f917427562c8a72e39788.png) 150 | 151 | ObjectMonitor中有两个队列,_WaitSet 和 152 | \_EntryList,用来保存ObjectWaiter对象列表(每个等待锁的线程都会被封装成ObjectWaiter对象),_owner指向持有ObjectMonitor对象的线程,当多个线程同时访问一段同步代码时,首先会进入 153 | \_EntryList集合,当线程获取到对象的monitor 后进入 \_Owner 154 | 区域并把monitor中的owner变量设置为当前线程同时monitor中的计数器count加1,若线程调用 155 | wait() 156 | 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 157 | WaitSet集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。如下图所示: 158 | 159 | ![](media/753f5a40efd070b30d36e396d96e9645.png) 160 | 161 | ### synchronized底层原理 162 | 163 | synchronized的实现离不开虚拟机JVM的支持,JVM是通过进入、退出对象监视器(monitor)来实现对方法、同步块的同步的。具体实现是在编译之后在同步方法调用前加入一个 164 | monitor.enter 指令,在退出方法和异常处插入monitor.exit 165 | 的指令。其本质就是对一个对象监视器(Monitor)进行获取,而这个获取过程具有排他性从而达到了同一时刻只能一个线程访问的目的。而对于没有获取到锁的线程将会阻塞到方法入口处,直到获取锁的线程 166 | monitor.exit之后才能尝试继续获取锁。流程图如下: 167 | 168 | ![](media/b7bf23f28c7de6d6e7867cbaac6e29d4.png) 169 | 170 | **具体来说:** 171 | 172 | **对代码块同步**:synchronized每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下: 173 | 174 | >1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。 175 | 176 | >2、如果线程己经占有该monitor,只是重新进入,则进入monitor的进入数加1。 177 | 178 | >3、如果其他线程己经占用了 179 | monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。 180 | 181 | **同步方法**:调用指令将会检查方法的ACC_SYNCHR〇NIZED访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放 182 | 183 | monitor。 184 | 185 | ### 自旋锁/偏向锁/轻量级锁/重量级锁 186 | 187 | 锁的状态总共有四种,无锁状态、偏向锁、轻量级锁和重量级锁(synchronized)。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁,但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级。锁的状态是保存在**对象头中**。 188 | 189 | **偏向锁目的**是消除数据在**由同一线程反复获得**的情况下的同步,即一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。 190 | 191 | 偏向锁的核心思想是:如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 192 | 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。所以,对于没有锁竞争的场合,偏向锁有很好的优化效果。**但是对于锁竞争比较激烈的场合,偏向锁就失效了**。偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁。 193 | 194 | **加锁**:当线程访问同步块时,会使用 CAS 将线程ID 更新到锁对象的 Mark Word中,如果更新成功则获得偏向锁,并且之后每次进入这个对象锁相关的同步块时都不需要再次获取锁了。 195 | 196 | **释放锁**:当有另外一个线程获取这个锁时,持有偏向锁的线程就会释放锁,释放时会等待全局安全点(这一时刻没有字节码运行),接着会暂停拥有偏向锁的线程,根据锁对象目前是否被锁来判定将对象头中的Mark Word 设置为无锁或者是轻量锁状态。 197 | 198 | **轻量级锁**是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。**轻量锁的特征是大多数锁在整个同步周期都不存在竞争,所以使用 199 | CAS 比使用互斥开销更少**。但如果锁竞争激烈,轻量锁就不但有互斥的开销,还有 CAS 200 | 的开销,甚至比重量锁更慢。轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。 201 | 202 | **加锁**:当代码进入同步块时,如果同步对象为无锁状态时,当前线程会在栈帧中创建一个锁记录(LockRecord)区域,同时将锁对象的对象头中 Mark Word 拷贝到锁记录中,再尝试使用 CAS 将Mark Word 更新为指向锁记录的指针。如果更新成功,当前线程就获得了锁。如果更新失败 JVM 会先检查锁对象的 Mark Word是否指向当前线程的锁记录。如果是则说明当前线程拥有锁对象的锁,可以直接进入同步块。不是则说明有其他线程抢占了锁,如果存在多个线程同时竞争一把锁,轻量锁就会膨胀为重量锁。 203 | 204 | **解锁**:轻量锁的解锁过程也是利用 CAS 来实现的,会尝试锁记录替换回锁对象的 Mark Word。如果替换成功则说明整个同步操作完成,失败则说明有其他线程尝试获取锁,这时就会唤醒被挂起的线程(此时已经膨胀为重量锁)。 205 | 206 | ![](media/6286448d2f5f2cb602cb7113f3bfe545.png) 207 | 208 | **自旋锁**:轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。**这是基于在大多数情况下,线程持有锁的时间都不会太长,如果直接挂起操作系统层面的线程可能会得不偿失**,毕竟操作系统实现线程之间的切换时需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,**因此自旋锁会假设在不久将来,当前的线程可以获得锁,**因此虚拟机会让当前想要获取锁的线程做几个空循环(这也是称为自旋的原因),一般不会太久,可能是50个循环或100循环,在经过若干次循环后,如果得到锁,就顺利进入临界区。如果还不能获得锁,那就会将线程在操作系统层面挂起,这就是自旋锁的优化方式,这种方式确实也是可以提升效率的。最后没办法也就只能升级为重量级锁了。 209 | 210 | 自旋锁的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。JDK1.6引入了自适应的自旋锁。它的基本原理是如果某个锁自旋很少成功获得,那么下一次就会减少自旋。如果自旋等待成功获取过锁,则会适当的延长获取锁的时间。 211 | 212 | **不同锁的比较** 213 | 214 | ![](media/a5379cb3f718cd7763573726ce52ed64.png) 215 | 216 | ReentrantLock 217 | ----------------- 218 | 219 | ### ReentrantLock 220 | 221 | ReentrantLock 是基于AQS实现的,AQS很好的封装了同步队列的管理,线程的阻塞与唤醒等基础操作。基于 AQS 的同步组件,ReentrantLock 中包含了一个静态抽象类Sync。 222 | 223 | AQS维护了一个基于双向链表的同步队列,线程在获取同步状态失败的情况下,都会被封装成节点,然后加入队列中。同步队列大致示意图如下: 224 | 225 | ![https://blog-pictures.oss-cn-shanghai.aliyuncs.com/15256142424566.jpg](media/764c4cb9f6d7115ca743ab9f9583e6d1.jpg) 226 | 227 | 在同步队列中,头结点是获取同步状态的节点。其他节点在尝试获取同步状态失败后,会被阻塞住,暂停运行。当头结点释放同步状态后,会唤醒其后继节点。后继节点会将自己设为头节点,并将原头节点从队列中移除。 228 | 229 | **公平锁的流程如下:** 230 | 231 | **1、调用 acquire 方法,将线程放入同步队列中进行等待** 232 | 233 | 2、线程在同步队列中成功获取锁,则将自己设为持锁线程后返回 234 | 235 | 3、若同步状态不为0,且当前线程为持锁线程,则执行重入逻辑 236 | 237 | **非公平锁步骤大致如下:** 238 | 239 | 1、调用compareAndSetState方法抢占式加锁,加锁成功则将自己设为持锁线程,并返回 240 | 241 | 2、若加锁失败,则调用 acquire 方法,将线程置于同步队列尾部进行等待 242 | 243 | 3、线程在同步队列中成功获取锁,则将自己设为持锁线程后返回 244 | 245 | 4、若同步状态不为0,且当前线程为持锁线程,则执行重入逻辑 246 | 247 | [具体源码参考](http://www.cnblogs.com/nullllun/p/9004309.html#autoid-1-0-0) 248 | 249 | **ReentrantLock 和 synchronized 区别** 250 | 251 | ![](media/cf1372bc829900ee9ebe3b98521c34d4.png) 252 | 253 | ### 多个线程交替打印 254 | 255 | ``` 256 | import java.util.concurrent.locks.Lock; 257 | import java.util.concurrent.locks.ReentrantLock; 258 | 259 | // 多个线程交替打印 260 | // 以三个为例 261 | public class Print { 262 | 263 | private int start; 264 | private int end; 265 | private int num= 3; //线程的数量 266 | 267 | // 对 flag 的写入虽然加锁保证了线程安全,但读取的时候由于 不是 volatile 所以可能会读取到旧值 268 | private volatile int flag = 0; //表明是哪个线程执行 269 | //定义一个充入锁 270 | private final static Lock LOCK = new ReentrantLock(); 271 | 272 | public Print(int start,int end,int num){ 273 | this.start = start; 274 | this.end = end; 275 | this.num = num; 276 | } 277 | 278 | public static void main(String[] args) { 279 | 280 | Print p = new Print(1,15,3); 281 | for(int i=0;i 1.继承Thread类,重写run方法 34 | 35 | > 2.实现Runnable接口,重写run方法 36 | 37 | > 3.应用程序可以使用Executor框架来创建线程池 38 | 39 | > 4.实现Callable接口 40 | 41 | 实现Runnable接口比继承Thread类所具有的优势: 42 | 43 | > 1.适合多个相同的程序代码的线程去处理同一个资源 44 | 45 | > 2.可以避免java中的单继承的限制 46 | 47 | > 3.增加程序的健壮性,代码可以被多个线程共享,代码和数据独立 48 | 49 | > 4.线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类 50 | 51 | > 5.runnable实现线程可以对线程进行复用,因为runnable是轻量级的对象,重复new不会耗费太大资源,而Thread则不然,它是重量级对象,而且线程执行完就完了,无法再次利用。 52 | 53 | 线程的状态 54 | -------------- 55 | 56 | **新建状态(New)**:新创建了一个线程对象。 57 | 58 | **就绪状态(Runnable)**:线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。 59 | 60 | **运行状态(Running)**:就绪状态的线程获取了CPU,执行程序代码。 61 | 62 | **阻塞状态(Blocked)**:阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种: 63 | 64 | 1.**等待阻塞**:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁) 65 | 66 | 2.**同步阻塞**:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。 67 | 68 | **3.其他阻塞**:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)。 69 | 70 | **死亡状态(Dead)**:线程执行完了或者因异常退出了run()方法,该线程结束生命周期。 71 | 72 | ![https://img-blog.csdn.net/20150309140927553](media/4aedb9d9d47f02ec77420699a86f22a7.jpg) 73 | 74 | [为什么 Java 线程没有 Running 状态](https://mp.weixin.qq.com/s/tNdUEkLGz9xrSEsRy-Ng-Q) 75 | 76 | 线程调度 77 | ------------ 78 | 79 | ### sleep与wait区别 80 | 81 | **sleep方**法属于Thread类中的静态方法,表示让一个线程进入睡眠状态,等待一定的时间之后,自动醒来进入到可运行状态,但是不会马上进入运行状态,因为线程调度机制恢复线程的运行也需要时间,一个线程对象调用了sleep方法之后,并不会释放他所持有的所有对象锁,所以也就不会影响其他进程对象的运行。**但在sleep的过程中过程中有可能被其他对象调用它的interrupt(),产生InterruptedException异常**,所以**sleep必须捕获异常。** 82 | 83 | **注意**sleep是静态方法,也就是说它只对当前对象有效。通过 对象名.sleep() 84 | 想让该对象线程进入休眠是无效的,它只会让当前线程进入休眠。 85 | 86 | **wait方法**是Object类里的方法,当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时释放了锁对象,等待期间可以调用里面的同步方法,其他线程可以访问,等待时不拥有CPU的执行权,否则其他线程无法获取执行权。当一个线程执行了wait方法后,必须调用notify或者notifyAll方法才能唤醒,而且是随机唤醒,若是被其他线程抢到了CPU执行权,该线程会继续进入等待状态。由于锁对象可以时任意对象,所以wait方法必须定义在Object类中,因为Obeject类是所有类的基类。 87 | 88 | **区别比较:** 89 | 90 | 1.这两个方法来自不同的类分别是Thread和Object。 91 | 92 | 2.最主要的区别是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。 93 | 94 | 3.wait,notify和notifyAll只能在**同步控制方法或者同步控制块里**面使用,而sleep可以在任何地方使用(使用范围)。 95 | 96 | 4.**wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 97 | ,从而使线程立刻抛出InterruptedException。** 98 | 99 | **为什么wait、notify和notifyAll方法要和synchronized关键字一起使用?** 100 | 101 | 因为wait和notify、notifyAll方法是由确定的对象即锁对象来调用的,这一过程是线程通信。wait方法是使一个线程进入等待状态,并且释放其所持有的锁对象,notify方法是通知等待该锁对象的线程重新获得锁对象,然而如果没有获得锁对象,wait方法和notify方法都是没有意义的,因此必须先获得锁对象再对锁对象进行进一步操作于是才要把wait方法和notify方法写到同步方法和同步代码块中了。 102 | 103 | sleep方法是让某个线程暂停运行一段时间,其控制范围是由当前线程决定,运行的主动权是由当前线程来控制(拥有CPU的执行权)。本质上是对线程的运行状态控制。 104 | 105 | ### yield/join 106 | 107 | yield方法是停止当前线程,让出CPU占有权,让同等优先权的线程或更高优先级的线程有执行的机会。yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。 108 | 109 | join方法是用于在某一个线程的执行过程中调用另一个线程执行,等到被调用的线程执行结束后,再继续执行当前线程。如:在main方法中调用t.join(),此时只有当t线程运行结束,main方法才会继续执行。 110 | 111 | ### 守护线程与非守护线程 112 | 113 | 所谓守护线程是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。 114 | 115 | **在使用守护线程时需要注意一下几点:** 116 | 117 | 1.thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常,即不能把正在运行的常规线程设置为守护线程。 118 | 119 | 2.在Daemon线程中产生的新线程也是Daemon的。 120 | 121 | 3.守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。 122 | 123 | ### 中断线程 124 | 125 | 中断线程有很多方法: 126 | 127 | > 1.使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。 128 | 129 | > 2.通过return退出run方法 130 | 131 | > 3.通过对有些状态中断抛异常退出。 132 | 133 | Thread.interrupt()并不能使得线程被中断,线程还是会执行。最靠谱的方法就是设置一个全局的标记位,然后再Thread中去检查这个标记位,发现标记位改变则中断线程。 134 | 135 | ![](media/7b02d3d12c1f34841dc96a2c0a25f592.png) 136 | 137 | ### 从线程中抛出异常 138 | 139 | 首先明确线程代码的边界。其实很简单,Runnable接口的run方法所界定的边界就可以看作是线程代码的边界。Runnable接口中run方法原型如下: 140 | 141 | **public void run();** 142 | 143 | 而所有的具体线程都实现这个方法,所以这里就明确了一点,**线程代码不能抛出任何checked异常**。所有的线程中的checked异常都只能被线程本身消化掉。 144 | 145 | 但是,线程代码中是可以抛出错误(Error)和运行级别异常(RuntimeException)的。Error通常Error应该留给JVM处理,而RuntimeException确是比较正常的,如果在运行过程中满足了某种条件导致线程必须中断,可以选择使用抛出运行级别异常来处理,当线程代码抛出运行级别异常之后,线程会中断。但是对开启子线程的主线程来说,并不受影响。正常情况下,如果不做特殊的处理,在主线程中是不能够捕获到子线程中的异常的。 146 | 147 | **但是,给某个thread设置一个UncaughtExceptionHandler,**可以确保在该线程出现异常时能通过**回调UncaughtExceptionHandler接口的uncaughtException(…) 148 | 方法**来处理异常,这样的好处或者说目的是可以在线程代码边界之外(Thread的run()方法之外),有一个地方能处理未捕获异常。但是要特别明确的是:**虽然是在回调方法中处理异常,但** 149 | 150 | **这个回调方法在执行时依然还在抛出异常的这个线程中**!另外还要特别说明一点:如果线程是通过线程池创建,线程异常发生时UncaughtExceptionHandler接口不一定会立即回调。 151 | 152 | 线程的活跃性问题 153 | -------------------- 154 | 155 | ### 多线程中的死锁 156 | 157 | **所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待,两个线程都处于阻塞状态,在等待获取锁),若无外力作用,这些进程都将无法向前推进。** 158 | 159 | 避**免死锁的三种方式:** 160 | 161 | > 1. **加锁顺序**:所有的线程按照一定的顺序加锁。 162 | 163 | > 2. **加锁时限**:在尝试获取锁的时候加一个超时时间,这也就意味着在尝试获取锁的过程中若超过了这个时限该线程则放弃对该锁请求。若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后**等待一段随机的时间再重试。**这段随机的等待时间让其它线程有机会尝试获取相同的这些锁,并且让该应用在没有获得锁的时候可以继续运行。 164 | 165 | > 3.**死锁检测:**死锁检测是一个更好的死锁预防机制,它主要是针对那些不可能实现按序加锁并且锁超时也不可行的场景。每当一个线程获得了锁,会在线程和锁相关的数据结构中将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。当一个线程请求锁失败时,这个线程可以遍历**锁的关系图看看是否有死锁发生**。那么当检测出死锁时,**一个可行的做法是释放所有锁,回退,并且等待一段随机的时间后重试**。这个和简单的加锁超时类似,不一样的是只有死锁已经发生了才回退,而不会是因为加锁的请求超时了。虽然有回退和等待,但是如果有大量的线程竞争同一批锁,它们还是会重复地死锁。 **一个更好的方案是给这些线程设置优先级**,让一个(或几个)线程回退,剩下的线程就像没发生死锁一样继续保持着它们需要的锁。如果赋予这些线程的优先级是固定不变的,同一批线程总是会拥有更高的优先级。为避免这个问题,可以在死锁发生的时候设置随机的优先级。 166 | 167 | ### 多线程中的活锁 168 | 169 | **活锁是指线程一直处于运行状态,但却是在做无用功,而这个线程本身要完成的任务却一直无法进展**。生活中的典型例子: 170 | 两个人在窄路相遇,同时向一个方向避让,然后又向另一个方向避让,如此反复。 171 | 172 | **解决协同活锁的一种方案是调整重试机制**。 173 | 174 | 比如引入一些随机性。例如如果检测到冲突,那么就暂停随机的一定时间进行重试。这回大大减少碰撞的可能性。典型的例子是以太网的CSMA/CD检测机制。另外为了避免可能的死锁,适当加入一定的重试次数也是有效的解决办法。尽管这在业务上会引起一些复杂的逻辑处理。 175 | 176 | ### 多线程中的饥饿 177 | 178 | 当线程长时间得不到所需资源,使线程难以推进时,**就发生饥饿**。若是长时间的处于饥饿状态导致后续已没有完成该线程任务的必要时,就称这个进程饿死了。饿死的线程是等待可以被释放,但不会分配给自己的资源。 179 | 180 | 比如如果线程T1占用了资源R,线程T2又请求封锁R,于是T2等待。T3也请求资源R,当T1释放了R上的封锁后,系统首先批准了T3的请求,T2仍然等待。然后T4又请求封锁R,当T3释放了R上的封锁之后,系统又批准了T4的请求......,T2可能永远等待。 181 | 182 | **一般情况下,想要解决饥饿问题,就会需要对线程竞争资源的竞争力(优先级)进行评估,竞争力强(优先级高)的线程优先分配资源,然后才是竞争力弱的。** 183 | 184 | 线程安全 185 | ------------ 186 | 187 | 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么这个类就是线程安全的。 188 | 189 | ### 三个基本概念 190 | 191 | 在并发编程中,通常会遇到以下三个问题:原子性问题,可见性问题,有序性问题。 192 | 193 | **原子性**:一个操作或者多个操作 194 | 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。 195 | 196 | **可见性**:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。 197 | 198 | **有序性**:程序执行的顺序按照代码的先后顺序(逻辑顺序,可能会出现指令优化重排)执行。 199 | 200 | 要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。 201 | 202 | ### Java内存模型 203 | 204 | Java内存模型(Java Memory 205 | Model,JMM)的主要目标是定义程序中各个变量的访问规则,**即在虚拟机中将变量存储到内存和从内存中取出变量这样底层细节,同时**屏蔽各个硬件平台和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。此处的变量与Java编程时所说的变量不一样,指包括了实例字段、静态字段和构成数组对象的元素,但是不包括局部变量与方法参数,后者是线程私有的,不会被共享。 206 | 207 | Java内存模型中规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程使用到的变量到主内存副本拷贝,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间无法直接访问对方工作内存中的变量,线程间变量值的传递均需要在主内存来完成,线程、主内存和工作内存的交互关系如下图所示: 208 | 209 | ![https://images0.cnblogs.com/i/475287/201403/091134177063947.jpg](media/b7f94146622574758fbe49f9785549ce.jpg) 210 | 211 | 为了获得较好的执行性能,Java内存模型并没有限制执行引擎使用处理器的寄存器或者高速缓存来提升指令执行速度,也没有限制编译器对指令进行重排序。也就是说,在java内存模型中,也会存在缓存一致性问题和指令重排序的问题。 212 | 213 | 在执行程序时为了提高性能,编译器和处理器经常会对指令进行重排序。实际上,正是由于编译器和处理器的一些优化,才是导致出现线程安全的根源。 214 | 215 | 虽然CPU、内存、I/O 设备都在不断迭代,不断朝着更快的方向努力。但是,在这个快速发展的过程中,有一个核心矛盾一直存在,就是这**三者的速度差异**。程序里大部分语句都要访问内存,有些还要访问 I/O,根据木桶理论(一只水桶能装多少水取决于它最短的那块木板),程序整体的性能取决于最慢的操作——读写 I/O 设备,也 216 | 就是说单方面提高 CPU 性能是无效的。为了合理利用 CPU 的高性能,平衡这三者的速度差异,计算机体系机构、操作系统、编译程序都做出了贡献,主要体现为: 217 | * CPU 增加了缓存,以均衡与内存的速度差异 218 | * 操作系统增加了进程、线程,以分时复用 CPU,进而均衡 CPU 与 I/O 设备的速度差异 219 | * 编译程序优化指令执行次序,使得缓存能够得到更加合理地利用 220 | 221 | 但是并发程序很多诡异问题的根源也在这里:缓存导致了可见性问题,线程切换导致了原子性问题,编译优化导致了有序性问题 222 | 223 | 224 | ### Java对线程安全的保证 225 | 226 | **原子性** 227 | 228 | 在Java中,对基本数据类型(除了long和double)的变量的**读取**和**赋值**操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。也就是说,只有简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)才是原子操作。 229 | 230 | 如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。 231 | 232 | **可见性** 233 | 234 | 对于可见性,Java提供了volatile关键字来保证可见性。当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。相当于在禁用了缓存以及编译优化。 235 | 236 | 而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。 237 | 238 | 另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。 239 | 240 | **有序性** 241 | 242 | 在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。在Java里面,可以通过volatile关键字来保证一定的“有序性”。另外可以通过synchronized和Lock来保证有序性,同样,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。 243 | 244 | 另外,Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 245 | **happens-before原则**:如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。 246 | 247 | 下面就来具体介绍下happens-before原则(先行发生原则): 248 | 249 | **程序次序规则**:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作。注意,虽然这条规则中提到“书写在前面的操作先行发生于书写在后面的操作”,这个应该是程序看起来执行的顺序是按照代码顺序执行的,**但是虚拟机可能会对程序代码进行指令重排序。虽然进行重排序,但是最终执行的结果是与程序顺序执行的结果一致的,它只会对不存在数据依赖性的指令进行重排序**。但是这个规则无法保证程序在多线程中执行的正确性。 250 | 251 | **锁定规则**:一个unLock操作先行发生于后面对同一个锁额lock操作。也就是说无论在单线程中还是多线程中,同一个锁如果出于被锁定的状态,那么必须先对锁进行了释放操作,后面才能继续进行lock操作。 252 | 253 | **volatile变量规则**:对一个变量的写操作先行发生于后面对这个变量的读操作。直观地解释就是,如果一个线程先去写一个变量,然后一个线程去进行读取,那么写入操作肯定会先行发生于读操作。 254 | 255 | **传递规则**:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C。这个规则实际上就是体现happens-before原则具备传递性。 256 | 257 | **线程启动规则**:Thread对象的start()方法先行发生于此线程的每个一个动作。 258 | 259 | **线程中断规则**:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生。 260 | 261 | **线程终结规则**:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行。 262 | 263 | **对象终结规则**:一个对象的初始化完成先行发生于它的finalize()方法的开始。 264 | 265 | ### 线程同步的方法 266 | 267 | 在多线程中线程的执行顺序是依靠哪个线程先获得到CUP的执行权谁就先执行,虽然说可以通过线程的优先权进行设置,但也只是获取CUP执行权的概率高点,也不一定必须先执行。 268 | 269 | **同步方法** 270 | 271 | 即有synchronized关键字修饰的方法。由于Java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。 272 | 273 | 注:synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。这个方法可以是静态方法和非静态方法,但是不能是抽象类的抽象方法,也不能是接口中的接口方法。 274 | 275 | 线程在执行同步方法时是具有排它性的。当任意一个线程进入到一个对象的任意一个同步方法时,这个对象的所有同步方法都被锁定了,在此期间,其他任何线程都不能访问这个对象的任意一个同步方法,直到这个线程执行完它所调用的同步方法并从中退出,从而导致它释放了该对象的同步锁之后。在一个对象被某个线程锁定之后,其他线程是可以访问这个 276 | 277 | 对象的所有非同步方法的。 278 | 279 | **同步代码块** 280 | 281 | 即有synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。 282 | 283 | 同步块是通过锁定一个指定的对象,来对同步块中包含的代码进行同步;而同步方法是对这个方法块里的代码进行同步,而这种情况下锁定的对象就是同步方法所属的主体对象自身。如果这个方法是静态同步方法呢?那么线程锁定的就不是这个类的对象了,也不是这个类自身,而是这个类对应的java.lang.Class类型的对象。同步方法和同步块之间的相互制约只限于同一个对象之间,所以静态同步方法只受它所属类的其它静态同步方法的制约,而跟这个类的实例(对象)没有关系。 284 | 285 | 如果一个对象既有同步方法,又有同步块,那么当其中任意一个同步方法或者同步块被某个线程执行时,这个对象就被锁定了,其他线程无法在此时访问这个对象的同步方法,也不能执行同步块。 286 | 287 | synchronized的目的是使同一个对象的多个线程,在某个时刻只有其中的一个线程可以访问这个对象的synchronized 288 | 数据。每个对象都有一个“锁标志”,当这个对象的一个线程访问这个对象的某个synchronized 289 | 数据时,这个对象的所有被synchronized 290 | 修饰的数据将被上锁(因为“锁标志”被当前线程拿走了),只有当前线程访问完它要访问的synchronized 291 | 数据时,当前线程才会释放“锁标志”,这样同一个对象的其它线程才有机会访问synchronized 292 | 数据。 293 | 294 | **wait与notify** 295 | 296 | wait方法使一个线程处于等待状态,并且释放所持有的对象的lock。 297 | 298 | sleep方法使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。 299 | 300 | notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。 301 | 302 | allnotity():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。 303 | 304 | 类似的还有**Condition的awiat和signal。** 305 | 306 | **使用特殊域变量(volatile)实现线程同步** 307 | 308 | volatile关键字为域变量的访问提供了一种免锁机制,使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,因此每次使用该域就要重新计算,而不是使用寄存器中的值。volatile不会提供任何原子操作,它也不能用来修饰final类型的变量 309 | 310 | 注:多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。用final域,有锁保护的域和volatile域可以避免非同步的问题。 311 | 312 | **使用重入锁实现线程同步** 313 | 314 | 在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。 315 | 316 | ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。 317 | 318 | **使用局部变量实现线程同步** 319 | 320 | 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 321 | 322 | 注:ThreadLocal与同步机制 323 | 324 | > a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。 325 | 326 | > b.前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式 327 | 328 | **使用阻塞队列实现线程同步** 329 | 330 | **ArrayBlockingQueue**:基于数组实现的一个阻塞队列,在创建ArrayBlockingQueue对象时必须制定容量大小。并且可以指定公平性与非公平性,默认情况下为非公平的,即不保证等待时间最长的队列最优先能够访问队列。 331 | 332 | **LinkedBlockingQueue**:基于链表实现的一个阻塞队列,在创建LinkedBlockingQueue对象时如果不指定容量大小,则默认大小为Integer.MAX_VALUE。 333 | 334 | **PriorityBlockingQueue**:以上2种队列都是先进先出队列,而PriorityBlockingQueue却不是,它会按照元素的优先级对元素进行排序,按照优先级顺序出队,每次出队的元素都是优先级最高的元素。注意,此阻塞队列为无界阻塞队列,即容量没有上限(通过源码就可以知道,它没有容器满的信号标志),前面2种都是有界队列。 335 | 336 | **DelayQueue**:基于PriorityQueue,一种延时阻塞队列,DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue也是一个无界队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。 337 | 338 | 使用上述介绍的队列可以实现一个类似于生产者-消费者模型。 339 | 340 | **使用原子变量实现线程同步** 341 | 342 | 需要使用线程同步的根本原因在于对普通变量的操作不是原子的。 343 | 344 | 原子操作是指将**读取变量值、修改变量值、保存变量值看成一个整体来操作**,即这几种行为要么同时完成,要么都不完成。在java.util.concurrent.atomic包中提供了创建了原子类型变量的工具类,使用该类可以简化线程同步。比如AtomicInteger表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器)。 345 | 346 | 此外也可以通过通过同步辅助类CountDownLatch、CyclicBarrier和Semaphore等实现线程的同步。 347 | 348 | 线程间通信 349 | ---------- 350 | 351 | **等待通知机制** 352 | 353 | 等待通知模式是 Java 中比较经典的线程通信方式。两个线程通过对同一对象调用等待 354 | wait() 和通知 notify() 方法来进行通讯。有一些需要注意: 355 | 356 | wait()、notify()、notifyAll() 调用的前提都是获得了对象的锁(也可称为对象监视器)。 357 | 358 | 调用 wait() 方法后线程会释放锁,进入 WAITING 359 | 状态,该线程也会被移动到等待队列中。 360 | 361 | 调用notify()方法会将等待队列中的线程移动到同步队列中,线程状态也会更新为 BLOCKED 362 | 363 | 从wait()方法返回的前提是调用 notify() 方法的线程释放锁,wait() 364 | 方法的线程获得锁。 365 | 366 | 等待通知有着一个经典范式: 367 | 368 | **线程 A 作为消费者:** 369 | 370 | > 获取对象的锁。 371 | 372 | > 进入while(判断条件),并调用 wait() 方法。 373 | 374 | > 当条件满足跳出循环执行具体处理逻辑。 375 | 376 | **线程 B 作为生产者:** 377 | 378 | > 获取对象锁。 379 | 380 | > 更改与线程 A 共用的判断条件。 381 | 382 | > 调用 notify() 方法。 383 | 384 | 385 | 386 | 387 | **volatile 共享内存** 388 | 389 | 因为Java是采用共享内存的方式进行线程通信的,采用volatile修饰的变量可以保证线程间的可见性。 390 | 391 | **CountDownLatch** 392 | 393 | CountDownLatch 可以实现 join 相同的功能,但是更加的灵活。 394 | 395 | **CyclicBarrier** 396 | 397 | 中文名叫做屏障或者是栅栏,也可以用于线程间通信。它可以等待 N 398 | 个线程都达到某个状态后继续运行的效果。 399 | 400 | 该工具可以实现 CountDownLatch 同样的功能,但是要更加灵活。甚至可以调用 reset() 401 | 方法重置 CyclicBarrier (需要自行捕获 BrokenBarrierException 处理) 然后重新执行。 402 | 403 | **线程响应中断** 404 | 405 | 可以采用中断线程的方式来通信,调用了 thread.interrupt() 方法其实就是将 Thread 406 | 中的一个标志属性置为了true。并不是说调用了该方法就可以中断线程,如果不对这个标志进行响应其实是没有什么作用。 407 | 408 | **对于正在运行的非阻塞线程来说, 409 | 只是改变了中断状态,不会对它的运行状态产生影响。** 410 | 411 | 对于可取消的阻塞状态(调用了 Thread.sleep(), Object.wait(), 412 | Thread.join()方法)中的线程来说,这个线程收到中断信号后, 413 | 会抛出InterruptedException, 同时会把中断状态置回为false。 414 | 415 | 在Core 416 | Java中有这样一句话:”没有任何语言方面的需求要求一个被中断的程序应该终止。中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断 417 | “。interrupt中断的是线程的某一部分业务逻辑,**前提是线程需要检查自己的中断状态(isInterrupted())**。但是当线程被阻塞的时候,调用它的interrput()方法。 418 | 419 | ### 创建线程的数量 420 | 421 | 使用多线程,本质上就是提升程序性能。 422 | 度量性能的指标有很多,但是有两个指标是最核心的,它们就是延迟和吞吐量。延迟指的是发出 423 | 请求到收到响应这个过程的时间;延迟越短,意味着程序执行得越快,性能也就越好。 吞吐量指 424 | 的是在单位时间内能处理请求的数量;吞吐量越大,意味着程序能处理的请求越多,性能也就越 425 | 好。这两个指标内部有一定的联系(同等条件下,延迟越短,吞吐量越大),但是由于它们隶属 426 | 不同的维度(一个是时间维度,一个是空间维度),并不能互相转换。 427 | 428 | 要想“降低延迟,提高吞吐量”,对应的方法呢,基本上有两个方向,一个方向是优化算法,另 429 | 一个方向是将硬件的性能发挥到极致。前者属于算法范畴,后者则是和并发编程息息相关了。那 430 | 计算机主要有哪些硬件呢?主要是两类:一个是 I/O,一个是 CPU。简言之,在并发编程领域, 431 | 提升性能本质上就是提升硬件的利用率,再具体点来说,就是提升 I/O 的利用率和 CPU 的利用 432 | 率。 433 | 434 | 创建多少线程合适,要看多线程具体的应用场景。程序一般都是 CPU 计算和 I/O 操作交 435 | 叉执行的,由于 I/O 设备的速度相对于 CPU 来说都很慢,所以大部分情况下,I/O 操作执行的 436 | 时间相对于 CPU 计算来说都非常长,这种场景一般都称为 I/O 密集型计算;和 I/O 密集型 437 | 计算相对的就是 CPU 密集型计算了,CPU 密集型计算大部分场景下都是纯 CPU 计算。I/O 密集 438 | 型程序和 CPU 密集型程序,计算最佳线程数的方法是不同的。 439 | 440 | * 对于 CPU 密集型计算,多线程本质上是提升多核 CPU 的利用率,理论上“线程的数量 =CPU 核数”就是最合适的。不过在工 441 | 程上,线程的数量一般会设置为“CPU 核数 +1”,这样的话,当线程因为偶尔的内存页失效或 442 | 其他原因导致阻塞时,这个额外的线程可以顶上,从而保证 CPU 的利用率。 443 | * 对于 I/O 密集型的计算场景,最佳的线程数是与程序中 CPU 计算和 I/O 操作的耗时比相关的,可以总结出这样一个公式:最佳线程数 =CPU 核数 * (1 +(I/O 耗时 / CPU 耗时)) 444 | 445 | >对于 I/O 密集型计算场景,I/O 耗时和 CPU 耗时的比值是一个关键参数,不幸的是这个参数是 446 | 未知的,而且是动态变化的,所以工程上需要估算这个参数,然后做各种不同场景下的压测来验证。 447 | 448 | ### 多线程中的设计模式 449 | #### 不可变模式 450 | 451 | 多个线程同时读写同一共享变量存在并发问题,这里的必要条件之一是读写,如果只有读,而没有写,是没有并发问题的。 452 | 453 | 解决并发问题,其实最简单的办法就是让共享变量只有读操作,而没有写操作。这个办法如此重要,以至于被上升到了一种解决并发问题的设计模式:**不变性(Immutability)模式**。所谓不变性,简单来讲,就是对象一旦被创建之后,状态就不再发生变化。换句话说,就是变量一旦被赋值,就不允许修改了(没有写操 454 | 作);没有修改操作,也就是保持了不变性。一个不可变的类具有三个属性: 455 | * 类和属性都必须是 final 的 456 | * 所有方法均是只读的 457 | * 类的属性如果是引用型,该属性对应的类也需要满足不可变类的条件,且不能提供修改该属性的方法 458 | 459 | >不可变对象虽然是线程安全的,但是并不意味着引用这些不可变对象的对象就是线程安全的。 460 | 461 | 具备不变性的对象,只有一种状态,这个状态由对象内部所有的不变属性共同决定。其实还有一种更简单的 462 | 不变性对象,那就是无状态。无状态对象内部没有属性,只有方法。除了无状态的对象,你可能还听说过无 463 | 状态的服务、无状态的协议等等。无状态有很多好处,最核心的一点就是性能。在多线程领域,无状态对象 464 | 没有线程安全问题,无需同步处理,自然性能很好;在分布式领域,无状态意味着可以无限地水平扩展,所 465 | 以分布式领域里面性能的瓶颈一定不是出在无状态的服务节点上。 466 | 467 | 468 | #### Copy-on-Write模式 469 | 470 | 所谓Copy-on-Write,经常被缩写为COW或者CoW,顾名思义就是**写时复制**。 471 | 472 | 不可变对象的写操作往往都是使用Copy-on-Write方法解决的,当然Copy-on-Write的应用领域并不局限于Immutability模式。 473 | 474 | Copy-on-Write是一项非常通用的技术方案,在很多领域都有着广泛的应用。不过,它也有缺点的,那就是 475 | 消耗内存,每次修改都需要复制一个新的对象出来,好在随着自动垃圾回收(GC)算法的成熟以及硬件的 476 | 发展,这种内存消耗已经渐渐可以接受了。 477 | 478 | #### 线程本地存储模式 479 | 多个线程同时读写同一共享变量存在并发问题,但是没有共享变量也不会有并发问题。也就是说如果每个线程都拥有自己的变量,彼此之间不共享,也就没有并发问题了。 480 | 除了局部变量可以做到避免共享外,Java语言提供的线程本地存储(ThreadLocal)。Java的实现里面也有每个线程有一个内部有一个私有属性threadLocals,其类型就是ThreadLocalMap,ThreadLocalMap的Key是ThreadLocal。 481 | 482 | ![](media/1258jhvsavgkvbkgk433.png) 483 | 484 | 在Java的实现方案里面,ThreadLocal仅仅是一个代理工具类,内部并不持有任何与线程相关的数据,所有和线程相关的数据都存储在Thread里面,这样的设计容易理解。而从数据的亲缘性上来讲,ThreadLocalMap属于Thread也更加合理。当然还有一个更加深层次的原因,那就是不容易产生内存泄露。而Java的实现中Thread持有ThreadLocalMap,而且ThreadLocalMap里对ThreadLocal的引用还是弱引用(WeakReference),所以只要Thread对象可以被回收,那么ThreadLocalMap就能被回收。 485 | 486 | Java的ThreadLocal实现应该称得上深思熟虑了,不过即便如此深思熟虑,还是不能百分百地让程序员避免内存泄露,例如在线程池中使用ThreadLocal,如果不谨慎就可能导致内存泄露。 487 | 488 | 在线程池中使用ThreadLocal为什么可能导致内存泄露呢?原因就出在线程池中线程的存活时间太长,往往 489 | 都是和程序同生共死的,这就意味着Thread持有的ThreadLocalMap一直都不会被回收,再加上 490 | ThreadLocalMap中的Entry对ThreadLocal是弱引用(WeakReference),所以只要ThreadLocal结束了自己 491 | 的生命周期是可以被回收掉的。但是Entry中的Value却是被Entry强引用的,所以即便Value的生命周期结束 492 | 了,Value也是无法被回收的,从而导致内存泄露。那在线程池中,该如何正确使用ThreadLocal呢?其实很简单,既然JVM不能做到自动释放对Value的强引用,那使用try{}finally{}手动释放就可以了。 493 | 494 | 线程本地存储模式本质上是一种避免共享的方案,由于没有共享,所以自然也就没有并发问题。如果需要 495 | 在并发场景中使用一个线程不安全的工具类,最简单的方案就是避免共享。避免共享有两种方案,一种方案 496 | 是将这个工具类作为局部变量使用,另外一种方案就是线程本地存储模式。这两种方案,局部变量方案的缺 497 | 点是在高并发场景下会频繁创建对象,而线程本地存储方案,每个线程只需要创建一个工具类的实例,所以 498 | 不存在频繁创建对象的问题。 499 | 500 | 501 | #### Guarded Suspension模式 502 | 503 | 多线程编程中,往往将一个任务分解为不同的部分,将其交由不同的线程来执行,这些线程相互协作的时候,会出现一个线程等待另一个线程一定操作过后才能进行的情景,这个时候就需要这个线程退出执行。 504 | 505 | Suspension是“挂起”、“暂停”的意思,而Guarded则是“保护”的意思,连在一起就是**保护性地暂停**:当线程在访问某个对象时, 506 | 发现条件不满足,就暂时挂起等待条件满足时再次访问。 507 | 508 | 下图就是Guarded Suspension模式的结构图,非常简单,一个对象GuardedObject,内部有一个成员变量 509 | ——受保护的对象,以及两个成员方法——get(Predicate p)和onChanged(T obj)方法。GuardedObject的核心是:get() 510 | 方法通过条件变量的await()方法实现等待,onChanged()方法通过条件变量的signalAll()方法实现唤醒功能。 511 | 512 | ![](https://raw.githubusercontent.com/LLLRS/git_resource/master/12ghvsvhshjjvj.png) 513 | 514 | 下面的示例代码是扩展Guarded Suspension模式的实现,扩展 515 | 后的GuardedObject内部维护了一个Map,其Key是消息id,而Value是GuardedObject对象实例,同时 516 | 增加了静态方法create()和fireEvent();create()方法用来创建一个GuardedObject对象实例,并根据key值将 517 | 其加入到Map中,而fireEvent()方法则是根据id唤醒对应的GuardedObject对象。 518 | 519 | 520 | ``` 521 | public class GuardedObject{ 522 | //受保护的对象 523 | T obj; 524 | final Lock lock = new ReentrantLock(); 525 | final Condition done = lock.newCondition(); 526 | final int timeout=1; 527 | 528 | //保存所有GuardedObject 主要用于区分不同的消息 529 | final static Map gos=new ConcurrentHashMap<>(); 530 | //静态⽅法创建GuardedObject 531 | static GuardedObject create(K key){ 532 | GuardedObject go=new GuardedObject(); 533 | gos.put(key, go); 534 | return go; 535 | } 536 | 537 | static void fireEvent(K key, T obj){ 538 | GuardedObject go=gos.remove(key); 539 | if (go != null){ 540 | go.onChanged(obj); 541 | } 542 | } 543 | 544 | //获取受保护对象 545 | T get(Predicate p) { 546 | lock.lock(); 547 | try { 548 | //MESA管程推荐写法 549 | while(!p.test(obj)){ 550 | done.await(timeout, TimeUnit.SECONDS); 551 | } 552 | }catch(InterruptedException e){ 553 | throw new RuntimeException(e); 554 | }finally{ 555 | lock.unlock(); 556 | } 557 | 558 | //返回⾮空的受保护对象 559 | return obj; 560 | } 561 | 562 | 563 | //事件通知⽅法 564 | void onChanged(T obj) { 565 | lock.lock(); 566 | try { 567 | this.obj = obj; 568 | done.signalAll(); 569 | } finally { 570 | lock.unlock(); 571 | } 572 | } 573 | } 574 | 575 | ``` 576 | 577 | 578 | Guarded Suspension模式本质上是一种等待唤醒机制的实现,只不过Guarded Suspension模式将其规范化 579 | 了。规范化的好处是你无需重头思考如何实现,也无需担心实现程序的可理解性问题。 580 | 581 | 582 | #### Thread-Per-Message 583 | 584 | Thread-Per-Message模式是一种最简单实用的分工方法,简言之就是为每个任务分配一个独立的线程。比如一个HTTP Server,很显然只能在主线程中接收请求,而不能处理HTTP请求,因为如果在主线程中处理HTTP请求的话,那同一时间只能处理一个请求,太慢了!可以利用代办的思路,创建一个子线程,委托子线程去处理HTTP请求。 585 | 586 | Thread-Per-Message模式的一个最经典的应用场景是网络编程里服务端的实现,服务端为每个客户端请求 587 | 创建一个独立的线程,当线程处理完请求后,自动销毁,这是一种最简单的并发处理网络请求的方法。但是这种实现方案是不具备可行 588 | 性的,原因在于Java中的线程是一个重量级的对象,创建成本很高,一方面创建线程比较耗时,另一方面线 589 | 程占用的内存也比较大。所以,为每个请求创建一个新的线程并不适合高并发场景。但是Thread-Per-Message模式作为一种最简单的分工方案,Java语言支持不了,显然是Java语言本身的问题。 590 | 591 | Java语言里,Java线程是和操作系统线程一一对应的,这种做法本质上是将Java线程的调度权完全委托给操 592 | 作系统,而操作系统在这方面非常成熟,所以这种做法的好处是稳定、可靠,但是也继承了操作系统线程的 593 | 缺点:创建成本高。为了解决这个缺点,Java并发包里提供了线程池等工具类。这个思路在很长一段时间里 594 | 都是很稳妥的方案,但是这个方案并不是唯一的方案。 595 | 596 | 业界还有另外一种方案,叫做轻量级线程。这个方案在Java领域知名度并不高,但是在其他编程语言里却叫 597 | 得很响,例如Go语言、Lua语言里的协程,本质上就是一种轻量级的线程。轻量级的线程,创建的成本很 598 | 低,基本上和创建一个普通对象的成本相似;并且创建的速度和内存占用相比操作系统线程至少有一个数量 599 | 级的提升,所以基于轻量级线程实现Thread-Per-Message模式就完全没有问题了。 600 | 601 | #### WorkerThread模式 602 | 603 | Worker Thread模式能避免线程频繁创建、销毁的问题,而且能够限制线程的最大数量。Java语言里可以直 604 | 接使用线程池来实现Worker Thread模式,线程池是一个非常基础和优秀的工具类,甚至有些大厂的编码规 605 | 范都不允许用new Thread()来创建线程的,必须使用线程池。 606 | 607 | 不过使用线程池还是需要格外谨慎的,除了要了解如何正确创建线程池、如何避免线程死锁问题, 608 | 还需要注意使用ThreadLocal内存泄露问题。同时对于提交到线程池的任务,还要做好异常 609 | 处理,避免异常的任务从眼前溜走,从业务的角度看,有时没有发现异常的任务后果往往都很严重。 610 | 611 | 612 | 此外,使用线程池过程中,还要注意一种线程死锁的场景。如果提交到相同线程池的任务不是相互独立的,而是有依赖关系的,那么就有可能导致线程死锁。具体现象是应用每运行一段时间偶尔就会处于无响应的状态,监控数据看上去一切都正常,但是实际上已经不能正常工作了。有一个可能的原因是由于线程池中线程数目过少,导致相互依赖的线程不能执行,这种情况下最简单粗暴的办法就是将线程池的最大线程数调大,如果能够确定任务 613 | 的数量不是非常多的话,这个办法也是可行的,否则这个办法就行不通了。其实这种问题通用的解决方案是 614 | 为不同的任务创建不同的线程池。 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | -------------------------------------------------------------------------------- /notes/Java/Java的集合类.md: -------------------------------------------------------------------------------- 1 | Java的集合类 2 | ================ 3 | 4 | ![](media/dbcd6216766296a1a9b5053ff1894251.jpg) 5 | 6 | 7 | * [Hashtable](#hashtable) 8 | * [HashMap](#hashmap) 9 | * [JDK1\.7下的HashMap](#jdk17%E4%B8%8B%E7%9A%84hashmap) 10 | * [put方法](#put%E6%96%B9%E6%B3%95) 11 | * [JDK1\.8下的HashMap](#jdk18%E4%B8%8B%E7%9A%84hashmap) 12 | * [默认的参数](#%E9%BB%98%E8%AE%A4%E7%9A%84%E5%8F%82%E6%95%B0) 13 | * [get过程](#get%E8%BF%87%E7%A8%8B) 14 | * [put过程](#put%E8%BF%87%E7%A8%8B) 15 | * [扩容过程](#%E6%89%A9%E5%AE%B9%E8%BF%87%E7%A8%8B) 16 | * [HashSet](#hashset) 17 | * [TreeMap](#treemap) 18 | * [TreeMap实现原理](#treemap%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86) 19 | * [put方法](#put%E6%96%B9%E6%B3%95-1) 20 | * [get方法](#get%E6%96%B9%E6%B3%95) 21 | * [TreeSet](#treeset) 22 | * [LinkedHashMap](#linkedhashmap) 23 | * [List类](#list%E7%B1%BB) 24 | * [ArrayList](#arraylist) 25 | * [LinkedList](#linkedlist) 26 | * [Vector](#vector) 27 | * [迭代器](#%E8%BF%AD%E4%BB%A3%E5%99%A8) 28 | * [Iterator和ListIterator的区别](#iterator%E5%92%8Clistiterator%E7%9A%84%E5%8C%BA%E5%88%AB) 29 | * [快速失败(fail\-fast)和安全失败(fail\-safe)的区别](#%E5%BF%AB%E9%80%9F%E5%A4%B1%E8%B4%A5fail-fast%E5%92%8C%E5%AE%89%E5%85%A8%E5%A4%B1%E8%B4%A5fail-safe%E7%9A%84%E5%8C%BA%E5%88%AB) 30 | * [Java 8 中的 Streams](https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/) 31 | 32 | 33 | 34 | Hashtable 35 | ------------- 36 | 37 | > public class Hashtable extends Dictionary implements Map, Cloneable, java.io.Serializable 38 | 39 | Hashtable实现了一个哈希表,它将键映射到值。**任何非null对象**都可以用作键或值。Hashtable 的一个实例有两个影响其性能的参数:**初始容量和负载因子**。容量是哈希表中的桶数,初始容量只是创建哈希表时的容量。请注意,哈希表是打开的:在“哈希冲突”的情况下,单个存储桶存储多个条目,必须按顺序搜索。加载因子是在自动增加容量之前允许哈希表获取的完整程度的度量。通常,默认负载系数(.75)在时间和空间成本之间提供了良好的折衷。较高的值会减少空间开销,但会增加查找条目的时间成本(这反映在大多数Hashtable操作中,包括get和put)。 40 | 41 | 初始容量控制了浪费空间和重新运算操作的需要之间的权衡,这是非常耗时的。如果初始容量大于Hashtable将包含的最大条目数除以其加载因子,则不会发生重复操作。但是,将初始容量设置得太高会浪费空间。 42 | 43 | 如果要将多个条目设置为Hashtable,则以足够大的容量创建条目可以允许更有效地插入条目,而不是根据需要执行自动重新分组来扩展表。默认初始容量为11,扩容方式为旧值*2+1 44 | 45 | **put方法** 46 | ```leaf 47 | public synchronized V put(K key, V value) { 48 | // Make sure the value is not null 49 | if (value == null) { 50 | throw new NullPointerException(); 51 | } 52 | 53 | // Makes sure the key is not already in the hashtable. 54 | Entry tab[] = table; 55 | int hash = key.hashCode(); 56 | int index = (hash & 0x7FFFFFFF) % tab.length; 57 | @SuppressWarnings("unchecked") 58 | Entry entry = (Entry)tab[index]; 59 | for(; entry != null ; entry = entry.next) { 60 | if ((entry.hash == hash) && entry.key.equals(key)) { 61 | V old = entry.value; 62 | entry.value = value; 63 | return old; 64 | } 65 | } 66 | 67 | addEntry(hash, key, value, index); 68 | return null; 69 | } 70 | ``` 71 | 72 | **解决hash冲突** 73 | ```leaf 74 | protected void rehash() { 75 | int oldCapacity = table.length; 76 | Entry[] oldMap = table; 77 | 78 | // overflow-conscious code 79 | int newCapacity = (oldCapacity << 1) + 1; 80 | if (newCapacity - MAX_ARRAY_SIZE > 0) { 81 | if (oldCapacity == MAX_ARRAY_SIZE) 82 | // Keep running with MAX_ARRAY_SIZE buckets 83 | return; 84 | newCapacity = MAX_ARRAY_SIZE; 85 | //省略 86 | 87 | } 88 | } 89 | ``` 90 | 91 | 92 | HashMap 93 | ----------- 94 | 95 | ### JDK1.7下的HashMap 96 | 97 | JDK1.7下HashMap的底层实现是通过数组和链表实现的,基本操作和HashTable类似,但是HashMap不是线程安全的。HashMap的初始容量为16,且保证每次每次扩容以后HashMap的数组长度一定是2的次幂。 98 | 99 | #### put方法 100 | ```leaf 101 | public V put(K key, V value) { 102 | //如果table数组为空数组{},进行数组填充(为table分配实际内存空间),入参为threshold,此时threshold为initialCapacity 默认是1<<4(24=16) 103 | if (table == EMPTY_TABLE) { 104 | inflateTable(threshold); 105 | } 106 | //如果key为null,存储位置为table[0]或table[0]的冲突链上 107 | if (key == null) 108 | return putForNullKey(value); 109 | int hash = hash(key);//对key的hashcode进重新计算,确保散列均匀 110 | int i = indexFor(hash, table.length);//获取在table中的实际位置 111 | for (Entry e = table[i]; e != null; e = e.next) { 112 | //如果该对应数据已存在,执行覆盖操作。用新value替换旧value,并返回旧value 113 | Object k; 114 | if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 115 | V oldValue = e.value; 116 | e.value = value; 117 | e.recordAccess(this); 118 | return oldValue; 119 | } 120 | } 121 | modCount++;//保证并发访问时,若HashMap内部结构发生变化,快速响应失败 122 | addEntry(hash, key, value, i);//新增一个entry 123 | return null; 124 | } 125 | ``` 126 | 127 | 128 | ### JDK1.8下的HashMap 129 | 130 | JDK1.8下HashMap的底层实现是通过**数组和链表以及红黑树**实现的。当 hash碰撞之后写入链表的长度超过了阈值(默认为8),链表将会转换为红黑树。这样做的原因是假设hash 冲突非常严重,一个数组后面接了很长的链表,此时查询的时间复杂度就 是 O(n)。但如果是红黑树,时间复杂度就是 O(logn) 。 131 | 132 | #### 默认的参数 133 | ```leaf 134 | // 默认的最大容量 135 | static final int MAXIMUM_CAPACITY = 1 << 30; 136 | // 默认的加载因子 137 | static final float DEFAULT_LOAD_FACTOR = 0.75f; 138 | // 添加一个元素时默认的从链表转换为红黑树的大小 139 | static final int TREEIFY_THRESHOLD = 8; 140 | // resize时默认的从链表转换为红黑树的大小 141 | static final int UNTREEIFY_THRESHOLD = 6; 142 | // 可以树化的最小表的容量 143 | static final int MIN_TREEIFY_CAPACITY = 64; 144 | ``` 145 | 146 | 147 | #### get过程 148 | ```leaf 149 | public V get(Object key) { 150 | Node e; 151 | return (e = getNode(hash(key), key)) == null ? null : e.value; 152 | } 153 | 154 | final Node getNode(int hash, Object key) { 155 | Node[] tab; Node first, e; int n; K k; 156 | if ((tab = table) != null && (n = tab.length) > 0 && 157 | (first = tab[(n - 1) & hash]) != null) { 158 | // 判断第一个节点是不是就是需要的 159 | if (first.hash == hash && // always check first node 160 | ((k = first.key) == key || (key != null && key.equals(k)))) 161 | return first; 162 | if ((e = first.next) != null) { 163 | // 判断是否是红黑树 164 | if (first instanceof TreeNode) 165 | return ((TreeNode)first).getTreeNode(hash, key); 166 | 167 | // 链表遍历 168 | do { 169 | if (e.hash == hash && 170 | ((k = e.key) == key || (key != null && key.equals(k)))) 171 | return e; 172 | } while ((e = e.next) != null); 173 | } 174 | } 175 | return null; 176 | } 177 | ``` 178 | 179 | #### put过程 180 | ```leaf 181 | public V put(K key, V value) { 182 | return putVal(hash(key), key, value, false, true); 183 | } 184 | 185 | // 第三个参数 onlyIfAbsent 如果是 true,那么只有在不存在该 key 时才会进行 put 操作 186 | final V putVal(int hash, K key, V value, boolean onlyIfAbsent, 187 | boolean evict) { 188 | Node[] tab; Node p; int n, i; 189 | 190 | // 第一次 put 值的时候,会触发下面的 resize(),初始化数组长度 191 | if ((tab = table) == null || (n = tab.length) == 0) 192 | n = (tab = resize()).length; 193 | 194 | // 找到具体的数组下标,如果此位置没有值,那么直接初始化一下 Node并放置在这个位置 195 | if ((p = tab[i = (n - 1) & hash]) == null) 196 | tab[i] = newNode(hash, key, value, null); 197 | 198 | else {// 数组该位置有数据 199 | Node e; K k; 200 | // 判断该位置的第一个数据和要插入的数据,如果是,取出这个节点 201 | if (p.hash == hash && 202 | ((k = p.key) == key || (key != null && key.equals(k)))) 203 | e = p; 204 | // 如果该节点是代表红黑树的节点,调用红黑树的插值方法 205 | else if (p instanceof TreeNode) 206 | e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); 207 | else { 208 | // 到这里,说明数组该位置上是一个链表 209 | for (int binCount = 0; ; ++binCount) { 210 | // 插入到链表的最后面 211 | if ((e = p.next) == null) { 212 | p.next = newNode(hash, key, value, null); 213 | // TREEIFY_THRESHOLD 为 8,所以,如果新插入的值是链表中的第 9 个 214 | // 会触发下面的 treeifyBin,也就是将链表转换为红黑树 215 | if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 216 | treeifyBin(tab, hash); 217 | break; 218 | } 219 | // 如果在该链表中找到了"相等"的 key(== 或 equals) 220 | if (e.hash == hash && 221 | ((k = e.key) == key || (key != null && key.equals(k)))) 222 | // 此时 break,那么 e 为链表中[与要插入的新值的 key "相等"]的 node 223 | break; 224 | p = e; 225 | } 226 | } 227 | // e!=null 说明存在旧值的key与要插入的key"相等" 228 | // 下面这个 if 其实就是进行 "值覆盖",然后返回旧值 229 | if (e != null) { 230 | V oldValue = e.value; 231 | if (!onlyIfAbsent || oldValue == null) 232 | e.value = value; 233 | afterNodeAccess(e); 234 | return oldValue; 235 | } 236 | } 237 | ++modCount; 238 | // 如果 HashMap 由于新插入这个值导致 size 已经超过了阈值,需要进行扩容 239 | if (++size > threshold) 240 | resize(); 241 | afterNodeInsertion(evict); 242 | return null; 243 | } 244 | ``` 245 | JDK1.8中是在**链表的尾部插入元素**,且可能会将链表转换为红黑树。 246 | #### 扩容过程 247 | ```leaf 248 | final Node[] resize() { 249 | Node[] oldTab = table; 250 | int oldCap = (oldTab == null) ? 0 : oldTab.length; 251 | int oldThr = threshold; 252 | int newCap, newThr = 0; 253 | if (oldCap > 0) { // 对应数组扩容 254 | if (oldCap >= MAXIMUM_CAPACITY) { 255 | threshold = Integer.MAX_VALUE; 256 | return oldTab; 257 | } 258 | // 将数组大小扩大一倍 259 | else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && 260 | oldCap >= DEFAULT_INITIAL_CAPACITY) 261 | // 将阈值扩大一倍 262 | newThr = oldThr << 1; // double threshold 263 | } 264 | else if (oldThr > 0) // 对应使用 new HashMap(int initialCapacity) 初始化后,第一次 put 的时候 265 | newCap = oldThr; 266 | else {// 对应使用 new HashMap() 初始化后,第一次 put 的时候 267 | newCap = DEFAULT_INITIAL_CAPACITY; 268 | newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 269 | } 270 | 271 | if (newThr == 0) { 272 | float ft = (float)newCap * loadFactor; 273 | newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? 274 | (int)ft : Integer.MAX_VALUE); 275 | } 276 | threshold = newThr; 277 | 278 | // 用新的数组大小初始化新的数组 279 | Node[] newTab = (Node[])new Node[newCap]; 280 | table = newTab; // 如果是初始化数组,到这里就结束了,返回 newTab 即可 281 | 282 | if (oldTab != null) { 283 | // 开始遍历原数组,进行数据迁移。 284 | for (int j = 0; j < oldCap; ++j) { 285 | Node e; 286 | if ((e = oldTab[j]) != null) { 287 | oldTab[j] = null; 288 | // 如果该数组位置上只有单个元素,那就简单了,简单迁移这个元素就可以了 289 | if (e.next == null) 290 | newTab[e.hash & (newCap - 1)] = e; 291 | // 如果是红黑树,具体我们就不展开了 292 | else if (e instanceof TreeNode) 293 | ((TreeNode)e).split(this, newTab, j, oldCap); 294 | else { 295 | // 这块是处理链表的情况, 296 | // 需要将此链表拆成两个链表,放到新的数组中,并且保留原来的先后顺序 297 | // loHead、loTail 对应一条链表,hiHead、hiTail 对应另一条链表,代码还是比较简单的 298 | Node loHead = null, loTail = null; 299 | Node hiHead = null, hiTail = null; 300 | Node next; 301 | do { 302 | next = e.next; 303 | if ((e.hash & oldCap) == 0) { 304 | if (loTail == null) 305 | loHead = e; 306 | else 307 | loTail.next = e; 308 | loTail = e; 309 | } 310 | else { 311 | if (hiTail == null) 312 | hiHead = e; 313 | else 314 | hiTail.next = e; 315 | hiTail = e; 316 | } 317 | } while ((e = next) != null); 318 | if (loTail != null) { 319 | loTail.next = null; 320 | // 第一条链表 321 | newTab[j] = loHead; 322 | } 323 | if (hiTail != null) { 324 | hiTail.next = null; 325 | // 第二条链表的新的位置是 j + oldCap 326 | newTab[j + oldCap] = hiHead; 327 | } 328 | } 329 | } 330 | } 331 | } 332 | return newTab; 333 | } 334 | ``` 335 | 扩充HashMap的时候,不需要像JDK1.7的实现那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”。 336 | 这个设计确实非常的巧妙,既省去了重新计算hash值的时间,而且同时,由于新增的1bit是0还是1可以认为是随机的,因此resize的过程,均匀的把之前的冲突的节点分散到新的bucket了。这一块就是JDK1.8新增的优化点。有一点注意区别,JDK1.7中rehash的时候,旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置,JDK1.8不会倒置。 337 | 338 | 339 | ### HashSet 340 | 341 | 对于HashSet而言,它是基于HashMap实现的,HashSet底层使用HashMap来保存所有元素,因此HashSet的实现比较简单,相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成, 342 | 343 | [参见](https://zhangshixi.iteye.com/blog/673143) 344 | 345 | TreeMap 346 | ----------- 347 | 348 | ### TreeMap实现原理 349 | 350 | TreeMap的底层是用红黑树来实现的,红黑树的最大高度为2 \* log 351 | n。内部定义的节点的类如下: 352 | 353 | ```leaf 354 | static final class Entry implements Map.Entry { 355 | K key; 356 | V value; 357 | Entry left; 358 | Entry right; 359 | Entry parent; 360 | boolean color = BLACK; 361 | 362 | Entry(K key, V value, Entry parent) { 363 | this.key = key; 364 | this.value = value; 365 | this.parent = parent; 366 | } 367 | 368 | // 省略树的其他操作 369 | } 370 | ``` 371 | 372 | ### put方法 373 | ```leaf 374 | public V put(K key, V value) { 375 | Entry t = root; 376 | if (t == null) { 377 | compare(key, key); //检查key是否为null 378 | 379 | root = new Entry<>(key, value, null); 380 | size = 1; 381 | modCount++; 382 | return null; 383 | } 384 | int cmp; 385 | Entry parent; 386 | //插值的方法就是普通的二叉搜索树 387 | Comparator cpr = comparator; 388 | if (cpr != null) { //传入的 comparator 排序 389 | do { 390 | parent = t; 391 | cmp = cpr.compare(key, t.key); 392 | if (cmp < 0) 393 | t = t.left; 394 | else if (cmp > 0) 395 | t = t.right; 396 | else 397 | return t.setValue(value); 398 | } while (t != null); 399 | } 400 | else { // 自然排序 comparable 401 | if (key == null) 402 | throw new NullPointerException(); 403 | @SuppressWarnings("unchecked") 404 | Comparable k = (Comparable) key; 405 | do { 406 | parent = t; 407 | cmp = k.compareTo(t.key); 408 | if (cmp < 0) 409 | t = t.left; 410 | else if (cmp > 0) 411 | t = t.right; 412 | else 413 | return t.setValue(value); 414 | } while (t != null); 415 | } 416 | //插入的值在叶子节点 417 | Entry e = new Entry<>(key, value, parent); 418 | if (cmp < 0) 419 | parent.left = e; 420 | else 421 | parent.right = e; 422 | fixAfterInsertion(e); //平衡红黑树 423 | size++; 424 | modCount++; 425 | return null; 426 | } 427 | 428 | 429 | 430 | // comparator 为null 自然排序 否则就是按照传入的comparator实现类排序 431 | final int compare(Object k1, Object k2) { 432 | return comparator==null ? ((Comparable)k1).compareTo((K)k2) 433 | : comparator.compare((K)k1, (K)k2); 434 | } 435 | ``` 436 | ### get方法 437 | 438 | ``` 439 | public V get(Object key) { 440 | Entry p = getEntry(key); 441 | return (p==null ? null : p.value); 442 | } 443 | 444 | 445 | final Entry getEntry(Object key) { 446 | 447 | if (comparator != null) 448 | return getEntryUsingComparator(key); 449 | if (key == null) 450 | throw new NullPointerException(); 451 | @SuppressWarnings("unchecked") 452 | //根据自然排序查找 453 | Comparable k = (Comparable) key; 454 | Entry p = root; 455 | while (p != null) { 456 | int cmp = k.compareTo(p.key); 457 | if (cmp < 0) 458 | p = p.left; 459 | else if (cmp > 0) 460 | p = p.right; 461 | else 462 | return p; 463 | } 464 | return null; 465 | } 466 | 467 | // 根据传入的排序方式查找 468 | final Entry getEntryUsingComparator(Object key) { 469 | @SuppressWarnings("unchecked") 470 | K k = (K) key; 471 | Comparator cpr = comparator; 472 | if (cpr != null) { 473 | Entry p = root; 474 | while (p != null) { 475 | int cmp = cpr.compare(k, p.key); 476 | if (cmp < 0) 477 | p = p.left; 478 | else if (cmp > 0) 479 | p = p.right; 480 | else 481 | return p; 482 | } 483 | } 484 | return null; 485 | } 486 | 487 | ``` 488 | 489 | ### TreeSet 490 | 491 | 对于TreeSet而言,它是基于HashMap实现的,TreeSet底层使用TreeMap来保存所有元素,因此TreeSet 492 | 的实现比较简单,相关TreeSet的操作,基本上都是直接调用底层TreeMap的相关方法来完成。 493 | 494 | 495 | 496 | LinkedHashMap 497 | ----------------- 498 | 499 | LinkedHashMap 继承自 HashMap,在 HashMap 基础上,通过维护一条双向链表,解决了 500 | HashMap 不能随时保持遍历顺序和插入顺序一致的问题。除此之外,LinkedHashMap 501 | 对访问顺序(LRU)也提供了相关支持。 502 | 503 | LinkedHashMap的底层实现和HashMap完全一致,只是增加了两个头尾节点来维护一个双向链表: 504 | 505 | ![](media/bbc18b5eea9f2d4b2b79d77a6776a839.png) 506 | 507 | 通过查看源码可知,LinkedHashMap的get、put、resize、remove方法基本过程和HashMap基本一致。一些小细节是通过在LinkedHashMap重写来实现的 508 | 509 | 510 | 511 | List类 512 | ---------- 513 | 514 | ### ArrayList 515 | 516 | ArrayList是一个不安全的类,它的底层就是一个数组,当数组容量不够时,会触发扩容操作。 517 | 518 | 由于ArrayList的底层是数组,所以get操作的复杂度是O(1)的,在确认要取的位置有效后(rangeCheck),直接返回就行。add方法在将元素放在数组尾部前,会确认数组是否放的下,如果放不下就触发扩容操作。 519 | 520 | ArrayList就是动态数组,是Array的复杂版本,动态的增加和减少元素.当更多的元素加入到ArrayList中时,其大小将会动态地增长。它的元素可以通过get/set方法直接访问,因为ArrayList本质上是一个数组。初始容量为10,插入元素的时候可能扩容,删除元素时不会缩小容量,扩容增长为Arraylist增长原来的0.5倍 521 | 522 | 523 | ### LinkedList 524 | 525 | LinkedList也实现了List接口,是一个双向链表,内部定义的链表节点结构如下: 526 | 527 | ```leaf 528 | private static class Node { 529 | E item; 530 | Node next; 531 | Node prev; 532 | 533 | Node(Node prev, E element, Node next) { 534 | this.item = element; 535 | this.next = next; 536 | this.prev = prev; 537 | } 538 | } 539 | ``` 540 | 541 | LinkedList的get和插入方法最多遍历一般链表的长度。LinkedList是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList.当然,这些对比都是指数据量很大或者操作很频繁的情况下的对比。它还实现了Queue 542 | 接口,该接口比List提供了更多的方法,包括 offer(),peek(),poll()等。 543 | 544 | > **ArrayList和LinkedList都实现了List接口,有以下的不同点:** 545 | 546 | > ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以元素链表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n/2)。 547 | 548 | > 相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。 549 | 550 | > LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。 551 | 552 | ### Vector 553 | 554 | Vector也实现了List接口,继承了AbstractList类。它的底层和ArrayList一样,也是一个数组。基本的操作过程也一致,最大的区别是Vector是安全的。 555 | 556 | Vector和ArrayList类似,区别在于Vector是同步类(synchronized).因此,开销就比ArrayList要大。初始容量为10。实现了随机访问接口,可以随机访问。Vector是内部是 557 | 以动态数组的形式来存储数据的。Vector还可以设置增长的空间大小,默认是一倍, 558 | 559 | 560 | 迭代器 561 | ----------- 562 | 563 | ### Iterator和ListIterator的区别 564 | 565 | Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。 566 | 567 | ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引等等 568 | 569 | ### 快速失败(fail-fast)和安全失败(fail-safe)的区别 570 | 571 | **快速失败和安全失败是对迭代器而言的。** 572 | 573 | **快速失败(fail—fast)** 574 | 在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent 575 | Modification Exception。 576 | 577 | 原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 578 | 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。 579 | 580 | 注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount 581 | 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。 582 | 583 | 场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。 584 | 585 | **安全失败(fail—safe)** 586 | 587 | 采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。 588 | 589 | 原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改 590 | 591 | 并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。 592 | 593 | 缺点:基于拷贝内容的优点是避免了Concurrent Modification 594 | Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。 595 | 596 | 场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。 597 | -------------------------------------------------------------------------------- /notes/Java/media/1258jhvsavgkvbkgk433.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/1258jhvsavgkvbkgk433.png -------------------------------------------------------------------------------- /notes/Java/media/208dc9874b6f917427562c8a72e39788.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/208dc9874b6f917427562c8a72e39788.png -------------------------------------------------------------------------------- /notes/Java/media/3974bdbfd0b225c61a1d9772bc7c854e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/3974bdbfd0b225c61a1d9772bc7c854e.png -------------------------------------------------------------------------------- /notes/Java/media/41e2624139de35352255a2b1c7a0b8a7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/41e2624139de35352255a2b1c7a0b8a7.png -------------------------------------------------------------------------------- /notes/Java/media/4aedb9d9d47f02ec77420699a86f22a7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/4aedb9d9d47f02ec77420699a86f22a7.jpg -------------------------------------------------------------------------------- /notes/Java/media/54082c5bf8e8163e4fb1072c84687e85.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/54082c5bf8e8163e4fb1072c84687e85.png -------------------------------------------------------------------------------- /notes/Java/media/6286448d2f5f2cb602cb7113f3bfe545.emf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/6286448d2f5f2cb602cb7113f3bfe545.emf -------------------------------------------------------------------------------- /notes/Java/media/6286448d2f5f2cb602cb7113f3bfe545.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/6286448d2f5f2cb602cb7113f3bfe545.png -------------------------------------------------------------------------------- /notes/Java/media/753f5a40efd070b30d36e396d96e9645.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/753f5a40efd070b30d36e396d96e9645.png -------------------------------------------------------------------------------- /notes/Java/media/764c4cb9f6d7115ca743ab9f9583e6d1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/764c4cb9f6d7115ca743ab9f9583e6d1.jpg -------------------------------------------------------------------------------- /notes/Java/media/7b02d3d12c1f34841dc96a2c0a25f592.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/7b02d3d12c1f34841dc96a2c0a25f592.png -------------------------------------------------------------------------------- /notes/Java/media/7b663471926809bfa3123ab57f227638.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/7b663471926809bfa3123ab57f227638.png -------------------------------------------------------------------------------- /notes/Java/media/8802ffa13051fd93161f3a2ee6f2eb8e.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/8802ffa13051fd93161f3a2ee6f2eb8e.jpg -------------------------------------------------------------------------------- /notes/Java/media/93ef7634eaf36d4c9a8ad277e23d14c4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/93ef7634eaf36d4c9a8ad277e23d14c4.jpg -------------------------------------------------------------------------------- /notes/Java/media/9a2eb3a0f7caca0fa4e33717087884ac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/9a2eb3a0f7caca0fa4e33717087884ac.png -------------------------------------------------------------------------------- /notes/Java/media/9ab28550cbc32507a85cea984c74dfd1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/9ab28550cbc32507a85cea984c74dfd1.png -------------------------------------------------------------------------------- /notes/Java/media/9e2974141f1be5d88523a1aff1eacb41.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/9e2974141f1be5d88523a1aff1eacb41.jpg -------------------------------------------------------------------------------- /notes/Java/media/9f373407f86d3dac6f0245ef34171ee5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/9f373407f86d3dac6f0245ef34171ee5.png -------------------------------------------------------------------------------- /notes/Java/media/a5379cb3f718cd7763573726ce52ed64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/a5379cb3f718cd7763573726ce52ed64.png -------------------------------------------------------------------------------- /notes/Java/media/a5822525881f9cbbdff2cd538538ac99.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/a5822525881f9cbbdff2cd538538ac99.png -------------------------------------------------------------------------------- /notes/Java/media/b7bf23f28c7de6d6e7867cbaac6e29d4.emf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/b7bf23f28c7de6d6e7867cbaac6e29d4.emf -------------------------------------------------------------------------------- /notes/Java/media/b7bf23f28c7de6d6e7867cbaac6e29d4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/b7bf23f28c7de6d6e7867cbaac6e29d4.png -------------------------------------------------------------------------------- /notes/Java/media/b7f94146622574758fbe49f9785549ce.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/b7f94146622574758fbe49f9785549ce.jpg -------------------------------------------------------------------------------- /notes/Java/media/bbc18b5eea9f2d4b2b79d77a6776a839.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/bbc18b5eea9f2d4b2b79d77a6776a839.png -------------------------------------------------------------------------------- /notes/Java/media/cf1372bc829900ee9ebe3b98521c34d4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/cf1372bc829900ee9ebe3b98521c34d4.png -------------------------------------------------------------------------------- /notes/Java/media/d10508a4655d6acc7e26bc1e50ac7cfc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/d10508a4655d6acc7e26bc1e50ac7cfc.png -------------------------------------------------------------------------------- /notes/Java/media/d492ad6909c09905de51b046c99b027b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/d492ad6909c09905de51b046c99b027b.jpg -------------------------------------------------------------------------------- /notes/Java/media/dbcd6216766296a1a9b5053ff1894251.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/dbcd6216766296a1a9b5053ff1894251.jpg -------------------------------------------------------------------------------- /notes/Java/media/ee303233eb0cf4de50329461cdff508c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/Java/media/ee303233eb0cf4de50329461cdff508c.png -------------------------------------------------------------------------------- /notes/操作系统/media/3a0d08e33dda0f01697fe7f2f9ea8f5e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/操作系统/media/3a0d08e33dda0f01697fe7f2f9ea8f5e.png -------------------------------------------------------------------------------- /notes/操作系统/media/4f19a1f675d490c4dc07b767ba3ebd45.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/操作系统/media/4f19a1f675d490c4dc07b767ba3ebd45.png -------------------------------------------------------------------------------- /notes/操作系统/media/57bc457a739f100c896db967128230df.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/操作系统/media/57bc457a739f100c896db967128230df.jpg -------------------------------------------------------------------------------- /notes/操作系统/media/5ba1a623955657a7ffc9328fb09097d7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/操作系统/media/5ba1a623955657a7ffc9328fb09097d7.png -------------------------------------------------------------------------------- /notes/操作系统/media/834250de1e7d3307cc47f33e0f74682a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/操作系统/media/834250de1e7d3307cc47f33e0f74682a.png -------------------------------------------------------------------------------- /notes/操作系统/media/93a92127842011539d88efb7b88d8125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/操作系统/media/93a92127842011539d88efb7b88d8125.png -------------------------------------------------------------------------------- /notes/操作系统/media/a25008f2f0b4ef9bb9b66f88119a6967.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/操作系统/media/a25008f2f0b4ef9bb9b66f88119a6967.jpg -------------------------------------------------------------------------------- /notes/操作系统/media/b423c1f3dfa24f50752341f07fbf033b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/操作系统/media/b423c1f3dfa24f50752341f07fbf033b.png -------------------------------------------------------------------------------- /notes/操作系统/media/e51457ba11bd8cc822654621ab142b61.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/操作系统/media/e51457ba11bd8cc822654621ab142b61.png -------------------------------------------------------------------------------- /notes/操作系统/media/e95ea1ecb82227738c88286e31349b7b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/操作系统/media/e95ea1ecb82227738c88286e31349b7b.jpg -------------------------------------------------------------------------------- /notes/数据库/MySql.md: -------------------------------------------------------------------------------- 1 | 2 | * [索引](#%E7%B4%A2%E5%BC%95) 3 | * [索引背后的数据结构](#%E7%B4%A2%E5%BC%95%E8%83%8C%E5%90%8E%E7%9A%84%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84) 4 | * [B树](#b%E6%A0%91) 5 | * [B\+树](#b%E6%A0%91-1) 6 | * [什么是B\+ Tree](#%E4%BB%80%E4%B9%88%E6%98%AFb-tree) 7 | * [索引分类](#%E7%B4%A2%E5%BC%95%E5%88%86%E7%B1%BB) 8 | * [B\+Tree 索引](#btree-%E7%B4%A2%E5%BC%95) 9 | * [哈希索引](#%E5%93%88%E5%B8%8C%E7%B4%A2%E5%BC%95) 10 | * [全文索引](#%E5%85%A8%E6%96%87%E7%B4%A2%E5%BC%95) 11 | * [空间数据索引(R\-Tree)](#%E7%A9%BA%E9%97%B4%E6%95%B0%E6%8D%AE%E7%B4%A2%E5%BC%95r-tree) 12 | * [索引的优点](#%E7%B4%A2%E5%BC%95%E7%9A%84%E4%BC%98%E7%82%B9) 13 | * [索引下推](#%E7%B4%A2%E5%BC%95%E4%B8%8B%E6%8E%A8) 14 | * [InnoDB与MyISAM引擎区别](#innodb%E4%B8%8Emyisam%E5%BC%95%E6%93%8E%E5%8C%BA%E5%88%AB) 15 | * [MyISAM](#myisam) 16 | * [InnoDB](#innodb) 17 | * [数据库特性](#%E6%95%B0%E6%8D%AE%E5%BA%93%E7%89%B9%E6%80%A7) 18 | * [事务隔离级别](#%E4%BA%8B%E5%8A%A1%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB) 19 | * [并发下事务会产生的问题](#%E5%B9%B6%E5%8F%91%E4%B8%8B%E4%BA%8B%E5%8A%A1%E4%BC%9A%E4%BA%A7%E7%94%9F%E7%9A%84%E9%97%AE%E9%A2%98) 20 | * [事务隔离级别](#%E4%BA%8B%E5%8A%A1%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB-1) 21 | * [SQL优化](#sql%E4%BC%98%E5%8C%96) 22 | * [数据库范式](#%E6%95%B0%E6%8D%AE%E5%BA%93%E8%8C%83%E5%BC%8F) 23 | * [数据库连接池](#%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%B1%A0) 24 | * [为什么会有数据库连接池](#%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BC%9A%E6%9C%89%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%B1%A0) 25 | * [数据库连接池的实现](#%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%B1%A0%E7%9A%84%E5%AE%9E%E7%8E%B0) 26 | * [Explain](#explain) 27 | * [分库分表](#%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8) 28 | * [其它问题](#%E5%85%B6%E5%AE%83%E9%97%AE%E9%A2%98) 29 | * [limit 20000 如何优化](#limit-20000-%E5%A6%82%E4%BD%95%E4%BC%98%E5%8C%96) 30 | * [隔离级别如何实现](#%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0) 31 | * [drop delete truncate区别](#drop-delete-truncate%E5%8C%BA%E5%88%AB) 32 | * [超键、候选键、主键、外键 视图](#%E8%B6%85%E9%94%AE%E5%80%99%E9%80%89%E9%94%AE%E4%B8%BB%E9%94%AE%E5%A4%96%E9%94%AE-%E8%A7%86%E5%9B%BE) 33 | * [MVCC多版本并发控制](#mvcc%E5%A4%9A%E7%89%88%E6%9C%AC%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6) 34 | * [SQL语句的5个连接概念](#sql%E8%AF%AD%E5%8F%A5%E7%9A%845%E4%B8%AA%E8%BF%9E%E6%8E%A5%E6%A6%82%E5%BF%B5) 35 | * [索引的最左前缀原则](#%E7%B4%A2%E5%BC%95%E7%9A%84%E6%9C%80%E5%B7%A6%E5%89%8D%E7%BC%80%E5%8E%9F%E5%88%99) 36 | * [跨库join](#%E8%B7%A8%E5%BA%93join) 37 | * [SQL经典50道](https://zhuanlan.zhihu.com/p/72223558?utm_source=wechat_session&utm_medium=social&utm_oi=668899885558927360) 38 | 39 | 40 | 索引 41 | -------- 42 | 43 | **索引(Index)是帮助MySQL高效获取数据的数据结构。索引是在存储引擎层实现的,** 44 | 45 | **而不是在服务器层实现的,所以不同存储引擎具有不同的索引类型和实现。** 46 | 47 | 48 | 49 | ### 索引背后的数据结构 50 | 51 | #### B树 52 | 53 | > **B树又叫平衡多路查找树。** 54 | 55 | ![](media/d802bdad2d733857287cf627ea12efc2.jpg) 56 | 57 | 58 | 59 | > B树的一个例子(小红方块表示对应关键字所代表的文件的存储位置,P是指向下一个磁盘块的指针): 60 | > 61 | 62 | ![](media/df9a8160b6b13dbe46660b46cb9be344.png) 63 | 64 | 65 | 66 | #### B+树 67 | 68 | > **B+tree是应文件系统所需而产生的一种Btree的变形树。** 69 | B+树中所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接。所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。 70 | 71 | ![](media/3ce21bb5c36a79cdd965f2a5b564efc8.png) 72 | 73 | #### 什么是B+ Tree 74 | 75 | > 红黑树等平衡树也可以用来实现索引,但是文件系统及数据库系统普遍采用 B+ Tree 76 | > 作为索引结构,主要有以下两个原因: 77 | 78 | > **1、B+树的磁盘读写代价更低** 79 | 80 | > B+的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。 81 | 82 | > **2、B+树的查询效率更加稳定** 83 | 84 | > 由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。 85 | 86 | > **3、B+树更有利于对数据库的扫描** 87 | 88 | > B树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题,而B+树只需要遍历叶子节点就可以解决对全部关键字信息的扫描,所以对于数据库中频繁使用的range 89 | > query,B+树有着更高的性能。 90 | 91 | ### 索引分类 92 | 93 | #### B+Tree 索引 94 | 95 | **B+Tree 索引是大多数 MySQL存储引擎的默认索引类型。** 96 | 因为不再需要进行全表扫描,只需要对树进行搜索即可,因此查找速度快很多。除了用于查找,还可以用于排序和分组。可以指定多个列作为索引列,多个索引列共同组成键。B+Tree索引适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。如果不是按照索引列的顺序进行查找,则无法使用索引。 97 | 98 | **普通索引:** 99 | 最基本的索引类型,而且它没有唯一性之类的限制。 100 | 101 | **UNIQUE索引:** 102 | 表示唯一的,不允许重复的索引,可以为NULL值,一个表可以有多个唯一索引。 103 | 104 | **主键索引:** 105 | 主键是一种唯一性索引,不能为NULL值,只能有一个。 106 | 107 | #### 哈希索引 108 | 109 | InnoDB 110 | 引擎有一个特殊的功能叫**自适应哈希索引** 111 | ,当某个索引值被使用的非常频繁时,会在 112 | B+Tree 索引之上再创建一个哈希索引,这样就让 B+Tree 113 | 索引具有哈希索引的一些优点,比如快速的哈希查找。哈希索引能以 O(1) 114 | 时间进行查找,但是失去了有序性,它具有以下限制: 115 | 116 | * 无法用于排序与分组; 117 | * 只支持精确查找,无法用于部分查找和范围查找; 118 | 119 | #### 全文索引 120 | 121 | **MyISAM 122 | 存储引擎支持全文索引,** 123 | 用于查找文本中的关键词,而不是直接比较是否相等。查找条件使用MATCH AGAINST,而不是普通的 124 | WHERE。全文索引一般使用倒排索引实现,它记录着关键词到其所在文档的映射。InnoDB 125 | 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。 126 | 127 | #### 空间数据索引(R-Tree) 128 | 129 | MyISAM 130 | 存储引擎支持空间数据索引,可以用于地理数据存储。空间数据索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。必须使用 131 | GIS 相关的函数来维护数据。 132 | 133 | **联合索引生效的条件、索引失效的条件** : https://blog.csdn.net/qq_35275233/article/details/87888809 134 | 135 | #### 索引的优点 136 | 137 | 1、大大减少了服务器需要扫描的数据行数。 138 | 139 | 2、帮助服务器避免进行排序和创建临时表(B+Tree 索引是有序的,可以用来做 ORDER BY 140 | 和 GROUP BY 操作); 141 | 142 | 3、将随机 I/O 变为顺序 I/O(B+Tree 143 | 索引是有序的,也就将相邻的数据都存储在一起)。 144 | 145 | 索引能够极大地提高数据检索效率,也能够改善排序分组操作的性能,但有不能忽略的一个问题就是索引是完全独立于基础数据之外的一部分数据。假设在Table 146 | ta 中的Column ca 创建了索引 idx_ta_ca,那么任何更新 Column ca 147 | 的操作,MySQL在更新表中 Column ca的同时,都须要更新Column ca 148 | 的索引数据,调整因为更新带来键值变化的索引信息。而如果没有对 Column ca 149 | 进行索引,MySQL要做的仅仅是更新表中 Column ca 150 | 的信息。这样,最明显的资源消耗就是增加了更新所带来的 IO 151 | 量和调整索引所致的计算量。此外,Column ca 152 | 的索引idx_ta_ca须要占用存储空间,而且随着 Table ta 数据量的增加,idx_ta_ca 153 | 所占用的空间也会不断增加,所以索引还会带来存储空间资源消耗的增加。 154 | 155 | #### 索引下推 156 | 157 | 索引下推(index condition pushdown )简称ICP,是MySql在5.6之后进行的一种优化。 158 | 159 | 如果禁用ICP,引擎层会穿过索引在基表中寻找数据行,然后返回给MySQL 160 | Server层,再去为这些数据行进行WHERE后的条件的过滤。 161 | 162 | ![http://blog.codinglabs.org/uploads/pictures/index-condition-pushdown/01.png](media/ddedd0b3683e99c97ac56be9fb1971d7.png) 163 | 164 | 如果ICP启用,如果部分WHERE条件能使用索引中的字段,MySQL Server 165 | 会把这部分下推到引擎层。存储引擎通过使用索引条目,然后推索引条件进行评估,使用这个索引把满足的行从表中读取出。 166 | 167 | ![http://blog.codinglabs.org/uploads/pictures/index-condition-pushdown/02.png](media/734a962da6997171b7f295dc2f6a1655.png) 168 | 169 | 综上:ICP能减少引擎层访问基表的次数和MySQL Server 访问存储引擎的次数。总之是 170 | ICP的优化在引擎层就能够过滤掉大量的数据,这样无疑能够减少了对base table和mysql 171 | server的访问次数。 172 | 173 | **注意事项:** 174 | 175 | 1.ICP只能用于二级索引,不能用于主索引。 176 | 177 | 2.也不是全部where条件都可以用ICP筛选,如果某where条件的字段不在索引中,当然还是要读取整条记录做筛选,在这种情况下,仍然要到server端做where筛选。 178 | 179 | 3.ICP的加速效果取决于在存储引擎内通过ICP筛选掉的数据的比例。 180 | 181 | InnoDB与MyISAM引擎区别 182 | -------------------------- 183 | 184 | ### MyISAM 185 | 186 | **MySql5.5之前的默认引擎,特点如下:** 187 | 188 | > 不支持行锁,读取是需要对读到的所有表加锁,写入时则对表加排他锁。 189 | 190 | > 不支持事务和外键。 191 | 192 | > 不支持崩溃后的安全恢复。 193 | 194 | > 在表有读取查询的同时,支持往表中插入新纪录。 195 | 196 | > 支持BLOB和TEXT的前600个字符索引,支持全文索引。 197 | 198 | > 支持延迟更新索引,极大的提升了写入性能。 199 | 200 | > 对于不会进行修改的表,支持压缩表,减少了磁盘空间占用。 201 | 202 | ### InnoDB 203 | 204 | > MySql5.5之后的默认引擎,特点如下: 205 | 206 | > 支持行锁,采用MVCC来支持高并发,可能死锁。 207 | 208 | > 支持事务和外键。 209 | 210 | > 支持崩溃后的安全恢复。 211 | 212 | > 不支持全文索引。 213 | 214 | 总而言之,MyISAM适合读密集的表,而InnoDB适合写密集的表。 215 | 216 | **MylSAM:**BTree叶节点的data域存放的是数据记录的地址。检索时,首先,按照BTree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为地址读取相应的数据记录。这被称非聚簇索引。 217 | 218 | **InnoDB:**其数据文件本身就是就是索引文件,树的叶子节点的data域保存了完整的数据记录。如果这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引,这被称为聚簇索引。而其余的索引都被称为辅助索引,辅助索引的data域记录相应的主键的值。根据主索引搜索时,直接到key所在的节点索引,即可取出数据。再根据辅助索引查找时,则需要先取出主键的值,再走一遍主索引。 219 | 220 | 数据库特性 221 | -------------- 222 | 223 | **事务是访问数据库的一个操作序列**,数据库应用系统通过事务集来完成对数据库的存取。事务的正确执行使得数据库从一种状态转换为另一种状态。事务必须服从ACID原则。ACID是原子性(atomicity)、一致性(consistency)、隔离性(isolation)、持久性(durability)的缩写,这四种状态的意思是: 224 | 225 | **1、原子性** 226 | 227 | 即不可分割,事务要么全部被执行,要么全部不执行。如果事务的所有子事务全部提交成功,则所有的数据库操作被提交,数据库状态发生变化;如果有子事务失败,则其他子事务的数据库操作被回滚,即数据库回到事务执行前的状态,不会发生状态转换 228 | 229 | **2、一致性** 230 | 231 | 事务的执行使得数据库从一种正确状态转换成另外一种正确状态 232 | 233 | **3、隔离性** 234 | 235 | 在事务正确提交之前,不允许把事务对该数据的改变提供给任何其他事务,即在事务正确提交之前,它可能的结果不应该显示给其他事务 236 | 237 | **4、持久性** 238 | 239 | 事务正确提交之后,其结果将永远保存在数据库之中,即使在事务提交之后有了其他故障,事务的处理结果也会得到保存 240 | 241 | **事务的作用** 242 | 243 | 事务管理对于企业级应用而言至关重要,它保证了用户的每一次操作都是可靠的,即便出现了异常的访问情况,也不至于破坏后台数据的完整性。就像银行的自动提款机ATM,通常ATM都可以正常为客户服务,但是也难免遇到操作过程中及其突然出故障的情况,此时,事务就必须确保出故障前对账户的操作不生效,就像用户刚才完全没有使用过ATM机一样,以保证用户和银行的利益都不受损失。 244 | 245 | 事务隔离级别 246 | ---------------- 247 | 248 | ### 并发下事务会产生的问题 249 | 250 | **1、脏读** 251 | 252 | 所谓脏读,就是指事务A读到了事务B还没有提交的数据,比如银行取钱,事务A开启事务,此时切换到事务B,事务B开启事务--\>取走100元,此时切换回事务A,事务A读取的肯定是数据库里面的原始数据,因为事务B取走了100块钱,并没有提交,数据库里面的账务余额肯定还是原始余额,这就是脏读。 253 | 254 | **2、不可重复读** 255 | 256 | 所谓不可重复读,就是指在一个事务里面读取了两次某个数据,读出来的数据不一致。还是以银行取钱为例,事务A开启事务--\>查出银行卡余额为1000元,此时切换到事务B事务B开启事务--\>事务B取走100元--\>提交,数据库里面余额变为900元,此时切换回事务A,事务A再查一次查出账户余额为900元,这样对事务A而言,在同一个事务内两次读取账户余额数据不一致,这就是不可重复读。 257 | 258 | **3、幻读** 259 | 260 | 所谓幻读,就是指在一个事务里面的操作中发现了未被操作的数据。比如学生信息,事务A开启事务--\>修改所有学生当天签到状况为false,此时切换到事务B,事务B开启事务--\>事务B插入了一条学生数据,此时切换回事务A,事务A提交的时候发现了一条自己没有修改过的数据,这就是幻读,就好像发生了幻觉一样。**幻读出现的前提是并发的事务中有事务发生了插入、删除操作。** 261 | 262 | ### 事务隔离级别 263 | 264 | 事务隔离级别,就是为了解决上面几种问题而诞生的。为什么要有事务隔离级别,因为事务隔离级别越高,在并发下会产生的问题就越少,但同时付出的性能消耗也将越大,因此很多时候必须在并发性和性能之间做一个权衡。所以设立了几种事务隔离级别,以便让不同的项目可以根据自己项目的并发情况选择合适的事务隔离级别,对于在事务隔离级别之外会产生的并发问题,在代码中做补偿。 265 | 266 | 事务隔离级别有4种: 267 | 268 | **1、READ_UNCOMMITTED** 269 | 270 | 读未提交,即能够读取到没有被提交的数据,所以很明显这个级别的隔离机制无法解决脏读、不可重复读、幻读中的任何一种,因此很少使用. 271 | 272 | **2、READ_COMMITED** 273 | 274 | 读已提交,即能够读到那些已经提交的数据,自然能够防止脏读,但是无法限制不可重复读和幻读。 275 | 276 | **3、REPEATABLE_READ** 277 | 278 | 重复读取,即在数据读出来之后加锁,类似"select \* from XXX for 279 | update",明确数据读取出来就是为了更新用的,所以要加一把锁,防止别人修改它。REPEATABLE_READ的意思也类似,读取了一条数据,这个事务不结束,别的事务就不可以改这条记录,这样就解决了脏读、不可重复读的问题,但是幻读的问题还是无法解决 280 | 281 | **4、SERLALIZABLE** 282 | 283 | 串行化,最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,这样就解决了脏读、不可重复读和幻读的问题了 284 | 285 | ![](media/221cc78a1d285ec5c774d2c7087efffd.png) 286 | 287 | SQL优化 288 | ----------- 289 | 290 | ![](media/c9beb8350bc758c62d845c38a5a497b7.png) 291 | 292 | ![](media/547d487238dbe2d2fcc64c67876bfcfc.png) 293 | 294 | ![](media/787dd3d1a08f120c8118d6554cac2fda.png) 295 | 296 | ![](media/7ee11229e2d1d4ef3c2396359d38ce0a.png) 297 | 298 | ![](media/2a7316dccbdedd2fb8fec00b36daf09c.png) 299 | 300 | ![](media/8b4b518fa85afcb8a7dd7d1353dbd4f9.png) 301 | 302 | 数据库范式 303 | -------------- 304 | 305 | **第一范式:** 306 | 数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。 307 | 308 | **第二范式:** 309 | 在满足第一范式的基础上,实体的每个非主键属性完全函数依赖于主键属性。 310 | 311 | **第三范式:** 312 | 在满足第二范式的基础上,在实体中不存在非主键属性传递函数依赖于主键属性。 313 | 314 | 具体例子参考: 315 | 316 | 317 | 数据库连接池 318 | ---------------- 319 | 320 | ### 为什么会有数据库连接池 321 | 322 | 一般来说,Java应用程序访问数据库的过程是: 323 | 324 | > ①装载数据库驱动程序; 325 | 326 | > ②建立数据库连接; 327 | 328 | > ③访问数据库,执行sql语句; 329 | 330 | > ④断开数据库连接。 331 | 332 | **分析:** 333 | 334 | 首先,每一次web请求都要建立一次数据库连接。建立连接是一个费时的活动,每次都得花费0.05s~1s的时间,而且系统还要分配内存资源。这个时间对于一次或几次数据库操作,或许感觉不出系统有多大的开销。可是对于现在的web应用,尤其是大型电子商务网站,同时有几百人甚至几千人在线是很正常的事。在这种情况下,频繁的进行数据库连接操作势必占用很多的系统资源,网站的响应速度必定下降,严重的甚至会造成服务器的崩溃。 335 | 336 | 其次,对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将不得不重启数据库。还有,这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。 337 | 338 | 总的来看,“数据库连接”是一种稀缺的资源,为了保障网站的正常使用,应该对其进行妥善管理。其实我们查询完数据库后,如果不关闭连接,而是暂时存放起来,当别人使用时,把这个连接给他们使用。就避免了一次建立数据库连接和断开的操作时间消耗。 339 | 340 | 341 | 342 | ### 数据库连接池的实现 343 | 344 | 现在很多WEB服务器(Weblogic, WebSphere, 345 | Tomcat)都提供了DataSoruce的实现,即连接池的实现。也有一些开源组织提供了数据源的独立实现: 346 | 347 | **DBCP 数据库连接池** 348 | 349 | **C3P0 数据库连接池** 350 | 351 | 在使用了数据库连接池之后,在项目的实际开发中就不需要编写连接数据库的代码了,直接从数据源获得数据库的连接。 352 | 353 | 354 | 355 | 356 | ### Explain 357 | 358 | **EXPLAIN命令是查看优化器如何决定执行查询的主要方法。** 359 | 360 | **Explain命令详解** 361 | 362 | ![](media/ab49f5fe6265ebca35624d11125dccdf.png) 363 | 364 | 1.ID 365 | 366 | **包含一组数字,表示查询中执行select子句或操作表的顺序。** 367 | 368 | **2.select_type** 369 | 370 | **表示查询中每个select子句的类型(简单OR复杂)** 371 | 372 | **a.SIMPLE:查询中不包含子查询或者UNION** 373 | **b.查询中若包含任何复杂的子部分,最外层查询则被标记为:PRIMARY** 374 | **c.在SELECT或WHERE列表中包含了子查询,该子查询被标记为:SUBQUERY** 375 | **d. 376 | 在FROM列表中包含的子查询被标记为:DERIVED(衍生)用来表示包含在from子句中的子查询的select,mysql会递归执行并将结果放到一个临时表中。服务器内部称为"派生表",因为该临时表是从子查询中派生出来的** 377 | **e.若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中,外层SELECT将被标记为:DERIVED** 378 | **f.从UNION表获取结果的SELECT被标记为:UNION RESULT** 379 | 380 | **3.type** 381 | 382 | 表示MySQL在表中找到所需行的方式,又称“访问类型”,常见类型如下: 383 | 384 | **ALL, index, range,ref, eq_ref, const, system, 385 | NULL(全局遍历啦,还是索引查询等)** 386 | 387 | **4.possible_keys** 388 | 389 | **指出MySQL能使用哪个索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用(表中的某一列上有多个索引都和这个有关系,索引建立在这个字段上了,那么都列出来)** 390 | 391 | **5.key** 392 | 393 | **显示MySQL在查询中实际使用的索引,若没有使用索引,显示为NULL** 394 | 395 | **6.key_len** 396 | 397 | **表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度** 398 | 399 | **7. ref** 400 | 401 | **表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值** 402 | 403 | **8. rows** 404 | 405 | **表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数** 406 | 407 | **9. Extra** 408 | 409 | **包含不适合在其他列中显示但十分重要的额外信息** 410 | 411 | **10.table** 412 | 413 | **Select 查询的表的名字** 414 | 415 | 416 | 417 | ### 分库分表 418 | 419 | 420 | **分表(水平划分):复制相同的表、库、服务器来共同承担压力。** 421 | 422 | 对于访问极为频繁且数据量巨大的单表来说,首先要做的就是减少**单表的记录条数**,以便减少数据查询所需要的时间,提高数据库的吞吐。 423 | 424 | 水平切分又称为Sharding,它是将同一个表中的记录拆分到**多个结构相同的表**中。当一个表的数据不断增多时,Sharding 425 | 是必然的选择,它可以将数据分布到集群的不同节点上,从而缓存单个数据库的压力。 426 | 427 | **分库(垂直切分):拆分功能为小功能,来转移压力的发生。** 428 | 429 | 分表能够解决单表数据量过大带来的查询效率下降的问题,但是,却无法给数据库的**并发处理能力**带来质的提升。所以必须对数据库进行拆分,从而提高数据库写入能力**。** 430 | 431 | **分库分表:**有时数据库可能既面临着**高并发访问**的压力,又需要面对**海量数据的存储**问题,这时需要对数据库既采用分表策略,又采用分库策略,以便同时扩展系统的并发处理能力,以及提升单表的查询性能,这就是所谓的分库分表。 432 | 433 | 434 | 其它问题 435 | ------------- 436 | 437 | ### limit 20000 如何优化 438 | 439 | 传统分页查询:SELECT c1,c2,cn… FROM table LIMIT n,m 440 | 441 | MySQL的limit工作原理就是先读取前面n条记录,然后抛弃前n条,读后面m条想要的,所以n越大,偏移量越大,性能就越差。 442 | 443 | 1、**尽量给出查询的大致范围** 444 | 445 | > SELECT c1,c2,cn... FROM table WHERE id\>=20000 LIMIT 10; 446 | 447 | **2、子查询法** 448 | 449 | > SELECT c1,c2,cn... FROM table WHERE id\>= 450 | 451 | > ( 452 | 453 | > SELECT id FROM table LIMIT 20000,1 454 | 455 | > ) 456 | 457 | > LIMIT 10; 458 | 459 | **3、只读索引方法** 460 | 461 | 优化前SQL: 462 | 463 | > SELECT c1,c2,cn... FROM member ORDER BY last_active LIMIT 50,5 464 | 465 | **优化后SQL:** 466 | 467 | > SELECT c1, c2, cn .. . 468 | 469 | > FROM member 470 | 471 | > INNER JOIN (SELECT member_id FROM member ORDER BY last_active LIMIT 50, 5) 472 | 473 | > USING (member_id) 474 | 475 | 分别在于,优化前的SQL需要更多I/O浪费,因为先读索引,再读数据,然后抛弃无需的行。而优化后的SQL(子查询那条)只读索引(Cover 476 | index)就可以了,然后通过member_id读取需要的列。 477 | 478 | 4、第一步用用程序读取出ID,然后再用IN方法读取所需记录 479 | 480 | 程序读ID: 481 | 482 | > SELECT id FROM table LIMIT 20000, 10; 483 | 484 | > SELECT c1, c2, cn .. . FROM table WHERE id IN (id1, id2, idn.. .) 485 | 486 | ### 隔离级别如何实现 487 | 488 | **1、Read 489 | uncommitted:** 490 | 事务对当前读的数据不加锁;事务在更新数据的瞬间(就是发生更新的瞬间),必须先对其加行级**共享锁**,直到事务结束释放。 491 | 492 | **表现:** 493 | 事务1读取记录时,事务2可以对这条记录读取,更新;当事务2对该记录更新时,事务1再次读取该记录,能读到事务2 494 | 对这条记录修改的版本,即使事务2未提交; 495 | 496 | 事务1更新某一行记录时,事务2不能对这条记录做更新,直到事务1结束。 497 | 498 | **2、Read committed** 499 | :事务对当前被读取的数据加行级共享锁(当读到时才加锁),一旦读完该行立即释放该行级共享锁;事务在更新某数据的瞬间(发生更新的瞬间),必须先对其加行级排它锁,直到事务结束才释放。 500 | 501 | **表现:** 502 | 事务1读取某行数据时,事务2也能对这条记录进行读取,更新;当事务2对该记录更新时,事务1再次读取该记录,读取到的只能是对其更新前的版本;事务1更新某行记录时,事务2不能对这行记录更新,直到事务1结束。 503 | 504 | **3、Repeated Read:** 505 | 事务在读取某数据的瞬间(开始读取的瞬间),必须先对其加行级共享锁,直到事务结束时才释放;事务在更新数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁,直到事务结束释放。 506 | 507 | **表现**: 508 | 事务1读取某行记录时,事务2也能对这条记录进行读取,更新;事务2对该记录进行更新时,事务1再次读取,读取到的仍然是第一次读取的那个版本;事务1更新某行记录时,事务2不能对这行记录做更新,直到事务1结束。 509 | 510 | **4、serializable:事务在**读取数据时,必须先对其加表级共享锁,直到事务结束才释放;事务在更新数据时,必须先对其先加表级排它锁,直到事务结束才释放。 511 | 512 | **表现:** 513 | 事务1正在读取A表中的记录,则事务2也能读取A表,但不能对A表新增,更新,删除,直到事务结束;事务1正在更新A表的记录,事务2不能读取A表的任意记录,更不能对A表新增,更新,删除,直到事务1结束。 514 | 515 | ### drop delete truncate区别 516 | 517 | delete和truncate只删除表的数据不删除表的结构 518 | 519 | 速度,一般来说: drop\> truncate \>delete 520 | 521 | delete语句是dml,这个操作会放到rollback segement中,事务提交之后才生效; 522 | 523 | 如果有相应的trigger,执行的时候将被触发. truncate,drop是ddl, 524 | 操作立即生效,原数据不放到rollback segment中,不能回滚. 操作不触发trigger. 525 | 526 | drop直接删掉表。 527 | 528 | truncate删除表中数据,再插入时自增长id又从1开始。 529 | 530 | delete删除表中数据,可以加where字句。 531 | 532 | ### 超键、候选键、主键、外键 视图 533 | 534 | **超键:** 535 | 在关系中能唯一标识元组的属性集称为关系模式的超键。一个属性可以为作为一个超键,多个属性组合在一起也可以作为一个超键。超键包含候选键和主键。 536 | 537 | **候选键:** 538 | 是最小超键,即没有冗余元素的超键。 539 | 540 | **主键:** 541 | 数据库表中对储存数据对象予以唯一和完整标识的数据列或属性的组合。一个数据列只能有一个主键,且主键的取值不能缺失,即不能为空值(Null)。 542 | 543 | **外键:** 544 | 在一个表中存在的另一个表的主键称此表的外键。 545 | 546 | **视图**:是一种虚拟的表,具有和物理表相同的功能。可以对视图进行增,改,查,操作,视图通常是有一个表或者多个表的行或列的子集。对视图的修改不影响基本表。它使得我们获取数据更容易,相比多表查询。 547 | 548 | ### MVCC多版本并发控制 549 | 550 | MySQL的大多数**事务型存储引擎**(InnoDB)实现的都不是简单的行级锁。基于提升并发性能的考虑,它们一般都同时实现了多版本并发控制(MVCC)。MVCC机制称为乐观机制,读不阻塞写,写也不阻塞读,等到提交的时候才检验是否有冲突,由于没有锁,所以读写不会相互阻塞,从而大大提升了并发性能。 551 | 552 | MVCC可以认为是行级锁的一个变种,但是它在很多情况下避免了加锁操作,因此开销更低。不同的数据库系统实现MVCC的机制都不同,但是大都实现了非阻塞的读操作,写操作也只锁定必要的行。 553 | 554 | MVCC的实现,是通过保存数据在某个时间的快照来实现的,也就是说不管需要执行多长的时间,每个事务看到的数据都是一致的。**根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的!** 555 | 556 | **下面通过InnoDB的简化版行为来说明MVCC是如何工作的。** 557 | 558 | **InnoDB的MVCC,是通过在每行纪录后面保存两个隐藏的列来实现的。** 559 | 这两个列,一个保存了行的创建时间,一个保存了行的过期时间,(存储的并不是实际的时间值,而是系统版本号)。**每开始一个新的事务,系统版本号都会自动递增。** 560 | 事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行纪录的版本号进行比较。在REPEATABLE 561 | READ隔离级别下,MVCC具体的操作如下: 562 | 563 | **SELECT** 564 | 565 | > InnoDB会根据以下两个条件检查每行纪录: 566 | 567 | InnoDB只查找版本早于当前事务版本的数据行,即,行的系统版本号小于或等于事务的系统版本号,这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。 568 | 569 | 行的过期版本,要么未定义,要么大于当前事务版本号。这样可以确保事务读取到的行,在事务开始之前未被删除。 570 | 571 | 只有符合上述两个条件的纪录,才能作为查询结果返回。 572 | 573 | **INSERT** 574 | 575 | > InnoDB为插入的每一行保存当前系统版本号作为行创建版本号。 576 | 577 | **DELETE** 578 | 579 | > InnoDB为删除的每一行保存当前系统版本号作为行删除标识。 580 | 581 | UPDATE 582 | 583 | > InnoDB为插入一行新纪录,保存当前系统版本号作为行版本号,同时,保存当前系统版本号到原来的行作为行删除标识。 584 | 585 | 优点:保存这两个额外系统版本号,使大多数读操作都可以不用加锁。这样设计使得读数据操作很简单,性能很好。缺点:每行纪录都需要额外的存储空间,需要做更多的行检查工作,以及一些额外的维护工作。 586 | 587 | ### SQL语句的5个连接概念 588 | 589 | **1.内连接(inner 590 | join):** 591 | 内连接查询操作列出与连接条件匹配的数据行,它使用比较运算符比较被连接列的列值。 592 | 593 | 2.外连接 594 | 595 | **2.1.左联接(**left 596 | join**):**是以左表为基准,对数据进行连接,然后将右表没有的对应项显示NULL 597 | 598 | **2.2.右连接(right 599 | join):** 600 | 是以右表为基准,对的数据进行连接,然以将左表没有的对应项显示为NULL 601 | 602 | **2.3.全连接(full outer 603 | join)** 604 | :完整外部联接返回左表和右表中的所有行。当某行在另一个表中没有匹配行时,则另一个表的选择列表列包含空值。如果表之间有匹配行,则整个结果集行包含基表的数据值。 605 | 606 | **3.交叉连接(cross 607 | join):** 608 | 交叉联接返回左表中的所有行,左表中的每一行与右表中的所有行组合。交叉联接也称作笛卡尔积。 609 | 610 | **连接表时,可以先用where条件对表进行过滤,然后做表连接。** 611 | 612 | **MySQL表关联的算法是 Nest Loop Join(嵌套联接循环),Nest Loop 613 | Join就是通过两层循环手段进行依次的匹配操作,最后返回结果集合。** 614 | 615 | > SQL语句基础大全:https://blog.csdn.net/dianxin113/article/details/77073625 616 | 617 | 618 | 619 | ### 索引的最左前缀原则 620 | 621 | 最左前缀原则:在联合索引中优先匹配按照顺序的最左字段。Mysql会一直向右匹配直到遇到范围查询(\>、\<、between、like)就停止匹配,比如a 622 | = 1 and b = 2 and c \> 3 and d = 4 623 | 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。**=和in可以乱序**,比如a 624 | = 1 and b = 2 and c = 3 625 | 建立(a,b,c)索引可以任意顺序,MySql的**查询优化器**会帮你优化成索引可以识别的形式 626 | 627 | 更具体的例子:https://www.kancloud.cn/kancloud/theory-of-mysql-index/41857 628 | 629 | ### 跨库join 630 | 631 | **目标:数据库A中的表可以join数据库B中的表。** 632 | 633 | > 1.开启FEDERATED引擎 634 | 635 | > 2.在数据库A中建立远程表 636 | 637 | > 3.在数据库A上进行join操作 638 | 639 | **对本地表进行更新操作,远程表也会同步更新。删除本地表,远程表不会删除。** 640 | 641 | **远程表结构修改,本地表不会更新。** 642 | 643 | -------------------------------------------------------------------------------- /notes/数据库/Redis面试题.md: -------------------------------------------------------------------------------- 1 | 2 | * [redis的线程模型是什么?为什么单线程的redis效率要高得多?](#redis%E7%9A%84%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B%E6%98%AF%E4%BB%80%E4%B9%88%E4%B8%BA%E4%BB%80%E4%B9%88%E5%8D%95%E7%BA%BF%E7%A8%8B%E7%9A%84redis%E6%95%88%E7%8E%87%E8%A6%81%E9%AB%98%E5%BE%97%E5%A4%9A) 3 | * [redis都有哪些数据类型?分别在哪些场景下使用比较合适?](#redis%E9%83%BD%E6%9C%89%E5%93%AA%E4%BA%9B%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%88%86%E5%88%AB%E5%9C%A8%E5%93%AA%E4%BA%9B%E5%9C%BA%E6%99%AF%E4%B8%8B%E4%BD%BF%E7%94%A8%E6%AF%94%E8%BE%83%E5%90%88%E9%80%82) 4 | * [redis的过期策略都有哪些?手写一下LRU代码实现?](#redis%E7%9A%84%E8%BF%87%E6%9C%9F%E7%AD%96%E7%95%A5%E9%83%BD%E6%9C%89%E5%93%AA%E4%BA%9B%E6%89%8B%E5%86%99%E4%B8%80%E4%B8%8Blru%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0) 5 | * [如何保证Redis高并发、高可用?](#%E5%A6%82%E4%BD%95%E4%BF%9D%E8%AF%81redis%E9%AB%98%E5%B9%B6%E5%8F%91%E9%AB%98%E5%8F%AF%E7%94%A8) 6 | * [redis的持久化有哪几种方式?不同的持久化机制都有什么优缺点?](#redis%E7%9A%84%E6%8C%81%E4%B9%85%E5%8C%96%E6%9C%89%E5%93%AA%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F%E4%B8%8D%E5%90%8C%E7%9A%84%E6%8C%81%E4%B9%85%E5%8C%96%E6%9C%BA%E5%88%B6%E9%83%BD%E6%9C%89%E4%BB%80%E4%B9%88%E4%BC%98%E7%BC%BA%E7%82%B9) 7 | * [redis集群模式的工作原理能说一下么?在集群模式下,redis的key是如何寻址的?分布式寻址都有哪些算法?了解一致性hash算法吗?如何动态增加和删除一个节点?](#redis%E9%9B%86%E7%BE%A4%E6%A8%A1%E5%BC%8F%E7%9A%84%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E8%83%BD%E8%AF%B4%E4%B8%80%E4%B8%8B%E4%B9%88%E5%9C%A8%E9%9B%86%E7%BE%A4%E6%A8%A1%E5%BC%8F%E4%B8%8Bredis%E7%9A%84key%E6%98%AF%E5%A6%82%E4%BD%95%E5%AF%BB%E5%9D%80%E7%9A%84%E5%88%86%E5%B8%83%E5%BC%8F%E5%AF%BB%E5%9D%80%E9%83%BD%E6%9C%89%E5%93%AA%E4%BA%9B%E7%AE%97%E6%B3%95%E4%BA%86%E8%A7%A3%E4%B8%80%E8%87%B4%E6%80%A7hash%E7%AE%97%E6%B3%95%E5%90%97%E5%A6%82%E4%BD%95%E5%8A%A8%E6%80%81%E5%A2%9E%E5%8A%A0%E5%92%8C%E5%88%A0%E9%99%A4%E4%B8%80%E4%B8%AA%E8%8A%82%E7%82%B9) 8 | * [了解什么是redis的雪崩和穿透?redis崩溃之后会怎么样?系统该如何应对这种情况?如何处理redis的穿透?](#%E4%BA%86%E8%A7%A3%E4%BB%80%E4%B9%88%E6%98%AFredis%E7%9A%84%E9%9B%AA%E5%B4%A9%E5%92%8C%E7%A9%BF%E9%80%8Fredis%E5%B4%A9%E6%BA%83%E4%B9%8B%E5%90%8E%E4%BC%9A%E6%80%8E%E4%B9%88%E6%A0%B7%E7%B3%BB%E7%BB%9F%E8%AF%A5%E5%A6%82%E4%BD%95%E5%BA%94%E5%AF%B9%E8%BF%99%E7%A7%8D%E6%83%85%E5%86%B5%E5%A6%82%E4%BD%95%E5%A4%84%E7%90%86redis%E7%9A%84%E7%A9%BF%E9%80%8F) 9 | * [如何保证缓存与数据库的双写一致性?](#%E5%A6%82%E4%BD%95%E4%BF%9D%E8%AF%81%E7%BC%93%E5%AD%98%E4%B8%8E%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9A%84%E5%8F%8C%E5%86%99%E4%B8%80%E8%87%B4%E6%80%A7) 10 | * [redis的并发竞争问题是什么?如何解决这个问题?了解Redis事务的CAS方案吗?](#redis%E7%9A%84%E5%B9%B6%E5%8F%91%E7%AB%9E%E4%BA%89%E9%97%AE%E9%A2%98%E6%98%AF%E4%BB%80%E4%B9%88%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3%E8%BF%99%E4%B8%AA%E9%97%AE%E9%A2%98%E4%BA%86%E8%A7%A3redis%E4%BA%8B%E5%8A%A1%E7%9A%84cas%E6%96%B9%E6%A1%88%E5%90%97) 11 | 12 | 13 | 14 | ### 面试题 15 | 16 | 17 | 18 | #### redis的线程模型是什么?为什么单线程的redis效率要高得多? 19 | 20 | redis是单线程模型 21 | 22 | 效率高的原因:单线程减少了线程间切换和锁的开销,基于内存实现,采用多路复用非阻塞IO实现 23 | 24 | #### redis都有哪些数据类型?分别在哪些场景下使用比较合适? 25 | 26 | (1)string:最基本的类型了,用来做简单的kv缓存 27 | 28 | (2)hash:类似map的一种结构,这个一般就是可以将结构化的数据 29 | 30 | 比如一个对象(前提是这个对象没嵌套其他的对象)给缓存在redis里,然后每次读写缓存的时候,可以就操作hash里的某个字段。 31 | 32 | (3)list:有序列表 33 | 34 | 可以通过list存储一些列表型的数据结构,类似粉丝列表了、文章的评论列表 35 | 36 | 可以通过lrange命令,基于redis实现简单的高性能分页 37 | 38 | (4)set:无序集合,自动去重 39 | 40 | 直接基于set将系统里需要去重的数据扔进去,自动就给去重了 41 | 42 | 可以基于set玩儿交集、并集、差集的操作,比如交集吧 43 | 44 | (5)sorted set 45 | 46 | 排序的set,去重而且可以排序, 47 | 48 | 可以自定义排序,实现类似排行榜的功能 49 | 50 | #### redis的过期策略都有哪些?手写一下LRU代码实现? 51 | 52 | 首先如果为Key设置了过期时间后,当其失效时,会进行被动删除和,定时删除,主动删除(当前已用内存超过maxmemory限定时,触发主动清理策略,可配置数据淘汰策略)。 53 | 54 | 如果内存空间占用过多,一般有六种种策略: 55 | 56 | volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰; 57 | 58 | volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰; 59 | 60 | volatile-random:从已设置过期时间的数据集中任意选择数据淘汰 ; 61 | 62 | allkeys-lru:从数据集中挑选最近最少使用的数据淘汰; 63 | 64 | allkeys-random:从数据集中任意选择数据淘汰; 65 | 66 | no-enviction(驱逐):禁止驱逐数据。 67 | 68 | LRU的实现可以通过继承LinkedHashMap并重写protected boolean 69 | removeEldestEntry(Map.Entry eldest){}方法实现 70 | 71 | #### 如何保证Redis高并发、高可用? 72 | 73 | redis高并发:主从架构,一主多从,一般来说,很多项目其实就足够了,单主用来写入数据,单机几万QPS,多从用来查询数据,多个从实例可以提供每秒10万的QPS。 74 | 75 | redis高可用:如果你做主从架构部署,其实就是加上哨兵就可以了,就可以实现,任何一个实例宕机,自动会进行主备切换。 76 | 77 | redis高并发的同时,还需要容纳大量的数据就需要redis集群,而且用redis集群之后,可以提供可能每秒几十万的读写并发。 78 | 79 | #### redis的持久化有哪几种方式?不同的持久化机制都有什么优缺点? 80 | 81 | redis的持久化是为了在重启后恢复数据 82 | 83 | RDB是通过保存数据快照来实现的,优点是恢复速度快、占用带宽小,缺点是可能丢失数据 84 | 85 | AOF是通过追加日志的形式来保存的,优缺点相反 86 | 87 | #### redis集群模式的工作原理能说一下么?在集群模式下,redis的key是如何寻址的?分布式寻址都有哪些算法?了解一致性hash算法吗?如何动态增加和删除一个节点? 88 | 89 | redis集群主要是为了突破单机redis在海量数据面前的内存瓶颈。 90 | 91 | redis的集群架构支撑N个redis master node,每个master node都可以挂载多个slave node 92 | 93 | 采用读写分离的架构,对于每个master来说,写就写到master,然后读就从mater对应的slave去读 94 | 95 | 实现了高可用,因为每个master都有salve节点,那么如果mater挂掉,就会自动将某个slave切换成master 96 | 97 | 在redis集群架构下,每个redis要放开两个端口号,比如一个端口用来进行节点间通信的,进行故障检测,配置更新,故障转移授权; 98 | 另一个端口用来客户端通信。 99 | 100 | #### 了解什么是redis的雪崩和穿透?redis崩溃之后会怎么样?系统该如何应对这种情况?如何处理redis的穿透? 101 | 102 | redis的雪崩:同时有大量缓存失效或者缓存机器宕机,请求全部转发到DB,DB瞬时压力过重雪崩。 103 | 104 | 解决方法:服务限流或者降级,尽量设置随机的缓存时间,设置redis持久化,快速恢复数据方案吗 105 | 106 | 缓存穿透:是指查询一个一定不存在的数据 107 | 108 | 解决方法:布隆过滤器,或者对不存在的数据进行缓存(过期时间较短) 109 | 110 | #### 如何保证缓存与数据库的双写一致性? 111 | 112 | Cache Aside Pattern 113 | 114 | (1)读的时候,先读缓存,缓存没有的话,那么就读数据库,然后取出数据后放入缓存,同时返回响应 115 | 116 | (2)更新的时候,先删除缓存,然后再更新数据库(再次删除) 117 | 118 | #### redis的并发竞争问题是什么?如何解决这个问题?了解Redis事务的CAS方案吗? 119 | 120 | redis的并发竞争问题是多客户端同时并发写一个 key,解决方法:分布式锁,加时间戳 121 | 122 | redis事务的CAS方案(乐观锁)工作机制: 123 | 124 | watch指令在redis事务中提供了类似于CAS的行为。为了检测被watch的keys在是否有多个clients同时改变引起冲突,这些keys将会被监控。如果至少有一个被监控的key在执行exec命令前被修改,整个事物将会回滚,不执行任何动作,从而保证原子性操作,并且执行exec会得到null的回复。注意watch的key是对整个连接有效的,事务也一样。如果连接断开,监视和事务都会被自动清除。 125 | -------------------------------------------------------------------------------- /notes/数据库/media/221cc78a1d285ec5c774d2c7087efffd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/数据库/media/221cc78a1d285ec5c774d2c7087efffd.png -------------------------------------------------------------------------------- /notes/数据库/media/2a7316dccbdedd2fb8fec00b36daf09c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/数据库/media/2a7316dccbdedd2fb8fec00b36daf09c.png -------------------------------------------------------------------------------- /notes/数据库/media/3ce21bb5c36a79cdd965f2a5b564efc8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/数据库/media/3ce21bb5c36a79cdd965f2a5b564efc8.png -------------------------------------------------------------------------------- /notes/数据库/media/547d487238dbe2d2fcc64c67876bfcfc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/数据库/media/547d487238dbe2d2fcc64c67876bfcfc.png -------------------------------------------------------------------------------- /notes/数据库/media/734a962da6997171b7f295dc2f6a1655.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/数据库/media/734a962da6997171b7f295dc2f6a1655.png -------------------------------------------------------------------------------- /notes/数据库/media/787dd3d1a08f120c8118d6554cac2fda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/数据库/media/787dd3d1a08f120c8118d6554cac2fda.png -------------------------------------------------------------------------------- /notes/数据库/media/7ee11229e2d1d4ef3c2396359d38ce0a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/数据库/media/7ee11229e2d1d4ef3c2396359d38ce0a.png -------------------------------------------------------------------------------- /notes/数据库/media/8b4b518fa85afcb8a7dd7d1353dbd4f9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/数据库/media/8b4b518fa85afcb8a7dd7d1353dbd4f9.png -------------------------------------------------------------------------------- /notes/数据库/media/9fc4ef4042e6927fb20e0b60a9281740.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/数据库/media/9fc4ef4042e6927fb20e0b60a9281740.png -------------------------------------------------------------------------------- /notes/数据库/media/ab49f5fe6265ebca35624d11125dccdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/数据库/media/ab49f5fe6265ebca35624d11125dccdf.png -------------------------------------------------------------------------------- /notes/数据库/media/c9beb8350bc758c62d845c38a5a497b7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/数据库/media/c9beb8350bc758c62d845c38a5a497b7.png -------------------------------------------------------------------------------- /notes/数据库/media/d802bdad2d733857287cf627ea12efc2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/数据库/media/d802bdad2d733857287cf627ea12efc2.jpg -------------------------------------------------------------------------------- /notes/数据库/media/ddedd0b3683e99c97ac56be9fb1971d7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/数据库/media/ddedd0b3683e99c97ac56be9fb1971d7.png -------------------------------------------------------------------------------- /notes/数据库/media/de254d956041a06f12d467c39dbc77e1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/数据库/media/de254d956041a06f12d467c39dbc77e1.png -------------------------------------------------------------------------------- /notes/数据库/media/df9a8160b6b13dbe46660b46cb9be344.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/数据库/media/df9a8160b6b13dbe46660b46cb9be344.png -------------------------------------------------------------------------------- /notes/框架/Netty.md: -------------------------------------------------------------------------------- 1 | 2 | * [Netty是什么](#netty%E6%98%AF%E4%BB%80%E4%B9%88) 3 | * [Netty高性能的表现](#netty%E9%AB%98%E6%80%A7%E8%83%BD%E7%9A%84%E8%A1%A8%E7%8E%B0) 4 | * [TCP粘包/拆包的问题解决](#tcp%E7%B2%98%E5%8C%85%E6%8B%86%E5%8C%85%E7%9A%84%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3) 5 | * [Reactor模式](#reactor%E6%A8%A1%E5%BC%8F) 6 | * [Netty的Reactor模式](#netty%E7%9A%84reactor%E6%A8%A1%E5%9E%8B) 7 | 8 | 9 | ### Netty是什么 10 | 11 | Netty是一款基于NIO开发的**高性能和异步事件驱动**的网络通信框架,支持高并发,传输速度快,封装好。 12 | 13 | **高并发:** 14 | 对比于BIO,并发性能得到了很大提高。 15 | 16 | **传输快:** 17 | Netty的传输依赖于零拷贝特性,尽量减少不必要的内存拷贝,实现了更高效率的传输。 18 | 19 | **封装好:** 20 | Netty封装了NIO操作的很多细节,提供了易于使用调用接口。 21 | 22 | 典型的应用有:阿里分布式服务框架Dubbo,默认使用Netty作为基础通信组件,还有 23 | RocketMQ也是使用Netty作为通讯的基础。 24 | 25 | ### Netty高性能的表现 26 | 27 | **IO线程模型:** 28 | 同步非阻塞,用最少的资源做更多的事。 29 | 30 | **内存零拷贝:** 31 | 尽量减少不必要的内存拷贝,实现了更高效率的传输。 32 | 33 | **内存池设计:** 34 | 申请的内存可以重用,主要指直接内存。内部实现是用一颗二叉查找树管理内存分配情况。 35 | 36 | **串形化处理读写:** 37 | 避免使用锁带来的性能开销。 38 | 39 | **高性能序列化协议**: 40 | 支持protobuf等高性能序列化协议。 41 | 42 | 43 | 44 | ### TCP粘包/拆包的问题解决 45 | 46 | TCP是一个“流”协议,所谓流,就是没有界限的一长串二进制数据。TCP作为传输层协议并不不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行数据包的划分,所以在业务上认为是一个完整的包,可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。 47 | 48 | **粘包问题的解决策略:** 49 | 由于底层的TCP无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计来解决。业界的主流协议的解决方案,可以归纳如下: 50 | 51 | 1.消息定长,报文大小固定长度,例如每个报文的长度固定为200字节,如果不够空位补空格 52 | 53 | 2.包尾添加特殊分隔符,例如每条报文结束都添加回车换行符(例如FTP协议)或者指定特殊字符作为报文分隔符,接收方通过特殊分隔符切分报文区分; 54 | 55 | 3.将消息分为消息头和消息体,消息头中包含表示信息的总长度(或者消息体长度)的字段; 56 | 57 | 4.更复杂的自定义应用层协议。 58 | 59 | **Netty粘包和拆包解决方案:** 60 | Netty提供了多个解码器,可以进行分包的操作,分别是: 61 | 62 | LineBasedFrameDecoder(回车换行结尾) 63 | 64 | DelimiterBasedFrameDecoder(添加特殊分隔符报文来分包) 65 | 66 | FixedLengthFrameDecoder(使用定长的报文来分包) 67 | 68 | LengthFieldBasedFrameDecoder(按照应用层数据包的大小,拆包) 69 | 70 | 71 | ### Reactor模式 72 | 73 | 反应器设计模式(Reactorpattern)是一种为处理并发服务请求,并将请求提交到一个或者多个服务处理程序的事件设计模式。当客户端请求抵达后,服务处理程序使用多路分配策略,由一个非阻塞的线程来接收所有的请求,然后派发这些请求至相关的工作线程进行处理。 74 | 75 | Reactor模式是事件驱动的,有一个或者多个并发输入源,有一个Server Handler和多个Request Handlers,这个Service Handler会同步的将输入的请求多路复用的分发给相应的Request Handler。 76 | 77 | ![](https://raw.githubusercontent.com/LLLRS/git_resource/master/4rvfsvfdh1223ttyy.png) 78 | 79 | Reactor模式从结构上有点类似生产者和消费者模型,即一个或多个生产者将事件放入一个Queue中,而一个或者多个消费者主动的从这个队列中poll事件来处理;而Reactor模式则没有Queue来做缓冲,每当一个事件输入到Service Handler之后,该Service Handler会主动根据不同的Evnent类型将其分发给对应的Request Handler来处理。 80 | 81 | ### Netty的Reactor模型 82 | Netty中Reactor模式的参与者主要有下面一些组件: 83 | 84 | * Selector 85 | * EventLoopGroup/EventLoop 86 | * ChannelPipeline 87 | 88 | #### EventLoopGroup / EventLoop 89 | 当系统在运行过程中,如果频繁的进行线程上下文切换,会带来额外的性能损耗。多线程并发执行某个业务流程,业务开发者还需要时刻对线程安全保持警惕,哪些数据可能会被并发修改,如何保护?这不仅降低了开发效率,也会带来额外的性能损耗。 90 | 91 | 为了解决上述问题,Netty采用了串行化设计理念。从消息的读取、编码以及后续Handler的执行,始终都由IO线程EventLoop负责,这就意味着整个流程不会进行线程上下文的切换,数据也不会面临被并发修改的风险。 92 | 93 | EventLoopGroup是一组EventLoop的抽象,EventLoopGroup提供next接口,可以从一组EventLoop里面按照一定规则获取其中一个EventLoop来处理任务。对于EventLoopGroup这里需要了解的是在Netty中,在Netty服务器编程中我们需要BossEventLoopGroup和WorkerEventLoopGroup两个EventLoopGroup来进行工作。通常一个服务端口即一个ServerSocketChannel对应一个Selector和一个EventLoop线程,也就是说BossEventLoopGroup的线程数参数为1。 94 | BossEventLoop负责接收客户端的连接并将SocketChannel交给WorkerEventLoopGroup来进行IO处理。 95 | EventLoop的实现充当Reactor模式中的分发(Dispatcher)的角色。 96 | 97 | #### ChannelPipeline 98 | 99 | ChannelPipeline其实是担任着Reactor模式中的请求处理器这个角色。ChannelPipeline的默认实现是DefaultChannelPipeline,DefaultChannelPipeline本身维护着一个用户不可见的tail和head的ChannelHandler,他们分别位于链表队列的头部和尾部。tail在更上层的部分,而head在靠近网络层的方向。 100 | 101 | 在Netty中关于ChannelHandler有两个重要的接口,ChannelInBoundHandler和ChannelOutBoundHandler。inbound可以理解为网络数据从外部流向系统内部,而outbound可以理解为网络数据从系统内部流向系统外部。用户实现的ChannelHandler可以根据需要实现其中一个或多个接口,将其放入Pipeline中的链表队列中,ChannelPipeline会根据不同的IO事件类型来找到相应的Handler来处理。 102 | 103 | 同时链表队列是责任链模式的一种变种,自上而下或自下而上所有满足事件关联的Handler都会对事件进行处理。ChannelInBoundHandler对从客户端发往服务器的报文进行处理,一般用来执行半包/粘包,解码,读取数据,业务处理等;ChannelOutBoundHandler对从服务器发往客户端的报文进行处理,一般用来进行编码,发送报文到客户端。 104 | 105 | 下图是对ChannelPipeline执行过程的说明: 106 | 107 | ![](https://raw.githubusercontent.com/LLLRS/git_resource/master/123df3535dczfffc.png) 108 | 109 | 110 | #### Buffer 111 | Netty提供的经过扩展的Buffer相对NIO中的有个许多优势,作为数据存取非常重要的一块。 112 | 113 | **ByteBuf读写指针** :在ByteBuffer中,读写指针都是position,而在ByteBuf中,读写指针分别为readerIndex和writerIndex。直观看上去ByteBuffer仅用了一个指针就实现了两个指针的功能,节省了变量,但是当对于ByteBuffer的读写状态切换的时候必须要调用flip方法,而当下一次写之前,必须要将Buffe中的内容读完,再调用clear方法。每次读之前调用flip,写之前调用clear,这样无疑给开发带来了繁琐的步骤,而且内容没有读完是不能写的,这样非常不灵活。相比之下使用ByteBuf读写指针,读的时候仅仅依赖readerIndex指针,写的时候仅仅依赖writerIndex指针,不需每次读写之前调用对应的方法,而且没有必须一次读完的限制。 114 | 115 | 116 | **零拷贝** :Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象,用户可以像操作一个Buffer那样方便的对组合Buffer进行操作,避免了传统通过内存拷贝的方式将几个小Buffer合并成一个大的Buffer。Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题。 117 | 118 | **引用计数与池化技术** :在Netty中,每个被申请的Buffer对于Netty来说都可能是很宝贵的资源,因此为了获得对于内存的申请与回收更多的控制权,Netty自己根据引用计数法去实现了内存的管理。Netty对于Buffer的使用都是基于直接内存(DirectBuffer)实现的,大大提高I/O操作的效率。然而DirectBuffer和HeapBuffer相比之下除了I/O操作效率高之外还有一个天生的缺点,即对于DirectBuffer的申请相比HeapBuffer效率更低。因此Netty结合引用计数实现了PolledBuffer,即池化的用法,当引用计数等于0的时候,Netty将Buffer回收致池中,在下一次申请Buffer的没某个时刻会被复用。 119 | 120 | 121 | [李林峰:Netty系列之Netty高性能之道](https://www.infoq.cn/article/netty-high-performance) 122 | -------------------------------------------------------------------------------- /notes/框架/Netty.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/框架/Netty.pdf -------------------------------------------------------------------------------- /notes/框架/Spring.md: -------------------------------------------------------------------------------- 1 | * [Spring](#spring) 2 | * [什么是Spring 以及优点](#%E4%BB%80%E4%B9%88%E6%98%AFspring-%E4%BB%A5%E5%8F%8A%E4%BC%98%E7%82%B9) 3 | * [Spring Bean生命周期](#spring-bean%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F) 4 | * [Spring中bean的作用域](#spring%E4%B8%ADbean%E7%9A%84%E4%BD%9C%E7%94%A8%E5%9F%9F) 5 | * [Spring IOC](#spring-ioc) 6 | * [Spring AOP](#spring-aop) 7 | * [Spring 常用注解](#spring-%E5%B8%B8%E7%94%A8%E6%B3%A8%E8%A7%A3) 8 | * [Spring事务的传播级别](#spring%E4%BA%8B%E5%8A%A1%E7%9A%84%E4%BC%A0%E6%92%AD%E7%BA%A7%E5%88%AB) 9 | * [Spring MVC](#spring-mvc) 10 | * [Spring中设计模式](#spring%E4%B8%AD%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F) 11 | * [Spring解决循环依赖](https://segmentfault.com/a/1190000015221968) 12 | * [MyBatis](#mybatis) 13 | * [\#\{\}和$\{\}的区别](#%E5%92%8C%E7%9A%84%E5%8C%BA%E5%88%AB) 14 | * [Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不?](#mybatis%E5%8A%A8%E6%80%81sql%E6%98%AF%E5%81%9A%E4%BB%80%E4%B9%88%E7%9A%84%E9%83%BD%E6%9C%89%E5%93%AA%E4%BA%9B%E5%8A%A8%E6%80%81sql%E8%83%BD%E7%AE%80%E8%BF%B0%E4%B8%80%E4%B8%8B%E5%8A%A8%E6%80%81sql%E7%9A%84%E6%89%A7%E8%A1%8C%E5%8E%9F%E7%90%86%E4%B8%8D) 15 | * [Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?](#mybatis%E6%98%AF%E5%90%A6%E6%94%AF%E6%8C%81%E5%BB%B6%E8%BF%9F%E5%8A%A0%E8%BD%BD%E5%A6%82%E6%9E%9C%E6%94%AF%E6%8C%81%E5%AE%83%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86%E6%98%AF%E4%BB%80%E4%B9%88) 16 | 17 | 18 | Spring 19 | ------------ 20 | 21 | ### 什么是Spring 以及优点 22 | 23 | Spring是个轻量ava企业级应用的开源开发框架。Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用。Spring框架目标是简化Java企业级应用开发,并通过POJO为基础的编程模型促进良好的编程习惯。 24 | 25 | **优点:** 26 | 27 | **控制反转:** 28 | Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。 29 | 30 | **面向切面的编程(AOP):** 31 | Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。 32 | 33 | **容器:** 34 | Spring包含并管理应用中对象的生命周期和配置。 35 | 36 | **MVC框架** 37 | :Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品。 38 | 39 | **事务管理:** 40 | Spring提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务。 41 | 42 | **异常处理:** 43 | Spring提供方便的API把具体技术相关的异常转化为一致的unchecked异常。 44 | 45 | ### Spring Bean生命周期 46 | 47 | bean 是一个被实例化并通过 Spring IoC 容器所管理的对象。 48 | 49 | Spring 只帮我们管理单例模式 Bean 的完整生命周期,对于 prototype 的 bean ,Spring 50 | 在创建好交给使用者之后则不会再管理后续的生命周期。流程如下所示: 51 | 52 | ![](media/b8c67a9a399aab7ab0d03f39605e5544.png) 53 | 54 | Spring的加载流程:https://www.cnblogs.com/xrq730/p/6285358.html 55 | 56 | Bean的生命周期: https://www.kancloud.cn/chenshaoqiang/spring/884419 57 | 58 | ### Spring中bean的作用域 59 | 60 | **默认是单例** 61 | 62 | | **作用域** | **字符** | **描述** | 63 | |------------|-----------|--------------------------| 64 | | 单例 | singleton | 整个应用中只创建一个实例 | 65 | | 原型 | prototype | 每次注入时都新建一个实例 | 66 | | 会话 | session | 为每个会话创建一个实例 | 67 | | 请求 | request | 为每个请求创建一个实例 | 68 | 69 | ### Spring IOC 70 | 71 | **IOC**:IOC利用Java反射机制,AOP利用代理模式。 72 | 73 | IoC(Inverse of Control:控制反转)是一种设计思想,就是 将原本在程序中手动创建对象的控制权,交由Spring框架来管理。 IoC 在其他语言中也有应用,并非 Spirng 特有。 IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。 74 | 75 | 将对象之间的相互依赖关系交给 IOC 容器来管理,并由 IOC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IOC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 在实际项目中一个 Service 类可能有几百甚至上千个类作为它的底层,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IOC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。 76 | 77 | **谁控制谁,控制什么:** 78 | 传统JavaSE程序设计,直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 79 | 象的创建;谁控制谁?当然是IoC容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。 80 | 81 | **为何是反转,哪些方面反转了:** 82 | 有反转就有正转,传统应用程序是由自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象。为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?**依赖对象的获取被反转了。** 83 | 84 | **DI—Dependency 85 | Injection,即“依赖注入”:** 86 | 组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。通过依赖注入机制,只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,就能完成自身的业务逻辑,而**不需要关心具体的资源来自何处,由谁实现**。 87 | 88 | 谁依赖于谁:当然是应用程序依赖于IoC容器; 89 | 90 | 为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源; 91 | 92 | 谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象; 93 | 94 | 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。 95 | 96 | **IoC和DI其实是同一个概念的不同角度描述,** 97 | 由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物MartinFowler又给出了一个新的名字:“依赖注入”,相对IoC而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”**。 98 | 99 | **解决循环依赖:** 100 | https://blog.csdn.net/u010853261/article/details/77940767* 101 | 102 | **依赖注入的三种方式** 103 | - 构造器注入: 104 | - setter方式注入:容器通过调用无参构造器或无参static工厂 方法实例化bean之后,调用该bean的setter方法 105 | - 注解方式注入:\@Autowired \@Resource 106 | 107 | ### Spring AOP 108 | 109 | AOP技术利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,**并将其名为“Aspect”,即切面。**所谓“切面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。使用“横切”技术,*AOP*把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。 110 | 111 | AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。 112 | 113 | 纵观AOP编程,程序员只需要**参与三个部分:** 114 | 115 | 定义普通业务组件 116 | 117 | 定义切入点,一个切入点可能横切多个业务组件 118 | 119 | 定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作 120 | 121 | 所以进行AOP编程的关键就是**定义切入点和定义增强处理**,一旦定义了合适的切入点和增强处理, 122 | 123 | AOP框架将自动生成AOP代理,即:**代理对象的方法=增强处理+被代理对象的方法。** 124 | 125 | AOP是通过动态代理来实现的,Spring创建代理的规则为: 126 | 127 | > **默认使用JDK动态代理**来创建AOP代理,这样就可以为任何**接口实例**创建代理了 128 | 129 | > 当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB。 130 | 131 | **动态代理**:http://www.importnew.com/27772.html 132 | 133 | 如果使用spring mvc,那post请求跟put请求有什么区别啊 134 | 135 | ### Spring 常用注解 136 | 137 | **\@Component:** 138 | 标准一个普通的spring Bean类 139 | 140 | **\@Repository:** 141 | 标注一个DAO组件类。 142 | 143 | **\@Service:** 144 | 标注一个业务逻辑组件类。 145 | 146 | **\@Controller:** 147 | 标注一个控制器组件类。 148 | 149 | **\@Autowired:** 150 | 依赖注入,只按照Type 注入 151 | 152 | **\@Qualifier:** 153 | 当容器中存在同样类型的多个bean时,可以使用 \@Qualifier 154 | 注解指定注入Bean 的名称 155 | 156 | \@Resource:\@Resource 的作用相当于 \@Autowired,只不过\@Resource 157 | 默认按byName自动注入 158 | 159 | **\@PostConstruct \@PreDestroy** 160 | 方法:实现初始化和销毁bean之前进行的操作 161 | **\@RequestMapping 162 | :这**个注解用于将url映射到整个处理类或者特定的处理请求的方法。如\@RequestMapping(value=”/haha”,method=RequestMethod.GET) 163 | 164 | **\@RequestParam:** 165 | 将请求的参数绑定到方法中的参数上,有required参数,默认情况下,required=true,也就是改参数必须要传。如果改参数可以传可不传,可以配置 166 | required=false。 167 | 168 | **\@RequestBody** 169 | : \@RequestBody是指方法参数应该被绑定到HTTP请求Body上。\@ResponseBody在输出JSON格式的数据时,会经常用到。 170 | 171 | **\@Transactional:** 172 | 事务注解。 173 | 174 | **\@Component 和 \@Bean 的区别:** 175 | * 作用对象不同: @Component 注解作用于类,而\@Bean注解作用于方法。 176 | * \@Component通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(可以使用 \@ComponentScan 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。\@Bean 注解通常是在标有该注解的方法中定义产生这个 bean,\@Bean告诉了Spring这是某个类的示例,当我需要用它的时候还给我。 177 | 178 | ### Spring事务的传播级别 179 | 180 | | 事务传播行为类型 | 说明 | 181 | | :------:| :------ | 182 | | PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中 | 183 | | PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行 | 184 | | PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常 | 185 | | PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起 | 186 | | PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起 | 187 | | PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常 | 188 | | PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作 | 189 | 190 | 191 | ### Spring MVC 192 | 193 | Spring 194 | MVC是一个基于MVC架构的用来简化web应用程序开发的应用开发框架,它是Spring的一个模块,无需中间整合层来整合,属于表现层的框架。在web模型中,MVC是一种很流行的框架,通过把Model,View,Controller分离,把较为复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。SpringMVC的核心架构如下: 195 | 196 | ![](media/7bb35a1120e5eb25745317d009811937.png) 197 | 198 | 具体流程: 199 | 200 | (1)首先用户发送请求——\>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制。 201 | 202 | (2)DispatcherServlet——\>HandlerMapping,映射处理器将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象。 203 | 204 | (3)DispatcherServlet——\>HandlerAdapter,处理器适配器将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器。 205 | 206 | (4)HandlerAdapter——\>调用处理器相应功能处理方法,并返回一个ModelAndView对象(包含模型数据、逻辑视图名)。 207 | 208 | (5)ModelAndView对象(Model部分是业务对象返回的模型数据,View部分为逻辑视图名)—\> 209 | ViewResolver, 视图解析器将把逻辑视图名解析为具体的View。 210 | 211 | (6)View——\>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构。 212 | 213 | (7)返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。 214 | 215 | ### Spring中设计模式 216 | 217 | **第一种:简单工厂** 218 | 219 | 简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。、 220 | 221 | **第二种:单例模式(Singleton)** 222 | 223 | 保证一个类仅有一个实例,并提供一个访问它的全局访问点。Spring中的单例模式完成了后半句话,即提供了全局的访问点BeanFactory。但没有从构造器级别去控制单例,这是因为spring管理的是是任意的Java对象。 224 | 225 | **Spring下默认的bean均为singleton,可以通过singleton=“true\|false”来指定。** 226 | 227 | **第三种:适配器(Adapter)** 228 | 229 | 在Spring的Aop中,使用的Advice(通知)来增强被代理类的功能。Spring实现这一AOP功能的原理就使用代理模式(1、JDK动态代理。2、CGLib字节码生成技术代理。)对类进行方法级别的切面增强,即生成被代理类的代理类, 230 | 并在代理类的方法前,设置拦截器,通过执行拦截器重的内容增强了代理方法的功能,实现的面向切面编程。 231 | 232 | **第四种:代理(Proxy)** 233 | 234 | 为其他对象提供一种代理以控制对这个对象的访问。从结构上来看和Decorator模式类似,但Proxy是控制,更像是一种对功能的限制,而Decorator是增加职责。Spring的Proxy模式在aop中有体现,比如JdkDynamicAopProxy和Cglib2AopProxy。 235 | 236 | **第五种:观察者(Observer)** 237 | 238 | 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。Spring中Observer模式常用的地方是listener的实现。如ApplicationListener。 239 | 240 | 241 | MyBatis 242 | ------------ 243 | 244 | MyBatis是一款优秀的持久层框架,它支持**定制化SQL**、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis可以使用简单的XML或注解来配置和映射原生信息,将接口和Java的POJOs(Plain 245 | Old Java Objects,普通的Java对象)映射成数据库中的记录。 246 | 247 | MyBatis的强大特性之一便是**它的**[动态SQL](http://www.mybatis.org/mybatis-3/zh/dynamic-sql.html)**。** 248 | 249 | ### \#{}和\${}的区别 250 | 251 | **\#{ 252 | }:占位符,** 253 | 防止sql注入,使用\#{}格式的语法在mybatis中使用Preparement语句来安全的设置值 254 | 255 | 如:order by \#{user_id} 256 | 257 | 如果传入的值是111,那么解析成sql时的值为order by "111" 258 | 259 | 如果传入的值是id,则解析成的sql为order by "id" 260 | 261 | **\${ }:sql拼接符号,** 262 | 将传入的数据直接显示生成在sql中(order by) 263 | 264 | 如:order by \${user_id} 265 | 266 | 如果传入的值是111,那么解析成sql时的值为order by 111 267 | 268 | 如果传入的值是id,则解析成的sql为order by id 269 | 270 | 271 | 272 | ### Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不? 273 | 274 | 答:Mybatis动态sql可以让我们在Xml映射文件内,以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能,Mybatis提供了9种动态sql标签**trim\|where\|set\|foreach\|if\|choose\|when\|otherwise\|bind**。 275 | 276 | 其执行原理为,使用OGNL从sql参数对象中计算表达式的值,根据表达式的值动态拼接sql,以此来完成动态sql的功能。 277 | 278 | ### Mybatis是否支持延迟加载?如果支持,它的实现原理是什么? 279 | 280 | 答:Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true\|false。 281 | 282 | 它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。 283 | 284 | 285 | -------------------------------------------------------------------------------- /notes/框架/media/0bd3287a63d24f7eafa5cd0efeb0e552.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/框架/media/0bd3287a63d24f7eafa5cd0efeb0e552.png -------------------------------------------------------------------------------- /notes/框架/media/7bb35a1120e5eb25745317d009811937.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/框架/media/7bb35a1120e5eb25745317d009811937.png -------------------------------------------------------------------------------- /notes/框架/media/b8c67a9a399aab7ab0d03f39605e5544.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/框架/media/b8c67a9a399aab7ab0d03f39605e5544.png -------------------------------------------------------------------------------- /notes/算法与数据结构/海量数据.md: -------------------------------------------------------------------------------- 1 | * [几种数据结构和算法](#%E5%87%A0%E7%A7%8D%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95) 2 | * [分而治之/Hash映射\+Hash\_map统计\+堆/快速/归并排序](#%E5%88%86%E8%80%8C%E6%B2%BB%E4%B9%8Bhash%E6%98%A0%E5%B0%84hash_map%E7%BB%9F%E8%AE%A1%E5%A0%86%E5%BF%AB%E9%80%9F%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F) 3 | * [问题1:海量日志数据,提取出某日访问百度次数最多的那个IP。](#%E9%97%AE%E9%A2%981%E6%B5%B7%E9%87%8F%E6%97%A5%E5%BF%97%E6%95%B0%E6%8D%AE%E6%8F%90%E5%8F%96%E5%87%BA%E6%9F%90%E6%97%A5%E8%AE%BF%E9%97%AE%E7%99%BE%E5%BA%A6%E6%AC%A1%E6%95%B0%E6%9C%80%E5%A4%9A%E7%9A%84%E9%82%A3%E4%B8%AAip) 4 | * [问题2:寻找热门查询,300万个查询字符串中统计最热门的10个查询](#%E9%97%AE%E9%A2%982%E5%AF%BB%E6%89%BE%E7%83%AD%E9%97%A8%E6%9F%A5%E8%AF%A2300%E4%B8%87%E4%B8%AA%E6%9F%A5%E8%AF%A2%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%BB%9F%E8%AE%A1%E6%9C%80%E7%83%AD%E9%97%A8%E7%9A%8410%E4%B8%AA%E6%9F%A5%E8%AF%A2) 5 | * [问题3:海量数据分布在100台电脑中,想个办法高效统计出这批数据的TOP10](#%E9%97%AE%E9%A2%983%E6%B5%B7%E9%87%8F%E6%95%B0%E6%8D%AE%E5%88%86%E5%B8%83%E5%9C%A8100%E5%8F%B0%E7%94%B5%E8%84%91%E4%B8%AD%E6%83%B3%E4%B8%AA%E5%8A%9E%E6%B3%95%E9%AB%98%E6%95%88%E7%BB%9F%E8%AE%A1%E5%87%BA%E8%BF%99%E6%89%B9%E6%95%B0%E6%8D%AE%E7%9A%84top10) 6 | * [问题4:有10个文件,每个文件1G,每个文件的每一行存放的都是用户的query,每个文件的query都可能重复。要求你按照query的频度排序。](#%E9%97%AE%E9%A2%984%E6%9C%8910%E4%B8%AA%E6%96%87%E4%BB%B6%E6%AF%8F%E4%B8%AA%E6%96%87%E4%BB%B61g%E6%AF%8F%E4%B8%AA%E6%96%87%E4%BB%B6%E7%9A%84%E6%AF%8F%E4%B8%80%E8%A1%8C%E5%AD%98%E6%94%BE%E7%9A%84%E9%83%BD%E6%98%AF%E7%94%A8%E6%88%B7%E7%9A%84query%E6%AF%8F%E4%B8%AA%E6%96%87%E4%BB%B6%E7%9A%84query%E9%83%BD%E5%8F%AF%E8%83%BD%E9%87%8D%E5%A4%8D%E8%A6%81%E6%B1%82%E4%BD%A0%E6%8C%89%E7%85%A7query%E7%9A%84%E9%A2%91%E5%BA%A6%E6%8E%92%E5%BA%8F) 7 | * [问题5:给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,找出a、b文件共同的url?](#%E9%97%AE%E9%A2%985%E7%BB%99%E5%AE%9Aab%E4%B8%A4%E4%B8%AA%E6%96%87%E4%BB%B6%E5%90%84%E5%AD%98%E6%94%BE50%E4%BA%BF%E4%B8%AAurl%E6%AF%8F%E4%B8%AAurl%E5%90%84%E5%8D%A064%E5%AD%97%E8%8A%82%E5%86%85%E5%AD%98%E9%99%90%E5%88%B6%E6%98%AF4g%E6%89%BE%E5%87%BAab%E6%96%87%E4%BB%B6%E5%85%B1%E5%90%8C%E7%9A%84url) 8 | * [问题6:一个文本文件,大约有一万行,每行一个词,要求统计出其中最频繁出现的前10个词,请给出思想,给出时间复杂度分析。](#%E9%97%AE%E9%A2%986%E4%B8%80%E4%B8%AA%E6%96%87%E6%9C%AC%E6%96%87%E4%BB%B6%E5%A4%A7%E7%BA%A6%E6%9C%89%E4%B8%80%E4%B8%87%E8%A1%8C%E6%AF%8F%E8%A1%8C%E4%B8%80%E4%B8%AA%E8%AF%8D%E8%A6%81%E6%B1%82%E7%BB%9F%E8%AE%A1%E5%87%BA%E5%85%B6%E4%B8%AD%E6%9C%80%E9%A2%91%E7%B9%81%E5%87%BA%E7%8E%B0%E7%9A%84%E5%89%8D10%E4%B8%AA%E8%AF%8D%E8%AF%B7%E7%BB%99%E5%87%BA%E6%80%9D%E6%83%B3%E7%BB%99%E5%87%BA%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6%E5%88%86%E6%9E%90) 9 | * [多层划分](#%E5%A4%9A%E5%B1%82%E5%88%92%E5%88%86) 10 | * [问题1:2\.5亿个整数中找出不重复的整数的个数,内存空间不足以容纳这2\.5亿个整数。](#%E9%97%AE%E9%A2%98125%E4%BA%BF%E4%B8%AA%E6%95%B4%E6%95%B0%E4%B8%AD%E6%89%BE%E5%87%BA%E4%B8%8D%E9%87%8D%E5%A4%8D%E7%9A%84%E6%95%B4%E6%95%B0%E7%9A%84%E4%B8%AA%E6%95%B0%E5%86%85%E5%AD%98%E7%A9%BA%E9%97%B4%E4%B8%8D%E8%B6%B3%E4%BB%A5%E5%AE%B9%E7%BA%B3%E8%BF%9925%E4%BA%BF%E4%B8%AA%E6%95%B4%E6%95%B0) 11 | * [问题2:5亿个int找它们的中位数。](#%E9%97%AE%E9%A2%9825%E4%BA%BF%E4%B8%AAint%E6%89%BE%E5%AE%83%E4%BB%AC%E7%9A%84%E4%B8%AD%E4%BD%8D%E6%95%B0) 12 | * [Bloom filter](#bloom-filter) 13 | * [问题1:\*给你A,B两个文件,各存放50亿条URL,每条URL占用64字节,内存限制是4G,让你找出A,B文件共同的URL。如果是三个乃至n个文件呢?](#%E9%97%AE%E9%A2%981%E7%BB%99%E4%BD%A0ab%E4%B8%A4%E4%B8%AA%E6%96%87%E4%BB%B6%E5%90%84%E5%AD%98%E6%94%BE50%E4%BA%BF%E6%9D%A1url%E6%AF%8F%E6%9D%A1url%E5%8D%A0%E7%94%A864%E5%AD%97%E8%8A%82%E5%86%85%E5%AD%98%E9%99%90%E5%88%B6%E6%98%AF4g%E8%AE%A9%E4%BD%A0%E6%89%BE%E5%87%BAab%E6%96%87%E4%BB%B6%E5%85%B1%E5%90%8C%E7%9A%84url%E5%A6%82%E6%9E%9C%E6%98%AF%E4%B8%89%E4%B8%AA%E4%B9%83%E8%87%B3n%E4%B8%AA%E6%96%87%E4%BB%B6%E5%91%A2) 14 | * [Bitmap](#bitmap) 15 | * [问题1:在2\.5亿个整数中找出不重复的整数,注,内存不足以容纳这2\.5亿个整数。](#%E9%97%AE%E9%A2%981%E5%9C%A825%E4%BA%BF%E4%B8%AA%E6%95%B4%E6%95%B0%E4%B8%AD%E6%89%BE%E5%87%BA%E4%B8%8D%E9%87%8D%E5%A4%8D%E7%9A%84%E6%95%B4%E6%95%B0%E6%B3%A8%E5%86%85%E5%AD%98%E4%B8%8D%E8%B6%B3%E4%BB%A5%E5%AE%B9%E7%BA%B3%E8%BF%9925%E4%BA%BF%E4%B8%AA%E6%95%B4%E6%95%B0) 16 | * [问题2:给40亿个不重复的unsignedint的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那40亿个数当中?](#%E9%97%AE%E9%A2%982%E7%BB%9940%E4%BA%BF%E4%B8%AA%E4%B8%8D%E9%87%8D%E5%A4%8D%E7%9A%84unsignedint%E7%9A%84%E6%95%B4%E6%95%B0%E6%B2%A1%E6%8E%92%E8%BF%87%E5%BA%8F%E7%9A%84%E7%84%B6%E5%90%8E%E5%86%8D%E7%BB%99%E4%B8%80%E4%B8%AA%E6%95%B0%E5%A6%82%E4%BD%95%E5%BF%AB%E9%80%9F%E5%88%A4%E6%96%AD%E8%BF%99%E4%B8%AA%E6%95%B0%E6%98%AF%E5%90%A6%E5%9C%A8%E9%82%A340%E4%BA%BF%E4%B8%AA%E6%95%B0%E5%BD%93%E4%B8%AD) 17 | * [分布式处理之Mapreduce](#%E5%88%86%E5%B8%83%E5%BC%8F%E5%A4%84%E7%90%86%E4%B9%8Bmapreduce) 18 | * [问题1:海量数据分布在100台电脑中,想个办法高效统计出这批数据的TOP10](#%E9%97%AE%E9%A2%981%E6%B5%B7%E9%87%8F%E6%95%B0%E6%8D%AE%E5%88%86%E5%B8%83%E5%9C%A8100%E5%8F%B0%E7%94%B5%E8%84%91%E4%B8%AD%E6%83%B3%E4%B8%AA%E5%8A%9E%E6%B3%95%E9%AB%98%E6%95%88%E7%BB%9F%E8%AE%A1%E5%87%BA%E8%BF%99%E6%89%B9%E6%95%B0%E6%8D%AE%E7%9A%84top10) 19 | * [问题2:一共有N个机器,每个机器上有N个数。每个机器最多存O(N)个数并对它们操作。如何找到N^2个数的中数(median)?](#%E9%97%AE%E9%A2%982%E4%B8%80%E5%85%B1%E6%9C%89n%E4%B8%AA%E6%9C%BA%E5%99%A8%E6%AF%8F%E4%B8%AA%E6%9C%BA%E5%99%A8%E4%B8%8A%E6%9C%89n%E4%B8%AA%E6%95%B0%E6%AF%8F%E4%B8%AA%E6%9C%BA%E5%99%A8%E6%9C%80%E5%A4%9A%E5%AD%98on%E4%B8%AA%E6%95%B0%E5%B9%B6%E5%AF%B9%E5%AE%83%E4%BB%AC%E6%93%8D%E4%BD%9C%E5%A6%82%E4%BD%95%E6%89%BE%E5%88%B0n2%E4%B8%AA%E6%95%B0%E7%9A%84%E4%B8%AD%E6%95%B0median) 20 | 21 | 22 | 23 | 几种数据结构和算法 24 | ---------------------- 25 | 26 | **HashMap:** 27 | 对于同一个键,哈希出来的值一定是一样的,但是不同的键哈希出来也可能一样,这就是发生了冲突(或碰撞)。 28 | 29 | **BitMap:** 30 | 可以看成是bit数组,数组的每个位置只有0或1两种状态。Java中可以使用int数组表示位图,arr[0]是一个int,一个int是32位,故可以表示0-31的数,同理arr[1]可表示32-63...实际上就是用一个32位整型表示了32个数。 31 | 32 | **大/小根堆:* 33 | *O(1)时间可在堆顶得到最大值/最小值。利用小根堆可用于求Top K问题。 34 | 35 | **布隆过滤器:** 36 | 使用长度为m的bit数组和k个Hash函数,某个键经过k个哈希函数得到k个下标,将k个下标在bit数组中对应的位置设置为1。对于每个键都重复上述过程,得到最终设置好的布隆过滤器。对于新来的键,使用同样的过程,得到k个下标,判断k个下标在bit数组中的值是否为1,若有一个不为1,说明这个键一定不在集合中。若全为1,也可能不在集合中。**就是说:查询某个键,判断不属于该集合是绝对正确的;判断属于该集合是低概率错误的。因为多个位置的1可能是由不同的键散列得到。**误报率和m的大小有关:m越小,误报率越高。利用布隆过滤器来加速查找和判断是否存在。HashMap的问题在于存储容量占比高。 37 | 38 | 分而治之/Hash映射+Hash_map统计+堆/快速/归并排序 39 | --------------------------------------------------- 40 | 41 | 既然是海量数据处理,那么可想而知,给的数据那就一定是海量的。针对这个数据的海量,可以进行**分而治之/hash映射 42 | + hash统计 + 43 | 堆/快速/归并排序** 44 | 这个过程,说白了,就是先映射,而后统计,最后排序: 45 | 46 | **分而治之/hash映射:** 47 | 针对数据太大,内存受限,只能是把大文件化成(取模映射)小文件。 48 | 49 | **hash_map统计:** 50 | 当大文件转化了小文件,那么便可以采用常规的hash_map(ip,value)来进行频率统计。 51 | 52 | **堆/快速排序**:统计完了之后,便进行排序(可采取堆排序),得到次数最多的IP。 53 | 54 | ### 问题1:海量日志数据,提取出某日访问百度次数最多的那个IP。 55 | 56 | 具体而论,则是:首先是这一天,并且是访问百度的日志中的IP取出来,逐个写入到一个大文件中。注意到IP是32位的,最多有个2\^32个IP。同样可以采用映射的方法,比如%1000,把整个大文件映射为1000个小文件,再找出每个小文中出现频率最大的IP(可以采用hash_map对那1000个文件中的所有IP进行频率统计,然后依次找出各个文件中频率最大的那个IP)及相应的频率。然后再在这1000个最大的IP中,找出那个频率最大的IP,即为所求。 57 | 58 | **关于本题,还有几个问题,如下**: 59 | 60 | 1、Hash取模是一种等价映射,不会存在同一个元素分散到不同小文件中的情况,即这里采用的是mod1000算法,那么相同的IP在hash取模后,只可能落在同一个文件中,不可能被分散的。因为如果两个IP相等,那么经过Hash(IP)之后的哈希值是相同的,将此哈希值取模(如模1000),必定仍然相等。 61 | 62 | 2、那到底什么是hash映射呢?简单来说,就是为了便于计算机在有限的内存中处理大数据,从而通过一种映射散列的方式让数据均匀分布在对应的内存位置(如大数据通过取余的方式映射成小树存放在内存中,或大文件映射成多个小文件),而这个映射散列方式便是通常所说的hash函数,设计的好的hash函数能让数据均匀分布而减少冲突。尽管数据映射到了另外一些不同的位置,但数据还是原来的数据,只是代替和表示这些原始数据的形式发生了变化而已。 63 | 64 | ### 问题2:寻找热门查询,300万个查询字符串中统计最热门的10个查询 65 | 66 | 原题:搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。假设目前有一千万个记录(这些查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就是越热门),请你统计最热门的10个查询串,要求使用的内存不能超过1G。 67 | 68 | **解答**:由上面第1题,可以看出,数据大则划为小的,如一亿个IP求Top 69 | 10,可先%1000将ip分到1000个小文件中去,并保证一种ip只出现在一个文件中,再对每个小文件中的ip进行hashmap计数统计并按数量排序,最后归并或者最小堆依次处理每个小文件的top10以得到最后的结。 70 | 71 | 但如果数据规模比较小,能一次性装入内存呢?比如这第2题,虽然有一千万个Query,但是由于重复度比较高,因此事实上只有300万的Query,每个Query255Byte,因此可以考虑把他们都放进内存中去(300万个字符串假设没有重复,都是最大长度,那么最多占用内存3M\*1K/4=0.75G。所以可以将所有字符串都存放在内存中进行处理),而现在只是需要一个合适的数据结构,在这里,HashMap绝对是最优先的选择。 72 | 73 | 所以这种情况下就可以放弃分而治之/hash映射的步骤**,直接上hash统计**,然后排序。So,针对此类典型的TOP 74 | K问题,采取的对策往往是:hashmap + 堆。如下所示: 75 | 76 | **hash_map统计:** 77 | 先对这批海量数据预处理。具体方法是:维护一个Key为Query字串,Value为该Query出现次数的HashMap,即hash_map(Query,Value),每次读取一个Query,如果该字串不在Table中,那么加入该字串,并且将Value值设为1;如果该字串在Table中,那么将该字串的计数加一即可。 78 | 79 | **堆排序:** 80 | 第二步借助堆这个数据结构,找出Top 81 | K,时间复杂度为N‘logK。即借助堆结构,我可以在log量级的时间内查找和调整/移动。因此,维护一个K(该题目中是10)大小的小根堆,然后遍历300万的Query,分别和根元素进行对比。所以,最终的时间复杂度是:O(N)+ N x O(logK),(N为1000万,N’为300万)。 82 | 83 | 当然,也可以采用trie树,关键字域存该查询串出现的次数,没有出现为0。最后用10个元素的最小推来对出现频率进行排序。 84 | 85 | ### 问题3:海量数据分布在100台电脑中,想个办法高效统计出这批数据的TOP10 86 | 87 | 如果每个数据元素只出现一次,而且只出现在某一台机器中,那么可以采取以下步骤统计出现次数TOP10的数据元素: 88 | 89 | 堆排序:在每台电脑上求出TOP10,可以采用包含10个元素的堆完成(TOP10小,用最大堆,TOP10大,用最小堆,比如求TOP10大,首先取前10个元素调整成最小堆,如果发现,然后扫描后面的数据,并与堆顶元素比较,如果比堆顶元素大,那么用该元素替换堆顶,然后再调整为最小堆。最后堆中的元素就是TOP10大)。 90 | 91 | 求出每台电脑上的TOP10后,然后把这100台电脑上的TOP10组合起来,共1000个数据,再利用上面类似的方法求出TOP10就可以了。 92 | 93 | **但如果同一个元素重复出现在不同的电脑中呢,如下例子所述**:这个时候有两种方法: 94 | 95 | **方法一:** 96 | 遍历一遍所有数据,重新hash取摸,如此使得同一个元素只出现在单独的一台电脑中,然后采用上面所说的方法,统计每台电脑中各个元素的出现次数找出TOP10,继而组合100台电脑上的TOP10,找出最终的TOP10。 97 | 98 | **方法二:** 99 | 暴力求解。直接统计统计每台电脑中各个元素的出现次数,然后把同一个元素在不同机器中的出现次数相加,最终从所有数据中找出TOP10。 100 | 101 | ### 问题4:有10个文件,每个文件1G,每个文件的每一行存放的都是用户的query,每个文件的query都可能重复。要求你按照query的频度排序。 102 | 103 | **方案1**:直接上hash映射:顺序读取10个文件,按照hash(query)%10的结果将query写入到另外10个文件(记为a0,a1,..a9)中。这样新生成的文件每个的大小大约也1G(假设hash函数是随机的)。 104 | 105 | hash_map统计:找一台内存在2G左右的机器,依次对用hash_map(query, 106 | query_count)来统计每个query出现的次数。注:hash_map(query,query_count)是用来统计每个query的出现次数,不是存储他们的值,出现一次,则count+1。 107 | 108 | 堆/快速/归并排序:利用快速/堆/归并排序按照出现次数进行排序,将排序好的query和对应的query_cout输出到文件中,这样得到了10个排好序的文件(记为)。最后,对这10个文件进行归并排序(内排序与外排序相结合)。 109 | 110 | **方案2** 111 | :一般query的总量是有限的,只是重复的次数比较多而已,可能对于所有的query,一次性就可以加入到内存了。这样就可以采用trie树/hash_map等直接来统计每个query出现的次数,然后按出现次数做快速/堆/归并排序就可以了。 112 | 113 | **方案3:** 114 | 与方案1类似,但在做完hash,分成多个文件后,可以交给多个文件来处理,采用分布式的架构来处理(比如MapReduce),最后再进行合并。 115 | 116 | ### 问题5:给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,找出a、b文件共同的url? 117 | 118 | 可以估计每个文件安的大小为5G×64=320G,远远大于内存限制的4G。所以不可能将其完全加载到内存中处理。考虑采取分而治之的方法。 119 | 120 | 分而治之/hash映射:遍历文件a,对每个url求取,然后根据所取得的值将url分别存储到1000个小文件中。这样每个小文件的大约为300M。遍历文件b,采取和a相同的方式将url分别存储到1000小文件中。这样处理后,所有可能相同的url都在对应的小文件()中,不对应的小文件不可能有相同的url。然后只要求出1000对小文件中相同的url即可。 121 | 122 | hash_set统计:求每对小文件中相同的url时,可以把其中一个小文件的url存储到hash_set中。然后遍历另一个小文件的每个url,看其是否在刚才构建的hash_set中,如果是,那么就是共同的url,存到文件里面就可以了。 123 | 124 | ### 问题6:一个文本文件,大约有一万行,每行一个词,要求统计出其中最频繁出现的前10个词,请给出思想,给出时间复杂度分析。 125 | 126 | **方案1:** 127 | 如果文件比较大,无法一次性读入内存,可以采用hash取模的方法,将大文件分解为多个小文件,对于单个小文件利用hash_map统计出每个小文件中10个最常出现的词,然后再进行归并处理,找出最终的10个最常出现的词。 128 | 129 | **方案2:** 130 | 通过hash取模将大文件分解为多个小文件后,除了可以用hash_map统计出每个小文件中10个最常出现的词,也可以用trie树统计每个词出现的次数,时间复杂度是O(n\*le)(le表示单词的平准长度),最终同样找出出现最频繁的前10个词(可用堆来实现),时间复杂度是O(n\*lg10)。 131 | 132 | 多层划分 133 | ------------ 134 | 135 | **多层划分其实本质上还是分而治之的思想,重在“分”的技巧上!** 136 | 137 | 适用范围:第k大,中位数,不重复或重复的数字 138 | 139 | 基本原理及要点:因为元素范围很大,不能利用直接寻址表,所以通过多次划分,逐步确定范围,然后最后在一个可以接受的范围内进行。 140 | 141 | ### 问题1:2.5亿个整数中找出不重复的整数的个数,内存空间不足以容纳这2.5亿个整数。 142 | 143 | 有点像鸽巢原理,整数个数为2\^32,也就是可以将这2\^32个数划分为2\^8个区域(比如用单个文件代表一个区域),然后将数据分离到不同的区域,然后不同的区域在利用bitmap就可以直接解决了。也就是说只要有足够的磁盘空间,就可以很方便的解决。 144 | 145 | ### 问题2:5亿个int找它们的中位数。 146 | 147 | 思路一:这个例子比上面那个更明显。首先将int划分为2\^16个区域,然后读取数据统计落到各个区域里的数的个数,之后根据统计结果就可以判断中位数落到那个区域,同时知道这个区域中的第几大数刚好是中位数。然后第二次扫描我们只统计落在这个区域中的那些数就可以了。 148 | 149 | 实际上,如果不是int是int64,可以经过3次这样的划分即可降低到可以接受的程度。即可以先将int64分成2\^24个区域,然后确定区域的第几大数,在将该区域分成2\^20个子区域,然后确定是子区域的第几大数,然后子区域里的数的个数只有2\^20,就可以直接利用direct 150 | addr table进行统计了。 151 | 152 | 思路二:同样需要做两遍统计,如果数据存在硬盘上,就需要读取2次。方法同基数排序有些像,开一个大小为65536的int数组,第一遍读取,统计Int32的高16位的情况,也就是0-65535,都算作0,65536 /- 131071都算作1。就相当于用该数除以65536。Int32 除以 153 | 65536的结果不会超过65536种情况,因此开一个长度为65536的数组计数就可以。每读取一个数,数组中对应的计数+1,考虑有负数的情况,需要将结果加32768后,记录在相应的数组内。 154 | 155 | 第一遍统计之后,遍历数组,逐个累加统计,看中位数处于哪个区间,比如处于区间k,那么0- 156 | k-1的区间里数字的数量sum应该\ 292 | 293 | > **三种协议:1.握手协议 2.记录协议 3,警报协议** 294 | 295 | 296 | 浏览器中输入URL到页面加载的发生了什么 297 | ----------------------------------------- 298 | 299 | **一般会经历以下几个过程:** 300 | 301 | 1、在浏览器地址栏中输入url 302 | 303 | 2、浏览器先查看**浏览器缓存-系统缓存-路由器缓存**,如果缓存中有,会直接在屏幕中显示页面内容。若没有,则跳到第三步操作。 304 | 305 | 3、在发送http请求前,需要**域名解析(DNS解析)**,解析获取相应的IP地址。 306 | 307 | 4、浏览器向服务器发起tcp连接,与浏览器建立tcp三次握手。 308 | 309 | 5、握手成功后,浏览器向服务器发送http请求,请求数据包。 310 | 311 | 6、服务器处理收到的请求,将数据返回至浏览器 312 | 313 | 7、浏览器收到HTTP响应 314 | 315 | 8、读取页面内容,浏览器渲染,解析html源码 316 | 317 | 9、生成Dom树、解析css样式、js交互 318 | 319 | 10、客户端和服务器交互 320 | 321 | **步骤2的具体过程是:** 322 | 323 | **浏览器缓存**:浏览器会记录DNS一段时间,因此,只是第一个地方解析DNS请求; 324 | 325 | **操作系统缓存**:如果在浏览器缓存中不包含这个记录,则会使系统调用操作系统,获取操作系统的记录(保存最近的DNS查询缓存); 326 | 327 | **路由器缓存**:如果上述两个步骤均不能成功获取DNS记录,继续搜索路由器缓存; 328 | 329 | **ISP缓存**:若上述均失败,继续向ISP搜索。 330 | 331 | 332 | **DNS的工作原理及过程分下面几个步骤:** 333 | 334 | 1、客户机提出域名解析请求,并将该请求发送给本地的域名服务器。 335 | 336 | 2、当本地的域名服务器收到请求后,就先查询本地的缓存,如果有该纪录项,则本地的域名服务器就直接把查询的结果返回。 337 | 338 | 3、如果本地的缓存中没有该纪录,则本地域名服务器就直接把请求发给根域名服务器,然后根域名服务器再返回给本地域名服务器一个所查询域(根的子域) 339 | 的主域名服务器的地址。 340 | 341 | 4、本地服务器再向上一步返回的域名服务器发送请求,然后接受请求的服务器查询自己的缓存,如果没有该纪录,则返回相关的下级的域名服务器的地址。 342 | 343 | 5、重复第四步,直到找到正确的纪录。 344 | 345 | 6、本地域名服务器把返回的结果保存到缓存,以备下一次使用,同时还将结果返回给客户机。 346 | 347 | WebSocket 348 | ------------- 349 | 350 | WebSocket解决的问题是实现客户端与服务端的双向通信,它的特点如下: 351 | 352 | (1)建立在TCP协议之上,服务器端的实现比较容易。 353 | 354 | (2)与HTTP协议有着良好的兼容性。默认端口也是80和443,并且**握手阶段采用 355 | HTTP协议**,因此握手时不容易屏蔽,能通过各种HTTP代理服务器。 356 | 357 | (3)数据格式比较轻量,性能开销小,通信高效。 358 | 359 | (4)可以发送文本,也可以发送二进制数据。 360 | 361 | (5)没有同源限制,客户端可以与任意服务器通信。 362 | 363 | **实际上,它的最大的特点在于服务端可以主动的向客户端推送消息。** 364 | 365 | **WebSocket是HTML5出的东西(协议)**,也就是说HTTP协议没有变化,或者说没关系,但HTTP是不支持持久连接的(长连接,循环连接的不算)。首先HTTP有1.1和1.0之说,也就是所谓的keep-alive,把多个HTTP请求合并为一个,但是Websocket其实是一个新协议,跟HTTP协议基本没有关系,只是为了兼容现有浏览器的握手规范而已,也就是说它是HTTP协议上的一种补充。 366 | 367 | HTTP的生命周期通过Request来界定,也就是一个Request 368 | 一个Response,那么在HTTP1.0中,这次HTTP请求就结束了。在HTTP1.1中进行了改进,使得有一个keep-alive,也就是说,在一个HTTP连接中,可以发送多个Request,接收多个Response。但是请记住 369 | Request = 370 | Response,在HTTP中永远是这样,也就是说一个request只能有一个response。而且这个response也是被动的,不能主动发起。 371 | 372 | https://halfrost.com/websocket/#websocket 373 | 374 | Restful 375 | ------------- 376 | 377 | REST的全称是Representational State 378 | Transfer,中文含义为**表现层状态变化**。符合REST规范的设计,都可以称为RESTful设计。 379 | 380 | RESTful的设计哲学主要将服务器提供的内容实体看作一个资源,并表现在URL上。对这个资源的操作,主要体现在HTTP请求的方法上,不是体现在URL上。 381 | 382 | **所谓"资源"**,就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或独一无二的识别符。**"资源"是一种信息实体**,它可以有多种外在表现形式,把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。比如图片可以用JPG格式表现,也可以用PNG格式表现。**URI只代表资源的实体,不代表它的形式.** 383 | 384 | 访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。 385 | 386 | 互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State 387 | Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。 388 | 389 | **客户端用到的手段,只能是HTTP协议。**具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。 390 | 391 | 综上来看: 392 | 393 | (1)每一个URI代表一种资源 394 | 395 | (2)客户端和服务器之间,传递这种资源的某种表现层 396 | 397 | (3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现表现层状态转化 398 | 399 | -------------------------------------------------------------------------------- /notes/计算机网络/TCP与UDP.md: -------------------------------------------------------------------------------- 1 | * [UDP](#udp) 2 | * [UDP的报文结构](#udp%E7%9A%84%E6%8A%A5%E6%96%87%E7%BB%93%E6%9E%84) 3 | * [UDP特性](#udp%E7%89%B9%E6%80%A7) 4 | * [UDP的缺陷](#udp%E7%9A%84%E7%BC%BA%E9%99%B7) 5 | * [对UDP一次发送多少bytes好](#%E5%AF%B9udp%E4%B8%80%E6%AC%A1%E5%8F%91%E9%80%81%E5%A4%9A%E5%B0%91bytes%E5%A5%BD) 6 | * [TCP](#tcp) 7 | * [TCP的报文格式](#tcp%E7%9A%84%E6%8A%A5%E6%96%87%E6%A0%BC%E5%BC%8F) 8 | * [TCP三次握手](#tcp%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B) 9 | * [TCP四次挥手](#tcp%E5%9B%9B%E6%AC%A1%E6%8C%A5%E6%89%8B) 10 | * [TCP粘包/拆包问题](#tcp%E7%B2%98%E5%8C%85%E6%8B%86%E5%8C%85%E9%97%AE%E9%A2%98) 11 | * [TCP如何保证可靠性传输](#tcp%E5%A6%82%E4%BD%95%E4%BF%9D%E8%AF%81%E5%8F%AF%E9%9D%A0%E6%80%A7%E4%BC%A0%E8%BE%93) 12 | * [滑动窗口机制](#%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%BA%E5%88%B6) 13 | * [TCP状态转移](#tcp%E7%8A%B6%E6%80%81%E8%BD%AC%E7%A7%BB) 14 | * [TCP第三次握手失败后怎么办](#tcp%E7%AC%AC%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B%E5%A4%B1%E8%B4%A5%E5%90%8E%E6%80%8E%E4%B9%88%E5%8A%9E) 15 | * [TCP当被告知接收窗口为0后的行为](#tcp%E5%BD%93%E8%A2%AB%E5%91%8A%E7%9F%A5%E6%8E%A5%E6%94%B6%E7%AA%97%E5%8F%A3%E4%B8%BA0%E5%90%8E%E7%9A%84%E8%A1%8C%E4%B8%BA) 16 | * [一个 TCP 连接上面能发多少 HTTP请求](#%E4%B8%80%E4%B8%AA-tcp-%E8%BF%9E%E6%8E%A5%E4%B8%8A%E9%9D%A2%E8%83%BD%E5%8F%91%E5%A4%9A%E5%B0%91-http%E8%AF%B7%E6%B1%82) 17 | * [TCP与UDP区别](#tcp%E4%B8%8Eudp%E5%8C%BA%E5%88%AB) 18 | 19 | 20 | 21 | ## UDP 22 | 23 | UDP是一种**属于传输层**的用户数据报**(User Datagram 24 | Protocol)**协议,无连接,不保证传输的可靠性。对于来自应用层的数据包,直接加上**UDP报头**然后传送给IP。UDP头部中有一个校验和字段,可用于差错的检测,但是UDP是不提供差错纠正的。此外IPV4不强制这个校验和字段必须使用,但IPV6是强制要求使用的。 25 | 26 | UDP报文没有可靠性保证、顺序保证和流量控制字段等,可靠性较差。但是正因为UDP协议的控制选项较少,在数据传输过程中延迟小、数据传输效率高,适合对可靠性要求不高的应用程序,或者可以保障可靠性的应用程序,如DNS、TFTP、SNMP等。 27 | 28 | ### UDP的报文结构 29 | 30 | UDP在IP报文中的位置如下图所示: 31 | 32 | ![这里写图片描述](media/611b769803b9c47bf7ce2f6303b8ce4f.png) 33 | 34 | **UDP的报头结构如下**,其中: 35 | 36 | 1.源端口号如果不需要可以置0。 37 | 38 | 2.长度字段是UDP首部和UDP数据的总长度,这个字段是冗余的,因为IP中包含了数据报长度信息。 39 | 40 | 3.校验和字段是端到端的,它覆盖了UDP头部、UDP数据、一个伪头部、填充字节,由初始的发送方计算得到,由最终的目的方计算然后校验。 41 | 42 | ![https://img-blog.csdn.net/20180320190843766](media/af8028eff4358aa8a1aa927457c5c07b.png) 43 | 44 | ### UDP特性 45 | 46 | UDP是一个无连接协议,传输数据之前源端和终端不建立连接,当UDP它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。在发送端,UDP传送数据的速度仅仅是受应用程序生成数据的速度、计算机的能力和传输带宽的限制;在接收端,UDP把每个消息段放在队列中,应用程序每次从队列中读一个消息段。 47 | 48 | 由于传输数据不建立连接,因此也就不需要维护连接状态,包括收发状态等,因此一台服务机可同时向多个客户机传输相同的消息。 49 | 50 | UDP信息包的标题很短,只有8个字节,相对于TCP的20个字节信息包的额外开销很小。 51 | 52 | 吞吐量不受拥挤控制算法的调节,只受应用软件生成数据的速率、传输带宽、源端和终端主机性能的限制。 53 | 54 | UDP使用尽最大努力交付,即不保证可靠交付,因此主机不需要维持复杂的链接状态表(这里面有许多参数)。 55 | 56 | UDP是面向报文的。发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付给IP层。既不拆分,也不合并,而是保留这些报文的边界,因此,应用程序需要选择合适的报文大小。 57 | 58 | ### UDP的缺陷 59 | 60 | **UDP传输过程中存在的主要问题:** 61 | 62 | > **1.丢失和乱序:** 63 | 因为UDP不提供ACK、序列号等机制,所以是没有办法知道是否有报文丢失以及接收方到达等报文顺序是否和发送方发送的报文数据一样。 64 | 65 | > **2.差错:** 66 | 对于差错问题则是可以通过校验和等检测到,但是不提供差错纠正。 67 | 68 | > **3.数据完整性:** 69 | UDP协议头部虽然有16位的校验和,但是IPv4并不强制执行,也就是说UDP无法保证数据的完整性。 70 | 71 | **UDP如何解决其传输过程中的问题:** 72 | 73 | 想要保证数据的可靠投递和正确排序,必须由**应用程序自己实现这些保护功能**,简单思路,既然原生UDP有那么多痛点,所以可以像应用层协议一样在UDP数据包头再加一段包头,从而定义为RUDP[(Reliable 74 | UDP)](http://www.sohu.com/a/208825991_467759)。 75 | 76 | > **1.数据完整性:** 77 | 加上一个16或者32位的CRC验证字段。 78 | 79 | > **2.乱序:** 80 | 加上一个数据包序列号SEQ。 81 | 82 | > **3.丢包**:需要确认和重传机制,就是和Tcp类似的Ack机制(若中间包丢失可以通过序列号累计而检测到,但是一开始的包就丢失是没有办法通过序列号检测到的) 83 | 84 | > **4.协议字段**:protol字段,标识当前使用协议。 85 | 86 | 87 | 88 | ### 对UDP一次发送多少bytes好 89 | 90 | 首先,TCP/IP通常被认为是一个四层协议系统,包括链路层、网络层、传输层、应用层。**UDP属于传输层,** 91 | 下面由下至上一步一步来看: 92 | 93 | 以太网(Ethernet)数据帧的长度必须在46-1500字节之间,这是由以太网的物理特性决定 94 | 95 | 的。这个1500字节被称为链路层的**MTU(最大传输单元)**。 96 | 但这并不是指链路层的长度被限制在1500字节,其实这个MTU指的是链路层的数据区并不包括链路层的首部和尾部的18个字节。所以**事实上这个1500字节就是网络层IP数据报的长度限制。**因为IP数据报的首部为20字节,所以IP数据报的数据区长度最大为1480字节。而这个1480字节就是用来放TCP传来的TCP报文段或UDP传来的UDP数据报的。又因为UDP数据报的首部8字节,所以UDP数据报的数据区最大长度为1472字节。**这个1472字节就是可以使用的字节数。** 97 | 98 | 当发送的UDP数据大于1472,也就是说IP数据报大于1500字节,大于MTU。这个时候发送方IP层就需要分片(fragmentation)。把数据报分成若干片,使每一片都小于MTU。而接收方IP层则需要进行数据报的重组。这样就会多做许多事情,而更严重的是,由于UDP的特性,当某一片数据传送中丢失时,无法重组数据报,将导致丢弃整个UDP数据报。因此,在普通的局域网环境下,建议将UDP的数据控制在1472字节以下为好。 99 | 100 | **UDP最大数据报长度:** 101 | 有两个原因使得大小满额的数据报不能被端到端投递:一是系统的本地协议实现可能有一些限制;二是接收应用程序可能没准备好去接收这么大的数据。 102 | 103 | **UDP数据报截断:** 104 | 当UDP数据报长度超过接收端允许长度时,会发生数据报截断,之后会有几种处理:丢弃超过应用程序可接收字节的部分;将这些超出的数据存到后续的读操作;通知调用者被截断了多少数据;或者只通知被截断,但不通知具体截断数量。 105 | 106 | ## TCP 107 | 108 | **TCP(Transmission Control Protocol,传输控制协议)** 109 | 是面向连接的协议,每一条TCP连接只能有两个端点(endpoint),每一条TCP连接只能是点对点的(一对一),TCP提供可靠交付的服务,TCP提供全双工通信,面向字节流。TCP传送的数据单位协议是 110 | TCP 报文段(segment)。 111 | 112 | ### TCP的报文格式 113 | 114 | ![https://pic002.cnblogs.com/images/2012/387401/2012070916030558.png](media/92e8ffd5026d530e630b6ba90c23236f.png) 115 | 116 | **各个段位说明:** 117 | 118 | **源端口和目的端口**:各2 119 | 字节.端口是传输层与应用层的服务接口.传输层的复用和分用功能都要通过端口才能实现。 120 | 121 | **序号:** 122 | 占4字节.TCP连接中传送的数据流中的每一个字节都编上一个序号.序号字段的值则指的是本报文段所发送的数据的第一个字节的序号。 123 | 124 | **确认号**:占4字节,是期望收到对方的下一个报文段的数据的第一个字节的序号。 125 | 126 | **数据偏移/首部长度**:占 4 位,它指出 TCP 报文段的数据起始处距离 TCP 127 | 报文段的起始处有多远。 128 | 129 | **保留**:占 6 位,保留为今后使用,但目前应置为 0。 130 | 131 | **紧急URG** 132 | :当URG=1时,表明紧急指针字段有效.它告诉系统此报文段中有紧急数据,应尽快传送(相当于高优先级的数据)。 133 | 134 | **确认ACK:** 135 | 只有当 ACK=1 时确认号字段才有效.当 ACK=0 时,确认号无效。 136 | 137 | **PSH(PuSH):* 138 | *接收 TCP 收到 PSH = 1 139 | 的报文段,就尽快地交付接收应用进程,而不再等到整个缓存都填满了后再向上交付。 140 | 141 | **RST (ReSeT):** 142 | 当 RST=1 时,表明 TCP连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立运输连接。 143 | 144 | 145 | **同步SYN:** 146 | 同步 SYN = 1 表示这是一个连接请求或连接接受报文。 147 | 148 | **终止FIN:** 149 | 用来释放一个连接.FIN=1表明此报文段的发送端的数据已发送完毕,并要求释放运输连接。 150 | 151 | **检验和:** 152 | 占 2字节.检验和字段检验的范围包括首部和数据这两部分.在计算检验和时,要在 TCP 153 | 报文段的前面加上 12 字节的伪首部紧急指针占 16 位,指出在本报文段中紧急数据共有多少个字节。 154 | 155 | **选项:** 156 | 长度可变.TCP 最初只规定了一种选项,即最大报文段长度 MSS.MSS 告诉对方TCP:“我的缓存所能接收的报文段的数据字段的最大长度是 MSS 个字节.” [MSS(Maximum 157 | Segment Size)是TCP报文段中的数据字段的最大长度.数据字段加上 TCP 首部才等于整个的TCP 报文段] 158 | 159 | **填充:** 160 | 这是为了使整个首部长度是 4 字节的整数倍 161 | 162 | **其他选项:** 163 | 164 | > **窗口扩大:** 165 | 占3字节,其中有一个字节表示移位值 S.新的窗口值等于TCP首部中的窗口位数增大到(16 + S),相当于把窗口值向左移动 S 位后获得实际的窗口大小 166 | 167 | > **时间戳:** 168 | 占10字节,其中最主要的字段时间戳值字段(4字节)和时间戳回送回答字段(4字节) 169 | 170 | > **选择确认:** 171 | 接收方收到了和前面的字节流不连续的两2字节.如果这些字节的序号都在接收窗口之内,那么接收方就先收下这些数据,但要把这些信息准确地告诉发送方,使发送方不要再重复发送这些已收到的数据 172 | 173 | ### TCP三次握手 174 | 175 | 三次握手发生在TCP连接的建立阶段: 176 | 177 | ![](media/c6f8db684870294ee2d10b9c1e0e7379.png) 178 | 179 | A的TCP向B发出连接请求报文段,其首部中的同步位SYN = 1,并选择序号 seq = x,表明传送数据时的第一个数据字节的序号是 x 180 | 181 | B的TCP收到连接请求报文段后,如同意,则发回确认(B在确认报文段中应使 SYN = 1,使ACK=1,其确认号ack =x﹢1,自己选择的序号 seq = y) 182 | 183 | A收到此报文段后向 B 给出确认,其 ACK = 1,确认号 ack = y﹢1(A 的 TCP 通知上层应用进程,连接已经建立,B 的 TCP 收到主机 A 的确认后,也通知其上层应用进程:TCP 连接已经建立)。 184 | 185 | **为什么是三次握手而不是采用两次握手?** 186 | 187 | **第三次握手是为了防止失效的连接请求到达服务器,让服务器错误打开连接。** 188 | 189 | **客户端发送的连接请求如果在网络中滞留,那么就会隔很长一段时间才能收到服务器端发回的连接确认。客户端等待一个超时重传时间之后,就会重新请求连接。但是这个滞留的连接请求最后还是会到达服务器,如果不进行三次握手,那么服务器就会打开两个连接。如果有第三次握手,客户端会忽略服务器之后发送的对滞留连接请求的连接确认,不进行第三次握手,因此就不会再次打开连接。** 190 | 191 | ### TCP四次挥手 192 | 193 | 四次握手发生在TCP连接的断开阶段: 194 | 195 | ![](media/511b0157a8f0b6ad5c37d379e35c6410.png) 196 | 197 | **数据传输结束后,** 198 | 通信的双方都可释放连接.现在 A 的应用进程先向其 TCP发出连接释放报文段,并停止再发送数据,主动关闭 TCP 连接(A 把连接释放报文段首部的FIN = 1,其序号seq = u,等待 B 的确认) 199 | 200 | B 发出确认,确认号 ack = u+1,而这个报文段自己的序号 seq = v(TCP服务器进程通知高层应用进程.从 A 到 B 这个方向的连接就释放了,**TCP连接处于半关闭状态**.B 若发送数据,A 仍要接收) 201 | 202 | 若 B 已经没有要向 A 发送的数据,其应用进程就通知 TCP 释放连接A 收到连接释放报文段后,必须发出确认,在确认报文段中 ACK = 1,确认号ack=w﹢1,自己的序号 seq = u + 1 203 | 204 | TIME_WAIT的原因: 205 | 206 | 客户端接收到服务器端的 FIN 报文后进入此状态,此时并不是直接进入 CLOSED状态,还需要等待一个时间计时器设置的时间 2MSL。这么做有两个理由: 207 | 208 | **确保最后一个确认报文能够到达。** 209 | 如果 B 没收到 A发送来的确认报文,那么就会重新发送连接释放请求报文,A等待一段时间就是为了处理这种情况的发生。 210 | 211 | **等待一段时间是为了让本连接持续时间内所产生的所有报文都从网络中消失**,使得下一个新的连接不会出现旧的连接请求报文。 212 | 213 | **利用保活计数器可以判断客户端是否断开。** 214 | 215 | ### TCP粘包/拆包问题 216 | 217 | 218 | 粘包、拆包表现形式 219 | 220 | 现在假设客户端向服务端连续发送了两个数据包,用packet1和packet2来表示,那么服务端收到的数据可以分为三种,现列举如下: 221 | 222 | 第一种情况,接收端正常收到两个数据包,即没有发生拆包和粘包的现象。 223 | 224 | ![normal](media/d8c9808d010f264e6b251f719138a80a.png) 225 | 226 | 第二种情况,接收端只收到一个数据包,由于TCP是不会出现丢包的,所以这一个数据包中包含了发送端发送的两个数据包的信息,这种现象即为粘包。这种情况由于接收端不知道这两个数据包的界限,所以对于接收端来说很难处理。 227 | 228 | ![one](media/4a7176622d55c07324e96b531e1b8f78.png) 229 | 230 | 第三种情况,这种情况有两种表现形式,如下图。接收端收到了两个数据包,但是这两个数据包要么是不完整的,要么就是多出来一块,这种情况即发生了拆包和粘包。这两种情况如果不加特殊处理,对于接收端同样是不好处理的。 231 | 232 | ![half_one](media/e294ecec380425a71209c1a7b464a359.png) 233 | 234 | ![one_half](media/651c2597c261a59847623aeb681fc045.png) 235 | 236 | **粘包、拆包发生原因** 237 | 238 | 发生TCP粘包或拆包有很多原因,现列出常见的几点, 239 | 240 | 1、要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。 241 | 242 | 2、待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。 243 | 244 | 3、要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。 245 | 246 | 4、接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。 247 | 248 | **粘包、拆包解决办法** 249 | 250 | 常用的方法有如下几个: 251 | 252 | 1、发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。 253 | 254 | 2、发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。 255 | 256 | 3、可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。 257 | 258 | **UDP为什么不会** 259 | 260 | UDP是基于报文发送的,从UDP的帧结构可以看出,在UDP首部采用了16bit来指示UDP数据报文的长度,因此在应用层能很好的将不同的数据报文区分开,从而避免粘包和拆包的问题。而TCP是基于字节流的,虽然应用层和TCP传输层之间的数据交互是大小不等的数据块,但是TCP把这些数据块仅仅看成一连串无结构的字节流,没有边界;另外从TCP的帧结构也可以看出,在TCP的首部没有表示数据长度的字段。基于上面两点,在使用TCP传输数据时,才有粘包或者拆包现象发生的可能。 261 | 262 | ### TCP如何保证可靠性传输 263 | 264 | **1.数据包校验:** 265 | 目的是检测数据在传输过程中的任何变化,若校验出包有错,则丢弃报文段并且不给出响应,这时TCP发送数据端超时后会重发数据 266 | 267 | **2.对失序数据包重排序:** 268 | 既然TCP报文段作为IP数据报来传输,而IP数据报的到达可能会失序,因此TCP报文段的到达也可能会失序。TCP将对失序数据进行重新排序,然后才交给应用层 269 | 270 | **3.丢弃重复数据:** 271 | 对于重复数据,能够丢弃重复数据 272 | 273 | **4.应答机制:** 274 | 当TCP收到发自TCP连接另一端的数据,它将发送一个确认。这个确认不是立即发送,通常将推迟几分之一秒 275 | 276 | **5.超时重发**:当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段 277 | 278 | **6.流量控制**:流量控制一般指的就是在接收方接受报文段的时候,应用层的上层程序可能在忙于做一些其他的事情,没有时间处理缓存中的数据,如果发送方在发送的时候不控制它的速度很有可能导致接受缓存溢出,导致数据丢失。流量控制的基础是滑动窗口机制。 279 | 280 | 相对的还有一种情况是由于两台主机之间的网络比较拥塞,如果发送方还是以一个比较快的速度发送的话就可能导致大量的丢包,这个时候也需要发送方降低发送的速度。 281 | 282 | 虽然看起来上面的两种情况都是由于可能导致数据丢失而让发送主机降低发送速度,但是一定要把这两种情况分开,**因为前者是属于流量控制而后者拥塞控制**。 283 | 284 | **7.拥塞控制:** 285 | 拥塞控制一般都是由于网络中的主机发送的数据太多导致的拥塞,一般拥塞的都是一些负载比较高的路由,这时候为了获得更好的数据传输稳定性,必须采用拥塞控制,当然也为了减轻路由的负载防止崩溃。 286 | 287 | 有两个拥塞控制的方法,一个是慢开始与拥塞避免,另外一个称为快重传与快恢复。 288 | 289 | **慢开始与拥塞避免:不要一开始就发送大量的数据,先探测一下网络的拥塞程度,也就是说由小到大逐渐增加拥塞窗口的大小,**当然收到单个确认但此确认多个数据报**(实际上应当是字节)**的时候就加相应的数值。所以一次传输轮次之后拥塞窗口就加倍。**这就是乘法增长,和后面的拥塞避免算法的加法增长比较。** 290 | 291 | 为了防止cwnd增长过大引起网络拥塞,还需设置一个慢开始门限ssthresh状态变量。ssthresh的用法如下: 292 | 293 | 当cwnd\ssthresh时,改用拥塞避免算法。 296 | 297 | 当cwnd=ssthresh时,慢开始与拥塞避免算法任意。 298 | 299 | 拥塞避免算法让拥塞窗口缓慢增长,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍。这样拥塞窗口按线性规律缓慢增长。 300 | 301 | 无论是在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞(**其根据就是没有收到确认,虽然没有收到确认可能是其他原因的分组丢失,但是因为无法判定,所以都当做拥塞来处理)**,就把慢开始门限设置为出现拥塞时的发送窗口大小的一半。然后把拥塞窗口设置为1,执行慢开始算法。如下图: 302 | 303 | ![https://img-blog.csdn.net/20130801220438375?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2ljb2ZpZWxk/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast](media/ecb3bf5299da01ef5a996f263d0dc8fe.jpg) 304 | 305 | 2.快重传与快恢复 306 | 307 | 快重传要求接收方在收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方)而不要等到自己发送数据时捎带确认。快重传算法规定,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器时间到期。 308 | 309 | 快重传配合使用的还有快恢复算法,有以下两个要点: 310 | 311 | 当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把ssthresh门限减半。但是接下去并不执行慢开始算法。 312 | 313 | 考虑到如果网络出现拥塞的话就不会收到好几个重复的确认,所以发送方现在认为网络可能没有出现拥塞。所以此时不执行慢开始算法,而是将cwnd设置为ssthresh的大小,然后执行拥塞避免算法。如下图 314 | 315 | ![https://img-blog.csdn.net/20130801220615250?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc2ljb2ZpZWxk/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast](media/328c8413e932880c1e7978b79c72cf41.jpg) 316 | 317 | ### 滑动窗口机制 318 | 319 | TCP的滑动窗口主要有两个作用,一是提供TCP的可靠性,二是提供TCP的流控特性。同时滑动窗口机制还体现了TCP面向字节流的设计思路。可靠:对发送的数据进行确认。流控制:窗口大小随链路变化。 320 | 321 | TCP的窗口滑动技术通过动态改变窗口的大小来调节两台主机之间数据传输。每个TCP/IP主机支持全双工数据传输,因此TCP有两个滑动窗口,一个用于接收数据,一个用于发送数据。接收方设备要求窗口大小为0时,表明接收方已经接收了全部数据,或者接收方应用程序没有时间读取数据,要求暂停发送。 322 | 323 | TCP在传送数据时,第一次发数据发送方的窗口大小是由链路带宽决定的,但是接受方在接收到发送方的数据后,返回ack确认报文,同时也告诉了发送方自己的窗口大小,此时发送发第二次发送数据时,会改变自己的窗口大小和接受方一致。 324 | 325 | 当窗口过大时,会导致不必要的数据来拥塞我们的链路,但是窗口太小时,会造成很大的延时。**当链路变好了或者变差了这个窗口还会发生变话,并不是第一次协商好了以后就永远不变了。** 326 | 327 | 滑动窗口协议是TCP使用的一种流量控制方法。该协议允许发送方在停止并等待接收确认报文前可以连续发送多个分组。由于发送方不必每发一个分组就停下来等待确认,因此该协议可以加速数据的传输。 328 | 只有在接收窗口向前滑动时(与此同时也发送了确认),发送窗口才有可能向前滑动,收发两端的窗口按照以上规律不断地向前滑动。 329 | 330 | ![https://pic002.cnblogs.com/images/2012/387401/2012070916135320.png](media/ecefe493cd15f8516606a66f0cc5d599.png) 331 | 332 | ![https://pic002.cnblogs.com/images/2012/387401/2012070916140463.png](media/37790def0829cfd904ce2ebf003c0a44.png) 333 | 334 | ![https://pic002.cnblogs.com/images/2012/387401/2012070916141121.png](media/dd5683b920fb4ac02ba9cb22438007e0.png) 335 | 336 | ![https://pic002.cnblogs.com/images/2012/387401/2012070916144958.png](media/483a269b059d43ecc09825bbd447a0a8.png) 337 | 338 | ### TCP状态转移 339 | 340 | ![IMG_256](media/8d0dca7af83c0c07123853bd4e03856e.jpg) 341 | 342 | **1.CLOSED:起始点**,在超时或者连接关闭时候进入此状态。 343 | 344 | 2.LISTEN:服务端在等待连接过来时候的状态,此称为应用程序被动打开(等待客户端来连接)。 345 | 346 | **3.SYN_SENT:** 347 | 客户端发起连接,发送SYN给服务器端。如果服务器端不能连接,则直接进入CLOSED状态。 348 | 349 | **4.SYN_RCVD:** 350 | 跟3对应,服务器端接受客户端的SYN请求,服务器端由LISTEN状态进入SYN_RCVD状态。同时服务器端要回应一个ACK,同时发送一个SYN给客户端;另外一种情况,客户端在发起SYN的同时接收到服务器端得SYN请求,客户端就会由SYN_SENT到 351 | SYN_RCVD状态。 352 | 353 | **5.ESTABLISHED:** 354 | 服务器端和客户端在完成3次握手进入状态,说明已经可以开始传输数据了 355 | 356 | **以上是建立连接时服务器端和客户端产生的状态转移说明。下面是连接关闭时候的状态转移说明,关闭需要进行4次双方的交互,还包括要处理一些善后工作(TIME_WAIT状态),注意,这里主动关闭的一方或被动关闭的一方不是指特指服务器端或者客户端,是相对于谁先发起关闭请求来说的:** 357 | 358 | **6.FIN_WAIT_1:** 359 | 主动关闭的一方,由状态5进入此状态。具体的动作时发送FIN给对方。 360 | 361 | **7.FIN_WAIT_2:** 362 | 主动关闭的一方,接收到对方的FINACK,进入此状态。由此不能再接收对方的数据。但是能够向对方发送数据。 363 | 364 | **8.CLOSE_WAIT:** 365 | 接收到FIN以后,被动关闭的一方进入此状态。具体动作时接收到FIN,同时发送ACK。 366 | 367 | **9.LAST_ACK:** 368 | 被动关闭的一方,发起关闭请求,由状态8进入此状态。具体动作时发送FIN给对方,同时在接收到ACK时进入CLOSED状态。 369 | 370 | **10.CLOSING:** 371 | 两边同时发起关闭请求时,会由FIN_WAIT_1进入此状态。具体动作是,接收到FIN请求,同时响应一个ACK。 372 | 373 | **11.TIME_WAIT:** 374 | 最纠结的状态来了。从状态图上可以看出,有3个状态可以转化成它, 375 | 376 | * a.由FIN_WAIT_2进入此状态:在双方不同时发起FIN的情况下,主动关闭的一方在完成自身发起的关闭请求后,接收到被动关闭一方的FIN后进入的状态。 377 | * b.由CLOSING状态进入:双方同时发起关闭,都做了发起FIN的请求,同时接收到了FIN并做了ACK的情况下,由CLOSING状态进入。 378 | * c.由FIN_WAIT_1状态进入:同时接受到FIN(对方发起),ACK(本身发起的FIN回应),与b的区别在于本身发起的FIN回应的ACK先于对方的FIN请求到达,而b是FIN先到达。这种情况概率最小。 379 | 380 | ### TCP第三次握手失败后怎么办 381 | 382 | 当客户端收到服务端的SYN+ACK应答后,其状态变为ESTABLISHED,并会发送ACK包给服务端,准备发送数据了。如果此时ACK在网络中丢失,过了超时计时器后,那么Server端会重新发送SYN+ACK包,重传次数根据/proc/sys/net/ipv4/tcp_synack_retries来指定,默认是5次。 383 | 384 | 如果重传指定次数到了后,仍然未收到ACK应答,那么一段时间后,Server自动关闭这个连接。**但是Client认为这个连接已经建立**,如果Client端向Server写数据,Server端将以RST包响应,方能感知到Server的错误。 385 | 386 | ### TCP当被告知接收窗口为0后的行为 387 | 388 | 当被告知接收窗口为0后,发送方不能再发送了,它会定期的发出一个探测信号以搞清这个窗口什么时间再次打开。如果发送方从来没有收到ACK(正常的窗口探测你肯定是能得到应答)信息,它就一直不断地重试,直到定时器过期。 389 | 390 | ### 一个 TCP 连接上面能发多少 HTTP请求 391 | 392 | HTTP/1.0中,一个服务器在发送完一个HTTP响应后,会断开TCP链接。但是这样每次请求都会重新建立和断开 393 | TCP 连接,代价过大。 394 | 395 | HTTP/1.1就把 Connection 头写进标准,默认开启持久连接,除非请求中写明 Connection: 396 | close,那么浏览器和服务器之间是会维持一段时间TCP连接,不会一个请求结束就断掉。这样的好处是连接可以被重新使用,之后发送 HTTP 请求的时候不需要重新建立 TCP 连接,以及如果维持连接,那么 SSL 的开销也可以避免。 397 | 398 | HTTP/1.1 399 | 存在一个问题,单个 TCP 400 | 连接在同一时刻只能处理一个请求,意思是说:两个请求的生命周期不能重叠,任意两个 401 | HTTP 请求从开始到结束的时间在同一个 TCP 连接里不能重叠。 402 | 403 | HTTP/1.1 规范中规定了 Pipelining 404 | 来试图解决这个问题,但是这个功能在浏览器中默认是关闭的。 405 | 406 | > Pipelining:一个支持持久连接的客户端可以在一个连接中发送多个请求(不需要等待任意请求的响应),收到请求的服务器必须按照请求收到的顺序发送响应。 407 | 408 | 409 | Pipelining 这种设想看起来比较美好,但是在实践中会出现许多问题。这是因为HTTP/1.1是个文本协议,同时返回的内容也并不能区分对应于哪个发送的请求,所以请求和响应顺序必须维持一致。这样的话在建立起一个TCP连接之后,假设客户端在这个连接连续向服务器发送了几个请求。按照标准,服务器应该按照收到请求的顺序返回结果,假设服务器在处理首个请求时花费了大量时间,那么后面所有的请求都需要等着首个请求结束才能响应。 410 | 411 | **HTTP/1.1 时代,浏览器主要通过以下面两点提高页面加载效率:** 412 | 413 | 1.维持和服务器已经建立的 TCP 连接,在同一连接上顺序处理多个请求。 414 | 415 | 2.和服务器建立多个 TCP 连接(浏览器对同一 Host 建立 TCP 连接有限制)。 416 | 417 | 418 | 419 | 在 HTTP/1.1 时代,那个时候没有多路传输,当浏览器拿到一个有几十张图片的网页该怎么办呢?肯定不能只开一个 TCP 连接顺序下载,那样用户肯定等的很难受。 420 | 但是如果每个图片都开一个 TCP 连接发 HTTP 请求,那电脑或者服务器都可能受不了,要是有 1000 张图片的话总不能开 1000 个TCP 连接吧,你的电脑同意 NAT 也不一定会同意。所以答案是:有,Chrome 最多允许对同一个 Host 建立六个 TCP 连接。不同的浏览器有一些区别。 421 | 422 | **这样的话如果一个 HTML 页面包含几十个图片标签,这些图片是以什么方式、什么顺序、建立了多少连接、使用什么协议被下载下来的呢**? 423 | 424 | * 如果图片都是 HTTPS 连接并且在同一个域名下,那么浏览器在 SSL 握手之后会和服务器商量能不能用 HTTP2,如果能的话就使用 Multiplexing 功能在这个连接上进行多路传输。不过也未必会所有挂在这个域名的资源都会使用一个 TCP 连接去获取,但是可以确定的是 Multiplexing 很可能会被用到。 425 | * 如果发现用不了 HTTP2 呢?或者用不了 HTTPS(现实中的 HTTP2 都是在 HTTPS 上实现的,所以也就是只能使用 HTTP/1.1)。那浏览器就会在一个 HOST 上建立多个 TCP 连接,连接数量的最大限制取决于浏览器设置,这些连接会在空闲的时候被浏览器用来发送新的请求。如果所有的连接都正在发送请求呢?那其他的请求就只能等等了。 426 | 427 | ### TCP长连接保持 428 | 429 | 有三种使用 KeepAlive 的实践方案: 430 | 431 | - 默认情况下使用 KeepAlive 周期为 2 个小时,如不选择更改,属于误用范畴,造成资源浪费:内核会为每一个连接都打开一个保活计时器,N 个连接会打开 N 个保活计时器。 优势很明显: 432 | - TCP 协议层面保活探测机制,系统内核完全替上层应用自动给做好了 433 | - 内核层面计时器相比上层应用,更为高效 434 | - 上层应用只需要处理数据收发、连接异常通知即可 435 | 436 | - 关闭 TCP 的 KeepAlive,完全使用应用层心跳保活机制。由应用掌管心跳,更灵活可控,比如可以在应用级别设置心跳周期,适配私有协议。 437 | - 业务心跳 + TCP KeepAlive 一起使用,互相作为补充,但 TCP 保活探测周期和应用的心跳周期要协调,以互补方可,不能够差距过大,否则将达不到设想的效果。 438 | 439 | 参考链接1:https://juejin.im/entry/5b063ace518825389f5f453b 440 | 参考链接2:https://www.cnkirito.moe/tcp-talk/ 441 | 442 | 443 | TCP与UDP区别 444 | ------------------ 445 | 446 | ![](media/7e8edd74d94c92974d01ddd1fccd3044.png) 447 | 448 | UDP首部8个字节,TCP首部最低20个字节。 449 | 450 | **TCP对应的协议:** 451 | 452 | 1、**FTP**:定义了文件传输协议,使用21端口。常说某某计算机开了FTP服务便是启动了文件传输服务。下载文件,上传主页,都要用到FTP服务。 453 | 454 | **2、Telnet**:它是一种用于远程登陆的端口,用户可以以自己的身份远程连接到计算机上,通过这种端口可以提供一种基于DOS模式下的通信服务。如以前的BBS是-纯字符界面的,支持BBS的服务器将23端口打开,对外提供服务。 455 | 456 | **3、SMTP**:定义了简单邮件传送协议,现在很多邮件服务器都用的是这个协议,用于发送邮件,所用的是25端口。 457 | 458 | **4、POP3**:它是和SMTP对应,POP3用于接收邮件。通常情况下,POP3协议所用的是110端口。也是说,只要你有相应的使用POP3协议的程序,就可以不以Web方式登陆进邮箱界面,直接用邮件程序就可以收到邮件。 459 | 460 | **5、HTTP协议:** 461 | 是从Web服务器传输超文本到本地浏览器的传送协议。 462 | 463 | **UDP对应的协议:** 464 | 465 | 1、**DNS**:用于域名解析服务,将域名地址转换为IP地址。DNS用的是53号端口。 466 | 467 | **2、SNMP**:简单网络管理协议,使用161号端口,是用来管理网络设备的。由于网络设备很多,无连接的服务就体现出其优势。 468 | 469 | **3、TFTP**(Trival File Transfer 470 | Protocal),简单文件传输协议,该协议在熟知端口69上使用UDP服务。 471 | -------------------------------------------------------------------------------- /notes/计算机网络/gh-md-toc.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/gh-md-toc.exe -------------------------------------------------------------------------------- /notes/计算机网络/media/063eafbf58d7323928914758dfe636b4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/063eafbf58d7323928914758dfe636b4.png -------------------------------------------------------------------------------- /notes/计算机网络/media/328c8413e932880c1e7978b79c72cf41.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/328c8413e932880c1e7978b79c72cf41.jpg -------------------------------------------------------------------------------- /notes/计算机网络/media/37790def0829cfd904ce2ebf003c0a44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/37790def0829cfd904ce2ebf003c0a44.png -------------------------------------------------------------------------------- /notes/计算机网络/media/483a269b059d43ecc09825bbd447a0a8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/483a269b059d43ecc09825bbd447a0a8.png -------------------------------------------------------------------------------- /notes/计算机网络/media/4a7176622d55c07324e96b531e1b8f78.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/4a7176622d55c07324e96b531e1b8f78.png -------------------------------------------------------------------------------- /notes/计算机网络/media/511b0157a8f0b6ad5c37d379e35c6410.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/511b0157a8f0b6ad5c37d379e35c6410.png -------------------------------------------------------------------------------- /notes/计算机网络/media/558215a64084e2ed0294d397ec9a40a8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/558215a64084e2ed0294d397ec9a40a8.png -------------------------------------------------------------------------------- /notes/计算机网络/media/611b769803b9c47bf7ce2f6303b8ce4f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/611b769803b9c47bf7ce2f6303b8ce4f.png -------------------------------------------------------------------------------- /notes/计算机网络/media/651c2597c261a59847623aeb681fc045.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/651c2597c261a59847623aeb681fc045.png -------------------------------------------------------------------------------- /notes/计算机网络/media/66ab9464b9680755d298336b633ba6dd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/66ab9464b9680755d298336b633ba6dd.png -------------------------------------------------------------------------------- /notes/计算机网络/media/707486bd9bb00900cae36ebb808bae6b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/707486bd9bb00900cae36ebb808bae6b.png -------------------------------------------------------------------------------- /notes/计算机网络/media/7730838766deeccbcfb552dececdc0df.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/7730838766deeccbcfb552dececdc0df.png -------------------------------------------------------------------------------- /notes/计算机网络/media/7e8edd74d94c92974d01ddd1fccd3044.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/7e8edd74d94c92974d01ddd1fccd3044.png -------------------------------------------------------------------------------- /notes/计算机网络/media/8d0dca7af83c0c07123853bd4e03856e.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/8d0dca7af83c0c07123853bd4e03856e.jpg -------------------------------------------------------------------------------- /notes/计算机网络/media/92e8ffd5026d530e630b6ba90c23236f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/92e8ffd5026d530e630b6ba90c23236f.png -------------------------------------------------------------------------------- /notes/计算机网络/media/954762ad809395ecb6f54e28b24b3ff2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/954762ad809395ecb6f54e28b24b3ff2.png -------------------------------------------------------------------------------- /notes/计算机网络/media/9e9dac1cdbd06c2c5c8ce5fc7d7bc586.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/9e9dac1cdbd06c2c5c8ce5fc7d7bc586.png -------------------------------------------------------------------------------- /notes/计算机网络/media/af7a0dc07fbdc659d1dc87465854e7d3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/af7a0dc07fbdc659d1dc87465854e7d3.png -------------------------------------------------------------------------------- /notes/计算机网络/media/af8028eff4358aa8a1aa927457c5c07b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/af8028eff4358aa8a1aa927457c5c07b.png -------------------------------------------------------------------------------- /notes/计算机网络/media/c6f8db684870294ee2d10b9c1e0e7379.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/c6f8db684870294ee2d10b9c1e0e7379.png -------------------------------------------------------------------------------- /notes/计算机网络/media/d8c9808d010f264e6b251f719138a80a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/d8c9808d010f264e6b251f719138a80a.png -------------------------------------------------------------------------------- /notes/计算机网络/media/dc4f98a2afa7adc89bf1377e18d4e78d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/dc4f98a2afa7adc89bf1377e18d4e78d.png -------------------------------------------------------------------------------- /notes/计算机网络/media/dd5683b920fb4ac02ba9cb22438007e0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/dd5683b920fb4ac02ba9cb22438007e0.png -------------------------------------------------------------------------------- /notes/计算机网络/media/e0788460db77c061553b23598aa03ecc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/e0788460db77c061553b23598aa03ecc.png -------------------------------------------------------------------------------- /notes/计算机网络/media/e294ecec380425a71209c1a7b464a359.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/e294ecec380425a71209c1a7b464a359.png -------------------------------------------------------------------------------- /notes/计算机网络/media/ecb3bf5299da01ef5a996f263d0dc8fe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/ecb3bf5299da01ef5a996f263d0dc8fe.jpg -------------------------------------------------------------------------------- /notes/计算机网络/media/ecefe493cd15f8516606a66f0cc5d599.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LLLRS/RunJava/8e408cabf5cdc090cda0bd9c3a34720291739bd0/notes/计算机网络/media/ecefe493cd15f8516606a66f0cc5d599.png -------------------------------------------------------------------------------- /notes/计算机网络/安全加密与网站攻击.md: -------------------------------------------------------------------------------- 1 | * [安全加密技术](#%E5%AE%89%E5%85%A8%E5%8A%A0%E5%AF%86%E6%8A%80%E6%9C%AF) 2 | * [单项散列加密](#%E5%8D%95%E9%A1%B9%E6%95%A3%E5%88%97%E5%8A%A0%E5%AF%86) 3 | * [对称加密](#%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86) 4 | * [非对称加密](#%E9%9D%9E%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86) 5 | * [网络攻击](#%E7%BD%91%E7%BB%9C%E6%94%BB%E5%87%BB) 6 | * [SYN Flood攻击](#syn-flood%E6%94%BB%E5%87%BB) 7 | * [DDOS攻击](#ddos%E6%94%BB%E5%87%BB) 8 | * [SQL注入](#sql%E6%B3%A8%E5%85%A5) 9 | * [XSS攻击](#xss%E6%94%BB%E5%87%BB) 10 | * [CSRF攻击](#csrf%E6%94%BB%E5%87%BB) 11 | 12 | 13 | 14 | ### 安全加密技术 15 | 16 | **常见的信息加密技术分为三类:单项散列加密、对称加密和非对称加密。** 17 | 18 | #### 单项散列加密 19 | 20 | 单项散列加密是指通过对不同输入长度的信息进行散列计算,得到固定长度的输出,这个散列计算过程是单向的。为了加强单向散列计算的安全性,可以给散列算法加盐,salt相当于加密的密钥,增加破解难度。 21 | 22 | **单向散列函数特点:** 23 | 24 | > 对任意长度的消息散列值是定长的。 25 | 26 | > 散列计算速度快,非常高效。 27 | 28 | > 明文不同,散列加密后的密文一定不同;明文相同,散列加密后密文一定相同。 29 | 30 | > 具备单向性,无法逆推计算。 31 | 32 | > 单向散列的经典算法有 MD5、SHA、RSA-SHA等。 33 | 34 | #### 对称加密 35 | 36 | 对称加密是最快速、最简单的一种加密方式,加密(encryption)与解密(decryption)用的是同样的密钥(secret 37 | key)。对称加密有很多种算法,由于它效率很高,所以被广泛使用在很多加密协议的核心当中。 38 | 39 | 对称加密通常使用的是相对较小的密钥,一般小于256 40 | bit。因为密钥越大,加密越强,但加密与解密的过程越慢。如果你只用1 41 | bit来做这个密钥,那黑客们可以先试着用0来解密,不行的话就再用1解;但如果你的密钥有1 42 | MB大,黑客们可能永远也无法破解,但加密和解密的过程要花费很长的时间。密钥的大小既要照顾到安全性,也要照顾到效率,是一个trade-off。 43 | 44 | 常见的对称加密算法:DES,AES,3DES等。 45 | 46 | #### 非对称加密 47 | 48 | 非对称加密为数据的加密与解密提供了一个非常安全的方法,它使用了一对密钥,公钥(public 49 | key)和私钥(private 50 | key)。私钥只能由一方安全保管,不能外泄,而公钥则可以发给任何请求它的人。非对称加密使用这对密钥中的一个进行加密,而解密则需要另一个密钥(公钥加密,私钥解密;私钥加密,公钥解密)。常见的非对称加密算法加密和签名算法:RSA、DSA等。 51 | 52 | 由于非对称加密算法的运行速度比对称加密算法的速度慢很多,当需要加密大量的数据时,一般采用对称加密算法,提高加解密速度。 53 | 对称加密算法不能实现签名,因此签名只能非对称算法。 54 | 55 | 对称加密加密与解密使用的是同样的密钥,所以速度快,但由于需要将密钥在网络传输,所以安全性不高。非对称加密使用了一对密钥,公钥与私钥,所以安全性高,但加密与解密速度慢。 56 | 根据对称加密和非对称加密的特点,在实际的操作过程中,采用的方式是:采用非对称加密算法管理对称算法的密钥,然后用对称加密算法加密数据,这样就集成了两类加密算法的优点,既实现了加密速度快的优点,又实现了安全方便管理密钥的优点。 57 | 58 | ### 网络攻击 59 | 60 | #### SYN Flood攻击 61 | 62 | **SYNFlood攻击:** 63 | 给服务器发了一个SYN后,就下线了,于是服务器需要默认等63s才会断开连接,这样,攻击者就可以把服务器的syn连接的队列耗尽,让正常的连接请求不能处理。 64 | 65 | Linux下给了一个叫tcp_syncookies的参数来应对这个事:当SYN队列满了后,TCP会通过源地址端口、目标地址端口和时间戳打造出一个特别的Sequence 66 | Number发回去(又叫cookie),如果是攻击者则不会有响应,如果是正常连接,则会把这个 67 | SYN Cookie发回来,然后服务端可以通过cookie建连接(即使不在SYN队列中)。 68 | 69 | 请注意,请**先千万别用tcp_syncookies来处理正常的大负载的连接的情况。** 70 | 因为,synccookies是妥协版的TCP协议,并不严谨。对于正常的请求,所以应该调整三个TCP参数:第一个是tcp_synack_retries,可以用来减少重试次数;第二个是tcp_max_syn_backlog,可以增大SYN连接数;第三个是tcp_abort_on_overflow,处理不过来干脆就直接拒绝连接了。 71 | 72 | #### DDOS攻击 73 | 74 | DDoS攻击是Distributed Denial of 75 | Service的缩写,即不法黑客组织通过控制服务器等资源,发动对包括国家骨干网络、重要网络设施、政企或个人网站在内的互联网上任一目标 76 | 77 | 的攻击,致使目标服务器断网,最终停止提供服务。 78 | 79 | 预防1:**高防服务器。** 80 | 主要是指能独立硬防御 50Gbps以上的服务器,能够帮助网站拒绝服务攻击,定期扫描网络主节点等。 81 | 82 | **预防2**:DDoS清洗会对用户请求数据进行实时监控,及时发现DOS攻击等异常流量,在不影响正常业务开展的情况下清洗掉这些异常流量。 83 | 84 | **预防3:CDN 加速。** 85 | 在现实中,CDN服务将网站访问流量分配到了各个节点中,这样一方面隐藏网站的真实IP,另一方面即使遭遇 DDoS 攻击,也可以将流量分散到各个节点中,防止源站崩溃。 86 | 87 | 88 | #### SQL注入 89 | 90 | 所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。 91 | 92 | **预防1:加密处理**。将用户登录名称、密码等数据加密保存。加密用户输入的数据,然后再将它与数据库中保存的数据比较,这相当于对用户输入的数据进行了“消毒”处理,用户输入的数据不再对数据库有任何特殊的意义,从而也就防止了攻击者注入SQL命令。 93 | 94 | **预防**2:**确保数据库安全**。只给访问数据库的web应用功能所需的最低的权限,撤销不必要的公共许可 95 | 96 | **预防3:输入验证**。检查用户输入的合法性,确信输入的内容只包含合法的数据。数据检查应当在客户端和服务器端都执行之所以要执行服务器端验证,是为了弥补客户端验证机制脆弱的安全性。 97 | 98 | #### XSS攻击 99 | 100 | XSS攻击全称为跨站脚本攻击(Cross Site 101 | Scripting)。它的原理是攻击者向有XSS漏洞的网站中插入恶意的HTML代码,当其它用户浏览该网站时,这段HTML代码会自动执行,从而达到攻击的目的。比如,盗取用户Cookie、破坏页面结构、重定向到其它网站等。 102 | 103 | **XSS的攻击方式分为两种** 104 | 105 | 反射式:点击恶意URL,恶意脚本会执行。 106 | 107 | 持久性XSS攻击:黑客提交含有恶意脚本的请求,保存在被攻击的web站点的数据库中,用户浏览网页的时候,恶意脚本被包含在正常页面中,达到攻击的目的。 108 | 109 | **XSS的防御措施** 110 | 111 | 编码:对用户输入的数据进行HTML Entity编码 112 | 113 | 过滤:移除用户上传的DOM属性,如onerror等,移除用户上传的style节点,script节点,iframe节点等。 114 | 115 | 校正:避免直接对HTML Entity编码,使用DOM Prase转换,校正不配对的DOM标签。 116 | 117 | #### CSRF攻击 118 | 119 | CSRF全称为跨站请求伪造(Cross-site request 120 | forgery),是一种网络攻击方式,也被称为 one-click attack 或者 session riding。 121 | 122 | ![](media/9e9dac1cdbd06c2c5c8ce5fc7d7bc586.png) 123 | 124 | 从上图可以看出,要完成一次CSRF攻击,受害者必须依次完成两个步骤: 125 | 126 | > **登录受信任网站A,并在本地生成Cookie。** 127 | 128 | > **在不登出A的情况下,访问危险网站B。** 129 | 130 | CSRF攻击是源于WEB的隐式身份验证机制。WEB的身份验证机制虽然可以保证一个请求是来自于某个用户的浏览器,但却无法保证该请求是用户批准发送的! 131 | -------------------------------------------------------------------------------- /notes/计算机网络/计算机网络分层模型.md: -------------------------------------------------------------------------------- 1 | * [计算机网络体系结构](#%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E4%BD%93%E7%B3%BB%E7%BB%93%E6%9E%84) 2 | * [ARP](#arp) 3 | * [ICMP 协议](#icmp-%E5%8D%8F%E8%AE%AE) 4 | * [DHCP协议](#dhcp%E5%8D%8F%E8%AE%AE) 5 | * [路由选择协议](#%E8%B7%AF%E7%94%B1%E9%80%89%E6%8B%A9%E5%8D%8F%E8%AE%AE) 6 | * [RIP协议:(routing information protocol)距离向量算法](#rip%E5%8D%8F%E8%AE%AErouting-information-protocol%E8%B7%9D%E7%A6%BB%E5%90%91%E9%87%8F%E7%AE%97%E6%B3%95) 7 | * [OSPF协议:链路状态算法](#ospf%E5%8D%8F%E8%AE%AE%E9%93%BE%E8%B7%AF%E7%8A%B6%E6%80%81%E7%AE%97%E6%B3%95) 8 | * [IP](#ip) 9 | * [ IP地址分类](#ip%E5%9C%B0%E5%9D%80%E5%88%86%E7%B1%BB) 10 | * [IP报文格式](#ip%E6%8A%A5%E6%96%87%E6%A0%BC%E5%BC%8F) 11 | 12 | 13 | ## 计算机网络体系结构 14 | 15 | ![](media/66ab9464b9680755d298336b633ba6dd.png) 16 | 17 | **五层协议** 18 | 19 | **应用层:** 20 | 为特定应用程序提供数据传输服务,例如 HTTP、DNS 等。数据单位为报文。 21 | 22 | **运输层:** 23 | 提供的是进程间的通用数据传输服务。由于应用层协议很多,定义通用的运输层协议就可以支持不断增多的应用层协议。运输层包括两种协议:传输控制协议 24 | TCP,提供面向连接、可靠的数据传输服务,数据单位为报文段;用户数据报协议 25 | UDP,提供无连接、尽最大努力的数据传输服务,数据单位为用户数据报。TCP 26 | 主要提供完整性服务,UDP 主要提供及时性服务。 27 | 28 | **网络层:** 29 | 为主机间提供数据传输服务,而运输层协议是为主机中的进程提供服务。网络层把运输层传递下来的报文段或者用户数据报封装成分组。 30 | 31 | **数据链路层** 32 | :网络层针对的还是主机之间的数据传输服务,而主机之间可以有很多链路,链路层协议就是为同一链路的主机提供服务。数据链路层把网络层传下来的分组封装成帧。 33 | 34 | **物理层** 35 | :考虑的是怎样在传输媒体上传输数据比特流,而不是指具体的传输媒体。物理层的作用是尽可能屏蔽传输媒体和通信手段的差异,使数据链路层感觉不到这些差异。 36 | 37 | **OSI** 38 | 39 | 其中表示层和会话层用途如下: 40 | 41 | **表示层:** 42 | 数据压缩、加密以及数据描述,这使得应用程序不必担心在各台主机中数据内部格式不同的问题。 43 | 44 | 会话层 :建立及管理会话。 45 | 46 | 五层协议没有表示层和会话层,而是将这些功能留给应用程序开发者处理。 47 | 48 | **TCP/IP** 49 | 50 | 只有四层,相当于五层协议中数据链路层和物理层合并为网络接口层。TCP/IP 51 | 体系结构不严格遵循 OSI 分层概念,应用层可能会直接使用 IP 层或者网络接口层。 52 | 53 | ![](media/954762ad809395ecb6f54e28b24b3ff2.png) 54 | 55 | ![](media/dc4f98a2afa7adc89bf1377e18d4e78d.png) 56 | 57 | ### ARP 58 | 59 | [有了IP地址为什么还需要ARP协议](https://blog.csdn.net/u014805066/article/details/79682772) 60 | ARP(地址解析协议,Address Resolution Protocol)是网络层协议。ARP 实现由 IP 61 | 地址得到 MAC 62 | 地址。每个主机都会在自己的ARP缓冲区中建立一个ARP列表,以表示IP地址和MAC地址之间的对应关系。 63 | 64 | 当源主机要发送数据时,首先检查ARP列表中是否有对应IP地址的目的主机的MAC地址,如果有,则直接发送数据,如果没有,就向本网段的所有主机发送ARP数据包,该数据包包括的内容有:源主机IP地址,源主机MAC地址,目的主机的IP地址。 65 | 66 | 当本网络的所有主机收到该ARP数据包时,首先检查数据包中的IP地址是否是自己的IP地址,如果不是,则忽略该数据包,如果是,则首先从数据包中取出源主机的IP和MAC地址写入到ARP列表中,如果已经存在,则覆盖,然后将自己的MAC地址写入ARP响应包中,告诉源主机自己是它想要找的MAC地址。 67 | 68 | 源主机收到ARP响应包后。将目的主机的IP和MAC地址写入ARP列表,并利用此信息发送数据。如果源主机一直没有收到ARP响应数据包,表示ARP查询失败。 69 | 70 | ![](media/063eafbf58d7323928914758dfe636b4.png) 71 | 72 | **RARP协议:** 73 | 逆地址解析协议,作用是完成硬件地址到IP地址的映射,主要用于无盘工作站,因为给无盘工作站配置的IP地址不能保存 74 | 75 | ### ICMP 协议 76 | 77 | ICMP是InternetControl Message Protocol,网际控制报文协。 78 | 79 | ICMP是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由器是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。ICMP报文有两种:差错报告报文和询问报文。 80 | 81 | **ICMP的应用** 82 | 83 | Ping:测试两台主机之间的连通性。 84 | 85 | Traceroute:跟踪一个分组从源点到终点的路径。 86 | 87 | ### DHCP协议 88 | 89 | 动态主机配置协议(Dynamic Host ConfigurationProtocol, 90 | DHCP),是一种让系统得以连接到网络上,并获取所需要的配置参数手段。通常被应用在大型的局域网络环境中,主要作用是集中的管理、分配IP地址,使网络环境中的主机动态的获得IP地址、Gateway地址、DNS服务器地址等信息,并能够提升地址的使用率。 91 | 92 | ### 路由选择协议 93 | 94 | **动态路由算法决定信息经过哪个路由器发送**。 95 | 96 | #### RIP协议:(routing information protocol)距离向量算法 97 | 98 | (1)以跳数作为度量,距离16表示不可达 99 | 100 | (2)使用UDP的520端口发送和接收RIP分组 101 | 102 | (3)RIP每隔30s以广播形式发送一次路由信息,在邻居之间互传,和邻居分享自己全部的信息,为了防止出现广播风暴,其后续的分组将做随机延时后发送。RIP协议规定,只和自己相邻的路由器交换信息,不相邻的路由器不交换信息。交换的信息是:“我到本自治系统中所有网络的最短距离,以及到每个网络应该经过的下一跳路由器” 103 | 104 | (4)如果一个路由在180s内未被更新,相应的距离应设置为无穷大,并从路由表中删除该表项 105 | 106 | (5)RIP分组分为请求分组 和响应分组  107 | 108 | (6)RIP选择一条具有最少路由器的路由,哪怕还存在另外一条高速但路由器较多的路由。 109 | 110 | (7)RIP基于Bellman-Ford 算法 111 | 112 | **过程分析:**在路由器刚启动的时候,启用了RIP的接口会向外广播请求信息,接下来RIP进程进入到一个循环状态:监听来自路由器的请求信息和应答信息。当邻居收到请求信息以后,就发送应答信息给这个发出请求信息的路由器。 113 | 114 | 平均每个30s,启用了RIP的接口会发送应答信息,也就是(updata),这个update包含了路由器完整的信息表。 115 | 116 | 117 | 118 | #### OSPF协议:链路状态算法 119 | 120 | OSPF协议:链路状态算法 121 | 122 | (1)每个结点都有完整的网络拓扑信息 123 | 124 | (2)一个结点检查所有直接链路的状态,并将所得的状态信息发送给网上所有其他的结点,故OSPF是将自己知道的部分信息告诉了所有结点,此处运用洪泛法 125 | 126 | (3)只有当链路状态变化时,路由器才用洪泛法向所有路由器发送信息 127 | 128 | (4)IP分装,协议号89 129 | 130 | (5)支持IPV4和IPV6,支持组播 131 | 132 | (6)死亡时间:40s 133 | 134 | (7)OSPF基于迪杰斯特拉算法 135 | 136 | 注:OSPF克服了RIP的很多缺陷,表现在 137 | 138 | (1)OSPF不再采用跳数的概念,而是根据接口的吞吐率,拥塞情况,往返时间等实际链路的负载能力定出路由的代价,同时选择最短,最优路由并允许保持到达同一目标地址的多条路由,从而平衡网络负荷 139 | 140 | (2)OSPF支持不同服务类型的不同代价。 141 | 142 | (3)OSPF路由器不再交换路由表,而是同步各路由器对于网络状态的认识,即链路状态数据库。 143 | 144 | ## IP 145 | 146 | 147 | IP(Internet 148 | Protocol的简写)地址,即用Internet协议语言表示的地址,用于在网络地址唯一地标识一台计算机。目前的IP版本有IPV4和IPV6。 149 | 150 | **IP协议功能** 151 | 152 | (1)寻址和路由;(根据对方的IP地址,寻找最佳路径传输信息); 153 | 154 | (2)传递服务: 155 | 156 | ①不可靠(IP协议只是尽自己最大努力去传输数据包),可靠性由上层协议提供(TCP协议)。 157 | 158 | ②无连接(事先不建立会话),不维护任何关于后续数据报的信息; 159 | 160 | (3)数据包的分片和重组。 161 | 162 | ### IP地址分类 163 | 164 | A类地址,一个A类地址是由一个字节的网络地址和三个字节的主机地址组成,网络地址的最高位必须是“0” 165 | 166 | B类地址,一个B类地址是由两个字节的网络地址和两个字节的主机地址组成,网络地址的最高位必须是“10” 167 | 168 | C类地址,一个C类地址是由三个字节的网络地址和一个字节的主机地址组成,网络地址的最高位必须是“110” 169 | 170 | D类地址,不区分网络地址和主机地址,D类地址是一种组播地址,D类地址的第一个字节以1110开始。目前D类地址被用在多点广播(Multicast)中。多点广播地址用来一次寻址一组计算机,它标识共享同一协议的一组计算机。 171 | 172 | E类地址,不区分网络地址和主机地址,E类地址是保留地址用于以后使用。E类地址的第一个字节以1111开始。 173 | 174 | ![](media/558215a64084e2ed0294d397ec9a40a8.png) 175 | 176 | **特殊IP地址** 177 | 178 | (1)**0.0.0.0** 179 | :严格说来,0.0.0.0已经不是一个真正意义上的IP地址了。它表示的是这样一个集合:所有不清楚的主机和目的网络。这里的“不清楚”是指在本机的路由表里没有特定条目指明如何到达。对本机来说,它就是一个“收容所”,所有不认识的“三无”人员,一律送进去。如果你在网络设置中设置了缺省网关,那么Windows系统会自动产生一个目的地址为0.0.0.0的缺省路由。 180 | 181 | **(2)广播地址(255.255.255.255):** 182 | 限制广播地址。对本机来说,这个地址指本网段内(同一广播域)的所有主机。如果翻译成人类的语言,应该是这样:“这个房间里的所有人都注意了!”这个地址不能被路由器转发。 183 | 184 | **(3)环回地址(127.0.0.1):** 185 | 本机地址,主要用于测试。用汉语表示,就是“我自己”。在Windows系统中,这个地址有一个别名“Localhost”。寻址这样一个地址,是不能把它发到网络接口的。除非出错,否则在传输介质上永远不应该出现目的地址为“127.0.0.1”的数据包。 186 | 187 | (4)**165.254.x.x**:如果你的主机使用了DHCP功能自动获得一个IP地址,那么当你的DHCP服务器发生故障,或响应时间太长而超出了一个系统规定的时间,Wingdows系统会为你分配这样一个地址。 188 | 189 | (5)**组播地址:** 190 | 组播地址,从224.0.0.0到239.255.255.254都是这样的地址。224.0.0.1特指所有主机, 191 | 224.0.0.2特指所有路由器。这样的地址多用于一些特定的程序以及多媒体程序。如果你的主机开启了IRDP(Internet路由发现协议,使用组播功能)功能,那么你的主机路由表中应该有这样一条路由,每个子网的第一个和最后一个地址无效。 192 | 193 | (6)**私有地址:这**些地址不会被Internet分配,他们再Internet上也不会被路由,虽然它们不能直接和Internet网连接,但通过技术手段仍旧可以和 194 | Internet通讯(NAT技术)。 195 | 196 | A类地址的私有地址是:10.0.0.0\~10.255.255.255 197 | 198 | B类地址的私有地址是:172.16.0.0\~172.31.255.255.255 199 | 200 | C类地址的私有地址是:192.168.0.0\~192.168.255.255 201 | 202 | **子网划分:** 203 | 通过在主机号字段中拿一部分作为子网号,把两级 IP 地址划分为三级 IP 204 | 地址。**IP 地址 ::= {\< 网络号 \>, \< 子网号 \>, \< 主机号 205 | \>}。**要使用子网,必须配置子网掩码。一个 B 类地址的默认子网掩码为 206 | 255.255.0.0,如果 B 类地址的子网占两个比特,那么子网掩码为 11111111 11111111 207 | 11000000 00000000,也就是255.255.192.0。注意,外部网络看不到子网的存在。 208 | 209 | **无分类:** 210 | 无分类编址 CIDR 消除了传统 A 类、B 类和 C 211 | 类地址以及划分子网的概念,使用网络前缀和主机号来对 IP 212 | 地址进行编码,网络前缀的长度可以根据需要变化。IP 地址 ::= {\< 网络前缀号 \>, \< 213 | 主机号 \>}。CIDR的记法上采用在 IP 214 | 地址后面加上网络前缀长度的方法,例如128.14.35.7/20表示前20位为网络前缀。CIDR的地址掩码可以继续称为子网掩码,子网掩码首1长度为网络前缀的长度。一个CIDR地址块中有很多地址,一个 CIDR表示的网络就可以表示原来的很多个网络,并且在路由表中只需要一个路由就可以代替原来的多个路由,减少了路由表项的数量。把这种通过使用网络前缀来减少路由表项的方式称为路由聚合,也称为构成超网。 215 | 216 | ### IP报文格式 217 | 218 | ![https://img-blog.csdn.net/20180423181601583?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2V2ZXJfcGVuZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70](media/7730838766deeccbcfb552dececdc0df.png) 219 | 220 | **版本号:** 221 | 4个bit,用来标识IP版本号。这个4位字段的值设置为二进制的0100表示IPv4,设置为0110表示IPv6。目前使用的IP协议版本号是4。 222 | 223 | **首部长度:** 224 | 4个bit。标识包括选项在内的IP头部字段的长度。 225 | 226 | **服务类型:** 227 | 8个bit。服务类型字段被划分成两个子字段:3bit的优先级字段和4bitTOS字段,最后一位置为0。4bit的TOS分别代表:最小时延,最大吞吐量,最高可靠性和最小花费。4bit中只能将其中一个bit位置1。如果4个bit均为0,则代表一般服务。 228 | 229 | **总长度:** 230 | 16个bit。接收者用IP数据报总长度减去IP报头长度就可以确定数据包数据有效负荷的大小。IP数据报最长可达65535字节。 231 | 232 | **标识:** 233 | 16个bit。唯一的标识主机发送的每一份数据报。接收方根据分片中的标识字段是否相同来判断这些分片是否是同一个数据报的分片,从而进行分片的重组。通常每发送一份报文它的值就会加1。 234 | 235 | **标志:** 236 | 3个bit。用于标识数据报是否分片。第1位没有使用,第2位是不分段(DF)位。当DF位被设置为1时,表示路由器不能对数据包进行分段处理。如果数据包由于不能分段而未能被转发,那么路由器将丢弃该数据包并向源发送ICMP不可达。第3位是分段(MF)位。当路由器对数据包进行分段时,除了最后一个分段的MF位被设置为0外,其他的分段的MF位均设置为1,以便接收者直到收到MF位为0的分片为止。 237 | 238 | **片偏移:** 239 | 13个bit。在接收方进行数据报重组时用来标识分片的顺序。用于指明分段起始点相对于报头起始点的偏移量。由于分段到达时可能错序,所以位偏移字段可以使接收者按照正确的顺序重组数据包。当数据包的长度超过它所要去的那个数据链路的MTU时,路由器要将它分片。数据包中的数据将被分成小片,每一片被封装在独立的数据包中。接收端使用标识符,分段偏移以及标记域的MF位来进行重组。 240 | 241 | **生存时间:** 242 | 8个bit。TTL域防止丢失的数据包在无休止的传播。该域包含一个8位整数,此数由产生数据包的主机设定。TTL值设置了数据报可以经过的最多的路由器数。TTL的初始值由源主机设置(通常为32或64),每经过一个处理它的路由器,TTL值减1。如果一台路由器将TTL减至0,它将丢弃该数据包并发送一个ICMP超时消息给数据包的源地址。 243 | 244 | **协议:** 245 | 8个bit。用来标识是哪个协议向IP传送数据。ICMP为1,IGMP为2,TCP为6,UDP为17,GRE为47,ESP为50。 246 | 247 | **首部检验和:** 248 | 根据IP首部计算的校验和码。 249 | 250 | **源地址:** 251 | IP报文发送端的IP地址 252 | 253 | **目的地址:** 254 | IP报文接收端的IP地址 255 | 256 | **选项:** 257 | 是数据报中的一个可变长的可选信息。选项字段以32bit为界,不足时插入值为0的填充字节。保证IP首部始终是32bit的整数倍。 258 | --------------------------------------------------------------------------------