diff --git a/.gitignore b/.gitignore index 7ee001fcc8..d778da740a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,25 +1,45 @@ -*.DS_Store -*.iml -*.xml -*.txt -*.dio +# Created by .ignore support plugin (hsz.mobi) +### Java template +# Compiled class file +*.class + +# Log file *.log +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +.DS_Store +.idea/JavaEdge.iml +.idea/compiler.xml .idea/inspectionProfiles/ +.idea/misc.xml +.idea/modules.xml +.idea/vcs.xml +.idea/workspace.xml +Python/.DS_Store +大数据/.DS_Store +操作系统/.DS_Store +数据存储/.DS_Store +面试题系列/.DS_Store + .metals/metals.h2.db +.metals/metals.log .vscode/settings.json -Spring/feign.md -TODO/MySQL会丢数据吗?.md -TODO/【阿里最新数据库面试题】MySQL主从一致性.md -TODO/【阿里数据库面试题解】MySQL高可用原理.md -TODO/MySQL执行更新语句时做了什么?.md -TODO/为何阿里不推荐MySQL使用join?.md -TODO/【阿里MySQL面试题】内部临时表.md -TODO/MySQL数据查询太多会OOM吗?.md -TODO/有了InnoDB,Memory存储引擎还有意义吗?.md -TODO/MySQL执行insert会如何加锁?.md -TODO/MySQL的自增id竟然用到头了怎么办?.md -TODO/MySQL全局锁和表锁.md -TODO/MySQL事务是怎么实现隔离的?.md diff --git a/.idea/Java-Interview-Tutorial.iml b/.idea/Java-Interview-Tutorial.iml deleted file mode 100644 index d6ebd48059..0000000000 --- a/.idea/Java-Interview-Tutorial.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index a55e7a179b..0000000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git "a/Docker/Docker\345\205\245\351\227\250\345\256\236\346\210\230(\344\270\211)---Docker\347\232\204\351\225\234\345\203\217\345\222\214\345\256\271\345\231\250.md" "b/Docker/Docker\345\205\245\351\227\250\345\256\236\346\210\230(\344\270\211)---Docker\347\232\204\351\225\234\345\203\217\345\222\214\345\256\271\345\231\250.md" new file mode 100644 index 0000000000..35a20886bc --- /dev/null +++ "b/Docker/Docker\345\205\245\351\227\250\345\256\236\346\210\230(\344\270\211)---Docker\347\232\204\351\225\234\345\203\217\345\222\214\345\256\271\345\231\250.md" @@ -0,0 +1,168 @@ +# 1 Docker架构和底层技术简介 +![Docker Platform](https://upload-images.jianshu.io/upload_images/4685968-8a50f52c8c208765.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![Docker Engine](https://upload-images.jianshu.io/upload_images/4685968-94cbf0ab167019a5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-de2befc2c04d25f8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-f5582bfe970d0c13.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![Docker Architecture](https://upload-images.jianshu.io/upload_images/4685968-468459a0cd76a9f0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![底层技术支持](https://upload-images.jianshu.io/upload_images/4685968-2483bf3e509b4f14.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 2 Docker Image概述 +![](https://upload-images.jianshu.io/upload_images/4685968-70ef1300c4bdfbcb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![dockerimage结构](https://upload-images.jianshu.io/upload_images/4685968-5e3ffdc7ede27900.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +从基本的看起,一个典型的 Linux 文件系统由 bootfs 和 rootfs 两部分组成, +- bootfs(boot file system) 主要包含 bootloader 和 kernel,bootloader 主要用于引导加载 kernel,当 kernel 被加载到内存中后 bootfs 会被 umount 掉 +- rootfs (root file system) 包含的就是典型 Linux 系统中的/dev,/proc,/bin,/etc 等标准目录和文件 +![docker image 中最基础的两层结构](https://upload-images.jianshu.io/upload_images/4685968-fe22863d36925635.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +不同的 linux 发行版(如 ubuntu 和 CentOS ) 在 rootfs 这一层会有所区别,体现发行版本的差异性 +传统的 Linux 加载 bootfs 时会先将 rootfs 设为 read-only,然后在系统自检之后将 rootfs 从 read-only 改为 read-write,然后就可在 rootfs 上进行读写操作了 +但 Docker 在 bootfs 自检完毕之后并不会把 rootfs 的 read-only 改为 read-write,而是利用 union mount(UnionFS 的一种挂载机制)将 image 中的其他的 layer 加载到之前的 read-only 的 rootfs 层之上,每一层 layer 都是 rootfs 的结构,并且是read-only 的。所以,我们是无法修改一个已有镜像里面的 layer 的!只有当我们创建一个容器,也就是将 Docker 镜像进行实例化,系统会分配一层空的 read-write 的 rootfs ,用于保存我们做的修改。一层 layer 所保存的修改是增量式的,就像 git 一样 +![](https://upload-images.jianshu.io/upload_images/4685968-46da122fa66ef4fc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## 2.2 image的获取 +### image的获取-1 +![](https://upload-images.jianshu.io/upload_images/4685968-6bb5b24cad291667.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +### image的获取-2 +![image的获取-2](https://upload-images.jianshu.io/upload_images/4685968-20c81225745462c3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![sudo docker pull ubuntu:16.04](https://upload-images.jianshu.io/upload_images/4685968-1d89f39390415913.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-7f6b1690bce02627.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![官方镜像仓库](https://upload-images.jianshu.io/upload_images/4685968-c7a0f3cf52174d23.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 3 DIY Base Image +![无需再用 sudo 权限](https://upload-images.jianshu.io/upload_images/4685968-24518ba6f7cb3f97.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-81717c25f8bf2f20.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-1b7bde9a7c7822e0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-3e4122555b3c50af.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-3680db12feae63a7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![运行结果](https://upload-images.jianshu.io/upload_images/4685968-8aeed6a6d3115375.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![vim Dockerfile](https://upload-images.jianshu.io/upload_images/4685968-d3d6e05936a5066f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![docker build -t root/hello-world .](https://upload-images.jianshu.io/upload_images/4685968-02b6d659365997ec.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-542b9b43e06f7fdb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![docker run root/hello-world](https://upload-images.jianshu.io/upload_images/4685968-563e8925bbde05e5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 4 初识Container +![什么是 Container](https://upload-images.jianshu.io/upload_images/4685968-62a26b73ac9a7279.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-43077ea9a69b27f2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +docker rm 默认就是移除 container +docker images = docker image ls +![](https://upload-images.jianshu.io/upload_images/4685968-5141e2b3adb62b25.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- docker rmi = docker image rm +![](https://upload-images.jianshu.io/upload_images/4685968-bad602072d2e7470.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![docker ps -a](https://upload-images.jianshu.io/upload_images/4685968-4cfa4be425ec20eb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![只显示 id](https://upload-images.jianshu.io/upload_images/4685968-c51873bbae28658e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![删除全部](https://upload-images.jianshu.io/upload_images/4685968-ef425e27274a4b7b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 5 构建自己的Docker镜像 +- docker container commit +从容器创建一个新的镜像 +``` +-a :提交的镜像作者; + +-c :使用Dockerfile指令来创建镜像; + +-m :提交时的说明文字; + +-p :在commit时,将容器暂停。 +``` +![](https://upload-images.jianshu.io/upload_images/4685968-1132c54fcc2051f9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- docker image build +使用 Dockerfile 创建镜像 +``` +使用当前目录的 Dockerfile 创建镜像,标签为 runoob/ubuntu:v1。 + +docker build -t runoob/ubuntu:v1 . +使用URL github.com/creack/docker-firefox 的 Dockerfile 创建镜像。 + +docker build github.com/creack/docker-firefox +也可以通过 -f Dockerfile 文件的位置: + +$ docker build -f /path/to/a/Dockerfile . +在 Docker 守护进程执行 Dockerfile 中的指令前,首先会对 Dockerfile 进行语法检查,有语法错误时会返回: + +$ docker build -t test/myapp . +Sending build context to Docker daemon 2.048 kB +Error response from daemon: Unknown instruction: RUNCMD +``` +![](https://upload-images.jianshu.io/upload_images/4685968-22c686d535b6da64.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- docker run +创建一个新的容器并运行一个命令 +``` +-a stdin: 指定标准输入输出内容类型,可选 STDIN/STDOUT/STDERR 三项; + +-d: 后台运行容器,并返回容器ID; + +-i: 以交互模式运行容器,通常与 -t 同时使用; + +-p: 端口映射,格式为:主机(宿主)端口:容器端口 + +-t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用; + +--name="nginx-lb": 为容器指定一个名称; + +--dns 8.8.8.8: 指定容器使用的DNS服务器,默认和宿主一致; + +--dns-search example.com: 指定容器DNS搜索域名,默认和宿主一致; + +-h "mars": 指定容器的hostname; + +-e username="ritchie": 设置环境变量; + +--env-file=[]: 从指定文件读入环境变量; + +--cpuset="0-2" or --cpuset="0,1,2": 绑定容器到指定CPU运行; + +-m :设置容器使用内存最大值; + +--net="bridge": 指定容器的网络连接类型,支持 bridge/host/none/container: 四种类型; + +--link=[]: 添加链接到另一个容器; + +--expose=[]: 开放一个端口或一组端口; +``` + +docker run -it ubuntu +![](https://upload-images.jianshu.io/upload_images/4685968-c607a53211cb5c8a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +新创建的container +![](https://upload-images.jianshu.io/upload_images/4685968-6faa797109b122b9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![docker commit wizardly_feynman root/ubuntu-vim](https://upload-images.jianshu.io/upload_images/4685968-5ac081dde9692622.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-c6c154f9ce89581e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![删除刚才创建的 docker](https://upload-images.jianshu.io/upload_images/4685968-a9ede667b30d5869.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +以上新建方式不推荐,下面看 +## 5.2 dockerfile 创建方法 +![](https://upload-images.jianshu.io/upload_images/4685968-2a0527b19d94ece5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-6ea1ff0840293da7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +# 6 Dockerfile语法梳理及最佳实践 +## 6.1 FROM +![](https://upload-images.jianshu.io/upload_images/4685968-351563c28498fae3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-63e54b4e97510154.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +## 6.2 LABEL +就如代码中的注释 +![](https://upload-images.jianshu.io/upload_images/4685968-71c14853d754b1df.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-4096084d19b9dd6f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +6.3 RUN +![](https://upload-images.jianshu.io/upload_images/4685968-a7fc82e0c33b03b7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-ecc90a7b741e4412.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +## 6.4 WORKDIR +![](https://upload-images.jianshu.io/upload_images/4685968-6441dd56a3956296.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-4d29341e8b3e9323.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +## ADD and COPY +![](https://upload-images.jianshu.io/upload_images/4685968-71d2a8195bfe6ab3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-98875a7091f3738a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + +## ENV +![](https://upload-images.jianshu.io/upload_images/4685968-36cdabfbabb60071.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-4fb9e6d5eacf5a20.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +## VOLUME and EXPOSE +![](https://upload-images.jianshu.io/upload_images/4685968-453c6e29e2cb3b23.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +## CMD and ENTRYPOINT +![](https://upload-images.jianshu.io/upload_images/4685968-07127360d1b00697.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +# 7 +![](https://upload-images.jianshu.io/upload_images/4685968-366e8c17aca38450.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +## Shell 和 Exec 格式 +![](https://upload-images.jianshu.io/upload_images/4685968-3d1fbaa8fbe03d23.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-a771d49d749e1fc5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) diff --git "a/\344\272\221\345\216\237\347\224\237/Docker/Docker\345\234\250\345\220\204\347\247\215\346\223\215\344\275\234\347\263\273\347\273\237\347\216\257\345\242\203\347\232\204\344\270\213\350\275\275\343\200\201\345\256\211\350\243\205\351\205\215\347\275\256\346\226\271\346\263\225.md" "b/Docker/Docker\345\234\250\345\220\204\347\247\215\346\223\215\344\275\234\347\263\273\347\273\237\347\216\257\345\242\203\347\232\204\344\270\213\350\275\275\343\200\201\345\256\211\350\243\205\351\205\215\347\275\256\346\226\271\346\263\225.md" similarity index 100% rename from "\344\272\221\345\216\237\347\224\237/Docker/Docker\345\234\250\345\220\204\347\247\215\346\223\215\344\275\234\347\263\273\347\273\237\347\216\257\345\242\203\347\232\204\344\270\213\350\275\275\343\200\201\345\256\211\350\243\205\351\205\215\347\275\256\346\226\271\346\263\225.md" rename to "Docker/Docker\345\234\250\345\220\204\347\247\215\346\223\215\344\275\234\347\263\273\347\273\237\347\216\257\345\242\203\347\232\204\344\270\213\350\275\275\343\200\201\345\256\211\350\243\205\351\205\215\347\275\256\346\226\271\346\263\225.md" diff --git "a/\344\272\221\345\216\237\347\224\237/Docker/Docker\345\237\272\347\241\200-Docker Container\350\257\246\350\247\243.md" "b/Docker/Docker\345\237\272\347\241\200-Docker Container\350\257\246\350\247\243.md" similarity index 86% rename from "\344\272\221\345\216\237\347\224\237/Docker/Docker\345\237\272\347\241\200-Docker Container\350\257\246\350\247\243.md" rename to "Docker/Docker\345\237\272\347\241\200-Docker Container\350\257\246\350\247\243.md" index b280094b97..620dbb0285 100644 --- "a/\344\272\221\345\216\237\347\224\237/Docker/Docker\345\237\272\347\241\200-Docker Container\350\257\246\350\247\243.md" +++ "b/Docker/Docker\345\237\272\347\241\200-Docker Container\350\257\246\350\247\243.md" @@ -6,7 +6,7 @@ image 相当于一个类,container 就是每个实例 - Image负责app的存储和分发,Container负责运行app -![](https://img-blog.csdnimg.cn/20201214232523826.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201214232523826.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) Dockerfile ```shel @@ -55,7 +55,7 @@ docker container ls -a - 现在退出刚才运行的 centos ![](https://img-blog.csdnimg.cn/20201216131852679.png) - 再次查看容器状态![](https://img-blog.csdnimg.cn/20201216131954946.png) -- 可还发现有很多重复的退出的容器 centos,怎么删除重复的呢![](https://img-blog.csdnimg.cn/20201216132056330.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +- 可还发现有很多重复的退出的容器 centos,怎么删除重复的呢![](https://img-blog.csdnimg.cn/20201216132056330.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) - 经过一顿`docker container rm`即可 ![](https://img-blog.csdnimg.cn/20201216132844403.png) # 3 构建自定义的 Docker image @@ -70,11 +70,11 @@ yum install -y yum 安装完成后,退出该 image ![](https://img-blog.csdnimg.cn/20201216155042755.png) - 提交刚才安装过 vim 的新的image -![](https://img-blog.csdnimg.cn/20201216160157694.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201216160157694.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) -- 可见的确大点![](https://img-blog.csdnimg.cn/20201216160354762.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +- 可见的确大点![](https://img-blog.csdnimg.cn/20201216160354762.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) - 我们直接见证 image 的变迁历史即可 -![](https://img-blog.csdnimg.cn/20201216160941763.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201216160941763.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) 但不推荐这种创建新 image 的方式,因为别人不知道这个 image 到底经历了啥,即是否安全呢? @@ -92,7 +92,7 @@ RUN yum install -y vim docker build -t javaedge/centos-vim-new . ``` -![](https://img-blog.csdnimg.cn/20201216161831492.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201216161831492.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) 从日志可见中间有个临时中转生成的 image,最后完成时被删除。 ```bash [+] Building 20.9s (6/6) FINISHED diff --git "a/\344\272\221\345\216\237\347\224\237/Docker/Docker\345\237\272\347\241\200\346\225\231\347\250\213(\344\272\214)-Dockerfile\345\221\275\344\273\244\350\257\246\350\247\243\345\217\212\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/Docker/Docker\345\237\272\347\241\200\346\225\231\347\250\213(\344\272\214)-Dockerfile\345\221\275\344\273\244\350\257\246\350\247\243\345\217\212\346\234\200\344\275\263\345\256\236\350\267\265.md" similarity index 85% rename from "\344\272\221\345\216\237\347\224\237/Docker/Docker\345\237\272\347\241\200\346\225\231\347\250\213(\344\272\214)-Dockerfile\345\221\275\344\273\244\350\257\246\350\247\243\345\217\212\346\234\200\344\275\263\345\256\236\350\267\265.md" rename to "Docker/Docker\345\237\272\347\241\200\346\225\231\347\250\213(\344\272\214)-Dockerfile\345\221\275\344\273\244\350\257\246\350\247\243\345\217\212\346\234\200\344\275\263\345\256\236\350\267\265.md" index 37c649d26e..2bf93cd00c 100644 --- "a/\344\272\221\345\216\237\347\224\237/Docker/Docker\345\237\272\347\241\200\346\225\231\347\250\213(\344\272\214)-Dockerfile\345\221\275\344\273\244\350\257\246\350\247\243\345\217\212\346\234\200\344\275\263\345\256\236\350\267\265.md" +++ "b/Docker/Docker\345\237\272\347\241\200\346\225\231\347\250\213(\344\272\214)-Dockerfile\345\221\275\344\273\244\350\257\246\350\247\243\345\217\212\346\234\200\344\275\263\345\256\236\350\267\265.md" @@ -41,14 +41,14 @@ ENTRYPOINT echo "hello docker" ```bash docker build -t javaedge/centos-shell . ``` -![](https://img-blog.csdnimg.cn/20201216173036468.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201216173036468.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) ```bash docker image ls docker run javaedge/centos-shell ``` -![](https://img-blog.csdnimg.cn/20201216173058724.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201216173058724.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) ```shell @@ -65,10 +65,10 @@ ENTRYPOINT [ "/bin/echo" , "hello docker" ] - Dockerfile2 ![](https://img-blog.csdnimg.cn/20201216173827345.png) -![](https://img-blog.csdnimg.cn/20201216174116135.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201216174116135.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) - 那如何修改才能让 exec 格式的命令能被 shell 识别呢,修正: -![](https://img-blog.csdnimg.cn/20201216174518222.png)![](https://img-blog.csdnimg.cn/20201216174538116.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201216174518222.png)![](https://img-blog.csdnimg.cn/20201216174538116.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) ```exec RUN ["可执行文件", "参数1", "参数2"] # 例如: @@ -188,10 +188,10 @@ RUN apt-get install -y mysql-server= "$(MYSQL_VERSION]" \ # 实战 - 项目源码 -![](https://img-blog.csdnimg.cn/20201216204315195.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) -- python app.py![](https://img-blog.csdnimg.cn/20201216204352396.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201216204315195.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) +- python app.py![](https://img-blog.csdnimg.cn/20201216204352396.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) ![](https://img-blog.csdnimg.cn/20201216204419843.png) -![](https://img-blog.csdnimg.cn/20201216204954213.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201216204954213.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) ## 如何调试Dockerfile? - 比如执行到如下步骤时报错 ![](https://img-blog.csdnimg.cn/20201216211834630.png) @@ -201,9 +201,9 @@ RUN apt-get install -y mysql-server= "$(MYSQL_VERSION]" \ docker run -it 4320f8b526bc /bin/bash ``` 进入后,直接查看 app,原来是个文件,并非路径!检查下 Dockerfile -![](https://img-blog.csdnimg.cn/20201216212243976.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201216212243976.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) 注意那行意思是将 app.py 放到根目录下并命名为 app,所以它不是个目录。 -![](https://img-blog.csdnimg.cn/20201216212451654.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201216212451654.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) 之后再 build,run 即可。 diff --git "a/\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\270\200)-\346\240\207\345\207\206\345\214\226\346\211\223\345\214\205\346\212\200\346\234\257.md" "b/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\270\200) - Server\347\253\257\346\212\200\346\234\257\345\260\201\347\245\236\346\246\234.md" similarity index 66% rename from "\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\270\200)-\346\240\207\345\207\206\345\214\226\346\211\223\345\214\205\346\212\200\346\234\257.md" rename to "Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\270\200) - Server\347\253\257\346\212\200\346\234\257\345\260\201\347\245\236\346\246\234.md" index 3083f320c0..858145fe3c 100644 --- "a/\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\270\200)-\346\240\207\345\207\206\345\214\226\346\211\223\345\214\205\346\212\200\346\234\257.md" +++ "b/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\270\200) - Server\347\253\257\346\212\200\346\234\257\345\260\201\347\245\236\346\246\234.md" @@ -1,89 +1,141 @@ -# 简介 +# 容器!容器! + +回溯历史源头 + 相比于盛极一时的 + - AWS -![](https://img-blog.csdnimg.cn/20190829000005552.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70) +![](https://ask.qcloudimg.com/http-save/1752328/hqmw47dtn3.png) - OpenStack -![](https://img-blog.csdnimg.cn/20190829000158193.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) - +![](https://ask.qcloudimg.com/http-save/1752328/0jacpe1bhj.png) - 以Cloud Foundry为代表的PaaS项目,却成了当时云计算技术中的一股清流 -![](https://img-blog.csdnimg.cn/20190829000421341.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70) +![](https://ask.qcloudimg.com/http-save/1752328/kb7sdiwxfu.png) Cloud Foundry项目已经基本度过了最艰难的概念普及和用户教育阶段,开启了以开源PaaS为核心构建平台层服务能力的变革 只是,后来一个叫 **`Docker`** 的开源项目横空出世 + 当时还名叫**dotCloud**的**Docker**公司,也是PaaS热潮中的一员 -![](https://img-blog.csdnimg.cn/20190829000752958.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)相比于Heroku、Pivotal、Red Hat等PaaS新宠,**dotCloud**微不足道,主打产品跟主流的Cloud Foundry社区脱节,门可罗雀! + +![](https://ask.qcloudimg.com/http-save/1752328/6cx8ht2oln.png)相比于Heroku、Pivotal、Red Hat等PaaS新宠,**dotCloud**微不足道,主打产品跟主流的Cloud Foundry社区脱节,门可罗雀! + **dotCloud**公司突然决定:开源自己的容器项目 **`Docker`**!!! 显然,这个决定在当时根本没人在乎。 + “容器”这个概念从来就不是什么新鲜的东西,也不是Docker公司发明的。 + 即使在当时最热门的PaaS项目Cloud Foundry中,容器也只是其最底层、最没人关注的那一部分。 **PaaS项目被大家接纳的一个主要原因是它提供“应用托管”能力** + 那时主流用户的普遍用法,就是租一批AWS或者OpenStack的虚拟机,然后像以前管理物理服务器那样,用脚本或者手工的方式在这些机器上部署应用。 + 当然,部署过程难免碰到云端虚拟机和本地环境不一致问题,当时的云计算服务,比的就是谁能更好模拟本地服务器环境,带来更好“上云”体验。 + 而PaaS开源项目的出现,就是当时解决这个问题的一个最佳方案。 +虚拟机创建后,运维只需在这些机器上部署一个Cloud Foundry项目 + +然后开发者只要执行一条命令就能把本地的应用部署到云上 -虚拟机创建后,运维只需在这些机器上部署一个Cloud Foundry项目,然后开发者只要执行一条命令就能把本地的应用部署到云上: -```bash +``` cf push "My APP" ``` -**`Cloud Foundry这样的PaaS项目,最核心的组件就是应用的打包和分发机制`**。 + +**`Cloud Foundry这样的PaaS项目,最核心的组件就是应用的打包和分发机制`** + Cloud Foundry为每种主流编程语言都定义了一种打包格式,“cf push”等同于用户把应用的可执行文件和启动脚本打进一个压缩包内,上传到云上Cloud Foundry的存储中。 -接着,Cloud Foundry会通过调度器选择一个可以运行这个应用的虚拟机,然后通知这个机器上的Agent把应用压缩包下载下来启动。 +接着,Cloud Foundry会通过调度器选择一个可以运行这个应用的虚拟机 + +然后通知这个机器上的Agent把应用压缩包下载下来启动。 由于需要在一个虚拟机上启动很多个来自不同用户的应用,Cloud Foundry会调用操作系统的Cgroups和Namespace机制为每一个应用单独创建一个称作“沙盒”的隔离环境,然后在“沙盒”中启动这些应用进程 + 这样就实现了把多个用户的应用互不干涉地在虚拟机里批量地、自动地运行起来的目的。 **`这就是PaaS项目最核心的能力`** + 这些Cloud Foundry用来运行应用的隔离环境,或者说“沙盒”,就是所谓的“容器”。 而Docker项目,实际上跟Cloud Foundry的容器并没有太大不同,所以在它发布后不久,Cloud Foundry的首席产品经理James Bayer就在社区里做了一次详细对比,告诉用户Docker实际上只是一个同样使用Cgroups和Namespace实现的“沙盒”而已,没有什么特别的黑科技,也不需要特别关注。 -Docker项目迅速崛起。如此之快,以至于Cloud Foundry以及所有的PaaS社区还没来得及成为它的竞争对手,就直接出局。 +Docker项目迅速崛起。如此之快,以至于Cloud Foundry以及所有的PaaS社区还没来得及成为它的竞争对手,就直接出局 + Docker项目确实与Cloud Foundry的容器在大部分功能和实现原理上都是一样的,可偏偏就是这剩下的一小部分不一样的功能,成了Docker项目接下来“呼风唤雨”的不二法宝。 # Docker镜像 -PaaS之所以能帮助用户大规模部署应用到集群里,是因为提供了一套**应用打包**的功能。打包功能恰好成了PaaS日后不断遭到用户诟病的一个“软肋”。 +恐怕连Docker项目作者都没想到,这个小小的创新,在短短几年内就如此迅速地改变了整个云计算领域的发展历程。 + +PaaS之所以能够帮助用户大规模部署应用到集群里,是因为提供了一套应用打包的功能。偏偏打包功能,成了PaaS日后不断遭到用户诟病的一个“软肋”。 + 一旦用上了PaaS,用户就必须为每种语言、每种框架,甚至每个版本的应用维护一个打好的包。 + 这个打包过程,没有任何章法可循,更麻烦的是,明明在本地运行得好好的应用,却需要做很多修改和配置工作才能在PaaS里运行起来。而这些修改和配置,并没有什么经验可以借鉴,基本上得靠不断试错,直到你摸清楚了本地应用和远端PaaS匹配的“脾气”才能够搞定。 最后结局就是,“cf push”确实是能一键部署了,但是为了实现这个一键部署,用户为每个应用打包的工作可谓一波三折,费尽心机。 -**`Docker镜像解决的,恰恰就是打包这个根本性问题`** -Docker镜像,其实就是一个压缩包。但这个压缩包内容比PaaS的应用可执行文件+启停脚本的组合就要丰富多了。 -大多数Docker镜像是直接由一个完整操作系统的所有文件和目录构成,所以这个压缩包里的内容跟你本地开发和测试环境用的os一样。 +**`Docker镜像解决的,恰恰就是打包这个根本性的问题`** + +Docker镜像,其实就是一个压缩包。但是这个压缩包里的内容,比PaaS的应用可执行文件+启停脚本的组合就要丰富多了 + +大多数Docker镜像是直接由一个完整操作系统的所有文件和目录构成的,所以这个压缩包里的内容跟你本地开发和测试环境用的操作系统是一样的。 假设你的应用在本地运行时,能看见的环境是CentOS 7.2操作系统的所有文件和目录,那么只要用CentOS 7.2的ISO做一个压缩包,再把你的应用可执行文件也压缩进去,那么无论在哪里解压这个压缩包,都可以得到与你本地测试时一样的环境。当然,你的应用也在里面!!! 这就是Docker镜像最厉害的地方:只要有这个压缩包在手,你就可以使用某种技术创建一个“沙盒”,在“沙盒”中解压这个压缩包,然后就可以运行你的程序了。 -更重要的是,这个压缩包包含了完整的os文件和目录,也就是包含了这个应用运行所需要的所有依赖,所以你可以先用这个压缩包在本地进行开发和测试,完成之后,再把这个压缩包上传到云端运行。在这个过程中,你完全不需要进行任何配置或者修改,因为这个压缩包赋予了你一种极其宝贵的能力:**本地环境和云端环境的一致**! -**`这就是Docker镜像的精髓。`** +更重要的是,这个压缩包包含了完整的操作系统文件和目录,也就是包含了这个应用运行所需要的所有依赖,所以你可以先用这个压缩包在本地进行开发和测试,完成之后,再把这个压缩包上传到云端运行。在这个过程中,你完全不需要进行任何配置或者修改,因为这个压缩包赋予了你一种极其宝贵的能力:本地环境和云端环境的一致! + +**`这是Docker镜像的精髓。`** 有了Docker镜像,PaaS里最核心的打包系统一下子就没了用武之地,最让用户抓狂的打包过程也随之消失了。 + 相比之下,在当今的互联网里,Docker镜像需要的操作系统文件和目录,可谓唾手可得。 + 所以,你只需要提供一个下载好的操作系统文件与目录,然后使用它制作一个压缩包即可,这个命令就是: -```bash + +``` docker build "我的镜像" ``` + 一旦镜像制作完成,用户就可以让Docker创建一个“沙盒”来解压这个镜像,然后在“沙盒”中运行自己的应用,这个命令就是: -```bash -$ docker run "我的镜像" + +``` +docker run "我的镜像" ``` -当然,docker run创建的“沙盒”,也是使用Cgroups和Namespace机制创建出来的隔离环境。 + +当然,docker run创建的“沙盒”,也是使用Cgroups和Namespace机制创建出来的隔离环境 Docker提供了一种非常便利的打包机制。直接打包了应用运行所需要的整个操作系统,保证了本地环境和云端环境的高度一致,避免了用户通过“试错”来匹配两种不同运行环境之间差异的痛苦过程。 不过,Docker项目固然解决了应用打包的难题,但正如前面所介绍的那样,它并不能代替PaaS完成大规模部署应用的职责。 + 遗憾的是,考虑到Docker公司是一个与自己有潜在竞争关系的商业实体,再加上对Docker项目普及程度的错误判断,Cloud Foundry项目并没有第一时间使用Docker作为自己的核心依赖,去替换自己那套饱受诟病的打包流程。 + 反倒是一些机敏的创业公司,纷纷在第一时间推出了Docker容器集群管理的开源项目(比如Deis和Flynn),它们一般称自己为CaaS,即Container-as-a-Service,用来跟“过时”的PaaS们划清界限。 而在2014年底的DockerCon上,Docker公司雄心勃勃地对外发布了自家研发的“Docker原生”容器集群管理项目Swarm,不仅将这波“CaaS”热推向了一个前所未有的高潮,更是寄托了整个Docker公司重新定义PaaS的宏伟愿望。 -参考 +在2014年的这段巅峰岁月里,Docker公司离自己的理想真的只有一步之遥。 + +# 总结 + +2013~2014年,以Cloud Foundry为代表的PaaS项目,逐渐完成了教育用户和开拓市场的艰巨任务,也正是在这个将概念逐渐落地的过程中,应用“打包”困难这个问题,成了整个后端技术圈子的一块心病。 + +Docker项目的出现,则为这个根本性的问题提供了一个近乎完美的解决方案。这正是Docker项目刚刚开源不久,就能够带领一家原本默默无闻的PaaS创业公司脱颖而出,然后迅速占领了所有云计算领域头条的技术原因。 + +而在成为了基础设施领域近十年难得一见的技术明星之后,dotCloud公司则在2013年底大胆改名为Docker公司。不过,这个在当时就颇具争议的改名举动,也成为了日后容器技术圈风云变幻的一个关键伏笔。 + +# 参考 + - docker官网 - Docker实战 -- 深入剖析Kubernetes \ No newline at end of file +- 深入剖析Kubernetes + +# X 交流学习 +![](https://img-blog.csdnimg.cn/20190504005601174.jpg) +## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T) +## [博客](https://blog.csdn.net/qq_33589510) +## [Github](https://github.com/Wasabi1234) \ No newline at end of file diff --git "a/\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\270\203) - \345\256\271\345\231\250\344\270\255\350\277\233\347\250\213\350\247\206\351\207\216\344\270\213\347\232\204\346\226\207\344\273\266\347\263\273\347\273\237.md" "b/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\270\203) - \345\256\271\345\231\250\344\270\255\350\277\233\347\250\213\350\247\206\351\207\216\344\270\213\347\232\204\346\226\207\344\273\266\347\263\273\347\273\237.md" similarity index 100% rename from "\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\270\203) - \345\256\271\345\231\250\344\270\255\350\277\233\347\250\213\350\247\206\351\207\216\344\270\213\347\232\204\346\226\207\344\273\266\347\263\273\347\273\237.md" rename to "Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\270\203) - \345\256\271\345\231\250\344\270\255\350\277\233\347\250\213\350\247\206\351\207\216\344\270\213\347\232\204\346\226\207\344\273\266\347\263\273\347\273\237.md" diff --git "a/\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\270\211) - Docker\347\232\204\350\207\252\346\210\221\351\207\215\346\226\260\345\256\232\344\275\215.md" "b/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\270\211) - Docker\347\232\204\350\207\252\346\210\221\351\207\215\346\226\260\345\256\232\344\275\215.md" similarity index 100% rename from "\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\270\211) - Docker\347\232\204\350\207\252\346\210\221\351\207\215\346\226\260\345\256\232\344\275\215.md" rename to "Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\270\211) - Docker\347\232\204\350\207\252\346\210\221\351\207\215\346\226\260\345\256\232\344\275\215.md" diff --git "a/\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\272\214) -\"\351\262\270\351\261\274\"\345\205\254\345\217\270\350\257\236\347\224\237.md" "b/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\272\214) -\"\351\262\270\351\261\274\"\345\205\254\345\217\270\350\257\236\347\224\237.md" similarity index 62% rename from "\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\272\214) -\"\351\262\270\351\261\274\"\345\205\254\345\217\270\350\257\236\347\224\237.md" rename to "Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\272\214) -\"\351\262\270\351\261\274\"\345\205\254\345\217\270\350\257\236\347\224\237.md" index 8eeb22f240..09503ac543 100644 --- "a/\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\272\214) -\"\351\262\270\351\261\274\"\345\205\254\345\217\270\350\257\236\347\224\237.md" +++ "b/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\272\214) -\"\351\262\270\351\261\274\"\345\205\254\345\217\270\350\257\236\347\224\237.md" @@ -2,50 +2,7 @@ 一天天的,PaaS深入人心,Cloud Foundry为首的传统PaaS,开始蓄力基础设施领域的 -**平台化**和**PaaS化**,于是发现了PaaS中的问题。 - -# 0 虚拟化 - -![](https://img-blog.csdnimg.cn/20210130181042995.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70) -- 一个物理机可以部署多个app -- 每个app独立运行在一个VM里 - -## 虚拟化的优点 -- 资源池 -一个物理机的资源分配到不同的虚拟机里 -- 很容易扩展 -加物理机器or加虚拟机 -- 很容易云化 -亚马逊AWS、阿里云 - - -## 虚拟化的局限性 -每一个虚拟机都是一个完整的操作系统 ,要给其分配资源,当虚拟机数量增多时,操作系统本身消耗的资源势必增多 - -- 开发和运维的挑战 -![](https://img-blog.csdnimg.cn/20210130181359864.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70) -## 容器解决了什么问题 -![](https://img-blog.csdnimg.cn/20210130181428699.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70) - -- 解决了开发和运维之间的矛盾 -- 在开发和运维之间搭建了一个桥梁,是实现devops的最佳解决方案 - -## 什么是容器 -![](https://img-blog.csdnimg.cn/2021013018164982.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70) -- 对软件和其依赖的标准化打包 -- 应用之间相互隔离 -- 共享同一个OS Kernel -- 可以运行在很多主流操作系统上 - -- 容器 V.S 虚拟机 -![=](https://img-blog.csdnimg.cn/20210130181622725.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70) - - -- 虚拟化+容器 -![](https://img-blog.csdnimg.cn/20210130181605762.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70) - -- Docker,容器技术实现的一种 -![](https://img-blog.csdnimg.cn/20210130181532518.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70) +**平台化**和**PaaS化**,于是发现了PaaS中的问题 # 1 如何给应用打包 diff --git "a/\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\272\224) - \347\211\271\346\256\212\347\232\204\350\277\233\347\250\213!.md" "b/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\272\224) - \347\211\271\346\256\212\347\232\204\350\277\233\347\250\213!.md" similarity index 98% rename from "\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\272\224) - \347\211\271\346\256\212\347\232\204\350\277\233\347\250\213!.md" rename to "Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\272\224) - \347\211\271\346\256\212\347\232\204\350\277\233\347\250\213!.md" index 1b5e026703..5ff0a3e778 100644 --- "a/\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\272\224) - \347\211\271\346\256\212\347\232\204\350\277\233\347\250\213!.md" +++ "b/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\344\272\224) - \347\211\271\346\256\212\347\232\204\350\277\233\347\250\213!.md" @@ -108,7 +108,7 @@ int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL); 谈到为“进程划分一个独立空间”的思想,相信你一定会联想到虚拟机 你应该还看过一张虚拟机和容器的对比图。 -![](https://img-blog.csdnimg.cn/20190906230851620.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20190906230851620.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) 左边虚拟机的工作原理 名为Hypervisor的软件是虚拟机最主要的部分,它通过硬件虚拟化功能,模拟出了运行一个操作系统需要的各种硬件,比如CPU、内存、I/O设备等等 然后,它在这些虚拟的硬件上安装了一个新的操作系统,即Guest OS。 diff --git "a/\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\345\205\253)-\350\260\210\350\260\210 Kubernetes \347\232\204\346\234\254\350\264\250.md" "b/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\345\205\253)-\350\260\210\350\260\210 Kubernetes \347\232\204\346\234\254\350\264\250.md" similarity index 92% rename from "\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\345\205\253)-\350\260\210\350\260\210 Kubernetes \347\232\204\346\234\254\350\264\250.md" rename to "Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\345\205\253)-\350\260\210\350\260\210 Kubernetes \347\232\204\346\234\254\350\264\250.md" index a83cc67093..75ec9a88a9 100644 --- "a/\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\345\205\253)-\350\260\210\350\260\210 Kubernetes \347\232\204\346\234\254\350\264\250.md" +++ "b/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\345\205\253)-\350\260\210\350\260\210 Kubernetes \347\232\204\346\234\254\350\264\250.md" @@ -30,14 +30,15 @@ Borg系统,一直以来都被誉为Google公司内部最强大的“秘密武器”。 相比于Spanner、BigTable等相对上层的项目,Borg要承担的责任,是承载Google整个基础设施的核心依赖。 在Google已经公开发表的基础设施体系论文中,Borg项目当仁不让地位居整个基础设施技术栈的最底层。 -![](https://img-blog.csdnimg.cn/2019101601190585.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70) -> 源于Google Omega论文的第一作者的博士毕业论文,描绘了当时Google已公开发表的整个基础设施栈。可发现MapReduce、BigTable等知名项目。Borg和其继任者Omega位于整个技术栈的最底层。 +![](https://img-blog.csdnimg.cn/2019101601190585.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) +> 这幅图,来自于Google Omega论文的第一作者的博士毕业论文。它描绘了当时Google已经公开发表的整个基础设施栈。在这个图里,你既可以找到MapReduce、BigTable等知名项目,也能看到Borg和它的继任者Omega位于整个技术栈的最底层。 -所以Borg可算是Google最不可能开源的一个项目。得益于Docker项目和容器技术发展,它却终于以Kubernetes身份开源。 +正是由于这样的定位,Borg可以说是Google最不可能开源的一个项目。 +得益于Docker项目和容器技术的风靡,它却终于得以以另一种方式与开源社区见面,就是Kubernetes项目。 相比于“小打小闹”的Docker公司、“旧瓶装新酒”的Mesos社区,**Kubernetes项目从一开始就比较幸运地站上了一个他人难以企及的高度:** 在它的成长阶段,这个项目每一个核心特性的提出,几乎都脱胎于Borg/Omega系统的设计与经验。 -这些特性在社区落地过程中,又在整个社区的合力之下得到了极大的改进,修复了大量Borg体系的历史缺陷。 +更重要的是,这些特性在开源社区落地的过程中,又在整个社区的合力之下得到了极大的改进,修复了很多当年遗留在Borg体系中的缺陷和问题。 尽管在发布之初被批“曲高和寡”,但在逐渐觉察到Docker技术栈的“稚嫩”和Mesos社区的“老迈”,社区很快就明白了:Kubernetes项目在Borg体系的指导下,体现出了一种独有的先进与完备性,这些才是一个基础设施领域开源项目的核心价值。 @@ -51,7 +52,7 @@ Borg系统,一直以来都被誉为Google公司内部最强大的“秘密武 但对于大多数用户,他们希望Kubernetes项目带来的体验是确定的: **现在我有应用的容器镜像,请帮我在一个给定的集群上把应用运行起来** 更进一步说,还希望Kubernetes能给我提供路由网关、水平扩展、监控、备份、灾难恢复等一系列运维能力。 -![](https://img-blog.csdnimg.cn/20191016015801907.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20191016015801907.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) 这不就是经典PaaS(eg. Cloud Foundry)项目的能力吗! 而且,有了Docker后,根本不需要什么Kubernetes、PaaS,只要使用Docker公司的**Compose+Swarm**项目,就完全可以很方便DIY出这些功能! @@ -61,7 +62,7 @@ Borg系统,一直以来都被誉为Google公司内部最强大的“秘密武 才在短短几个月内迅速站稳了脚跟 - 进而确定了一个如下所示的全局架构 -![](https://img-blog.csdnimg.cn/20191016020017448.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20191016020017448.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) 可以看到,Kubernetes项目的架构,跟它的原型项目Borg类似,都由Master和Node两种节点组成,分别对应着控制节点和计算节点。 其中,控制节点 --- 即Master节点,由三个紧密协作的独立组件组合而成,它们分别是 @@ -73,7 +74,7 @@ Borg系统,一直以来都被誉为Google公司内部最强大的“秘密武 而计算节点上最核心的是 # kubelet组件 -![](https://img-blog.csdnimg.cn/20191016215202805.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20191016215202805.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) kubelet是用来操作Docker等容器运行时的核心组件。 @@ -83,7 +84,7 @@ kubelet是用来操作Docker等容器运行时的核心组件。 而具体的容器运行时,比如Docker项目,则一般通过OCI这个容器运行时规范同底层的Linux操作系统进行交互,即:把CRI请求翻译成对Linux操作系统的调用(操作Linux Namespace和Cgroups等)。 此外,kubelet还通过gRPC协议同一个叫作Device Plugin的插件进行交互。 -![](https://img-blog.csdnimg.cn/20191017053653175.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20191017053653175.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) 这个插件,是Kubernetes项目用来管理GPU等宿主机物理设备的主要组件,也是基于Kubernetes项目进行机器学习训练、高性能作业支持等工作必须关注的功能。 而kubelet的另一个重要功能,则是调用网络插件和存储插件为容器配置网络和持久化存储。 @@ -174,7 +175,7 @@ DB_PORT_5432_TCP_ADDR=172.17.0.5 Pod里的容器共享同一个Network Namespace、同一组数据卷,从而达到高效率交换信息的目的。 Pod是Kubernetes中最基础的一个对象,源自于Google Borg论文中一个名叫Alloc的设计 -![](https://img-blog.csdnimg.cn/20191017234708371.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20191017234708371.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) > Borg分配(分配的缩写)是一台机器上可以运行一个或多个任务的资源的保留集。无论是否使用资源,资源都会保持分配状态。 Alloc可用于为将来的任务留出资源,在停止任务和重新启动任务之间保留资源,以及将来自不同作业的任务收集到同一台计算机上–例如,一个Web服务器实例和一个关联的logaver任务,用于复制服务器的URL日志从本地磁盘记录到分布式文件系统。分配资源与机器资源的处理方式相似。在一个内部运行的多个任务共享其资源。如果必须将分配重定位到另一台计算机,则其任务将随之重新安排。 分配集就像一项工作:它是一组在多台机器上保留资源的分配。创建分配集后,可以提交一个或多个作业以在其中运行。为简便起见,我们通常使用“任务”来指代分配或顶级任务(在分配外的一个),使用“作业”来指代作业或分配集。 @@ -189,7 +190,7 @@ Service服务声明的IP地址等信息是“终生不变”的。Service主要 围绕着容器和Pod不断向真实的技术场景扩展,我们就能够摸索出一幅如下所示 - Kubernetes核心功能“全景图” -![](https://img-blog.csdnimg.cn/20191018000558394.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20191018000558394.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) - 从容器这个最基础的概念出发,首先遇到了容器间“紧密协作”关系的难题,于是就扩展到了Pod - 有了Pod之后,我们希望能一次启动多个应用的实例,这样就需要Deployment这个Pod的多实例管理器 @@ -283,5 +284,6 @@ $ kubectl create -f nginx-deployment.yaml Kubernetes为用户提供的不仅限于一个工具。它真正的价值,还是在于提供了一套基于容器构建分布式系统的基础依赖 # 参考 + - 深入剖析Kubernetes - [Large-scale cluster management at Google with Borg](https://storage.googleapis.com/pub-tools-public-publication-data/pdf/43438.pdf) \ No newline at end of file diff --git "a/\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\345\205\255) - Docker\346\230\257\345\246\202\344\275\225\345\256\236\347\216\260\351\232\224\347\246\273\347\232\204.md" "b/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\345\205\255) - Docker\346\230\257\345\246\202\344\275\225\345\256\236\347\216\260\351\232\224\347\246\273\347\232\204.md" similarity index 69% rename from "\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\345\205\255) - Docker\346\230\257\345\246\202\344\275\225\345\256\236\347\216\260\351\232\224\347\246\273\347\232\204.md" rename to "Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\345\205\255) - Docker\346\230\257\345\246\202\344\275\225\345\256\236\347\216\260\351\232\224\347\246\273\347\232\204.md" index 0fd56b0575..651020445c 100644 --- "a/\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\345\205\255) - Docker\346\230\257\345\246\202\344\275\225\345\256\236\347\216\260\351\232\224\347\246\273\347\232\204.md" +++ "b/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\345\205\255) - Docker\346\230\257\345\246\202\344\275\225\345\256\236\347\216\260\351\232\224\347\246\273\347\232\204.md" @@ -1,14 +1,14 @@ # 1 Namespace -## 1.1 容器为何需要进程隔离 -- 被其他容器修改文件,导致安全问题 +## 1.1 容器为什么需要进程隔离 +- 被其他容器篡改文件,导致安全问题 - 资源的并发写入导致不一致性 -- 资源的抢占,导致其他容器被影响 +- 资源的抢占,导致其他容器被影响 ```bash docker run -it --name demo_docker busybox /bin/sh / # ps -ef ``` -![](https://img-blog.csdnimg.cn/20200923172437444.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center) +![](https://img-blog.csdnimg.cn/20200923172437444.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70#pic_center) 在宿主机查看进程ID @@ -16,13 +16,14 @@ docker run -it --name demo_docker busybox /bin/sh ps -ef|grep busybox ``` 真实的 docker 容器 pid -![](https://img-blog.csdnimg.cn/20200923173033582.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center) -这就是进程资源隔离表象: -- 对于宿主机 `docker run` 启动的只是一个进程,它的pid是44451 -- 而容器程序本身被隔离了,容器的内部都只能看到自己内部的进程 -- 这其实是基于Linux的Namespace技术(即使是 Windows 版本的 Docker 也是依托于 Windows 实现的类似Namespace的技术) +![](https://img-blog.csdnimg.cn/20200923173033582.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70#pic_center) +这就是进程资源隔离现象 +- 对于宿主机 docker run 执行命令启动的只是一个进程,它的pid是44451 +- 而容器程序本身,它被隔离了,在容器内部都只能看到自己内部的进程 +- 它这其实是借助了Linux 内核的Namespace技术 ## 1.2 Linux Namespace +Linux容器中用来实现“隔离”的技术手段:Namespace。 Linux 命名空间对全局操作系统资源进行了抽象,对于命名空间内的进程来说,他们拥有独立的资源实例,在命名空间内部的进程可以实现资源可见。 对于命名空间外部的进程,则不可见,实现了资源的隔离。这种技术广泛的应用于容器技术里。 @@ -47,41 +48,47 @@ int flags, void *arg, . . . 通过调用该方法,这个进程会获得一个独立的进程空间,它的pid是1 ,并且看不到宿主机上的其他进程,也就是在容器内执行PS命令的结果。 -不应该把Docker Engine或者任何容器管理工具放在跟Hypervisor相同的位置,因为它们并不像Hypervisor那样对应用进程的隔离环境负责,也不会创建任何实体的“容器”,真正对隔离环境负责的是宿主机os: -![](https://img-blog.csdnimg.cn/20210112193730251.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) -![](https://img-blog.csdnimg.cn/20190922013040574.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) -该对比图应该把Docker画在跟应用同级别并且靠边的位置。 + +不应该把Docker Engine或者任何容器管理工具放在跟Hypervisor相同的位置,因为它们并不像Hypervisor那样对应用进程的隔离环境负责,也不会创建任何实体的“容器”,真正对隔离环境负责的是宿主机操作系统本身: + +![](https://img-blog.csdnimg.cn/20190922013040574.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) +在这个对比图里,应该把Docker画在跟应用同级别并且靠边的位置。 用户运行在容器里的应用进程,跟宿主机上的其他进程一样,都由宿主机操作系统统一管理,只不过这些被隔离的进程拥有额外设置过的Namespace参数,Docker在这里更多的是辅助和管理工作。 -这也解释了 -### 为何Docker项目比虚拟机更好? -使用虚拟化技术作为应用沙盒,就必须由Hypervisor负责创建虚拟机,这个虚拟机是真实存在的,它里面必须运行一个完整的Guest OS才能执行用户的应用进程。这就不可避免地带来额外的资源消耗和占用。 +这样的架构也解释了为什么Docker项目比虚拟机更受欢迎。 + +使用虚拟化技术作为应用沙盒,就必须要由Hypervisor来负责创建虚拟机,这个虚拟机是真实存在的,它里面必须运行一个完整的Guest OS才能执行用户的应用进程。这就不可避免地带来了额外的资源消耗和占用。 > 据实验,一个运行着CentOS的KVM虚拟机启动后,在不做优化的情况下,虚拟机自己就需要占用100~200 MB内存。此外,用户应用运行在虚拟机里面,它对宿主机操作系统的调用就不可避免地要经过虚拟化软件的拦截和处理,这本身又是一层性能损耗,尤其对计算资源、网络和磁盘I/O的损耗非常大。 -而容器化后的用户应用,依然还是宿主机上的一个普通进程,这就意味着这些因为虚拟化而带来的性能损耗都不存在。 -使用Namespace作为隔离手段的容器无需单独的Guest OS,使得容器额外的资源占用可忽略不计。 +而容器化后的用户应用,依然还是宿主机上的一个普通进程,这就意味着这些因为虚拟化而带来的性能损耗都是不存在的 +使用Namespace作为隔离手段的容器并不需要单独的Guest OS,这就使得容器额外的资源占用几乎可以忽略不计。 “敏捷”和“高性能”是容器相较于虚拟机最大的优势。 有利必有弊,基于 Namespace 的隔离机制相比虚拟化技术也有很多不足。 -## 1.5 Namespace的缺点 +## 1.5 Namespace的缺陷 ### 隔离不彻底 -- 多容器间使用的还是同一宿主机os内核 -尽管可在容器里通过 `Mount Namespace` 单独挂载其他不同版本的os文件,比如 CentOS 或者 Ubuntu,但这并不能改变共享宿主机内核的事实! -所以不可能在Windows宿主机运行Linux容器或在低版本Linux宿主机运行高版本Linux容器。 -而拥有硬件虚拟化技术和独立Guest OS的虚拟机,比如Microsoft的云计算平台Azure,就是运行于Windows服务器集群,但可在其上面创建各种Linux虚拟机。 +#### 多容器间使用的还是同一宿主机的os内核 +尽管可以在容器里通过 `Mount Namespace` 单独挂载其他不同版本的os文件,比如 CentOS 或者 Ubuntu,但这并不能改变共享宿主机内核的事实! +这意味着如果不可能在Windows宿主机上运行Linux容器,或在低版本Linux宿主机运行高版本Linux容器。 + +而拥有硬件虚拟化技术和独立Guest OS的虚拟机就要方便得多。 +最极端的例子,Microsoft的云计算平台Azure,就是运行于Windows服务器集群,但可以在它上面创建各种Linux虚拟机。 -- Linux内核很多资源无法被Namespace -最典型的比如时间。 -若你的容器中的程序使用`settimeofday(2)`系统调用修改时间,整个宿主机的时间都会被随之修改,这并不符合用户预期。 -而相比于在虚拟机里可自己随便折腾,在容器里部署应用时,“什么能做,什么不能做”,用户都必须考虑。 +#### Linux内核中很多资源和对象无法被Namespace化 +最典型的:时间。 -尤其是**共享宿主机内核**: +如果你的容器中的程序使用settimeofday(2)系统调用修改时间,整个宿主机的时间都会被随之修改,这显然不符合用户预期。 +相比于在虚拟机里可瞎折腾,在容器里部署应用时,“什么能做,什么不能做”,都是用户必须考虑的。 -- 容器给应用暴露出来的攻击面是相当大的 +由于上述问题,尤其是共享宿主机内核的事实: + +#### 容器给应用暴露出来的攻击面是相当大的 应用“越狱”难度也比虚拟机低得多。 -尽管可使用Seccomp等技术,过滤和甄别容器内部发起的所有系统调用来进行安全加固,但这就多了一层对系统调用的过滤,一定会拖累容器性能。默认情况下,也不知道到底该开启哪些系统调用,禁止哪些系统调用。 + +尽管可以使用Seccomp等技术,过滤和甄别容器内部发起的所有系统调用来进行安全加固,但这就多了一层对系统调用的过滤,一定会拖累容器性能。默认情况下,也不知道到底该开启哪些系统调用,禁止哪些系统调用。 + 所以,在生产环境中,无人敢把运行在物理机上的Linux容器直接暴露至公网。 >基于虚拟化或者独立内核技术的容器实现,则可以比较好地在隔离与性能之间做出平衡。 @@ -105,7 +112,7 @@ Docker实现CPU、内存、网络的限制也均通过cgroups实现。 在Linux中,Cgroups给用户暴露出来的操作接口是文件系统,即它以文件和目录的方式组织在os的/sys/fs/cgroup路径。 -- 在笔者的 CentOS7 VM里,可以用mount指令把它们展示出来![](https://img-blog.csdnimg.cn/20191006205125242.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +- 在笔者的 CentOS7 VM里,可以用mount指令把它们展示出来![](https://img-blog.csdnimg.cn/20191006205125242.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) 它的输出结果,是一系列文件系统目录(如果你在自己的机器上没有看到这些目录,那你就需要自己去挂载Cgroups) 在/sys/fs/cgroup下面有很多诸如cpuset、cpu、 memory这样的子目录,也叫`子系统` @@ -114,14 +121,14 @@ Docker实现CPU、内存、网络的限制也均通过cgroups实现。 而在子系统对应的资源种类下,你就可以看到该类资源具体可以被限制的方法。 - 譬如,对CPU子系统来说,就可以看到如下配置文件 -![](https://img-blog.csdnimg.cn/20191006205510919.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20191006205510919.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) 注意到cfs_period和cfs_quota这样的关键词,这两个参数需要组合使用,可用来 `限制进程在长度为cfs_period的一段时间内,只能被分配到总量为cfs_quota的CPU时间` ### 这样的配置文件如何使用呢? 需要在对应的子系统下面创建一个目录 比如,我们现在进入/sys/fs/cgroup/cpu目录下: -![](https://img-blog.csdnimg.cn/20191006210203229.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20191006210203229.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) 这个目录就称为一个“控制组”。 OS会在你新创建的container目录下,自动生成该子系统对应的资源限制文件! # 4 Cgroups实战 @@ -129,7 +136,7 @@ OS会在你新创建的container目录下,自动生成该子系统对应的资 - 执行脚本 ![](https://img-blog.csdnimg.cn/20191006210331271.png) - 死循环可致CPU 100%,top确认: -![](https://img-blog.csdnimg.cn/20191008004521574.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20191008004521574.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) 此时,可以查看container目录下的文件,看到 - container控制组里的CPU quota还没有任何限制(即:-1) @@ -144,7 +151,7 @@ OS会在你新创建的container目录下,自动生成该子系统对应的资 - 接下来把被限制的进程的PID写入container组里的tasks文件,上面的设置就会对该进程生效 ![](https://img-blog.csdnimg.cn/20191008005155574.png) - top,可见CPU使用率立刻降到20% -![](https://img-blog.csdnimg.cn/20191008005126191.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20191008005126191.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) 除CPU子系统外,Cgroups的每一项子系统都有其独有的资源限制能力,比如: - blkio,为​​​块​​​设​​​备​​​设​​​定​​​I/O限​​​制,一般用于磁盘等设备 @@ -175,14 +182,12 @@ xxx ``` # 6 总结 -![](https://img-blog.csdnimg.cn/20210112194029263.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) - -容器只是一种特殊的进程,一个正在运行的Docker容器,就是一个启用了多个Linux Namespace的应用进程,而该进程能够使用的资源量,则受Cgroups限制。即`容器是一个“单进程”模型`。 +所谓容器只是一种特殊的进程,一个正在运行的Docker容器,就是一个启用了多个Linux Namespace的应用进程,而该进程能够使用的资源量,则受Cgroups限制。即:`容器是一个“单进程”模型`。 -由于一个容器本质就是一个进程,用户的应用进程实际上就是容器里PID=1的进程,也是其他后续创建的所有进程的父进程。 +由于一个容器的本质就是一个进程,用户的应用进程实际上就是容器里PID=1的进程,也是其他后续创建的所有进程的父进程。 这意味着,在一个容器,无法同时运行两个不同应用,除非你能事先找到一个公共的PID=1的程序充当两个不同应用的父进程,这也解释了为何很多人会用`systemd`或`supervisord`这样的软件代替应用本身作为容器的启动进程。 -容器本身设计就是希望容器和应用能同生命周期,这对容器的编排很重要。否则,一旦出现类似于“容器是正常运行的,但是里面的应用早已经挂了”的情况,编排系统处理起来就非常麻烦了。 +后面还会推荐其他更好的解决办法。这是因为容器本身的设计,就是希望容器和应用能够同生命周期,这个概念对后续的容器编排非常重要。否则,一旦出现类似于“容器是正常运行的,但是里面的应用早已经挂了”的情况,编排系统处理起来就非常麻烦了。 跟Namespace的情况类似,Cgroups对资源的限制能力也有很多不完善的地方,被提及最多的就是/proc文件系统的问题。 如果在容器里执行top,会发现它显示的信息是宿主机的CPU和内存数据,而不是当前容器的。造成这个问题的原因就是,/proc文件系统并不知道用户通过Cgroups给这个容器做了什么样的资源限制,即:/proc文件系统不了解Cgroups限制的存在。 diff --git "a/\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\345\233\233) - \347\272\267\347\272\267\346\211\260\346\211\260,\347\273\210\345\275\222\345\260\230\345\234\237.md" "b/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\345\233\233) - \347\272\267\347\272\267\346\211\260\346\211\260,\347\273\210\345\275\222\345\260\230\345\234\237.md" similarity index 83% rename from "\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\345\233\233) - \347\272\267\347\272\267\346\211\260\346\211\260,\347\273\210\345\275\222\345\260\230\345\234\237.md" rename to "Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\345\233\233) - \347\272\267\347\272\267\346\211\260\346\211\260,\347\273\210\345\275\222\345\260\230\345\234\237.md" index 482d249621..b79c0810f4 100644 --- "a/\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\345\233\233) - \347\272\267\347\272\267\346\211\260\346\211\260,\347\273\210\345\275\222\345\260\230\345\234\237.md" +++ "b/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\345\233\233) - \347\272\267\347\272\267\346\211\260\346\211\260,\347\273\210\345\275\222\345\260\230\345\234\237.md" @@ -15,13 +15,13 @@ Docker项目此时已经成为Docker公司一个商业产品。而开源,只 其实在Docker项目刚刚兴起时 Google也开源了一个在内部使用多年、经历过生产环境验证的Linux容器 # 1 lmctfy(Let Me Container That For You) -![](https://img-blog.csdnimg.cn/20190903001505838.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20190903001505838.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) 然而,面对Docker项目的强势崛起,这个对用户没那么友好的Google容器项目根本没有招架之力。所以,知难而退的Google公司,向Docker公司表示了合作的愿望:关停这个项目,和Docker公司共同推进一个中立的容器运行时(container runtime)库作为Docker项目的核心依赖。 不过,Docker公司并没有认同这个明显会削弱自己地位的提议,还在不久后,自己发布了一个容器运行时库 # 2 Libcontainer -![](https://img-blog.csdnimg.cn/20190903003257729.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20190903003257729.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) 这次匆忙的、由一家主导的、并带有战略性考量的重构,成了Libcontainer被社区长期诟病代码可读性差、可维护性不强的一个重要原因。 至此,Docker公司在容器运行时层面上的强硬态度,以及Docker项目在高速迭代中表现出来的不稳定和频繁变更的问题,开始让社区叫苦不迭。 @@ -29,10 +29,10 @@ Google也开源了一个在内部使用多年、经历过生产环境验证的Li 这种情绪在2015年达到了一个小高潮,容器领域的其他几位玩家开始商议“切割”Docker项目的话语权 --- 成立一个中立的基金会。 2015年6月22日,由Docker公司牵头,CoreOS、Google、RedHat等公司共同宣布,Docker公司将Libcontainer捐出,并改名为 # 3 runc -![](https://img-blog.csdnimg.cn/20190903003421391.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)交由一个完全中立的基金会管理,然后以runc为依据,大家共同制定一套容器和镜像的标准和规范。 +![](https://img-blog.csdnimg.cn/20190903003421391.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)交由一个完全中立的基金会管理,然后以runc为依据,大家共同制定一套容器和镜像的标准和规范。 这套标准和规范,就是 # 4 **OCI( Open Container Initiative )** -![](https://img-blog.csdnimg.cn/20190904001537969.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20190904001537969.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) OCI的提出,意在将容器运行时和镜像的实现从Docker项目中完全剥离出来 - 一方面可以改善Docker公司在容器技术上一家独大的现状 - 另一方面也为其他玩家不依赖于Docker项目构建各自的平台层能力提供了可能 @@ -52,7 +52,7 @@ Docker之所以不担心OCI威胁,就在于它的Docker项目是容器生态 所以这次,Google、RedHat等开源基础设施领域玩家们,发起名为 # 5 CNCF(Cloud Native Computing Foundation) 的基金会 -![](https://img-blog.csdnimg.cn/20190904002014145.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20190904002014145.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) 基金会希望,以 **`Kubernetes`** 项目为基础,建立一个由`开源基础设施领域厂商主导的、按照独立基金会方式运营`的平台级社区,来`对抗以Docker公司为核心的容器商业生态`。 CNCF社区就需要至少确保两件事情 @@ -96,18 +96,18 @@ Kubernetes项目并没有跟Swarm项目展开同质化的竞争,所以“Docke 在已经囊括了容器监控事实标准的 # 6 Prometheus 项目之后 -![](https://img-blog.csdnimg.cn/20190904003456378.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) -![](https://img-blog.csdnimg.cn/20190904003540329.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20190904003456378.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20190904003540329.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) CNCF社区迅速在成员项目中添加了 - Fluentd -![](https://img-blog.csdnimg.cn/20190904003638833.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) -![](https://img-blog.csdnimg.cn/20190904003805591.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20190904003638833.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20190904003805591.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) - jagerTracing -![](https://img-blog.csdnimg.cn/20190904004141329.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) -![](https://img-blog.csdnimg.cn/20190904004259522.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20190904004141329.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20190904004259522.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) - CNI -![](https://img-blog.csdnimg.cn/20190904004442161.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20190904004442161.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) 等一系列容器生态的知名工具和项目。 而在看到了CNCF社区对用户表现出来的巨大吸引力之后,大量的公司和创业团队也开始专门针对CNCF社区而非Docker公司制定推广策略。 @@ -126,11 +126,11 @@ CNCF社区迅速在成员项目中添加了 Kubernetes项目的这个变革的效果立竿见影,很快在整个容器社区中催生出了大量的、基于Kubernetes API和扩展接口的二次创新工作,比如: - 热度极高的微服务治理项目Istio -![](https://img-blog.csdnimg.cn/20190904004635785.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20190904004635785.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) - 广泛采用的有状态应用部署框架Operator -![](https://img-blog.csdnimg.cn/20190904004806161.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20190904004806161.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) - 开源创业项目 --- Rook,通过Kubernetes的可扩展接口,把Ceph这样的重量级产品封装成简单易用的容器存储插件 -![](https://img-blog.csdnimg.cn/20190904004859809.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20190904004859809.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) 就这样,在这种鼓励二次创新的整体氛围当中,Kubernetes社区在2016年之后得到了空前的发展 不同于之前局限于“打包、发布”这样的PaaS化路线,这一次容器社区的繁荣,是一次完全以Kubernetes项目为核心的“百花争鸣”。 @@ -140,12 +140,12 @@ Kubernetes项目的这个变革的效果立竿见影,很快在整个容器社 所以,从2017年开始,Docker公司先是将Docker项目的容器运行时部分 - Containerd -![](https://img-blog.csdnimg.cn/20190904005045114.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20190904005045114.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) 捐赠给CNCF社区,标志着Docker项目已经全面升级成为一个PaaS平台 紧接着,Docker公司宣布将Docker项目改名为 - Moby -![](https://img-blog.csdnimg.cn/20190904005214749.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)然后交给社区自行维护,而Docker公司的商业产品将占有Docker这个注册商标。 +![](https://img-blog.csdnimg.cn/20190904005214749.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)然后交给社区自行维护,而Docker公司的商业产品将占有Docker这个注册商标。 Docker公司这些举措背后的含义非常明确:它将全面放弃在开源社区同Kubernetes生态的竞争,转而专注于自己的商业业务 并且通过将Docker项目改名为Moby的举动,将原本属于Docker社区的用户转化成了自己的客户。 diff --git "a/\344\272\221\345\216\237\347\224\237/Kubernetes/Kubernetes\345\237\272\347\241\200\347\275\221\347\273\234Cluster Network\346\250\241\345\236\213.md" "b/Docker/Kubernetes/Kubernetes\345\237\272\347\241\200\347\275\221\347\273\234Cluster Network\346\250\241\345\236\213.md" similarity index 54% rename from "\344\272\221\345\216\237\347\224\237/Kubernetes/Kubernetes\345\237\272\347\241\200\347\275\221\347\273\234Cluster Network\346\250\241\345\236\213.md" rename to "Docker/Kubernetes/Kubernetes\345\237\272\347\241\200\347\275\221\347\273\234Cluster Network\346\250\241\345\236\213.md" index e859ebeb25..5dbe043287 100644 --- "a/\344\272\221\345\216\237\347\224\237/Kubernetes/Kubernetes\345\237\272\347\241\200\347\275\221\347\273\234Cluster Network\346\250\241\345\236\213.md" +++ "b/Docker/Kubernetes/Kubernetes\345\237\272\347\241\200\347\275\221\347\273\234Cluster Network\346\250\241\345\236\213.md" @@ -2,11 +2,11 @@ # 创建两个 pod -![](https://img-blog.csdnimg.cn/20201230171631219.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) -查看 刚才创建的 nginx-pod的 ip![](https://img-blog.csdnimg.cn/20201231144959527.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201230171631219.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) +查看 刚才创建的 nginx-pod的 ip![](https://img-blog.csdnimg.cn/20201231144959527.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) - 进入 busybox并查看网络 -![](https://img-blog.csdnimg.cn/20201230171755578.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201230171755578.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) - 在 busybox 中ping得通nginx-pod![](https://img-blog.csdnimg.cn/20201231144835180.png) 可在k8s 节点上任一 pod 里 ping 通其它 pod。 - 原理模型如下: -![](https://img-blog.csdnimg.cn/20201231145818490.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201231145818490.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) diff --git "a/\344\272\221\345\216\237\347\224\237/Kubernetes/Kubernetes\351\233\206\347\276\244\345\256\211\350\243\205\345\217\212kubectl\345\221\275\344\273\244\350\241\214\345\244\247\345\205\250.md" "b/Docker/Kubernetes/Kubernetes\351\233\206\347\276\244\345\256\211\350\243\205\345\217\212kubectl\345\221\275\344\273\244\350\241\214\345\244\247\345\205\250.md" similarity index 91% rename from "\344\272\221\345\216\237\347\224\237/Kubernetes/Kubernetes\351\233\206\347\276\244\345\256\211\350\243\205\345\217\212kubectl\345\221\275\344\273\244\350\241\214\345\244\247\345\205\250.md" rename to "Docker/Kubernetes/Kubernetes\351\233\206\347\276\244\345\256\211\350\243\205\345\217\212kubectl\345\221\275\344\273\244\350\241\214\345\244\247\345\205\250.md" index f4bf6f697b..4e791f5e22 100644 --- "a/\344\272\221\345\216\237\347\224\237/Kubernetes/Kubernetes\351\233\206\347\276\244\345\256\211\350\243\205\345\217\212kubectl\345\221\275\344\273\244\350\241\214\345\244\247\345\205\250.md" +++ "b/Docker/Kubernetes/Kubernetes\351\233\206\347\276\244\345\256\211\350\243\205\345\217\212kubectl\345\221\275\344\273\244\350\241\214\345\244\247\345\205\250.md" @@ -12,12 +12,12 @@ MAC 切换到 Kubernetes 标签页 并勾选启动 Enable Kubernetes,点击 Apply -![](https://img-blog.csdnimg.cn/20201230114128502.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201230114128502.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) ```bash kubectl config view ``` -![](https://img-blog.csdnimg.cn/20201230114528963.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201230114528963.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) ```bash kubectl config get-contexts @@ -83,7 +83,7 @@ docker network inspect bridge ``` kubectl exec -it nginx sh ``` -![](https://img-blog.csdnimg.cn/2020123013362484.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/2020123013362484.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) > kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead. 因为刚才创建的 nginx pod 里只有一个容器,所以我们就进入了那个。可如果nginx pod 里有俩容器,默认只会进第一个,如何进第二个呢? @@ -164,7 +164,7 @@ kubectl label --overwrite pods foo status=unhealthy ``` - kubectl scale rs nginx --replicas=2 更新某 pod 内集群数 ![](https://img-blog.csdnimg.cn/20201230145136314.png) -![](https://img-blog.csdnimg.cn/20201230145258337.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201230145258337.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) # 创建资源 ```bash # 创建一个service,暴露 nginx 这个rc diff --git "a/\344\272\221\345\216\237\347\224\237/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\345\256\236\346\210\230(\344\270\211)-Kubernetes\345\267\245\344\275\234\350\264\237\350\275\275\350\265\204\346\272\220PodReplicaSet.md" "b/Docker/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\345\256\236\346\210\230(\344\270\211)-Kubernetes\345\267\245\344\275\234\350\264\237\350\275\275\350\265\204\346\272\220PodReplicaSet.md" similarity index 89% rename from "\344\272\221\345\216\237\347\224\237/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\345\256\236\346\210\230(\344\270\211)-Kubernetes\345\267\245\344\275\234\350\264\237\350\275\275\350\265\204\346\272\220PodReplicaSet.md" rename to "Docker/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\345\256\236\346\210\230(\344\270\211)-Kubernetes\345\267\245\344\275\234\350\264\237\350\275\275\350\265\204\346\272\220PodReplicaSet.md" index 90f37106d9..3a4cfedc40 100644 --- "a/\344\272\221\345\216\237\347\224\237/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\345\256\236\346\210\230(\344\270\211)-Kubernetes\345\267\245\344\275\234\350\264\237\350\275\275\350\265\204\346\272\220PodReplicaSet.md" +++ "b/Docker/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\345\256\236\346\210\230(\344\270\211)-Kubernetes\345\267\245\344\275\234\350\264\237\350\275\275\350\265\204\346\272\220PodReplicaSet.md" @@ -1,8 +1,6 @@ ReplicaSet 的目的是维护一组在任何时候都处于运行状态的 Pod 副本的稳定集合。 因此,它通常用来保证给定数量的、完全相同的 Pod 的可用性。 # ReplicaSet 的工作原理 -![](https://img-blog.csdnimg.cn/20210112220555367.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) - RepicaSet 是通过一组字段来定义的,包括一个用来识别可获得的 Pod 的集合的选择算符、一个用来标明应该维护的副本个数的数值、一个用来指定应该创建新 Pod 以满足副本个数条件时要使用的 Pod 模板等等。 每个 ReplicaSet 都通过根据需要创建和 删除 Pod 以使得副本个数达到期望值, 进而实现其存在价值。当 ReplicaSet 需要创建新的 Pod 时,会使用所提供的 Pod 模板。 ReplicaSet 通过 Pod 上的 metadata.ownerReferences 字段连接到附属 Pod,该字段给出当前对象的属主资源。 ReplicaSet 所获得的 Pod 都在其 ownerReferences 字段中包含了属主 ReplicaSet 的标识信息。正是通过这一连接,ReplicaSet 知道它所维护的 Pod 集合的状态, 并据此计划其操作行为。 @@ -43,6 +41,6 @@ spec: kubectl create -f rs_nginx.yml ``` ![](https://img-blog.csdnimg.cn/20201230143146759.png) -![](https://img-blog.csdnimg.cn/20201230143541575.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) -删除第一个 image 后,发现还是三个,说明他自己又补了一个。![](https://img-blog.csdnimg.cn/20201230144215219.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201230143541575.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) +删除第一个 image 后,发现还是三个,说明他自己又补了一个。![](https://img-blog.csdnimg.cn/20201230144215219.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) 所以推荐使用该种方式创建 pod,保证都会起来。 \ No newline at end of file diff --git "a/\344\272\221\345\216\237\347\224\237/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\345\256\236\346\210\230(\344\272\214)-Kubernetes\350\260\203\345\272\246\345\215\225\344\275\215Pod.md" "b/Docker/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\345\256\236\346\210\230(\344\272\214)-Kubernetes\350\260\203\345\272\246\345\215\225\344\275\215Pod.md" similarity index 63% rename from "\344\272\221\345\216\237\347\224\237/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\345\256\236\346\210\230(\344\272\214)-Kubernetes\350\260\203\345\272\246\345\215\225\344\275\215Pod.md" rename to "Docker/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\345\256\236\346\210\230(\344\272\214)-Kubernetes\350\260\203\345\272\246\345\215\225\344\275\215Pod.md" index aea98a8b41..9f7a5d76ae 100644 --- "a/\344\272\221\345\216\237\347\224\237/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\345\256\236\346\210\230(\344\272\214)-Kubernetes\350\260\203\345\272\246\345\215\225\344\275\215Pod.md" +++ "b/Docker/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\345\256\236\346\210\230(\344\272\214)-Kubernetes\350\260\203\345\272\246\345\215\225\344\275\215Pod.md" @@ -1,18 +1,18 @@ -# K8S最小调度单位Pod -![](https://img-blog.csdnimg.cn/20210112220718366.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +@[TOC](目录) -![](https://img-blog.csdnimg.cn/2020122717311732.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +- K8S最小调度单位Pod +![](https://img-blog.csdnimg.cn/2020122717311732.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) 我们不直接操作容器container 一个 pod 里可包含一或多个container,共享一个 namespace(用户,网络,存储等),其中的进程之间通过 localhost 本地通信 - 创建一个 yml 文件,并创建 -``` +```bash kubectl create -f pod_nginx.yml ``` -![](https://img-blog.csdnimg.cn/20201230123615919.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201230123615919.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) ``` kubectl get pods @@ -20,7 +20,7 @@ kubectl get pods 第一次运行状态字段为 pull,因为要先拉取 nginx 的 image,ready ![](https://img-blog.csdnimg.cn/20201230123831908.png) -- 查看 docker 面板,已经成功拉取下来 nginx 镜像,再次查看![](https://img-blog.csdnimg.cn/20201230124118185.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +- 查看 docker 面板,已经成功拉取下来 nginx 镜像,再次查看![](https://img-blog.csdnimg.cn/20201230124118185.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) - ready 为 1,说明已启动![](https://img-blog.csdnimg.cn/2020123012415544.png) pod 里面现在运行了一个 nginx 的 container,查看详情 @@ -29,17 +29,17 @@ kubectl get pods -o wide ``` ![](https://img-blog.csdnimg.cn/20201230124514125.png) -如果想进入容器咋办呢?查看他的 imageid![](https://img-blog.csdnimg.cn/20201230124815316.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +如果想进入容器咋办呢?查看他的 imageid![](https://img-blog.csdnimg.cn/20201230124815316.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) - 直接点击 cli 工具进入 -![](https://img-blog.csdnimg.cn/20201230125014402.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) -但是我们必须通过 dockercli 才能访问里面的 nginx,无法在本地命令行直接通信![](https://img-blog.csdnimg.cn/20201230140725330.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201230125014402.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) +但是我们必须通过 dockercli 才能访问里面的 nginx,无法在本地命令行直接通信![](https://img-blog.csdnimg.cn/20201230140725330.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) ![](https://img-blog.csdnimg.cn/20201230140800829.png) -![](https://img-blog.csdnimg.cn/20201230140852190.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201230140852190.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) 那如何才能映射一个可访问的 ip,让我们在本地也能与 nginx 通信呢? -![](https://img-blog.csdnimg.cn/20201230142100447.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201230142100447.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) -![](https://img-blog.csdnimg.cn/20201230141916719.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201230141916719.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) 但这样如果把该命令停止,就会无法访问了。 # 删除 pod ```bash diff --git "a/\344\272\221\345\216\237\347\224\237/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\345\256\236\346\210\230(\345\233\233)-Kubernetes\345\267\245\344\275\234\350\264\237\350\275\275\350\265\204\346\272\220\344\271\213Deployment.md" "b/Docker/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\345\256\236\346\210\230(\345\233\233)-Kubernetes\345\267\245\344\275\234\350\264\237\350\275\275\350\265\204\346\272\220\344\271\213Deployment.md" similarity index 82% rename from "\344\272\221\345\216\237\347\224\237/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\345\256\236\346\210\230(\345\233\233)-Kubernetes\345\267\245\344\275\234\350\264\237\350\275\275\350\265\204\346\272\220\344\271\213Deployment.md" rename to "Docker/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\345\256\236\346\210\230(\345\233\233)-Kubernetes\345\267\245\344\275\234\350\264\237\350\275\275\350\265\204\346\272\220\344\271\213Deployment.md" index 5bb31f97ed..7752dfbb23 100644 --- "a/\344\272\221\345\216\237\347\224\237/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\345\256\236\346\210\230(\345\233\233)-Kubernetes\345\267\245\344\275\234\350\264\237\350\275\275\350\265\204\346\272\220\344\271\213Deployment.md" +++ "b/Docker/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\345\256\236\346\210\230(\345\233\233)-Kubernetes\345\267\245\344\275\234\350\264\237\350\275\275\350\265\204\346\272\220\344\271\213Deployment.md" @@ -1,5 +1,4 @@ 一个 Deployment 控制器为 Pods 和 ReplicaSets 提供声明式的更新能力。 -![](https://img-blog.csdnimg.cn/20210112221207576.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) 你负责描述 Deployment 中的 目标状态,而 Deployment 控制器以受控速率更改实际状态, 使其变为期望状态。你可以定义 Deployment 以创建新的 ReplicaSet,或删除现有 Deployment, 并通过新的 Deployment 收养其资源。 @@ -15,12 +14,12 @@ 清理较旧的不再需要的 ReplicaSet 。 # 创建 Deployment 下面是 Deployment 示例。其中创建了一个 ReplicaSet,负责启动三个 nginx Pods: -![](https://img-blog.csdnimg.cn/20201230151932819.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201230151932819.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) ## 常用命令 ### kubectl get deployments - 检查 Deployment 是否已创建 -![](https://img-blog.csdnimg.cn/20201230152411555.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201230152411555.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) 在检查集群中的 Deployment 时,所显示的字段有: - NAME 集群中 Deployment 的名称。 @@ -55,7 +54,7 @@ kubectl --record deployment.apps/nginx-deployment set image \ kubectl set image deployment nginx-deployment nginx=nginx:1.13 ``` -![](https://img-blog.csdnimg.cn/20201230155606597.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201230155606597.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) # 检查 Deployment 上线历史 按照如下步骤检查回滚历史: @@ -65,11 +64,9 @@ kubectl set image deployment nginx-deployment nginx=nginx:1.13 kubectl rollout history deployment.v1.apps/nginx-deployment ``` 输出: -![](https://img-blog.csdnimg.cn/20201230160137995.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201230160137995.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) CHANGE-CAUSE 的内容是从 Deployment 的 kubernetes.io/change-cause 注解复制过来的。 复制动作发生在修订版本创建时。你可以通过以下方式设置 CHANGE-CAUSE 消息: 使用 kubectl annotate deployment.v1.apps/nginx-deployment kubernetes.io/change-cause="image updated to 1.9.1" 为 Deployment 添加注解。 追加 --record 命令行标志以保存正在更改资源的 kubectl 命令。 手动编辑资源的清单。 -# 滚动发布 -![](https://img-blog.csdnimg.cn/20210112221343847.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) diff --git "a/\344\272\221\345\216\237\347\224\237/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\346\225\231\347\250\213(\344\270\200)-Kubernetes\347\232\204\345\237\272\346\234\254\346\236\266\346\236\204.md" "b/Docker/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\346\225\231\347\250\213(\344\270\200)-Kubernetes\347\232\204\345\237\272\346\234\254\346\236\266\346\236\204.md" similarity index 73% rename from "\344\272\221\345\216\237\347\224\237/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\346\225\231\347\250\213(\344\270\200)-Kubernetes\347\232\204\345\237\272\346\234\254\346\236\266\346\236\204.md" rename to "Docker/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\346\225\231\347\250\213(\344\270\200)-Kubernetes\347\232\204\345\237\272\346\234\254\346\236\266\346\236\204.md" index f7a7965838..84c5db27aa 100644 --- "a/\344\272\221\345\216\237\347\224\237/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\346\225\231\347\250\213(\344\270\200)-Kubernetes\347\232\204\345\237\272\346\234\254\346\236\266\346\236\204.md" +++ "b/Docker/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\346\225\231\347\250\213(\344\270\200)-Kubernetes\347\232\204\345\237\272\346\234\254\346\236\266\346\236\204.md" @@ -1,24 +1,22 @@ -# 简介 -Kubernetes,希腊语,意舵手。有时简写为“K8s”,其中“8”代表“K”和“s”之间的 8 个字母,是一个开源系统,支持在任何地方部署、扩缩和管理容器化应用。 +> `文章收录在我的 GitHub 仓库,欢迎Star/fork:` +[Java-Interview-Tutorial](https://github.com/Wasabi1234/Java-Interview-Tutorial) +https://github.com/Wasabi1234/Java-Interview-Tutorial # 1 Kubernetes架构 - 整体架构图 -![](https://img-blog.csdnimg.cn/20201227162743754.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201227162743754.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) Kubernetes的这种架构为服务发现提供了一种灵活的,松耦合的机制。与大多数分布式计算平台架构一样,Kubernetes集群至少包含一个主节点和多个计算节点。主服务器负责公开应用程序接口(API),安排部署并管理整个集群。 每个节点都运行一个运行时容器,例如Docker或rkt,以及一个与主机通信的代理。该节点还运行用于日志记录,监视,服务发现和可选附件的其他组件。节点是Kubernetes集群的主力军。它们向应用程序公开计算,网络和存储资源。节点可以是在云中运行的虚拟机(VM)或在数据中心内运行的裸机服务器。 -![](https://img-blog.csdnimg.cn/20201227155709834.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) - - 只不过 -- manager 在这里是 Master -- worker 是 普通的 Node +![](https://img-blog.csdnimg.cn/20201227155709834.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) + 只不过 manager 在这里是 Master + worker 是 普通的 Node Master会对外暴露很多接口供我们操作 k8s 集群,比如查看节点状态、将应用部署到k8s集群。 -- Kubernetes 集群(Cluster) -![](https://img-blog.csdnimg.cn/20210112221020243.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) + ## 1.1 Master 节点功能 -![](https://img-blog.csdnimg.cn/20201227155733397.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201227155733397.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) ### 核心组件 #### etcd 分布式的 K.V 存储,保存了整个k8s集群的状态和配置 @@ -30,7 +28,7 @@ Master会对外暴露很多接口供我们操作 k8s 集群,比如查看节点 负责资源的调度,按照预定的调度策略将Pod调度到相应的机器上。比如我要部署一个应用,该应用需要两个容器,那这俩容器又该运行在哪个 Node 呢?这就是负责的事情。 ## 1.2 Node 节点功能 -![](https://img-blog.csdnimg.cn/20201227160931908.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201227160931908.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) ### 核心组件 #### Pod @@ -43,8 +41,6 @@ Master会对外暴露很多接口供我们操作 k8s 集群,比如查看节点 我们知道Node 节点受 Master 控制的,那就需要一个代理在 Node 中做这些事。负责维护容器的生命周期,同时也负责Volume(CSI)和网络(CNI)的管理。 #### Container runtime -![](https://img-blog.csdnimg.cn/20210112220908927.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) - 负责镜像管理以及Pod和容器的真正运行(CRI) #### kube-proxy 负责为Service提供集群内部的服务发现和负载均衡。 diff --git "a/\344\272\221\345\216\237\347\224\237/Kubernetes/\345\256\211\350\243\205\351\205\215\347\275\256/\344\270\213\350\275\275\345\256\211\350\243\205minikube.md" "b/Docker/Kubernetes/\345\256\211\350\243\205\351\205\215\347\275\256/\344\270\213\350\275\275\345\256\211\350\243\205minikube.md" similarity index 100% rename from "\344\272\221\345\216\237\347\224\237/Kubernetes/\345\256\211\350\243\205\351\205\215\347\275\256/\344\270\213\350\275\275\345\256\211\350\243\205minikube.md" rename to "Docker/Kubernetes/\345\256\211\350\243\205\351\205\215\347\275\256/\344\270\213\350\275\275\345\256\211\350\243\205minikube.md" diff --git "a/\344\272\221\345\216\237\347\224\237/Kubernetes/\346\213\245\346\212\261Kubernetes,\345\206\215\350\247\201\344\272\206Spring Cloud.md" "b/Docker/Kubernetes/\346\213\245\346\212\261Kubernetes,\345\206\215\350\247\201\344\272\206Spring Cloud.md" similarity index 97% rename from "\344\272\221\345\216\237\347\224\237/Kubernetes/\346\213\245\346\212\261Kubernetes,\345\206\215\350\247\201\344\272\206Spring Cloud.md" rename to "Docker/Kubernetes/\346\213\245\346\212\261Kubernetes,\345\206\215\350\247\201\344\272\206Spring Cloud.md" index 6fac8fe2fb..7227db5e8e 100644 --- "a/\344\272\221\345\216\237\347\224\237/Kubernetes/\346\213\245\346\212\261Kubernetes,\345\206\215\350\247\201\344\272\206Spring Cloud.md" +++ "b/Docker/Kubernetes/\346\213\245\346\212\261Kubernetes,\345\206\215\350\247\201\344\272\206Spring Cloud.md" @@ -50,7 +50,7 @@ Spring Cloud原有方案:Netflix Ribbon 和 Hystrix,但在 k8s 有 Service # Spring Cloud V.S k8s 重叠方案 -![](https://img-blog.csdnimg.cn/20201226153733174.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20201226153733174.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) 我们当然也能完全不理會 k8s 原生组件,完全采用 Spring Boot 和 Spring Cloud 的解決方案,只把 k8s 当做部署应用的工具和平台。但显然在未來,Service Mesh 及其通用的 Cloud Native 技术发展,就会和Spring Cloud脱轨,无法再和我们的应用深度整合。 diff --git "a/Docker/\345\217\262\344\270\212\346\234\200\345\277\253Docker\345\205\245\351\227\250!(\344\270\200)-\345\256\271\345\231\250\346\212\200\346\234\257\345\222\214Docker\347\256\200\344\273\213.md" "b/Docker/\345\217\262\344\270\212\346\234\200\345\277\253Docker\345\205\245\351\227\250!(\344\270\200)-\345\256\271\345\231\250\346\212\200\346\234\257\345\222\214Docker\347\256\200\344\273\213.md" new file mode 100644 index 0000000000..0505d1519c --- /dev/null +++ "b/Docker/\345\217\262\344\270\212\346\234\200\345\277\253Docker\345\205\245\351\227\250!(\344\270\200)-\345\256\271\345\231\250\346\212\200\346\234\257\345\222\214Docker\347\256\200\344\273\213.md" @@ -0,0 +1,14 @@ +# 1 容器技术概述 +聊聊容器技术 +![](https://upload-images.jianshu.io/upload_images/4685968-bd8b71e958d72512.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-38b190b30a6725fc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-d5964b56c470f7da.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-efbae9d25a5587bc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-37b6ff19a297195e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-ed3e0e9ca31eecb9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-6e58dfc936b99b9f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-4e4e60f90a74b6cc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-70f576b03b6e3d07.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-c28e798b24cde9b6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-2727f55a7e591e11.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-decd32927628981b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) diff --git "a/Git/Git\345\267\245\344\275\234:\351\235\242\350\257\225\345\277\205\347\237\245\345\277\205\344\274\232.md" "b/Git/Git\345\267\245\344\275\234:\351\235\242\350\257\225\345\277\205\347\237\245\345\277\205\344\274\232.md" index 3abed05a64..a8d61ba449 100644 --- "a/Git/Git\345\267\245\344\275\234:\351\235\242\350\257\225\345\277\205\347\237\245\345\277\205\344\274\232.md" +++ "b/Git/Git\345\267\245\344\275\234:\351\235\242\350\257\225\345\277\205\347\237\245\345\277\205\344\274\232.md" @@ -234,8 +234,8 @@ git://协议较为快速和有效,但是有时必须使用http协议,比如你 在默认情况下,Git会把"Git URL"里最后一级目录名的'.git'的后辍去掉,做为新克隆(clone)项目的目录名: (例如. git clone [http://git.kernel.org/linux/kernel/git/torvalds/linux-2.6.git](https://link.jianshu.com?t=http://git.kernel.org/linux/kernel/git/torvalds/linux-2.6.git) 会建立一个目录叫'linux-2.6') 另外,如果访问一个Git URL需要用法名和密码,可以在Git URL前加上用户名,并在它们之间加上@符合以表示分割,然后执行git clone命令,git会提示你输入密码。 示例 -git clone [v_JavaEdge@http://www.kernel.org/pub/scm/git/git.git](https://link.jianshu.com?t=http://v_JavaEdge@http://www.kernel.org/pub/scm/git/git.git) -这样将以作为[v_JavaEdge](https://link.jianshu.com?t=http://v_JavaEdge)用户名访问[http://www.kernel.org/pub/scm/git/git.git](https://link.jianshu.com?t=http://www.kernel.org/pub/scm/git/git.git),然后按回车键执行git clone命令,git会提示你输入密码。 +git clone [v_shishusheng@http://www.kernel.org/pub/scm/git/git.git](https://link.jianshu.com?t=http://v_shishusheng@http://www.kernel.org/pub/scm/git/git.git) +这样将以作为[v_shishusheng](https://link.jianshu.com?t=http://v_shishusheng)用户名访问[http://www.kernel.org/pub/scm/git/git.git](https://link.jianshu.com?t=http://www.kernel.org/pub/scm/git/git.git),然后按回车键执行git clone命令,git会提示你输入密码。 另外,我们可以通过-b 来指定要克隆的分支名,比如 $ git clone -b master2 ../server . 表示克隆名为master2的这个分支,如果省略-b 表示克隆master分支。 @@ -250,7 +250,7 @@ $ git clone https://github.com/usern/repositoryname.git ```bash git rm -r -n --cached 文件/文件夹名称 ``` -![](https://img-blog.csdnimg.cn/2020030909581747.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/2020030909581747.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) > 加上 -n 参数,执行命令时,不会删除任何文件,而是展示此命令要删除的文件列表预览 - 确认后删除 @@ -258,18 +258,18 @@ git rm -r -n --cached 文件/文件夹名称 ```bash git rm -r --cached 文件/文件夹名称 ``` -![](https://img-blog.csdnimg.cn/20200309100251449.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20200309100251449.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) - 提交到本地并推送到远程服务器 ```bash git commit -m "提交说明" ``` -![](https://img-blog.csdnimg.cn/20200309100358480.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/20200309100358480.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) ```bash git push origin master ``` -![](https://img-blog.csdnimg.cn/2020030910043664.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) +![](https://img-blog.csdnimg.cn/2020030910043664.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) ## 2.4 分支 ### 2.4.1 创建分支 ```bash diff --git "a/Git/Git\347\211\210\346\234\254\345\233\236\351\200\200\346\226\271\346\263\225\350\256\272(\345\217\257\350\203\275\350\247\243\345\206\263\344\275\240101%\351\201\207\345\210\260\347\232\204Git\347\211\210\346\234\254\351\227\256\351\242\230).md" "b/Git/Git\347\211\210\346\234\254\345\233\236\351\200\200\346\226\271\346\263\225\350\256\272(\345\217\257\350\203\275\350\247\243\345\206\263\344\275\240101%\351\201\207\345\210\260\347\232\204Git\347\211\210\346\234\254\351\227\256\351\242\230).md" deleted file mode 100644 index 8d5f27d5c5..0000000000 --- "a/Git/Git\347\211\210\346\234\254\345\233\236\351\200\200\346\226\271\346\263\225\350\256\272(\345\217\257\350\203\275\350\247\243\345\206\263\344\275\240101%\351\201\207\345\210\260\347\232\204Git\347\211\210\346\234\254\351\227\256\351\242\230).md" +++ /dev/null @@ -1,120 +0,0 @@ -# 1 本地回退 -你在本地做了错误的 commit,先找到要回退的版本的`commit id`: - -```bash -git reflog -``` -![](https://img-blog.csdnimg.cn/20200414142250436.png) -接着回退版本: - -```bash -git reset --hard cac0 -``` -> cac0就是你要回退的版本的`commit id`的前面几位 - -回退到某次提交。回退到的指定提交以后的提交都会从提交日志上消失。 - -> 工作区和暂存区的内容都会被重置到指定提交的时候,如果不加`--hard`则只移动`HEAD`指针,不影响工作区和暂存区的内容。 - -结合`git reflog`找回提交日志上看不到的版本历史,撤回某次操作前的状态 -这个方法可以对你的回退操作进行回退,因为这时候`git log`已经找不到历史提交的hash值了。 - -# 2 远程回退 -## 2.1 回退自己的远程分支 -你的错误commit已经推送到远程分支,就需要回滚远程分支。 -- 首先要回退本地分支: - -```bash -git reflog -git reset --hard cac0 -``` -![](https://img-blog.csdnimg.cn/20200414142459436.png) -- 由于本地分支回滚后,版本将落后远程分支,必须使用强制推送覆盖远程分支,否则后面将无法推送到远程分支。 - -```bash -git push -f -``` -![](https://img-blog.csdnimg.cn/20200414142539953.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) -- 注意修正为`git push -f origin branch_name` -![](https://img-blog.csdnimg.cn/20200414142624784.png) - -## 2.2 回退公共远程分支 -如果你回退公共远程分支,把别人的提交给丢掉了怎么办? -> 本人毕业时在前东家 hw 经常干的蠢事。 - -### 分析 -假如你的远程master分支情况是这样的: - -```bash -A1–A2–B1 -``` -> A、B分别代表两个人 -> A1、A2、B1代表各自的提交 -> 所有人的本地分支都已经更新到最新版本,和远程分支一致 - -这时发现A2这次commit有误,你用reset回滚远程分支master到A1,那么理想状态是你的同事一拉代码git pull,他们的master分支也回滚了 -然而现实却是,你的同事会看到下面的提示: - -```bash -$ git status -On branch master -Your branch is ahead of 'origin/master' by 2 commits. - (use "git push" to publish your local commits) -nothing to commit, working directory clean -``` -也就是说,你的同事的分支并没有主动回退,而是比远程分支超前了两次提交,因为远程分支回退了。 -不幸的是,现实中,我们经常遇到的都是猪一样的队友,他们一看到下面提示: - -```bash -$ git status -On branch master -Your branch is ahead of 'origin/master' by 2 commits. - (use "git push" to publish your local commits) -nothing to commit, working directory clean -``` -就习惯性的git push一下,或者他们直接用的SourceTree这样的图形界面工具,一看到界面上显示的是推送的提示就直接点了推送按钮,卧槽,辛辛苦苦回滚的版本就这样轻松的被你猪一样的队友给还原了,所以,只要有一个队友push之后,远程master又变成了: - -```bash -A1 – A2 – B1 -``` - -这就是分布式,每个人都有副本。 - -用另外一种方法来回退版本。 - -# 3 公共远程回退 -使用git reset回退公共远程分支的版本后,需要其他所有人手动用远程master分支覆盖本地master分支,显然,这不是优雅的回退方法。 - -```bash -git revert HEAD //撤销最近一次提交 -git revert HEAD~1 //撤销上上次的提交,注意:数字从0开始 -git revert 0ffaacc //撤销0ffaacc这次提交 -``` -git revert 命令意思是撤销某次提交。它会产生一个新的提交,虽然代码回退了,但是版本依然是向前的,所以,当你用revert回退之后,所有人pull之后,他们的代码也自动的回退了。但是,要注意以下几点: - -- revert 是撤销一次提交,所以后面的commit id是你需要回滚到的版本的前一次提交 -- 使用revert HEAD是撤销最近的一次提交,如果你最近一次提交是用revert命令产生的,那么你再执行一次,就相当于撤销了上次的撤销操作,换句话说,你连续执行两次revert HEAD命令,就跟没执行是一样的 -- 使用revert HEAD~1 表示撤销最近2次提交,这个数字是从0开始的,如果你之前撤销过产生了commi id,那么也会计算在内的 -- 如果使用 revert 撤销的不是最近一次提交,那么一定会有代码冲突,需要你合并代码,合并代码只需要把当前的代码全部去掉,保留之前版本的代码就可以了 -- git revert 命令的好处就是不会丢掉别人的提交,即使你撤销后覆盖了别人的提交,他更新代码后,可以在本地用 reset 向前回滚,找到自己的代码,然后拉一下分支,再回来合并上去就可以找回被你覆盖的提交了。 - -# 4 revert 合并代码,解决冲突 -使用revert命令,如果不是撤销的最近一次提交,那么一定会有冲突,如下所示: - -```bash -<<<<<<< HEAD -全部清空 -第一次提交 -======= -全部清空 ->>>>>>> parent of c24cde7... 全部清空 -``` -解决冲突很简单,因为我们只想回到某次提交,因此需要把当前最新的代码去掉即可,也就是HEAD标记的代码: - -```bash -<<<<<<< HEAD -全部清空 -第一次提交 -======= -``` -把上面部分代码去掉就可以了,然后再提交一次代码就可以解决冲突了。 \ No newline at end of file diff --git "a/Go/Go-\350\257\255\350\250\200\345\256\236\346\210\230(\344\270\200)-\344\273\213\347\273\215.md" "b/Go/Go-\350\257\255\350\250\200\345\256\236\346\210\230(\344\270\200)-\344\273\213\347\273\215.md" new file mode 100644 index 0000000000..753a7bc490 --- /dev/null +++ "b/Go/Go-\350\257\255\350\250\200\345\256\236\346\210\230(\344\270\200)-\344\273\213\347\273\215.md" @@ -0,0 +1,39 @@ +# 1 导读 +![](https://upload-images.jianshu.io/upload_images/4685968-8067e131b74cf883.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-05849479768d9a59.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-a94edab973aa9a90.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-5646f82218592cee.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-a9a6d876127e4602.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-5e4078206028a1ba.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-4d073cfef6322d14.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-a7b692cf53c4b34a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-830265d690b8bc04.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-1b2ed4e4ee24d0be.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-f6ce781b6a033ef9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-e64d8a1c7a7105d1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-0cd7ec2892ae20b2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-31a8ec633fce04f5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-06d96554e4264a78.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + ![](https://upload-images.jianshu.io/upload_images/4685968-a4183e137d911e18.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-da6ffcadddb67c63.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 2 安装与环境 +- 官网 +https://golang.org/ +![](https://upload-images.jianshu.io/upload_images/4685968-ba6f181f533c7958.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- 中文官网 +https://studygolang.com/ +![](https://upload-images.jianshu.io/upload_images/4685968-a749c9fa21a7d644.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-48407483cfcb14f9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-18d11b08613794a0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +# X 交流学习 +![](https://upload-images.jianshu.io/upload_images/16782311-8d7acde57fdce062.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T) + + +## [博客](https://blog.csdn.net/qq_33589510) + + + +## [Github](https://github.com/Wasabi1234) diff --git "a/Go/Go\345\256\236\346\210\230\344\272\221\345\255\230\345\202\250(\344\270\211) - \346\214\201\344\271\205\345\214\226\344\272\221\346\226\207\344\273\266\344\277\241\346\201\257.md" "b/Go/Go\345\256\236\346\210\230\344\272\221\345\255\230\345\202\250(\344\270\211) - \346\214\201\344\271\205\345\214\226\344\272\221\346\226\207\344\273\266\344\277\241\346\201\257.md" new file mode 100644 index 0000000000..f664fcc153 --- /dev/null +++ "b/Go/Go\345\256\236\346\210\230\344\272\221\345\255\230\345\202\250(\344\270\211) - \346\214\201\344\271\205\345\214\226\344\272\221\346\226\207\344\273\266\344\277\241\346\201\257.md" @@ -0,0 +1,71 @@ +# 0 [相关源码](https://github.com/Wasabi1234/Go-Cloud-Store) + +# 1 MySQL基础 +## 简介 +为避免每次重启系统从零开始的问题,需要将数据持久化. +![](https://upload-images.jianshu.io/upload_images/16782311-d59a5f2a87c4a342.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +## 服务架构变迁 +![](https://upload-images.jianshu.io/upload_images/16782311-7888a1be823355ef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +## 安装配置 +### 安装模式 +- 单点 +- 主从 + +![](https://upload-images.jianshu.io/upload_images/16782311-a588e4e352f3b543.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +- 多主 + +# 2 文件表设计 +- 新建数据库 +![](https://upload-images.jianshu.io/upload_images/16782311-93d994cc266e86d3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +- 新建文件表 +![](https://upload-images.jianshu.io/upload_images/16782311-03abc93755e61f19.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +## 分库分表 +### 水平分表 +![](https://upload-images.jianshu.io/upload_images/16782311-1cd30450d73cbafa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/16782311-1dcc2a59e1813009.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +## Golang 操作MySQL +![](https://upload-images.jianshu.io/upload_images/16782311-e96ba8777c17421c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +# 4 持久化元数据到文件表 +![](https://upload-images.jianshu.io/upload_images/16782311-c495ccb60d234cd8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +![](https://upload-images.jianshu.io/upload_images/16782311-86b0de25058fd99a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +# 5 从文件表中获取元数据 +- file.go +![](https://upload-images.jianshu.io/upload_images/16782311-df37fab7de1abb14.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- handler.go +![](https://upload-images.jianshu.io/upload_images/16782311-f716d1c342913724.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- filemeta.go +![](https://upload-images.jianshu.io/upload_images/16782311-a58f2e525809ead1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- test over! +![](https://upload-images.jianshu.io/upload_images/16782311-2dd492a053782a40.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +# 6 Go - MySQL使用小结 +## 通过sqI.DB来管理数据库连接对象 +## 通过sql.Open来创建协程安全的sql.DB对象 +## 优先使用Prepared Statement + +### 1 MySQL特点与应用场景 +### 2 主从架构与文件表设计逻辑 +### 3 Golang与MySQL的亲密接触 + +# X 交流学习 +![](https://upload-images.jianshu.io/upload_images/16782311-8d7acde57fdce062.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T) + + +## [博客](https://blog.csdn.net/qq_33589510) + + + +## [Github](https://github.com/Wasabi1234) + + diff --git "a/Go/Go\345\256\236\346\210\230\344\272\221\345\255\230\345\202\250(\345\233\233) - \350\264\246\345\217\267\347\263\273\347\273\237\344\270\216\351\211\264\346\235\203.md" "b/Go/Go\345\256\236\346\210\230\344\272\221\345\255\230\345\202\250(\345\233\233) - \350\264\246\345\217\267\347\263\273\347\273\237\344\270\216\351\211\264\346\235\203.md" new file mode 100644 index 0000000000..b55cb016fc --- /dev/null +++ "b/Go/Go\345\256\236\346\210\230\344\272\221\345\255\230\345\202\250(\345\233\233) - \350\264\246\345\217\267\347\263\273\347\273\237\344\270\216\351\211\264\346\235\203.md" @@ -0,0 +1,63 @@ +# 0 [相关源码](https://github.com/Wasabi1234/Go-Cloud-Store) +修改代码部分直接查看github commit历史即可! + +加入用户系统后架构升级说明; 快速实现用户注册/登录/信息查询功能; 快速实现用户资源隔离存储及安全鉴权功能。 + +# 1 帐号系统介绍与用户表设计 + +## 1.1 账号系统的功能 +支持用户注册/登录 +支持用户Session鉴权 +用户数据资源隔离 + +![在系统架构中的地位](https://upload-images.jianshu.io/upload_images/16782311-2392dcd62cfa1819.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +## 1.2 MySQL用户表的设计 +![](https://upload-images.jianshu.io/upload_images/16782311-228a0c7eca1e1093.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + + +# 2 实现用户注册接口 +- 注册页面 +![](https://upload-images.jianshu.io/upload_images/16782311-cdd0eba9d8ff9d87.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +- 成功入库 +![](https://upload-images.jianshu.io/upload_images/16782311-4c0e6e59e6c0c98e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +# 3 实现用户登录接口 +![](https://upload-images.jianshu.io/upload_images/16782311-bbe121950f743239.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +- 添加路由规则,注意静态路由 +![](https://upload-images.jianshu.io/upload_images/16782311-40dc32688dcdb765.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +- 登录界面 +![](https://upload-images.jianshu.io/upload_images/16782311-29e162e74da73cde.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +# 4 实现用户信息查询接口 +除了注册登录,其他接口都需要验证token信息 + +- 登录后的重定向界面 +![](https://upload-images.jianshu.io/upload_images/16782311-838a12d892b7c542.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +# 5 接口梳理小结 +## 用户注册 +![](https://upload-images.jianshu.io/upload_images/16782311-71e82a1ae3a10986.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +## 用户登录 +![](https://upload-images.jianshu.io/upload_images/16782311-c473e82fd21b0783.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +## 用户信息查询 +![](https://upload-images.jianshu.io/upload_images/16782311-53f80ac192de5dc7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +## 拦截验证token +业务接口需要验证请求合法性 +![](https://upload-images.jianshu.io/upload_images/16782311-b2a446b19d8fae0c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + +# 6 编码实战:快速实现访问鉴权接口+小结 + +## 6.2 小结 +### MySQL 用户表的设计 +### 注册/登录/查询接口的实现 +### 验证Token的拦截器 + diff --git "a/Go/Go\350\257\255\350\250\200\345\256\236\346\210\230(\344\270\211)---\345\206\205\345\273\272\345\256\271\345\231\250.md" "b/Go/Go\350\257\255\350\250\200\345\256\236\346\210\230(\344\270\211)---\345\206\205\345\273\272\345\256\271\345\231\250.md" new file mode 100644 index 0000000000..e0b5d9e31f --- /dev/null +++ "b/Go/Go\350\257\255\350\250\200\345\256\236\346\210\230(\344\270\211)---\345\206\205\345\273\272\345\256\271\345\231\250.md" @@ -0,0 +1,90 @@ +# 1 数组 +数组是单一类型元素的编号序列,该单一类型称为元素类型。元素的数量称为长度且为非负数。 + +>数组类型 = "[" 数组长度 "]" 元素类型 . +数组长度 = 表达式 . +元素类型 = 类型 . + +长度是数组类型的一部分,其求值结果必须可表示为 `int` 类型的非负 +数组 `a` 的长度可使用内建函数 [`len`]获取, 其元素可通过整数[下标]0 到 `len(a)-1` 寻址 + 数组类型总是一维的,但可组合构成多维的类型。 +``` +[32]byte +[2*N] struct { x, y int32 } +[1000]*float64 +[3][5]int +[2][2][2]float64 // 等价于[2]([2]([2]float64)) +``` +## 构造数组 +![](https://upload-images.jianshu.io/upload_images/4685968-21b6511cb0771db4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![运行结果](https://upload-images.jianshu.io/upload_images/4685968-c93e33633eec5e22.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## 遍历 +- 首先想到这样 +![](https://upload-images.jianshu.io/upload_images/4685968-c59b16f93675b8a1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- 但是通常使用 range关键字遍历 +![](https://upload-images.jianshu.io/upload_images/4685968-30c512bd06035fb5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-621aaf1e5fa8cdb3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- 为什么要用 range +![](https://upload-images.jianshu.io/upload_images/4685968-8eae12986ac725c9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## 数组是值类型 +![](https://upload-images.jianshu.io/upload_images/4685968-2befddde7ce6ce8c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-f8b18c079a3e1e52.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![运行结果](https://upload-images.jianshu.io/upload_images/4685968-a2a3ac6f37c0b7b4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-f137f07f12479533.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +要想传递引用可修改数组,可传指针参数 +![](https://upload-images.jianshu.io/upload_images/4685968-3130ca70a71a94d7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 2 切片 (Slice) 的概念 +切片是数组连续段的引用及包含此数组的元素的编号序列。 切片类型表示元素类型为数组的所有切片的集。未初始化切片的值为 nil。 + +>切片类型 = "[" "]" 元素类型 . + +类似于数组,切片是可索引的且拥有一个长度 +切片 `s` 的长度可通过内建函数 [`len`]获取;不同于数组的是,切片可`在执行过程中被改变`, 其元素可通过整数(§[下标]) 0 到 `len(s)-1` 寻址。 给定元素的切片下标可能小于它在其基本数组中的下标。 + +切片一旦初始化,就总是伴随着一个包含其元素的基本数组。 +因此,切片与其数组及其它本数组的切片共享存储 + 与此相反,不同的数组总是表示其不同的存储 + +切片的基本数组可扩展其切片的结尾 +- **容量** 是该扩展的量度 +它是切片的长度和切片往后数组的长度之和;长度达到其容量的切片可通过从原切片 (§[Slices](http://docscn.studygolang.com/ref/spec.old#Slices))‘切下’一个新的来创建。 切片 `a` 的容量可使用内建函数 [`cap(a)`](http://docscn.studygolang.com/ref/spec.old#%E9%95%BF%E5%BA%A6%E4%B8%8E%E5%AE%B9%E9%87%8F) 获取。 + +给定元素类型 `T` 的一个新的,已初始化的切片值使用内建函数 [`make`](http://docscn.studygolang.com/ref/spec.old#%E5%88%9B%E5%BB%BA%E5%88%87%E7%89%87%E3%80%81%E6%98%A0%E5%B0%84%E4%B8%8E%E4%BF%A1%E9%81%93)创建, 它需要一个切片类型和指定其长度与可选容量的形参: +``` +make([]T, length) +make([]T, length, capacity) +``` +调用 make将分配一个被返回的切片值所引用的,新的、隐藏的数组。即,执行 +``` +make([]T, length, capacity) +``` +产生切片与分配数组后再对其进行切片相同,因此这两个例子的结果为相同的切片: +``` +make([]int, 50, 100) +new([100]int)[0:50] +``` +类似于数组,切片总是一维的,但可组合构造更高维的对象 +元素为数组的数组,根据其构造,其内部数组的长度始终相同 +然而元素为切片的切片(或元素为数组的切片),其长度会动态地改变 +此外,其内部的切片必须单独地(通过 make)分配 + + +## 基本定义 +![](https://upload-images.jianshu.io/upload_images/4685968-ead1ce9561545a4a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-e41c5f608c014cc9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![运行结果](https://upload-images.jianshu.io/upload_images/4685968-12c9801aca625402.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## 运行可修改 +![](https://upload-images.jianshu.io/upload_images/4685968-2c896e39b6aea368.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +# X 交流学习 +![](https://upload-images.jianshu.io/upload_images/16782311-8d7acde57fdce062.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T) + + +## [博客](https://blog.csdn.net/qq_33589510) + + + +## [Github](https://github.com/Wasabi1234) + diff --git "a/Go/Go\350\257\255\350\250\200\345\256\236\346\210\230(\344\272\214)---\345\237\272\347\241\200\350\257\255\346\263\225.md" "b/Go/Go\350\257\255\350\250\200\345\256\236\346\210\230(\344\272\214)---\345\237\272\347\241\200\350\257\255\346\263\225.md" new file mode 100644 index 0000000000..be174d2255 --- /dev/null +++ "b/Go/Go\350\257\255\350\250\200\345\256\236\346\210\230(\344\272\214)---\345\237\272\347\241\200\350\257\255\346\263\225.md" @@ -0,0 +1,130 @@ +# 1 变量定义 +![](https://upload-images.jianshu.io/upload_images/4685968-dcc545a0c25312cd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +var 语句定义了一个变量的列表;跟函数的参数列表一样,类型在后面。 +就像在这个例子中看到的一样,`var` 语句可以定义在包或函数级别。 + +![](https://upload-images.jianshu.io/upload_images/4685968-c761b0b8887cb33b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-24cbfe3978b0a256.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +变量一旦定义了,就必须使用到,否则报错 +![](https://upload-images.jianshu.io/upload_images/4685968-d9849076af8bd39e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +根据变量内容即可判断变量类型,无须再显式声明 +![](https://upload-images.jianshu.io/upload_images/4685968-c033e5c0f088b243.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-49d94f156dd5b46e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-399b556331d40c82.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- 总结 +![](https://upload-images.jianshu.io/upload_images/4685968-159e6852758c709a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-d1adadf8cea48117.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 2 内置变量类型 +![](https://upload-images.jianshu.io/upload_images/4685968-ab2098c4512d04c3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## 复数机制 +![](https://upload-images.jianshu.io/upload_images/4685968-a3af538c25f3a461.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +验证欧拉公式 +![](https://upload-images.jianshu.io/upload_images/4685968-e316fa9f439a6702.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## 强制类型转换 +![sqrt源码](https://upload-images.jianshu.io/upload_images/4685968-eb4bae006a5dee01.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +传参 int 直接报错 +![](https://upload-images.jianshu.io/upload_images/4685968-01e1a1da01217af9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +因为没有隐式转换,只有显式的强制类型转换 +![](https://upload-images.jianshu.io/upload_images/4685968-c22fda2c24ab0e0d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-82cd955ce6fe0838.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 3 常量与枚举 +![](https://upload-images.jianshu.io/upload_images/4685968-8de62a5eb0bf7618.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![常量的定义](https://upload-images.jianshu.io/upload_images/4685968-793b802cb1aeb54d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-f5bd95e7786dbed5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +使用 iota 在 const 块定义中实现自增值 +![iota = 0](https://upload-images.jianshu.io/upload_images/4685968-727bc25d5d409b51.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-26d00599bd2cab78.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![内存进制大小数值](https://upload-images.jianshu.io/upload_images/4685968-d052ac5984efae3f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![使用常量定义枚举类型](https://upload-images.jianshu.io/upload_images/4685968-61754fdce22e95a2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## 常量定义要点回顾 +![](https://upload-images.jianshu.io/upload_images/4685968-38af7e8d959d8bcf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 4 条件语句 +## if +"If"语句根据一个布尔表达式的值指定两个分支的条件来执行。 若该表达式求值为true,则执行"if"分支,否则执行"else"分支 +``` +If语句 = "if" [ 简单语句 ";" ] 表达式 块 [ "else" ( If语句 | 块 ) ] . +``` +``` +if x > max { + x = max +} +``` +![](https://upload-images.jianshu.io/upload_images/4685968-1030048192aa8c60.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +工具方法,该方法有两个返回值哦! +![](https://upload-images.jianshu.io/upload_images/4685968-d455cae29784ba11.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![文件位置](https://upload-images.jianshu.io/upload_images/4685968-0f10aeb7c16461b1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-b3966cbf6a39a7dc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-023df8ffbbbdd0a6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## switch +"Switch"语句提供多路执行。表达式或类型说明符与"switch"中的"cases"相比较从而决定执行哪一分支。 +``` +Switch语句 = 表达式选择语句 | 类型选择语句 +``` +![](https://upload-images.jianshu.io/upload_images/4685968-43a05a5d91b99c5b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-c1f3b76493c2ba18.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-aa6666c6a42f0616.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-84520d3418ef0700.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-34463d922ce507b7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## 循环 +![](https://upload-images.jianshu.io/upload_images/4685968-afd40ac5b52b975d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-3994aac2770e4490.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-0a56457977716208.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-ead89acd1f82240d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-12979092e66a0dc5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-4c16122e1ce2a270.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-db220303bd7260ea.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-52430de72c205b31.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-28865bac63f52d6b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 5 函数 +- 参数 参数类型, +![](https://upload-images.jianshu.io/upload_images/4685968-7ede8751dc54c5fc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-341698877c01a08c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- Go的函数可以返回多个值 +![多返回值](https://upload-images.jianshu.io/upload_images/4685968-822efa5637e6f0b3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- 若参数过多,这样并不是一种清晰的写法 +![](https://upload-images.jianshu.io/upload_images/4685968-fdd54a3889db81c3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +建议如下最佳实践! +![](https://upload-images.jianshu.io/upload_images/4685968-704fe54da0cece27.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- 多返回值函数,只接收一个返回值 +![](https://upload-images.jianshu.io/upload_images/4685968-c582a9c2b3fb2933.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- 对于多返回值参数,一般可用于返回`值 + error` +![](https://upload-images.jianshu.io/upload_images/4685968-c850ba0c797cd2cd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +比如这样,控制台输出就很难看,因为error直接程序中断了 +![](https://upload-images.jianshu.io/upload_images/4685968-88ec2aed632194da.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-bf666abd6bf34d57.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-d3e2f0d88b15b07a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-c5d32e7246e69988.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +- 亦可写成匿名函数 +![](https://upload-images.jianshu.io/upload_images/4685968-212c829f3b098030.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-50f02de0b6e82784.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +无默认参数,有可变参数,可变参数类型其实是[] type 类型 +![](https://upload-images.jianshu.io/upload_images/4685968-98976a75ee0cbe25.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-91c8fb4da3fff9f0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-f516bfbbed050cf0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +# 6 指针 +![](https://upload-images.jianshu.io/upload_images/4685968-48f45af0ade89920.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +## 6.1 参数传递 +![](https://upload-images.jianshu.io/upload_images/4685968-fae13000a0e0e46d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-cbdaefbf665f5318.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-433a6d23da7d5e90.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![1](https://upload-images.jianshu.io/upload_images/4685968-878f29183b7cc5f2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![2](https://upload-images.jianshu.io/upload_images/4685968-37024340d43a58eb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![3](https://upload-images.jianshu.io/upload_images/4685968-e03a16755081e3f5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-26869ed2467509bd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +![](https://upload-images.jianshu.io/upload_images/4685968-c7b7a157d43769e0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +当然啦,还有最简单的 +![](https://upload-images.jianshu.io/upload_images/4685968-b0283ef0cae97b30.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + +# X 交流学习 +![](https://upload-images.jianshu.io/upload_images/16782311-8d7acde57fdce062.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T) + + +## [博客](https://blog.csdn.net/qq_33589510) + + + +## [Github](https://github.com/Wasabi1234) + diff --git "a/Go/Go\350\257\255\350\250\200\347\232\204\351\235\242\345\220\221\345\257\271\350\261\241.md" "b/Go/Go\350\257\255\350\250\200\347\232\204\351\235\242\345\220\221\345\257\271\350\261\241.md" new file mode 100644 index 0000000000..20b20225bd --- /dev/null +++ "b/Go/Go\350\257\255\350\250\200\347\232\204\351\235\242\345\220\221\345\257\271\350\261\241.md" @@ -0,0 +1,33 @@ +go语言仅支持封装,不支持继承和多态。 +go语言没有class,只有struct。 + +# 结构的定义 +![](https://img-blog.csdnimg.cn/20201223155119118.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) +- go 语言即使是指针,不像 C语言使用`->`也可一直使用`.`引用下去![](https://img-blog.csdnimg.cn/20201223192039227.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) +- go 没有构造器,但可使用工厂函数 +![](https://img-blog.csdnimg.cn/20201223193155572.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) +那么这到底创建在了堆还是栈呢? +不需要知道。如果返回了没人用就在栈上 +如果返回了有人用,就在堆,并参与到 GC。 +所以没必要知道,编译器自己都知道。 + +# 参数前后区别 +- 参数定义在函数名前后有啥区别呢? +![](https://img-blog.csdnimg.cn/20201223194005570.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) +- 使用区别 +![](https://img-blog.csdnimg.cn/20201223194043874.png) +Go都是值传递,记住了,和 Java 一样。 + +# 使用指针作为方法接收者 +![](https://img-blog.csdnimg.cn/20201223200025882.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) +- 只有使用指针才可以改变结构内容 +- nil 指针也可调用方法(Go 很特殊) + +# 值接收者 V.S 指针接收者 +- 要改变内容必须使用指针接收者 +- 结构过大也考虑使用指针接收者 +- 一致性:如有指针接收者,最好都是指针接收者 + + +值接收者才是go语言特有的! +值/指针接收者均可接收值/指针 \ No newline at end of file diff --git "a/Go/\360\237\247\247\347\263\273\347\273\237/Go\345\256\236\346\210\230\360\237\247\247\347\263\273\347\273\237(\344\270\211) - \346\236\266\346\236\204\350\256\276\350\256\241.md" "b/Go/\360\237\247\247\347\263\273\347\273\237/Go\345\256\236\346\210\230\360\237\247\247\347\263\273\347\273\237(\344\270\211) - \346\236\266\346\236\204\350\256\276\350\256\241.md" new file mode 100644 index 0000000000..f701665b6c --- /dev/null +++ "b/Go/\360\237\247\247\347\263\273\347\273\237/Go\345\256\236\346\210\230\360\237\247\247\347\263\273\347\273\237(\344\270\211) - \346\236\266\346\236\204\350\256\276\350\256\241.md" @@ -0,0 +1,226 @@ +# 1 代码架构的意义 +![](https://img-blog.csdnimg.cn/20190621180825370.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) + +代码架构就是详细设计中的核心内容! + +## 1.1 代码架构承上启下,决定软件质量 +◆ 承上 +说明业务逻辑和业务领域模型 +◆ 本身 +保证代码有更好的可读性和可维护性、可扩展性 +◆ 启下 +承载代码运行的硬件部署架构 + +# 2 代码架构的操作 +## 2.1 业务逻辑表达 +向上沟通,提供交互入口 +## 2.2 自身业务逻辑及技术实现 +向下沟通,保存运行状态 + +# 3 代码架构的设计 + +先看一下DDD和分层架构的相关知识。 +## 3.1 DDD +DDD(Domain Driven Design,领域驱动设计)作为一种软件开发方法,它可以帮助我们设计高质量的软件模型。在正确实现的情况下,我们通过DDD完成的设计恰恰就是软件的工作方式。 + +UL(Ubiquitous Language,通用语言)是团队共享的语言,是DDD中最具威力的特性之一。不管你在团队中的角色如何,只要你是团队的一员,你都将使用UL。由于UL的重要性,所以需要让每个概念在各自的上下文中是清晰无歧义的,于是DDD在战略设计上提出了模式BC(Bounded Context,限界上下文)。UL和BC同时构成了DDD的两大支柱,并且它们是相辅相成的,即UL都有其确定的上下文含义,而BC中的每个概念都有唯一的含义。 + +一个业务领域划分成若干个BC,它们之间通过Context Map进行集成。BC是一个显式的边界,领域模型便存在于这个边界之内。领域模型是关于某个特定业务领域的软件模型。通常,领域模型通过对象模型来实现,这些对象同时包含了数据和行为,并且表达了准确的业务含义。 + +从广义上来讲,领域即是一个组织所做的事情以及其中所包含的一切,表示整个业务系统。 +由于“领域模型”包含了“领域”这个词,我们可能会认为应该为整个业务系统创建一个单一的、内聚的和全功能式的模型。然而,这并不是我们使用DDD的目标。正好相反,领域模型存在于BC内。 +在微服务架构实践中,人们大量地使用了DDD中的概念和技术: + +微服务中应该首先建立UL,然后再讨论领域模型。 +一个微服务最大不要超过一个BC,否则微服务内会存在有歧义的领域概念。 +一个微服务最小不要小于一个聚合,否则会引入分布式事务的复杂度。 +微服务的划分过程类似于BC的划分过程,每个微服务都有一个领域模型。 +微服务间的集成可以通过Context Map来完成,比如ACL(Anticorruption Layer,防腐层)。 +微服务间最好采用Domain Event(领域事件)来进行交互,使得微服务可以保持松耦合。 +. + +下面介绍最为流行的分层代码架构 +## 3.1 分层架构简介 + + +分层架构的一个重要原则是每层只能与位于其下方的层发生耦合。 +分层架构可以简单分为两种 +- 严格分层架构 +某层只能与位于其直接下方的层发生耦合 +- 松散分层架构 +松散分层架构中,则允许某层与它的任意下方层发生耦合。 + +分层架构的好处是显而易见的。首先,由于层间松散的耦合关系,使得我们可以专注于本层的设计,而不必关心其他层的设计,也不必担心自己的设计会影响其它层,对提高软件质量大有裨益。其次,分层架构使得程序结构清晰,升级和维护都变得十分容易,更改某层的具体实现代码,只要本层的接口保持稳定,其他层可以不必修改。即使本层的接口发生变化,也只影响相邻的上层,修改工作量小且错误可以控制,不会带来意外的风险。 +要保持程序分层架构的优点,就必须坚持层间的松散耦合关系。设计程序时,应先划分出可能的层次,以及此层次提供的接口和需要的接口。设计某层时,应尽量保持层间的隔离,仅使用下层提供的接口。 + +## 3.2 分层架构的优点 +单一职责 +高内聚低耦合 +提高复用性 + +开发人员可以只关注整个结构中的某一层。 +可以很容易的用新的实现来替换原有层次的实现。 +可以降低层与层之间的依赖。 +有利于标准化。 +利于各层逻辑的复用。 + + +## 3.3 分层架构的缺陷 +“金无足赤,人无完人”,分层架构也不可避免具有一些缺陷: + +降低了系统的性能。这是显然的,因为增加了中间层,不过可以通过缓存机制来改善。 +可能会导致级联的修改。这种修改尤其体现在自上而下的方向,不过可以通过依赖倒置来改善。 + +在每个BC中为了凸显领域模型,DDD中提出了分层架构模式 + +最基本的为三层架构 +## 3.4 三层架构 +表现层 +业务逻辑层 +数据持久层 + +但是职责定义并不明确,耦合度高 + +所以我们项目使用四层逻辑分层架构 +## 3.5 逻辑分层架构 +![](https://img-blog.csdnimg.cn/20190621235019157.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) +- User Interface - 用户接口 +人机交互,为用户界面层(或表示层),负责向用户显示信息和解释用户命令。这里指的用户可以是另一个计算机系统,不一定是使用用户界面的人。 + +- Application - 应用层 +定义软件要完成的任务,并且指挥表达领域概念的对象来解决问题。这一层所负责的工作对业务来说意义重大,也是与其它系统的应用层进行交互的必要渠道。应用层要尽量简单,不包含业务规则或者知识,而只为下一层中的领域对象协调任务,分配工作,使它们互相协作。它没有反映业务情况的状态,但是却可以具有另外一种状态,为用户或程序显示某个任务的进度。 + +- Domain - 领域层(或模型层) +表达业务概念,业务状态信息以及业务规则。尽管保存业务状态的技术细节是由基础设施层实现的,但是反映业务情况的状态是由本层控制并且使用的。领域层是业务软件的核心,领域模型位于这一层。 + +- Infrastructure - 基础实施层 +向其他层提供通用的技术能力:为应用层传递消息,为领域层提供持久化机制,为用户界面层绘制屏幕组件,等等。基础设施层还能够通过架构框架来支持四个层次间的交互模式。 + + +传统的四层架构都是限定型松散分层架构,即Infrastructure层的任意上层都可以访问该层(“L”型),而其它层遵守严格分层架构 + +在四层架构模式的实践中,对于分层的本地化定义主要为: +- User Interface层主要是Restful消息处理,配置文件解析,等等。 +- Application层主要是多进程管理及调度,多线程管理及调度,多协程调度和状态机管理,等等。 +- Domain层主要是领域模型的实现,包括领域对象的确立,这些对象的生命周期管理及关系,领域服务的定义,领域事件的发布,等等。 +- Infrastructure层主要是业务平台,编程框架,第三方库的封装,基础算法,等等。 + +> 严格意义上来说,User Interface指的是用户界面,Restful消息和配置文件解析等处理应该放在Application层,User Interface层没有的话就空缺。但User Interface也可以理解为用户接口,所以将Restful消息和配置文件解析等处理放在User Interface层也行。 + +## 3.6 物理分层 +- 魔改四层的六层架构 +![](https://img-blog.csdnimg.cn/2019062201340683.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) + +◆ 用户接口 +◆ 应用服务层接口: +◆ 核心层 +- 应用服务实现层 +- 领域层 +- 数据访问层 + +◆ 基础设施层 + + +- 架构设计图 +![](https://img-blog.csdnimg.cn/20190622013814347.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) + +# 4 Go 语言规范 +## 4.1 包名 +◆ 完整包名组成:引入路径+包名 +◆ 源代码中的包名称 +- 可以和文件夹名称不一致,建议尽量一致 +- 同一文件夹中所有源文件中的包名必须一致 +◆ 代码引用时使用包名,而非文件夹名称 +◆ 源代码导入的是文件夹路径名称 +- 非包名 +- 非文件名 +## 4.2 源代码文件名 +◆ 文件名称只是约定描述性的,并无任何编程含义 + + +# 5 🧧系统 - 代码结构 +![](https://img-blog.csdnimg.cn/20190622101718233.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) + +![](https://img-blog.csdnimg.cn/20190622102431607.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) + +# 6 🧧系统 - 包结构 +![](https://img-blog.csdnimg.cn/20190622103131713.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) + +# 7 包设计规范 +## 7.1 apis包 - 用户接口层 +◆ 文件名称可以描述其业务含义的单词 +◆ 定义外部交互逻辑和交互形式: UI、RESTful接口 +◆ 不涉及任何业务,随时可以替换为其他形式的交互方式 +◆ services构造和初始化 + +## 7.2 services包 - 应用层接口 +◆ 文件名称使用可以描述其业务含义 +◆ 需要对外暴露 +- DTO、 service interface +- 枚举、常数等 + +## 7.3 core包 - 应用层/领域层/数据访问层 +◆ 文件名称使用可以描述 +业务含义 +分层名称 +◆ Service实现 +Domain、Dao、 PO + +# 8 Go的包管理 +## 8.1 历史 +go get => vendor => go modules +◆ go get无版本概念 +◆ vendor曲线救国,但仍未版本化 +◆ go1.11 modules开启版本依赖新大门 + +> 详细过程推荐阅读 +[Go 包管理的前世今生](https://www.infoq.cn/article/history-go-package-management) + +## 8.2 Go modules +通过GO 1.11 MODULE环境变量来开启或者关闭,默认是auto + +◆ off/on/auto +关闭,开启,自动识别 + +◆ 使用module后,GOPATH失去了部分意义 + +◆ 要用module ,第一步将项目从GOPATH中移出去 + +## 8.3 go.mod 文件 +go.mod文件来管理依赖,定义模块依赖 +◆ go.mod文件放在项目根目录 +◆ go.mod文件面向行,由指令+参数组成 +◆ 注释使用// + +### 8.3.1 go.mod 主要指令 +◆ module:定义当前模块和包路径 +◆ require: 定义依赖的模块和版本 +◆ exclude: 排除特定模块和版本的使用 +◆ replace:模块源的替换 + +### 8.3.2 go.mod 命令 +go.mod文件用go mod命令来创建和维护 + +◆ 命令格式 +``` +go mod <命令> [可选参数] +``` +◆ 8个子命令 +- init ,tidy,vendor,verify +- download,edit,graph ,why + +### 8.3.3 实战演示 +#### 8.3.3.1 使用go mod init 创建和初始化go.mod文件 +``` + go mod init javaedge.com/GoDemo +``` +![](https://img-blog.csdnimg.cn/20190622113300232.png) +- 生成文件 +![](https://img-blog.csdnimg.cn/20190622113357360.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) + +#### 8.3.3.2 go get引入依赖 +![](https://img-blog.csdnimg.cn/20190622200239750.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) +#### 8.3.3.3 tidy子命令更新模块依赖 +对于已存在项目进行module化,即可使用该命令 +会自动添加依赖的包,使用go build更新依赖 +# 参考 +[DDD分层架构的三种模式](https://www.jianshu.com/p/a775836c7e25) diff --git "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/JAVA-Calendar.md" b/JDK/JAVA-Calendar.md similarity index 100% rename from "JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/JAVA-Calendar.md" rename to JDK/JAVA-Calendar.md diff --git "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/AbstractQueuedSynchronizer\345\216\237\347\220\206\350\247\243\346\236\220.md" "b/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/AbstractQueuedSynchronizer\345\216\237\347\220\206\350\247\243\346\236\220.md" deleted file mode 100644 index 75c4862c41..0000000000 --- "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/AbstractQueuedSynchronizer\345\216\237\347\220\206\350\247\243\346\236\220.md" +++ /dev/null @@ -1,448 +0,0 @@ -AbstractQueuedSynchronizer 抽象同步队列简称 AQS ,它是实现同步器的基础组件, -并发包中锁的底层就是使用 AQS 实现的. -大多数开发者可能永远不会直接使用AQS ,但是知道其原理对于架构设计还是很有帮助的,而且要理解ReentrantLock、CountDownLatch等高级锁我们必须搞懂 AQS. - -# 1 整体感知 -## 1.1 架构图 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMjA3XzIwMjAwMjA5MTk1MDI3NjQ4LnBuZw?x-oss-process=image/format,png) -AQS框架大致分为五层,自上而下由浅入深,从AQS对外暴露的API到底层基础数据. - - -当有自定义同步器接入时,只需重写第一层所需要的部分方法即可,不需要关注底层具体的实现流程。当自定义同步器进行加锁或者解锁操作时,先经过第一层的API进入AQS内部方法,然后经过第二层进行锁的获取,接着对于获取锁失败的流程,进入第三层和第四层的等待队列处理,而这些处理方式均依赖于第五层的基础数据提供层。 - -AQS 本身就是一套锁的框架,它定义了获得锁和释放锁的代码结构,所以如果要新建锁,只要继承 AQS,并实现相应方法即可。 - -## 1.2 类设计 -该类提供了一种框架,用于实现依赖于先进先出(FIFO)等待队列的阻塞锁和相关的同步器(信号量,事件等)。此类的设计旨在为大多数依赖单个原子int值表示 state 的同步器提供切实有用的基础。子类必须定义更改此 state 的 protected 方法,并定义该 state 对于 acquired 或 released 此对象而言意味着什么。鉴于这些,此类中的其他方法将执行全局的排队和阻塞机制。子类可以维护其他状态字段,但是就同步而言,仅跟踪使用方法 *getState*,*setState* 和 *compareAndSetState* 操作的原子更新的int值。 -子类应定义为用于实现其所在类的同步属性的非公共内部帮助器类。 - -子类应定义为用于实现其所在类的同步属性的非 public 内部辅助类。类AbstractQueuedSynchronizer不实现任何同步接口。 相反,它定义了诸如*acquireInterruptible*之类的方法,可以通过具体的锁和相关的同步器适当地调用这些方法来实现其 public 方法。 - -此类支持默认的排他模式和共享模式: -- 当以独占方式进行获取时,其他线程尝试进行的获取将无法成功 -- 由多个线程获取的共享模式可能(但不一定)成功 - -该类不理解这些差异,只是从机制的意义上说,当共享模式获取成功时,下一个等待线程(如果存在)也必须确定它是否也可以获取。在不同模式下等待的线程们共享相同的FIFO队列。 通常,实现的子类仅支持这些模式之一,但也可以同时出现,比如在ReadWriteLock.仅支持排他模式或共享模式的子类无需定义支持未使用模式的方法. - -此类定义了一个内嵌的 **ConditionObject** 类,可由支持独占模式的子类用作Condition 的实现,该子类的 *isHeldExclusively* 方法报告相对于当前线程是否独占同步,使用当前 *getState* 值调用的方法 *release* 会完全释放此对象 ,并获得给定的此保存状态值,最终将该对象恢复为其先前的获取状态。否则,没有AbstractQueuedSynchronizer方***创建这样的条件,因此,如果无法满足此约束,请不要使用它。ConditionObject的行为当然取决于其同步器实现的语义。 - -此类提供了内部队列的检查,检测和监视方法,以及条件对象的类似方法。 可以根据需要使用 AQS 将它们导出到类中以实现其同步机制。 - -此类的序列化仅存储基础原子整数维护状态,因此反序列化的对象具有空线程队列。 需要序列化性的典型子类将定义一个readObject方法,该方法在反序列化时将其恢复为已知的初始状态。 - -# 2 用法 -要将此类用作同步器的基础,使用*getState* *setState*和/或*compareAndSetState*检查和/或修改同步状态,以重新定义以下方法(如适用) -- tryAcquire -- tryRelease -- tryAcquireShared -- tryReleaseShared -- isHeldExclusively - -默认情况下,这些方法中的每一个都会抛 *UnsupportedOperationException*。 -这些方法的实现必须在内部是线程安全的,并且通常应简短且不阻塞。 定义这些方法是使用此类的**唯一**受支持的方法。 所有其他方法都被声明为final,因为它们不能独立变化。 - -从 AQS 继承的方法对跟踪拥有排他同步器的线程很有用。 鼓励使用它们-这将启用监视和诊断工具,以帮助用户确定哪些线程持有锁。 - -虽然此类基于内部的FIFO队列,它也不会自动执行FIFO获取策略。 独占同步的核心采用以下形式: -- Acquire -```java -while (!tryAcquire(arg)) { - 如果线程尚未入队,则将其加入队列; - 可能阻塞当前线程; -} -``` -- Release - -```java -if (tryRelease(arg)) - 取消阻塞第一个入队的线程; -``` -共享模式与此相似,但可能涉及级联的signal。 - -acquire 中的检查是在入队前被调用,所以新获取的线程可能会在被阻塞和排队的其他线程之前插入。但若需要,可以定义tryAcquire、tryAcquireShared以通过内部调用一或多种检查方法来禁用插入,从而提供公平的FIFO获取顺序。 - -特别是,若 hasQueuedPredecessors()(公平同步器专门设计的一种方法)返回true,则大多数公平同步器都可以定义tryAcquire返回false. - -- 公平与否取决于如下一行代码: -```java -if (c == 0) { - if (!hasQueuedPredecessors() && - compareAndSetState(0, acquires)) { - setExclusiveOwnerThread(current); - return true; - } -} -``` -### hasQueuedPredecessors -```java -public final boolean hasQueuedPredecessors() { - // The correctness of this depends on head being initialized - // before tail and on head.next being accurate if the current - // thread is first in queue. - Node t = tail; // Read fields in reverse initialization order - Node h = head; - // s代表等待队列的第一个节点 - Node s; - // (s = h.next) == null 说明此时有另一个线程正在尝试成为头节点,详见AQS的acquireQueued方法 - // s.thread != Thread.currentThread():此线程不是等待的头节点 - return h != t && - ((s = h.next) == null || s.thread != Thread.currentThread()); -} -``` - - - -对于默认的插入(也称为贪婪,放弃和convoey-avoidance)策略,吞吐量和可伸缩性通常最高。 尽管不能保证这是公平的或避免饥饿,但允许较早排队的线程在较晚排队的线程之前进行重新竞争,并且每个重新争用都有一次机会可以毫无偏向地成功竞争过进入的线程。 -同样,尽管获取通常无需自旋,但在阻塞前,它们可能会执行tryAcquire的多次调用,并插入其他任务。 如果仅短暂地保持排他同步,则这将带来自旋的大部分好处,而如果不进行排他同步,则不会带来很多负担。 如果需要的话,可以通过在调用之前使用“fast-path”检查来获取方法来增强此功能,并可能预先检查*hasContended*()和/或*hasQueuedThreads()*,以便仅在同步器可能不存在争用的情况下这样做。 - -此类为同步提供了有效且可扩展的基础,部分是通过将其使用范围规范化到可以依赖于int状态,acquire 和 release 参数以及内部的FIFO等待队列的同步器。 当这还不够时,可以使用原子类、自定义队列类和锁支持阻塞支持从较低级别构建同步器。 - -# 3 使用案例 -这里是一个不可重入的排他锁,它使用值0表示解锁状态,使用值1表示锁定状态。虽然不可重入锁并不严格要求记录当前所有者线程,但是这个类这样做是为了更容易监视使用情况。它还支持条件,并暴露其中一个检测方法: - -```java -class Mutex implements Lock, java.io.Serializable { - - // 我们内部的辅助类 - private static class Sync extends AbstractQueuedSynchronizer { - // 报告是否处于锁定状态 - protected boolean isHeldExclusively() { - return getState() == 1; - } - - // 如果 state 是 0,获取锁 - public boolean tryAcquire(int acquires) { - assert acquires == 1; // Otherwise unused - if (compareAndSetState(0, 1)) { - setExclusiveOwnerThread(Thread.currentThread()); - return true; - } - return false; - } - - // 通过将 state 置 0 来释放锁 - protected boolean tryRelease(int releases) { - assert releases == 1; // Otherwise unused - if (getState() == 0) throw new IllegalMonitorStateException(); - setExclusiveOwnerThread(null); - setState(0); - return true; - } - - // 提供一个 Condition - Condition newCondition() { return new ConditionObject(); } - - // 反序列化属性 - private void readObject(ObjectInputStream s) - throws IOException, ClassNotFoundException { - s.defaultReadObject(); - setState(0); // 重置到解锁状态 - } - } - - // 同步对象完成所有的工作。我们只是期待它. - private final Sync sync = new Sync(); - - public void lock() { sync.acquire(1); } - public boolean tryLock() { return sync.tryAcquire(1); } - public void unlock() { sync.release(1); } - public Condition newCondition() { return sync.newCondition(); } - public boolean isLocked() { return sync.isHeldExclusively(); } - public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); } - public void lockInterruptibly() throws InterruptedException { - sync.acquireInterruptibly(1); - } - public boolean tryLock(long timeout, TimeUnit unit) - throws InterruptedException { - return sync.tryAcquireNanos(1, unit.toNanos(timeout)); - } -} -``` - -这是一个闩锁类,它类似于*CountDownLatch*,只是它只需要一个单信号就可以触发。因为锁存器是非独占的,所以它使用共享的获取和释放方法。 - -```java - class BooleanLatch { - - private static class Sync extends AbstractQueuedSynchronizer { - boolean isSignalled() { return getState() != 0; } - - protected int tryAcquireShared(int ignore) { - return isSignalled() ? 1 : -1; - } - - protected boolean tryReleaseShared(int ignore) { - setState(1); - return true; - } - } - - private final Sync sync = new Sync(); - public boolean isSignalled() { return sync.isSignalled(); } - public void signal() { sync.releaseShared(1); } - public void await() throws InterruptedException { - sync.acquireSharedInterruptibly(1); - } - } -``` - -# 4 基本属性与框架 -## 4.1 继承体系图 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkyOTU4XzIwMjAwMjEwMjMyNTQwMzUwLnBuZw?x-oss-process=image/format,png) -## 4.2 定义 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDk2XzIwMjAwMjEwMjMxMTIwMTMzLnBuZw?x-oss-process=image/format,png) - -可知 AQS 是一个抽象类,生来就是被各种子类锁继承的。继承自AbstractOwnableSynchronizer,其作用就是为了知道当前是哪个线程获得了锁,便于后续的监控 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDYyXzIwMjAwMjEwMjMyMzE4MzcyLnBuZw?x-oss-process=image/format,png) - - -## 4.3 属性 -### 4.3.1 状态信息 -- volatile 修饰,对于可重入锁,每次获得锁 +1,释放锁 -1 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMTEwXzIwMjAwMjEwMjMyOTI0MTczLnBuZw?x-oss-process=image/format,png) -- 可以通过 *getState* 得到同步状态的当前值。该操作具有 volatile 读的内存语义。 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkyOTM5XzIwMjAwMjEwMjMzMjM0OTE0LnBuZw?x-oss-process=image/format,png) -- setState 设置同步状态的值。该操作具有 volatile 写的内存语义 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDc4XzIwMjAwMjEwMjMzOTI2NjQ3LnBuZw?x-oss-process=image/format,png) -- compareAndSetState 如果当前状态值等于期望值,则以原子方式将同步状态设置为给定的更新值。此操作具有 volatile 读和写的内存语义 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDMyXzIwMjAwMjEwMjM1MjM1NDAzLnBuZw?x-oss-process=image/format,png) -- 自旋比使用定时挂起更快。粗略估计足以在非常短的超时时间内提高响应能力,当设置等待时间时才会用到这个属性 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDY0XzIwMjAwMjExMDAzOTIzNjQxLnBuZw?x-oss-process=image/format,png) - -这写方法都是Final的,子类无法重写。 -- 独占模式 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkyODIzXzIwMjAwMjExMDIyNTI0NjM5LnBuZw?x-oss-process=image/format,png) -- 共享模式 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDQzXzIwMjAwMjExMDIyNTUxODMwLnBuZw?x-oss-process=image/format,png) -### 4.3.2 同步队列 -- CLH 队列( FIFO) -![](https://img-blog.csdnimg.cn/2020100800492945.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70#pic_center)![在这里插入图片描述](https://img-blog.csdnimg.cn/img_convert/22686302fc050911b9dc9cdaf672934b.png) - -- 作用 -阻塞获取不到锁(独占锁)的线程,并在适当时机从队首释放这些线程。 - -同步队列底层数据结构是个双向链表。 - -- 等待队列的头,延迟初始化。 除初始化外,只能通过 *setHead* 方法修改 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkyODc4XzIwMjAwMjExMDIzMjU4OTU4LnBuZw?x-oss-process=image/format,png) -注意:如果head存在,则其waitStatus保证不会是 *CANCELLED* - -- 等待队列的尾部,延迟初始化。 仅通过方法 *enq* 修改以添加新的等待节点 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDk0XzIwMjAwMjExMDIzNTU3MTkyLnBuZw?x-oss-process=image/format,png) -### 4.3.4 条件队列 -#### 为什么需要条件队列? -同步队列并非所有场景都能cover,遇到锁 + 队列结合的场景时,就需要 Lock + Condition,先使用 Lock 决定: -- 哪些线程可以获得锁 -- 哪些线程需要到同步队列里排队阻塞 - -获得锁的多个线程在碰到队列满或空时,可使用 Condition 来管理这些线程,让这些线程阻塞等待,然后在合适的时机后,被正常唤醒。 - -**同步队列 + 条件队列的协作多被用在锁 + 队列场景。** -#### 作用 -AQS 的内部类,结合锁实现线程同步。存放调用条件变量的 await 方法后被阻塞的线程 - -- 实现了 Condition 接口,而 Condition 接口就相当于 Object 的各种监控方法 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMTEwXzIwMjAwMjExMDI0MDUzNi5wbmc?x-oss-process=image/format,png) -需要使用时,直接 new ConditionObject()。 - -### 4.3.5 Node -同步队列和条件队列的共用节点。 -入队时,用 Node 把线程包装一下,然后把 Node 放入两个队列中,我们看下 Node 的数据结构,如下: -#### 4.3.5.1 模式 -- 共享模式 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDUyXzIwMjAwMjExMDI1NTQxMTYyLnBuZw?x-oss-process=image/format,png) -- 独占模式 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkyOTAzXzIwMjAwMjExMDI1NjE2NzkyLnBuZw?x-oss-process=image/format,png) - -#### 4.3.5.2 waitstatus - 等待状态 -```java -volatile int waitStatus; -``` -仅能为如下值: -##### SIGNAL -- 同步队列中的节点在自旋获取锁时,如果前一个节点的状态是 `SIGNAL`,那么自己就直接被阻塞,否则一直自旋 -- 该节点的后继节点会被(或很快)阻塞(通过park),因此当前节点释放或取消时必须unpark其后继节点。为避免竞争,acquire方法必须首先指示它们需要一个 signal,然后重试原子获取,然后在失败时阻塞。 -```java -static final int SIGNAL = -1; -``` - -##### CANCELLED -表示线程获取锁的请求已被取消了: -```java -static final int CANCELLED = 1; -``` -可能由于超时或中断,该节点被取消。 - -节点永远不会离开此状态,此为一种终极状态。具有 cancelled 节点的线程永远不会再次阻塞。 -##### CONDITION -该节点当前在条件队列,当节点从同步队列被转移到条件队列,状态就会被更改该态: -```java -static final int CONDITION = -2; -``` -在被转移之前,它不会用作同步队列的节点,此时状态将置0(该值的使用与该字段的其他用途无关,仅是简化了机制)。 - -##### PROPAGATE -线程处在 `SHARED` 情景下,该字段才会启用。 - -指示下一个**acquireShared**应该无条件传播,共享模式下,该状态的线程处Runnable态 -```java -static final int PROPAGATE = -3; -``` -*releaseShared* 应该传播到其他节点。 在*doReleaseShared*中对此进行了设置(仅适用于头节点),以确保传播继续进行,即使此后进行了其他操作也是如此。 -##### 0 -初始化时的默认值。 -##### 小结 -这些值是以数字方式排列,极大方便了开发者的使用。我们在平时开发也可以定义一些有特殊意义的常量值。 - -非负值表示节点不需要 signal。 因此,大多数代码并不需要检查特定值,检查符号即可。 - -- 对于普通的同步节点,该字段初始化为0 -- 对于条件节点,该字段初始化为`CONDITION` - -使用CAS(或在可能的情况下进行无条件的 volatile 写)对其进行修改。 - -注意两个状态的区别 -- state 是锁的状态,int 型,子类继承 AQS 时,都是要根据 state 字段来判断有无得到锁 -- waitStatus 是节点(Node)的状态 - -#### 4.3.5.3 数据结构 -##### 前驱节点 -- 链接到当前节点/线程所依赖的用来检查 *waitStatus* 的前驱节点 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkyODY0XzIwMjAwMjEyMDMyNjI1NjYxLnBuZw?x-oss-process=image/format,png) - -在入队期间赋值,并且仅在出队时将其清空(为了GC)。 - -此外,在取消一个前驱结点后,在找到一个未取消的节点后会短路,这将始终存在,因为头节点永远不会被取消:只有成功 acquire 后,一个节点才会变为头。 - -取消的线程永远不会成功获取,并且线程只会取消自身,不会取消任何其他节点。 - -##### 后继节点 -链接到后继节点,当前节点/线程在释放时将其unpark。 在入队时赋值,在绕过已取消的前驱节点时进行调整,在出队时置null(为了GC)。 -入队操作直到附加后才赋值前驱节点的`next`字段,因此看到`next`字段为 null,并不一定意味该节点位于队尾(有时间间隙)。 - -但若`next == null`,则可从队尾开始扫描`prev`以进行再次检查。 -```java -// 若节点通过从tail向前搜索发现在在同步队列上,则返回 true -// 仅在调用了 isOnSyncQueue 且有需要时才调用 -private boolean findNodeFromTail(Node node) { - Node t = tail; - for (;;) { - if (t == node) - return true; - if (t == null) - return false; - t = t.prev; - } -} -``` -```java -final boolean isOnSyncQueue(Node node) { - if (node.waitStatus == Node.CONDITION || node.prev == null) - return false; - if (node.next != null) // If has successor, it must be on queue - return true; - /** - * node.prev 可以非null,但还没有在队列中,因为将它放在队列中的 CAS 可能会失败。 - * 所以必须从队尾向前遍历以确保它确实成功了。 - * 在调用此方法时,它将始终靠近tail,并且除非 CAS 失败(这不太可能) - * 否则它会在那里,因此几乎不会遍历太多 - */ - return findNodeFromTail(node); -} -``` -已取消节点的`next`字段设置为指向节点本身而不是null,以使isOnSyncQueue更轻松。 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDg3XzIwMjAwMjEyMDM1NTAzNjAyLnBuZw?x-oss-process=image/format,png) -- 使该节点入队的线程。 在构造时初始化,使用后消亡。![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkyODcwXzIwMjAwMjEyMjAwMTI3OTkwLnBuZw?x-oss-process=image/format,png) - -在同步队列中,nextWaiter 表示当前节点是独占模式还是共享模式 -在条件队列中,nextWaiter 表示下一个节点元素 - -链接到在条件队列等待的下一个节点,或者链接到特殊值`SHARED`。 由于条件队列仅在以独占模式保存时才被访问,因此我们只需要一个简单的链接队列即可在节点等待条件时保存节点。 然后将它们转移到队列中以重新获取。 并且由于条件只能是独占的,因此我们使用特殊值来表示共享模式来保存字段。![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDUxXzIwMjAwMjEyMjAxMjMyODMucG5n?x-oss-process=image/format,png) -# 5 Condition 接口 -JDK5 时提供。 -- 条件队列 ConditionObject 实现了 Condition 接口 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDc1XzIwMjAwMjEyMjA0NjMxMTMzLnBuZw?x-oss-process=image/format,png) -- 本节就让我们一起来研究之 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDgwXzIwMjAwMjEyMjA1MzQ2NzIyLnBuZw?x-oss-process=image/format,png) - -Condition 将对象监视方法(wait,notify和notifyAll)分解为不同的对象,从而通过与任意Lock实现结合使用,从而使每个对象具有多个wait-sets。 当 Lock 替换了 synchronized 方法和语句的使用,Condition 就可以替换了Object监视器方法的使用。 - -Condition 的实现可以提供与 Object 监视方法不同的行为和语义,例如保证通知的顺序,或者在执行通知时不需要保持锁定。 如果实现提供了这种专门的语义,则实现必须记录这些语义。 - -Condition实例只是普通对象,它们本身可以用作 synchronized 语句中的目标,并且可以调用自己的监视器 wait 和 notification 方法。 获取 Condition 实例的监视器锁或使用其监视器方法与获取与该条件相关联的锁或使用其 await 和 signal 方法没有特定的关系。 建议避免混淆,除非可能在自己的实现中,否则不要以这种方式使用 Condition 实例。 - -```java - class BoundedBuffer { - final Lock lock = new ReentrantLock(); - final Condition notFull = lock.newCondition(); - final Condition notEmpty = lock.newCondition(); - - final Object[] items = new Object[100]; - int putptr, takeptr, count; - - public void put(Object x) throws InterruptedException { - lock.lock(); - try { - while (count == items.length) - notFull.await(); - items[putptr] = x; - if (++putptr == items.length) putptr = 0; - ++count; - notEmpty.signal(); - } finally { - lock.unlock(); - } - } - - public Object take() throws InterruptedException { - lock.lock(); - try { - while (count == 0) - notEmpty.await(); - Object x = items[takeptr]; - if (++takeptr == items.length) takeptr = 0; - --count; - notFull.signal(); - return x; - } finally { - lock.unlock(); - } - } - } -``` -(ArrayBlockingQueue类提供了此功能,因此没有理由实现此示例用法类。) -定义出一些方法,这些方法奠定了条件队列的基础 -## API -### await -- 使当前线程等待,直到被 signalled 或被中断 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkyOTkwXzIwMjAwMjEyMjExNzU3ODYyLnBuZw?x-oss-process=image/format,png) - -与此 Condition 相关联的锁被原子释放,并且出于线程调度目的,当前线程被禁用,并且处于休眠状态,直到发生以下四种情况之一: -- 其它线程为此 Condition 调用了 signal 方法,并且当前线程恰好被选择为要唤醒的线程 -- 其它线程为此 Condition 调用了 signalAll 方法 -- 其它线程中断了当前线程,并且当前线程支持被中断 -- 发生“虚假唤醒”。 - -在所有情况下,在此方法可以返回之前,必须重新获取与此 Condition 关联的锁,才能真正被唤醒。当线程返回时,可以保证保持此锁。 -### await 超时时间 -- 使当前线程等待,直到被 signal 或中断,或经过指定的等待时间 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDczXzIwMjAwMjEyMjIxMjU3NTcyLnBuZw?x-oss-process=image/format,png) - -此方法在行为上等效于: - - -```java -awaitNanos(unit.toNanos(time)) > 0 -``` -所以,虽然入参可以是任意单位的时间,但其实仍会转化成纳秒 -### awaitNanos -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkyOTg0XzIwMjAwMjEyMjIxOTMxNTU5LnBuZw?x-oss-process=image/format,png) -注意这里选择纳秒是为了避免计算剩余等待时间时的截断误差 - - -### signal() -- 唤醒条件队列中的一个线程,在被唤醒前必须先获得锁 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDYzXzIwMjAwMjEyMjMwNTQ3NjMzLnBuZw?x-oss-process=image/format,png) -### signalAll() -- 唤醒条件队列中的所有线程 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkyODkyXzIwMjAwMjEyMjMwNjUzNTM5LnBuZw?x-oss-process=image/format,png) \ No newline at end of file diff --git "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/FurureTask.md" "b/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/FurureTask.md" index 149a2a9585..3c1bbd6ce7 100644 --- "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/FurureTask.md" +++ "b/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/FurureTask.md" @@ -1,26 +1,32 @@ -# 1 简介 -- 使用继承方式的好处是方便传参,可以在子类里面添加成员变量,通过 set 方法设置参数或者通过构造函数进行传递 +# 1 引导语 +研究源码,一般我们都从整体以及实例先入手,再研究细节,不至于一开始就“深陷其中而"当局者迷". +本文,我们来看最后一种有返回值的线程创建方式。 + +- 使用继承方式的好处是方便传参,可以在子类里面添加成员变量,通过 set 方法设置参数或者通过构造函数进行传递 - 使用 Runnable 方式,则只能使用主线程里面被声明为 final 变量 -不好的地方是 Java 不支持多继承,如果继承了 Thread 类,那么子类不能再继承其他 ,而 Runable接口则没有这个限制 。而且 Thread 类和 Runnable 接口都不允许声明检查型异常,也不能定义返回值。没有返回值这点稍微有点麻烦。前两种方式都没办法拿到任务的返回结果,但今天的主角 FutureTask 却可以。 +不好的地方是 Java 不支持多继承,如果继承了 Thread 类,那么子类不能再继承其他 ,而 Runable接口则没有这个限制 。而且 Thread 类和 Runnable 接口都不允许声明检查型异常,也不能定义返回值。没有返回值这点稍微有点麻烦。前两种方式都没办法拿到任务的返回结果,但今天的主角 FutureTask 却可以. 不能声明抛出检查型异常则更麻烦一些。run()方法意味着必须捕获并处理检查型异常。即使小心地保存了异常信息(在捕获异常时)以便稍后检查,但也不能保证这个 Runnable 对象的所有使用者都读取异常信息。你也可以修改Runnable实现的getter,让它们都能抛出任务执行中的异常。但这种方法除了繁琐也不是十分安全可靠,你不能强迫使用者调用这些方法,程序员很可能会调用join()方法等待线程结束然后就不管了。 但是现在不用担心了,以上的问题终于在1.5中解决了。Callable接口和Future接口的引入以及他们对线程池的支持优雅地解决了这两个问题。 # 2 案例 先看一个demo,了解 FutureTask 相关组件是如何使用的 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY3MjUwXzIwMjAwMjA3MjIxNjM4OTk2LnBuZw?x-oss-process=image/format,png) +![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177167250_20200207221638996.png) +CallerTask 类实现了 Callable 接口的 call() 方法 。在 main 函数内首先创建FutrueTask对 象(构造函数为 CallerTask 实例), 然后使用创建的 FutureTask 作为任务创建了一个线程并且启动它, 最后通过 futureTask.get()等待任务执行完毕并返回结果. + + # 3 Callable Callable函数式接口定义了唯一方法 - call(). 我们可以在Callable的实现中声明强类型的返回值,甚至是抛出异常。同时,利用call()方法直接返回结果的能力,省去读取值时的类型转换。 - 源码定义 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2NjQ1XzIwMjAwMjAyMjA0MjA0MjIyLnBuZw?x-oss-process=image/format,png) +![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166645_20200202204204222.png) 注意到返回值是一个泛型,使用的时候,不会直接使用 Callable,而是和 FutureTask 协同. # 4 Future - Callable 可以返回线程的执行结果,在获取结果时,就需要用到 Future 接口. -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2OTIwXzIwMjAwMjA0MDQwMDA3MzMucG5n?x-oss-process=image/format,png) +![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166920_2020020404000733.png) Future是 Java5 中引入的接口,当提交一个Callable对象给线程池时,将得到一个Future对象,并且它和传入的Callable有相同的结果类型声明。 @@ -34,7 +40,7 @@ Future表示异步计算的结果。提供了一些方法来检查计算是否 ## 4.1 Future API ### 4.1.1 cancel - 尝试取消执行任务 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2Njc4XzIwMjAwMjA0MDIxOTEwMTI1LnBuZw?x-oss-process=image/format,png) +![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166678_20200204021910125.png) 一个比较复杂的方法,当任务处于不同状态时,该方法有不同响应: - 任务 已经完成 / 已经取消 / 由于某些其他原因无法被取消,该尝试会直接失败 - 尝试成功,且此时任务尚未开始,调用后是可以取消成功的 @@ -45,42 +51,34 @@ Future表示异步计算的结果。提供了一些方法来检查计算是否 如果此方法返回 true,则随后对 *isCancelled* 的调用将始终返回 true. ### 4.1.2 isCancelled - 是否被取消 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2ODM0XzIwMjAwMjA0MDMwMzU2OTM1LnBuZw?x-oss-process=image/format,png) +![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166834_20200204030356935.png) 如果此任务在正常完成之前被取消,则返回true. ### 4.1.3 isDone - 是否完成 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2NTc0XzIwMjAwMjA0MDMxMDA1NDg4LnBuZw?x-oss-process=image/format,png) +![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166574_20200204031005488.png) 如果此任务完成,则返回true. 完成可能是由于正常终止,异常或取消引起的,在所有这些情况下,此方法都将返回true. ### 4.1.4 get - 获取结果 -等待任务完成,然后获取其结果。 -![](https://img-blog.csdnimg.cn/20210615233538777.png) +![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166906_20200204031206355.png) +等待任务完成,然后获取其结果. -若: -- 任务被取消,抛 *CancellationException* -- 当前线程在等待时被中断,抛 *InterruptedException* -- 任务抛出了异常,抛 *ExecutionException* +- 如果任务被取消,抛 *CancellationException* +- 如果当前线程在等待时被中断,抛 *InterruptedException* +- 如果任务抛出了异常,抛 *ExecutionException* ### 4.1.5 timed get - 超时获取 -- 必要时最多等待给定时间以完成任务,然后获取其结果(如果有的话)。 -![](https://img-blog.csdnimg.cn/20210615233634165.png) +![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166633_20200204033827757.png) +必要时最多等待给定时间以完成任务,然后获取其结果(如果有的话)。 - 抛CancellationException 如果任务被取消 - 抛 ExecutionException 如果任务抛了异常 - 抛InterruptedException 如果当前线程在等待时被中断 - 抛TimeoutException 如果等待超时了 -两个get()方法都是阻塞的,若被调用时,任务还没有执行完,则调用get()方法的线程会阻塞,直到任务执行完才会被唤醒。所以future.get()是会阻塞当前调用线程。 -- 阻塞异步线程 -![](https://img-blog.csdnimg.cn/20210420165206559.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) - -![](https://img-blog.csdnimg.cn/2021042016492740.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) -- 阻塞主线程 -![](https://img-blog.csdnimg.cn/20210420165240137.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) +需要注意:这两个get()方法都是阻塞式的,如果被调用的时候,任务还没有执行完,那么调用get()方法的线程会阻塞,直到任务执行完才会被唤醒。 -![](https://img-blog.csdnimg.cn/20210420165122388.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) -Future 接口定义了许多对任务进行管理的 API,极大地方便了我们的开发调控。 +Future 接口定义了许多对任务进行管理的 API,极大地方便了我们的开发调控. # 5 RunnableFuture Java6 时提供的持有 Runnable 性质的 Future. @@ -88,7 +86,7 @@ Java6 时提供的持有 Runnable 性质的 Future. 成功执行run方法导致Future的完成,并允许访问其结果. RunnableFuture接口比较简单,就是继承了 Runnable 和 Future 接口。只提供一个*run*方法 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2OTE2XzIwMjAwMjA0MDQwMjM0NzM1LnBuZw?x-oss-process=image/format,png) +![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166916_20200204040234735.png) 现在,我们应该都知道,创建任务有两种方式 - 无返回值的 Runnable @@ -99,8 +97,8 @@ RunnableFuture接口比较简单,就是继承了 Runnable 和 Future 接口。 所以铺垫了这么多,本文的主角 FutureTask 来了! # 6 FutureTask -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2NTk5XzIwMjAwMjAyMjA1MzM1MzA3LnBuZw?x-oss-process=image/format,png) -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2OTQ3XzIwMjAwMjA4MjI0MjEzNDYucG5n?x-oss-process=image/format,png) +![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166599_20200202205335307.png) +![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166947_2020020822421346.png) 前面的Future是一个接口,而 FutureTask 才是一个实实在在的工具类,是线程运行的具体任务. - 实现了 RunnableFuture 接口 - 也就是实现了 Runnnable 接口,即FutureTask 本身就是个 Runnnable @@ -114,7 +112,7 @@ RunnableFuture接口比较简单,就是继承了 Runnable 和 Future 接口。 - 注意这些常量字段的定义方式,遵循避免魔鬼数字的编程规约. -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2ODg4XzIwMjAwMjA4MTM0OTU2MTQ1LnBuZw?x-oss-process=image/format,png) +![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166888_20200208134956145.png) - NEW 线程任务创建,开始状态 @@ -139,28 +137,28 @@ RunnableFuture接口比较简单,就是继承了 Runnable 和 Future 接口。 ### 6.1.2 其他属性 - 组合的 callable,这样就具备了转化 Callable 和 Runnable 的功能 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2ODQ3XzIwMjAwMjA4MjI0MDM0NzU0LnBuZw?x-oss-process=image/format,png) +![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166847_20200208224034754.png) - 从ge()返回或抛出异常的结果,非volatile,受状态读/写保护 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2NTYzXzIwMjAwMjA4MjI0MzQzNjQ2LnBuZw?x-oss-process=image/format,png) +![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166563_20200208224343646.png) - 运行 callable 的线程; 在run()期间进行CAS -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2NzkyXzIwMjAwMjA4MjI1NDU3Mzc2LnBuZw?x-oss-process=image/format,png) +![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166792_20200208225457376.png) - 记录调用 get 方法时被等待的线程 - 栈形式 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2NjI5XzIwMjAwMjA4MjI1NzM1NzE5LnBuZw?x-oss-process=image/format,png) +![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166629_20200208225735719.png) 从属性上我们明显看到 Callable 是作为 FutureTask 的属性之一,这也就让 FutureTask 接着我们看下 FutureTask 的构造器,看看两者是如何转化的。 ## 6.2 构造方法 ### 6.2.1 Callable 参数 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY3MTI4XzIwMjAwMjA4MjMwMjIyNDg2LnBuZw?x-oss-process=image/format,png) +![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177167128_20200208230222486.png) ### 6.2.2 Runnable 参数 为协调 callable 属性,辅助result 参数 Runnable 是没有返回值的,所以 result 一般没有用,置为 null 即可,正如 JDK 所推荐写法 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2ODcwXzIwMjAwMjA4MjMxNTEwMzEwLnBuZw?x-oss-process=image/format,png) -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2OTcwXzIwMjAwMjA4MjMwMzM2MjIxLnBuZw?x-oss-process=image/format,png) +![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166870_20200208231510310.png) +![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166970_20200208230336221.png) - Executors.callable 方法负责将 runnable 适配成 callable. - ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2NzE0XzIwMjAwMjA4MjMyMDUxMjQ2LnBuZw?x-oss-process=image/format,png) + ![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166714_20200208232051246.png) - 通过转化类 RunnableAdapter进行适配 - ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY3MzE0XzIwMjAwMjA4MjMyMjExNjE2LnBuZw?x-oss-process=image/format,png) + ![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177167314_20200208232211616.png) ### 6.2.3 小结 我们可以学习这里的适配器模式,目标是要把 Runnable 适配成 Callable,那么我们首先要实现 Callable 接口,并且在 Callable 的 call 方法里面调用被适配对象即 Runnable的方法即可. @@ -236,12 +234,12 @@ private int awaitDone(boolean timed, long nanos) } ``` -get 是一种阻塞式方法,当发现任务还在进行中,没有完成时,就会阻塞当前进程,等待任务完成后再返回结果值。 - -阻塞底层使用的是 LockSupport.park 方法,使当前线程进入 `WAITING` 或 `TIMED_WAITING` 态。 +get 是一种阻塞式方法,当发现任务还在进行中,没有完成时,就会阻塞当前进程,等待任务完成后再返回结果值. +阻塞底层使用的是 LockSupport.park 方法,使当前线程进入 `WAITING` 或 `TIMED_WAITING` 态. ## 6.4 run -该方法可被直接调用,也可由线程池调用 +该方法可被直接调用,也可由线程池调用 + ```java public void run() { // 状态非 NEW 或当前任务已有线程在执行,直接返回 @@ -277,9 +275,12 @@ public void run() { } ``` -run 方法没有返回值,通过给 outcome 属性赋值(set(result)),get 时就能从 outcome 属性中拿到返回值。 +run 方法我们再说明几点: + +run 方法是没有返回值的,通过给 outcome 属性赋值(set(result)),get 时就能从 outcome 属性中拿到返回值; FutureTask 两种构造器,最终都转化成了 Callable,所以在 run 方法执行的时候,只需要执行 Callable 的 call 方法即可,在执行 c.call() 代码时,如果入参是 Runnable 的话, 调用路径为 c.call() -> RunnableAdapter.call() -> Runnable.run(),如果入参是 Callable 的话,直接调用。 ## 6.5 cancel + ```java // 取消任务,如果正在运行,尝试去打断 public boolean cancel(boolean mayInterruptIfRunning) { @@ -308,4 +309,4 @@ public boolean cancel(boolean mayInterruptIfRunning) { ``` # 7 总结 -FutureTask 统一了 Runnnable 和 Callable,方便了我们后续对线程池的使用。 \ No newline at end of file +FutureTask 统一了 Runnnable 和 Callable,方便了我们后续对线程池的使用. \ No newline at end of file diff --git "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/CountDownLatch.md" "b/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/JDK\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230 - CountDownLatch.md" similarity index 100% rename from "JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/CountDownLatch.md" rename to "JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/JDK\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230 - CountDownLatch.md" diff --git "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/Java\351\233\206\345\220\210\344\271\213HashMap\346\272\220\347\240\201\350\247\243\346\236\220.md" "b/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/Java\351\233\206\345\220\210\344\271\213HashMap\346\272\220\347\240\201\350\247\243\346\236\220.md" deleted file mode 100644 index 59050eb978..0000000000 --- "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/Java\351\233\206\345\220\210\344\271\213HashMap\346\272\220\347\240\201\350\247\243\346\236\220.md" +++ /dev/null @@ -1,715 +0,0 @@ -# 1 概述 -HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长. - -HashMap是非线程安全的,只适用于单线程环境,多线程环境可以采用并发包下的`concurrentHashMap` - -HashMap 实现了Serializable接口,因此它支持序列化,实现了Cloneable接口,能被克隆 - -HashMap是基于哈希表的Map接口的非同步实现.此实现提供所有可选的映射操作,并允许使用null值和null键.此类不保证映射的顺序,特别是它不保证该顺序恒久不变. - -Java8中又对此类底层实现进行了优化,比如引入了红黑树的结构以解决哈希碰撞 -  -# 2 HashMap的数据结构 -在Java中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造,HashMap也不例外. -HashMap实际上是一个"链表散列"的数据结构,即数组和链表的结合体. - -![HashMap的结构](https://img-blog.csdnimg.cn/img_convert/c5ac3aacefa3b745d9d2fa48c577b11d.png) -HashMap的主结构类似于一个数组,添加值时通过`key`确定储存位置. -每个位置是一个Entry的数据结构,该结构可组成链表. -当发生冲突时,相同hash值的键值对会组成链表. -这种`数组+链表`的组合形式大部分情况下都能有不错的性能效果,Java6、7就是这样设计的. -然而,在极端情况下,一组(比如经过精心设计的)键值对都发生了冲突,这时的哈希结构就会退化成一个链表,使HashMap性能急剧下降. - -所以在Java8中,HashMap的结构实现变为数组+链表+红黑树 -![Java8 HashMap的结构](https://img-blog.csdnimg.cn/img_convert/7668e49d6bb167520dcf09ab09537378.png) -可以看出,HashMap底层就是一个数组结构 -数组中的每一项又是一个链表 -当新建一个HashMap时,就会初始化一个数组. - -# 3 三大集合与迭代子 -HashMap使用三大集合和三种迭代子来轮询其Key、Value和Entry对象 -```java -public class HashMapExam { - public static void main(String[] args) { - Map map = new HashMap<>(16); - for (int i = 0; i < 15; i++) { - map.put(i, new String(new char[]{(char) ('A'+ i)})); - } - - System.out.println("======keySet======="); - Set set = map.keySet(); - Iterator iterator = set.iterator(); - while (iterator.hasNext()) { - System.out.println(iterator.next()); - } - - System.out.println("======values======="); - Collection values = map.values(); - Iterator stringIterator=values.iterator(); - while (stringIterator.hasNext()) { - System.out.println(stringIterator.next()); - } - - System.out.println("======entrySet======="); - for (Map.Entry entry : map.entrySet()) { - System.out.println(entry); - } - } -} -``` - -# 4 源码分析 -```java - //默认的初始容量16,且实际容量是2的整数幂 - static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; - - //最大容量(传入容量过大将被这个值替换) - static final int MAXIMUM_CAPACITY = 1 << 30; - - // 默认加载因子为0.75(当表达到3/4满时,才会再散列),这个因子在时间和空间代价之间达到了平衡.更高的因子可以降低表所需的空间,但是会增加查找代价,而查找是最频繁操作 - static final float DEFAULT_LOAD_FACTOR = 0.75f; - - //桶的树化阈值:即 链表转成红黑树的阈值,在存储数据时,当链表长度 >= 8时,则将链表转换成红黑树 - static final int TREEIFY_THRESHOLD = 8; - // 桶的链表还原阈值:即 红黑树转为链表的阈值,当在扩容(resize())时(HashMap的数据存储位置会重新计算),在重新计算存储位置后,当原有的红黑树内数量 <= 6时,则将 红黑树转换成链表 - static final int UNTREEIFY_THRESHOLD = 6; - //最小树形化容量阈值:即 当哈希表中的容量 > 该值时,才允许树形化链表 (即 将链表 转换成红黑树) -``` -因为红黑树的平均查找长度是log(n),长度为8的时候,平均查找长度为3,如果继续使用链表,平均查找长度为8/2=4,这才有转换为树的必要 -链表长度如果是小于等于6,6/2=3,虽然速度也很快的,但是转化为树结构和生成树的时间并不会太短 - -还有选择6和8,中间有个差值7可以有效防止链表和树频繁转换 -假设一下,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。 -``` - // 为了避免扩容/树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD - // 小于该值时使用的是扩容哦!!! - static final int MIN_TREEIFY_CAPACITY = 64; - - // 存储数据的Node数组,长度是2的幂. - // HashMap采用链表法解决冲突,每一个Node本质上是一个单向链表 - //HashMap底层存储的数据结构,是一个Node数组.上面得知Node类为元素维护了一个单向链表.至此,HashMap存储的数据结构也就很清晰了:维护了一个数组,每个数组又维护了一个单向链表.之所以这么设计,考虑到遇到哈希冲突的时候,同index的value值就用单向链表来维护 - //与 JDK 1.7 的对比(Entry类),仅仅只是换了名字 - transient Node[] table; - - // HashMap的底层数组中已用槽的数量 - transient int size; - // HashMap的阈值,用于判断是否需要调整HashMap的容量(threshold = 容量*加载因子) - int threshold; - - // 负载因子实际大小 - final float loadFactor; - - // HashMap被改变的次数 - transient int modCount; - - // 指定“容量大小”和“加载因子”的构造函数,是最基础的构造函数 - public HashMap(int initialCapacity, float loadFactor) { - if (initialCapacity < 0) - throw new IllegalArgumentException("Illegal initial capacity: " + - initialCapacity); - // HashMap的最大容量只能是MAXIMUM_CAPACITY - if (initialCapacity > MAXIMUM_CAPACITY) - initialCapacity = MAXIMUM_CAPACITY; - //负载因子须大于0 - if (loadFactor <= 0 || Float.isNaN(loadFactor)) - throw new IllegalArgumentException("Illegal load factor: " + - loadFactor); - // 设置"负载因子" - this.loadFactor = loadFactor; - // 设置"HashMap阈值",当HashMap中存储数据的数量达到threshold时,就需将HashMap的容量加倍 - this.threshold = tableSizeFor(initialCapacity); - } -``` - -- 上面的tableSizeFor有何用? -tableSizeFor方法保证函数返回值是大于等于给定参数initialCapacity最小的2的幂次方的数值 -``` - static final int tableSizeFor(int cap) { - int n = cap - 1; - n |= n >>> 1; - n |= n >>> 2; - n |= n >>> 4; - n |= n >>> 8; - n |= n >>> 16; - return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; - } -``` - 可以看出该方法是一系列的二进制位操作 - ->a |= b 等同于 a = a|b - -逐行分析 -- `int n = cap - 1` -给定的cap 减 1,为了避免参数cap本来就是2的幂次方,这样一来,经过后续操作,cap将会变成2 * cap,是不符合我们预期的 - -- `n |= n >>> 1` -n >>> 1 : n无符号右移1位,即n二进制最高位的1右移一位 -n | (n >>> 1) 导致 n二进制的高2位值为1 -目前n的高1~2位均为1 -- `n |= n >>> 2` -n继续无符号右移2位 -n | (n >>> 2) 导致n二进制表示的高3~4位经过运算值均为1 -目前n的高1~4位均为1 -- `n |= n >>> 4` -n继续无符号右移4位 -n | (n >>> 4) 导致n二进制表示的高5~8位经过运算值均为1 -目前n的高1~8位均为1 -- `n |= n >>> 8` -n继续无符号右移8位 -n | (n >>> 8) 导致n二进制表示的高9~16位经过运算值均为1 -目前n的高1~16位均为1 - -可以看出,无论给定cap(cap < MAXIMUM_CAPACITY )的值是多少,经过以上运算,其值的二进制所有位都会是1.再将其加1,这时候这个值一定是2的幂次方. -当然如果经过运算值大于MAXIMUM_CAPACITY,直接选用MAXIMUM_CAPACITY. -![例子](https://img-blog.csdnimg.cn/img_convert/e58fef4c158d1c978777f5aa40ebee5e.png) -至此tableSizeFor如何保证cap为2的幂次方已经显而易见了,那么问题来了 - -## 4.1 **为什么cap要保持为2的幂次方?** -主要与HashMap中的数据存储有关. - -在Java8中,HashMap中key的Hash值由Hash(key)方法计得 -![](https://img-blog.csdnimg.cn/img_convert/0882f5d36b225a33c5e17666f5fb6695.png) - -HashMap中存储数据table的index是由key的Hash值决定的. -在HashMap存储数据时,我们期望数据能均匀分布,以防止哈希冲突. -自然而然我们就会想到去用`%`取余操作来实现我们这一构想 - ->取余(%)操作 : 如果除数是2的幂次则等价于与其除数减一的与(&)操作. - -这也就解释了为什么一定要求cap要为2的幂次方.再来看看table的index的计算规则: -![](https://img-blog.csdnimg.cn/img_convert/d8df9e11f3a143218a08f515ba6e805e.png) - 等价于: -``` - index = e.hash % newCap -``` -采用二进制位操作&,相对于%,能够提高运算效率,这就是cap的值被要求为2幂次的原因 -![](https://img-blog.csdnimg.cn/img_convert/6fcd40c3f37371a2a9ebb2a209f3be58.png) -![数据结构 & 参数与 JDK 7 / 8](https://img-blog.csdnimg.cn/img_convert/3d946e0e1bd86c8ae41c1d6857377445.png) -## 4.2 **Node类** - -``` -static class Node implements Map.Entry { - final int hash; - final K key; - V value; - Node next; - - Node(int hash, K key, V value, Node next) { - this.hash = hash; - this.key = key; - this.value = value; - this.next = next; - } - - public final K getKey() { return key; } - public final V getValue() { return value; } - public final String toString() { return key + "=" + value; } - - public final int hashCode() { - return Objects.hashCode(key) ^ Objects.hashCode(value); - } - - public final V setValue(V newValue) { - V oldValue = value; - value = newValue; - return oldValue; - } - - public final boolean equals(Object o) { - if (o == this) - return true; - if (o instanceof Map.Entry) { - Map.Entry e = (Map.Entry)o; - if (Objects.equals(key, e.getKey()) && - Objects.equals(value, e.getValue())) - return true; - } - return false; - } - } -``` -Node 类是HashMap中的静态内部类,实现Map.Entry接口.定义了key键、value值、next节点,也就是说元素之间构成了单向链表. - -## 4.3 TreeNode -``` -static final class TreeNode extends LinkedHashMap.Entry { - TreeNode parent; // red-black tree links - TreeNode left; - TreeNode right; - TreeNode prev; // needed to unlink next upon deletion - boolean red; - TreeNode(int hash, K key, V val, Node next) {} - - // 返回当前节点的根节点 - final TreeNode root() { - for (TreeNode r = this, p;;) { - if ((p = r.parent) == null) - return r; - r = p; - } - } - } -``` -红黑树结构包含前、后、左、右节点,以及标志是否为红黑树的字段 -此结构是Java8新加的 - -## 4.4 hash方法 -Java 8中的散列值优化函数 -![](https://img-blog.csdnimg.cn/img_convert/5a02fb83605c5435e8585b2c37175e69.png) -只做一次16位右位移异或 -key.hashCode()函数调用的是key键值类型自带的哈希函数,返回int型散列值 - -理论上散列值是一个int型,如果直接拿散列值作为下标访问HashMap主数组的话,考虑到2进制32位带符号的int范围大概40亿的映射空间。只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。 -但问题是一个40亿长度的数组,内存是放不下的.HashMap扩容之前的数组初始大小才16,所以这个散列值是不能直接拿来用的. -用之前还要先做对数组的长度取模运算,得到的余数才能用来访问数组下标 -源码中模运算就是把散列值和数组长度做一个"与"操作, -![](https://img-blog.csdnimg.cn/img_convert/d92c8b227a759d2c17736bc2b6403d57.png) -这也正好解释了为什么HashMap的数组长度要取2的整次幂 -因为这样(数组长度-1)正好相当于一个“低位掩码” -“与”操作的结果就是散列值的高位全部归零,只保留低位值,用来做数组下标访问 - -以初始长度16为例,16-1=15 -2进制表示是00000000 00000000 00001111 -和某散列值做“与”操作如下,结果就是截取了最低的四位值 -![](https://img-blog.csdnimg.cn/img_convert/553e83380e5587fd4e40724b373d79e4.png) -但这时候问题就来了,这样就算我的散列值分布再松散,要是只取最后几位的话,碰撞也会很严重 - -这时候“扰动函数”的价值就体现出来了 -![](https://img-blog.csdnimg.cn/img_convert/b2051fb82b033621ca8d154861ff5c15.png) -右位移16位,正好是32位一半,自己的高半区和低半区做异或,就是为了混合原始hashCode的高位和低位,以此来加大低位的随机性 -而且混合后的低位掺杂了高位的部分特征,这样高位的信息也被变相保留下来。 - -index的运算规则是 -``` -e.hash & (newCap - 1) -``` -newCap是2的幂,所以newCap - 1的高位全0 - -若e.hash值只用自身的hashcode,index只会和e.hash的低位做&操作.这样一来,index的值就只有低位参与运算,高位毫无存在感,从而会带来哈希冲突的风险 -所以在计算key的hashCode时,用其自身hashCode与其低16位做异或操作 -这也就让高位参与到index的计算中来了,即降低了哈希冲突的风险又不会带来太大的性能问题 - -## 4.5 Put方法 -![](https://img-blog.csdnimg.cn/img_convert/da29710b339ad70260610e2ed358305f.png) - -![](https://img-blog.csdnimg.cn/img_convert/977877dab11a1fcd23dffadecbe6b2cd.png) - -![HashMap-put(k,v)](https://img-blog.csdnimg.cn/img_convert/0df25cf27e264797c7f1451e11c927f1.png) -①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容 - -②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③ - -③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals - -④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤ - -⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可 - -⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,执行resize()扩容 -``` - public V put(K key, V value) { - // 对key的hashCode()做hash - return putVal(hash(key), key, value, false, true); - } - -final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { - Node[] tab; Node p; int n, i; - // 步骤① tab为空则调用resize()初始化创建 - if ((tab = table) == null || (n = tab.length) == 0) - n = (tab = resize()).length; - // 步骤② 计算index,并对null做处理 - //tab[i = (n - 1) & hash对应下标的第一个节点 - if ((p = tab[i = (n - 1) & hash]) == null) - // 无哈希冲突的情况下,将value直接封装为Node并赋值 - tab[i] = newNode(hash, key, value, null); - else { - Node e; K k; - // 步骤③ 节点的key相同,直接覆盖节点 - if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) - e = p; - // 步骤④ 判断该链为红黑树 - else if (p instanceof TreeNode) - // p是红黑树类型,则调用putTreeVal方式赋值 - e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); - // 步骤⑤ p非红黑树类型,该链为链表 - else { - // index 相同的情况下 - for (int binCount = 0; ; ++binCount) { - if ((e = p.next) == null) { - // 如果p的next为空,将新的value值添加至链表后面 - p.next = newNode(hash, key, value, null); - if (binCount >= TREEIFY_THRESHOLD - 1) - // 如果链表长度大于8,链表转化为红黑树,执行插入 - treeifyBin(tab, hash); - break; - } - // key相同则跳出循环 - if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) - break; - //就是移动指针方便继续取 p.next - - p = e; - } - } - if (e != null) { // existing mapping for key - V oldValue = e.value; - //根据规则选择是否覆盖value - if (!onlyIfAbsent || oldValue == null) - e.value = value; - afterNodeAccess(e); - return oldValue; - } - } - ++modCount; - // 步骤⑥:超过最大容量,就扩容 - if (++size > threshold) - // size大于加载因子,扩容 - resize(); - afterNodeInsertion(evict); - return null; - } -``` -在构造函数中最多也只是设置了initialCapacity、loadFactor的值,并没有初始化table,table的初始化工作是在put方法中进行的. -## 4.6 resize -![](https://img-blog.csdnimg.cn/img_convert/24a22ebcda082c0c13df1c2193ee18d6.png) -扩容(resize)就是重新计算容量,向HashMap对象里不停的添加元素,内部的数组无法装载更多的元素时,就需要扩大数组的长度. -当然Java里的数组是无法自动扩容的,方法是使用一个新的数组代替已有的容量小的数组 -``` - /** - * 该函数有2种使用情况:1.初始化哈希表 2.当前数组容量过小,需扩容 - */ -final Node[] resize() { - Node[] oldTab = table; - int oldCap = (oldTab == null) ? 0 : oldTab.length; - int oldThr = threshold; - int newCap, newThr = 0; - - // 针对情况2:若扩容前的数组容量超过最大值,则不再扩充 - if (oldCap > 0) { - if (oldCap >= MAXIMUM_CAPACITY) { - threshold = Integer.MAX_VALUE; - return oldTab; - } - // 针对情况2:若无超过最大值,就扩充为原来的2倍 - else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && - oldCap >= DEFAULT_INITIAL_CAPACITY) - //newCap设置为oldCap的2倍并小于MAXIMUM_CAPACITY,且大于默认值, 新的threshold增加为原来的2倍 - newThr = oldThr << 1; // double threshold - } - - // 针对情况1:初始化哈希表(采用指定 or 默认值) - else if (oldThr > 0) // initial capacity was placed in threshold - // threshold>0, 将threshold设置为newCap,所以要用tableSizeFor方法保证threshold是2的幂次方 - newCap = oldThr; - else { // zero initial threshold signifies using defaults - // 默认初始化 - newCap = DEFAULT_INITIAL_CAPACITY; - newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); - } - - // 计算新的resize上限 - if (newThr == 0) { - // newThr为0,newThr = newCap * 0.75 - float ft = (float)newCap * loadFactor; - newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? - (int)ft : Integer.MAX_VALUE); - } - threshold = newThr; - @SuppressWarnings({"rawtypes","unchecked"}) - // 新生成一个table数组 - Node[] newTab = (Node[])new Node[newCap]; - table = newTab; - if (oldTab != null) { - // oldTab 复制到 newTab - for (int j = 0; j < oldCap; ++j) { - Node e; - if ((e = oldTab[j]) != null) { - oldTab[j] = null; - if (e.next == null) - // 链表只有一个节点,直接赋值 - //为什么要重新Hash呢?因为长度扩大以后,Hash的规则也随之改变。 - newTab[e.hash & (newCap - 1)] = e; - else if (e instanceof TreeNode) - // e为红黑树的情况 - ((TreeNode)e).split(this, newTab, j, oldCap); - else { // preserve order链表优化重hash的代码块 - Node loHead = null, loTail = null; - Node hiHead = null, hiTail = null; - Node next; - do { - next = e.next; - // 原索引 - if ((e.hash & oldCap) == 0) { - if (loTail == null) - loHead = e; - else - loTail.next = e; - loTail = e; - } - // 原索引 + oldCap - else { - if (hiTail == null) - hiHead = e; - else - hiTail.next = e; - hiTail = e; - } - } while ((e = next) != null); - // 原索引放到bucket里 - if (loTail != null) { - loTail.next = null; - newTab[j] = loHead; - } - // 原索引+oldCap放到bucket里 - if (hiTail != null) { - hiTail.next = null; - newTab[j + oldCap] = hiHead; - } - } - } - } - } - return newTab; - } -``` -![图片发自简书App](https://img-blog.csdnimg.cn/img_convert/dd74408150f2d30938f08296a6501d71.png) - - - -## 4.7 remove方法 -remove(key) 方法 和 remove(key, value) 方法都是通过调用removeNode的方法来实现删除元素的 -```java - final Node removeNode(int hash, Object key, Object value, - boolean matchValue, boolean movable) { - Node[] tab; Node p; int n, index; - if ((tab = table) != null && (n = tab.length) > 0 && - (p = tab[index = (n - 1) & hash]) != null) { - Node node = null, e; K k; V v; - if (p.hash == hash && - ((k = p.key) == key || (key != null && key.equals(k)))) - // index 元素只有一个元素 - node = p; - else if ((e = p.next) != null) { - if (p instanceof TreeNode) - // index处是一个红黑树 - node = ((TreeNode)p).getTreeNode(hash, key); - else { - // index处是一个链表,遍历链表返回node - do { - if (e.hash == hash && - ((k = e.key) == key || - (key != null && key.equals(k)))) { - node = e; - break; - } - p = e; - } while ((e = e.next) != null); - } - } - // 分不同情形删除节点 - if (node != null && (!matchValue || (v = node.value) == value || - (value != null && value.equals(v)))) { - if (node instanceof TreeNode) - ((TreeNode)node).removeTreeNode(this, tab, movable); - else if (node == p) - tab[index] = node.next; - else - p.next = node.next; - ++modCount; - --size; - afterNodeRemoval(node); - return node; - } - } - return null; - } -``` -## 4.8 get -```java -/** - * 函数原型 - * 作用:根据键key,向HashMap获取对应的值 - */ - map.get(key); - - - /** - * 源码分析 - */ - public V get(Object key) { - Node e; - // 1. 计算需获取数据的hash值 - // 2. 通过getNode()获取所查询的数据 ->>分析1 - // 3. 获取后,判断数据是否为空 - return (e = getNode(hash(key), key)) == null ? null : e.value; -} - -/** - * 分析1:getNode(hash(key), key)) - */ -final Node getNode(int hash, Object key) { - Node[] tab; Node first, e; int n; K k; - - // 1. 计算存放在数组table中的位置 - if ((tab = table) != null && (n = tab.length) > 0 && - (first = tab[(n - 1) & hash]) != null) { - - // 4. 通过该函数,依次在数组、红黑树、链表中查找(通过equals()判断) - // a. 先在数组中找,若存在,则直接返回 - if (first.hash == hash && // always check first node - ((k = first.key) == key || (key != null && key.equals(k)))) - return first; - - // b. 若数组中没有,则到红黑树中寻找 - if ((e = first.next) != null) { - // 在树中get - if (first instanceof TreeNode) - return ((TreeNode)first).getTreeNode(hash, key); - - // c. 若红黑树中也没有,则通过遍历,到链表中寻找 - do { - if (e.hash == hash && - ((k = e.key) == key || (key != null && key.equals(k)))) - return e; - } while ((e = e.next) != null); - } - } - return null; -} -``` -> 在JDK1.7及以前的版本中,HashMap里是没有红黑树的实现的,在JDK1.8中加入了红黑树是为了防止哈希表碰撞攻击,当链表链长度为8时,及时转成红黑树,提高map的效率 - -如果某个桶中的记录过大的话(当前是TREEIFY_THRESHOLD = 8),HashMap会动态的使用一个专门的treemap实现来替换掉它。这样做的结果会更好,是O(logn),而不是糟糕的O(n)。它是如何工作的? -前面产生冲突的那些KEY对应的记录只是简单的追加到一个链表后面,这些记录只能通过遍历来进行查找。但是超过这个阈值后HashMap开始将列表升级成一个二叉树,使用哈希值作为树的分支变量,如果两个哈希值不等,但指向同一个桶的话,较大的那个会插入到右子树里。如果哈希值相等,HashMap希望key值最好是实现了Comparable接口的,这样它可以按照顺序来进行插入。这对HashMap的key来说并不是必须的,不过如果实现了当然最好。如果没有实现这个接口,在出现严重的哈希碰撞的时候,你就并别指望能获得性能提升了。 - -这个性能提升有什么用处?比方说恶意的程序,如果它知道我们用的是哈希算法,它可能会发送大量的请求,导致产生严重的哈希碰撞。然后不停的访问这些key就能显著的影响服务器的性能,这样就形成了一次拒绝服务攻击(DoS)。JDK 8中从O(n)到O(logn)的飞跃,可以有效地防止类似的攻击,同时也让HashMap性能的可预测性稍微增强了一些 -```java -/** - * 源码分析:resize(2 * table.length) - * 作用:当容量不足时(容量 > 阈值),则扩容(扩到2倍) - */ - void resize(int newCapacity) { - - // 1. 保存旧数组(old table) - Entry[] oldTable = table; - - // 2. 保存旧容量(old capacity ),即数组长度 - int oldCapacity = oldTable.length; - - // 3. 若旧容量已经是系统默认最大容量了,那么将阈值设置成整型的最大值,退出 - if (oldCapacity == MAXIMUM_CAPACITY) { - threshold = Integer.MAX_VALUE; - return; - } - - // 4. 根据新容量(2倍容量)新建1个数组,即新table - Entry[] newTable = new Entry[newCapacity]; - - // 5. (重点分析)将旧数组上的数据(键值对)转移到新table中,从而完成扩容 ->>分析1.1 - transfer(newTable); - - // 6. 新数组table引用到HashMap的table属性上 - table = newTable; - - // 7. 重新设置阈值 - threshold = (int)(newCapacity * loadFactor); -} - - /** - * 分析1.1:transfer(newTable); - * 作用:将旧数组上的数据(键值对)转移到新table中,从而完成扩容 - * 过程:按旧链表的正序遍历链表、在新链表的头部依次插入 - */ -void transfer(Entry[] newTable) { - // 1. src引用了旧数组 - Entry[] src = table; - - // 2. 获取新数组的大小 = 获取新容量大小 - int newCapacity = newTable.length; - - // 3. 通过遍历 旧数组,将旧数组上的数据(键值对)转移到新数组中 - for (int j = 0; j < src.length; j++) { - // 3.1 取得旧数组的每个元素 - Entry e = src[j]; - if (e != null) { - // 3.2 释放旧数组的对象引用(for循环后,旧数组不再引用任何对象) - src[j] = null; - - do { - // 3.3 遍历 以该数组元素为首 的链表 - // 注:转移链表时,因是单链表,故要保存下1个结点,否则转移后链表会断开 - Entry next = e.next; - // 3.3 重新计算每个元素的存储位置 - int i = indexFor(e.hash, newCapacity); - // 3.4 将元素放在数组上:采用单链表的头插入方式 = 在链表头上存放数据 = 将数组位置的原有数据放在后1个指针、将需放入的数据放到数组位置中 - // 即 扩容后,可能出现逆序:按旧链表的正序遍历链表、在新链表的头部依次插入 - e.next = newTable[i]; - newTable[i] = e; - // 访问下1个Entry链上的元素,如此不断循环,直到遍历完该链表上的所有节点 - e = next; - } while (e != null); - // 如此不断循环,直到遍历完数组上的所有数据元素 - } - } - } -``` -从上面可看出:在扩容resize()过程中,在将旧数组上的数据 转移到 新数组上时,转移数据操作 = 按旧链表的正序遍历链表、在新链表的头部依次插入,即在转移数据、扩容后,容易出现链表逆序的情况 - ->`设重新计算存储位置后不变,即扩容前 = 1->2->3,扩容后 = 3->2->1` - -此时若并发执行 put 操作,一旦出现扩容情况,则 容易出现 环形链表,从而在获取数据、遍历链表时 形成死循环(Infinite Loop),即死锁 -![](https://img-blog.csdnimg.cn/img_convert/cd3bbd816bcefb360280b591d5cf41cf.png) -![image.png](https://img-blog.csdnimg.cn/img_convert/cb6354c50e5af1ef24c1ab36b802217e.png) -![](https://img-blog.csdnimg.cn/img_convert/5e35431f5f3c179e389f5528e1b03d42.png) -![为什么 HashMap 中 String、Integer 这样的包装类适合作为 key 键](https://img-blog.csdnimg.cn/img_convert/38321f907a385a91616ff972204237d4.png) -## 4.9 getOrDefault -getOrDefault() 方法获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值。 -![](https://img-blog.csdnimg.cn/55e1bdebe6cd4fc9aaa22d6662c501e4.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16) -# 5 单线程rehash -单线程情况下,rehash无问题 -[![HashMap rehash single thread](https://img-blog.csdnimg.cn/img_convert/50437495a5eda75989d04de58316b1f7.png)](http://www.jasongj.com/img/java/concurrenthashmap/single_thread_rehash.png) -# 6 多线程并发下的rehash - -这里假设有两个线程同时执行了put操作并引发了rehash,执行了transfer方法,并假设线程一进入transfer方法并执行完next = e.next后,因为线程调度所分配时间片用完而“暂停”,此时线程二完成了transfer方法的执行。此时状态如下。 - -[![HashMap rehash multi thread step 1](https://img-blog.csdnimg.cn/img_convert/a5aaefdb773217a54d29cbe9809e2aa9.png)](http://www.jasongj.com/img/java/concurrenthashmap/multi_thread_rehash_1.png) -接着线程1被唤醒,继续执行第一轮循环的剩余部分 -``` -e.next = newTable[1] = null -newTable[1] = e = key(5) -e = next = key(9) -``` -结果如下图所示 -[![HashMap rehash multi thread step 2](https://img-blog.csdnimg.cn/img_convert/a261863e3e27077ba7b3223a3914f0de.png)](http://www.jasongj.com/img/java/concurrenthashmap/multi_thread_rehash_2.png) - -接着执行下一轮循环,结果状态图如下所示 -[![HashMap rehash multi thread step 3](https://img-blog.csdnimg.cn/img_convert/03cea3cdb5a9477ca25e98ee6f37cf43.png)](http://www.jasongj.com/img/java/concurrenthashmap/multi_thread_rehash_3.png) - -继续下一轮循环,结果状态图如下所示 -[![HashMap rehash multi thread step 4](https://img-blog.csdnimg.cn/img_convert/22e59112a7635a44dff4fa42a7e6a840.png)](http://www.jasongj.com/img/java/concurrenthashmap/multi_thread_rehash_4.png) - -此时循环链表形成,并且key(11)无法加入到线程1的新数组。在下一次访问该链表时会出现死循环。 -# 7 Fast-fail -## 产生原因 - -在使用迭代器的过程中如果HashMap被修改,那么`ConcurrentModificationException`将被抛出,也即Fast-fail策略。 - -当HashMap的iterator()方法被调用时,会构造并返回一个新的EntryIterator对象,并将EntryIterator的expectedModCount设置为HashMap的modCount(该变量记录了HashMap被修改的次数)。 -``` -HashIterator() { - expectedModCount = modCount; - if (size > 0) { // advance to first entry - Entry[] t = table; - while (index < t.length && (next = t[index++]) == null) - ; - } -} -``` - - -在通过该Iterator的next方法访问下一个Entry时,它会先检查自己的expectedModCount与HashMap的modCount是否相等,如果不相等,说明HashMap被修改,直接抛出`ConcurrentModificationException`。该Iterator的remove方法也会做类似的检查。该异常的抛出意在提醒用户及早意识到线程安全问题。 - -## 线程安全解决方案 -单线程条件下,为避免出现`ConcurrentModificationException`,需要保证只通过HashMap本身或者只通过Iterator去修改数据,不能在Iterator使用结束之前使用HashMap本身的方法修改数据。因为通过Iterator删除数据时,HashMap的modCount和Iterator的expectedModCount都会自增,不影响二者的相等性。如果是增加数据,只能通过HashMap本身的方法完成,此时如果要继续遍历数据,需要重新调用iterator()方法从而重新构造出一个新的Iterator,使得新Iterator的expectedModCount与更新后的HashMap的modCount相等。 - -多线程条件下,可使用`Collections.synchronizedMap`方法构造出一个同步Map,或者直接使用线程安全的ConcurrentHashMap。 \ No newline at end of file diff --git "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/ReentrantLock\346\272\220\347\240\201\350\247\243\346\236\220.md" "b/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/ReentrantLock\346\272\220\347\240\201\350\247\243\346\236\220.md" index ad32141823..a1b5860985 100644 --- "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/ReentrantLock\346\272\220\347\240\201\350\247\243\346\236\220.md" +++ "b/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/ReentrantLock\346\272\220\347\240\201\350\247\243\346\236\220.md" @@ -74,79 +74,90 @@ ReentrantLock 就负责实现这些接口,使用时,直接调用的也是这 ## 4.2 FairSync - 公平锁 只实现 *lock* 和 *tryAcquire* 两个方法 ### 4.2.1 lock -lock 方法加锁成功,直接返回,所以可以继续执行业务逻辑。 - 公平模式的 lock ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzE3LzE3MjIxYzJhNGEyZmViNzY?x-oss-process=image/format,png) + 直接调用 acquire,而没有像非公平模式先试图获取,因为这样可能导致违反“公平”的语义:在已等待在队列中的线程之前获取了锁。 -*acquire* 是 AQS 的方法,表示先尝试获得锁,失败之后进入同步队列阻塞等待。 +*acquire* 是 AQS 的方法,表示先尝试获得锁,失败之后进入同步队列阻塞等待,详情见本专栏的上一文 ### 4.2.2 tryAcquire +公平模式的 *tryAcquire*。不要授予访问权限,除非递归调用或没有等待线程或是第一个调用的。 - 该方法是 AQS 在 acquire 方法中留给子类去具体实现的 -![](https://img-blog.csdnimg.cn/20210705225313380.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) -#### 公平模式 -不要授予访问权限,除非递归调用或没有等待线程或是第一个调用的。 +![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzE3LzE3MjIxYzJhMjJmYmVkZWM?x-oss-process=image/format,png) + +话不多说,看源码: ```java protected final boolean tryAcquire(int acquires) { - // 获取当前的线程 + // 获取当前的线程 final Thread current = Thread.currentThread(); - // 获取 state 锁的状态(volatile 读语义) + // 获取 state 锁的状态 int c = getState(); // state == 0 => 尚无线程获取锁 if (c == 0) { - // 判断 AQS 的同步对列里是否有线程等待 + // 判断 AQS 的同步对列里是否有线程等待,若没有则直接 CAS 获取锁 if (!hasQueuedPredecessors() && - // 若没有则直接 CAS(保证原子性,线程安全) 获取锁 compareAndSetState(0, acquires)) { // 获取锁成功,设置独占线程 setExclusiveOwnerThread(current); return true; } } - // 已经获取锁的是否为当前的线程? + // 判断已经获取锁是否为当前的线程 else if (current == getExclusiveOwnerThread()) { // 锁的重入, 即 state 加 1 int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); - // 已经获取 lock,所以这里不考虑并发 setState(nextc); return true; } return false; } ``` -和Sync#nonfairTryAcquire类似,唯一不同的是当发现锁未被占用时,使用 **hasQueuedPredecessors** 确保了公平性。 +和 Sync 的 *nonfairTryAcquire* 方法实现类似,唯一不同的是当发现锁未被占用时,使用 *hasQueuedPredecessors* 确保了公平性。 #### hasQueuedPredecessors -判断当前线程是不是属于同步队列的头节点的下一个节点(头节点是释放锁的节点): -- 是(返回false),符合FIFO,可以获得锁 -- 不是(返回true),则继续等待 +会判断当前线程是不是属于同步队列的头节点的下一个节点(头节点是释放锁的节点) +- 如果是(返回false),符合FIFO,可以获得锁 +- 如果不是(返回true),则继续等待 ```java -public final boolean hasQueuedPredecessors() { - // 这种方法的正确性取决于头在尾之前初始化和头初始化。如果当前线程是队列中的第一个线程,则next是精确的 - Node t = tail; // 按反初始化顺序读取字段 - Node h = head; - Node s; - return h != t && - ((s = h.next) == null || s.thread != Thread.currentThread()); -} + public final boolean hasQueuedPredecessors() { + // 这种方法的正确性取决于头在尾之前初始化和头初始化。如果当前线程是队列中的第一个线程,则next是精确的 + Node t = tail; // 按反初始化顺序读取字段 + Node h = head; + Node s; + return h != t && + ((s = h.next) == null || s.thread != Thread.currentThread()); + } ``` + + + # 5 nonfairTryAcquire -执行非公平的 *tryLock*。 *tryAcquire* 是在子类中实现的,但是都需要对*trylock* 方法进行非公平的尝试。 +执行非公平的 *tryLock*。 +*tryAcquire* 是在子类中实现的,但是都需要对*trylock* 方法进行非公平的尝试。 + ```java final boolean nonfairTryAcquire(int acquires) { + // 获取当前的线程 final Thread current = Thread.currentThread(); + // 获取 AQS 中的 state 字段 int c = getState(); + // state 为 0,表示同步器的锁尚未被持有 if (c == 0) { - // 这里可能有竞争,所以可能失败 + // CAS state 获取锁(这里可能有竞争,所以可能失败) if (compareAndSetState(0, acquires)) { // 获取锁成功, 设置获取独占锁的线程 setExclusiveOwnerThread(current); + // 直接返回 true return true; } } + // 判断现在获取独占锁的线程是否为当前线程(可重入锁的体现) else if (current == getExclusiveOwnerThread()) { + // state 计数加1(重入获取锁) int nextc = c + acquires; - if (nextc < 0) - throw new Error("Maximum lock count exceeded"); + if (nextc < 0) // 整型溢出 + throw new Error("Maximum lock count exceeded"); + // 已经获取 lock,所以这里不考虑并发 setState(nextc); return true; } @@ -154,7 +165,6 @@ final boolean nonfairTryAcquire(int acquires) { } ``` 无参的 *tryLock* 调用的就是此方法 - # 6 tryLock ## 6.1 无参 Lock 接口中定义的方法。 @@ -166,6 +176,19 @@ Lock 接口中定义的方法。 如果当前线程已经持有该锁,那么持有计数将增加1,方法返回true。 如果锁被另一个线程持有,那么这个方法将立即返回值false。 +- 典型的使用方法 +```java + Lock lock = ...; + if (lock.tryLock()) { + try { + // manipulate protected state + } finally { + lock.unlock(); + } + } else { + // 执行可选的操作 + } +``` ## 6.2 有参 - 提供了超时时间的入参,在时间内,仍没有得到锁,会返回 false ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzE3LzE3MjIxYzJhMzZhNTAwMzQ?x-oss-process=image/format,png) @@ -191,4 +214,6 @@ protected final boolean tryRelease(int releases) { setState(c); return free; } -``` \ No newline at end of file +``` +# 8 总结 +AQS 搭建了整个锁架构,子类锁的实现只需要根据场景,实现 AQS 对应的方法即可。 \ No newline at end of file diff --git "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/ThreadLocal.md" "b/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/ThreadLocal.md" deleted file mode 100644 index 5f5a4c37b5..0000000000 --- "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/ThreadLocal.md" +++ /dev/null @@ -1,246 +0,0 @@ - -# 1 前言 - -此类提供线程本地变量,与普通变量不同,因为每个访问一个变量(通过其get或set方法)的线程都有其自己的,独立初始化的变量副本。 -ThreadLocal 实例通常是期望将状态与线程(例如,用户ID或事务ID)关联的类中的 private static 字段。 - -例如,下面的类生成每个线程本地的唯一标识符。线程的ID是在第一次调用ThreadId.get() 时赋值的,并且在以后的调用中保持不变。 - -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzcvMTcxZWZhYzdiMDdiMDMzNw?x-oss-process=image/format,png) - -只要线程是活跃的并且 ThreadLocal 实例是可访问的,则每个线程都对其线程本地变量的副本持有隐式的引用。线程消失后,线程本地实例的所有副本都会被 GC(除非存在对这些副本的其他引用)。 - -# 2 继续体系 -- 继承?不存在的,这其实也是 java.lang 包下的工具类,但是 ThreadLocal 定义带有泛型,说明可以储存任意格式的数据。 -![](https://img-blog.csdnimg.cn/20210615235535658.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) - - -# 3 属性 -ThreadLocal 依赖于附加到每个线程(Thread.threadLocals和InheritableThreadLocals)的线程线性探测哈希表。 - -## threadLocalHashCode -ThreadLocal 对象充当key,通过 threadLocalHashCode 进行搜索。这是一个自定义哈希码(仅在ThreadLocalMaps 中有用),它消除了在相同线程使用连续构造的threadlocal的常见情况下的冲突,而在不太常见的情况下仍然表现良好。 - -ThreadLocal 通过这样的 hashCode,计算当前 ThreadLocal 在 ThreadLocalMap 中的索引 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzcvMTcxZWZhYzdiNDcxM2Q2Mw?x-oss-process=image/format,png) -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzcvMTcxZWZhYzdhZjZjMDQwMg?x-oss-process=image/format,png) - -- 连续生成的哈希码之间的差值,该值的设定参考文章[ThreadLocal的hash算法(关于 0x61c88647)](https://juejin.im/post/5cced289f265da03804380f2) -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzcvMTcxZWZhYzdiNmUwNjA0NQ?x-oss-process=image/format,png) - -- 注意 static 修饰。ThreadLocalMap 会被 set 多个 ThreadLocal ,而多个 ThreadLocal 就根据 threadLocalHashCode 区分 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzcvMTcxZWZhYzdkMTkzOWUwMw?x-oss-process=image/format,png) -# 4 ThreadLocalMap -自定义的哈希表,仅适用于维护线程本地的值。没有操作导出到ThreadLocal类之外。 -该类包私有,允许在 Thread 类中的字段声明。为帮助处理非常长的使用寿命,哈希表节点使用 WeakReferences 作为key。 -但由于不使用引用队列,因此仅在表空间不足时,才保证删除过时的节点。 -```java -static class ThreadLocalMap { - - /** - * 此哈希表中的节点使用其主引用字段作为key(始终是一个 ThreadLocal 对象),继承了 WeakReference。 - * 空键(即entry.get()== null)意味着不再引用该键,因此可以从表中删除该节点。 - * 在下面的代码中,此类节点称为 "stale entries" - */ - static class Entry extends WeakReference> { - /** 与此 ThreadLocal 关联的值 */ - Object value; - - Entry(ThreadLocal k, Object v) { - super(k); - value = v; - } - } - - private static final int INITIAL_CAPACITY = 16; - - private Entry[] table; - - private int size = 0; - - private int threshold; // 默认为 0 -``` -## 特点 -- key 是 ThreadLocal 的引用 -- value 是 ThreadLocal 保存的值 -- 数组的数据结构 -# 5 set -## 5.1 ThreadLocal#set -将此线程本地变量的当前线程副本设置为指定值。子类无需重写此方法,而仅依靠initialValue方法设置线程本地变量的值。 -![](https://img-blog.csdnimg.cn/20210616000550930.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) - -### 执行流程 -1. 获取当前线程 -2. 获取线程所对应的ThreadLocalMap。每个线程都是独立的,所以该方法天然线程安全 -3. 判断 map 是否为 null - - 否,K.V 对赋值,k 为this(即当前的 ThreaLocal 对象) - - 是,初始化一个 ThreadLocalMap 来维护 K.V 对 - -来具体看看ThreadLocalMap中的 set - -## 5.2 ThreadLocalMap#set -```java -private void set(ThreadLocal key, Object value) { - // 新引用指向 table - Entry[] tab = table; - int len = tab.length; - // 获取对应 ThreadLocal 在table 中的索引 - int i = key.threadLocalHashCode & (len-1); - - /** - * 从该下标开始循环遍历 - * 1、如遇相同key,则直接替换value - * 2、如果该key已经被回收失效,则替换该失效的key - */ - for (Entry e = tab[i]; - e != null; - e = tab[i = nextIndex(i, len)]) { - ThreadLocal k = e.get(); - // 找到内存地址一样的 ThreadLocal,直接替换 - if (k == key) { - e.value = value; - return; - } - // 若 k 为 null,说明 ThreadLocal 被清理了,则替换当前失效的 k - if (k == null) { - replaceStaleEntry(key, value, i); - return; - } - } - // 找到空位,创建节点并插入 - tab[i] = new Entry(key, value); - // table内元素size自增 - int sz = ++size; - // 达到阈值(数组大小的三分之二)时,执行扩容 - if (!cleanSomeSlots(i, sz) && sz >= threshold) - rehash(); -} -``` -注意通过 hashCode 计算的索引位置 i 处如果已经有值了,会从 i 开始,通过 +1 不断的往后寻找,直到找到索引位置为空的地方,把当前 ThreadLocal 作为 key 放进去。 - -# 6 get -```java -public T get() { - // 获取当前线程 - Thread t = Thread.currentThread(); - // 获取当前线程对应的ThreadLocalMap - ThreadLocalMap map = getMap(t); - - // 如果map不为空 - if (map != null) { - // 取得当前ThreadLocal对象对应的Entry - ThreadLocalMap.Entry e = map.getEntry(this); - // 如果不为空,读取当前 ThreadLocal 中保存的值 - if (e != null) { - @SuppressWarnings("unchecked") - T result = (T)e.value; - return result; - } - } - // 否则都执行 setInitialValue - return setInitialValue(); -} -``` -### setInitialValue -```java -private T setInitialValue() { - // 获取初始值,一般是子类重写 - T value = initialValue(); - - // 获取当前线程 - Thread t = Thread.currentThread(); - - // 获取当前线程对应的ThreadLocalMap - ThreadLocalMap map = getMap(t); - - // 如果map不为null - if (map != null) - - // 调用ThreadLocalMap的set方法进行赋值 - map.set(this, value); - - // 否则创建个ThreadLocalMap进行赋值 - else - createMap(t, value); - return value; -} -``` - -接着我们来看下 -## ThreadLocalMap#getEntry -```java -// 得到当前 thradLocal 对应的值,值的类型是由 thradLocal 的泛型决定的 -// 由于 thradLocalMap set 时解决数组索引位置冲突的逻辑,导致 thradLocalMap get 时的逻辑也是对应的 -// 首先尝试根据 hashcode 取模数组大小-1 = 索引位置 i 寻找,找不到的话,自旋把 i+1,直到找到索引位置不为空为止 -private Entry getEntry(ThreadLocal key) { - // 计算索引位置:ThreadLocal 的 hashCode 取模数组大小-1 - int i = key.threadLocalHashCode & (table.length - 1); - Entry e = table[i]; - // e 不为空,并且 e 的 ThreadLocal 的内存地址和 key 相同,直接返回,否则就是没有找到,继续通过 getEntryAfterMiss 方法找 - if (e != null && e.get() == key) - return e; - else - // 这个取数据的逻辑,是因为 set 时数组索引位置冲突造成的 - return getEntryAfterMiss(key, i, e); -} -// 自旋 i+1,直到找到为止 -private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) { - Entry[] tab = table; - int len = tab.length; - // 在大量使用不同 key 的 ThreadLocal 时,其实还蛮耗性能的 - while (e != null) { - ThreadLocal k = e.get(); - // 内存地址一样,表示找到了 - if (k == key) - return e; - // 删除没用的 key - if (k == null) - expungeStaleEntry(i); - // 继续使索引位置 + 1 - else - i = nextIndex(i, len); - e = tab[i]; - } - return null; -} -``` -# 6 扩容 -ThreadLocalMap 中的 ThreadLocal 的个数超过阈值时,ThreadLocalMap 就要开始扩容了,我们一起来看下扩容的逻辑: -```java -private void resize() { - // 拿出旧的数组 - Entry[] oldTab = table; - int oldLen = oldTab.length; - // 新数组的大小为老数组的两倍 - int newLen = oldLen * 2; - // 初始化新数组 - Entry[] newTab = new Entry[newLen]; - int count = 0; - // 老数组的值拷贝到新数组上 - for (int j = 0; j < oldLen; ++j) { - Entry e = oldTab[j]; - if (e != null) { - ThreadLocal k = e.get(); - if (k == null) { - e.value = null; // Help the GC - } else { - // 计算 ThreadLocal 在新数组中的位置 - int h = k.threadLocalHashCode & (newLen - 1); - // 如果索引 h 的位置值不为空,往后+1,直到找到值为空的索引位置 - while (newTab[h] != null) - h = nextIndex(h, newLen); - // 给新数组赋值 - newTab[h] = e; - count++; - } - } - } - // 给新数组初始化下次扩容阈值,为数组长度的三分之二 - setThreshold(newLen); - size = count; - table = newTab; -} -``` -扩容时是绝对没有线程安全问题的,因为 ThreadLocalMap 是线程的一个属性,一个线程同一时刻只能对 ThreadLocalMap 进行操作,因为同一个线程执行业务逻辑必然是串行的,那么操作 ThreadLocalMap 必然也是串行的。 -# 7 总结 -我们在写中间件的时候经常会用到,比如说流程引擎中上下文的传递,调用链ID的传递等。 \ No newline at end of file diff --git "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/\343\200\220\346\255\273\347\243\225JDK\346\272\220\347\240\201\343\200\221ThreadPoolExecutor\346\272\220\347\240\201\344\277\235\345\247\206\347\272\247\350\257\246\350\247\243.md" "b/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/\343\200\220\346\255\273\347\243\225JDK\346\272\220\347\240\201\343\200\221ThreadPoolExecutor\346\272\220\347\240\201\344\277\235\345\247\206\347\272\247\350\257\246\350\247\243.md" deleted file mode 100644 index 6968385400..0000000000 --- "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/\343\200\220\346\255\273\347\243\225JDK\346\272\220\347\240\201\343\200\221ThreadPoolExecutor\346\272\220\347\240\201\344\277\235\345\247\206\347\272\247\350\257\246\350\247\243.md" +++ /dev/null @@ -1,686 +0,0 @@ -位运算表示线程池状态,因为位运算是改变当前值的一种高效手段。 - -# 属性 -## 线程池状态 -Integer 有32位: -- 最左边3位表示线程池状态,可表示从0至7的8个不同数值 -- 最右边29位表工作线程数 -```java -private static final int COUNT_BITS = Integer.SIZE - 3; -``` - - -线程池的状态用高3位表示,其中包括了符号位。五种状态的十进制值按从小到大依次排序为: - -```bash -RUNNING < SHUTDOWN < STOP < TIDYING =核心线程数 或线程创建失败,则将当前任务放到工作队列中 -// 只有线程池处于 RUNNING 态,才执行后半句 : 置入队列 -if (isRunning(c) && workQueue.offer(command)) { - int recheck = ctl.get(); - - // 只有线程池处于 RUNNING 态,才执行后半句 : 置入队列 - if (! isRunning(recheck) && remove(command)) - reject(command); - // 若之前的线程已被消费完,新建一个线程 - else if (workerCountOf(recheck) == 0) - addWorker(null, false); -// 核心线程和队列都已满,尝试创建一个新线程 -} -else if (!addWorker(command, false)) - // 抛出RejectedExecutionException异常 - // 若 addWorker 返回是 false,即创建失败,则唤醒拒绝策略. - reject(command); -} -``` -发生拒绝的理由有两个 -( 1 )线程池状态为非RUNNING状态 -(2)等待队列已满。 - -下面继续分析`addWorker` - -## addWorker 源码解析 -原子性地检查 runState 和 workerCount,通过返回 false 来防止在不应该添加线程时出现误报。 - -根据当前线程池状态,检查是否可以添加新的线程: -- 若可 -则创建并启动任务;若一切正常则返回true; -- 返回false的可能原因: -1. 线程池没有处`RUNNING`态 -2. 线程工厂创建新的任务线程失败 -### 参数 -- firstTask -外部启动线程池时需要构造的第一个线程,它是线程的母体 -- core -新增工作线程时的判断指标 - - true -需要判断当前`RUNNING`态的线程是否少于`corePoolsize` - - false -需要判断当前`RUNNING`态的线程是否少于`maximumPoolsize` -### JDK8源码 -```java -private boolean addWorker(Runnable firstTask, boolean core) { - // 1. 不需要任务预定义的语法标签,响应下文的continue retry - // 快速退出多层嵌套循环 - retry: - // 外自旋,判断线程池的运行状态 - for (;;) { - int c = ctl.get(); - int rs = runStateOf(c); - // 2. 若RUNNING态,则条件为false,不执行后面判断 - // 若STOP及以上状态,或firstTask初始线程非空,或队列为空 - // 都会直接返回创建失败 - // Check if queue empty only if necessary. - if (rs >= SHUTDOWN && - ! (rs == SHUTDOWN && - firstTask == null && - ! workQueue.isEmpty())) - return false; - - for (;;) { - int wc = workerCountOf(c); - // 若超过最大允许线程数,则不能再添加新线程 - if (wc >= CAPACITY || - wc >= (core ? corePoolSize : maximumPoolSize)) - return false; - // 3. 将当前活动线程数+1 - if (compareAndIncrementWorkerCount(c)) - break retry; - // 线程池状态和工作线程数是可变化的,需经常读取最新值 - c = ctl.get(); // Re-read ctl - // 若已关闭,则再次从retry 标签处进入,在第2处再做判断(第4处) - if (runStateOf(c) != rs) - continue retry; - //如果线程池还是RUNNING态,说明仅仅是第3处失败 -//继续循环执行(第5外) - // else CAS failed due to workerCount change; retry inner loop - } - } - - // 开始创建工作线程 - boolean workerStarted = false; - boolean workerAdded = false; - Worker w = null; - try { - // 利用Worker 构造方法中的线程池工厂创建线程,并封装成工作线程Worker对象 - // 和 AQS 有关!!! - w = new Worker(firstTask); - // 6. 注意这是Worker中的属性对象thread - final Thread t = w.thread; - if (t != null) { - // 在进行ThreadpoolExecutor的敏感操作时 - // 都需要持有主锁,避免在添加和启动线程时被干扰 - final ReentrantLock mainLock = this.mainLock; - mainLock.lock(); - try { - // Recheck while holding lock. - // Back out on ThreadFactory failure or if - // shut down before lock acquired. - int rs = runStateOf(ctl.get()); - // 当线程池状态为RUNNING 或SHUTDOWN - // 且firstTask 初始线程为空时 - if (rs < SHUTDOWN || - (rs == SHUTDOWN && firstTask == null)) { - if (t.isAlive()) // precheck that t is startable - throw new IllegalThreadStateException(); - workers.add(w); - int s = workers.size(); - // 整个线程池在运行期间的最大并发任务个数 - if (s > largestPoolSize) - // 更新为工作线程的个数 - largestPoolSize = s; - // 新增工作线程成功 - workerAdded = true; - } - } finally { - mainLock.unlock(); - } - if (workerAdded) { - // 看到亲切迷人的start方法了! - // 这并非线程池的execute 的command 参数指向的线程 - t.start(); - workerStarted = true; - } - } - } finally { - // 线程启动失败,把刚才第3处加,上的工作线程计数再减-回去 - if (! workerStarted) - addWorkerFailed(w); - } - return workerStarted; -} -``` -#### 第1处 -配合循环语句出现的标签,类似于goto语法作用。label 定义时,必须把标签和冒号的组合语句紧紧相邻定义在循环体之前,否则编译报错。目的是在实现多重循环时能够快速退出到任何一层。出发点似乎非常贴心,但在大型软件项目中,滥用标签行跳转的后果将是无法维护的! - - -在 **workerCount** 加1成功后,直接退出两层循环。 - -#### 第2处,这样的表达式不利于阅读,应如是 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyNzg1XzQ2ODU5NjgtMDg2ZTlkNWY5ZGEyYWZkNC5wbmc?x-oss-process=image/format,png) - -#### 第3处 -与第1处的标签呼应,`AtomicInteger`对象的加1操作是原子性的。`break retry`表 直接跳出与`retry` 相邻的这个循环体 - -#### 第4处 -此`continue`跳转至标签处,继续执行循环. -如果条件为false,则说明线程池还处于运行状态,即继续在`for(;)`循环内执行. - -#### 第5处 -`compareAndIncrementWorkerCount `方法执行失败的概率非常低. -即使失败,再次执行时成功的概率也是极高的,类似于自旋原理. -这里是先加1,创建失败再减1,这是轻量处理并发创建线程的方式; -如果先创建线程,成功再加1,当发现超出限制后再销毁线程,那么这样的处理方式明显比前者代价要大. - -#### 第6处 -`Worker `对象是工作线程的核心类实现。它实现了`Runnable`接口,并把本对象作为参数输入给`run()`中的`runWorker (this)`。所以内部属性线程`thread`在`start`的时候,即会调用`runWorker`。 - -```java -private final class Worker - extends AbstractQueuedSynchronizer - implements Runnable -{ - /** - * This class will never be serialized, but we provide a - * serialVersionUID to suppress a javac warning. - */ - private static final long serialVersionUID = 6138294804551838833L; - - /** Thread this worker is running in. Null if factory fails. */ - final Thread thread; - /** Initial task to run. Possibly null. */ - Runnable firstTask; - /** Per-thread task counter */ - volatile long completedTasks; - - /** - * Creates with given first task and thread from ThreadFactory. - * @param firstTask the first task (null if none) - */ - Worker(Runnable firstTask) { - setState(-1); // 直到调用runWorker前,禁止被中断 - this.firstTask = firstTask; - this.thread = getThreadFactory().newThread(this); - } - - /** 将主线程的 run 循环委托给外部的 runWorker 执行 */ - public void run() { - runWorker(this); - } - - // Lock methods - // - // The value 0 represents the unlocked state. - // The value 1 represents the locked state. - - protected boolean isHeldExclusively() { - return getState() != 0; - } - - protected boolean tryAcquire(int unused) { - if (compareAndSetState(0, 1)) { - setExclusiveOwnerThread(Thread.currentThread()); - return true; - } - return false; - } - - protected boolean tryRelease(int unused) { - setExclusiveOwnerThread(null); - setState(0); - return true; - } - - public void lock() { acquire(1); } - public boolean tryLock() { return tryAcquire(1); } - public void unlock() { release(1); } - public boolean isLocked() { return isHeldExclusively(); } - - void interruptIfStarted() { - Thread t; - if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) { - try { - t.interrupt(); - } catch (SecurityException ignore) { - } - } - } -} -``` -#### setState(-1)是为何 -设置个简单的状态,检查状态以防止中断。在调用停止线程池时会判断state 字段,决定是否中断之。 -![](https://img-blog.csdnimg.cn/20210713174701301.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)![](https://img-blog.csdnimg.cn/20210713175625198.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)![](https://img-blog.csdnimg.cn/20210713175645371.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)![](https://img-blog.csdnimg.cn/20210713175718745.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) -#### t 到底是谁? -![](https://img-blog.csdnimg.cn/20210713180707150.png) -# 源码分析 -```java - /** - * 检查是否可以根据当前池状态和给定的边界(核心或最大) - * 添加新工作线程。如果是这样,工作线程数量会相应调整,如果可能的话,一个新的工作线程创建并启动 - * 将firstTask作为其运行的第一项任务。 - * 如果池已停止此方法返回false - * 如果线程工厂在被访问时未能创建线程,也返回false - * 如果线程创建失败,或者是由于线程工厂返回null,或者由于异常(通常是在调用Thread.start()后的OOM)),我们干净地回滚。 - */ - private boolean addWorker(Runnable firstTask, boolean core) { - /** - * Check if queue empty only if necessary. - * - * 如果线程池已关闭,并满足以下条件之一,那么不创建新的 worker: - * 1. 线程池状态大于 SHUTDOWN,也就是 STOP, TIDYING, 或 TERMINATED - * 2. firstTask != null - * 3. workQueue.isEmpty() - * 简单分析下: - * 状态控制的问题,当线程池处于 SHUTDOWN ,不允许提交任务,但是已有任务继续执行 - * 当状态大于 SHUTDOWN ,不允许提交任务,且中断正在执行任务 - * 多说一句:若线程池处于 SHUTDOWN,但 firstTask 为 null,且 workQueue 非空,是允许创建 worker 的 - * - */ - if (rs >= SHUTDOWN && - ! (rs == SHUTDOWN && - firstTask == null && - ! workQueue.isEmpty())) - return false; - - for (;;) { - int wc = workerCountOf(c); - if (wc >= CAPACITY || - wc >= (core ? corePoolSize : maximumPoolSize)) - return false; - // 如果成功,那么就是所有创建线程前的条件校验都满足了,准备创建线程执行任务 - // 这里失败的话,说明有其他线程也在尝试往线程池中创建线程 - if (compareAndIncrementWorkerCount(c)) - break retry; - // 由于有并发,重新再读取一下 ctl - c = ctl.get(); // Re-read ctl - // 正常如果是 CAS 失败的话,进到下一个里层的for循环就可以了 - // 可如果是因为其他线程的操作,导致线程池的状态发生了变更,如有其他线程关闭了这个线程池 - // 那么需要回到外层的for循环 - if (runStateOf(c) != rs) - continue retry; - // else CAS failed due to workerCount change; retry inner loop - } - } - - /* * - * 到这里,我们认为在当前这个时刻,可以开始创建线程来执行任务 - */ - - // worker 是否已经启动 - boolean workerStarted = false; - // 是否已将这个 worker 添加到 workers 这个 HashSet 中 - boolean workerAdded = false; - Worker w = null; - try { - // 把 firstTask 传给 worker 的构造方法 - w = new Worker(firstTask); - // 取 worker 中的线程对象,Worker的构造方法会调用 ThreadFactory 来创建一个新的线程 - final Thread t = w.thread; - if (t != null) { - //先加锁 - final ReentrantLock mainLock = this.mainLock; - // 这个是整个类的全局锁,持有这个锁才能让下面的操作“顺理成章”, - // 因为关闭一个线程池需要这个锁,至少我持有锁的期间,线程池不会被关闭 - mainLock.lock(); - try { - // Recheck while holding lock. - // Back out on ThreadFactory failure or if - // shut down before lock acquired. - int rs = runStateOf(ctl.get()); - - // 小于 SHUTTDOWN 即 RUNNING - // 如果等于 SHUTDOWN,不接受新的任务,但是会继续执行等待队列中的任务 - if (rs < SHUTDOWN || - (rs == SHUTDOWN && firstTask == null)) { - // worker 里面的 thread 不能是已启动的 - if (t.isAlive()) // precheck that t is startable - throw new IllegalThreadStateException(); - // 加到 workers 这个 HashSet 中 - workers.add(w); - int s = workers.size(); - if (s > largestPoolSize) - largestPoolSize = s; - workerAdded = true; - } - } finally { - mainLock.unlock(); - } - // 若添加成功 - if (workerAdded) { - // 启动线程 - t.start(); - workerStarted = true; - } - } - } finally { - // 若线程没有启动,做一些清理工作,若前面 workCount 加了 1,将其减掉 - if (! workerStarted) - addWorkerFailed(w); - } - // 返回线程是否启动成功 - return workerStarted; - } -``` -看下 `addWorkFailed` -![](https://img-blog.csdnimg.cn/20210714141244398.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70) - - -![记录 workers 中的个数的最大值,因为 workers 是不断增加减少的,通过这个值可以知道线程池的大小曾经达到的最大值](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyNzA4XzQ2ODU5NjgtMDc4NDcyYjY4MmZjYzljZC5wbmc?x-oss-process=image/format,png) -继续看 -### runWorker -```java -// worker 线程启动后调用,while 循环(即自旋!)不断从等待队列获取任务并执行 -// worker 初始化时,可指定 firstTask,那么第一个任务也就可以不需要从队列中获取 -final void runWorker(Worker w) { - Thread wt = Thread.currentThread(); - // 该线程的第一个任务(若有) - Runnable task = w.firstTask; - w.firstTask = null; - // 允许中断 - w.unlock(); - - boolean completedAbruptly = true; - try { - // 循环调用 getTask 获取任务 - while (task != null || (task = getTask()) != null) { - w.lock(); - // 若线程池状态大于等于 STOP,那么意味着该线程也要中断 - /** - * 若线程池STOP,请确保线程 已被中断 - * 如果没有,请确保线程未被中断 - * 这需要在第二种情况下进行重新检查,以便在关中断时处理shutdownNow竞争 - */ - if ((runStateAtLeast(ctl.get(), STOP) || - (Thread.interrupted() && - runStateAtLeast(ctl.get(), STOP))) && - !wt.isInterrupted()) - wt.interrupt(); - try { - // 这是一个钩子方法,留给需要的子类实现 - beforeExecute(wt, task); - Throwable thrown = null; - try { - // 到这里终于可以执行任务了 - task.run(); - } catch (RuntimeException x) { - thrown = x; throw x; - } catch (Error x) { - thrown = x; throw x; - } catch (Throwable x) { - // 这里不允许抛出 Throwable,所以转换为 Error - thrown = x; throw new Error(x); - } finally { - // 也是一个钩子方法,将 task 和异常作为参数,留给需要的子类实现 - afterExecute(task, thrown); - } - } finally { - // 置空 task,准备 getTask 下一个任务 - task = null; - // 累加完成的任务数 - w.completedTasks++; - // 释放掉 worker 的独占锁 - w.unlock(); - } - } - completedAbruptly = false; - } finally { - // 到这里,需要执行线程关闭 - // 1. 说明 getTask 返回 null,也就是说,这个 worker 的使命结束了,执行关闭 - // 2. 任务执行过程中发生了异常 - // 第一种情况,已经在代码处理了将 workCount 减 1,这个在 getTask 方法分析中说 - // 第二种情况,workCount 没有进行处理,所以需要在 processWorkerExit 中处理 - processWorkerExit(w, completedAbruptly); - } -} -``` -看看 -### getTask() -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyNzgwXzQ2ODU5NjgtNWU5NDc3MzE5M2Q5Y2Y0OS5wbmc?x-oss-process=image/format,png) -```java -// 此方法有三种可能 -// 1. 阻塞直到获取到任务返回。默认 corePoolSize 之内的线程是不会被回收的,它们会一直等待任务 -// 2. 超时退出。keepAliveTime 起作用的时候,也就是如果这么多时间内都没有任务,那么应该执行关闭 -// 3. 如果发生了以下条件,须返回 null -// 池中有大于 maximumPoolSize 个 workers 存在(通过调用 setMaximumPoolSize 进行设置) -// 线程池处于 SHUTDOWN,而且 workQueue 是空的,前面说了,这种不再接受新的任务 -// 线程池处于 STOP,不仅不接受新的线程,连 workQueue 中的线程也不再执行 -private Runnable getTask() { - boolean timedOut = false; // Did the last poll() time out? - - for (;;) { - // 允许核心线程数内的线程回收,或当前线程数超过了核心线程数,那么有可能发生超时关闭 - - // 这里 break,是为了不往下执行后一个 if (compareAndDecrementWorkerCount(c)) - // 两个 if 一起看:如果当前线程数 wc > maximumPoolSize,或者超时,都返回 null - // 那这里的问题来了,wc > maximumPoolSize 的情况,为什么要返回 null? - // 换句话说,返回 null 意味着关闭线程。 - // 那是因为有可能开发者调用了 setMaximumPoolSize 将线程池的 maximumPoolSize 调小了 - - // 如果此 worker 发生了中断,采取的方案是重试 - // 解释下为什么会发生中断,这个读者要去看 setMaximumPoolSize 方法, - // 如果开发者将 maximumPoolSize 调小了,导致其小于当前的 workers 数量, - // 那么意味着超出的部分线程要被关闭。重新进入 for 循环,自然会有部分线程会返回 null - int c = ctl.get(); - int rs = runStateOf(c); - - // Check if queue empty only if necessary. - if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { - // CAS 操作,减少工作线程数 - decrementWorkerCount(); - return null; - } - - int wc = workerCountOf(c); - - // Are workers subject to culling? - boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; - - if ((wc > maximumPoolSize || (timed && timedOut)) - && (wc > 1 || workQueue.isEmpty())) { - if (compareAndDecrementWorkerCount(c)) - return null; - continue; - } - - try { - Runnable r = timed ? - workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : - workQueue.take(); - if (r != null) - return r; - timedOut = true; - } catch (InterruptedException retry) { - // 如果此 worker 发生了中断,采取的方案是重试 - // 解释下为什么会发生中断,这个读者要去看 setMaximumPoolSize 方法, - // 如果开发者将 maximumPoolSize 调小了,导致其小于当前的 workers 数量, - // 那么意味着超出的部分线程要被关闭。重新进入 for 循环,自然会有部分线程会返回 null - timedOut = false; - } - } -} -``` -到这里,基本上也说完了整个流程,回到 execute(Runnable command) 方法,看看各个分支,我把代码贴过来一下: -```java - public void execute(Runnable command) { - if (command == null) - throw new NullPointerException(); - //表示 “线程池状态” 和 “线程数” 的整数 - int c = ctl.get(); - // 如果当前线程数少于核心线程数,直接添加一个 worker 执行任务, - // 创建一个新的线程,并把当前任务 command 作为这个线程的第一个任务(firstTask) - if (workerCountOf(c) < corePoolSize) { - // 添加任务成功,即结束 - // 执行的结果,会包装到 FutureTask - // 返回 false 代表线程池不允许提交任务 - if (addWorker(command, true)) - return; - - c = ctl.get(); - } - // 到这说明,要么当前线程数大于等于核心线程数,要么刚刚 addWorker 失败 - - // 如果线程池处于 RUNNING ,把这个任务添加到任务队列 workQueue 中 - if (isRunning(c) && workQueue.offer(command)) { - /* 若任务进入 workQueue,我们是否需要开启新的线程 - * 线程数在 [0, corePoolSize) 是无条件开启新线程的 - * 若线程数已经大于等于 corePoolSize,则将任务添加到队列中,然后进到这里 - */ - int recheck = ctl.get(); - // 若线程池不处于 RUNNING ,则移除已经入队的这个任务,并且执行拒绝策略 - if (! isRunning(recheck) && remove(command)) - reject(command); - // 若线程池还是 RUNNING ,且线程数为 0,则开启新的线程 - // 这块代码的真正意图:担心任务提交到队列中了,但是线程都关闭了 - else if (workerCountOf(recheck) == 0) - addWorker(null, false); - } - // 若 workQueue 满,到该分支 - // 以 maximumPoolSize 为界创建新 worker, - // 若失败,说明当前线程数已经达到 maximumPoolSize,执行拒绝策略 - else if (!addWorker(command, false)) - reject(command); - } -``` -**工作线程**:线程池创建线程时,会将线程封装成工作线程Worker,Worker在执行完任务后,还会循环获取工作队列里的任务来执行.我们可以从Worker类的run()方法里看到这点 - -```java - public void run() { - try { - Runnable task = firstTask; - firstTask = null; - while (task != null || (task = getTask()) != null) { - runTask(task); - task = null; - } - } finally { - workerDone(this); - } - } - boolean workerStarted = false; - boolean workerAdded = false; - Worker w = null; - try { - w = new Worker(firstTask); - - final Thread t = w.thread; - if (t != null) { - //先加锁 - final ReentrantLock mainLock = this.mainLock; - mainLock.lock(); - try { - // Recheck while holding lock. - // Back out on ThreadFactory failure or if - // shut down before lock acquired. - int rs = runStateOf(ctl.get()); - - if (rs < SHUTDOWN || - (rs == SHUTDOWN && firstTask == null)) { - if (t.isAlive()) // precheck that t is startable - throw new IllegalThreadStateException(); - workers.add(w); - int s = workers.size(); - if (s > largestPoolSize) - largestPoolSize = s; - workerAdded = true; - } - } finally { - mainLock.unlock(); - } - if (workerAdded) { - t.start(); - workerStarted = true; - } - } - } finally { - if (! workerStarted) - addWorkerFailed(w); - } - return workerStarted; - } -``` -线程池中的线程执行任务分两种情况 - - 在execute()方法中创建一个线程时,会让这个线程执行当前任务 - - 这个线程执行完上图中 1 的任务后,会反复从BlockingQueue获取任务来执行 \ No newline at end of file diff --git "a/JDK/JVM/JVM\345\256\236\346\210\230(\345\205\255)-class\346\226\207\344\273\266\347\273\223\346\236\204.md" "b/JDK/JVM/JVM\345\256\236\346\210\230(\345\205\255)-class\346\226\207\344\273\266\347\273\223\346\236\204.md" deleted file mode 100644 index e8af44394b..0000000000 --- "a/JDK/JVM/JVM\345\256\236\346\210\230(\345\205\255)-class\346\226\207\344\273\266\347\273\223\346\236\204.md" +++ /dev/null @@ -1,267 +0,0 @@ -# 1 JVM的“平台无关性” - -Java具有平台无关性,即任何操作系统都能运行Java代码。 -之所以能实现这一点,是因为Java运行在虚拟机之上,不同的操作系统都拥有各自的Java虚拟机,因此Java能实现"一次编写,处处运行"。 - -而JVM不仅具有平台无关性,还具有语言无关性: -- 平台无关性是指不同操作系统都有各自的JVM -- 语言无关性是指Java虚拟机能运行除Java以外的代码! - -但JVM对能运行的语言是有严格要求的。首先来了解下Java代码的运行过程: -Java源代码首先需要使用Javac编译器编译成class文件,然后启动JVM执行class文件,从而程序开始运行。 -即JVM只认识class文件,它并不管何种语言生成了class文件,只要class文件符合JVM的规范就能运行。 - -因此目前已经有Scala、JRuby、Jython等语言能够在JVM上运行。它们有各自的语法规则,不过它们的编译器都能将各自的源码编译成符合JVM规范的class文件,从而能够借助JVM运行它们。 -![](https://imgconvert.csdnimg.cn/aHR0cDovL2ltZy5ibG9nLmNzZG4ubmV0LzIwMTYwNTI5MTQxNTU4NDg3?x-oss-process=image/format,png) - -Class文件是JVM的输入, Java虚拟机规范中定义了Class文件的结构。Class文件是JVM实现平台无关、技术无关的基础。 - -# 2 纵观Class文件结构 -class文件包含Java程序执行的字节码,数据严格按照格式紧凑排列在class文件中的二进制流,中间无任何分隔符。 -文件开头有一个0xcafebabe(16进制)特殊的一个标志。 -- 下图展示为16进制 - -![](https://img-blog.csdnimg.cn/20190822021614774.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) - - - -![](https://img-blog.csdnimg.cn/20190822021315238.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) -class文件是一组以8字节为单位的二进制字节流,对于占用空间大于8字节的数据项,按照高位在前的方式分割成多个8字节进行存储。 -它的内容具有严格的规范,文件中没有任何分隔符,全是连续的0/1。 - -class文件中的所有内容被分为两种类型: -- 无符号数 -基本的数据类型,以u1、u2、u4、u8,分别代表1字节、2字节、4字节、8字节的无符号数 -- 表 -class文件中所有数据(即无符号数)要么单独存在,要么由多个无符号数组成二维表.即class文件中的数据要么是单个值,要么是二维表.通常以_info 结尾 - -## 文件格式 - javap工具生成非正式的"虚拟机汇编语言” ,格式如下: - -```java - [ [ ...]][] -``` - -- ``是指令操作码在数组中的下标,该数组以字节形式来存储当前方法的Java虚拟机代码;也可以是相对于方法起始处的字节偏移量 -- ``是指令的助记码 -- `< operand>`是操作数 -- ``是行尾的注释 - - - -## 实践 -- Demo1.java -![](https://img-blog.csdnimg.cn/20190823232245291.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70) -- Demo1.txt![](https://img-blog.csdnimg.cn/20190823232156855.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) -![](https://img-blog.csdnimg.cn/20190823232641596.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) -版本号规则: JDK5,6,7,8 -分别对应49,50,51,52 -## 2.1 魔数(Magic Number) -class文件的头4个字节称为魔数,唯一作用是确定这个文件是否为一个能被JVM接受的Class文件. -作用就相当于文件后缀名,只不过后缀名容易被修改,不安全. -是用16进制表示的"CAFEBABE". -## 2.2 版本信息 -紧接着魔数的4个字节是版本号.它表示本class中使用的是哪个版本的JDK. -在高版本的JVM上能够运行低版本的class文件,但在低版本的JVM上无法运行高版本的class文件. - -## 2.3 常量池 -### 2.3.1 什么是常量池? -紧接着版本号之后的就是常量池. -常量池中存放两种类型的常量: - -- 字面量 (Literal) -接近Java语言的常量概念,如:字符串文本、final常量值等. -- 符号引用 (Symbolic Reference) -属于编译原理方面,包括下面三类常量: - - 类和接口的全限定名 - - 字段的名称和描述符 - - 方法的名称和描述符 - -## 2.3.2 常量池的特点 -- 长度不固定 -常量池的大小不固定,因此常量池开头放置一个u2类型的无符号数,代表当前常量池的容量. -**该值从1开始,若为5表示池中有4项常量,索引值1~5** -- 常量由二维表表示 -开头有个常量池容量计数值,接下来就全是一个个常量了,只不过常量都是由一张张二维表构成,除了记录常量的值以外,还记录当前常量的相关信息 -- class文件的资源仓库 -- 与本class中其它部分关联最多的数据类型 -- 占用Class文件空间最大的部分之一 ,也是第一个出现的表类型项目 - -### 2.3.3 常量池中常量的类型 -根据常量的数据类型不同,被细分为14种常量类型,都有各自的二维表示结构 -每种常量类型的头1个字节都是tag,表示当前常量属于14种类型中的哪一个. -![这里写图片描述](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9waWMxLnpoaW1nLmNvbS92Mi0xY2E2NmU3NmFhMWEyMjYzNGUzYzQ2YjVmZDQ2M2FkY19yLnBuZw?x-oss-process=image/format,png) -以CONSTANT_Class_info常量为例,它的二维表示结构如下: -**CONSTANT_Class_info表** - -|类型|名称 |数量| -|--|--|--| -| u1 |tag |1| -| u2 |name_index |1| - -- tag 表示当前常量的类型(当前常量为CONSTANT_Class_info,因此tag的值应为7,表一个类或接口的全限定名); -- name_index 表示这个类或接口全限定名的位置.它的值表示指向常量池的第几个常量.它会指向一个**CONSTANT_Utf8_info**类型的常量 - -|类型|名称 |数量| -|--|--|--| -| u1 |tag |1| -| u2 |length |1| -| u1 |bytes |length| - -**CONSTANT_Utf8_info**表字符串常量 -- tag 表当前常量的类型,这里是1 -- length 表该字符串的长度 -- bytes为这个字符串的内容(采用缩略的UTF8编码) - -**Java中定义的类、变量名字必须小于64K** -类、接口、变量等名字都属于符号引用,它们都存储在常量池中 -而不管哪种符号引用,它们的名字都由**CONSTANT_Utf8_info**类型的常量表示,这种类型的常量使用u2存储字符串的长度 -由于2字节最多能表示65535个数,因此这些名字的最大长度最多只能是64K - -**UTF-8编码 VS 缩略UTF-8编码** -前者每个字符使用3个字节表示,而后者把128个ASCII码用1字节表示,某些字符用2字节表示,某些字符用3字节表示。 - -- Demo1.txt中的常量池部分 -![](https://img-blog.csdnimg.cn/20190823232901622.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) - -- 类信息包含的静态常量,编译之后就能确认 -![](https://img-blog.csdnimg.cn/20190823233205779.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70) - -## JVM 指令 -| | | -|--|--| -invokeinterface |用以调用接口方法,在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。(Invoke interface method) -invokevirtual |指令用于调用对象的实例方法,根据对象的实际类型进行分派(Invoke instance method; dispatch based on class) -invokestatic |用以调用类方法(Invoke a class (static) method ) -invokespecial |指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法。(Invoke instance method; special handling for superclass, private, and instance initialization method invocations ) - -invokedynamic JDK1.7新加入的一个虚拟机指令,相比于之前的四条指令,他们的分派逻辑都是固化在JVM内部,而invokedynamic则用于处理新的方法分派:它允许应用级别的代码来确定执行哪一个方法调用,只有在调用要执行的时候,才会进行这种判断,从而达到动态语言的支持。(Invoke dynamic method) - - -## 2.4 访问控制 -在常量池结束之后是2字节的访问控制 -表示这个class文件是类/接口、是否被public/abstract/final修饰等. - -由于这些标志都由是/否表示,因此可以用0/1表示. -访问标志为2字节,可以表示16位标志,但JVM目前只定义了8种,未定义的直接写0. -|标志名称 | 标志值 |含义 -|--|--|--| -| ACC_INTERFACE | |是一个接口,而不是一个类 -ACC_MODULE | | 声明的模块; 可能无法从其模块外部访问。 仅当ClassFile具有Module属性时才可以设置。 -ACC_STATIC | 0x0008 |声明为静态 -![](https://img-blog.csdnimg.cn/20190824003331383.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) - -- Demo1.txt中的构造方法 -![](https://img-blog.csdnimg.cn/20190824001922551.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) - -Demo1这个示例中,我们并没有写构造函数。 -由此可见,`没有定义构造函数时,会有隐式的无参构造函数` - -## 2.5 类索引、父类索引、接口索引集合 -表示当前class文件所表示类的名字、父类名字、接口们的名字. -它们按照顺序依次排列,类索引和父类索引各自使用一个u2类型的无符号常量,这个常量指向CONSTANT_Class_info类型的常量,该常量的bytes字段记录了本类、父类的全限定名. -由于一个类的接口可能有好多个,因此需要用一个集合来表示接口索引,它在类索引和父类索引之后.这个集合头两个字节表示接口索引集合的长度,接下来就是接口的名字索引. -## 2.6 字段表的集合 -### 2.6.1 什么是字段表集合? -用于存储本类所涉及到的成员变量,包括实例变量和类变量,但不包括方法中的局部变量. -每一个字段表只表示一个成员变量,本类中所有的成员变量构成了字段表集合. -### 2.6.2 字段表结构的定义 -![这里写图片描述](https://imgconvert.csdnimg.cn/aHR0cDovL2ltZy5ibG9nLmNzZG4ubmV0LzIwMTcwNTEwMTc1NjM4NDM2?x-oss-process=image/format,png) - -- access_flags -字段的访问标志。在Java中,每个成员变量都有一系列的修饰符,和上述class文件的访问标志的作用一样,只不过成员变量的访问标志与类的访问标志稍有区别。 -- name_index -本字段名字的索引。指向一个CONSTANT_Class_info类型的常量,这里面存储了本字段的名字等信息。 -- descriptor_index -描述符。用于描述本字段在Java中的数据类型等信息(下面详细介绍) -- attributes_count -属性表集合的长度。 -- attributes -属性表集合。到descriptor_index为止是字段表的固定信息,光有上述信息可能无法完整地描述一个字段,因此用属性表集合来存放额外的信息,比如一个字段的值。(下面会详细介绍) -### 2.6.3 什么是描述符? -成员变量(包括静态成员变量和实例变量) 和 方法都有各自的描述符。 -对于字段而言,描述符用于描述字段的数据类型; -对于方法而言,描述符用于描述字段的数据类型、参数列表、返回值。 - -在描述符中,基本数据类型用大写字母表示,对象类型用“L对象类型的全限定名”表示,数组用“[数组类型的全限定名”表示。 -描述方法时,将参数根据上述规则放在()中,()右侧按照上述方法放置返回值。而且,参数之间无需任何符号。 -### 2.6.4 字段表集合的注意点 - -- 一个class文件的字段表集合中不能出现从父类/接口继承而来字段; -- 一个class文件的字段表集合中可能会出现程序猿没有定义的字段 -如编译器会自动地在内部类的class文件的字段表集合中添加外部类对象的成员变量,供内部类访问外部类。 -- Java中只要两个字段名字相同就无法通过编译。但在JVM规范中,允许两个字段的名字相同但描述符不同的情况,并且认为它们是两个不同的字段。 - - -- Demo1.txt中的程序入口main方法 -![](https://img-blog.csdnimg.cn/20190824002128156.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) -![](https://img-blog.csdnimg.cn/2019082400225092.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) -## 2.7 方法表的集合 -在class文件中,所有的方法以二维表的形式存储,每张表来表示一个函数,一个类中的所有方法构成方法表的集合。 -方法表的结构和字段表的结构一致,只不过访问标志和属性表集合的可选项有所不同。 -![这里写图片描述](https://imgconvert.csdnimg.cn/aHR0cDovL2ltZy5ibG9nLmNzZG4ubmV0LzIwMTYwNjEwMTEzNzM0NTU5?x-oss-process=image/format,png) -方法表的属性表集合中有一张Code属性表,用于存储当前方法经编译器编译过后的字节码指令。 - -### **方法表集合的注意点** - -- 如果本class没有重写父类的方法,那么本class文件的方法表集合中是不会出现父类/父接口的方法表; -- 本class的方法表集合可能出现程序猿没有定义的方法 -编译器在编译时会在class文件的方法表集合中加入类构造器和实例构造器。 -- 重载一个方法需要有相同的简单名称和不同的特征签名。JVM的特征签名和Java的特征签名有所不同: - - Java特征签名:方法参数在常量池中的字段符号引用的集合 - - JVM特征签名:方法参数+返回值 - -## 2.8 属性表的集合 - -![](https://img-blog.csdnimg.cn/20190824004202215.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) -- 1![](https://img-blog.csdnimg.cn/20190824004232751.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) - -- 2 -![](https://img-blog.csdnimg.cn/2019082400425144.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) - -# 程序完整运行分析 -![](https://img-blog.csdnimg.cn/20190824004554877.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) -![](https://img-blog.csdnimg.cn/20190824004639785.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) - -![](https://img-blog.csdnimg.cn/20190824004654676.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) - -![](https://img-blog.csdnimg.cn/20190824004709591.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70) - -![](https://img-blog.csdnimg.cn/20190824004835459.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) -![](https://img-blog.csdnimg.cn/20190824004727517.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70) - -![](https://img-blog.csdnimg.cn/20190824004849121.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) -![](https://img-blog.csdnimg.cn/20190824005033311.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) - -![](https://img-blog.csdnimg.cn/2019082400582822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) - -![](https://img-blog.csdnimg.cn/20190824005917921.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) - -![](https://img-blog.csdnimg.cn/20190824005953346.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) - -![](https://img-blog.csdnimg.cn/2019082401001818.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) - - - -![](https://img-blog.csdnimg.cn/2019082401003190.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) - - - - - - - - - -![](https://img-blog.csdnimg.cn/20190824010045825.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) -![](https://img-blog.csdnimg.cn/20190824010233509.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) - -# 总结 -我们将JVM运行的核心逻辑进行了详细剖析。 - -> JVM运行原理中更底层实现,针对不同的操作系统或者处理器,会有不同的实现。 -这也是JAVA能够实现“`一处编写,处处运行`”的原因。 -开发人员理解到这个层次,就足够掌握高深的多线程 - -参考 -- 《码出高效》 \ No newline at end of file diff --git "a/JDK/JVM/JVM\345\256\236\346\210\230-\347\261\273\345\212\240\350\275\275\343\200\201\351\252\214\350\257\201\343\200\201\345\207\206\345\244\207\343\200\201\350\247\243\346\236\220\343\200\201\345\210\235\345\247\213\345\214\226\343\200\201\345\215\270\350\275\275\350\277\207\347\250\213.md" "b/JDK/JVM/JVM\345\256\236\346\210\230-\347\261\273\345\212\240\350\275\275\343\200\201\351\252\214\350\257\201\343\200\201\345\207\206\345\244\207\343\200\201\350\247\243\346\236\220\343\200\201\345\210\235\345\247\213\345\214\226\343\200\201\345\215\270\350\275\275\350\277\207\347\250\213.md" deleted file mode 100644 index 4ba99cfd6f..0000000000 --- "a/JDK/JVM/JVM\345\256\236\346\210\230-\347\261\273\345\212\240\350\275\275\343\200\201\351\252\214\350\257\201\343\200\201\345\207\206\345\244\207\343\200\201\350\247\243\346\236\220\343\200\201\345\210\235\345\247\213\345\214\226\343\200\201\345\215\270\350\275\275\350\277\207\347\250\213.md" +++ /dev/null @@ -1,212 +0,0 @@ -# 0 使用类的准备工作 - -任何程序都需要加载到内存才能与CPU进行交流,同理, 字节码.class文件同样需要加载到内存中,才可以实例化类。 -`ClassLoader`的使命就是提前加载.class 类文件到内存中,在加载类时,使用的是Parents Delegation Model(溯源委派加载模型)。 - -Java的类加载器是一个运行时核心基础设施模块,主要是在启动之初进行类的加载、链接、初始化: -- Java 类加载过程 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWJmZGRhYjUzMjk5ZTIyZTQucG5n) -## Load-加载 -由类加载器执行。 - -读取类文件(通常在 classpath 所指定的路径中查找,但classpath非必须),查找字节码,从而产生二进制流,并转为特定数据结构,初步校验cafe babe魔法数、常量池、文件长度、是否有父类等,然后创建对应类的java.lang.Class实例。 - -## Link-链接 -将已读入内存的类的二进制数据合并到 JVM 运行时环境。 -包括验证、准备、解析三步: -- 验证 -确保被加载类的正确性。验证类中的字节码,是更详细的校验,比如final是否合规、类型是否正确、静态变量是否合理 -- 准备 -为类的static字段分配内存,并设定初始默认值,解析类和方法确保类与类之间的相互引用正确性,完成内存结构布局 -- 解析 -如果需要的话,将解析这个类创建的对其他类的所有引用,将常量池的符号引用转换成直接引用 。 -## Init-初始化 -执行类构造器 方法,如果赋值运算是通过其他类的静态方法来完成的,那么会马上解析另外一个类,在虚拟机栈中执行完毕后通过返回值进行赋值 - - 类加载是一个将.class字节码文件实例化成Class对象并进行相关初始化的过程。 -在这个过程中,JVM会初始化继承树上还没有被初始化过的所有父类,并且会执行这个链路上所有未执行过的静态代码块、静态变量赋值语句等。 -某些类在使用时,也可以按需由类加载器进行加载。 - -全小写的class是关键字,用来定义类 -而首字母大写的Class,它是所有class的类 -这句话理解起来有难度,类已经是现实世界中某种事物的抽象,为什么这个抽象还是另外一个类Class的对象? -示例代码如下: -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTM0MTk3NGNiNTllZTY4MmEucG5n) -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTdlYmNlYjI0NDVmYzA3MjUucG5n) -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTViZDgyODE1ODU4MDI0YWUucG5n) -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWE4MmM0NzUyMThhYzQ5NzAucG5n) - ● 第1处说明: -Class类下的`newInstance()`在JDK9中已经置为过时,使用`getDeclaredConstructor().newInstance()`的方式 -着重说明一下new与newInstance的区别 -- new是强类型校验,可以调用任何构造方法,在使用new操作的时候,这个类可以没有被加载过 -- 而Class类下的newInstance是弱类型,只能调用无参构造方法 - - 如果没有默认构造方法,就拋出`InstantiationException`异常; - - 如果此构造方法没有权限访问,则拋 `IllegalAccessException`异常 - -Java 通过类加载器把类的实现与类的定义进行解耦,所以是实现面向接口编程、依赖倒置的必然选择。 - -● 第2处说明: -可以使用类似的方式获取其他声明,如注解、方法等 -![类的反射信息](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTdkNTRhODI2OTI5NDU4NzUucG5n) - -● 第3处说明: private 成员在类外是否可以修改? -通过`setccessible(true)`,即可使用Class类的set方法修改其值 -如果没有这一步,则抛出如下异常: -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTQ3YWNmNjU0ZWQyNzZjOTUucG5n) - - -- 参考 -[看完这篇JVM类加载器,再也不怕阿里面试官了!](https://javaedge.blog.csdn.net/article/details/105250625) - -# 1 加载的定位 -> “加载”是“类加载”(Class Loading)过程的第一步。 -## 1.1 加载过程 -JVM主要做如下事情: -- 通过类的全限定名(保证全局唯一)获取该类的二进制字节流(class文件) -在程序运行过程中,当要访问一个类时,若发现这个类尚未被加载,并满足类初始化的条件时,就根据要被初始化的这个类的全限定名找到该类的二进制字节流,开始加载过程。 -把类加载阶段的“通过类的全限定名来获取该类的二进制字节流”这个动作交给虚拟机之外的类加载器来完成的好处在于,可自行实现类加载器来加载其他格式的类,只要是二进制字节流就行,这就大大增强加载器的灵活性。 -- 将这个字节流的静态存储结构转化为方法区的运行时数据结构 -- 在内存中创建一个该类的`java.lang.Class`对象,作为方法区该类的各种数据的访问入口,所以所有类都可以调用 getClass 方法 - -程序在运行中所有对该类的访问都通过这个类对象,也就是这个Class对象是提供给外界访问该类的接口 -## 1.2 加载源 -JVM规范对于加载过程给予了较大的宽松度。一般二进制字节流都从已经编译好的本地class文件中读取,此外还可以从以下地方读取 -- zip包 -Jar、War、Ear等 -- 其它文件生成 -由JSP文件中生成对应的Class类 -- 数据库中 -将二进制字节流存储至数据库中,然后在加载时从数据库中读取.有些中间件会这么做,用来实现代码在集群间分发 -- 网络 -从网络中获取二进制字节流,比如Applet -- 运行时动态计算生成 -动态代理技术,用`PRoxyGenerator.generateProxyClass`为特定接口生成形式为"*$Proxy"的代理类的二进制字节流 - - -## 1.3 类和数组加载过程的区别 -数组也有类型,称为“数组类型”,如: -```java -String[] str = new String[10]; -``` -这个数组的数组类型是`Ljava.lang.String`,而String只是这个数组的元素类型。 -当程序在运行过程中遇到new关键字创建一个数组时,由JVM直接创建数组类,再由类加载器创建数组中的元素类型。 - -而普通类的加载由类加载器创建。既可以使用系统提供的引导类加载器,也可以由用户自定义的类加载器完成(即重写一个类加载器的loadClass()方法) -## 1.4 加载过程的注意点 -- JVM规范并未给出类在方法区中存放的数据结构 -类完成加载后,二进制字节流就以特定的数据结构存储在方法区中,但存储的数据结构是由虚拟机自己定义的,虚拟机规范并没有指定 -- JVM规范并没有指定Class对象存放的位置 -在二进制字节流以特定格式存储在方法区后,JVM会创建一个java.lang.Class类的对象,作为本类的外部访问接口 -既然是对象就应该存放在Java堆中,不过JVM规范并没有给出限制,不同的虚拟机根据自己的需求存放这个对象 -HotSpot将Class对象存放在方法区. -- 加载阶段和链接阶段是交叉的 -类加载的过程中每个步骤的开始顺序都有严格限制,但每个步骤的结束顺序没有限制.也就是说,类加载过程中,必须按照如下顺序开始: ->加载 -> 链接 -> 初始化 - -但结束顺序无所谓,因此由于每个步骤处理时间的长短不一就会导致有些步骤会出现交叉 -# 2 验证 -验证阶段比较耗时,它非常重要但不一定必要(因为对程序运行期没有影响),如果所运行的代码已经被反复使用和验证过,那么可以使用`-Xverify:none`参数关闭,以缩短类加载时间 -## 2.1 验证的目的 -保证二进制字节流中的信息符合虚拟机规范,并没有安全问题 -## 2.2 验证的必要性 -虽然Java语言是一门安全的语言,它能确保程序猿无法访问数组边界以外的内存、避免让一个对象转换成任意类型、避免跳转到不存在的代码行.也就是说,Java语言的安全性是通过编译器来保证的. - -但是我们知道,编译器和虚拟机是两个独立的东西,虚拟机只认二进制字节流,它不会管所获得的二进制字节流是哪来的,当然,如果是编译器给它的,那么就相对安全,但如果是从其它途径获得的,那么无法确保该二进制字节流是安全的。 - -通过上文可知,虚拟机规范中没有限制二进制字节流的来源,在字节码层面上,上述Java代码无法做到的都是可以实现的,至少语义上是可以表达出来的,为了防止字节流中有安全问题,需要验证! -## 2.3 验证的过程 -- 文件格式验证 -验证字节流是否符合Class文件格式的规范,并且能被当前的虚拟机处理. -本验证阶段是基于二进制字节流进行的,只有`通过本阶段验证,才被允许存到方法区` -后面的三个验证阶段都是基于方法区的存储结构进行,不会再直接操作字节流. - -> 通过上文可知,加载开始前,二进制字节流还没进方法区,而加载完成后,二进制字节流已经存入方法区 -> 而在文件格式验证前,二进制字节流尚未进入方法区,文件格式验证通过之后才进入方法区 -> 也就是说,加载开始后,立即启动了文件格式验证,本阶段验证通过后,二进制字节流被转换成特定数据结构存储至方法区中,继而开始下阶段的验证和创建Class对象等操作 -这个过程印证了:加载和验证是交叉进行的 - -- 元数据验证 -对字节码描述的信息进行语义分析,确保符合Java语法规范。 -- 字节码验证 -验证过程的最复杂的阶段。 本阶段对数据流和控制流(主要为方法体)进行语义分析。字节码验证将对类的方法进行校验分析,保证被校验的方法在运行时不会做出危害虚拟机的事,一个类方法体的字节码没有通过字节码验证,那一定有问题,但若一个方法通过了验证,也不能说明它一定安全。 -- 符号引用验证 -发生在JVM将符号引用转化为直接引用的时候,这个转化动作发生在解析阶段,对类自身以外的信息进行匹配校验,确保解析能正常执行. -# 3 准备 -完成两件事情 -- 为已在方法区中的类的静态成员变量分配内存 -- 为静态成员变量设置初始值 -初始值为0、false、null等 -![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtZjYyYTU2YzAzNGM1MTgyMC5qcGc) -``` -public static final int value = 123; -``` -准备阶段后 a 的值为 0,而不是 123,要在初始化之后才变为 123,但若被final修饰的常量如果有初始值,那么在编译阶段就会将初始值存入constantValue属性中,在准备阶段就将constantValue的值赋给该字段(此处将value赋为123). -# 4 解析 -把常量池中的**符号引用**转换成**直接引用**的过程。包括: -- 符号引用 -以一组无歧义的符号来描述所引用的目标,与虚拟机的实现无关。 -- 直接引用 -直接指向目标的指针、相对偏移量、或是能间接定位到目标的句柄,是和虚拟机实现相关的。 - -主要针对:类、接口、字段、类方法、接口方法、方法类型、方法句柄、调用点限定符。 - -# 5 初始化 -真正开始执行类中定义的Java程序代码(或说是字节码),类的初始化就是为类的静态变量赋初始值,初始化阶段就是执行类构造器``的过程。 - -- 如果类还没有加载和连接,就先加载和连接 -- 如果类存在父类,且父类没有初始化,就先初始化父类 -- 如果类中存在初始化语句,就依次执行这些初始化语句 -- 如果是接口 - - 初始化一个类时,并不会先初始化它实现的接口 - - 初始化一个接口时,并不会初始化它的父接口 -只有当程序首次使用接口里面的变量或者是调用接口方法的时候,才会导致接口初始化 -- 调用Classloader类的loadClass方法来装载一个类,并不会初始化这个类,不属于对类的主动使用 - - -clinit()方法由编译器自动产生,收集类中static{}代码块中的类变量赋值语句和类中静态成员变量的赋值语句。 -在准备阶段,类中静态成员变量已经完成了默认初始化,而在初始化阶段,clinit()方法对静态成员变量进行显示初始化。 - -## 类的初始化时机 -Java程序对类的使用方式分为: -- 主动使用 -- 被动使用 - -JVM必须在每个类或接口“首次主动使用”时才初始化它们,被动使用类不会导致类的初始化。主动使用的场景: -- **创建类实例** -- 访问某个类或接口的**静态变量** -如果是 final 常量,而常量在编译阶段就会在常量池,没有引用到定义该常量的类,因此不会触发定义该常量类的初始化 -- 调用类的**静态方法** -- **反射**某个类 -- 初始化某个类的子类,而父类还没有初始化 -- JVM启动的时候运行的主类(等于第三条) -- 定义了 default 方法的接口,当接口实现类初始化时 - - -## 初始化过程的注意点 -- clinit()方法是IDE自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的,IDE收集的顺序是由语句在源文件中出现的顺序所决定的. -- 静态代码块只能访问到出现在静态代码块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问. -```java -public class Test { - static { - i=0; - System.out.println(i); //编译失败:"非法向前引用" - } - static int i = 1; -} -``` -- 实例构造器init()需要显式调用父类构造器,而类的clinit()无需调用父类的类构造器,JVM会确保子类的clinit()方法执行前已执行完毕父类的clinit()方法。 -因此在JVM中第一个被执行的clinit()方法的类肯定是java.lang.Object. -- 如果一个类/接口无static代码块,也无 static成员变量的赋值操作,则编译器不会为此类生成clinit()方法 -- 接口也需要通过clinit()方法为接口中定义的static成员变量显示初始化。 -- 接口中不能使用静态代码块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成clinit()方法.不同的是,执行接口的clinit()方法不需要先执行父接口的clinit()方法.只有当父接口中的静态成员变量被使用到时才会执行父接口的clinit()方法. -- 虚拟机会保证在多线程环境中一个类的clinit()方法别正确地加锁,同步.当多条线程同时去初始化一个类时,只会有一个线程去执行该类的clinit()方法,其它线程都被阻塞等待,直到活动线程执行clinit()方法完毕. - -> 其他线程虽会被阻塞,只要有一个clinit()方法执行完,其它线程唤醒后不会再进入clinit()方法。同一个类加载器下,一个类型只会初始化一次。 - -# 6 类的卸载 -当代表一个类的Class对象不再被引用,那么Class对象的生命周期就结束了,对应的在方法区中的数据也会被卸载。 -Jvm自带的类加载器装载的类,是不会卸载的,由用户自定义的类加载器加载的类是可以卸载的。 - -参考 -- 《码到成功》 -- 《深入理解Java虚拟机第三版》 \ No newline at end of file diff --git "a/JDK/JVM/JVM\347\261\273\345\212\240\350\275\275\345\231\250.md" "b/JDK/JVM/JVM\347\261\273\345\212\240\350\275\275\345\231\250.md" deleted file mode 100644 index 706d5f0785..0000000000 --- "a/JDK/JVM/JVM\347\261\273\345\212\240\350\275\275\345\231\250.md" +++ /dev/null @@ -1,135 +0,0 @@ -类加载器是如何定位具体的类文件并读取的呢? - -# 1 类加载器 -在类加载器家族中存在着类似人类社会的权力等级制度: -## 1.1 `Bootstrap` -由C/C++实现,启动类加载器,属最高层,JVM启动时创建,通常由与os相关的本地代码实现,是最根基的类加载器。 -### JDK8 时 -> **需要注意的是**,Bootstrap ClassLoader智慧加载特定名称的类库,比如rt.jar.这意味我们自定义的jar扔到`\jre\lib`也不会被加载. - -负责将`/jre/lib`或`- -Xbootclasspath`参数指定的路径中的,且是虚拟机识别的类库加载到内存中(按照名字识别,比如rt.jar,对于不能识别的文件不予装载),比如: -- Object -- System -- String -- Java运行时的rt.jar等jar包 -- 系统属性sun.boot.class.path指定的目录中特定名称的jar包 - -在JVM启动时,通过**Bootstrap ClassLoader**加载`rt.jar`,并初始化`sun.misc.Launcher`从而创建**Extension ClassLoader**和**Application ClassLoader**的实例。 -查看Bootstrap ClassLoader到底初始化了那些类库: - -```java -URL[] urLs = Launcher.getBootstrapClassPath().getURLs(); - for (URL urL : urLs) { - System.out.println(urL.toExternalForm()); - } -``` -### JDK9 后 -负责加载启动时的基础模块类,比如: -- java.base -- java.management -- java.xml -## 1.2 `Platform ClassLoader` -### JDK8 时`Extension ClassLoader` -只有一个实例,由sun.misc.Launcher$ExtClassLoader实现: -- 负责加载`\lib\ext`或`java.ext.dirs`系统变量指定的路径中的所有类库 -- 加载一些扩展的系统类,比如XML、加密、压缩相关的功能类等 - -### JDK9时替换为平台类加载器 -加载一些平台相关的模块,比如`java.scripting`、`java.compiler*`、 `java.corba*`。 -### 那为何 9 时废除替换了呢? -JDK8 的主要加载 jre lib 的ext,扩展 jar 包时使用,这样操作并不推荐,所以废除。而 JDK9 有了模块化,更无需这种扩展加载器。 -## 1.3 `Application ClassLoader` -只有一个实例,由`sun.misc.Launcher$AppClassLoader`实现。 -### JDK8 时 -负责加载系统环境变量ClassPath或者系统属性java.class.path指定目录下的所有类库。 -如果应用程序中没有定义自己的加载器,则该加载器也就是默认的类加载器。该加载器可以通过java.lang.ClassLoader.getSystemClassLoader获取。 -### JDK9 后 -应用程序类加载器,用于加载应用级别的模块,比如: -- jdk.compiler -- jdk.jartool -- jdk.jshell -![](https://img-blog.csdnimg.cn/2021011914324377.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) - -- classpath路径中的所有类库 - -第二、三层类加载器为Java语言实现,用户也可以 -## 1.4 自定义类加载器 -用户自定义的加载器,是`java.lang.ClassLoader`的子类,用户可以定制类的加载方式;只不过自定义类加载器其加载的顺序是在所有系统类加载器的最后。 - -## 1.5 Thread Context ClassLoader -每个线程都有一个类加载器(jdk 1.2后引入),称之为Thread Context ClassLoader,如果线程创建时没有设置,则默认从父线程中继承一个,如果在应用全局内都没有设置,则所有Thread Context ClassLoader为Application ClassLoader.可通过Thread.currentThread().setContextClassLoader(ClassLoader)来设置,通过Thread.currentThread().getContextClassLoader()来获取. - -线程上下文加载器有什么用? -该类加载器容许父类加载器通过子类加载器加载所需要的类库,也就是打破了我们下文所说的双亲委派模型。 -这有什么好处呢? -利用线程上下文加载器,我们能够实现所有的代码热替换,热部署,Android中的热更新原理也是借鉴如此。 - -# 2 验证类加载器 -## 2.1 查看本地类加载器 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWM2YTlkN2YwOTEwZDNiZGMucG5n) -在JDK8环境中,执行结果如下 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWExYjA3N2UzYTRiYzRhZjMucG5n) -AppClassLoader的Parent为Bootstrap,它是通过C/C++实现的,并不存在于JVM体系内,所以输出为 null。 - - -# 类加载器的特点 -- 类加载器并不需要等到某个类"首次主动使用”的时候才加载它,JVM规范允许类加载器在预料到某个类将要被使用的时候就预先加载它。 -- Java程序不能直接引用启动类加载器,直接设置classLoader为null,默认就使用启动类加载器 -- 如果在加载的时候`.class`文件缺失,会在该类首次主动使用时通知LinkageError错误,如果一直没有被使用,就不会报错 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWQ2ZGU1NDU2OTNmNGZjYjEucG5n) - - -低层次的当前类加载器,不能覆盖更高层次类加载器已经加载的类 -如果低层次的类加载器想加载一个未知类,要非常礼貌地向上逐级询问:“请问,这个类已经加载了吗?” -被询问的高层次类加载器会自问两个问题 -- 我是否已加载过此类 -- 如果没有,是否可以加载此类 - -只有当所有高层次类加载器在两个问题的答案均为“否”时,才可以让当前类加载器加载这个未知类 -左侧绿色箭头向上逐级询问是否已加载此类,直至`Bootstrap ClassLoader`,然后向下逐级尝试是否能够加载此类,如果都加载不了,则通知发起加载请求的当前类加载器,准予加载 -在右侧的三个小标签里,列举了此层类加载器主要加载的代表性类库,事实上不止于此 - -通过如下代码可以查看Bootstrap 所有已加载类库 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTg0MGEwNDUwNWI1Y2I1YTgucG5n) -执行结果 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWNiNTdkZGNmZjBmZDI0ZWMucG5n) - -Bootstrap加载的路径可以追加,不建议修改或删除原有加载路径 -在JVM中增加如下启动参数,则能通过`Class.forName`正常读取到指定类,说明此参数可以增加Bootstrap的类加载路径: -```bash --Xbootclasspath/a:/Users/sss/book/ easyCoding/byJdk11/src -``` -如果想在启动时观察加载了哪个jar包中的哪个类,可以增加 -```bash --XX:+TraceClassLoading -``` -此参数在解决类冲突时非常实用,毕竟不同的JVM环境对于加载类的顺序并非是一致的 -有时想观察特定类的加载上下文,由于加载的类数量众多,调试时很难捕捉到指定类的加载过程,这时可以使用条件断点功能 -比如,想查看HashMap的加载过程,在loadClass处打个断点,并且在condition框内输入如图 -![设置条件断点](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTZhMjk0YjhhNzVkYWM4MTIucG5n) - -### JVM如何确立每个类在JVM的唯一性 -类的全限定名和加载这个类的类加载器的ID - -在学习了类加载器的实现机制后,知道双亲委派模型并非强制模型,用户可以自定义类加载器,在什么情况下需要自定义类加载器呢? -- 隔离加载类 -在某些框架内进行中间件与应用的模块隔离,把类加载到不同的环境 -比如,阿里内某容器框架通过自定义类加载器确保应用中依赖的jar包不会影响到中间件运行时使用的jar包 -- 修改类加载方式 -类的加载模型并非强制,除Bootstrap外,其他的加载并非一定要引入,或者根据实际情况在某个时间点进行按需进行动态加载 -- 扩展加载源 -比如从数据库、网络,甚至是电视机机顶盒进行加载 -- 防止源码泄露 -Java代码容易被编译和篡改,可以进行编译加密。那么类加载器也需要自定义,还原加密的字节码。 - -实现自定义类加载器的步骤 -- 继承ClassLoader -- 重写findClass()方法 -- 调用defineClass()方法 - -一个简单的类加载器实现的示例代码如下 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTdiYjk1NGI1NThlOTAwMzAucG5n) - -由于中间件一般都有自己的依赖jar包,在同一个工程内引用多个框架时,往往被迫进行类的仲裁。按某种规则jar包的版本被统一指定, 导致某些类存在包路径、类名相同的情况,就会引起类冲突,导致应用程序出现异常。 -主流的容器类框架都会自定义类加载器,实现不同中间件之间的类隔离,有效避免了类冲突。 \ No newline at end of file diff --git "a/JDK/JVM/JVM\347\261\273\345\212\240\350\275\275\345\231\250\347\232\204\345\217\214\344\272\262\345\247\224\346\264\276\346\250\241\345\236\213.md" "b/JDK/JVM/JVM\347\261\273\345\212\240\350\275\275\345\231\250\347\232\204\345\217\214\344\272\262\345\247\224\346\264\276\346\250\241\345\236\213.md" deleted file mode 100644 index 3b7c8da6e8..0000000000 --- "a/JDK/JVM/JVM\347\261\273\345\212\240\350\275\275\345\231\250\347\232\204\345\217\214\344\272\262\345\247\224\346\264\276\346\250\241\345\236\213.md" +++ /dev/null @@ -1,195 +0,0 @@ -说是双亲,其实多级单亲,无奈迎合历史的错误翻译吧。 -# 1 工作流程 -- 当一个类加载器收到一个类加载请求 -在 JDK9 后,会首先搜索它的内建加载器定义的所有“具名模块”: - - 如果找到合适的模块定义,将会使用该加载器来加载 - - 如果未找到,则会将该请求委派给父级加载器去加载 -- 因此所有的类加载请求最终都应该被传入到启动类加载器(Bootstrap ClassLoader)中,只有当父级加载器反馈无法完成这个列的加载请求时(它的搜索范围内不存在这个类),子级加载器才尝试加载。 - -在类路径下找到的类将成为这些加载器的无名模块。 - -**这里的父子关系是组合而不是继承**。 - -- 双亲委派模型示意图 -![](https://img-blog.csdnimg.cn/img_convert/2f38d489c0a1a948239765913d64cfcf.png) - -# 双亲委派模型的优点 -- 避免重复加载 -父类已经加载了,子类就不需要再次加载。 -eg,object 类。它存放在 rt.jar 中,无论哪个类加载器要加载这个类,最终都是委派给处于模型顶端的启动类加载器加载,因此 object 类在程序的各种加载环境中都是同一个类。 -- 更安全 -解决了各个类加载器的基础类的统一问题,如果不使用该种方式,那么用户可以随意定义类加载器来加载核心 API,会带来安全隐患。 - -# 双亲委派模型的实现 -```java - protected Class loadClass(String name, boolean resolve) - throws ClassNotFoundException - { - synchronized (getClassLoadingLock(name)) { - // 首先先检查该类已经被加载过了 - Class c = findLoadedClass(name); - if (c == null) {//该类没有加载过,交给父类加载 - long t0 = System.nanoTime(); - try { - if (parent != null) {//交给父类加载 - c = parent.loadClass(name, false); - } else {//父类不存在,则交给启动类加载器加载 - c = findBootstrapClassOrNull(name); - } - } catch (ClassNotFoundException e) { - //父类加载器抛出异常,无法完成类加载请求 - } - - if (c == null) {// - long t1 = System.nanoTime(); - //父类加载器无法完成类加载请求时,调用自身的findClass方法来完成类加载 - c = findClass(name); - sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); - sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); - sun.misc.PerfCounter.getFindClasses().increment(); - } - } - if (resolve) { - resolveClass(c); - } - return c; - } - } - -``` - -## 3.3 类加载的方式 -1. 通过命令行启动应用时由JVM初始化加载含有main()方法的主类。 -2. 通过Class.forName()方法动态加载,会默认执行初始化块(static{}),但是Class.forName(name,initialize,loader)中的initialze可指定是否要执行初始化块。 -3. 通过ClassLoader.loadClass()方法动态加载,不会执行初始化块。 - -# 自定义类加载器 -### 实现方式 -- 遵守双亲委派模型 -继承ClassLoader,重写findClass()方法。 -- 破坏双亲委派模型 -继承ClassLoader,重写loadClass()方法。 - -通常我们推荐采用第一种方法自定义类加载器,最大程度上的遵守双亲委派模型。 - -如果有一个类加载器能加载某个类,称为**定义类加载器**,所有能成功返回该类的Class的类加载器都被称为**初始类加载器**。 - -自定义类加载的目的是想要手动控制类的加载,那除了通过自定义的类加载器来手动加载类这种方式,还有其他的方式么? - -> 利用现成的类加载器进行加载: -> -> ``` -> 1. 利用当前类加载器 -> Class.forName(); -> -> 2. 通过系统类加载器 -> Classloader.getSystemClassLoader().loadClass(); -> -> 3. 通过上下文类加载器 -> Thread.currentThread().getContextClassLoader().loadClass(); -> ``` -> -> l -> 利用URLClassLoader进行加载: -> -> ``` -> URLClassLoader loader=new URLClassLoader(); -> loader.loadClass(); -> ``` - -* * * - -**类加载实例演示:** -命令行下执行HelloWorld.java - -```java -public class HelloWorld{ - public static void main(String[] args){ - System.out.println("Hello world"); - } -} -``` - -该段代码大体经过了一下步骤: - -1. 寻找jre目录,寻找jvm.dll,并初始化JVM. -2. 产生一个Bootstrap ClassLoader; -3. Bootstrap ClassLoader加载器会加载他指定路径下的java核心api,并且生成Extended ClassLoader加载器的实例,然后Extended ClassLoader会加载指定路径下的扩展java api,并将其父设置为Bootstrap ClassLoader。 -4. Bootstrap ClassLoader生成Application ClassLoader,并将其父Loader设置为Extended ClassLoader。 -5. 最后由AppClass ClassLoader加载classpath目录下定义的类——HelloWorld类。 - -我们上面谈到 Extended ClassLoader和Application ClassLoader是通过Launcher来创建,现在我们再看看源代码: - -```java - public Launcher() { - Launcher.ExtClassLoader var1; - try { - //实例化ExtClassLoader - var1 = Launcher.ExtClassLoader.getExtClassLoader(); - } catch (IOException var10) { - throw new InternalError("Could not create extension class loader", var10); - } - - try { - //实例化AppClassLoader - this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); - } catch (IOException var9) { - throw new InternalError("Could not create application class loader", var9); - } - //主线程设置默认的Context ClassLoader为AppClassLoader. - //因此在主线程中创建的子线程的Context ClassLoader 也是AppClassLoader - Thread.currentThread().setContextClassLoader(this.loader); - String var2 = System.getProperty("java.security.manager"); - if(var2 != null) { - SecurityManager var3 = null; - if(!"".equals(var2) && !"default".equals(var2)) { - try { - var3 = (SecurityManager)this.loader.loadClass(var2).newInstance(); - } catch (IllegalAccessException var5) { - ; - } catch (InstantiationException var6) { - ; - } catch (ClassNotFoundException var7) { - ; - } catch (ClassCastException var8) { - ; - } - } else { - var3 = new SecurityManager(); - } - - if(var3 == null) { - throw new InternalError("Could not create SecurityManager: " + var2); - } - - System.setSecurityManager(var3); - } - - } -``` -# 破坏双亲委派模型 -双亲模型有个问题:父加载器无法向下识别子加载器加载的资源。 -- 如下证明 JDBC 是启动类加载器加载,但 mysql 驱动是应用类加载器。而 JDBC 运行时又需要去访问子类加载器加载的驱动,就破坏了该模型。 -![](https://img-blog.csdnimg.cn/2021011918265025.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) -JDK 自己为解决该问题,引入线程上下问类加载器,可以通过Thread的setContextClassLoader()进行设置 -- 当为启动类加载器时,使用当前实际加载驱动类的类加载器 -![](https://img-blog.csdnimg.cn/20210119184938411.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) -## 热替换 -比如OSGI的模块化热部署,它的类加载器就不再是严格按照双亲委派模型,很多 -可能就在平级的类加载器中执行了。 - -# FAQ -1. ClassLoader通过一个类全限定名来获取二进制流,如果我们需通过自定义类加载其来加载一个Jar包的时候,难道要自己遍历jar中的类,然后依次通过ClassLoader进行加载吗?或者说我们怎么来加载一个jar包呢? -对于动态加载jar而言,JVM默认会使用第一次加载该jar中指定类的类加载器作为默认的ClassLoader。 - -假设我们现在存在名为sbbic的jar包,该包中存在ClassA和ClassB类(ClassA中没有引用ClassB)。 -现在我们通过自定义的ClassLoaderA来加载在ClassA这个类,此时ClassLoaderA就成为sbbic.jar中其他类的默认类加载器。即ClassB默认也会通过ClassLoaderA去加载。 - -2. 如果一个类引用的其他的类,那么这个其他的类由谁来加载? - -如果ClassA中引用了ClassB呢? -当类加载器在加载ClassA的时候,发现引用了ClassB,此时类加载如果检测到ClassB还没有被加载,则先回去加载。当ClassB加载完成后,继续回来加载ClassA。即类会通过自身对应的来加载其加载其他引用的类。 - -3. 既然类可以由不同的加载器加载,那么如何确定两个类如何是同一个类? - -JVM规定:对于任何一个类,都需要由加载它的类加载器和这个类本身一同确立在java虚拟机中的唯一性。即在jvm中判断两个类是否是同一个类取决于类加载和类本身,也就是同一个类加载器加载的同一份Class文件生成的Class对象才是相同的,类加载器不同,那么这两个类一定不相同。 \ No newline at end of file diff --git "a/JDK/JVM/JVM\351\200\203\351\200\270\345\210\206\346\236\220.md" "b/JDK/JVM/JVM\351\200\203\351\200\270\345\210\206\346\236\220.md" deleted file mode 100644 index e65e59102a..0000000000 --- "a/JDK/JVM/JVM\351\200\203\351\200\270\345\210\206\346\236\220.md" +++ /dev/null @@ -1,215 +0,0 @@ -1 逃逸分析 - - -JVM中高深的优化技术,如同类继承关系分析,该技术并非直接去优化代码,而是一种为其他优化措施提供依据的分析技术。 - -分析对象的动态作用域,当某对象在方法里被定义后,它可能 - -* **方法**逃逸 - - 被外部方法引用,例如作为参数传递给其他方法 - -* **线程**逃逸 - - 被外部线程访问,例如赋值给可以在其他线程中访问的实例变量 - - - - -所以 Java 对象由低到高的逃逸程度即为: - -* **不逃逸 =》** - -* **方法逃逸 =》** - -* **线程逃逸** - - - - -若能确定一个对象 - -* 不会逃逸到方法或线程外(即其它方法、线程无法访问到该对象) - -* 或逃逸程度较低(只逃逸出方法而不逃逸出线程) - - -则可为该对象实例采取不同程度的优化方案。 - -2 优化方案 - - - - - - - - - -2.1  栈上分配(Stack Allocations) - - - - -> 由于复杂度等原因,HotSpot中目前暂时还没有做这项优化,但一些其他的虚拟机(如Excelsior JET)使用了该优化。 - -JVM的GC模块会回收堆中不再使用的对象,但如下回收动作 - -* 标记筛选出可回收对象 - -* 回收和整理内存 - - -都需耗费大量资源。 -若确定一个对象不会逃逸出线程,那让该对象在栈上分配内存就是个不错主意,对象所占用内存空间就可随栈帧出栈而销毁。 - - -在一般应用中,完全不会逃逸的局部对象和不会逃逸出线程的对象所占比例很大,若能使用栈上分配,则大量对象就会随方法结束而自动销毁,GC系统压力会下降很多。 - -**栈上分配可支持方法逃逸,但不能支持线程逃逸。** - - - - -2.2 标量替换(Scalar Replacement) - - -2.2.1 标量 - - - - -若一个数据已经无法再分解成更小数据,JVM中的原始数据类型(如 int、long 等数值类型及 reference 类型)都不能再进一步分解,这些数据即为标量。 - - -2.2.2 聚合量 - - - - -若一个数据可继续分解,则称为聚合量(Aggregate),比如 Java 对象就是聚合量。 - - -2.2.3 标量替换 - - - - -把一个Java对象拆散,根据程序访问情况,将其用到的成员变量恢复为原始类型来访问。 - - -假如逃逸分析能证明一个对象不会被方法外部访问,并且该对象可被分解,那么程序真正执行时将可能不去创建该对象,而改为直接创建它的若干个被这方法使用的成员变量。 -将对象拆分后: - -* 可让对象的成员变量在栈上 (栈上存储的数据,很大概率会被JVM分配至物理机器的高速寄存器中存储)分配和读写 - -* 为后续进步优化创建条件 - - -2.2.4 适用场景 - - - -标量替换可视为栈上分配一种特例,实现更简单(不用考虑对象完整结构的分配),但对逃逸程度的要求更高,它不允许对象逃逸出方法范围内。 - - - - - -2.3 同步消除(Synchronization Elimination) - - - - -线程同步是个相对耗时的过程,若逃逸分析能确定一个变量不会逃逸出线程,即不会被其他线程访问,则该变量的读写肯定不会有线程竞争, 也可安全消除对该变量实施的同步措施。 - -> 逃逸分析的论文在1999年就已发表,但到JDK 6,HotSpot才开始初步支持逃逸分析,至今该也尚未成熟,主要因为逃逸分析的计算成本高到无法保证带来的性能收益会高于它的消耗。要百分百准确判断一个对象是否会逃逸,需进行一系列复杂数据流敏感的过程间分析,才能确定程序各个分支执行时对此对象的影响。过程间分析这种大压力的分析算法正是即时编译的弱项。试想,若逃逸分析完毕后发现几乎找不到几个不逃逸的对象, 那这些运行期耗用的时间就白费了,所以目前JVM只能采用不那么准确,但时间压力相对较小的算法来完成分析。 - -C和C++原生支持栈上分配(不使用new即可),灵活运用栈内存方面,Java的确是弱势群体。 -在现在仍处于实验阶段的Valhalla项目,设计了新的inline关键字用于定义Java的内联类型, 对标C#的值类型。有了该标识与约束,以后逃逸分析做起来就会简单很多。 - -3 代码实战验证 - - - - - - - - - -3.1 全无优化的代码  - - - -``` -public int test(int x) {   int xx = x + 2;   Point p = new Point(xx, 42);   return p.getX(); } -``` - - - - - - - -3.2 优化step1:内联构造器和getX()方法 - - - -``` -public int test(int x) {   int xx = x + 2;  // 在堆中分配P对象   Point p = point\_memory\_alloc();  // Point构造器被内联后    p.x = xx;   p.y = 42;  // Point::getX()被内联后   return p.x;} -``` - - - - - - - -优化step2:标量替换 - - - - -逃逸分析后,发现在整个test()方法的范围内Point对象实例不会发生任何程度逃逸, 便可对它进行标量替换:把其内部的x和y直接置换出来,分解为test()方法内的局部变量,从而避免了Point对象实例的创建 - -``` -public int test(int x) {    int xx = x + 2;    int px = xx;    int py = 42    return px; } -``` - - -step3:无效代码消除 - - -数据流分析,发现py的值其实对方法不会造成任何影响,那就可以放心地去做无效代码消除得到最终优化结果,如下所示: - -``` -public int test(int x) {   return x + 2; } -``` - -观察测试结果,实施逃逸分析后的程序在MicroBenchmarks中往往能得到不错的成绩,但在实际应用程序中,尤其是大型程序中反而发现实施逃逸分析可能出现效果不稳定,或分析过程耗时但却无法有效判别出非逃逸对象而导致性能(即时编译的收益)下降,所以曾经在很长的一段时间,即使是服务端编译器,也默认不开启逃逸分析(从JDK 6 Update 23开始,服务端编译器中开始才默认开启逃逸分析。),甚至在某些版本(如JDK 6 Update 18)中还曾完全禁止这项优化,一直到JDK 7时这项优化才成为服务端编译器默认开启的选项。 - -若有需要或确认对程序有益,可使用参数: - -* **-XX:+DoEscapeAnalysis **手动开启逃逸分析 - - -开启后可通过参数: - -* **-XX:+PrintEscapeAnalysis **查看分析结果 - - -有逃逸分析支持后,用户可使用如下参数: - -* **-XX:+EliminateAllocations **开启标量替换 - -* **+XX:+EliminateLocks **开启同步消除 - -* **-XX:+PrintEliminateAllocations **查看标量的替换情况 - - - 让我们一起期待该JIT优化技术之逃逸分析的发展。 - -> 参考 -> -> 《深入理解 Java 虚拟机》 \ No newline at end of file diff --git "a/JDK/JVM/Java\345\206\205\345\255\230\346\250\241\345\236\213\346\267\261\345\205\245\350\257\246\350\247\243(JMM).md" "b/JDK/JVM/Java\345\206\205\345\255\230\346\250\241\345\236\213\346\267\261\345\205\245\350\257\246\350\247\243(JMM).md" deleted file mode 100644 index 1dbb544b41..0000000000 --- "a/JDK/JVM/Java\345\206\205\345\255\230\346\250\241\345\236\213\346\267\261\345\205\245\350\257\246\350\247\243(JMM).md" +++ /dev/null @@ -1,128 +0,0 @@ -# 前言 -定义俩共享变量及俩方法: -- 第一个方法, -- 第二个方法 -- (r1,r2)的可能值有哪些? -![](https://img-blog.csdnimg.cn/05139ccfbb40447a869632ff35959841.png) - -在单线程环境下,可先调用第一个方法,最终(r1,r2)为(1,0) -也可以先调用第二个方法,最终为(0,2)。 - -![](https://img-blog.csdnimg.cn/20200404214401993.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) -# 1 Java内存模型的意义 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTkyYTFmZGY0OGJlMTllMDYucG5n?x-oss-process=image/format,png) -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTIzZTVlOWE0OWFkZWI1YTEucG5n?x-oss-process=image/format,png) -JMM 与硬件内存架构对应关系![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTVlMTM3NGEwYWJmOWM5MjkucG5n?x-oss-process=image/format,png) -JMM抽象结构图 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWQ0ZWE4ODQzYTg4YTk0MGQucG5n?x-oss-process=image/format,png) -内存模型描述程序的可能行为。 - -Java虚拟机规范中试图定义一种Java内存模型,`来屏蔽掉各种硬件和os的内存访问差异`,规定: -- 线程如何、何时能看到其他线程修改过的共享变量的值 -- 必要时,如何同步地访问共享变量 - -以实现让Java程序在各种平台下都能达到一致性的内存访问效果。 - -JMM通过检查执行跟踪中的每个读操作,并根据某些规则检查该读操作观察到的写操作是否有效来工作。 - -只要程序的所有执行产生的结果都可由JMM预测。具体实现者任意实现,包括操作的重新排序和删除不必要的同步。 - -JMM决定了在程序的每个点上可以读取什么值。 -## 1.1 共享变量(Shared Variables) -可在线程之间共享的内存称为`共享内存或堆内存`。所有实例字段、静态字段和数组元素都存储在堆内存。 -不包括局部变量与方法参数,因为这些是线程私有的,不存在共享。 - -对同一变量的两次访问(读或写),若有一个是写请求,则是冲突的! -# 2 主内存与工作内存 -工作内存缓存 -![](https://img-blog.csdnimg.cn/20191014024209488.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70) -JMM的主要是定义了`各个变量的访问规则`,在JVM中的如下底层细节: -- 将变量存储到内存 -- 从内存中取出变量值 - -为获得较好执行效率,JMM并未限制执行引擎使用处理器的特定寄存器或缓存来和主内存进行交互,也没有限制即时编译器调整代码执行顺序这类权限。 - -JMM规定: -- 所有变量都存储在主内存(Main Memory) -- 每条线程有自己的工作内存(Working Memory) -保存了该线程使用到的`变量的主内存副本拷贝`(线程所访问对象的引用或者对象中某个在线程访问到的字段,不会是整个对象的拷贝) -线程对变量的所有操作(读,赋值等)都必须在工作内存进行,不能直接读写主内存中的变量 -volatile变量依然有工作内存的拷贝,,是他特殊的操作顺序性规定,看起来如同直接在主内存读写 -不同线程间,无法直接访问对方工作内存中的变量,线程间变量值的传递均要通过主内存 - -线程、主内存、工作内存三者的交互关系: -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTEyMjA5YjEyZDU3OGEyZWQucG5n?x-oss-process=image/format,png) -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWJiM2QzN2MxNTVjZDgyZDgucG5n?x-oss-process=image/format,png) - -JVM模型与JMM不是同一层次的内存划分,基本毫无关系的,硬要对应起来,从变量,内存,工作内存的定义来看 -- 主内存 《=》Java堆中的对象实例数据部分 -- 工作内存 《=》虚拟机栈中的部分区域 - -从更底层的层次来看: -- 主内存直接对应物理硬件的内存 -- 为更好的运行速度,虚拟机(甚至硬件系统的本身的优化措施)可能会让工作内存优先存储于寄存器和高速缓存器,因为程序运行时主要访问读写的是工作内存 -# 3 内存间同步操作 -## 3.1 线程操作的定义 -### 操作定义 -write要写的变量以及要写的值。 -read要读的变量以及可见的写入值(由此,我们可以确定可见的值)。 -lock要锁定的管程(监视器monitor)。 -unlock要解锁的管程。 -外部操作(socket等等..) -启动和终止 -### 程序顺序 -如果一个程序没有数据竞争,那么程序的所有执行看起来都是顺序一致的 - -本规范只涉及线程间的操作; -一个变量如何从主内存拷贝到工作内存,从工作内存同步回主内存的实现细节 - -JMM 本身已经定义实现了以下8种操作来完成,且都具备`原子性` -- lock(锁定) -作用于主内存变量,把一个变量标识为一条线程独占的状态 -- unlock(解锁) -作用于主内存变量,把一个处于锁定状态的变量释放,释放后的变量才可以被其它线程锁定 -unlock之前必须将变量值同步回主内存 -- read(读取) -作用于主内存变量,把一个变量的值从主内存传输到工作内存,以便随后的load -- load(载入) -作用于工作内存变量,把read从主内存中得到的变量值放入工作内存的变量副本 -- use(使用) -作用于工作内存变量,把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到的变量的值得字节码指令时将会执行这个操作 -- assign(赋值) -作用于工作内存变量,把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作 -- store(存储) -作用于工作内存变量,把工作内存中一个变量的值传送到主内存,以便随后的write操作使用 -- write(写入) -作用于主内存变量,把store操作从工作内存中得到的值放入主内存的变量中 - -- 把一个变量从主内存`复制`到工作内存 -就要顺序执行read和load - -- 把变量从工作内存`同步`回主内存 -就要顺序地执行store和write操作 - -JMM只要求上述两个操作必须`按序执行`,而没有保证连续执行 -也就是说read/load之间、store/write之间可以插入其它指令 -如对主内存中的变量a,b访问时,一种可能出现的顺序是read a->readb->loadb->load a - -JMM规定执行上述八种基础操作时必须满足如下 -## 3.1 同步规则 -◆ 对于监视器 m 的解锁与所有后续操作对于 m 的加锁 `同步`(之前的操作保持可见) -◆对 volatile变量v的写入,与所有其他线程后续对v的读同步 - -◆ `启动` 线程的操作与线程中的第一个操作同步 -◆ 对于每个属性写入默认值(0, false, null)与每个线程对其进行的操作同步 -◆ 线程 T1的最后操作与线程T2发现线程T1已经结束同步。( isAlive ,join可以判断线程是否终结) -◆ 如果线程 T1中断了T2,那么线程T1的中断操作与其他所有线程发现T2被中断了同步通过抛出*InterruptedException*异常,或者调用*Thread.interrupted*或*Thread.isInterrupted* - -- 不允许read/load、store/write操作之一单独出现 -不允许一个变量从主内存读取了但工作内存不接收,或从工作内存发起回写但主内存不接收 -- 不允许一个线程丢弃它的最近的assign -即变量在工作内存中改变(为工作内存变量赋值)后必须把该变化同步回主内存 -- 新变量只能在主内存“诞生”,不允许在工作内存直接使用一个未被初始化(load或assign)的变量 -换话说就是一个变量在实施use,store之前,必须先执行过assign和load -- 如果一个变量事先没有被load锁定,则不允许对它执行unlock,也不允许去unlock一个被其它线程锁定的变量 -- 对一个变量执行unloack前,必须把此变量同步回主内存中(执行store,write) - -> 参考 -> - https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.1 \ No newline at end of file diff --git "a/JDK/JVM/Java\346\200\247\350\203\275\350\260\203\344\274\230\345\267\245\345\205\267\344\271\213JDK\345\221\275\344\273\244\350\241\214.md" "b/JDK/JVM/Java\346\200\247\350\203\275\350\260\203\344\274\230\345\267\245\345\205\267\344\271\213JDK\345\221\275\344\273\244\350\241\214.md" deleted file mode 100644 index 59c68099d5..0000000000 --- "a/JDK/JVM/Java\346\200\247\350\203\275\350\260\203\344\274\230\345\267\245\345\205\267\344\271\213JDK\345\221\275\344\273\244\350\241\214.md" +++ /dev/null @@ -1,158 +0,0 @@ -## 1.1 jps -类似Linux的ps,但jps只列出Java的进程。可方便查看Java进程的启动类、传入参数和JVM参数。直接运行,不加参数,列出Java程序的进程ID及Main函数名称。 -![jps命令本质也是Java程序](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTljMjE4OWRlZDljYmQ1M2UucG5n?x-oss-process=image/format,png) -- -m 输出传递给Java进程的参数![](https://img-blog.csdnimg.cn/20210117135731422.png) - -- -l 输出主函数的完整路径![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWJkOGY1NzU2NWQzNDY1NDMucG5n?x-oss-process=image/format,png) -- -q 只输出进程ID![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWUzODhhY2U5MmYzMDNkYWYucG5n?x-oss-process=image/format,png) -- -v 显示传递给jvm的参数![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTlhM2JhYjkzZjk0Y2U2YzgucG5n?x-oss-process=image/format,png) -## 1.2 jstat -观察Java应用程序运行时信息的工具,详细查看堆使用情况以及GC情况 -- jstat -options -![](https://img-blog.csdnimg.cn/20210117142257563.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70) - -### 1.2.1 jstat -class pid -显示加载class的数量及所占空间等信息 -- -compiler -t:显示JIT编译的信息 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTcyM2Y5ZjA4MjMyMjcyMDQucG5n?x-oss-process=image/format,png) -### 1.2.2 -gc pid -显示gc信息,查看gc的次数及时间 -![](https://img-blog.csdnimg.cn/20210117144320641.png) - -```shell -➜ ~ jstat -gc 87552 - S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT CGC CGCT GCT -25088.0 20992.0 0.0 20992.0 500224.0 56227.0 363008.0 35238.1 76672.0 72902.5 10368.0 9590.5 9 0.078 3 0.162 - - 0.239 -``` - -### 1.2.3 -gccapacity -比-gc多了各个代的最大值和最小值 -![jstat -gccapacity 3661](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LThmMGMwNTY4YTgzM2M5MzkucG5n?x-oss-process=image/format,png) -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWI1ZGVjMWVmNDgzYzM5ODUucG5n?x-oss-process=image/format,png) -### -gccause -最近一次GC统计和原因 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTE5ZTcxOTQ3NThlNGI5MzgucG5n?x-oss-process=image/format,png) -- LGCC:上次GC原因 -- GCC:当前GC原因 -![ -gcnew pid:new对象的信息。](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTMwNzQwZDQ1ODIyNzRmZGUucG5n?x-oss-process=image/format,png) -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWU4ZTkxN2Y1YjQ2YjgzMWMucG5n?x-oss-process=image/format,png) -### jstat -gcnewcapacity pid:new对象的信息及其占用量 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTVkNDIyNjNhNWNmYjQ1MjAucG5n?x-oss-process=image/format,png) - -![-gcold 显示老年代和永久代信息](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTI3OTI1YWUyNzA3YWI5Y2MucG5n?x-oss-process=image/format,png) -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWRkYzU1MTQ2ZDI3ZmY4ZDgucG5n?x-oss-process=image/format,png) -![-gcoldcapacity展现老年代的容量信息](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTc0NjhlMDA4YTUwZGMyMDQucG5n?x-oss-process=image/format,png) -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTA0OWRjZjBhYzRkNjM0YjYucG5n?x-oss-process=image/format,png) -### -gcutil -相比于-gc 参数,只显示使用率而非使用量了。 -显示GC回收相关信息 -![](https://img-blog.csdnimg.cn/20210117144845233.png) - -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWY5MDZjZTFhZDA2MDA3YTAucG5n?x-oss-process=image/format,png) -### -printcompilation -当前VM执行的信息 -![jstat -printcompilation 3661](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWMxMzk3NmMzZjE4OTViYWIucG5n?x-oss-process=image/format,png) -### 还可以同时加两个数 -- 输出进程4798的ClassLoader信息,每1秒统计一次,共输出2次 -![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTBjMjcyNGI5MzIwOWU4ZjIucG5n?x-oss-process=image/format,png) -## 1.3 jinfo -`jinfo