├── Database ├── Mysql.md └── Oracle.md ├── JVM ├── JVM垃圾收集器.md ├── JVM基础知识点.md ├── asset │ ├── 1.8堆内存模型.jpg │ ├── Hotshot.jpg │ ├── JVM内存模型的概述.jpg │ ├── Java堆分配内存空间.jpg │ ├── Java对象创建底层顺序.jpg │ ├── Java对象结构.jpg │ ├── cms垃圾收集器.jpg │ ├── java堆.jpg │ ├── parnew垃圾收集器.jpg │ ├── serial垃圾收集器.jpg │ ├── 复制算法.jpg │ ├── 对象句柄访问.jpg │ ├── 对象存活算法可达性.jpg │ ├── 对象访问定位直接访问.jpg │ ├── 标记整理.jpg │ ├── 标记清除.jpg │ ├── 程序计数器.jpg │ └── 虚拟机栈.jpg ├── 垃圾回收涉及的主要算法.md └── 对象.md ├── Message Queue ├── Kafka.md ├── README.md └── imgs │ ├── HW和LEO.png │ ├── Partition.png │ ├── RangeAssignor.png │ ├── RoundRobinAssignor.png │ ├── RoundRobinAssignor2.png │ ├── sendModel.png │ └── 整体架构.png ├── Ngnix ├── Nginx目录文件.md ├── accessLog日志.md └── 安装Nginx.md ├── README.md ├── Zookeeper └── zookeeper.md ├── springboot ├── SpringBoot 注解.md ├── SpringBoot拦截器和 Servlet3.0自定义Filter、Listener.md ├── Springboot 的目录结构.md ├── springboot 异常处理.md └── thymeleaf.md └── 常见Java漏洞类型和具体解决方法.md /Database/Mysql.md: -------------------------------------------------------------------------------- 1 | - [特殊属性字段](#特殊属性字段) 2 | - [1、bit](#1bit) 3 | - [2、blob](#2blob) 4 | - [3、binary](#3binary) 5 | 6 | ## 特殊属性字段 7 | ### 1、bit 8 | 可以使用b'value'符号写位字段值。value是一个用0和1写成的二进制值。 9 | 10 | 位字段符号可以方便指定分配给BIT列的值: 11 | 12 | ```sql 13 | mysql> CREATE TABLE t (b BIT(8)); 14 | 15 | mysql> INSERT INTO t SET b = b'11111111'; 16 | 17 | mysql> INSERT INTO t SET b = b'1010'; 18 | ``` 19 | 20 | **查询** 21 | ```sql 22 | -- 十进制显示 23 | select field+0 from tablename; 24 | -- 二进制 25 | select bin(field+0) from tablename; 26 | -- 八进制 27 | select oct(field+0) from tablename; 28 | -- 十六进制 29 | select hex(field+0) from tablename; 30 | ``` 31 | 32 | ### 2、blob 33 | 34 | 在MySQL中Blob是一个二进制的对象,它是一个可以存储大量数据的容器(如图片,音乐等等),且能容纳不同大小的数据,在MySQL中有四种Blob类型,他们的区别就是可以容纳的信息量不容分别是以下四种: 35 | 36 | - TinyBlob类型 最大能容纳255B的数据 37 | - Blob类型 最大能容纳65KB的 38 | - MediumBlob类型 最大能容纳16MB的数据 39 | - LongBlob类型 最大能容纳4GB的数据 40 | 41 | ```sql 42 | -- 常用类型为 utf8mb4,gbk,utf8 43 | select CONVERT(`field` USING utf8mb4)AS dataConvertValue from tableName; 44 | ``` 45 | 46 | ### 3、binary 47 | 48 | binary 和 varbinary 类型类似于 CHAR 和 VARCHAR,不同的是它们包含二进制字节字符串 49 | - binary:固定长度二进制字符串,**指定长度后,不足最大长度的,将在它们右边填充 “\0” 补齐,以达到指定长度** 50 | - varbinary:可变长度二进制字符串 51 | 52 | ```sql 53 | -- 常用类型为 utf8mb4,gbk,utf8 54 | select CONVERT(`field` USING utf8mb4)AS dataConvertValue from tableName; 55 | ``` 56 | -------------------------------------------------------------------------------- /Database/Oracle.md: -------------------------------------------------------------------------------- 1 | - [特殊属性字段](#特殊属性字段) 2 | - [1、Blob和Clob](#1blob和clob) 3 | 4 | ## 特殊属性字段 5 | ### 1、Blob和Clob 6 | BLOB和CLOB都是大字段类型,BLOB是按二进制来存储的,而CLOB是可以直接存储文字的。其实两个是可以互换的的,或者可以直接用LOB字段代替这两个。但是为了更好的管理ORACLE数据库,通常像图片、文件、音乐等信息就用BLOB字段来存储,先将文件转为二进制再存储进去。而像文章或者是较长的文字,就用CLOB存储,这样对以后的查询更新存储等操作都提供很大的方便。 7 | ```sql 8 | -- dbms_lob.substr()函数用来操作的大型对象,叫做大型对象定位器,但是此方法查询出的数据长度不可超过4000 9 | -- 查询Blob和Clob属性字段,以文本格式显示 10 | select utl_raw.cast_to_varchar2("field") from table 11 | 12 | select utl_raw.cast_to_varchar2(dbms_lob.substr("field")) from table 13 | 14 | -- dbms_lob.instr(源字符串, 目标字符串, 起始位置, 匹配序号) 15 | 16 | -- 以Blob属性字段为条件查询 17 | select * from table t where dbms_lob.instr(blob_msg,utl_raw.CAST_TO_RAW('同意'),1,1)>0; 18 | 19 | -- 以Clob属性字段为条件查询 20 | select * from table t where dbms_lob.instr(msg_clob,'liaoyuhan',1,1) > 0; 21 | ``` -------------------------------------------------------------------------------- /JVM/JVM垃圾收集器.md: -------------------------------------------------------------------------------- 1 | - [1、serial垃圾收集器](#1-serial-----) 2 | - [2、serial old 垃圾收集器](#2-serial-old------) 3 | - [3、PerNew垃圾收集器](#3-pernew-----) 4 | - [4、Parallel Scavenge收集器](#4-parallel-scavenge---) 5 | + [4.1 Parallel Scavenge 是什么?](#41-parallel-scavenge-----) 6 | + [4.2 Parallel Scavenge 应用场景](#42-parallel-scavenge-----) 7 | + [4.3 Parallel Scavenge 的参数设置](#43-parallel-scavenge------) 8 | - [5、Parallel Old收集器](#5-parallel-old---) 9 | - [6、CMS收集器](#6-cms---) 10 | 11 | ## 1、serial垃圾收集器 12 | - serial 回收器是最基础,历史最悠久的收集器,用于**新生代** 13 | - Serial是一个**单线程**的垃圾收集器 14 | 15 | **特点** 16 | 17 | 1. “Stop The World”,它进行垃圾收集时,必须暂停其他所有的线作线程,直到它收集结束。 18 | 2. 多用于桌面应用,**Client端**的垃圾回收器。桌面应用(Client端应用)内存小,进行垃圾回收的时间比较短,只要不频繁发生停顿就可以接受 19 | 3. 使用复制算法完成垃圾清理工作。 20 | 21 | **缺点** 22 | 23 | 1. 垃圾回收速度较慢且回收能力有限 24 | 25 | ![serial垃圾收集器](https://github.com/xujiangchen/Java-Study-Notes/blob/main/JVM/asset/serial%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E5%99%A8.jpg) 26 | 27 | ## 2、serial old 垃圾收集器 28 | - Serial Old是 Serial收集器的老年代版本 29 | - 针对老年代 30 | - 采用"标记-整理"算法 31 | - 单线程收集 32 | - 主要用于Client模式 33 | 34 | ## 3、PerNew垃圾收集器 35 | - ParNew垃圾收集器是Serial收集器的**多线程**版本。**除了多线程外,其余的行为、特点和Serial收集器一样**。 36 | - 可以**使用-XX: ParallelGCThreads参数**来限制垃圾收集的线程数。一般设置和CPU的核数相等即可,但也不用太多,会产生线程上下文切换的时耗。 37 | 38 | ![PerNew垃圾收集器](https://github.com/xujiangchen/Java-Study-Notes/blob/main/JVM/asset/parnew%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E5%99%A8.jpg) 39 | 40 | ## 4、Parallel Scavenge收集器 41 | 42 | #### 4.1 Parallel Scavenge 是什么? 43 | Parallel Scavenge收集器是一个**新生代收集器**,它也是使用**复制算法**的收集器,也是**并行的多线程**收集器。 44 | 45 | 由于与吞吐量关系密切,Parallel Scavenge收集器也经常称为“吞吐量优先”收集器 46 | 47 | > 什么是吞吐量:吞吐量是什么?CPU用于运行用户代码的时间与CPU总时间的比值,99%时间执行用户线程,1%时间回收垃圾 ,这时候吞吐量就是99%。 48 | 49 | #### 4.2 Parallel Scavenge 应用场景 50 | - 场景一: 高吞吐量为目标,即减少垃圾收集时间(就是每次垃圾收集时间短,但是收集次数多),让用户代码获得更长的运行时间。 51 | - 场景二:当应用程序运行在具有多个CPU上,对暂停时间没有特别高的要求时,即程序主要在后台进行计算,而不需要与用户进行太多交互;就是说可以计算完后进行一次长时间的GC。 52 | 53 | #### 4.3 Parallel Scavenge 的参数设置 54 | 55 | (A)、"-XX:MaxGCPauseMillis" 56 | 57 | 控制最大垃圾收集停顿时间,大于0的毫秒数;MaxGCPauseMillis设置得稍小,停顿时间可能会缩短,但也可能会使得吞吐量下降;因为可能导致垃圾收集发生得更频繁。 58 | 59 | (B)、"-XX:GCTimeRatio" 60 | 61 | 设置垃圾收集时间占总时间的比率,0<n<100的整数;GCTimeRatio相当于设置吞吐量大小; 垃圾收集执行时间占应用程序执行时间的比例的计算方法是: 62 | 63 | ``` 64 | 1 / (1 + n) 65 | 66 | 例如,选项-XX:GCTimeRatio=19,设置了垃圾收集时间占总时间的5%--1/(1+19) 67 | 68 | 默认值是1%--1/(1+99),即n=99; 69 | ``` 70 | 71 | ## 5、Parallel Old收集器 72 | - Parallel Old垃圾收集器是Parallel Scavenge收集器的老年代版本 73 | - JDK1.6中才开始提供,JDK1.6及之后用来代替老年代的Serial Old收集器 74 | - 采用"标记-整理"算法 75 | - 多线程收集 76 | - "-XX:+UseParallelOldGC":指定使用Parallel Old收集器 77 | 78 | ## 6、CMS收集器 79 | > 集中在互联网站或者B/S系统的服务端上 80 | 81 | CMS收集器:Mostly-Concurrent收集器,也称并发标记清除收集器(Concurrent Mark-Sweep GC,CMS收集器),**它管理新生代的方式与Parallel收集器和Serial收集器相同,而在老年代则是尽可能得并发执行**,每个垃圾收集器周期只有==2次==短停顿。 82 | 83 | **步骤** 84 | 1. 初始标记(CMS initial mark)-----标记⼀下GC Roots 能直接关联到的对象,速度很快 85 | 2. 并发标记(CMS concurrent mark --------并发标记阶段就是进行 GC RootsTracing(跟搜索算法) 的过程 86 | 3. 重新标记(CMS remark) -----------为了修正并发标记期间因⽤户程序导致标记产生变动的标记记录 87 | 4. 并发清除(CMS concurrent sweep) 88 | 89 | **缺点** 90 | - 对CPU资源非常敏感 91 | - 无法处理浮动垃圾,程序在进行并发清除阶段⽤户线程所产生的新垃圾叫做浮动垃圾 92 | - 标记-清除算法会产生空间碎片 93 | 94 | ![CMS收集器](https://github.com/xujiangchen/Java-Study-Notes/blob/main/JVM/asset/cms%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E5%99%A8.jpg) 95 | 96 | ## 7、G1收集器 97 | > G1是一款面向服务端应用的垃圾收集器 98 | 99 | **步骤** 100 | - 初始标记(Initial Marking) --标记一下 GC Roots 能直接关联到的对象 101 | - 并发标记(Concurrent Marking)---从GC Root 开始对堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行 102 | - 最终标记(Final Marking) ---为了修正在并发标记期间因用户程序继续运作而导致标记产生 103 | 变动的那一部分标记记录。虚拟机将这段时间对象变化记录在线程 Remembered Set Logs里面,最终标记阶段需要把 Remembered Set Logs的数据合并到 Remembered Set 中 104 | - 筛选回收(Live Data Counting and Evacuation) 105 | 106 | **扩展** 107 | 108 | G1的每个region都有一个Remember Set(Rset) 109 | 这个数据结构,用来保存别的region的对象对我这个region的对象的引用,通过Remember Set我们可以找到哪些对象引用了当前region的对象 110 | 111 | **优势** 112 | 1. **空间整合**:基于“标记-整理”算法实现为主和Region之间采用复制算法实现的垃圾收集 113 | 2. **可预测的停顿**:这是 G1 相对于 CMS 的另一个优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1除了追求低停顿外,还能建立可预测的停顿时间模型 114 | 3. 在 G1 之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1 不再是这样。使用 G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个 Java雄划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新**生代和老年代不再是物理隔离的了,它们都是一部分 Region(不需要连续)的集合**。 115 | -------------------------------------------------------------------------------- /JVM/JVM基础知识点.md: -------------------------------------------------------------------------------- 1 | - [1、JVM内存模型的概述](#1jvm内存模型的概述) 2 | - [2、程序计数器](#2程序计数器) 3 | - [2.1 什么是程序计数器?](#21-什么是程序计数器) 4 | - [2.2 字节码的执行原理](#22-字节码的执行原理) 5 | - [2.3 程序计数器的作用?](#23-程序计数器的作用) 6 | - [2.4 程序计数器的特点](#24-程序计数器的特点) 7 | - [3、JAVA虚拟机栈](#3java虚拟机栈) 8 | - [3.1 什么是JAVA虚拟机栈?](#31-什么是java虚拟机栈) 9 | - [3.2 虚拟机栈原理](#32-虚拟机栈原理) 10 | - [3.3 易出现的问题](#33-易出现的问题) 11 | - [4、本地方法栈](#4本地方法栈) 12 | - [4.1 什么是本地方法栈?](#41-什么是本地方法栈) 13 | - [4.2 什么是Native方法](#42-什么是native方法) 14 | - [4.3 本地方法栈和Java虚拟机栈的异同](#43-本地方法栈和java虚拟机栈的异同) 15 | - [4.4 注意点](#44-注意点) 16 | - [5、Java堆](#5java堆) 17 | - [5.1 什么是Java堆](#51-什么是java堆) 18 | - [5.2 Java堆的特点](#52-java堆的特点) 19 | - [6、Java方法区](#6java方法区) 20 | - [6.1 什么是Java方法区](#61-什么是java方法区) 21 | - [6.2 错误](#62-错误) 22 | - [6.3 运行时常量池(以jdk1.7 以后为重点)](#63-运行时常量池以jdk17-以后为重点) 23 | - [6.4 常量池的分类【理解即可】](#64-常量池的分类理解即可) 24 | - [6.4.1 class文件常量池](#641-class文件常量池) 25 | - [6.4.2 运行时常量池](#642-运行时常量池) 26 | - [6.4.3 字符串常量池](#643-字符串常量池) 27 | 28 | ## 1、JVM内存模型的概述 29 | 30 | ![JVM内存模型的概述](https://github.com/xujiangchen/Java-Study-Notes/blob/main/JVM/asset/JVM%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B%E7%9A%84%E6%A6%82%E8%BF%B0.jpg) 31 | 32 | **线程共享数据区:方法区、堆** 33 | 34 | **线程隔离数据区:虚拟机栈、本地方法栈、程序计数器** 35 | 36 | ## 2、程序计数器 37 | 38 | #### 2.1 什么是程序计数器? 39 | 40 | 程序计数器是当前线程正在执行的字节码的地址。程序计数器是线程隔离的,每一个线程在工作的时候都有一个独立的计数器。**它可以看作是当前线程所执行的字节码的序号号指示器**。 41 | 42 | #### 2.2 字节码的执行原理 43 | 44 | 编译后的字节码在没有经过JIT(实时编译器)编译前,是通过字节码解释器进行解释执行。其执行原理为:字节码解释器读取内存中的字节码,按照顺序读取字节码指令,读取一个指令就将其翻译成固定的操作,根据这些操作进行分支,循环,跳转等动作。 45 | 46 | #### 2.3 程序计数器的作用? 47 | 48 | 从字节码的执行原理来看,**单线程的情况下程序计数器是可有可无的**。因为即使没有程序计数器的情况下,程序会按照指令顺序执行下去,即使遇到了分支跳转这样的流程也会按照跳转到指定的指令处继续顺序执行下去,是完全能够保证执行顺序的。 49 | 50 | **但是现实中程序往往是多线程协作完成任务的**。JVM的多线程是通过CPU时间片轮转来实现的,某个线程在执行的过程中可能会因为时间片耗尽而挂起。当它再次获取时间片时,需要从挂起的地方继续执行。在JVM中,通过程序计数器来记录程序的字节码执行位置。**程序计数器具有线程隔离性,每个线程拥有自己的程序计数器** 51 | 52 | #### 2.4 程序计数器的特点 53 | 54 | (1)程序计数器具有线程隔离性 55 | 56 | (2)程序计数器占用的内存空间非常小,可以忽略不计 57 | 58 | (3)程序计数器是java虚拟机规范中唯一一个没有规定任何OutofMemeryError(内存溢出)的区域,原因jvm自行维护,程序员不允许操作,所以不会出现om的情况 59 | 60 | (4)程序执行的时候,程序计数器是有值的,其记录的是程序正在执行的字节码的地址 61 | 62 | (5)执行native本地方法时,程序计数器的值为空。原因是native方法是java通过jni调用本地C/C++库来实现,非java字节码实现,所以无法统计 63 | 64 | **实战** 65 | ```java 66 | public class User{ 67 | 68 | String name; 69 | 70 | public String getName() { 71 | return name; 72 | } 73 | public void setName(String name){ 74 | this.name = name; 75 | } 76 | } 77 | ``` 78 | 79 | ![程序计数器](https://github.com/xujiangchen/Java-Study-Notes/blob/main/JVM/asset/%E7%A8%8B%E5%BA%8F%E8%AE%A1%E6%95%B0%E5%99%A8.jpg) 80 | 81 | ## 3、JAVA虚拟机栈 82 | 83 | - 栈是一个先进后出,后进先出的数据结构 84 | 85 | #### 3.1 什么是JAVA虚拟机栈? 86 | 87 | 作用于方法执行的Java内存区域。它的生命周期与线程相同(**随线程而生,随线程而灭**) 88 | 89 | #### 3.2 虚拟机栈原理 90 | 91 | 每个方法在执行的同时都会创建多个栈帧(StackFramel)用于存储局部变量表、操作数、栈、动态链接等信息。**每一个方法从调用直到执行完成的过程,就对应着一个栈帧在虚拟机栈中进栈到出栈的过程。** 92 | 93 | #### 3.3 易出现的问题 94 | 95 | - 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出**StackOverflowError异常** 96 | - 如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出**OutOfMemoryError异常**;当前大部分JVM都可以动态扩展,只不过JVM规范也允许固定长度的虚拟机栈 97 | 98 | **实战** 99 | 100 | ``` 101 | public class A { 102 | public native static void c(); 103 | 104 | public static void a(){ 105 | System.out.println("enter method a"); 106 | } 107 | 108 | public static void b(){ 109 | a(); 110 | System.out.println("enter method b"); 111 | } 112 | 113 | public static void main(String[] args) { 114 | b(); 115 | System.out.println("enter method main"); 116 | } 117 | } 118 | ``` 119 | 120 | ![虚拟机栈](https://github.com/xujiangchen/Java-Study-Notes/blob/main/JVM/asset/%E8%99%9A%E6%8B%9F%E6%9C%BA%E6%A0%88.jpg) 121 | 122 | ## 4、本地方法栈 123 | 124 | #### 4.1 什么是本地方法栈? 125 | 126 | 本地方法栈(Native MethodStacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。 127 | 128 | #### 4.2 什么是Native方法 129 | 130 | Native关键字修饰的方法叫做本地方法,本地方法和其它方法不一样,本地方法意味着**和平台有关,因此使用了native的程序可移植性都不太高。被Native关键字声明的方法说明该方法不是以Java语言实现**的,而是以本地语言实现的,Java可以直接拿来用,例如,某些dll动态链接库文件。 131 | 132 | #### 4.3 本地方法栈和Java虚拟机栈的异同 133 | 134 | 其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。**与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常**。 135 | 136 | #### 4.4 注意点 137 | 138 | 虽然jvm规定本地方法栈和Java虚拟机栈是分开的,但是本地方法栈和Java虚拟机栈结构和功能相似,Hotshot将Java虚拟机栈和本地方法栈合二为一。 139 | 140 | > HotSpot虚拟机是SunJDK和OpenJDK中所带的虚拟机,也是目前使用范围最广的Java虚拟机。HotSpot只是jvm实现的一种方式而已 141 | 142 | ![Hotshot](https://github.com/xujiangchen/Java-Study-Notes/blob/main/JVM/asset/Hotshot.jpg) 143 | 144 | ## 5、Java堆 145 | 146 | #### 5.1 什么是Java堆 147 | 148 | 是Java内存区域中一块用来存放对象实例的区域,**几乎所有的数组和对象实例都在这里分配内存**。 149 | 150 | #### 5.2 Java堆的特点 151 | 152 | (1)Java 中的堆是 JVM 所管理的最大的一块内存空间 153 | 154 | (2)Java 堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC 堆”(Garbage Collection) 155 | 156 | (3)Java堆可以分成新生代和老年代,新生代又可分为To Space、From Space、Eden,这样划分的目的是为了使JVM能够更好的管理堆内存中的对象,包括内存的分配以及回收。 157 | > 老年代 : 三分之二的堆空间,年轻代 : 三分之一的堆空间 eden区: 8/10 的年轻代空间 survivor0:1/10的年轻代空间survivor1:1/10的年轻代空间 158 | 159 | >在JDK1.8版本废弃了永久代,替代的是元空间(MetaSpace),元空间与永久代上类似,都是方法区的实现,他们最大区别是:元空间并不在JVM中,而是使用本地内存。 160 | 161 | 堆的内存模型大致为(以jdk1.8为例): 162 | 163 | ![java1.8堆内存模型](https://github.com/xujiangchen/Java-Study-Notes/blob/main/JVM/asset/1.8%E5%A0%86%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B.jpg) 164 | 165 | **实战** 166 | 167 | ![java堆](https://github.com/xujiangchen/Java-Study-Notes/blob/main/JVM/asset/java%E5%A0%86.jpg) 168 | 169 | ## 6、Java方法区 170 | 171 | #### 6.1 什么是Java方法区 172 | 173 | 方法区在JVM中也是一个非常重要的区域,它与堆一样,是被线程共享的区域。在方法区中,存储了每个类的信息、静态变量、常量以及编译器编译后的代码等。 174 | 175 | > 方法区(method area)只是JVM规范中定义的一个概念,用于存储类信息、常量池、静态变量、JIT编译后的代码等数据,具体放在哪里,不同的实现可以放在不同的地方。而永久代是Hotspot虚拟机特有的概念,是方法区的一种实现,别的JVM都没有这个东西。 176 | 177 | **扩展知识** 178 | 179 | ```java 180 | 类的信息(Class对象): 181 | 类的名称、方法信息、字段信息、运行时常量池 182 | 183 | 静态变量: 184 | 1.由 static 修饰 185 | 2.静态变量是一个公共的存储单元,所以类的任何一个对象去修改它时,都是在对同一个内存单元做操作。 186 | 187 | 常量: 188 | 1.由 final 修饰 189 | 2.声明常量的同时要赋予一个初始值。常量一旦初始化就不可以被修改。 190 | ``` 191 | 192 | #### 6.2 错误 193 | 194 | 根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出**OutOfMemoryError异常**。 195 | 196 | #### 6.3 运行时常量池(以jdk1.7 以后为重点) 197 | 198 | - 是一个**HashSet**的结构 199 | 200 | 运行时常量池是方法区的一部分,Class文件除了有类的版本、字段、方法、接口等描述信息,还有一项信息是常量池,用于存放**编译器生成的各种字面量和符号引用**,这部分内容将在类加载后进入方法区的运行时常量池中存放。 201 | 202 | 203 | 204 | 205 | 206 | #### 6.4 常量池的分类【理解即可】 207 | 208 | ##### 6.4.1 class文件常量池 209 | 210 | 在Class文件中除了有类的版本【高版本可以加载低版本】、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table)【此时没有加载进内存,也就是在文件中】,用于存放编译期生成的各种字面量和符号引用。 211 | 212 | **扩展知识** 213 | 214 | ```java 215 | 字面量是指由字母,数字等构成的字符串或者数值,它只能作为右值出现,所谓右值是指等号右边的值,如:int a = 123这里的a为左值,123为右值。 216 | 217 | 常量和变量都属于变量,只不过常量是赋过值后不能再改变的变量,而普通的变量可以再进行赋值操作。 218 | 219 | int a;//a变量 220 | 221 | const int b=10;//b为常量,10为字面量 222 | 223 | string str="hello world";//str为变量,hello world为也字面量 224 | 225 | 226 | 符号引用: 227 | 1.类和接口和全限定名:例如对于String这个类,它的全限定名就是java/lang/String。 228 | 2.字段的名称和描述符:所谓字段就是类或者接口中声明的变量,包括类级别变量(static)和实例级的变量。 229 | 3.方法的名称和描述符。所谓描述符就相当于方法的参数类型+返回值类型。 230 | ``` 231 | 232 | ##### 6.4.2 运行时常量池 233 | 234 | 我们知道类加载器会加载对应的Class文件,而上面的class文件中的常量池,会**在类加载后进入方法区中的运行时常量池【此时存在在内存中】**。并且需要的注意的是,**运行时常量池是全局共享的**,多个类共用一个运行时常量池。并且class文件中常量池多个相同的字符串在运行时常量池只会存在一份。**注意运行时常量池存在于方法区中**。 235 | 236 | ##### 6.4.3 字符串常量池 237 | 238 | 看名字我们就可以知道字符串常量池会用来存放字符串,也就是说常量池中的文本字符串会在类加载时进入字符串常量池。 239 | 240 | 那字符串常量池和运行时常量池是什么关系呢?上面我们说常量池中的字面量会在类加载后进入运行时常量池,其中字面量中有包括文本字符串,显然从这段文字我们可以**知道字符串常量池存在于运行时常量池中。也就存在于方法区中**。不过在周志明那本深入java虚拟机中有说到,到了JDK1.7时,字符串常量池就被移出了方法区,转移到了堆里了。 241 | 242 | **那么我们可以推断,到了JDK1.7以及之后的版本中,运行时常量池并没有包含字符串常量池,运行时常量池存在于方法区中,而字符串常量池存在于堆中。** 243 | 244 | ```java 245 | public static void main(String[] args) { 246 | // String 直接赋值不会在堆中创建对象,而是只在常量池创建一个对象 247 | String a = "abc"; 248 | String b = "abc"; 249 | // 俩者都指向常量池 结果为true 250 | System.out.println(a==b); 251 | } 252 | 253 | public static void main(String[] args) { 254 | // 首先此行代码创建了两个对象,在执行前会在常量池中创建一个"ABC"的对象 255 | // 然后执行该行代码时new一个"ABC"的对象存放在堆区中;然后str1指向堆区中的对象; 256 | String str1 = new String("ABC"); 257 | //该行代码首先查看"ABC"字符串有没有存在在常量池中,此时存在则直接返回该常量,这里返回后没有引用接受他, 258 | //【假如不存在的话在 jdk1.6中会在常量池中建立该常量,在jdk1.7以后会把堆中该对象的引用放在常量池中】 259 | str1.intern(); 260 | //此时"ABC"已经存在在常量池中,str2指向常量池中的对象; 261 | String str2 = "ABC"; 262 | //str1指向堆区的对象,str2指向常量池中的对象,两个引用指向的地址不同,输入false; 263 | System.out.println(str1 == str2); 264 | // true 265 | System.out.println(str1.intern() == str2); 266 | } 267 | 268 | 269 | public static void main(String[] args) { 270 | // 此行代码执行的底层执行过程是 首先使用StringBuffer的append方法将"2"和"2"拼接在一块, 271 | // 然后调用toString方法new出“22”;所以此时的“22”字符串是创建在堆区的;注意这是常量池中没有数据 272 | String str3 = new String("a") + new String("b"); 273 | //此行代码执行时字符串常量池中没有"22",所以此时在jdk1.6中会在字符串常量池中创建"22",而在jdk1.7以后会把堆中该对象的引用放在常量池中; 274 | str3.intern(); 275 | //此时的str4在jdk1.6中会指向方法区,而在jdk1,7中会指向堆区; 276 | String str4 = "ab"; 277 | //jdk1.6中为false 在jdk1.7中为true; 278 | System.out.println(str3 == str4); 279 | } 280 | ``` 281 | 282 | -------------------------------------------------------------------------------- /JVM/asset/1.8堆内存模型.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/JVM/asset/1.8堆内存模型.jpg -------------------------------------------------------------------------------- /JVM/asset/Hotshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/JVM/asset/Hotshot.jpg -------------------------------------------------------------------------------- /JVM/asset/JVM内存模型的概述.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/JVM/asset/JVM内存模型的概述.jpg -------------------------------------------------------------------------------- /JVM/asset/Java堆分配内存空间.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/JVM/asset/Java堆分配内存空间.jpg -------------------------------------------------------------------------------- /JVM/asset/Java对象创建底层顺序.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/JVM/asset/Java对象创建底层顺序.jpg -------------------------------------------------------------------------------- /JVM/asset/Java对象结构.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/JVM/asset/Java对象结构.jpg -------------------------------------------------------------------------------- /JVM/asset/cms垃圾收集器.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/JVM/asset/cms垃圾收集器.jpg -------------------------------------------------------------------------------- /JVM/asset/java堆.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/JVM/asset/java堆.jpg -------------------------------------------------------------------------------- /JVM/asset/parnew垃圾收集器.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/JVM/asset/parnew垃圾收集器.jpg -------------------------------------------------------------------------------- /JVM/asset/serial垃圾收集器.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/JVM/asset/serial垃圾收集器.jpg -------------------------------------------------------------------------------- /JVM/asset/复制算法.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/JVM/asset/复制算法.jpg -------------------------------------------------------------------------------- /JVM/asset/对象句柄访问.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/JVM/asset/对象句柄访问.jpg -------------------------------------------------------------------------------- /JVM/asset/对象存活算法可达性.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/JVM/asset/对象存活算法可达性.jpg -------------------------------------------------------------------------------- /JVM/asset/对象访问定位直接访问.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/JVM/asset/对象访问定位直接访问.jpg -------------------------------------------------------------------------------- /JVM/asset/标记整理.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/JVM/asset/标记整理.jpg -------------------------------------------------------------------------------- /JVM/asset/标记清除.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/JVM/asset/标记清除.jpg -------------------------------------------------------------------------------- /JVM/asset/程序计数器.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/JVM/asset/程序计数器.jpg -------------------------------------------------------------------------------- /JVM/asset/虚拟机栈.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/JVM/asset/虚拟机栈.jpg -------------------------------------------------------------------------------- /JVM/垃圾回收涉及的主要算法.md: -------------------------------------------------------------------------------- 1 | - [1、对象存活算法引用计数法](#1------------) 2 | + [1.1 算法简介](#11-----) 3 | + [1.2 优缺点](#12----) 4 | + [1.3 实战](#13---) 5 | - [2、对象存活算法可达性分析](#2------------) 6 | + [2.1、算法](#21---) 7 | + [2.2、java中可作为GC Root的对象有](#22-java----gc-root----) 8 | - [3、标记清除算法](#3-------) 9 | - [4、复制算法](#4-----) 10 | + [4.1 具体步骤](#41-----) 11 | - [5、标记整理算法](#5-------) 12 | - [6、分代收集算法](#6-------) 13 | 14 | ## 1、对象存活算法引用计数法 15 | 16 | > 这个算法基本不再使用了,因为有非常明显的缺点 17 | 18 | #### 1.1 算法简介 19 | 20 | 给对象中添加一个引用计数器,每当有一个地方引用他时,计数器值就+1;当引用失效时,计数器值就-1;任何时刻计数器为0的对象就是不可能在被使用 21 | 22 | #### 1.2 优缺点 23 | - **判定效率很高**,引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。 24 | - **不会完全准确**,因为如果出现两个对象相互引用的问题就不行了。就会出现内存泄漏的问题 25 | 26 | #### 1.3 实战 27 | 28 | ```java 29 | public class ReferenceCountingGC { 30 | 31 | public Object instance = null; 32 | 33 | public static void testGC() { 34 | 35 | //step 1 36 | ReferenceCountingGC objA = new ReferenceCountingGC(); 37 | //step 2 38 | ReferenceCountingGC objB = new ReferenceCountingGC(); 39 | //相互引用 40 | //step 3 41 | objA.instance = objB; 42 | //step 4 43 | objB.instance = objA; 44 | 45 | //step 5 46 | objA = null; 47 | //step 6 48 | objB = null; 49 | 50 | //假设在这行发生CG,objA和objB是否能被回收? 不能!!!! 51 | System.gc(); 52 | } 53 | 54 | public static void main(String[] args) { 55 | testGC(); 56 | } 57 | } 58 | ``` 59 | ``` 60 | step1:objA的引用+1 =1 61 | step2:objB的引用+1 =1 62 | 63 | step3:objB的引用+1 =2 64 | step4:objA的引用+1 =2 65 | 66 | step5:objA的引用-1 =1 67 | step6:objB的引用-1 =1 68 | 69 | 永远都不会为0,GC永远都不会回收这些对象,导致内存泄漏 70 | ``` 71 | 72 | ## 2、对象存活算法可达性分析 73 | 74 | #### 2.1、算法 75 | 76 | 通过一系列的GC Roots的对象作为起始点,从这些根节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GCRoots没有任何引用链相连时,则证明此对象是不可用的。 77 | 78 | **当对象不可达时,并不是立即清除而是进行标记,标记为需要清除** 79 | 80 | #### 2.2、java中可作为GC Root的对象有 81 | - 虚拟机栈中引用的对象(本变量表) 82 | - 本地方法栈中引用的对象 83 | - 方法区中静态属性引用的对象 84 | - 方法区中常量引用的对象 85 | 86 | ![对象存活算法可达性](https://github.com/xujiangchen/Java-Study-Notes/blob/main/JVM/asset/%E5%AF%B9%E8%B1%A1%E5%AD%98%E6%B4%BB%E7%AE%97%E6%B3%95%E5%8F%AF%E8%BE%BE%E6%80%A7.jpg) 87 | 88 | ## 3、标记清除算法 89 | 90 | > 这个算法也基本不再使用了 91 | 92 | 最基础的收集算法是“标记-清除”(Mark-Sweep)算法,如同它的名字一样,算法分为“标记”和“清除”两个阶段: 93 | - 首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象,它的标记过程就是可达性分析中的标记。 94 | 95 | **问题** 96 | - 一个是效率问题,标记和清除两个过程的**效率都不高** 97 | - 另一个是空间问题,标记清除之后会**产生大量不连续的内存碎片**,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到能够的连续内存而不得不提前触发另一次垃圾收集动作。 98 | 99 | ![标记清除](https://github.com/xujiangchen/Java-Study-Notes/blob/main/JVM/asset/%E6%A0%87%E8%AE%B0%E6%B8%85%E9%99%A4.jpg) 100 | 101 | ## 4、复制算法 102 | 现在的商业虚拟机都采用这种收集算法来回收新生代,研究表明,新生代中的对象 98%是“朝生夕死”的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden和其中一块 Survivor。 Survivor from 和Survivor to,内存比例 8:1:1 103 | 104 | #### 4.1 具体步骤 105 | 106 | - 当Eden区满的时候,会触发第一次young gc,把还活着的对象拷贝到Survivor From区;当Eden区再次触发young 107 | gc的时候,会扫描Eden区和From区域,对两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域,并将Eden和From区域清空。 108 | - 当后续Eden又发生young gc的时候,会对Eden和To区域进行垃圾回收,存活的对象复制到From区域,并将Eden和To区域清空。 109 | - 可见部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代 110 | - 注意:万一存活对象数量比较多,那么To域的内存可能不够存放,这个时候会借助老年代的空间 111 | 112 | ![复制算法](https://github.com/xujiangchen/Java-Study-Notes/blob/main/JVM/asset/%E5%A4%8D%E5%88%B6%E7%AE%97%E6%B3%95.jpg) 113 | 114 | ## 5、标记整理算法 115 | > 该算法类似于标记清除算法,主要是用于老年代的GC回收 116 | 117 | 复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法 118 | 119 | 根据老年代的特点,有人提出了另外一种“标记-整理(Mark-Compact)算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向前端移动,然后直接清理掉端边界以外的内存。 120 | 121 | ![标记整理](https://github.com/xujiangchen/Java-Study-Notes/blob/main/JVM/asset/%E6%A0%87%E8%AE%B0%E6%95%B4%E7%90%86.jpg) 122 | 123 | 124 | ## 6、分代收集算法 125 | - 一般把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法 126 | - 在新生代中,每次垃圾收集时都发现有一批对象死去,只有少量存活,那就选择复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”或者“标记法整理”算法来进行回收 127 | -------------------------------------------------------------------------------- /JVM/对象.md: -------------------------------------------------------------------------------- 1 | - [1、Java对象创建底层顺序](#1-java--------) 2 | - [2、Java对象创建具体步骤](#2-java--------) 3 | + [2.1 步骤](#21---) 4 | - [中Java堆分配内存空间的方式主要有以下两种](#-java-----------------) 5 | + [2.2 可能会出现的问题](#22---------) 6 | + [2.3 初始化为零值](#23-------) 7 | + [2.4 对象相关数据](#24-------) 8 | - [3、Java 对象的内存布局](#3-java--------) 9 | + [3.1 对象头](#31----) 10 | - [3.1.1 Mark Word 部分](#311-mark-word---) 11 | - [3.1.2 类型指针 部分](#312--------) 12 | + [3.2 实例数据](#32-----) 13 | + [3.3 对其填充](#33-----) 14 | - [4、对象的访问](#4------) 15 | + [4.1 方式1:直接指针访问](#41---1-------) 16 | + [4.2 方式2:句柄访问](#42---2-----) 17 | + [4.3 两种访问方式的对比](#43----------) 18 | 19 | ## 1、Java对象创建底层顺序 20 | 21 | ![Java对象创建底层顺序](https://github.com/xujiangchen/Java-Study-Notes/blob/main/JVM/asset/Java%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA%E5%BA%95%E5%B1%82%E9%A1%BA%E5%BA%8F.jpg) 22 | 23 | ## 2、Java对象创建具体步骤 24 | 25 | #### 2.1 步骤 26 | 27 | - 虚拟机遇到一条new指令时,会先检查这个对应的类能否在常量池中定位到一个类的符号引用 28 | - 判断这个类是否已被加载、解析和初始化 29 | - 为这个新生对象在Java堆中分配内存空间 30 | - 将分配到的内存空间都初始化为零值 31 | - 设置对象相关数据 32 | - 执行对象方法 33 | 34 | **扩展知识** 35 | ##### 中Java堆分配内存空间的方式主要有以下两种 36 | 37 | > 指针碰撞:分配内存空间包括开辟一块内存和移动指针两个步骤 38 | 39 | > 空闲列表:分配内存空间包括开辟一块内存和修改空闲列表两个步骤 40 | 41 | ![Java堆分配内存空间](https://github.com/xujiangchen/Java-Study-Notes/blob/main/JVM/asset/Java%E5%A0%86%E5%88%86%E9%85%8D%E5%86%85%E5%AD%98%E7%A9%BA%E9%97%B4.jpg) 42 | 43 | #### 2.2 可能会出现的问题 44 | 45 | 指针碰撞 和 空闲列表 的操作可以看出分为两个步骤,那么就会出现原子性问题。Java虚拟机是如何解决这个问题的呢? 46 | > 例如:现在有多个线程都要进行分配对象,这时候就会出现指针的碰撞或者空闲列表的碰撞,这时候就出现了线程不安全的问题,Java虚拟机采用乐观锁的方式进行一个CAS[1]的判断,以失败重试的方式保证更新操作的原子性 47 | 48 | #### 2.3 初始化为零值 49 | 50 | 什么叫初始化话零值? 51 | 52 | 就是Java虚拟机对各种对象赋上初值;对于基础类型,类似于 int 的 初始化值 是0 ,boolean 的初始化值是false,而对于非基础类型就是 null 53 | 54 | #### 2.4 对象相关数据 55 | 56 | 包括: 57 | - GC分代年龄 58 | - 对象的hashCode 59 | - 元数据信息 60 | 61 | **备注** 62 | 63 | [1] : CAS是compareandswap的简称,从字面上理解就是比较并更新,简单来说:从某一内存上取值V,和预期值A进行比较,如果内存值V和预期值A的结果相等,那么我们就把新值B更新到内存,如果不相等,那么就重复上述操作直到成功为止。 64 | 65 | ## 3、Java 对象的内存布局 66 | 67 | ![Java对象结构](https://github.com/xujiangchen/Java-Study-Notes/blob/main/JVM/asset/Java%E5%AF%B9%E8%B1%A1%E7%BB%93%E6%9E%84.jpg) 68 | 69 | #### 3.1 对象头 70 | - 主要是用于存储对象的元数据信息 71 | - 分为两个部分 **Mark Word 部分** 和 **类型指针** 72 | 73 | ##### 3.1.1 Mark Word 部分 74 | 75 | 哈希值 -> 返回对象的内存地址经过处理后的结构 76 | 77 | - GC分代年龄 -> Java 虚拟机对垃圾回收时会分为新生代和老年代,然后根据不同的策略进行回收 78 | - 锁状态 -> 多线程访问和操作这个对象的时候,为了保证线程安全的概念 79 | - 线程持有锁 -> 存线程占有锁的指针 80 | 81 | ##### 3.1.2 类型指针 部分 82 | 83 | - 指向它的类元数据的指针,用于判断对象属于哪个类的实例。 84 | 85 | >Header长度在32位和64位虚拟机(未开启压缩指针)中分别为32bit和64bit,Mark Word 一般被设计为**非固定的数据结构**,以便存储更多的数据信息和复用自己的存储空间。 86 | 87 | #### 3.2 实例数据 88 | 89 | 存储的是真正有效数据,如各种字段内容,各字段的分配策略为longs/doubles、ints、shorts/chars、bytes/boolean、oops(ordinary object pointers),**相同宽度的字段总是被分配到一起,便于之后取数据**。父类定义的变量会出现在子类定义的变量的前面。 90 | 91 | #### 3.3 对其填充 92 | - 对其填充部分仅仅起到占位符的作用 93 | 94 | 通过Header的大小可以看出,32和64都是8的倍数,**java对象的存储也遵循这个规则以8的倍数**,Header的大小可以确定,但是实例数据的大小就难以确定,这时候就通过对其填充部分将期变为8的倍数 95 | 96 | ## 4、对象的访问 97 | - 访问对象的类型数据和实例数据 98 | 99 | #### 4.1 方式1:直接指针访问 100 | 即reference中存储的就是对象地址,相当于⼀级指针。 101 | - 对象实例数据(堆):对象中各个实例字段的数据 102 | - 对象类型数据(方法区):对象的类型、父类、实现的接口、方法等 103 | > 局部变量表存在栈中 104 | 105 | ![对象访问定位直接访问](https://github.com/xujiangchen/Java-Study-Notes/blob/main/JVM/asset/%E5%AF%B9%E8%B1%A1%E8%AE%BF%E9%97%AE%E5%AE%9A%E4%BD%8D%E7%9B%B4%E6%8E%A5%E8%AE%BF%E9%97%AE.jpg) 106 | 107 | #### 4.2 方式2:句柄访问 108 | 即reference中存储的是对象句柄的地址,⽽句柄中包含了对象实例数据 109 | 与类型数据的具体地址信息,相当于⼆级指针 110 | 111 | ![对象句柄访问](https://github.com/xujiangchen/Java-Study-Notes/blob/main/JVM/asset/%E5%AF%B9%E8%B1%A1%E5%8F%A5%E6%9F%84%E8%AE%BF%E9%97%AE.jpg) 112 | 113 | #### 4.3 两种访问方式的对比 114 | 115 | **访问效率** 116 | 117 | - 直接指针访问方式的最大好处是速度更快,它节省了一次时间定位的开销,由于对象访问在Java中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本,而这也**是HotSpot采用的实现方式** 118 | 119 | **垃圾回收** 120 | 121 | - 句柄访问方式最大好处就是reference存放的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改。 122 | 123 | -------------------------------------------------------------------------------- /Message Queue/Kafka.md: -------------------------------------------------------------------------------- 1 | - [Kafka](#kafka) 2 | - [一、了解Kafka](#一了解kafka) 3 | - [二、Kafka核心概念](#二kafka核心概念) 4 | - [三、Linux环境中安装Kafka](#三linux环境中安装kafka) 5 | - [3.1 安装Zookeeper](#31-安装zookeeper) 6 | - [3.2 安装Kafka](#32-安装kafka) 7 | - [四、Kafka点对点-发布订阅](#四kafka点对点-发布订阅) 8 | - [4.1 命令行进行生产和消费](#41-命令行进行生产和消费) 9 | - [4.2 Kafka消费者组配置实现点对点消费模型](#42-kafka消费者组配置实现点对点消费模型) 10 | - [4.3 Kafka消费者组配置实现发布订阅消费模型](#43-kafka消费者组配置实现发布订阅消费模型) 11 | - [4.5 Kafka数据存储流程和原理概述和LEO+HW讲解](#45-kafka数据存储流程和原理概述和leohw讲解) 12 | - [五、Springboot整合kafka](#五springboot整合kafka) 13 | - [5.1 Admin相关Api](#51-admin相关api) 14 | - [5.1.1 配置客户端](#511-配置客户端) 15 | - [5.1.2 创建Topic](#512-创建topic) 16 | - [5.1.3 列举Topic](#513-列举topic) 17 | - [5.1.4 删除Topic](#514-删除topic) 18 | - [5.1.5 查看Topic详情](#515-查看topic详情) 19 | - [5.1.6 增加分区数量](#516-增加分区数量) 20 | - [5.2 生产者相关Api](#52-生产者相关api) 21 | - [5.2.1 发送分区策略和常见配置](#521-发送分区策略和常见配置) 22 | - [5.2.2 封装配置属性](#522-封装配置属性) 23 | - [5.2.3 生产者投递消息(同步发送)](#523-生产者投递消息同步发送) 24 | - [5.2.4 回调函数](#524-回调函数) 25 | - [5.2.5 生产者发送指定分区](#525-生产者发送指定分区) 26 | - [5.2.6 生产者自定义partition分区规则](#526-生产者自定义partition分区规则) 27 | - [5.3 消费者相关Api](#53-消费者相关api) 28 | - [5.3.1 消费者机制和分区策略](#531-消费者机制和分区策略) 29 | - [5.3.2 消费者重新分配策略和offset维护机制](#532-消费者重新分配策略和offset维护机制) 30 | - [5.3.3 Kafka调试日志配置](#533-kafka调试日志配置) 31 | - [5.3.4 Consumer配置和消费订阅](#534-consumer配置和消费订阅) 32 | - [5.3.5 手工提交offset](#535-手工提交offset) 33 | - [六、kafka可靠性保证](#六kafka可靠性保证) 34 | - [6.1 副本Replica+ACK](#61-副本replicaack) 35 | - [6.2 in-sync-replica-set机制](#62-in-sync-replica-set机制) 36 | - [6.3 HighWatermark的作用](#63-highwatermark的作用) 37 | - [七、kafka高可用和高性能](#七kafka高可用和高性能) 38 | - [7.1 搭建kafka集群](#71-搭建kafka集群) 39 | - [7.2 Springboot连接kafka集群](#72-springboot连接kafka集群) 40 | - [7.3 日志数据清理](#73-日志数据清理) 41 | - [7.4 ZeroCopy](#74-zerocopy) 42 | 43 | 44 | # Kafka 45 | 46 | ## 一、了解Kafka 47 | kafka(open-source distributed event streaming platform),高吞吐量的分布式流处理平台 48 | 49 | Kafka严格意义上是不属于队列产品,是一个**流处理平台**,它提供了类似于JMS的特性,但是在设计实现上完全不同,它并不是JMS规范的实现。类似MQ,功能较为简单,**主要支持常规的MQ功能**,但是它的运维难度大,文档比较少, 需要掌握Scala。 50 | 51 | 它可以处理消费者在网站中的所有动作流数据。 52 | 53 | 官网:http://kafka.apache.org/ 54 | 55 | ## 二、Kafka核心概念 56 | 57 | **Broker:** 58 | - 可以类比于数据库 59 | - Kafka的服务端程序,可以认为**一个mq节点就是一个broker** 60 | - broker存储topic的数据 61 | 62 | **Producer生产者:** 63 | - 创建消息Message,然后发布到MQ中 64 | - 该角色将消息发布到Kafka的topic中 65 | 66 | **Consumer消费者:** 67 | - 消费队列里面的消息 68 | 69 | **ConsumerGroup消费者组** 70 | - 同个topic, 广播发送给不同的group,一个group中只有一个consumer可以消费此消息 71 | 72 | **Topic:** 73 | - 可以类比于数据库中的表 74 | - 每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic,主题的意思 75 | 76 | **Partition分区:** 77 | - kafka数据存储的基本单元,topic中的数据分割为一个或多个partition,**每个topic至少有一个partition,是有序的** 78 | - 一个Topic的多个partitions, 被分布在kafka集群中的多个server上 79 | - **消费者数量 <=小于或者等于Partition数量** 80 | 81 | **Replication 副本(备胎):** 82 | - 同个Partition会有多个副本replication ,多个副本的数据是一样的,当其他broker挂掉后,系统可以主动用副本提供服务 83 | - 默认每个topic的副本都是1(默认是没有副本,节省资源),也可以在创建topic的时候指定 84 | 85 | **ReplicationLeader、ReplicationFollower:** 86 | - 每一个Partition**有且只有一个Replica可以作为Leader** 87 | - 每个Partition中除了Leader以外的所有Replica均为follower 88 | - Partition有多个副本,但只有一个replicationLeader负责该Partition和生产者消费者交互 89 | - ReplicationFollower只是做一个备份,从replicationLeader进行同步 90 | 91 | **ReplicationManager:** 92 | - 负责Broker所有分区副本信息,Replication 副本状态切换 93 | 94 | **offset:** 95 | - 每个consumer实例需要为他消费的partition维护一个记录自己消费到哪里的偏移offset 96 | - kafka把offset保存在消费端的消费者组里 97 | 98 | ## 三、Linux环境中安装Kafka 99 | 100 | - kafka下载地址:http://kafka.apache.org/downloads 101 | - zookeeper下载格式:https://zookeeper.apache.org/releases.html 102 | 103 | ### 3.1 安装Zookeeper 104 | - 默认2181端口 105 | ``` 106 | # 解压 107 | tar -zxvf apache-zookeeper-3.7.0-bin.tar.gz 108 | 109 | # 对解压文件重命名,方便管理 110 | mv apache-zookeeper-3.7.0-bin zookeeper 111 | 112 | # 进入zookeeper配置文件夹 113 | cd zookeeper/conf/ 114 | 115 | # 复制默认配置文件进行自定义 116 | cp zoo_sample.cfg zoo.cfg 117 | 118 | # 启动zookeeper,回到上级,进入bin目录 119 | cd bin/ 120 | ./zkServer.sh start 121 | 122 | # 查看日志 123 | cd logs/ 124 | tail -300f zookeeper-root-server-izm5e55czz7lmt0ny9zu62z.out 125 | 126 | # 查看端口: 127 | yum install -y lsof 128 | lsof -i:2181 129 | ``` 130 | 131 | 132 | ### 3.2 安装Kafka 133 | - 默认端口9092 134 | ``` 135 | # 解压 136 | tar -zvxf kafka_2.13-2.8.0.tgz 137 | 138 | # 对解压文件重命名,方便管理 139 | mv kafka_2.13-2.8.0 kafka 140 | ``` 141 | - config目录下 server.properties 142 | ``` 143 | #标识broker编号,集群中有多个broker,则每个broker的编号(id)需要设置不同 144 | broker.id=0 145 | 146 | 147 | #修改下面两个配置 ( listeners 配置的ip和advertised.listeners相同时启动kafka会报错) 148 | 149 | listeners(内网Ip) => listeners=PLAINTEXT://内网Ip:9092 150 | advertised.listeners(公网ip) => advertised.listeners=PLAINTEXT://公网ip:9092 151 | 152 | 153 | #修改zk地址,默认地址(如果zk和kafka在一台机器上就不要修改) 154 | zookeeper.connection=localhost:2181 155 | ``` 156 | - bin目录启动 157 | ``` 158 | # 启动,并指定配置文件 159 | ./kafka-server-start.sh ../config/server.properties & 160 | 161 | # 停止kafka 162 | ./kafka-server-stop.sh 163 | ``` 164 | 165 | - 创建topic 166 | ``` 167 | ./kafka-topics.sh --create --zookeeper 外网ip:2181 --replication-factor 1 --partitions 1 --topic demo-topic 168 | 169 | # 创建成功后可以到,这个目录下载查看 170 | cd /tmp/kafka-logs/ 171 | ``` 172 | 173 | - 查看当前的topic 174 | ``` 175 | ./kafka-topics.sh --list --zookeeper 外网ip:2181 176 | ``` 177 | 178 | - 守护进程启动kafka 179 | ``` 180 | ./kafka-server-start.sh -daemon ../config/server.properties & 181 | ``` 182 | 183 | ## 四、Kafka点对点-发布订阅 184 | 185 | ### 4.1 命令行进行生产和消费 186 | - 创建topic 187 | ``` 188 | ./kafka-topics.sh --create --zookeeper 外网ip:2181 --replication-factor 1 --partitions 2 --topic version1-topic 189 | ``` 190 | 191 | - 查看topic 192 | ``` 193 | ./kafka-topics.sh --list --zookeeper 外网ip:2181 194 | ``` 195 | 196 | - 生产者发送消息 197 | ``` 198 | # --topic 后面为指定的topic,默认配置下如果没有,就会自动创建topic 199 | # 真实开发时建议将这个设置给关闭 200 | 201 | ./kafka-console-producer.sh --broker-list 外网ip:9092 --topic version1-topic 202 | ``` 203 | 204 | - 消费者消费消息 205 | ``` 206 | # --from-beginning:从头开始消费,并不会使用偏移量(上次消费到哪),会把主题中以往所有的数据都读取出来, 重启后会有这个重复消费 207 | 208 | ./kafka-console-consumer.sh --bootstrap-server 外网ip:9092 --from-beginning --topic version1-topic 209 | ``` 210 | 211 | - 删除topic 212 | topic 删除的时候,并不是立即删除,而是先改名字,检查相关配置,再进行真实删除 213 | ``` 214 | ./kafka-topics.sh --zookeeper 外网ip:2181 --delete --topic version1-topic 215 | ``` 216 | 217 | - 查看broker节点topic状态信息 218 | ``` 219 | ./kafka-topics.sh --describe --zookeeper 外网ip:2181 --topic version1-topic 220 | ``` 221 | 222 | ### 4.2 Kafka消费者组配置实现点对点消费模型 223 | 224 | **一个分区,只能被消费者组下的某一个消费者进行消费** 225 | 226 | **编辑消费者配置** 227 | 228 | 默认的消费者配置文件在,`config/consumer.properties` ,其中指定了相关的消费者配置项包括消费者组,**注意:不要认为默认启动消费者不指定配置文件时用的就时该文件**。在编辑该文件的时候,应该先拷贝一份,再进行修改 229 | 230 | - 指定配置文件开启消费者 231 | ``` 232 | ./kafka-console-consumer.sh --bootstrap-server 外网ip:9092 --from-beginning --topic version1-topic --consumer.config ../config/consumer.properties 233 | ``` 234 | ### 4.3 Kafka消费者组配置实现发布订阅消费模型 235 | **两个不同消费者组的节点,都可以消费到消息,实现发布订阅模型** 236 | - 创建两个消费者配置(确保group.id 不一样) 237 | - 创建topic, 2个分区 238 | ``` 239 | ./kafka-topics.sh --create --zookeeper 外网ip:2181 --replication-factor 1 --partitions 2 --topic t2 240 | ``` 241 | - 指定配置文件启动 两个消费者 242 | ``` 243 | ./kafka-console-consumer.sh --bootstrap-server 112.74.55.160:9092 --from-beginning --topic t1 --consumer.config ../config/consumer-1.properties 244 | ​ 245 | ./kafka-console-consumer.sh --bootstrap-server 112.74.55.160:9092 --from-beginning --topic t1 --consumer.config ../config/consumer-2.properties 246 | ``` 247 | 248 | ### 4.5 Kafka数据存储流程和原理概述和LEO+HW讲解 249 | 250 | - **Partition:** 251 | - topic物理上的分组,一个topic可以分为多个partition,每个partition是一个**有序的队列**。 252 | - 是以文件夹的形式存储在具体Broker本机上 253 | 254 | ![Partition](https://github.com/xujiangchen/Java-Study-Notes/blob/main/Message%20Queue/imgs/Partition.png) 255 | 256 | - **LEO(LogEndOffset)** 257 | - 表示每个partition的log最后一条Message的位置 258 | 259 | - **HW(HighWatermark)** 260 | - 表示partition各个replicas数据间同步且一致的offset位置,即表示allreplicas已经commit的位置 261 | - HW之前的数据才是Commit后的,对消费者才可见 262 | - ISR集合里面最小leo 263 | 264 | ![LEO AND HW](https://github.com/xujiangchen/Java-Study-Notes/blob/main/Message%20Queue/imgs/HW%E5%92%8CLEO.png) 265 | 266 | - **offset** 267 | - 每个partition都由一系列有序的、不可变的消息组成,这些消息被连续的追加到partition中 268 | - partition中的每个消息都有一个连续的序列号叫做offset,用于partition唯一标识一条消息 269 | - 可以认为offset是partition中Message的id 270 | 271 | - **Segment** 272 | - segment file 由2部分组成,分别为index file和data file(log file) 273 | - 两个文件是一一对应的,后缀”.index”和”.log”分别表示索引文件和数据文件 274 | - 命名规则:partition的第一个segment从0开始,后续每个segment文件名为上一个segment文件最后一条消息的offset+1 275 | 276 | ![整体架构](https://github.com/xujiangchen/Java-Study-Notes/blob/main/Message%20Queue/imgs/%E6%95%B4%E4%BD%93%E6%9E%B6%E6%9E%84.png) 277 | 278 | - **Kafka高效文件存储设计特点:** 279 | - Kafka把topic中一个parition大文件分成多个小文件段,通过多个小文件段,就容易定期清除或删除已经消费完文件,减少磁盘占用。 280 | - 通过索引信息可以快速定位message 281 | - producer生产数据,要写入到log文件中,写的过程中一直追加到文件末尾,为顺序写,官网数据表明。同样的磁盘,顺序写能到600M/S,而随机写只有100K/S 282 | 283 | 284 | ## 五、Springboot整合kafka 285 | 286 | ### 5.1 Admin相关Api 287 | 288 | #### 5.1.1 配置客户端 289 | ```java 290 | public static AdminClient initAdminClient(){ 291 | Properties properties = new Properties(); 292 | // 设置连接地址 293 | properties.setProperty(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); 294 | AdminClient adminClient = AdminClient.create(properties); 295 | return adminClient; 296 | } 297 | ``` 298 | 299 | #### 5.1.2 创建Topic 300 | ```java 301 | private static final String TOPIC_NAME = "demo-topic"; 302 | 303 | public void createTopic(){ 304 | AdminClient adminClient = initAdminClient(); 305 | 306 | // 指定分区数量,副本数量 307 | NewTopic newTopic = new NewTopic(TOPIC_NAME, 2, (short)1); 308 | CreateTopicsResult createTopicsResult = adminClient.createTopics(Arrays.asList(newTopic)); 309 | 310 | try { 311 | //future等待创建,成功不会有任何报错,如果创建失败和超时会报错。 312 | createTopicsResult.all().get(); 313 | } catch (InterruptedException e) { 314 | e.printStackTrace(); 315 | } catch (ExecutionException e) { 316 | e.printStackTrace(); 317 | } 318 | } 319 | ``` 320 | #### 5.1.3 列举Topic 321 | ```java 322 | public void listTopic(){ 323 | AdminClient adminClient = initAdminClient(); 324 | // 不带参数返回的是用户自己创建的相关Topic 325 | ListTopicsResult listTopicsResult = adminClient.listTopics(); 326 | try { 327 | Set topicSet = listTopicsResult.names().get(); 328 | for (String item:topicSet) { 329 | System.err.println(item); 330 | } 331 | } catch (InterruptedException e) { 332 | e.printStackTrace(); 333 | } catch (ExecutionException e) { 334 | e.printStackTrace(); 335 | } 336 | 337 | // 如果想带参数进行查询 338 | ListTopicsOptions listTopicsOptions = new ListTopicsOptions(); 339 | // 展示kafka内部创建的topic 340 | listTopicsOptions.listInternal(true); 341 | ListTopicsResult listTopicsResult2 = adminClient.listTopics(listTopicsOptions); 342 | try { 343 | Set topicSet = listTopicsResult2.names().get(); 344 | for (String item:topicSet) { 345 | System.err.println(item); 346 | } 347 | } catch (InterruptedException e) { 348 | e.printStackTrace(); 349 | } catch (ExecutionException e) { 350 | e.printStackTrace(); 351 | } 352 | } 353 | ``` 354 | 355 | #### 5.1.4 删除Topic 356 | ```java 357 | public void delTopic(){ 358 | AdminClient adminClient = initAdminClient(); 359 | DeleteTopicsResult deleteTopicsResult = adminClient.deleteTopics(Arrays.asList("xudemo-topic")); 360 | try { 361 | deleteTopicsResult.all().get(); 362 | } catch (InterruptedException e) { 363 | e.printStackTrace(); 364 | } catch (ExecutionException e) { 365 | e.printStackTrace(); 366 | } 367 | } 368 | ``` 369 | #### 5.1.5 查看Topic详情 370 | ```java 371 | public void detailTopic(){ 372 | AdminClient adminClient = initAdminClient(); 373 | DescribeTopicsResult describeTopicsResult = adminClient.describeTopics(Arrays.asList(TOPIC_NAME)); 374 | try { 375 | Map stringTopicDescriptionMap = describeTopicsResult.all().get(); 376 | Set> entries = stringTopicDescriptionMap.entrySet(); 377 | entries.stream().forEach((entry)-> System.out.println("name:" + entry.getKey() + " value:" + entry.getValue())); 378 | } catch (InterruptedException e) { 379 | e.printStackTrace(); 380 | } catch (ExecutionException e) { 381 | e.printStackTrace(); 382 | } 383 | } 384 | ``` 385 | 386 | #### 5.1.6 增加分区数量 387 | - Kafka中的分区数只能增加不能减少,减少的话数据不知怎么处理 388 | ```java 389 | public void incrPartitions(){ 390 | Map infoMap = new HashMap<>(); 391 | // 分区增加到5个 392 | NewPartitions newPartitions = NewPartitions.increaseTo(5); 393 | AdminClient adminClient = initAdminClient(); 394 | infoMap.put(TOPIC_NAME, newPartitions); 395 | // 创建分区 396 | ​ CreatePartitionsResult createPartitionsResult = adminClient.createPartitions(infoMap); 397 | try { 398 | createPartitionsResult.all().get(); 399 | } catch (InterruptedException e) { 400 | e.printStackTrace(); 401 | } catch (ExecutionException e) { 402 | e.printStackTrace(); 403 | } 404 | } 405 | ``` 406 | 407 | ### 5.2 生产者相关Api 408 | 409 | #### 5.2.1 发送分区策略和常见配置 410 | - 如果指定Partition ID,则PR(ProducerRecord)被发送至指定Partition 411 | - 如果未指定Partition ID,但发送方的消息中指定了Key, PR会按照hash(key)发送至对应Partition 412 | - 如果未指定Partition ID,也没指定Key,PR会按照默认 round-robin轮训模式发送到每个Partition 413 | - 如果同时指定了Partition ID和Key, PR只会发送到指定的Partition 414 | 415 | ![Partition](https://github.com/xujiangchen/Java-Study-Notes/blob/main/Message%20Queue/imgs/sendModel.png) 416 | **生产者常见配置** 417 | - 官方文档 http://kafka.apache.org/documentation/#producerconfigs 418 | 419 | 420 | #### 5.2.2 封装配置属性 421 | ```java 422 | public static Properties getProperties(){ 423 | 424 | Properties props = new Properties(); 425 | 426 | props.put("bootstrap.servers", "118.190.132.65:9092"); 427 | 428 | // 当producer向leader发送数据时,可以通过request.required.acks参数来设置数据可靠性的级别,分别是0, 1,all。 429 | props.put("acks", "all"); 430 | 431 | 432 | // 请求失败,生产者会自动重试,指定是0次,如果启用重试,则会有重复消息的可能性 433 | props.put("retries", 0); 434 | 435 | // 生产者缓存每个分区未发送的消息,缓存的大小是通过 batch.size 配置指定的,默认值是16KB 436 | props.put("batch.size", 16384); 437 | 438 | /** 439 | * 默认值就是0,消息是立刻发送的,即便batch.size缓冲空间还没有满 440 | * 如果想减少请求的数量,可以设置 linger.ms 大于0,即消息在缓冲区保留的时间,超过设置的值就会被提交到服务端 441 | * 通俗解释是,本该早就发出去的消息被迫至少等待了linger.ms时间,相对于这时间内积累了更多消息,批量发送减少请求 442 | * 如果batch被填满或者linger.ms达到上限,满足其中一个就会被发送 443 | */ 444 | props.put("linger.ms", 1); 445 | 446 | /** 447 | * buffer.memory的用来约束Kafka Producer能够使用的内存缓冲的大小的,默认值32MB。 448 | * 如果buffer.memory设置的太小,可能导致消息快速的写入内存缓冲里,但Sender线程来不及把消息发送到Kafka服务器 449 | * 会造成内存缓冲很快就被写满,而一旦被写满,就会阻塞用户线程,不让继续往Kafka写消息了 450 | * buffer.memory要大于batch.size,否则会报申请内存不#足的错误,不要超过物理内存,根据实际情况调整 451 | * 需要结合实际业务情况压测进行配置 452 | */ 453 | props.put("buffer.memory", 33554432); 454 | 455 | /** 456 | * key的序列化器,将用户提供的 key和value对象ProducerRecord 进行序列化处理,key.serializer必须被设置, 457 | * 即使消息中没有指定key,序列化器必须是一个实 458 | org.apache.kafka.common.serialization.Serializer接口的类, 459 | * 将key序列化成字节数组。 460 | */ 461 | props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); 462 | props.put("value.serializer","org.apache.kafka.common.serialization.StringSerializer"); 463 | return props; 464 | } 465 | ``` 466 | #### 5.2.3 生产者投递消息(同步发送) 467 | ```java 468 | /** 469 | * send()方法是异步的,添加消息到缓冲区等待发送,并立即返回 470 | * 生产者将单个的消息批量在一起发送来提高效率,即 batch.size和linger.ms结合 471 | * 472 | * 实现同步发送:一条消息发送之后,会阻塞当前线程,直至返回 ack 473 | * 发送消息后返回的一个 Future 对象,调用get即可 474 | * 475 | * 消息发送主要是两个线程:一个是Main用户主线程,一个是Sender线程 476 | * 1)main线程发送消息到RecordAccumulator即返回 477 | * 2)sender线程从RecordAccumulator拉取信息发送到broker 478 | * 3) batch.size和linger.ms两个参数可以影响 sender 线程发送次数 479 | */ 480 | public void sender(){ 481 | Properties properties = getProperties(); 482 | Producer producer = new KafkaProducer(properties); 483 | for (int i = 0; i < 3 ; i++) { 484 | Future future = producer.send(new ProducerRecord<>("demo-topic", "demo-key" + i, "demo-value" + i)); 485 | try { 486 | // 如果不关心结果,以下代码无用 487 | // 同样这段代码实现了同步发送的功能 488 | RecordMetadata recordMetadata = future.get(); 489 | System.out.println("发送状态码:" + recordMetadata.toString()); 490 | } catch (InterruptedException e) { 491 | e.printStackTrace(); 492 | } catch (ExecutionException e) { 493 | e.printStackTrace(); 494 | } 495 | } 496 | producer.close(); 497 | } 498 | ``` 499 | #### 5.2.4 回调函数 500 | - 生产者发送消息是异步调用,怎么知道是否有异常?发送消息配置回调函数即可 501 | ```java 502 | public void sendWithCallback(){ 503 | Properties props = getProperties(); 504 | Producer producer = new KafkaProducer(props); 505 | for (int i = 0; i < 3 ; i++) { 506 | producer.send(new ProducerRecord<>("demo-topic", "demo-key" + i, "demo-value" + i), new Callback() { 507 | @Override 508 | public void onCompletion(RecordMetadata recordMetadata, Exception e) { 509 | if (e == null){ 510 | System.err.println("发送状态:"+recordMetadata.toString()); 511 | }else { 512 | e.printStackTrace(); 513 | } 514 | } 515 | }); 516 | } 517 | producer.close(); 518 | } 519 | ``` 520 | 521 | #### 5.2.5 生产者发送指定分区 522 | ```java 523 | public void sendAppointPartition(){ 524 | Properties properties = getProperties(); 525 | Producer producer = new KafkaProducer(properties); 526 | for (int i = 0; i < 3 ; i++) { 527 | // 在发送时第二个参数指定分区编号,分区编号从0开始 528 | // 如果发送到一个不存在的分区编号,则会报错 529 | producer.send(new ProducerRecord<>("demo-topic", 2, "demo-key" + i, "demo-value" + i), new Callback() { 530 | @Override 531 | public void onCompletion(RecordMetadata recordMetadata, Exception e) { 532 | if (e == null){ 533 | System.err.println("发送状态:"+recordMetadata.toString()); 534 | }else { 535 | e.printStackTrace(); 536 | } 537 | } 538 | }); 539 | } 540 | } 541 | ``` 542 | 543 | #### 5.2.6 生产者自定义partition分区规则 544 | - 创建类,实现Partitioner接口,重写方法 545 | ```java 546 | public class demoPartitioner implements Partitioner { 547 | 548 | @Override 549 | public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) { 550 | // 如果key为空则给出异常消息 551 | if (keyBytes == null){ 552 | throw new IllegalArgumentException("Key 参数不能为空"); 553 | } 554 | 555 | // 如果key等于demo,则放到分区编号为0的分区里面 556 | if ("demo".equals(key)){ 557 | return 0; 558 | } 559 | 560 | // 否则根据默认hash取模对消息进行分布 561 | List partitions = cluster.partitionsForTopic(topic); 562 | int numPartitions = partitions.size(); 563 | return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions; 564 | } 565 | 566 | @Override 567 | public void close() { 568 | 569 | } 570 | 571 | @Override 572 | public void configure(Map map) { 573 | 574 | } 575 | } 576 | ``` 577 | - 使用自定义的分区策略 578 | ```java 579 | public void sendAppointPartition(){ 580 | Properties properties = getProperties(); 581 | // 指定分区策略 582 | props.put("partitioner.class", "com.blaster.kafkademo.config.demoPartitioner"); 583 | Producer producer = new KafkaProducer(properties); 584 | for (int i = 0; i < 3 ; i++) { 585 | producer.send(new ProducerRecord<>("demo-topic", "demo-key" + i, "demo-value" + i), new Callback() { 586 | @Override 587 | public void onCompletion(RecordMetadata recordMetadata, Exception e) { 588 | if (e == null){ 589 | System.err.println("发送状态:"+recordMetadata.toString()); 590 | }else { 591 | e.printStackTrace(); 592 | } 593 | } 594 | }); 595 | } 596 | } 597 | ``` 598 | 599 | ### 5.3 消费者相关Api 600 | 601 | #### 5.3.1 消费者机制和分区策略 602 | - **round-robin (RoundRobinAssignor非默认策略)轮训** 603 | 604 | 原理是将消费组内所有消费者以及消费者所订阅的所有topic的partition按照字典序排序,然后通过轮询方式逐个将分区以此分配给每个消费者。如果同一个消费组内所有的消费者的订阅信息都是相同的,那么RoundRobinAssignor策略的分区分配会是均匀的。 605 | 606 | ![Partition](https://github.com/xujiangchen/Java-Study-Notes/blob/main/Message%20Queue/imgs/RoundRobinAssignor.png) 607 | 608 | 它存在的弊端 609 | 610 | ![Partition](https://github.com/xujiangchen/Java-Study-Notes/blob/main/Message%20Queue/imgs/RoundRobinAssignor2.png) 611 | 612 | 613 | - **range (RangeAssignor默认策略)范围** 614 | 615 | 按照主进行分配,如果不平均分配,则第一个消费者会分配比较多分区, 一个消费者监听不同主题也不影响。 616 | 617 | ![Partition](https://github.com/xujiangchen/Java-Study-Notes/blob/main/Message%20Queue/imgs/RangeAssignor.png) 618 | 619 | 它存在的弊端: 620 | 621 | 如果有 N 多个 topic,那么针对每个 topic,消费者 C-1 都将多消费 1 个分区,topic越多则消费的分区也越多,则性能有所下降 622 | 623 | #### 5.3.2 消费者重新分配策略和offset维护机制 624 | 625 | - **什么是Rebalance操作:** 626 | - kafka 怎么均匀地分配某个 topic 下的所有 partition 到各个消费者,从而使得消息的消费速度达到最快,这就是平衡(balance),前面讲了 Range 范围分区 和 RoundRobin 轮询分区,也支持自定义分区策略。 627 | - 而 rebalance(重平衡)其实就是重新进行 partition 的分配,从而使得 partition 的分配重新达到平衡状态 628 | 629 | > 例如70个分区,10个消费者,但是先启动一个消费者,后续再启动一个消费者,这个会怎么分配? 630 | > 631 | > Kafka 会进行一次分区分配操作,即 Kafka 消费者端的 Rebalance 操作 ,下面都会发生rebalance操作: 632 | > 633 | > 1.当消费者组内的消费者数量发生变化(增加或者减少),就会产生重新分配patition 634 | > 635 | > 2.分区数量发生变化时(即 topic 的分区数量发生变化时) 636 | > 637 | 638 | - **当消费者在消费过程突然宕机了,重新恢复后是从哪里消费,会有什么问题?** 639 | - 消费者会记录offset,故障恢复后从这里继续消费 640 | - 记录在zk里面和本地,新版默认将offset保证在kafka的内置topic中,名称是 __consumer_offsets 641 | - 由 消费者组名+主题+分区,确定唯一的offset的key,从而获取对应的值 642 | - 三元组:group.id+topic+分区号,而 value 就是 offset 的值 643 | 644 | 645 | #### 5.3.3 Kafka调试日志配置 646 | ```yml 647 | logging: 648 | config: classpath:logback.xml 649 | ``` 650 | ```xml 651 | 652 | 653 | 654 | 655 | %d{yyyy-MM-dd HH:mm:ss.SSS}[%thread] %-5level %logger{50} - %msg%n 656 | 657 | 658 | ​ 659 | 660 | 661 | 662 | 663 | ``` 664 | 665 | #### 5.3.4 Consumer配置和消费订阅 666 | - **配置信息:** 667 | ```java 668 | public static Properties getProperties() { 669 | Properties props = new Properties(); 670 | ​ 671 | //broker地址 672 | props.put("bootstrap.servers", "ip:9092"); 673 | ​ 674 | //消费者分组ID,分组内的消费者只能消费该消息一次,不同分组内的消费者可以重复消费该消息 675 | props.put("group.id", ""); 676 | ​ 677 | //开启自动提交offset 678 | props.put("enable.auto.commit", "true"); 679 | ​ 680 | //自动提交offset延迟时间 681 | props.put("auto.commit.interval.ms", "1000"); 682 | ​ 683 | //反序列化 684 | props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); 685 | props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); 686 | ​ 687 | return props; 688 | } 689 | ``` 690 | - **消费者订阅:** 691 | ```java 692 | public void simpleConsumer(){ 693 | // 获取配置信息 694 | Properties properties = getProperties(); 695 | // 创建消费者 696 | KafkaConsumer kafkaConsumer = new KafkaConsumer(properties); 697 | // 订阅他topic 698 | kafkaConsumer.subscribe(Arrays.asList("demo-topic")); 699 | while (true){ 700 | //拉取时间控制,阻塞超时时间 701 | ConsumerRecords poll = kafkaConsumer.poll(Duration.ofMillis(100)); 702 | for (ConsumerRecord record : poll){ 703 | System.err.printf("topic = %s, offset = %d, key = %s, value = %s%n",record.topic(), record.offset(), record.key(), record.value()); 704 | } 705 | } 706 | } 707 | ``` 708 | - **重新从头消费:** 709 | - auto.offset.reset 配置策略即可 `props.put("auto.offset.reset","earliest")` 710 | - 消费者组名变更 711 | 712 | #### 5.3.5 手工提交offset 713 | 714 | - 自动提交offset问题: 715 | ``` 716 | 在实际使用中,kafka只是一个中间件,后面可能关联各种各样的服务,消息被这些服务消费之后,进行后续操作时,例如保存数据库,如果失败了,自动提交offset就会出现问题。这个消息我们并没用成功处理,我们不应该让kafka确认消费。 717 | 718 | 适合不严谨的场景,比如日志收集发送 719 | ``` 720 | 721 | - 手工提交offset 722 | - 同步 commitSync 阻塞当前线程 723 | - 优点:失败重试,(内部机制,不保证100%) 724 | - 缺点:同步请求,会阻塞线程,使用较少 725 | - 异步 commitAsync 不会阻塞当前线程 726 | - 优点:不会阻塞当前线程,回调callback函数获取提交信息,记录日志 727 | - 缺点:没有失败重试 728 | 729 | 730 | **更改配置:**: 731 | ```java 732 | properties.put("enable.auto.commit", "false"); 733 | ``` 734 | 735 | **测试代码:** 736 | ```java 737 | public void simpleConsumer(){ 738 | Properties properties = getProperties(); 739 | KafkaConsumer kafkaConsumer = new KafkaConsumer(properties); 740 | kafkaConsumer.subscribe(Arrays.asList("demo-topic")); 741 | while (true){ 742 | ConsumerRecords poll = kafkaConsumer.poll(Duration.ofMillis(100)); 743 | for (ConsumerRecord record : poll){ 744 | System.err.printf("topic = %s, offset = %d, key = %s, value = %s%n",record.topic(), record.offset(), record.key(), record.value()); 745 | } 746 | // 缺点:同步请求,会阻塞线程,使用较少 747 | // 优点:失败重试,(内部机制,不保证100%) 748 | // kafkaConsumer.commitSync(); 749 | 750 | if (!poll.isEmpty()){ 751 | // 异步 752 | kafkaConsumer.commitAsync((map, e) -> { 753 | if (e == null){ 754 | System.out.println("提交成功!" + map.toString()); 755 | }else { 756 | e.printStackTrace(); 757 | } 758 | }); 759 | } 760 | } 761 | } 762 | ``` 763 | 764 | ## 六、kafka可靠性保证 765 | 766 | ### 6.1 副本Replica+ACK 767 | 768 | - 生产者发送数据流程: 769 | - 1、producer 发送到指定的 topic 770 | - 2、topic 发送 ack 确认收到 771 | - 3、producer 收到 ack 进行下一轮的发送,否则重新发送数据 772 | 773 | - 副本数据同步机制: 774 | - 当producer在向partition中写数据时,根据ack机制,默认ack=1,只会向leader中写入数据 775 | - 然后leader中的数据会复制到其他的replica中,follower会周期性的从leader中pull数据 776 | - 对于数据的读写操作都在leader replica中,follower副本只是当leader副本挂了后才重新选取leader 777 | 778 | **问题:** follower并不向外提供服务,假如还没同步完成,leader副本就宕机了,怎么办? 779 | ``` 780 | 首先,kafka通过LEO+HW 保证了消费者可见数据的同一性。其次,kafka对这个ack返回机制,提供了多种模式。 781 | ``` 782 | **要追求高吞吐量,那么就要放弃可靠性,两者不可兼得。** 783 | 784 | - ack=0 785 | - producer发送一次就不再发送了,不管是否发送成功 786 | - 发送出去的消息还在半路,或者还没写入磁盘, Partition Leader所在Broker就直接挂了,客户端认为消息发送成功了,此时就会导致这条消息就丢失 787 | 788 | - ack=1(默认) 789 | - 只要Partition Leader接收到消息而且写入【本地磁盘】,就认为成功了,不管他其他的Follower有没有同步过去这条消息了 790 | - 万一Partition Leader刚刚接收到消息,Follower还没来得及同步过去,结果Leader所在的broker宕机了 791 | 792 | - ack= all(即-1) 793 | - producer只有收到分区内所有副本的成功写入全部落盘的通知才认为推送消息成功 794 | ``` 795 | 1、如果出现一个follower一直同步不成功,难道一直不返回ack吗? 796 | 797 | Leader会维护一个ISR(副本集合,包括leader自身),里面会记录所有的副本,当kafka任务某一个副本失去连接的时候,会在ISR中将该副本提出,ISR是动态变化的。 798 | 799 | 2、acks=all 就可以代表数据一定不会丢失了吗? 800 | 801 | - Partition只有一个副本,也就是一个Leader,任何Follower都没有 802 | - 接收完消息后宕机,也会导致数据丢失,acks=all,必须跟ISR列表里至少有2个以上的副本配合使用 803 | - 在设置request.required.acks=-1的同时,也要min.insync.replicas这个参数设定 ISR中的最小副本数是多少,默认值为1,改为 >=2,如果ISR中的副本数少于min.insync.replicas配置的数量时,客户端会返回异常 804 | 805 | 3、ack= all还有可能会导致数据重复 806 | 807 | 数据发送到leader后 ,部分ISR的副本同步,leader此时挂掉,ack还没有返回。producer端会得到返回异常,producer端会重新发送数据,数据可能会重复。 808 | ``` 809 | 810 | ### 6.2 in-sync-replica-set机制 811 | - 什么是ISR (in-sync replica set ) 812 | - leader会维持一个与其保持同步的replica集合,该集合就是ISR,**每一个leader partition都有一个ISR,leader动态维护**, 要保证kafka不丢失message,就要保证ISR这组集合存活(至少有一个存活),并且消息commit成功 813 | - Partition leader 保持同步的 Partition Follower 集合, 当 ISR 中的Partition Follower 完成数据的同步之后,就会给 leader 发送 ack 814 | - 如果Partition follower长时间(**replica.lag.time.max.ms**) 未向leader同步数据,则该Partition Follower将被踢出ISR 815 | - Partition Leader 发生故障之后,就**会从 ISR 中选举新的 Partition Leader**。 816 | 817 | - OSR (out-of-sync-replica set) 818 | - 与leader副本分区 同步滞后过多的副本集合 819 | 820 | - AR(Assign Replicas) 821 | - 分区中所有副本统称为AR, AR = ISR + OSR 822 | 823 | ### 6.3 HighWatermark的作用 824 | 保证消费数据的一致性和副本数据的一致性 825 | ``` 826 | 假设没有HW,消费者消费leader到15,下面消费者应该消费16。 827 | ​ 828 | 此时leader挂掉,选下面某个follower为leader,此时消费者找新leader消费数据,发现新Leader没有16数据,报错。 829 | ​ 830 | HW(High Watermark)是所有副本中最小的LEO。 831 | ``` 832 | 833 | - **Follower故障** 834 | - Follower发生故障后会被临时踢出ISR(动态变化),待该follower恢复后,follower会读取本地的磁盘记录的上次的HW,并将该log文件高于HW的部分截取掉,从HW开始向leader进行同步,等该follower的LEO大于等于该Partition的hw,即follower追上leader后,就可以重新加入ISR 835 | 836 | - **Leader故障** 837 | - Leader发生故障后,会从ISR中选出一个新的leader,为了保证多个副本之间的数据一致性,其余的follower会先将各自的log文件高于hw的部分截掉(新leader自己不会截掉),然后从新的leader同步数据 838 | 839 | ## 七、kafka高可用和高性能 840 | 841 | ### 7.1 搭建kafka集群 842 | ``` 843 | # 解压 844 | tar -zvxf kafka_2.13-2.8.0.tgz 845 | 846 | # 对解压文件重命名,方便管理 847 | mv kafka_2.13-2.8.0 kafka 848 | 849 | # 编辑配置文件 850 | 1. broker.id # 节点id,配置不同的id 851 | 2. listeners # 局域网IP,内网部署 kafka 集群只需要用到 listeners 852 | 3. advertised.listeners # 外网IP,内外网需要作区分时 才需要用到advertised.listeners 853 | 4. port # 添加端口属性,指定端口,每个节点都不一样 854 | 5. log.dirs # 日志存储位置 855 | 6. zookeeper.connect # zk地址,集群的话用逗号分隔 856 | 857 | # 启动kafka 858 | ./kafka-server-start.sh -daemon ../config/server.properties & # 守护进程启动 859 | ​ 860 | ./kafka-server-start.sh ../config/server.properties & # 非守护进程启动 861 | ``` 862 | ### 7.2 Springboot连接kafka集群 863 | ```java 864 | public class Admin { 865 | 866 | private static final String TOPIC_NAME = "demo-cluster-topic"; 867 | 868 | 869 | public static AdminClient initAdminClient(){ 870 | Properties properties = new Properties(); 871 | properties.setProperty(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "118.191.132.65:9092,118.191.132.65:9093,118.191.132.65:9094"); 872 | AdminClient adminClient = AdminClient.create(properties); 873 | return adminClient; 874 | } 875 | 876 | /** 877 | * 创建topic 878 | */ 879 | @Test 880 | public void creatTopic(){ 881 | AdminClient adminClient = initAdminClient(); 882 | NewTopic newTopic = new NewTopic(TOPIC_NAME, 6, (short) 3); 883 | CreateTopicsResult topics = adminClient.createTopics(Arrays.asList(newTopic)); 884 | try { 885 | topics.all().get(); 886 | } catch (Exception e) { 887 | e.printStackTrace(); 888 | } 889 | } 890 | 891 | /** 892 | * 查看topic 893 | */ 894 | @Test 895 | public void listTopic(){ 896 | AdminClient adminClient = initAdminClient(); 897 | ListTopicsResult listTopicsResult = adminClient.listTopics(); 898 | 899 | try { 900 | Set strings = listTopicsResult.names().get(); 901 | for (String item : strings){ 902 | System.err.println(item); 903 | } 904 | } catch (Exception e) { 905 | e.printStackTrace(); 906 | } 907 | 908 | } 909 | 910 | } 911 | 912 | ``` 913 | 914 | ### 7.3 日志数据清理 915 | 916 | Kafka将数据持久化到了硬盘上,为了控制磁盘容量,需要对过去的消息进行清理。kafka并不是为了做存储使用的。 917 | 918 | - kafka的默认配置: 919 | - 内部有个定时任务检测删除日志,默认是5分钟 `log.retention.check.interval.ms` 920 | - 支持配置策略对数据清理 921 | - 根据segment单位进行定期清理 922 | 923 | 924 | - 启用cleaner 925 | - log.cleaner.enable=true 926 | - log.cleaner.threads = 2 (清理线程数配置) 927 | 928 | **在kafka中,清理日志给出了两大类的策略:日志删除 和 日志压缩** 929 | - 日志删除 `log.cleanup.policy=delete` 930 | ``` 931 | #清理超过指定时间的消息,默认是168小时,7天, 932 | #还有log.retention.ms, log.retention.minutes, log.retention.hours,优先级高到低 933 | log.retention.hours=168 934 | ​ 935 | #超过指定大小后,删除旧的消息,下面是1G的字节数,-1就是没限制 936 | log.retention.bytes=1073741824 937 | ​ 938 | 还有基于日志起始位移(log start offset),未来社区还有更多 939 | 940 | 941 | 问题一:配置了7天后删除,那7天如何确定呢? 942 | ​ 943 | 每个日志段文件都维护一个最大时间戳字段,每次日志段写入新的消息时,都会更新该字段,一个日志段segment写满了被切分之后,就不再接收任何新的消息,最大时间戳字段的值也将保持不变,kafka通过将当前时间与该最大时间戳字段进行比较,从而来判定是否过期 944 | 945 | log.retention.bytes和log.retention.minutes任意一个达到要求,都会执行删除 946 | ``` 947 | - 日志压缩 `log.cleanup.policy=compact` 948 | ``` 949 | 按照消息key进行整理,有相同key不同value值,只保留最后一个 950 | ``` 951 | 952 | ### 7.4 ZeroCopy 953 | Linux有两个上下文,内核态,用户态。 954 | 955 | - 原始的文件发送: 956 | - 调用read,将文件拷贝到了kernel内核态 957 | - CPU控制 kernel态的数据copy到用户态 958 | - 调用write时,user态下的内容会copy到内核态的socket的buffer中 959 | - 最后将内核态socket buffer的数据copy到网卡设备中传送 960 | 961 | - zero拷贝 962 | - 请求kernel直接把disk的data传输给socket,而不是通过应用程序传输。Zero copy大大提高了应用程序的性能,减少不必要的内核缓冲区跟用户缓冲区间的拷贝,从而减少CPU的开销和减少了kernel和user模式的上下文切换,达到性能的提升 -------------------------------------------------------------------------------- /Message Queue/README.md: -------------------------------------------------------------------------------- 1 | - [一、MQ消息中间件和应用场景](#一MQ消息中间件和应用场景) 2 | - [二、JMS消息服务](#二JMS消息服务) 3 | - [三、AMQP高级消息队列协议和MQTT](三AMQP高级消息队列协议和MQTT) 4 | - [3.1 AMQP](#31-AMQP) 5 | - [3.2 MQTT](#32-MQTT) 6 | 7 | # 消息队列 8 | 9 | ## 一、MQ消息中间件和应用场景 10 | 11 | **什么是MQ消息中间:** 12 | - 全称MessageQueue,主要是用于程序和程序直接通信,异步+解耦 13 | 14 | **使用场景:** 15 | - 解耦:订单系统->物流系统 16 | - 异步:用户注册->发送邮件,初始化信息 17 | - 削峰:秒杀、日志处理 18 | - 跨平台 、多语言 19 | - 分布式事务、最终一致性 20 | - RPC调用上下游对接,数据源变动->通知下属 21 | 22 | 23 | ## 二、JMS消息服务 24 | 25 | Java消息服务(Java Message Service),Java平台中关于面向消息中间件的接口。 26 | - JMS是一种与厂商无关的 API,用来访问消息收发系统消息,它类似于JDBC 27 | - 是由Sun公司早期提出的消息标准,旨在为java应用提供统一的消息操作,包括create、send、receive 28 | - JMS是针对java的,那微软开发了NMS(.NET消息传递服务) 29 | 30 | **特性:** 31 | - 面向Java平台的标准消息传递API 32 | - 在Java或JVM语言比如Scala、Groovy中具有互用性 33 | - 无需担心底层协议 34 | - 有queues和topics两种消息传递模型 35 | - 支持事务、能够定义消息格式(消息头、属性和内容) 36 | - JMS消息通常有两种类型:点对点(Point-to-Point)、发布/订阅(Publish/Subscribe) 37 | 38 | ## 三、AMQP高级消息队列协议和MQTT 39 | 40 | ### 3.1 AMQP 41 | 42 | **背景:** 43 | JMS或者NMS都**没有标准的底层协议,API是与编程语言绑定的,每个消息队列厂商就存在多种不同格式规范的产品**,对使用者就产生了很多问题,如果有一天公司使用的消息队列供应商倒闭了了,或者产品不再维护了,替换消息中间件的代价就会非常的高昂,AMQP解决了这个问题,它使用了一套标准的底层协议。 44 | 45 | **什么是AMQP:** 46 | - AMQP(advanced message queuing protocol)在2003年时被提出,最早用于解决金融领不同平台之间的消息传递交互问题,就是是一种协议,兼容JMS 47 | - 更准确说的链接协议 binary- wire-level-protocol 直接定义网络交换的数据格式,类似http 48 | - 具体的产品实现比较多,RabbitMQ就是其中一种 49 | 50 | **特性:** 51 | - 独立于平台的底层消息传递协议 52 | - 消费者驱动消息传递 53 | - 跨语言和平台的互用性、属于底层协议 54 | - 有5种交换类型direct,fanout,topic,headers,system 55 | - 面向缓存的、可实现高性能、支持经典的消息队列,循环,存储和转发 56 | - 支持长周期消息传递、支持事务(跨消息队列) 57 | 58 | **AMQP和JMS的主要区别:** 59 | - AMQP不从API层进行限定,直接**定义网络交换的数据格式** 60 | - JMS消息类型:TextMessage/ObjectMessage/StreamMessage等,AMQP消息类型:**Byte[]** 61 | 62 | ### 3.2 MQTT 63 | 64 | MQTT: 消息队列遥测传输(Message Queueing Telemetry Transport ) 65 | 66 | 计算性能不高的设备不能适应AMQP上的复杂操作,MQTT它是专门为小设备设计的 67 | 68 | **特性:** 69 | - 内存占用低,为小型无声设备之间通过低带宽发送短消息而设计 70 | - 不支持长周期存储和转发,不允许分段消息(很难发送长消息) 71 | - 支持主题发布-订阅、不支持事务 72 | - 消息实际上是短暂的(短周期) 73 | - 简单用户名和密码、不支持安全连接、消息不透明 -------------------------------------------------------------------------------- /Message Queue/imgs/HW和LEO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/Message Queue/imgs/HW和LEO.png -------------------------------------------------------------------------------- /Message Queue/imgs/Partition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/Message Queue/imgs/Partition.png -------------------------------------------------------------------------------- /Message Queue/imgs/RangeAssignor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/Message Queue/imgs/RangeAssignor.png -------------------------------------------------------------------------------- /Message Queue/imgs/RoundRobinAssignor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/Message Queue/imgs/RoundRobinAssignor.png -------------------------------------------------------------------------------- /Message Queue/imgs/RoundRobinAssignor2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/Message Queue/imgs/RoundRobinAssignor2.png -------------------------------------------------------------------------------- /Message Queue/imgs/sendModel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/Message Queue/imgs/sendModel.png -------------------------------------------------------------------------------- /Message Queue/imgs/整体架构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xujiangchen/Java-Study-Notes/a9f750cb3cc06411fed573373e755b1c372b97c0/Message Queue/imgs/整体架构.png -------------------------------------------------------------------------------- /Ngnix/Nginx目录文件.md: -------------------------------------------------------------------------------- 1 | # Nginx目录文件 2 | 3 | ## Nginx核心目录 4 | ``` 5 | - conf #所有配置文件目录 6 | - nginx.conf #默认的主要的配置文件 7 | - nginx.conf.default #默认模板 8 | 9 | - html # 这是编译安装时Nginx的默认站点目录 10 | - 50x.html #错误页面 11 | - index.html #默认首页 12 | 13 | - logs # nginx默认的日志路径,包括错误日志及访问日志 14 | - error.log #错误日志 15 | - nginx.pid #nginx启动后的进程id 16 | - access.log #nginx访问日志 17 | 18 | - sbin #nginx命令的目录 19 | - nginx #启动命令 20 | ``` 21 | 22 | ## Nginx核心配置文件 23 | ``` 24 | #user nobody; # 指定Nginx Worker进程运行以及用户组 25 | worker_processes 1; #保持和cup的核数一致 26 | 27 | #error_log logs/error.log; # 错误日志的存放路径 和错误日志 28 | #error_log logs/error.log notice; 29 | #error_log logs/error.log info; 30 | 31 | #pid logs/nginx.pid; # 进程PID存放路径 32 | 33 | # 事件模块指令,用来指定Nginx的IO模型,Nginx支持的有select、poll、kqueue、epoll等。 34 | #不同的是epoll用在Linux平台上,而kqueue用在BSD系统中,对于Linux系统,epoll工作模式是首选 35 | events { 36 | use epoll; 37 | # 定义Nginx每个进程的最大连接数, 作为服务器来说: worker_connections * worker_processes, 38 | # 作为反向代理来说,最大并发数量应该是worker_connections * worker_processes/2。 39 | # 因为反向代理服务器,每个并发会建立与客户端的连接和与后端服务的连接,会占用两个连接 40 | worker_connections 1024; 41 | } 42 | 43 | 44 | http { 45 | include mime.types; 46 | default_type application/octet-stream; 47 | 48 | # 自定义服务日志 49 | #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 50 | # '$status $body_bytes_sent "$http_referer" ' 51 | # '"$http_user_agent" "$http_x_forwarded_for"'; 52 | 53 | #access_log logs/access.log main; 54 | 55 | # 是否开启高效传输模式 on开启 off关闭 56 | sendfile on; 57 | 58 | # 减少网络报文段的数量 59 | #tcp_nopush on; 60 | 61 | #keepalive_timeout 0; 62 | # 客户端连接保持活动的超时时间,超过这个时间之后,服务器会关闭该连接 63 | keepalive_timeout 65; 64 | 65 | # 是否进行压缩,带宽减少,性能下降 66 | #gzip on; 67 | 68 | # 虚拟主机的配置 69 | server { 70 | listen 80; # 虚拟主机的服务端口 71 | server_name localhost; #用来指定IP地址或域名,多个域名之间用空格分开 72 | 73 | #charset koi8-r; 74 | 75 | #access_log logs/host.access.log main; 76 | 77 | # URL地址匹配,可以添加多个 78 | location / { 79 | root html; # 服务默认启动目录 80 | index index.html index.htm; # 默认访问文件,按照顺序找 81 | } 82 | 83 | #error_page 404 /404.html; 84 | 85 | # redirect server error pages to the static page /50x.html 86 | # 87 | error_page 500 502 503 504 /50x.html; 88 | location = /50x.html { 89 | root html; 90 | } 91 | 92 | # proxy the PHP scripts to Apache listening on 127.0.0.1:80 93 | # 94 | #location ~ \.php$ { 95 | # proxy_pass http://127.0.0.1; 96 | #} 97 | 98 | # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 99 | # 100 | #location ~ \.php$ { 101 | # root html; 102 | # fastcgi_pass 127.0.0.1:9000; 103 | # fastcgi_index index.php; 104 | # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; 105 | # include fastcgi_params; 106 | #} 107 | 108 | # deny access to .htaccess files, if Apache's document root 109 | # concurs with nginx's one 110 | # 111 | #location ~ /\.ht { 112 | # deny all; 113 | #} 114 | } 115 | 116 | 117 | # another virtual host using mix of IP-, name-, and port-based configuration 118 | # 119 | #server { 120 | # listen 8000; 121 | # listen somename:8080; 122 | # server_name somename alias another.alias; 123 | 124 | # location / { 125 | # root html; 126 | # index index.html index.htm; 127 | # } 128 | #} 129 | 130 | 131 | # HTTPS server 132 | # 133 | #server { 134 | # listen 443 ssl; 135 | # server_name localhost; 136 | 137 | # ssl_certificate cert.pem; 138 | # ssl_certificate_key cert.key; 139 | 140 | # ssl_session_cache shared:SSL:1m; 141 | # ssl_session_timeout 5m; 142 | 143 | # ssl_ciphers HIGH:!aNULL:!MD5; 144 | # ssl_prefer_server_ciphers on; 145 | 146 | # location / { 147 | # root html; 148 | # index index.html index.htm; 149 | # } 150 | #} 151 | 152 | } 153 | ``` 154 | -------------------------------------------------------------------------------- /Ngnix/accessLog日志.md: -------------------------------------------------------------------------------- 1 | - [accessLog日志](#accessLog日志) 2 | * [默认配置解析](#默认配置解析) 3 | * [自定义日志格式,统计接口响应耗时](#自定义日志格式统计接口响应耗时) 4 | * [挖掘案例](#挖掘案例) 5 | 6 | 7 | # accessLog日志 8 | 9 | **access.log日志用处:** 10 | 11 | - 统计站点访问ip来源、某个时间段的访问频率 12 | - 查看访问最频的页面、Http响应状态码、接口性能 13 | - 接口秒级访问量、分钟访问量、小时和天访问量 14 | 15 | ## 默认配置解析 16 | 17 | ```shell 18 | #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 19 | # '$status $body_bytes_sent "$http_referer" ' 20 | # '"$http_user_agent" "$http_x_forwarded_for"'; 21 | ``` 22 | 23 | **日志结果** 24 | 25 | ``` 26 | 122.70.148.18 - - [04/Aug/2020:14:46:48 +0800] "GET /user/api/v1/product/order/query_state?product_id=1&token=xdclasseyJhbGciOJE HTTP/1.1" 200 48 "https://xdclass.net/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36" 27 | ``` 28 | 29 | **具体说明** 30 | 31 | ``` 32 | $remote_addr 对应的是真实日志里的122.70.148.18,即客户端的IP。 33 | 34 | $remote_user 对应的是第二个中杠“-”,没有远程用户,所以用“-”填充。 35 | 36 | [$time_local]对应的是[04/Aug/2020:14:46:48 +0800]。 37 | 38 | “$request”对应的是请求接口 39 | 40 | $status对应的是200状态码,200表示正常访问。 41 | 42 | $body_bytes_sent对应的是48字节,即响应body的大小。 43 | 44 | “$http_referer” 对应的是”https://xdclass.net/“,若是直接打开域名浏览的时,referer就会没有值,为”-“。可以用于防盗链 45 | 46 | “$http_user_agent” 对应的是”Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:56.0) Gecko/20100101 Firefox/56.0”。 47 | 48 | “$http_x_forwarded_for” 对应的是”-“或者空。 49 | ``` 50 | 51 | ## 自定义日志格式,统计接口响应耗时 52 | 53 | ```shell 54 | $request_time:从接受用户请求的第一个字节到发送完响应数据的时间,即包括接收请求数据时间、程序响应时间、输出响应数据时间 55 | 56 | $upstream_response_time:指从Nginx向后端建立连接开始到接受完数据然后关闭连接为止的时间 57 | 58 | $request_time一般会比upstream_response_time大,因为用户网络较差,或者传递数据较大时,前者会耗时大很多 59 | ``` 60 | 61 | ## 挖掘案例 62 | 63 | - **查看访问最频繁的前100个IP** 64 | 65 | ```shell 66 | awk '{print $1}' access_temp.log | sort -n |uniq -c | sort -rn | head -n 100 67 | ``` 68 | 69 | - **统计访问最多的url 前20名** 70 | 71 | ```shell 72 | cat access_temp.log |awk '{print $7}'| sort|uniq -c| sort -rn| head -20 | more 73 | ``` 74 | 75 | - **常用命令** 76 | 77 | ```shell 78 | awk 是文本处理工具,默认按照空格切分,$N 是第切割后第N个,从1开始 79 | 80 | sort命令用于将文本文件内容加以排序,-n 按照数值排,-r 按照倒序来排 81 | 案例的sort -n 是按照第一列的数值大小进行排序,从小到大,倒序就是 sort -rn 82 | 83 | uniq 去除重复出现的行列, -c 在每列旁边显示该行重复出现的次数。 84 | 85 | $NF 表示最后一列 86 | ``` 87 | -------------------------------------------------------------------------------- /Ngnix/安装Nginx.md: -------------------------------------------------------------------------------- 1 | ## 安装Nginx服务器 2 | 3 | **下载** 4 | 官方网址:http://nginx.org/en/download.h 5 | 6 | Nginx官网提供了三个类型的版本 7 | - Mainline version:Mainline 是 Nginx 目前主力在做的版本,可以说是开发版 8 | - Stable version:最新稳定版,生产环境上建议使用的版本 9 | - Legacy versions:遗留的老版本的稳定版 10 | 11 | **安装依赖** 12 | 13 | ```shell 14 | yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel 15 | ``` 16 | 17 | **解压nginx压缩包** 18 | ```shell 19 | tar -zxvf nginx-1.18.0.tar.gz 20 | ``` 21 | 22 | **执行C语言编译命令** 23 | ```shell 24 | ./configure 25 | 26 | make 27 | 28 | make install 29 | ``` 30 | 31 | **默认安装位置** 32 | ```shell 33 | /usr/local/nginx 34 | ``` 35 | 36 | ## Nginx的启动和关闭 37 | 38 | #### 启动 39 | 40 | - **进入安装目录:** `cd /usr/local/nginx/sbin/` 41 | - **启动:** `./nginx` 42 | 43 | 44 | #### 关闭 45 | 46 | - **查询nginx进程:** `ps -ef|grep nginx` 47 | - 会出现两个nginx进程,关闭master进程即可 48 | 49 | #### 重启 50 | 51 | - **进入安装目录:** `cd /usr/local/nginx/sbin/` 52 | - **重启:** `./nginx -s reload` 53 | 54 | #### 指定配置文件启动 55 | 56 | 不论是启动还是重启都会默认使用conf 目录下的nginx.conf 配置文件 57 | - `./nginx -c [绝对路径]` 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 前言 2 | 用于记录在工作和学习中Java相关的知识(包括:从过去记录在其它笔记软件和个人博客中的java学习笔记迁移到github) 3 | 4 | ## JVM 5 | 6 | ​ JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。 7 | 8 | - [JVM基础知识](https://github.com/xujiangchen/Java-Study-Notes/blob/main/JVM/JVM%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E7%82%B9.md) 9 | - [对象](https://github.com/xujiangchen/Java-Study-Notes/blob/main/JVM/%E5%AF%B9%E8%B1%A1.md) 10 | - [垃圾回收涉及的主要算法](https://github.com/xujiangchen/Java-Study-Notes/blob/main/JVM/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%B6%89%E5%8F%8A%E7%9A%84%E4%B8%BB%E8%A6%81%E7%AE%97%E6%B3%95.md) 11 | - [JVM垃圾收集器](https://github.com/xujiangchen/Java-Study-Notes/blob/main/JVM/JVM%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E5%99%A8.md) 12 | - [内存分配]() 13 | 14 | ## Springboot 15 | 16 | - [Springboot基础注解](https://github.com/xujiangchen/Java-Study-Notes/blob/main/springboot/SpringBoot%20%E6%B3%A8%E8%A7%A3.md) 17 | - [Springboot目录结构](https://github.com/xujiangchen/Java-Study-Notes/blob/main/springboot/Springboot%20%E7%9A%84%E7%9B%AE%E5%BD%95%E7%BB%93%E6%9E%84.md) 18 | - [Springboot异常处理](https://github.com/xujiangchen/Java-Study-Notes/blob/main/springboot/springboot%20%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86.md) 19 | - [SpringBoot拦截器实战和 Servlet3.0自定义Filter、Listener](https://github.com/xujiangchen/Java-Study-Notes/blob/main/springboot/SpringBoot%E6%8B%A6%E6%88%AA%E5%99%A8%E5%92%8C%20Servlet3.0%E8%87%AA%E5%AE%9A%E4%B9%89Filter%E3%80%81Listener.md) 20 | - [thymeleaf](https://github.com/xujiangchen/Java-Study-Notes/blob/main/springboot/thymeleaf.md) 21 | 22 | ## 漏洞相关 23 | - [Java代码扫描中常见的漏洞和相应解决方法](https://github.com/xujiangchen/Java-Study-Notes/blob/main/%E5%B8%B8%E8%A7%81Java%E6%BC%8F%E6%B4%9E%E7%B1%BB%E5%9E%8B%E5%92%8C%E5%85%B7%E4%BD%93%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95.md#portability-flaw--locale-dependent-comparison) 24 | 25 | 26 | ## Nginx 27 | - [Nginx的安装和启动](https://github.com/xujiangchen/Java-Study-Notes/blob/main/Ngnix/%E5%AE%89%E8%A3%85Nginx.md) 28 | - [Nginx的目录以及配置文件](https://github.com/xujiangchen/Java-Study-Notes/blob/main/Ngnix/Nginx%E7%9B%AE%E5%BD%95%E6%96%87%E4%BB%B6.md) 29 | - [accessLog日志](https://github.com/xujiangchen/Java-Study-Notes/blob/main/Ngnix/accessLog%E6%97%A5%E5%BF%97.md) 30 | 31 | ## 消息队列 32 | - [Zookeeper](https://github.com/xujiangchen/Java-Study-Notes/tree/main/Zookeeper) 33 | - [Kafka](https://github.com/xujiangchen/Java-Study-Notes/blob/main/Message%20Queue/Kafka.md) -------------------------------------------------------------------------------- /Zookeeper/zookeeper.md: -------------------------------------------------------------------------------- 1 | ## 搭建zookeeper集群 2 | ``` 3 | # 解压 4 | tar -zxvf apache-zookeeper-3.7.0-bin.tar.gz 5 | 6 | # 对解压文件重命名,方便管理 7 | mv apache-zookeeper-3.7.0-bin zookeeper 8 | 9 | # 进入zookeeper配置文件夹 10 | cd zookeeper/conf/ 11 | 12 | # 复制默认配置文件进行自定义 13 | cp zoo_sample.cfg zoo.cfg 14 | 15 | # 修改配置文件中属性 16 | dataDir=/tmp/zookeeper/1 # 添加一个编号 17 | clientPort=2181 # 每个zookeeper的端口号都不相同 18 | admin.serverPort=10808 # 添加admin.serverPort防止端口占用,每个节点都不相同 19 | 20 | # 在dataDir对应目录下分别创建myid文件, 根据上一步指定的dataDir,找到对应的文件地址,如果没有则进行创建,进入目录后输入 21 | echo 1 > myid 22 | # 数字按顺序递增,这个非常重要,用于后面配置集群 23 | 24 | # 配置集群信息,每个节点对应一条数据,需要添加到每一个节点中去 25 | # server.服务器id=服务器IP地址:服务器直接通信端口:服务器之间选举投票端口 26 | server.1=127.0.0.1:2881:3881 27 | server.2=127.0.0.1:2882:3882 28 | server.3=127.0.0.1:2883:3883 29 | ... 30 | 31 | # 启动zookeeper节点,每个节点都要启动 32 | cd bin/ 33 | ./zkServer.sh start 34 | 35 | # 查看节点状态(一个leader,多个follower) 36 | cd bin/ 37 | ./zkServer.sh status 38 | ``` -------------------------------------------------------------------------------- /springboot/SpringBoot 注解.md: -------------------------------------------------------------------------------- 1 | - [SpringBoot 注解](#springboot---) 2 | * [一、启动类注解](#一启动类注解) 3 | + [1.1@SpringBootApplication](#11springbootapplication) 4 | * [二、HTTP请求注解](#二HTTP请求注解) 5 | + [2.1 @RestController](#21-restcontroller) 6 | + [2.2 从请求路径中获取参数](#22-从请求路径中获取参数) 7 | + [2.2 @GetMapping](#22-getmapping) 8 | + [2.3 @RequestParam](#23-requestparam) 9 | + [2.4 @RequestBody](#24-requestbody) 10 | + [2.5 @RequestHeader](#25-requestheader) 11 | + [2.6 @PostMapping](#26-postmapping) 12 | # SpringBoot 注解 13 | 14 | ## 一、启动类注解 15 | 16 | ### 1.1@SpringBootApplication 17 | 18 | 其实这个注解是多个注解的集合 19 | 20 | ```java 21 | @Target({ElementType.TYPE}) 22 | @Retention(RetentionPolicy.RUNTIME) 23 | @Documented 24 | @Inherited 25 | @SpringBootConfiguration 26 | @EnableAutoConfiguration 27 | @ComponentScan 28 | ``` 29 | 30 | 其中 这三个注解是核心,将这三个替换(SpringBootApplication) springboot依旧可以正常启动 31 | 32 | ```java 33 | @SpringBootConfiguration 34 | @EnableAutoConfiguration 35 | @ComponentScan 36 | ``` 37 | 38 | - `SpringBootConfiguration`: 继承自@Configuration,二者功能也一致,标注当前类是配置类 39 | - `EnableAutoConfiguration`: 帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot,并创建对应配置类的Bean,并把该Bean实体交给IoC容器进行管理。 40 | - `ComponentScan`: 根据指定的配置自动扫描package,将符合条件的组件加入到IOC容器中; 41 | 42 | ## 二、HTTP请求注解 43 | 44 | ### 2.1 @RestController 45 | 46 | - `@RestController = @Controller+@ResponseBody` 47 | - `@ResponseBody` 作用是将java对象转为json格式的数据 48 | 49 | ```java 50 | @RestController 51 | public class SampleController { 52 | 53 | @RequestMapping("/") 54 | @ResponseBody 55 | String home(){ 56 | return "hello world"; 57 | } 58 | 59 | @RequestMapping("/test") 60 | public Map testMap(){ 61 | Map map = new HashMap<>(); 62 | map.put("name","mapTest"); 63 | return map; 64 | } 65 | } 66 | ``` 67 | 68 | ### 2.2 从请求路径中获取参数 69 | 70 | ```java 71 | 1)@RequestMapping(path = "/{id}", method = RequestMethod.GET) 72 | public String getUser(@PathVariable String id ) {} 73 | 74 | 2)@RequestMapping(path = "/{depid}/{userid}", method = RequestMethod.GET) 75 | getUser(@PathVariable("depid") String departmentID,@PathVariable("userid") String userid) 76 | 77 | 78 | 79 | /** 80 | * 功能描述:测试restful协议,从路径中获取字段 81 | * @param depId 82 | * @param userId 83 | * @return 84 | */ 85 | @RequestMapping(path = "/{dep_id}/{user_id}", method = RequestMethod.GET) 86 | //@GetMapping(path = "/{dep_id}/{user_id}") 87 | public Object findUser(@PathVariable("dep_id") String depId,@PathVariable("user_id") String userId){ 88 | param.clear(); 89 | param.put("depId",depId); 90 | param.put("userId",userId); 91 | return param; 92 | } 93 | ``` 94 | 95 | ### 2.2 @GetMapping 96 | 97 | - `@GetMapping = @RequestMapping(method = RequestMethod.GET)` 98 | 99 | ```java 100 | /** 101 | * 功能描述:测试GetMapping 102 | * @param num 103 | * @param s 104 | * @return 105 | */ 106 | @GetMapping("/dome/getmapping") 107 | public Object getMapping(int num, String s){ 108 | param.clear(); 109 | param.put("num", num); 110 | param.put("s", s); 111 | return param; 112 | } 113 | ``` 114 | 115 | ### 2.3 @RequestParam 116 | 117 | ```java 118 | /** 119 | * 功能描述:默认值,是否必须的参数 120 | * @param page 121 | * @param size 122 | * @return 123 | */ 124 | @GetMapping("/demo/demo_page") 125 | public Object page(@RequestParam(defaultValue = "0", name = "page")String page, int size){ 126 | param.clear(); 127 | param.put("page", page); 128 | param.put("size", size); 129 | return param; 130 | } 131 | ``` 132 | 133 | ### 2.4 @RequestBody 134 | 135 | ```java 136 | /** 137 | * 功能描述:bean对象的传值 138 | * 注意:1、http头要指定content—type为application/json 139 | * 2、要使用body传值 140 | * @param user 141 | * @return 142 | */ 143 | 144 | @RequestMapping("save_user") 145 | // 也可以使用@GetMapper("save_user") 146 | public Object saveUser(@RequestBody User user){ 147 | param.clear(); 148 | param.put("user",user); 149 | return param; 150 | } 151 | ``` 152 | 153 | ### 2.5 @RequestHeader 154 | 155 | ```java 156 | /** 157 | * 功能描述:测试获取Http的头部信息 158 | * 159 | * @param taken 160 | * @param id 161 | * @return 162 | */ 163 | @GetMapping("get_header") 164 | public Object getHeader(@RequestHeader("taken")String taken,String id){ 165 | param.clear(); 166 | param.put("taken",taken); 167 | param.put("id",id); 168 | return param; 169 | } 170 | ``` 171 | 172 | ### 2.6 @PostMapping 173 | 174 | - `@PostMapping = @RequestMapping(method = RequestMethod.POST)` 175 | 176 | ```java 177 | @PostMapping(value = "/create") 178 | public Map createBanner(@RequestBody Map banner){ 179 | banner.put("id", 1243); 180 | return banner; 181 | } 182 | ``` 183 | -------------------------------------------------------------------------------- /springboot/SpringBoot拦截器和 Servlet3.0自定义Filter、Listener.md: -------------------------------------------------------------------------------- 1 | - [一、SpringBoot启动默认加载的Filter](#一SpringBoot启动默认加载的Filter) 2 | - [二、Filter优先级](#二Filter优先级) 3 | - [三、Servlet3.0自定义Filter](#三Servlet3.0自定义Filter) 4 | * [自定义过滤器](#自定义过滤器) 5 | * [启动类](#启动类) 6 | - [四、Servlet3.0的注解自定义原生Listener](#四Servlet30的注解自定义原生Listener) 7 | 8 | 9 | # 一、SpringBoot启动默认加载的Filter 10 | 11 | ``` 12 | - characterEncodingFilter 13 | 是spring内置过滤器的一种,用来指定请求或者响应的编码格式。在web开发中经常被从来使用 14 | 15 | - hiddenHttpMethodFilter 16 | 浏览器form表单只支持Get和POST请求,而Delete、Put等method并不支持。而HiddenHttpMethodFilter,可以将这些请求转换为标准的http方法,使得支持GET、POST、PUT和DELETE请求 17 | 18 | - httpPutFormContentFilter 19 | 用于自动的封装前台传递过来的PUT请求的参数 20 | 21 | - requestContextFilter 22 | 基于LocalThread将HTTP request对象绑定到为该请求提供服务的线程上。 23 | ``` 24 | 25 | # 二、Filter优先级 26 | 27 | 低位值意味着更高的优先级,自定义Filter,避免和默认的Filter优先级一样,不然会冲突。 28 | 29 | ``` 30 | - 设置最高优先级 31 | Ordered.HIGHEST_PRECEDENCE 32 | @Order(Ordered.HIGHEST_PRECEDENCE) 33 | 34 | - 设置最低优先级 35 | Ordered.LOWEST_PRECEDENCE 36 | @Order(Ordered.LOWEST_PRECEDENCE) 37 | ``` 38 | 39 | # 三、Servlet3.0自定义Filter 40 | 41 | - 1、使用Servlet3.0的注解进行配置 42 | - 2、启动类里面增加 @ServletComponentScan,进行扫描 43 | - 3、新建一个Filter类,implements Filter,并实现对应的接口 44 | - 4、@WebFilter 标记一个类为filter,被spring进行扫描 45 | 46 | ## 自定义过滤器 47 | 48 | ```java 49 | import javax.servlet.*; 50 | import javax.servlet.http.HttpServletRequest; 51 | import javax.servlet.http.HttpServletResponse; 52 | import java.io.IOException; 53 | 54 | // urlPatterns 拦截请求路径,filterName 名称 55 | // @WebFilter(serlvet3.0提供)注解以注册到servlet容器中 56 | @WebFilter(urlPatterns = "/api/*", filterName = "loginFilter") 57 | public class LoginFilter implements Filter { 58 | 59 | 60 | @Override 61 | // 在启动容器的时候进行加载 62 | public void init(FilterConfig filterConfig) throws ServletException { 63 | System.out.println("init loginFilter"); 64 | } 65 | 66 | @Override 67 | // 处理业务的方法 68 | public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 69 | System.out.println("doFilter loginFilter"); 70 | 71 | // 进行强转,拿到前台给我传来的参数 72 | HttpServletRequest req = (HttpServletRequest)servletRequest; 73 | HttpServletResponse resp = (HttpServletResponse)servletResponse; 74 | // 业务操作,进行判断 75 | 76 | // 符合的进行放行 77 | filterChain.doFilter(servletRequest,servletResponse); 78 | 79 | // 不符合的自行接解决 80 | } 81 | 82 | @Override 83 | // 容器销毁的时候调用的方法 84 | public void destroy() { 85 | System.out.println("destroy loginFilter"); 86 | } 87 | } 88 | ``` 89 | 90 | ## 启动类 91 | 92 | ```java 93 | // 添加 ServletComponentScan 注解对Filter进行扫描 94 | @SpringBootApplication 95 | @ServletComponentScan 96 | public class Application{ 97 | public static void main(String[] args){ 98 | SpringApplication.run(Application.class, args); 99 | } 100 | } 101 | ``` 102 | 103 | # 四、Servlet3.0的注解自定义原生Listener 104 | 105 | 常用的监听器接口有 ServletContextListener、ServletRequestListener、ServletSessionListener,分别监听容器、请求、会话的创建和销毁。 106 | 107 | ServletContextAttributeListener、ServletRequestAttributeListener、ServletSessionAttributeListener对应监听三者的 addAttribute和 removeAttribute方法。 108 | 109 | ```java 110 | package top.zhenganwen.springbootweb.servlet; 111 | 112 | import javax.servlet.ServletException; 113 | import javax.servlet.annotation.WebServlet; 114 | import javax.servlet.http.HttpServlet; 115 | import javax.servlet.http.HttpServletRequest; 116 | import javax.servlet.http.HttpServletResponse; 117 | import java.io.IOException; 118 | 119 | @WebServlet(name = "userServlet",urlPatterns = "/user/*") 120 | public class UserServlet extends HttpServlet{ 121 | @Override 122 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 123 | 124 | resp.getWriter().write("hello"); 125 | resp.getWriter().flush(); 126 | resp.getWriter().close(); 127 | } 128 | 129 | @Override 130 | protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 131 | this.doGet(req, resp); 132 | } 133 | } 134 | ``` 135 | -------------------------------------------------------------------------------- /springboot/Springboot 的目录结构.md: -------------------------------------------------------------------------------- 1 | ## 基础结构 2 | 3 | - **src/main/java:** 存放代码 4 | - **src/main/resources:** 5 | - static: 存放静态文件,比如 css、js、image,(访问方式http://localhost:8080/js/main.js) 6 | - templates:存放静态页面jsp,html,tpl 7 | - config:存放配置文件,application.properties 8 | - resources: 9 | 10 | ## 同个文件的加载顺序,静态资源文件 11 | 12 | **Spring Boot** 默认会挨个从**META/resources > resources > static > public** 里面找是否存在相应的资源,如果有则直接返回。 13 | 14 | ## package组织原则 15 | 16 | 先分模块,再分层。先分层,再分模块的组织方式,**被称为“贫血模型”**,不利于面向对象思维,需要尽量避免。 17 | 18 | -------------------------------------------------------------------------------- /springboot/springboot 异常处理.md: -------------------------------------------------------------------------------- 1 | - [Springboot的异常处理](#springboot-----) 2 | * [一、自定义错误页面](#一自定义错误页面) 3 | * [二、@ExceptionHandle 注解处理异常](#二ExceptionHandle-注解处理异常) 4 | * [三、@ControllerAdvice+@ExceptionHandler 注解处理异常](#三ControllerAdviceExceptionHandler-注解处理异常) 5 | * [四、SimpleMappingExceptionResolver 处理异常](#SimpleMappingExceptionResolver-处理异常) 6 | * [五、自定义 HandlerExceptionResolver 类处理异常](#五自定义-HandlerExceptionResolver-类处理异常) 7 | 8 | 9 | 10 | # Springboot的异常处理 11 | 12 | ## 一、自定义错误页面 13 | 14 | Springboot自己提供了一套处理异常的机制,一旦程序中出现了异常SpringBoot会像error的url发送请求。在SpringBoot中提供了一个叫BasicExceptionController来处理/error 请求,然后跳转到默认显示异常的页面来展示异常信息。 15 | 16 | 如果我们需要将所有的异常都跳转到自定义的错误页面,那么就需要在src/main/resources/templates下创建一个 error.html 页面,**必须叫error** 17 | 18 | ## 二、@ExceptionHandle 注解处理异常 19 | 20 | 如果需要针对特定的异常做出不同的处理,我们可以通过@ExceptionHandle来处理实现,具体如下 21 | 22 | **Controller** 23 | 24 | ```java 25 | /** 26 | * @program: springboot-exception 27 | * @description: 异常处理 28 | * @author: xujiangchen 29 | */ 30 | @Controller 31 | public class DemoController { 32 | 33 | /** 34 | * 模拟 NullPointerException 35 | * @return 36 | */ 37 | @RequestMapping("/show1") 38 | public String showInfo(){ 39 | String str = null; 40 | str.length(); 41 | return "index"; 42 | } 43 | 44 | /** 45 | * 模拟 ArithmeticException 46 | * @return 47 | */ 48 | @RequestMapping("/show2") 49 | public String showInfo2(){ 50 | int a = 10/0; 51 | return "index"; 52 | } 53 | 54 | /** 55 | * java.lang.ArithmeticException 56 | * 该方法需要返回一个 ModelAndView:目的是可以让我们封装异常信息以及视图的指定 57 | * 参数 Exception e:会将产生异常对象注入到方法中 58 | */ 59 | @ExceptionHandler(value={java.lang.ArithmeticException.class}) 60 | public ModelAndView arithmeticExceptionHandler(Exception e){ 61 | ModelAndView mv = new ModelAndView(); 62 | mv.addObject("error", e.toString()); 63 | mv.setViewName("error1"); 64 | return mv; 65 | } 66 | /** 67 | * java.lang.NullPointerException 68 | * 该方法需要返回一个 ModelAndView:目的是可以让我们封装异常信息以及视 69 | 图的指定 70 | * 参数 Exception e:会将产生异常对象注入到方法中 71 | */ 72 | @ExceptionHandler(value={java.lang.NullPointerException.class}) 73 | public ModelAndView nullPointerExceptionHandler(Exception e){ 74 | ModelAndView mv = new ModelAndView(); 75 | mv.addObject("error", e.toString()); 76 | mv.setViewName("error2"); 77 | return mv; 78 | } 79 | 80 | } 81 | ``` 82 | 83 | ## 三、@ControllerAdvice+@ExceptionHandler 注解处理异常 84 | 85 | 第二中方式中的处理有一个非常明显的弊端,异常处理的代码和业务代码放在一个类中了,这种方式**耦合性太强了**,最好的方式是将两者拆开管理,这时候我们就可以使用 `ControllerAdvice` 来定义一个专门处理异常的类。这样逻辑代码中,只需要关注自己所写的业务逻辑即可,异常信息同一交由这个特殊的类来处理。 86 | 87 | **异常处理类** 88 | 89 | ```java 90 | ** 91 | * @program: springboot-exception 92 | * @description: 全局异常处理 93 | * @author: xujianchen 94 | */ 95 | @ControllerAdvice 96 | public class GlobalException { 97 | 98 | /** 99 | * java.lang.ArithmeticException 100 | * 该方法需要返回一个 ModelAndView:目的是可以让我们封装异常信息以及视图的指定 101 | * 参数 Exception e:会将产生异常对象注入到方法中 102 | */ 103 | @ExceptionHandler(value={java.lang.ArithmeticException.class}) 104 | public ModelAndView arithmeticExceptionHandler(Exception e){ 105 | ModelAndView mv = new ModelAndView(); 106 | mv.addObject("error", e.toString()+" -- advice"); 107 | mv.setViewName("error1"); 108 | return mv; 109 | } 110 | 111 | /** 112 | * java.lang.NullPointerException 113 | * 该方法需要返回一个 ModelAndView:目的是可以让我们封装异常信息以及视 114 | 图的指定 115 | * 参数 Exception e:会将产生异常对象注入到方法中 116 | */ 117 | @ExceptionHandler(value={java.lang.NullPointerException.class}) 118 | public ModelAndView nullPointerExceptionHandler(Exception e){ 119 | ModelAndView mv = new ModelAndView(); 120 | mv.addObject("error", e.toString()+" -- advice"); 121 | mv.setViewName("error2"); 122 | return mv; 123 | } 124 | } 125 | 126 | ``` 127 | 128 | ## 四、SimpleMappingExceptionResolver 处理异常 129 | 130 | 第三种方法中也有一个小的弊端,就是每种类型的异常,都要写一个单独的方法,并指定对应的页面,这时候就可以使用 `SimpleMappingExceptionResolver` 来简化这个步骤 131 | 132 | ```java 133 | /** 134 | * @program: springboot-exception 135 | * @description: 全局异常处理器 136 | * @author: xujiangchen 137 | */ 138 | @Configuration 139 | public class GlobalException { 140 | /** 141 | * 该方法必须要有返回值。返回值类型必须是: 142 | SimpleMappingExceptionResolver 143 | */ 144 | @Bean 145 | public SimpleMappingExceptionResolver 146 | getSimpleMappingExceptionResolver(){ 147 | SimpleMappingExceptionResolver resolver = new 148 | SimpleMappingExceptionResolver(); 149 | Properties mappings = new Properties(); 150 | /** 151 | * 参数一:异常的类型,注意必须是异常类型的全名 152 | * 参数二:视图名称 153 | */ 154 | mappings.put("java.lang.ArithmeticException", "error1"); 155 | mappings.put("java.lang.NullPointerException","error2"); 156 | //设置异常与视图映射信息的 157 | resolver.setExceptionMappings(mappings); 158 | return resolver; 159 | } 160 | } 161 | 162 | ``` 163 | 164 | ## 五、自定义 HandlerExceptionResolver 类处理异常 165 | 166 | ```java 167 | /** 168 | * @program: springboot-exception 169 | * @description: 全局异常处理器 170 | * @author: xujiangchen 171 | * @create: 2019-05-17 10:07 172 | */ 173 | @Configuration 174 | public class GlobalException implements HandlerExceptionResolver { 175 | 176 | /** 177 | * @param httpServletRequest 178 | * @param httpServletResponse 179 | * @param o 180 | * @param e 181 | * @return 182 | */ 183 | @Override 184 | public ModelAndView resolveException(HttpServletRequest httpServletRequest 185 | , HttpServletResponse httpServletResponse 186 | , Object o, Exception ex) { 187 | ModelAndView mv = new ModelAndView(); 188 | //判断不同异常类型,做不同视图跳转 189 | if (ex instanceof ArithmeticException) { 190 | mv.setViewName("error1"); 191 | } 192 | if (ex instanceof NullPointerException) { 193 | mv.setViewName("error2"); 194 | } 195 | mv.addObject("error", ex.toString()); 196 | return mv; 197 | } 198 | } 199 | ``` 200 | -------------------------------------------------------------------------------- /springboot/thymeleaf.md: -------------------------------------------------------------------------------- 1 | - [模板引擎Thymeleaf](#模板引擎Thymeleaf) 2 | * [1、什么是模板引擎](#1什么是模板引擎) 3 | * [2、为什么jsp被淘汰了](#2为什么jsp被淘汰了) 4 | * [3、导入Thymeleaf](#3导入Thymeleaf) 5 | * [4、Thymeleaf配置](#4Thymeleaf配置) 6 | * [5、实战](#5实战) 7 | + [TestController](#testcontroller) 8 | + [test.html](#testhtml) 9 | * [6、thymeleaf语法](#6thymeleaf语法) 10 | 11 | # 模板引擎Thymeleaf 12 | 13 | ## 1、什么是模板引擎 14 | 15 | 是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的[HTML]文档;SpringBoot推荐使用模板引擎 16 | 17 | ## 2、为什么jsp被淘汰了 18 | 19 | 虽然jsp也是算是模拟中的一种吗,但是现在强调前后端分离,前端写前端的代码,后端写后端的代码,没有必要将前端和后端代码融合在一起,所以jsp使用就受限了。 20 | 21 | - 动态资源和静态资源全部耦合在一起,无法做到真正的动静分离。服务器压力大,因为服务器会收到各种http请求,例如css的http请求、js的、图片的、动态代码的等等。一旦服务器出现状况,前后台一起玩完,用户体验极差。 22 | 23 | - 前端工程师做好html后,需要由Java工程师来将html修改成jsp页面,出错率较高(因为页面中经常会出现大量的js代码),修改问题时需要双方协同开发,效率低下。 24 | 25 | - JSP 必须要在支持Java的Web服务器里运行(例如tomcat等),无法使用nginx等(nginx单实例http并发高达5w),性能提不上来。 26 | 27 | - 第一次请JSP,必须要在web服务器中编译成servlet,第一次运行会较慢。 28 | 29 | - 每次请求JSP都是访问Servlet再用输出流输出的html页面,效率没有直接使用html高。 30 | 31 | - JSP 内有较多标签和表达式,前端工程师在修改页面时会捉襟见肘,遇到很多痛点。 32 | 33 | - 如果JSP中的内容很多,页面响应会很慢,因为是同步加载。 34 | 35 | ## 3、导入Thymeleaf 36 | ```xml 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-thymeleaf 41 | 42 | ``` 43 | 44 | ## 4、Thymeleaf配置 45 | ``` 46 | # 开发时关闭缓存,不然没法看到实时页面 47 | spring.thymeleaf.cache=false 48 | spring.thymeleaf.mode=HTML5 49 | # 前缀 50 | spring.thymeleaf.prefix=classpath:/templates/ 51 | # 编码 52 | spring.thymeleaf.encoding=UTF-8 53 | # 类型 54 | spring.thymeleaf.content-type=text/html 55 | # 名称的后缀 56 | spring.thymeleaf.suffix=.html 57 | ``` 58 | 59 | ## 5、实战 60 | 61 | ### TestController 62 | ```java 63 | import org.springframework.stereotype.Controller; 64 | import org.springframework.ui.Model; 65 | import org.springframework.web.bind.annotation.RequestMapping; 66 | 67 | @Controller 68 | public class TestController { 69 | 70 | @RequestMapping("/test") 71 | public String TestThymeleaf(Model model) { 72 | model.addAttribute("msg", "Hello,Thymeleaf"); 73 | return "test"; 74 | } 75 | 76 | } 77 | ``` 78 | ### test.html 79 | 80 | - 文件位于springboot资源目录,resources的templates目录之下 81 | 82 | 首先引入thymeleaf命名空间约束 83 | ```html 84 | xmlns:th="http://www.thymeleaf.org" 85 | ``` 86 | ```html 87 | 88 | 89 | 90 | 91 | 测试Thymeleaf 92 | 93 | 94 | 95 |
96 | 97 | 98 | ``` 99 | 100 | ## 6、thymeleaf语法 101 | 102 | 参考,https://www.cnblogs.com/itdragon/archive/2018/04/13/8724291.html 103 | -------------------------------------------------------------------------------- /常见Java漏洞类型和具体解决方法.md: -------------------------------------------------------------------------------- 1 | - [HTTP Parameter Pollution](#http-parameter-pollution) 2 | - [Portability Flaw:Locale Dependent Comparison](#portability-flaw--locale-dependent-comparison) 3 | - [Header Manipulation](#header-manipulation) 4 | - [Insecure Randomness](#insecure-randomness) 5 | + [前段解决该漏洞方法](#前段解决该漏洞方法) 6 | + [后台解决方法](#后台解决方法) 7 | - [Path Manipulation](#path-manipulation) 8 | - [Log Forging](#log-forging) 9 | - [Mass Assignment:Insecure Binder Configuration](#mass-assignment-insecure-binder-configuration) 10 | 11 | 12 | ## HTTP Parameter Pollution 13 | 14 | **HPP是HTTP Parameter Pollution的缩写,意为HTTP参数污染。** 15 | 16 | 将直接拼接在请求路径后面的参数通过该方法进行过滤 17 | ```java 18 | public class ParamsUtils { 19 | 20 | /** 21 | * 删除重复的参数,防止HTTP Parameter Pollution 22 | * @param params 23 | * @return 24 | */ 25 | public static String checkParams(String params) { 26 | HashMap map = new HashMap<>(); 27 | StringBuilder paramsBuilder = new StringBuilder(); 28 | String[] arr = new String[] {}; 29 | if (isNotEmpty(params)) { 30 | arr = params.split("&", -1); 31 | for (int i = 0; i < arr.length; i++) { 32 | if (arr.length > 0) { 33 | String[] item = arr[i].split("=", -1); 34 | map.put(item[0], item[1]); 35 | } 36 | } 37 | } 38 | if (map.size() < arr.length) { 39 | return params.replaceAll("&", ""); 40 | } else { 41 | for (Map.Entry m : map.entrySet()) { 42 | paramsBuilder.append(m.getKey()).append("=").append(m.getValue()).append("&"); 43 | } 44 | return paramsBuilder.substring(0, paramsBuilder.lastIndexOf("&")); 45 | } 46 | } 47 | 48 | private static boolean isNotEmpty(String str) { 49 | return !isEmpty(str); 50 | } 51 | 52 | private static boolean isEmpty(String str) { 53 | return str == null || str.length() == 0; 54 | } 55 | } 56 | ``` 57 | 58 | ## Portability Flaw:Locale Dependent Comparison 59 | **可移植性缺陷:与区域设置相关的比较** 60 | ``` java 61 | tag.toUpperCase(Locale.ENGLISH) 62 | ``` 63 | 64 | ## Header Manipulation 65 | **HTTP 响应头文件中包含未验证的数据会引发 cache-poisoning、 cross-site scripting、 cross-user defacement、 page hijacking、 cookie manipulation 或 open redirect。** 66 | 67 | 把出现漏洞的参数通过下面的方法进行过滤 68 | 69 | ```java 70 | /** 71 | * 替换非法字符 72 | * 73 | * @param text 74 | * @return 75 | */ 76 | public static String replaceIllegalChar(String text) { 77 | if (org.apache.commons.lang.StringUtils.isEmpty(text)) { 78 | return ""; 79 | } 80 | StringBuilder result = new StringBuilder(); 81 | char[] chars = text.toCharArray(); 82 | for (int i = 0; i < chars.length; i++) { 83 | Character temp = chars[i]; 84 | if (CHARACTER_SET.contains(temp)) { 85 | // do nothing 86 | } else { 87 | result.append(temp); 88 | } 89 | } 90 | 91 | //将&字符替换为空,修复漏洞所用 92 | char[] textChars = result.toString().toCharArray(); 93 | StringBuilder textBuilder = new StringBuilder(); 94 | for (int j = 0; j < textChars.length; j++) { 95 | Character textChar = Character.valueOf(textChars[j]); 96 | if (!Character.valueOf('&').equals(textChar)) { 97 | textBuilder.append(textChar); 98 | }else { 99 | textBuilder.append(""); 100 | } 101 | } 102 | return textBuilder.toString(); 103 | } 104 | 105 | 106 | @SuppressWarnings("serial") 107 | private static final Set CHARACTER_SET = new HashSet() { 108 | { 109 | add(Character.valueOf('\r')); 110 | add(Character.valueOf('\n')); 111 | } 112 | }; 113 | ``` 114 | 115 | ## Insecure Randomness 116 | 117 | **不安全随机数** 118 | 119 | #### 前段解决该漏洞方法 120 | ```js 121 | // 该方法向下兼容到Google44版本 122 | window.crypto.getRandomValues(new Uint32Array(1))[0]; 123 | // 如果对随机数的要求不是很高的话 124 | // 可以用 Date 的 getTime() ,以免工具误会你要拿 Random 来做什么事! 125 | ``` 126 | 127 | #### 后台解决方法 128 | ```java 129 | SecureRandom secureRandom = new SecureRandom(); 130 | int number = (int) ((secureRandom.nextDouble() * 9 + 1) * 100000); 131 | ``` 132 | 133 | ## Path Manipulation 134 | 135 | **路径篡改** 136 | 1. 攻击者能够指定文件系统上的操作中使用的路径。 137 | 2. 通过指定资源,攻击者获得了不允许的功能。 138 | 139 | 将文件名通过下面的方法进行过滤 140 | ```java 141 | /** 142 | * 解决路径篡改 143 | * @param path 144 | * @return 145 | */ 146 | public static String clearPath(String path) { 147 | HashMap map = new HashMap(); 148 | map.put("a", "a"); 149 | map.put("b", "b"); 150 | map.put("c", "c"); 151 | map.put("d", "d"); 152 | map.put("e", "e"); 153 | map.put("f", "f"); 154 | map.put("g", "g"); 155 | map.put("h", "h"); 156 | map.put("i", "i"); 157 | map.put("j", "j"); 158 | map.put("k", "k"); 159 | map.put("l", "l"); 160 | map.put("m", "m"); 161 | map.put("n", "n"); 162 | map.put("o", "o"); 163 | map.put("p", "p"); 164 | map.put("q", "q"); 165 | map.put("r", "r"); 166 | map.put("s", "s"); 167 | map.put("t", "t"); 168 | map.put("u", "u"); 169 | map.put("v", "v"); 170 | map.put("w", "w"); 171 | map.put("x", "x"); 172 | map.put("y", "y"); 173 | map.put("z", "z"); 174 | 175 | map.put("A", "A"); 176 | map.put("B", "B"); 177 | map.put("C", "C"); 178 | map.put("D", "D"); 179 | map.put("E", "E"); 180 | map.put("F", "F"); 181 | map.put("G", "G"); 182 | map.put("H", "H"); 183 | map.put("I", "I"); 184 | map.put("J", "J"); 185 | map.put("K", "K"); 186 | map.put("L", "L"); 187 | map.put("M", "M"); 188 | map.put("N", "N"); 189 | map.put("O", "O"); 190 | map.put("P", "P"); 191 | map.put("Q", "Q"); 192 | map.put("R", "R"); 193 | map.put("S", "S"); 194 | map.put("T", "T"); 195 | map.put("U", "U"); 196 | map.put("V", "V"); 197 | map.put("W", "W"); 198 | map.put("X", "X"); 199 | map.put("Y", "Y"); 200 | map.put("Z", "Z"); 201 | 202 | map.put("0", "0"); 203 | map.put("1", "1"); 204 | map.put("2", "2"); 205 | map.put("3", "3"); 206 | map.put("4", "4"); 207 | map.put("5", "5"); 208 | map.put("6", "6"); 209 | map.put("7", "7"); 210 | map.put("8", "8"); 211 | map.put("9", "9"); 212 | 213 | map.put(".", "."); 214 | map.put("_", "_"); 215 | 216 | 217 | map.put(":", ":"); 218 | map.put("/", File.separator); 219 | map.put("\\", File.separator); 220 | 221 | String temp = ""; 222 | for (int i = 0; i < path.length(); i++) { 223 | if (map.get(path.charAt(i)+"")!=null) { 224 | temp += map.get(path.charAt(i)+""); 225 | } 226 | } 227 | return temp; 228 | } 229 | ``` 230 | ## Log Forging 231 | 232 | **日志伪造** 233 | 1. 数据从一个不可信赖的数据源进入应用程序。 234 | 235 | 2. 数据写入到应用程序或系统日志文件中。 236 | 237 | ```java 238 | /** 239 | * 过滤引起Log Forging漏洞的敏感字符 240 | * @param str 241 | */ 242 | public String filterLogForging(String str){ 243 | List sensitiveStr = new ArrayList<>(); 244 | sensitiveStr.add("%0d"); 245 | sensitiveStr.add("%0a"); 246 | sensitiveStr.add("%0A"); 247 | sensitiveStr.add("%0D"); 248 | sensitiveStr.add("\r"); 249 | sensitiveStr.add("\n"); 250 | String normalize = Normalizer.normalize(str, Normalizer.Form.NFKC); 251 | Iterator iterator = sensitiveStr.iterator(); 252 | while (iterator.hasNext()){ 253 | normalize.replace(iterator.next(),""); 254 | } 255 | return normalize; 256 | } 257 | ``` 258 | ## Mass Assignment:Insecure Binder Configuration 259 | 不安全的参数绑定配置,是指我们的controller中xxxMethod(User user) 未明确指定接口所需属性,而是把整个对象所有属性暴露出去。 260 | 261 | 在Controller类中添加以下代码: 262 | ```java 263 | final String[] DISALLOWED_FIELDS = new String[] {"is_admin"}; 264 | 265 | @InitBinder 266 | public void initBinder(WebDataBinder binder) { 267 | binder.setDisallowedFields(DISALLOWED_FIELDS); 268 | } 269 | ``` 270 | 这种方式只是解决了扫描问题,但是实际没有真正解决该漏洞,因为这个错误出现的并不多,高风险也允许有万分之三的存在。 271 | --------------------------------------------------------------------------------