├── IO.md ├── JVM虚拟机.md ├── NoSql缓存.md ├── README.md ├── Web相关.md ├── 基础知识点.md ├── 多线程.md ├── 操作系统.md ├── 数据库.md ├── 数据结构与算法.md ├── 架构.md ├── 框架.md ├── 计算机网络.md ├── 设计模式.md └── 集合.md /IO.md: -------------------------------------------------------------------------------- 1 | - 标★号为重要知识点 2 | 3 | ## java中IO流的体系? 4 | Java中的流分为两种,一种是字节流,另一种是字符流,分别由四个抽象类来表示(每种流包括输入和输出两种所以一共四 个):InputStream,OutputStream,Reader,Writer。基于这四种IO流父类根据不同需求派生出其他IO流。 5 | 6 | ## ★BIO,NIO,AIO? 7 | BIO是同步阻塞IO,NIO是同步非阻塞IO,AIO是异步非阻塞IO;三种IO方式相比较而言,BIO是一个客户端对应一个线程, 8 | 优化的话可以用线程池进行线程复用,但本质还是一个客户端-服务端通信对应一个线程;NIO只需要一个线程负责多路复用 9 | 器selector的轮询,就可以处理不同客户端channel中的读/写事件,所以多个客户端实际只对应一个线程,另外服务器端 10 | 和客户端均使用缓冲区的方式进行读写;AIO不需要轮询去查看读写事件是否就绪,而是由内核通过回调函数通知并完成后续 11 | 操作。 12 | 13 | ## NIO和IO的区别 14 | 15 | 第一点,NIO少了1次从内核空间到用户空间的拷贝。 16 | 17 | ByteBuffer.allocateDirect()分配的内存使用的是本机内存而不是Java堆上的内存,和网络或者磁盘交互都在操作系统的内核空间中发生。 18 | allocateDirect()的区别在于这块内存不由java堆管理, 但仍然在同一用户进程内。 19 | 20 | 第二点,NIO以块处理数据,IO以流处理数据 21 | 22 | 第三点,非阻塞,NIO1个线程可以管理多个输入输出通道 23 | 24 | 25 | ## 讲讲IO里面的常见类,字节流、字符流、接口、实现类 26 | InputStream和OutStream属于字节流。底下有ByteArray、Object、File等实现类,以及Buffer增强。 27 | Reader和Writer属于字符流。底下有String、File、Buffer等实现。 28 | 29 | ## 讲讲NIO。 30 | NIO通过观测多个缓冲区,哪个缓冲区就绪的话,就处理哪个缓冲区。原本的IO会对一个缓冲区等待,效率比较慢, 31 | 而现在NIO是对一堆缓冲区进行等待,效率比较高。 32 | 33 | 34 | ## ★讲一下NIO和网络传输 35 | 36 | NIO Reactor反应器模式,例如汽车是乘客访问的实体reactor,乘客上车后到售票员处Acceptor登记,之后乘客便可休息睡觉了,到达乘客目的地后,售票员Aceptor将其唤醒即可。持久TCP长链接每个client和server之间有存在一个持久连接,当CCU(用户并发数量)上升,阻塞server无法为每个连接运行1个线程,自己开发1个二进制协议,将message压缩至3-6倍,传输双向且消息频率高,假设server链接了2000个client,每个client平均每分钟传输1-10个message,1个messaged的大小为几百字节/几千字节,而server也要向client广播其他玩家的当前信息,需要高速处理消息的能力。Buffer,网络字节存放传输的地方,从channel中读写,从buffer作为中间存储格式,channel是网络连接与buffer间数据通道,像之前的socket的stream。 37 | 38 | 39 | ## 字节流和字符流的区别? 40 | 41 | 字节流不会用到内存缓冲区,文件本身直接操作。字符流操作使用内存缓存区,用缓存存操作文件。字符流在输出前将所有内容暂时保存到内存中,即缓存区暂时存储,如果想不关闭也将字符流输出则可以使用flush方法强制刷出。字节字符转化可能存在系统编码lang,要制定编码。getbyte字节流使用更加广泛。 42 | 43 | 44 | ## FileInputStream 在使用完以后,不关闭流,想二次使用可以怎么操作? 45 | 可以使用apache的IOUtils包进行copy转成ByteArrayInputStream进行重复读取。 46 | 或者使用反射调用open方法进行重复读取。 47 | 48 | ## ★Java NIO使用 49 | 利用 Selector、Buffer、Channel三个部件。Selector可以同时监听一组通信信道(Channel)上的I/O状态,前提是这个Selector已经注册到这些通信信道中。选择器Selector可以调用select()方法检查已经注册的通信信道上I/O是否已经准备好,如果已经准备好的话,直接读取Buffer中的数据。 50 | 51 | ## ★IO与NIO的比较 52 | 53 | 面向流 VS 面向缓冲 54 | 55 | Java I/O是面向流的, 每次从流(InputStream/OutputStream)中读一个或多个字节,直到读取完所有字节,它们没有被缓存在任何地方。另外,它不能前后移动流中的数据,如需前后移动处理,需要先将其缓存至一个缓冲区。 56 | 57 | Java I/O是面向缓冲, 数据会被读取到一个缓冲区,需要时可以在缓冲区中前后移动处理,这增加了处理过程的灵活性。但与此同时在处理缓冲区前需要检查该缓冲区中是否包含有所需要处理的数据,并需要确保更多数据读入缓冲区时,不会覆盖缓冲区内尚未处理的数据。 58 | 59 | 阻塞 VS 非阻塞 60 | 61 | Java IO的各种流是阻塞的。当某个线程调用read()或write()方法时,该线程被阻塞,直到有数据被读取到或者数据完全写入。阻塞期间该线程无法处理任何其它事情。 62 | 63 | Java NIO非阻塞模式。读写请求并不会阻塞当前线程,在数据可读/写前当前线程可以继续做其它事情,所以一个单独的线程可以管理多个输入和输出通道。 64 | 65 | 选择器 66 | 67 | Java NIO的选择器允许一个单独的线程同时监视多个通道,可以注册多个通道到同一个选择器上,然后使用一个单独的线程来“选择”已经就绪的通道。这种“选择”机制为一个单独线程管理多个通道提供了可能。 68 | 69 | 零拷贝 70 | 71 | Java NIO中提供的FileChannel拥有transferTo和transferFrom两个方法,可直接把FileChannel中的数据拷贝到另外一个Channel,或者直接把另外一个Channel中的数据拷贝到FileChannel。该接口常被用于高效的网络/文件的数据传输和大文件拷贝。在操作系统支持的情况下,通过该方法传输数据并不需要将源数据从内核态拷贝到用户态,再从用户态拷贝到目标通道的内核态,同时也避免了两次用户态和内核态间的上下文切换,也即使用了“零拷贝”,所以其性能一般高于Java IO中提供的方法。 72 | 73 | 通道 74 | NIO的一大创新就是通道,channel可以是双向的,而IO流是单向的。 75 | 76 | ## 谈谈reactor模型。 77 | 同步的等待多个事件源到达(采用select()实现) 78 | 将事件多路分解以及分配相应的事件服务进行处理,这个分派采用server集中处理(dispatch) 79 | 分解的事件以及对应的事件服务应用从分派服务中分离出去(handler) 80 | 81 | 1. Reactor 将I/O事件分派给对应的Handler 82 | 2. Acceptor 处理客户端新连接,并分派请求到处理器链中 83 | 3. Handlers 执行非阻塞读/写 任务 84 | 85 | reactor有三种模型 86 | 87 | 1.Reactor单线程模型 88 | ``` 89 | Reactor单线程模型,指的是所有的I/O操作都在同一个NIO线程上面完成,NIO线程的职责如下: 90 | 作为NIO服务端,接收客户端的TCP连接; 91 | 作为NIO客户端,向服务端发起TCP连接; 92 | 读取通信对端的请求或者应答消息; 93 | 向通信对端发送消息请求或者应答消息; 94 | Reactor线程是个多面手,负责多路分离套接字,Accept新连接,并分派请求到处理器链中。该模型 适用于处理器链中业务处理组件能快速完成的场景。不过,这种单线程模型不能充分利用多核资源,所以实际使用的不多。 95 | 对于一些小容量应用场景,可以使用单线程模型,但是对于高负载、大并发的应用却不合适,主要原因如下: 96 | 一个NIO线程同时处理成百上千的链路,性能上无法支撑。即便NIO线程的CPU负荷达到100%,也无法满足海量消息的编码、解码、读取和发送; 97 | 当NIO线程负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往进行重发,这更加重了NIO线程的负载,最终导致大量消息积压和处理超时,NIO线程会成为系统的性能瓶颈; 98 | 可靠性问题。一旦NIO线程意外跑飞,或者进入死循环,会导致整个系统通讯模块不可用,不能接收和处理外部信息,造成节点故障。 99 | ``` 100 | 101 | 2.单Reactor多线程模型 102 | ``` 103 | Reactor多线程模型与单线程模型最大区别就是有一组NIO线程处理I/O操作,它的特点如下: 104 | 有一个专门的NIO线程--acceptor新城用于监听服务端,接收客户端的TCP连接请求; 105 | 网络I/O操作--读、写等由一个NIO线程池负责,线程池可以采用标准的JDK线程池实现,它包含一个任务队列和N个可用的线程, 106 | 由这些NIO线程负责消息的读取、解码、编码和发送;1个NIO线程可以同时处理N条链路,但是1个链路只对应1个NIO线程,防止发生并发操作问题。 107 | 在绝大多数场景下,Reactor多线程模型都可以满足性能需求;但是,在极特殊应用场景中,一个NIO线程负责监听和处理所有的客户端 108 | 连接可能会存在性能问题。例如百万客户端并发连接,或者服务端需要对客户端的握手信息进行安全认证,认证本身非常损耗性能。 109 | 这类场景下,单独一个Acceptor线程可能会存在性能不足问题,为了解决性能问题,产生了第三种Reactor线程模型--主从Reactor多线程模型。 110 | ``` 111 | 112 | 3.主从Reactor多线程模型 113 | ``` 114 | 特点是:服务端用于接收客户端连接的不再是1个单独的NIO线程,而是一个独立的NIO线程池。Acceptor接收到客户端TCP连接请求处理完成后 115 | (可能包含接入认证等),将新创建的SocketChannel注册到I/O线程池(sub reactor线程池)的某个I/O线程上,由它负责SocketChannel 116 | 的读写和编解码工作。 117 | Acceptor线程池只用于客户端的登录、握手和安全认证,一旦链路建立成功,就将链路注册到后端subReactor线程池的I/O线程上, 118 | 有I/O线程负责后续的I/O操作。第三种模型比起第二种模型,是将Reactor分成两部分,mainReactor负责监听server socket,accept新连接, 119 | 并将建立的socket分派给subReactor。subReactor负责多路分离已连接的socket,读写网 络数据,对业务处理功能,其扔给worker线程池完成。 120 | 通常,subReactor个数上可与CPU个数等同。 121 | ``` 122 | 123 | ## ★select,poll,epoll 124 | (1)select==>时间复杂度O(n) 125 | 126 | 它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流, 127 | 找出能读出数据,或者写入数据的流,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。 128 | 129 | (2)poll==>时间复杂度O(n) 130 | 131 | poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制, 132 | 原因是它是基于链表来存储的. 133 | 134 | (3)epoll==>时间复杂度O(1) 135 | 136 | epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。所以我们说epoll实际上 137 | 是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1)) 138 | 139 | select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪 140 | (一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都 141 | 需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现 142 | 会负责把数据从内核拷贝到用户空间。 143 | 144 | 145 | 146 | ## netty的线程模型,netty如何基于reactor模型上实现的。 147 | https://blog.csdn.net/quxing10086/article/details/80296245 148 | 149 | 150 | ## 为什么选择netty。 151 | 1. API使用简单,开发门槛低; 152 | 2. 功能强大,预置了多种编解码功能,支持多种主流协议; 153 | 3. 定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展; 154 | 4. 性能高,通过与其他业界主流的NIO框架对比,Netty的综合性能最优; 155 | 5. 成熟、稳定,Netty修复了已经发现的所有JDK NIO BUG,业务开发人员不需要再为NIO的BUG而烦恼; 156 | 6. 社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时,更多的新功能会加入; 157 | 7. 经历了大规模的商业应用考验,质量得到验证。在互联网、大数据、网络游戏、企业应用、电信软件等众多行业得到成功商用, 158 | 证明了它已经完全能够满足不同行业的商业应用了。 159 | 160 | 161 | ## 什么是TCP粘包,拆包。解决方式是什么。 162 | 163 | 客户端在发送数据包的时候,每个包都固定长度,比如1024个字节大小,如果客户端发送的数据长度不足1024个字节, 164 | 则通过补充空格的方式补全到指定长度; 165 | 166 | 客户端在每个包的末尾使用固定的分隔符,例如\r\n,如果一个包被拆分了,则等待下一个包发送过来之后找到其中的\r\n, 167 | 然后对其拆分后的头部部分与前一个包的剩余部分进行合并,这样就得到了一个完整的包; 168 | 169 | 将消息分为头部和消息体,在头部中保存有当前整个消息的长度,只有在读取到足够长度的消息之后才算是读到了一个完整的消息; 170 | 通过自定义协议进行粘包和拆包的处理。 171 | 172 | 173 | ## netty是如何解决粘包的? 174 | 175 | - FixedLengthFrameDecoder 176 | 对于使用固定长度的粘包和拆包场景,可以使用FixedLengthFrameDecoder,该解码一器会每次读取固定长度的消息, 177 | 如果当前读取到的消息不足指定长度,那么就会等待下一个消息到达后进行补足。 178 | 179 | - LineBasedFrameDecoder与DelimiterBasedFrameDecoder 180 | 对于通过分隔符进行粘包和拆包问题的处理,Netty提供了两个编解码的类,LineBasedFrameDecoder和 181 | DelimiterBasedFrameDecoder。这里LineBasedFrameDecoder的作用主要是通过换行符,即\n或者\r\n对数据进行处理; 182 | 而DelimiterBasedFrameDecoder的作用则是通过用户指定的分隔符对数据进行粘包和拆包处理。 183 | 184 | 185 | - LengthFieldBasedFrameDecoder与LengthFieldPrepender 186 | 这里LengthFieldBasedFrameDecoder与LengthFieldPrepender需要配合起来使用,其实本质上来讲,这两者一个是解码, 187 | 一个是编码的关系。它们处理粘拆包的主要思想是在生成的数据包中添加一个长度字段,用于记录当前数据包的长度。 188 | 189 | - 自定义粘包与拆包器 190 | 对于粘包与拆包问题,其实前面三种基本上已经能够满足大多数情形了,但是对于一些更加复杂的协议,可能有一些定制化的需求。 191 | 对于这些场景,其实本质上,我们也不需要手动从头开始写一份粘包与拆包处理器,而是通过继承LengthFieldBasedFrameDecoder 192 | 和LengthFieldPrepender来实现粘包和拆包的处理。 193 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /JVM虚拟机.md: -------------------------------------------------------------------------------- 1 | - 标★号为重要知识点 2 | 3 | ## ★JVM回收算法和回收器,CMS采用哪种回收算法,怎么解决内存碎片问题? 4 | CMD采用的是标记-清除算法。会导致内存碎片。 5 | 可打开-XX:+UseCMSCompactAtFullCollection开关参数(默认打开)在进行Full GC之前整理内存碎片(称为“压缩”); 6 | 使用-XX:CMSFullGCsBeforeCompaction参数(默认0)设置多少次不带压缩的Full CG之后才进行一次带压缩的Full GC。 7 | 内存整理无法并行,还需要STW,需要适当调整内存整理的频率,在GC性能与空间利用率之间平衡。 8 | 9 | ## ★哪些情况会导致Full GC? 10 | 老年代满、永久代满、CMS回收失败、从新生代要放入老年代的对象超过剩余空间。 11 | 12 | ## ★JVM内存区域如何划分? 13 | 14 | - 内存区域只是一个划分规范,并不是所有虚拟机都是按照这样做的 15 | 16 | - 最新的java8内存模型为:程序计数器、本地方法栈、java虚拟机栈、堆。以及放置在本地内存的元数据区。元数据区即java7的永久代。 17 | 18 | - 堆:Java中的堆是用来存储对象本身的以及数组(当然,数组引用是存放在Java栈中的),是Java垃圾收集器管理的主要区域。堆是被所有线程共享的,在JVM中只有一个堆。 19 | 20 | - 虚拟机栈:虚拟机栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表、操作数栈、指向当前方法所属的类的运行时常量池的引用、方法返回地址和一些额外的附加信息。当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。 21 | 22 | - 本地方法栈:本地方法栈则是为执行本地方法(Native Method)服务的,在HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一 23 | 24 | - 方法区:方法区与堆一样,是被线程共享的区域。方法区存储了类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。在方法区中有一个非常重要的部分就是运行时常量池,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。当方法区无法满足内存分配需求时,则抛出OutOfMemoryError异常。在HotSpot虚拟机中,用永久代来实现方法区,将GC分代收集扩展至方法区,但是这样容易遇到内存溢出的问题。JDK1.7中,已经把放在永久代的字符串常量池移到堆中。JDK1.8撤销永久代,引入元空间。 25 | 26 | - 程序计数器(线程私有):是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为“线程私有”的内存。正在执行java方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果还是Native方法,则为空。 27 | 28 | - 直接内存:在JDK1.4中新加入的NOI类,引入了一种基于通道与缓冲区的I/O方式,它可以使用Native函数直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。 29 | 30 | 31 | ## ★介绍JVM中7个区域,然后把每个区域可能造成内存的溢出的情况说明 32 | - 程序计数器:不会出现内存溢出 33 | - 线程栈:每个线程栈需要一定的内存空间,虚拟机内存极小的情况下可能出现内存溢出。大部分情况出现stackOverFlow的异常。 34 | - 本地方法栈:一样有可能出现内存溢出。 35 | - 方法区:类信息或者静态变量过多的话会导致溢出。 36 | - 常量池:字符串过多的情况下溢出。 37 | - 直接内存区: 38 | 39 | ## ★哪些属于GC Root不正常引用或者哪些情况会出现内存泄露? 40 | - 静态集合中我们已经不需要但没有删除的数据。 41 | - 未关闭的IO流。 42 | - 内部类持有外部类,由于内部类隐式持有外部类的引用,但此时已经不再需要外部类时,外部类并未被回收。 43 | - 改变哈希值,如果在哈希集合中已存好一个对象,之后修改对象的属性导致哈希值发生改变后,就定位不到该对象了,造成内存泄露。(比如student一开始hashcode为1,修改属性之后hashcode为2,但是hash表中位置没有变,到时候remove student的时候是按hashcode为2去删除,发现删除不掉hashcode为1的位置) 44 | - 对象的生命周期超过它的使用周期。 45 | 46 | ## ★内存溢出的原因 47 | 48 | 过多使用了static变量;大量的递归或者死循环;大数据项的查询,如返回表的所有记录,应该采用分页查询。 49 | 栈过大会导致内存占用过多,频繁页交换阻碍效率。 50 | 51 | - A,HashMap,vector等容易(静态集合类), 和应用程序生命周期一样,所引用的所有对象Object也不能释放。 52 | - B,当集合类里面的对象属性被修改后,再调用remove()不起作用,hashcode值发生了改变 53 | - C,其对象add监听器,但是往往释放对象时忘记去删除这些监听器 54 | - D,各种连接记得关闭 55 | - E,内部类的引用 56 | - F,调用其他模块,对象作用参数 57 | - G,单例模式,持有外部对象引用无法收回。 58 | 59 | 内存泄露例子 60 | ``` 61 | Vector A = new Vector(); 62 | for(int i = 0; i < 100; i++){ 63 | Object o = new Object (); 64 | A.add(o); 65 | o = null; 66 | } 67 | ``` 68 | 69 | 内存溢出的例子 70 | ``` 71 | StringBuffer b = new StringBuffer (); 72 | for(int i =0; i < 100; i++){ 73 | for(int j =0; i < 100; j++){ 74 | b.append(*); 75 | } 76 | } 77 | ``` 78 | 79 | ## 内存溢出可能原因和解决。 80 | 81 | 原因可能是: 82 | - A,数据加载过多,如1次从数据库中取出过多数据 83 | - B,集合类中有对对象的引用,用完后没有清空或者集合对象未置空导致引用存在等,使得JVM无法回收 84 | - C,死循环,过多重复对象 85 | - D,第三方软件的bug 86 | - E,启动参数内存值设定的过小。 87 | 88 | 89 | 例如方法:修改JVM启动参数,加内存(-Xms,-Xmx);错误日志,是否还有其他错误;代码走查 90 | 91 | ## ★请问java中内存泄漏是什么意思?什么场景下会出现内存泄漏的情况? 92 | 93 | - 内存泄漏定义(memory leak):一个不再被程序使用的对象或变量还在内存中占有存储空间。一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。 94 | - 内存溢出(out of memory):指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。 95 | 96 | 1. 静态集合类: 97 | 98 | 如HashMap、LinkedList等等。如果这些容器为静态变量,那么它们的生命周期与程序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。简单而言,长生命周期的对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收。 99 | 100 | 2. 各种连接: 101 | 102 | 如数据库连接、网络连接和IO连接等。在对数据库进行操作的过程中,首先需要建立与数据库的连接,当不再使用时,需要调用close方法来释放与数据库的连接。只有连接被关闭后,垃圾回收器才会回收对应的对象。否则,如果在访问数据库的过程中,对Connection、Statement或ResultSet不显性地关闭,将会造成大量的对象无法被回收,从而引起内存泄漏。 103 | 104 | 3. 变量不合理的作用域: 105 | 106 | 一个变量的定义的作用范围大于其使用范围,很有可能会造成内存泄漏。另一方面,如果没有及时地把对象设置为null,很有可能导致内存泄漏的发生。 107 | 108 | 4. 内部类持有外部类: 109 | 110 | 如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露。 111 | 112 | 5. 改变哈希值: 113 | 114 | 当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄露。 115 | 116 | 6. 单例模式: 117 | 118 | 不正确使用单例模式是引起内存泄漏的一个常见问题,单例对象在初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部的引用,那么这个对象将不能被JVM正常回收,导致内存泄漏。 119 | 120 | 121 | ## ★如何避免内存泄漏 122 | 123 | 未对作废数据内存单元置为null,尽早释放无用对象的引用,使用临时变量时,让引用变量在推出活动域后自动设置为null,暗示垃圾收集器收集;程序避免用String拼接,用StringBuffer,因为每个String会占用内存一块区域;尽量少用静态变量(全局不会回收);不要集中创建对象尤其大对象,可以使用流操作;尽量使用对象池,不再循环中创建对象,优化配置;创建对象到单例getInstance中,对象无法回收被单例引用;服务器session时间设置过长也会引起内存泄漏。 124 | 125 | 126 | ## ★请写出几段可以导致内存溢出、内存泄漏、栈溢出的代码? 127 | - 内存溢出(数组中不断添加对象) 128 | - 内存泄漏 ( 静态变量引用对象、长字符串Intern、未关闭流) 129 | - 栈溢出 ( 无线递归) 130 | 131 | ## ★JVM对象的结构? 132 | 对象头(哈希值、gc年龄、一些锁相关数据)、对象实例数据、对齐填充。 133 | 134 | 135 | ## JVM双亲委派机制? 136 | - 类加载每次都递归交给父类去加载,是在加载不到才会交给下层加载 137 | - Application ClassLoader --> Extension ClassLoader --> Bootstrap ClassLoader 138 | 139 | ## 数组多大放在 JVM 老年代? 140 | -XX:PretenureSizeThreshold 参数可以设置超过这个值直接进入老年代。 141 | 142 | 或者年轻代放不下时就直接进入老年代了。 143 | 144 | ## ★GC 有环怎么处理? 145 | 从GC roots分析可达性解决循环引用的问题。 146 | 147 | 148 | 149 | ## ★如果想不被 GC 怎么办?JVM可以作为GC Root的对象有哪些? 150 | 虚拟机栈中的对象、本地方法栈中的对象、以及方法区中静态属性引用对象、常量引用对象。 151 | 152 | 153 | ## ★如果想在 GC 中生存 1 次怎么办 154 | finalize 方法重写,持有自身对象,即可逃逸一次(因finalize方法只执行一次) 155 | 156 | 157 | ## jvm 如何分配直接内存, new 对象如何不分配在堆而是栈上 158 | - -XX:+DoEscapeAnalysis : 表示开启逃逸分析 159 | - 逃逸分析指的是,在方法中创建的对象被传递出去后,就产生方法逃逸。 160 | - 而非方法逃逸的变量,有可能在编译器的优化下直接分配到栈上。 161 | 162 | ## ★类加载过程 ? 163 | 164 | #### 1.加载 165 | 通过类的全限定名查找到该类的字节码文件,将该字节码文件装载到jvm中,jvm将文件中静态字节码结构转换成 166 | 运行时动态数据结构,并在方法区生成一个定义该类的Class对象,作为方法区中该类的各种数据访问的入口。 167 | #### 2.验证 168 | 确保该类的字节码文件中所包含的信息是否符合当前虚拟机的要求,不包含有危害虚拟机的信息(主要有四种验证, 169 | 文件格式验证,元数据验证排(语义)、字节码验证(防止危害虚拟机),符号引用验证) 170 | #### 3.准备 171 | 为类变量分配内存,并设置一个初始值。被final修饰的类变量,该类型会在编译期就已经被分配并确定 172 | #### 4.解析 173 | 将常量池中符号间接引用替换成直接引用 174 | #### 5.初始化 175 | 为类变量、静态代码块进行真正初始化(赋值操作)(类的初始化顺序,如果有父类先初始化父类中类变量 176 | 和静态代码块,在初始化子类的静态变量、静态代码块’) 177 | 178 | 179 | 180 | 181 | ## JVM堆中对象是如何创建的? 182 | 当遇到new指令的时候,检查这个类是否被加载,没被加载的话加载,然后为对象分配内存空间并进行默认初始化,执行方法。 183 | 184 | ## ★eden区,Survivor区? 185 | 这两个区都是属于新生代。eden区用于存放那些刚被new出来的对象(因为大部分对象都是朝生夕死的,所以eden区的对象生命周期都比较短暂)。 186 | 187 | survivor区分为两个,一个是s1,一个是s2。存在对象的区标记为from,另外一个空的区标记为to。对象会在这两区中倒腾,所以s1这轮是from,下一次就是to。 188 | 189 | 当eden区满的时候,开始清理eden区和from区,将剩下存活的对象移入to区当中去。 190 | 191 | 192 | ## java虚拟机的主要作用? 193 | 主要作用就是解释运行java字节码程序消除平台相关性。 194 | 195 | 196 | ## ★GC中如何判断对象需要被回收? 197 | - 可达性分析:通过一些被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到GC Roots没有任何引用链相连时(即从GC Roots节点到该节点不可达),则证明该对象是不可用的。 198 | - 引用计数分析:对象每被引用一次就+1,这个规则比较简单,但是会出现两个对象互相引用。但是不可达的情况,却没有被回收。 199 | 200 | 201 | ## ★JVM内存模型是什么? 202 | - JMM(Java Memory Model) 是线程间通信的机制 。线程间共享变量存储在主内存,每个线程都有自己的本地内存,存储的是共享变量在本地的副本。 203 | - 对应于cpu中的寄存器(主内存)与高速缓存(本地内存),可以这么理解。 204 | 205 | ## ★JVM的线程模型是什么? 206 | 207 | - 内核线程(Kernel-Level Thread, KLT) 就是由操作系统内核支持的线程,内核通过操纵调度器(Scheduler)对线程进行调度。程序一般不会直接使用内核线程,而是去使用内核线程的一种高级接口-轻量级进程(Light Weight Process, LWP),轻量级进程就是我们通常意义上所讲的线程。这种轻量级进程与内核线程之间1:1的关系称为一对一的线程模型 208 | 209 | - 用户线程(User Thread, UT)指的是完全建立在用户空间的线程库上,系统内核不能感知线程存在的实现。用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。因此操作比内核线程更快速,并可以支持更大的线程数量。这种进程(不是轻量级进程)与用户线程之间1:N的关系称为一对多的线程模型。 210 | 211 | - 在早期的JVM实现当中使用的是用户线程1:N的一对多线程模型,在当时cpu的核数普遍较少。随着cpu的性能演进,核数越来越多了,如果继续采用用户线程模型的话,就很难利用cpu的多核优势。 212 | 213 | - 如果某几个JVM线程被映射到一个内核线程后,(1:N或者M:N内存模型),如果这里面的一个JVM线程发起系统调用导致内核线程阻塞,那么剩下的几个线程依旧会被阻塞。 214 | 215 | - 所以现今的JVM实现采用的是1:1的内核线程模型。 216 | 217 | 218 | ## ★JVM的最大内存限制。 219 | 220 | 首先JVM内存限制于实际的最大物理内存了 假设物理内存无限大的话 JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然可控内存空间有4GB,但是具体的操作系统会给一个限制,这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G Linux系统 下为2G-3G) 而64bit以上的处理器就不会有限制了。 221 | 222 | 223 | 但是,这里会有一个问题在默认情况下,堆大小在32G以下的话JVM中的引用会占用4个字节。这是JVM在启动的时候就已经决定了的。如果你去掉了-XX:-UseCompressedOops选项的话,当然也可以在较小的堆上使用8字节的引用(但在生产系统中这么做是毫无意义的!)。一旦堆超过了32G,你就进入到64位的世界里了,因此对象引用就只能是8字节而非4字节了。此时,Java程序的堆中平均会有20%的空间是被对象引用占据了。相当于非常浪费,而且垃圾回收时间也会很长。所以每个JVM实例分配的内存最好控制在32G以内。 224 | 225 | 226 | ## 为什么Java被称作是“平台无关的编程语言”? 227 | ``` 228 | 因为JVM针对不同的操作系统进行了的编译,编译的结果统一了对字节码文件的执行。 229 | 通俗的解释就是:有一个中国人(windows平台),还有一个日本人(linux平台),如果要与他们交流的话,我(开发者)必须 230 | 会说中国话,还得会说日本话。这时候一个英国人(JVM虚拟机)同时会中国话和日本话,而这时我让这个英国人当翻译, 231 | 我只需要会英语就行了,英国人会自动将英文翻译成中文和日文。(JVM虚拟机会自动将字节码翻译成平台能理解的操作指令)。 232 | ``` 233 | 234 | 235 | ## ★JVM加载class文件的原理机制? 236 | 1. BootstrapLoader: 237 | 238 | BootstrapLoard是用C++语言写的,它是在Java虚拟机启动后初始化的,它主要负责加载%JAVA_HOME%/jre/lib,-Xbootclasspath参数指定的路径以及%JAVA_HOME%/jre/classes中的类。rt.jar 239 | 240 | 2. ExtClassLoader: 241 | 242 | Bootstraploader加载ExtClassLoader,并且将ExtClassLoader的父加载器设置为Bootstrploader,ExtClassLoader是用Java写的,具体来说就是 sun.misc.Launcher$ExtClassLoader,ExtClassLoader主要加载%JAVA_HOME%/jre/lib/ext,此路径下的所有classes目录以及java.ext.dirs系统变量指定的路径中类库。 243 | 244 | 245 | 3. AppClassLoader: 246 | 247 | Bootstrploader加载完ExtClassLoader后,就会加载AppClassLoader,并且将AppClassLoader的父加载器指定为ExtClassLoader。 AppClassLoader也是用Java写成的,它的实现类是sun.misc.Launcher$AppClassLoader,另外我们知道ClassLoader中有个getSystemClassLoader方法,此方法返回的正是AppclassLoader.AppClassLoader主要负责加载classpath所指定的位置的类或者是jar文档,它也是Java程序默认的类加载器。 248 | 249 | 250 | 双亲委派机制的工作流程: 251 | 252 | 1. 当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。 253 | 254 | 2. 当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrp ClassLoader. 255 | 256 | 3. 当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。 257 | 258 | 259 | 260 | ## ★minor gc如果运行的很频繁,可能是什么原因引起的,minor gc如果运行的很慢,可能是什么原因引起的? 261 | 频繁的原因: 262 | 263 | - 新生代的内存空间分配过小。 264 | - 程序中new太多声明周期短的对象。 265 | - threshold值太高,新生代中的对象迟迟不进入老年代,使得一直占用新生代空间。 266 | 267 | 很慢的原因: 268 | 269 | - 新生代内存空间太大,扫描时间过长。 270 | - 对象引用链较长,进行可达性分析时间较长。 271 | - 新生代survivor区设置的比较小,清理后剩余的对象不能装进去需要移动到老年代,造成移动开销。 272 | - 内存分配担保失败,由minor gc转化为full gc。 273 | - 采用的垃圾收集器效率较低,比如新生代使用serial收集器。 274 | 275 | ## ★频繁GC问题或内存溢出问题,如何定位? 276 | 277 | - GC:GC前后进行dump, dump出来后分析到底是哪些大对象造成full GC。 278 | - 内存溢出: 在内存溢出的时候进行Dump 279 | 280 | 281 | ## ★JVM垃圾收集器有哪些? 282 | 283 | ##### 新生代中 284 | - Serial (Client,复制,串行) 285 | - parNew(serial的并行版本,可和CMS配合) 286 | - parallel Scavenge(并行回收,致力于吞吐量,不适合交互频繁的服务器) 287 | 288 | ##### 老年代中 289 | - serial old(跟serial差不多,区别是使用标记整理,适合Client) 290 | - parallel old(和parallel Scavenge差不多,区别是标记整理,不适合交互频繁的服务器) 291 | - cms(低停顿,并发收集,标记两次,适合Server) 292 | 293 | ##### 不分新生代和老年代 294 | G1(将内存分为一个一个region,性能良好,低停顿,双清理算法,适合Server) 295 | 296 | 297 | 298 | ## ★简述GC算法 299 | 300 | GC算法分成四种: 301 | 302 | - 标记清除算法:首先先标记,然后统一把标记的对象依次清除,缺点是CPU消耗大,极易出现内存碎片,所以一般用于老年代。 303 | - 复制算法:把内存区域分成俩块,每次只使用其中一块,然后把还存活的对象放在另一块中,清空原先的块,这样的话不会出现内存碎片。新生代常用的。 304 | - 复制整理:指针碰撞,将使用过的对象移动到内存的一段,不用的放在另一端。 305 | - 分代收集:根据不同代的区别,使用符合不同代的算法。 306 | 307 | - 简单来说minorGC发生在新生代,gc频繁而且需要开销小,如果采用整理算法的话,频繁整理效率低,所以采取复制算法。 308 | - 老年代:对象相较于新生代gc不频繁且对象少,采取标记清除或者标记整理算法。 309 | 310 | ## 垃圾回收算法的实现原理。 311 | 1.复制算法 312 | 复制算法将可用内存按容量划分为相等的两部分,然后每次只使用其中的一块,当一块内存用完时,就将还存活的对象复制到第二块内存上,然后一次性清楚完第一块内存,再将第二块上的对象复制到第一块。但是这种方式,内存的代价太高,每次基本上都要浪费一半的内存。 313 | 2.标记清除算法 314 | 是JVM垃圾回收算法中最古老的一个,该算法共分成两个阶段,第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,清除未被标记的对象。该算法的缺点是需要暂停整个应用,并且在回收以后未使用的空间是不连续,即内存碎片,会影响到存储。 315 | 3.标记整理算法 316 | 此算法结合了标记-清楚算法和复制算法的优点,也分为两个阶段,第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题,按顺序排放,同时解决了复制算法所需内存空间过大的问题。 317 | 4.分代收集 318 | 分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),在堆区之外还有一个代就是永久代(Permanet Generation)。老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。 319 | 320 | ## ★GC是什么? 为什么要有GC? 321 | - GC即垃圾回收,回收的是不再使用的对象的内存空间。 322 | - 在java语言当中封装了内存分配的操作,程序员不需要关心开辟内存空间和释放内存空间。(C++语言就需要)就可以把更多精力放在与内存无关的编码上。 323 | - 所以java语言需要设计一套方法用于回收程序中不再使用的对象的内存空间,即GC。 324 | 325 | 326 | ## 垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收? 327 | - 通过可达性分析,回收确定不可达的对象的内存空间。 328 | - 不能,调用System.gc()并不一点执行。 329 | - System.gc()可以通知虚拟机进行垃圾回收,但不保证执行。 330 | 331 | ## ★运行时异常与受检查异常有何异同? 332 | - 检查异常是在程序中最经常碰到异常,所有继承自Exception并且不是运行时异常的异常都是检查异常,比如咱们最常见的IO异常和SQL异常。这种异常都发生在编译的阶段,Java编译器强制程序去捕获此类型的异常,即它会把可能会出现这些异常的代码放到try块中,把对异常的处理代码放到catch块中。受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。 333 | 334 | - 运行时异常不同于检查异常,编译器没有强制对其进行捕获并处理,如果不对异常进行处理,那么当出现这种异常的时候,会由JVM来处理,比如NullPointerException异常,它就是运行时异常。只要程序设计得没有问题通常就不会发生运行时异常。 335 | 336 | 以下是一些关于异常的优良实践(出自Effective Java) 337 | * 不要将异常处理用于正常的控制流(设计良好的API不应该强迫它的调用者为了正常的控制流而使用异常) 338 | * 对可以恢复的情况使用受检异常,对编程错误使用运行时异常 339 | * 避免不必要的使用受检异常(可以通过一些状态检测手段来避免异常的发生) 340 | * 优先使用标准的异常 341 | * 每个方法抛出的异常都要有文档 342 | * 保持异常的原子性 343 | * 不要在catch中忽略掉捕获到的异常 344 | 345 | 346 | ## ★解释内存中的栈(stack)、堆(heap)和静态区(static area)的用法。 347 | 通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用内存中的栈空间;而通过new关键字和构造器创建的对象放在堆空间;程序中的字面量(literal)如直接书写的100、"hello"和常量都是放在静态区中。栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,理论上整个内存没 348 | 有被其他进程使用的空间甚至硬盘上的虚拟内存都可以被当成堆空间来使用。 349 | 350 | ## ★JVM是如何分代的? 351 | java堆,分新生代老年代,新生代有Eden,from surviver,to surviver三个空间,堆被所有线程共。eden内存不足时,发生一次minor GC,会把from survivor和eden的对象复制到to survivor,这次的to survivor就变成了下次的from survivor,经过多次minor GC,默认15次,达到次数的对象会从survivor进行老年代。1次new如果新生代装不下,则直接进入老年代。还有动态年龄判定。 年龄从小到大的累加和超过survivor一半的话,大于或者等于这个最大年龄的直接进入老年代。 352 | 353 | 354 | 堆的年轻代大则老年代小,GC少,但是每次时间会比较长。年轻代小则老年代大,会缩短每次GC的时间,但是次数频繁。可以让老年代尽量缓存常用对象,JVM默认年轻代和老年代的大小比例为1:2,。观察峰值老年代内存,不影响full GC,加大老年代可调1:1,但是要给老年代预留三分之一的空间。减少使用全局变量和大对象 ,调整新生代,老年代到最合适。 355 | 356 | 357 | ## ★jvm YGC和FGC发生的具体场景? 358 | 359 | YGC :对新生代堆进行gc。频率比较高,因为大部分对象的存活寿命较短,在新生代里被回收。性能耗费较小。 360 | FGC :全堆范围的gc。默认堆空间使用到达80%(可调整)的时候会触发fgc。以我们生产环境为例,一般比较少会触发fgc,有时10天或一周左右会有一次。 361 | 362 | YGC发生场景:edn空间不足 363 | FGC发生场景:old空间不足,perm空间不足,调用方法System.gc() ,ygc时的悲观策略, dump live的内存信息时(jmap –dump:live) 364 | 365 | 366 | ## ★Java 8的内存分代改进 367 | - 方法区是虚拟机规范,而永久代是HotSpot的实现,其他虚拟机可能没有永久代这个概念。 368 | - 在java8中将永久代从虚拟机堆内存移到了本地内存。称之为元空间。 369 | - 不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。(字符串常量移至java堆中)因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过参数来指定元空间的大小。 370 | - 为什么要将永久代替换成Metaspace?可能的原因有: 371 | 1. 字符串存在永久代中,容易出现性能问题和内存溢出。 372 | 2. 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。 373 | 3. 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。 374 | 4. Oracle 可能会将HotSpot 与 JRockit 合二为一。 375 | 376 | 377 | 378 | ## ★新生代和老生代的内存回收策略 379 | 复制算法(新生代算法): 380 | 381 | 382 | - 复制算法是针对Java堆中的新生代内存垃圾回收所使用的回收策略,解决了”标记-清理”的效率问题。 383 | - 复制算法将堆中可用的新生代内存按容量划分成大小相等的两块内存区域,每次只使用其中的一块区域。当其中一块内存区域需要进行垃圾回收时,会将此区域内还存活着的对象复制到另一块上面,然后再把此内存区域一次性清理掉。这样做的好处是每次都是对整个新生代一半的内存区域进行内存回收,内存分配时也就不需要考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配即可。此算法实现简单,运行高效。 384 | 385 | 新生代的对象大多数gc完没剩多少,没必要使用整理算法 386 | 387 | 标记整理算法(老年代回收算法): 388 | 389 | - 复制算法在对象存活率较高的老年代会进行很多次的复制操作,效率很低,所以在栈的老年代不适用复制算法。 390 | - 针对老年代对象存活率高的特点,提出了一种称之为”标记-整理算法”。标记过程仍与”标记-清除”过程一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。 391 | - 有一个参数控制对象的年龄多少进入老年代,还有一个参数是控制对象多大直接进入老年代 392 | 393 | 老年代的对象大多回收比较少,如果采用复制算法的话,复制效率低。 394 | 395 | 396 | ## ★JVM的编译优化 397 | - 语言无关的经典优化技术之一:公共子表达式消除。 398 | - 语言相关的经典优化技术之一:数组范围检查消除。 399 | - 最重要的优化技术之一:方法内联。 400 | - 最前沿的优化技术之一:逃逸分析。(栈上分配,标量替换,消除同步) 401 | 402 | 403 | ## 指令重排序,内存栅栏等 404 | - 大多数现代微处理器都会采用将指令乱序执行(out-of-order execution,简称OoOE或OOE)的方法,在条件允许的情况下,直接运行当前有能力立即执行的后续指令,避开获取下一条指令所需数据时造成的等待。 405 | - 通过乱序执行的技术,处理器可以大大提高执行效率。除了处理器,常见的Java运行时环境的JIT编译器也会做指令重排序操作,即生成的机器指令与字节码指令顺序不一致。 406 | 407 | - 内存屏障(Memory Barrier,或有时叫做内存栅栏,Memory Fence)是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。Java编译器也会根据内存屏障的规则禁止重排序。java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序。 408 | - StoreLoad Barriers是一个“全能型”的屏障,它同时具有其他三个屏障的效果。现代的多处理器大都支持该屏障(其他类型的屏障不一定被所有处理器支持)。执行该屏障开销会很昂贵,因为当前处理器通常要把写缓冲区中的数据全部刷新到内存中(buffer fully flush)。 409 | 410 | 411 | ## JVM常用参数 412 | https://cloud.tencent.com/developer/article/1198524 413 | 一、堆设置 414 | ``` 415 | -Xms:初始堆大小 416 | -Xmx:最大堆大小 417 | -XX:NewSize=n:设置年轻代大小 418 | -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4 419 | -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2, 420 | 一个Survivor区占整个年轻代的1/5 421 | -XX:MaxPermSize=n:设置持久代大小 422 | ``` 423 | 424 | 二、收集器设置 425 | ``` 426 | -XX:+UseSerialGC:设置串行收集器 427 | -XX:+UseParallelGC:设置并行收集器 428 | -XX:+UseParalledlOldGC:设置并行年老代收集器 429 | -XX:+UseConcMarkSweepGC:设置并发收集器 430 | ``` 431 | 432 | 三、垃圾回收统计信息 433 | ``` 434 | -XX:+PrintGC 435 | -XX:+PrintGCDetails 436 | -XX:+PrintGCTimeStamps 437 | -Xloggc:filename 438 | ``` 439 | 440 | 四、并行收集器设置 441 | ``` 442 | -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。 443 | -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间 444 | -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n) 445 | ``` 446 | 五、并发收集器设置 447 | ``` 448 | -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。 449 | -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。 450 | ``` 451 | 452 | 453 | ## ★你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms和G1,包括原理,流程,优缺点。 454 | ##### CMS:以获取最短回收停顿时间为目标的收集器,基于并发“标记清理”实现 455 | 456 | ##### 过程: 457 | 458 | 1. 初始标记:独占PUC,仅标记GCroots能直接关联的对象 459 | 460 | 2. 并发标记:可以和用户线程并行执行,标记所有可达对象 461 | 462 | 3. 重新标记:独占CPU(STW),对并发标记阶段用户线程运行产生的垃圾对象进行标记修正 463 | 464 | 4. 并发清理:可以和用户线程并行执行,清理垃圾 465 | 466 | ##### 优点: 467 | 468 | 并发,低停顿 469 | 470 | 471 | ##### 缺点: 472 | 473 | 474 | 1. 对CPU非常敏感:在并发阶段虽然不会导致用户线程停顿,但是会因为占用了一部分线程使应用程序变慢 475 | 476 | 2. 无法处理浮动垃圾:在最后一步并发清理过程中,用户线程执行也会产生垃圾,但是这部分垃圾是在标记之后,所以只有等到下一次gc的时候清理掉,这部分垃圾叫浮动垃圾 477 | 478 | 3, CMS使用“标记-清理”法会产生大量的空间碎片,当碎片过多,将会给大对象空间的分配带来很大的麻烦,往往会出现老年代还有很大的空间但无法找到足够大的连续空间来分配当前对象,不得不提前触发一次FullGC,为了解决这个问题CMS提供了一个开关参数,用于在CMS顶不住,要进行FullGC时开启内存碎片的合并整理过程,但是内存整理的过程是无法并发的,空间碎片没有了但是停顿时间变长了 479 | 480 | 481 | ##### CMS 出现FullGC的原因: 482 | 483 | 484 | 1. 年轻带晋升到老年带没有足够的连续空间,很有可能是内存碎片导致的 485 | 486 | 2. 在并发过程中JVM觉得在并发过程结束之前堆就会满,需要提前触发FullGC 487 | 488 | 489 | 490 | ##### G1:是一款面向服务端应用的垃圾收集器 491 | 492 | ##### 特点: 493 | 494 | 1. 并行于并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。 495 | 496 | 2. 分代收集:分代概念在G1中依然得以保留。虽然G1可以不需要其它收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。也就是说G1可以自己管理新生代和老年代了。 497 | 498 | 3. 空间整合:由于G1使用了独立区域(Region)概念,G1从整体来看是基于“标记-整理”算法实现收集,从局部(两个Region)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片。 499 | 500 | 4. 可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用这明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。 501 | 502 | - 与其它收集器相比,G1变化较大的是它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留了新生代和来年代的概念,但新生代和老年代不再是物理隔离的了它们都是一部分Region(不需要连续)的集合。同时,为了避免全堆扫描,G1使用了Remembered Set来管理相关的对象引用信息。当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏了。 503 | 504 | 505 | 如果不计算维护Remembered Set的操作,G1收集器的运作大致可划分为以下几个步骤: 506 | 507 | 508 | 1. 初始标记(Initial Making) 509 | 510 | 2. 并发标记(Concurrent Marking) 511 | 512 | 3. 最终标记(Final Marking) 513 | 514 | 4. 筛选回收(Live Data Counting and Evacuation) 515 | 516 | 517 | 看上去跟CMS收集器的运作过程有几分相似,不过确实也这样。初始阶段仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS(Next Top Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可以用的Region中创建新对象,这个阶段需要停顿线程,但耗时很短。并发标记阶段是从GC Roots开始对堆中对象进行可达性分析,找出存活对象,这一阶段耗时较长但能与用户线程并发运行。而最终标记阶段需要吧Remembered Set Logs的数据合并到Remembered Set中,这阶段需要停顿线程,但可并行执行。最后筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划,这一过程同样是需要停顿线程的,但Sun公司透露这个阶段其实也可以做到并发,但考虑到停顿线程将大幅度提高收集效率,所以选择停顿。 518 | 519 | 520 | ## 当出现了内存溢出,你怎么排错。 521 | 使用一个参数使得内存溢出的时候生成堆快照,然后用jvisualvm等内存分析工具进行分析 522 | 523 | ## ★简单说说你了解的类加载器,可以打破双亲委派么,怎么打破。 tomcat如何实现多版本及热部署? 524 | 525 | - 双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委派父类加载器去完成。每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个请求(他的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。 526 | 为什么要这么做呢? 527 | - 如果没有使用双亲委派模型,由各个类加载器自行加载的话,如果用户自己编写了一个称为java.lang.Object的类,并放在程序的ClassPath中,那系统将会出现多个不同的Object类, Java类型体系中最基础的行为就无法保证。应用程序也将会变得一片混乱。 528 | 529 | 线程上下文类加载器可打破。tomcat为了实现类的多版本以及热部署,使用自定义的类加载器来打破双亲委托机制。 530 | ## 怎么打出线程栈信息。 531 | jstack命令 532 | 533 | ## 请解释如下jvm参数的含义: 534 | ``` 535 | -server 服务器启动模式 536 | -Xms512m 最小堆大小 537 | -Xmx512m 最大堆大小 538 | -Xss1024K 栈空间大小 539 | -XX:PermSize=256m 永久代大小 540 | -XX:MaxPermSize=512m 最大永久代大小 541 | -XX:MaxTenuringThreshold=20 晋升年龄 542 | -XX:CMSInitiatingOccupancyFraction=80 当内存达到的阈值进行gc 543 | -XX:+UseCMSInitiatingOccupancyOnly。是否一直使用最开始的阈值,后期不动态调整。 544 | ``` 545 | -------------------------------------------------------------------------------- /NoSql缓存.md: -------------------------------------------------------------------------------- 1 | ## ★redis的主从复制怎么做的? 2 | Redis主从复制可以根据是否是全量分为全量同步和增量同步。以下对其相应的同步过程及原理做下简要说明。 3 | 增量同步 4 | 5 | Redis增量同步主要指Slave完成初始化后开始正常工作时,Master发生的写操作同步到Slave的过程。通常情况下, 6 | Master每执行一个写命令就会向Slave发送相同的写命令,然后Slave接收并执行。 7 | 全量同步 8 | 9 | Redis的全量同步过程主要分三个阶段: 10 | 11 | 同步快照阶段:Master创建并发送快照给Slave,Slave载入并解析快照。Master同时将此阶段所产生的新的写命令存储到缓冲区。 12 | 同步写缓冲阶段:Master向Slave同步存储在缓冲区的写操作命令。 13 | 同步增量阶段:Master向Slave同步写操作命令。 14 | 15 | 16 | ## ★redis为什么读写速率快性能好? 17 | 18 | 纯内存操作。 19 | 核心是基于非阻塞的 IO 多路复用机制。 20 | C 语言实现,一般来说,C 语言实现的程序“距离”操作系统更近,执行速度相对会更快。 21 | 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。 22 | 23 | 24 | ## redis为什么是单线程的? 25 | - 我们要去理解为什么多线程好,多线程好在IO慢的时候才能凸显出高性能,因为在一个线程等待IO的时候马上切换线程运行。 26 | - 而在Redis中数据都在内存当中,IO非常快,如果还是采用多线程的话,反而会浪费时间在线程的上下文切换。 27 | 28 | ## 缓存的优点? 29 | 1. 减少了对数据库的读操作,数据库的压力降低 30 | 2. 加快了响应速度 31 | 32 | 33 | ## ★aof与rdb的优点和区别? 34 | 持久化RDB(redis database)和AOF(append only file)的区别 35 | 36 | RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件, 37 | 写入成功后,再替换之前的文件,用二进制压缩存储。 RDB持久化保存键空间的所有键值对(包括过期字典中的数据),并以二进制 38 | 形式保存,符合rdb文件规范,根据不同数据类型会有不同处理。 39 | 40 | AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细 41 | 的操作记录。AOF持久化保存redis服务器所执行的所有写命令来记录数据库状态,在写入之前命令存储在aof_buf缓冲区。 42 | 43 | 持久化RDB和AOF的优缺点: 44 | 45 | RDB存在哪些优势呢? 46 | (1) 一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的。比如,你可能打算每个小时 47 | 归档一次最近24小时的数据,同时还要每天归档一次最近30天的数据。通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常 48 | 容易的进行恢复。 49 | (2) 对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。 50 | (3) 性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化 51 | 的工作,这样就可以极大的避免服务进程执行IO操作了。 52 | (4) 相比于AOF机制,如果数据集很大,RDB的启动效率会更高。 53 | 54 | RDB又存在哪些劣势呢? 55 | 56 | (1) 如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。因为系统一旦在定时持久化之前出现 57 | 宕机现象,此前没有来得及写入磁盘的数据都将丢失。 58 | (2). 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒, 59 | 甚至是1秒钟。 60 | 61 | AOF的优势有哪些呢? 62 | (1) 该机制可以带来更高的数据安全性,即数据持久性。Redis中提供了3种同步策略,即每秒同步、每个命令同步和不同步。事实上, 63 | 每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每个命令 64 | 同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。至于无 65 | 同步,由操作系统决定任何将缓冲区里面的命令写入磁盘里面,在这种模式写,服务器遭遇意外停机时,丢失命令的数据是不确定的 66 | (2) 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的 67 | 内容。然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis- 68 | check-aof工具来帮助我们解决数据一致性的问题。 69 | (3) 如果日志过大,Redis可以自动启用rewrite机制。主要是为了解决aof文件不断增长减少重复键读写操作的日志, 通过定时触发, 70 | 重新根据实际内存键值情况写入新的aof文件, 再进新旧替换文件,实现aof文件最小化。 71 | (4) AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。 72 | 73 | AOF的劣势有哪些呢? 74 | (1) 对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。 75 | (2) 根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB 76 | 一样高效。 77 | 78 | 二者选择的标准,就是看系统是愿意牺牲一些性能,换取更高的缓存一致性(aof),还是愿意写操作频繁的时候,不启用备份来 79 | 换取更高的性能,待手动运行save的时候,再做备份(rdb)。rdb这个就更有些 eventually consistent的意思了。不过生产环境 80 | 其实更多都是二者结合使用的。 81 | 82 | 83 | ## redis的List能用做什么场景? 84 | 1.消息队列。2.排行榜。3.最新列表。 85 | 86 | 87 | ## 用java自己实现一个LRU。 88 | 继承LinkHashMap并重写removeEldestEntry方法。 89 | 90 | ## ★为什么用缓存,用过哪些缓存,redis和memcache的区别 91 | 92 | 使用缓存,最主要的目的是提升系统的响应速度和对高负载的承受能力。 93 | 94 | redis,memcached都有用过。 95 | 96 | 1. 数据操作不同 97 | 98 | - 与Memcached仅支持简单的key-value结构的数据记录不同,Redis支持的数据类型要丰富得多。Memcached基本只支持简单的key-value存储,不支持枚举,不支持持久化和复制等功能。Redis支持服务器端的数据操作相比Memcached来说,拥有更多的数据结构和并支持更丰富的数据操作,支持list、set、sorted set、hash等众多数据结构,还同时提供了持久化和复制等功能。 99 | - 而通常在Memcached里,使用者需要将数据拿到客户端来进行类似的修改再set回去,这大大增加了网络IO的次数和数据体积。在Redis中,这些复杂的操作通常和一般的GET/SET一样高效。所以,如果需要缓存能够支持更复杂的结构和操作, Redis会是更好的选择。 100 | 2. 内存管理机制不同 101 | - 在Redis中,并不是所有的数据都一直存储在内存中的。这是和Memcached相比一个最大的区别。当物理内存用完时,Redis可以将一些很久没用到的value交换到磁盘。Redis只会缓存所有的key的信息,如果Redis发现内存的使用量超过了某一个阀值,将触发swap的操作,Redis根据“swappability = age*log(size_in_memory)”计算出哪些key对应的value需要swap到磁盘。然后再将这些key对应的value持久化到磁盘中,同时在内存中清除。这种特性使得Redis可以保持超过其机器本身内存大小的数据。 102 | - 而Memcached默认使用Slab Allocation机制管理内存,其主要思想是按照预先规定的大小,将分配的内存分割成特定长度的块以存储相应长度的key-value数据记录,以完全解决内存碎片问题。 103 | - 从内存利用率来讲,使用简单的key-value存储的话,Memcached的内存利用率更高。而如果Redis采用hash结构来做key-value存储,由于其组合式的压缩,其内存利用率会高于Memcached。 104 | 3. 性能不同 105 | - 由于Redis只使用单核,而Memcached可以使用多核,所以平均每一个核上Redis在存储小数据时比Memcached性能更高。而在100k以上的数据中,Memcached性能要高于Redis,虽然Redis也在存储大数据的性能上进行了优化,但是比起Memcached,还是稍有逊色。 106 | 4. 集群管理不同 107 | - Memcached是全内存的数据缓冲系统,Redis虽然支持数据的持久化,但是全内存毕竟才是其高性能的本质。作为基于内存的存储系统来说,机器物理内存的大小就是系统能够容纳的最大数据量。如果需要处理的数据量超过了单台机器的物理内存大小,就需要构建分布式集群来扩展存储能力。 108 | - Memcached本身并不支持分布式,因此只能在客户端通过像一致性哈希这样的分布式算法来实现Memcached的分布式存储。相较于Memcached只能采用客户端实现分布式存储,Redis更偏向于在服务器端构建分布式存储。 109 | 110 | 111 | ## redis的持久化方式,以及项目中用的哪种,为什么 112 | - RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。 113 | - AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。 114 | 115 | - 二者优缺点 116 | - RDB存在哪些优势呢? 117 | - 1). 一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的。比如,你可能打算每个小时归档一次最近24小时的数据,同时还要每天归档一次最近30天的数据。通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复。 118 | - 2). 对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。 119 | - 3). 性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。 120 | - 4). 相比于AOF机制,如果数据集很大,RDB的启动效率会更高。 121 | - RDB又存在哪些劣势呢? 122 | - 1). 如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。 123 | - 2). 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。 124 | - AOF的优势有哪些呢? 125 | - 1). 该机制可以带来更高的数据安全性,即数据持久性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。至于无同步,无需多言,我想大家都能正确的理解它。 126 | - 2). 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决数据一致性的问题。 127 | - 3). 如果日志过大,Redis可以自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证数据安全性。 128 | - 4). AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。 129 | - AOF的劣势有哪些呢? 130 | - 1). 对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。 131 | - 2). 根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。 132 | - 二者选择的标准,就是看系统是愿意牺牲一些性能,换取更高的缓存一致性(aof),还是愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份(rdb)。rdb这个就更有些 eventually consistent的意思了。 133 | 134 | 135 | ## ★redis集群的理解,怎么动态增加或者删除一个节点,而保证数据不丢失。(一致性哈希问题) 136 | 先添加删除节点,再移动槽位 reshard,利用一致性哈希的原理保证移动槽位的时候不影响其他节点。 137 | 138 | 139 | ## ★Redis的缓存失效策略 140 | FIFO:First In First Out,先进先出。判断被存储的时间,离目前最远的数据优先被淘汰。 141 | LRU:Least Recently Used,最近最少使用。判断最近被使用的时间,目前最远的数据优先被淘汰。 142 | LFU:Least Frequently Used,最不经常使用。在一段时间内,数据被使用次数最少的,优先被淘汰。 143 | 144 | 145 | ## 缓存穿透的解决办法 146 | 导致缓存穿透的原因: 147 | 148 | 1. 恶意攻击,猜测你的key命名方式,然后估计使用一个你缓存中不会有的key进行访问。 149 | 2. 第一次数据访问,这时缓存中还没有数据,则并发场景下,所有的请求都会压到数据库。 150 | 3. 数据库的数据也是空,这样即使访问了数据库,也是获取不到数据,那么缓存中肯定也没有对应的数据。这样也会导致穿透。 151 | 解决缓存穿透: 152 | 1. 再web服务器启动时,提前将有可能被频繁并发访问的数据写入缓存。—这样就规避大量的请求在第3步出现排队阻塞。 153 | 2. 规范key的命名,并且统一缓存查询和写入的入口。这样,在入口处,对key的规范进行检测。–这样保存恶意的key被拦截。 154 | 3. Synchronized双重检测机制,这时我们就需要使用同步(Synchronized)机制,在同步代码块前查询一下缓存是否存在对应的key,然后同步代码块里面再次查询缓存里是否有要查询的key。 这样“双重检测”的目的,还是避免并发场景下导致的没有意义的数据库的访问(也是一种严格避免穿透的方案)。这一步会导致排队,但是第一步中我们说过,为了避免大量的排队,可以提前将可以预知的大量请求提前写入缓存。 155 | 4. 不管数据库中是否有数据,都在缓存中保存对应的key,值为空就行。–这样是为了避免数据库中没有这个数据,导致的平凡穿透缓存对数据库进行访问。 156 | 5. 第4步中的空值如果太多,也会导致内存耗尽。导致不必要的内存消耗。这样就要定期的清理空值的key。避免内存被恶意占满。导致正常的功能不能缓存数据。 157 | 158 | ## ★redis集群,高可用,原理 159 | https://www.cnblogs.com/telwanggs/p/10856157.html 160 | 161 | ## mySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据 162 | 即redis的缓存每命中一次,就给命中的缓存增加一定ttl(过期时间)(根据具体情况来设定, 比如10分钟). 163 | 一段时间后, 热数据的ttl都会较大, 不会自动失效, 而冷数据基本上过了设定的ttl就马上失效了. 164 | 165 | 166 | ## 用Redis和任意语言实现一段恶意登录保护的代码,限制1小时内每用户Id最多只能登录5次 167 | 使用userId为key 168 | ttl为一小时的数字值表示登录次数 169 | 超过5次即限制 170 | 171 | 172 | 173 | 174 | ## ★如何做到缓存数据一致性。 175 | 设置过期时间 加 双删 176 | 177 | ## 如何防止缓存穿透、雪崩、击穿。 178 | 一、缓存穿透 179 | 180 | 概念:缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。 181 | 182 | 解决方案: 183 | 184 | 1. 接口校验请求参数的有效性,如校验id是否合法 185 | 2. 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒。 186 | 187 | 二、缓存雪崩 188 | 189 | 概念:指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。侧重很多的缓存数据都失效了。 190 | 191 | 解决方案: 192 | 193 | 1. 设置不同的缓存到期时间,如热门数据有效期长一点,冷门数据有效期短一点。 194 | 2. 针对长期有效,数据变化基本不变的数据进行永久性缓存。 195 | 3. 缓存服务集群部署 196 | 197 | 三、缓存击穿 198 | 199 | 概念: 200 | 201 | 指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。 202 | 203 | 解决方案: 204 | 205 | 1. 热点数据实现缓存永久有效 206 | 2. 采用互斥锁来缓存数据 207 | 3. 缓存击穿的重点就是,同一时间点上,大量请求没有在缓存中命中数据,而对数据库服务产生巨大的压力,所以我们可以在请求数据库之前加上一个锁,持有锁的线程才能去请求数据库,并缓存到缓存数据库,其他的线程等待,最后从缓存获获取数据。 208 | 209 | 210 | ## redis的list结构相关的操作。 211 | list是双向链表。列表常用来作为异步队列使用 212 | 通过使用rpush、rpop、lpush、lpop四条指令,在链表的表头和表尾追加或移除元素,可以将链表作为队列或堆栈使用; 213 | 214 | 215 | 216 | ## ★redis2和redis3的区别,redis3内部通讯机制。 217 | - 哨兵的作用就是监控Redis系统的运行状况。它的功能包括以下两个。 218 | - 监控主数据库和从数据库是否正常运行。 219 | - 主数据库出现故障时自动将从数据库转换为主数据库。 220 | - sentinel发现master挂了后,就会从slave中重新选举一个master。 221 | - 哨兵模式强调高可用 222 | - Sentinel 系统用于管理多个 Redis 服务器(instance), 该系统执行以下三个任务: 223 | - 监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。 224 | - 提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。 225 | 自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。 226 | - 客户端中不会记录redis的地址(某个IP),而是记录sentinel的地址,这样我们可以直接从sentinel获取的redis地址,因为sentinel会对所有的master、slave进行监控,它是知道到底谁才是真正的master的,例如我们故障转移,这时候对于sentinel来说,master是变了的,然后通知客户端。而客户端根本不用关心到底谁才是真正的master,只关心sentinel告知的master。 227 | - 集群 228 | - 即使使用哨兵,redis每个实例也是全量存储,每个redis存储的内容都是完整的数据,浪费内存且有木桶效应。为了最大化利用内存,可以采用集群,就是分布式存储。即每台redis存储不同的内容,共有16384个slot。每个redis分得一些slot,hash_slot = crc16(key) mod 16384 找到对应slot,键是可用键,如果有{}则取{}内的作为可用键,否则整个键是可用键 229 | - 集群至少需要3主3从,且每个实例使用不同的配置文件,主从不用配置,集群会自己选。 230 | - cluster是为了解决单机Redis容量有限的问题,将数据按一定的规则分配到多台机器。 231 | - 集群模式提高并发量。 232 | 233 | ## redis和memcached 的内存管理的区别。 234 | - 在Redis中,并不是所有的数据都一直存储在内存中的。这是和Memcached相比一个最大的区别。当物理内存用完时,Redis可以将一些很久没用到的value交换到磁盘。 235 | - Memcached默认使用Slab Allocation机制管理内存,其主要思想是按照预先规定的大小,将分配的内存分割成特定长度的块以存储相应长度的key-value数据记录,以完全解决内存碎片问题。 236 | 237 | 238 | ## ★Redis的并发竞争问题如何解决,了解Redis事务的CAS操作吗。 239 | 1. 使用incr 240 | 2. 使用watch 241 | 3. 分布式锁 242 | 243 | 244 | ## Redis的选举算法和流程是怎样的。 245 | ``` 246 | 监视这个挂了的主节点的所有Sentinel都有被选举为领头的资格 247 | 每进行一次选举,不论是否成功,配置纪元+1,配置纪元就是个计数器 248 | 每个Sentinel在每个配置纪元中有且仅有一次选举机会,一旦选好了该节点认为的主节点,在这个纪元内,不可以再更改 249 | 每个发现服务器挂了的Sentinel都会配置纪元+1并投自己一票,接着发消息要求其他Sentinel设置自己为领头人1,每个Sentinel都想成为领头的 250 | 每个Sentinel会将最先发来请求领头的节点设为自己的领头节点并发送回复,谁先来我选谁 251 | 当源Sentinel收到回复,并且回复中的配置纪元和自己的一致且领头Id是自己的Sentinel Id时,表明目标Sentinel已经将自己设为领头 252 | 在一个配置纪元内,当某个Sentinel收到半数以上的同意回复时,它就是领头的了 253 | 如果在给定时间内,没有被成功选举的Sentinel,那么过段时间发起新的选举 254 | 选举领头Sentinel的过程和规则大概就如上所述,需要注意的是只有集群出现节点挂了才需要选举出领头Sentinel,平时每个Sentinel还是平等身份~ 255 | 这里的选举其实是raft算法的一个应用. 256 | ``` 257 | 258 | ## ★redis的集群怎么同步的数据的(Reids的主从复制机制原理)。 259 | 260 | 1. redis的复制功能是支持多个数据库之间的数据同步。一类是主数据库(master)一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,而从数据库一般是只读的,并接收主数据库同步过来的数据,一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。 261 | 2. 通过redis的复制功能可以很好的实现数据库的读写分离,提高服务器的负载能力。主数据库主要进行写操作,而从数据库负责读操作。 262 | 3. 当一个从数据库启动时,会向主数据库发送sync命令, 263 | 4. 主数据库接收到sync命令后会开始在后台保存快照(执行rdb操作),并将保存期间接收到的命令缓存起来 264 | 5. 当快照完成后,redis会将快照文件和所有缓存的命令发送给从数据库。 265 | 6. 从数据库收到后,会载入快照文件并执行收到的缓存的命令。 266 | 267 | 268 | ## redis优化详解 269 | https://blog.csdn.net/qq_39399966/article/details/101211939 270 | https://blog.csdn.net/achuo/article/details/80600170 271 | 272 | ## ★Redis的线程模型是什么。 273 | - Redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器。它的组成结构为4部分:多个套接字、IO多路复用程序、文件事件分派器、事件处理器。因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型。 274 | - 尽管多个文件事件可能会并发地出现,但I/O多路复用程序总是会将所有产生事件的套接字都推到一个队列里面,然后通过这个队列,以有序(sequentially)、同步(synchronously)、每次一个套接字的方式向文件事件分派器传送套接字:当上一个套接字产生的事件被处理完毕之后(该套接字为事件所关联的事件处理器执行完毕), I/O多路复用程序才会继续向文件事件分派器传送下一个套接字。 275 | 276 | 277 | ## 集中式缓存和分布式缓存? 278 | 集中式:容量,并发瓶颈,但一致性有保证。 279 | 分布式:提升容量,并发能力,但会有一致性问题。 280 | 281 | 282 | ## elasticsearch 索引数据多了怎么办,如何调优,部署。 283 | ## Elasticsearch在部署时,对Linux的设置有哪些优化方法? 284 | ## elasticsearch是如何实现master选举的。 285 | ## 详细描述一下Elasticsearch搜索的过程。 286 | 287 | 288 | 289 | 290 | ## redis和memcache的区别 291 | - redis使用单线程模型,数据顺序提交,redis支持主从模式,mencache只支持一致性hash做分布式;redis支持数据落地,rdb定时快照和aof实时记录操作命令的日志备份,memcache不支持;redis数据类型丰富,有string,hash,set,list, sort set,而memcache只支持简单数据类型;memcache使用cas乐观锁做一致性。 292 | 293 | - jedis操作Hash:hmset, hmget, hdel, hkeys 294 | 295 | - jedis操作List: lpush,lrange按照范围取出,rpush, del, sort等keyjedis操作Set:sadd,srem移除noname,smembers, sismember, scard等。summary.note 296 | 297 | - 使用场景例如 298 | 299 | - Hash:存储读取更新用户多个属性 300 | 301 | - List:微博TimeLine,消息列表 302 | 303 | - Set:共同好友,二度好友,用唯一性可以统计网站所有独立IP,好友推荐根据tag求交集,大于threshold就可以推荐。 304 | 305 | - sortset:set增加1个权重score参数 306 | 307 | - 其他场景:A订阅发布系统,redis对某个key消息发布及订阅,当1个key消息发布后,所有订阅它的客户端都会收到相应消息,例如实时消息系统,即时聊天,群聊等。 308 | 309 | - 事务-常用EX,EC提交执行的命令,在server不出问题,可以保证一连串的命令是顺序执行额;提供1个watch功能,对1个key作watch,然后再执行transation。 310 | 311 | 312 | 313 | ## redis过期淘汰策略 314 | 315 | - redis内存数据上升到一定大小会执行数据淘汰策略,redis提供了6种数据淘汰策略。 316 | 317 | - LRU:从已设置过期时间的数据集合中挑选最近最少使用的数据淘汰 318 | 319 | - random:从已设置过期时间的数据中挑选任意数据淘汰 320 | 321 | - ttl:从已设置过期时间的数据集合中挑选将要过期的数据淘汰。 322 | 323 | - notenvision:禁止驱逐数据 324 | 325 | - 如mysql中有2千万数据,redis只存储20万的热门数据。LRU或者TTL都满足热点数据读取较多,不太可能超时特点。 326 | 327 | - redis特点:速度块,O(1),丰富的数据类型,支持事物原子性,可用于缓存,比memecache速度块,可以持久化数据。 328 | 329 | - 常见问题和解决:Master最好不做持久化如RDB快照和AOF日志文件;如果数据比较重要,某分slave开启AOF备份数据,策略为每秒1次,为了主从复制速度及稳定,MS主从在同一局域网内;主从复制不要用图状结构,用单向链表更为稳定 M-S-S-S-S。。。。;redis过期采用懒汉+定期,懒汉即get/set时候检查key是否过期,过期则删除key,定期遍历每个DB,检查制定个数个key;结合服务器性能调节并发情况。 330 | 331 | - 过期淘汰,数据写入redis会附带1个有效时间,这个有效时间内该数据被认为是正确的并不关心真实情况,例如对支付等业务采用版本号实现,redis中每一份数据都维持1个版本号,DB中也维持1份,只有当redis的与DB中的版本一致时,才会认为redis为有效的,不过仍然每次都要访问DB,只需要查询version版本字段即可。 332 | 333 | 334 | ## 如何将数据分布在redis第几个库? 335 | 336 | 答:redis 本身支持16个数据库,通过 数据库id 设置,默认为0。 337 | 例如jedis客户端设置。一:JedisPool(org.apache.commons.pool.impl.GenericObjectPool.Config poolConfig, String host, int port, int timeout, String password, int database); 338 | 第一种通过指定构造函数database字段选择库,不设置则默认0库。二:jedis.select(index);调用jedis的select方法指定。 339 | 340 | 341 | ## Elasticsearch分片使用优化? 342 | 343 | (1)拆分集群 344 | 345 | 对于存在明显分界线的业务,可以按照业务、地域使用不同集群,这种拆分集群的思路是非常靠谱的。对于我们的场景,已经按照地域拆分了集群,且同一地域的子业务间分界线不明显,拆分过多的集群维护成本较高。 346 | 347 | (2)调整滚动周期 348 | 349 | 根据保留时长调整index滚动周期是最简单有效的思路。例如保留3天的数据按天滚动,保留31天的数据按周滚动,保留一年的数据按月滚动。合理的滚动周期,可以在存储成本增加不大的情况下,大幅降低分片数量。 350 | 对于我们的场景,大部分数据保留31天,在按周滚动的情况下,集群的总分片数可以下降到6.5w~个。 351 | 352 | (3)合理设置分片数和副本数 353 | 354 | 除个别子业务压力较高外,大部分业务压力较小,合理设置单Index的分片数效果也不错。我们的经验是单个分片的大小在10GB~30GB之间比较合适,对于压力非常小的业务可以直接分配1个分片。其他用户可结合具体场景考虑,同时注意单分片的记录条数不要超过上限2,147,483,519。 355 | 在平衡我们的业务场景对数据可靠性的要求 及 不同副本数对存储成本的开销 两个因素之后,我们选择使用一主一从的副本策略。 356 | 目前我们集群单Index的平均分配数为3,集群的总分片数下降到3w~个。 357 | 358 | (4)分片分配流程优化 359 | 360 | 默认情况下,ES在分配分片时会考虑分片relocation对磁盘空间的影响。在分片数较少时,这个优化处理的副作用不明显。但随着单机分片数量的上升,这个优化处理涉及的多层循环嵌套过程耗时愈发明显。可通过cluster.routing.allocation.disk.include_relocations: false关闭此功能,这对磁盘均衡程度影响不明显。 361 | 362 | (5)预创建Index 363 | 364 | 对于单集群3w分片的场景,集中在每周某天0点创建Index,对集群的压力还是较大,且存储空间存在波动。考虑到集群的持续扩展能力和可靠性,我们采用预创建方式提前创建分片,并把按Index的创建时间均匀打散到每周的每一天。 365 | 366 | (6)持续调整分片数 367 | 368 | 对于集群分片的调整,通常不是一蹴而就的。随着业务的发展,不断新增的子业务 或 原有子业务规模发生突变,都需要持续调整分片数量。 369 | 默认情况下,新增的子业务会有默认的分片数量,如果不足,会在测试阶段及上线初期及时发现。随着业务发展,系统会考虑Index近期的数据量、写入速度、集群规模等因素,动态调整分片数量。 370 | 371 | 372 | ## ElasticSearch如何解决深度分页的问题? 373 | 374 | 使用scroll(有状态)和search after(无状态)的游标方式。 375 | 376 | ## lucence倒排索引 377 | 378 | - 三个文件:字典文件,频率文件,位置文件。词典文件不仅保存有每个关键词,还保留了指向频率文件和位置文件的指针,通过指针可以找到该关键字的频率信息和位置信息。 379 | 380 | - field的概念,用于表达信息所在位置(如标题中,文章中,url中),在建索引中,该field信息也记录在词典文件中,每个关键词都有一个field信息(因为每个关键字一定属于一个或多个field)。 381 | 382 | - 关键字是按字符顺序排列的(lucene没有使用B树结构),因此lucene可以用二元搜索算法快速定位关键词。 383 | - 假设要查询单词 “live”,lucene先对词典二元查找、找到该词,通过指向频率文件的指针读出所有文章号,然后返回结果。词典通常非常小,因而,整个过程的时间是毫秒级的。    384 | 385 | - 对词典文件中的关键词进行了压缩,关键词压缩为<前缀长度,后缀>,例如:当前词为“阿拉伯语”,上一个词为“阿拉伯”,那么“阿拉伯语”压缩为<3,语>。对数字的压缩,数字只保存与上一个值的差值。 386 | 387 | 388 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Java工程师知识体系总结 2 | 3 | 4 | - 作为一名合格的Coder 5 | - 实践固然重要,但理论知识也很重要 6 | - 所以在这里梳理一下这些年学习的知识体系 7 | 8 | 9 | #### 知识体系: 10 | 11 | 12 | - [基础知识点](https://github.com/valarchie/java_technology_summary/blob/master/%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E7%82%B9.md) 13 | 14 | - [IO](https://github.com/valarchie/java_technology_summary/blob/master/IO.md) 15 | 16 | - [多线程](https://github.com/valarchie/java_technology_summary/blob/master/%E5%A4%9A%E7%BA%BF%E7%A8%8B.md) 17 | 18 | - [集合](https://github.com/valarchie/java_technology_summary/blob/master/%E9%9B%86%E5%90%88.md) 19 | 20 | - [设计模式](https://github.com/valarchie/java_technology_summary/blob/master/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md) 21 | 22 | - [JVM虚拟机](https://github.com/valarchie/java_technology_summary/blob/master/JVM%E8%99%9A%E6%8B%9F%E6%9C%BA.md) 23 | 24 | - [Web相关](https://github.com/valarchie/java_technology_summary/blob/master/Web%E7%9B%B8%E5%85%B3.md) 25 | 26 | - [操作系统](https://github.com/valarchie/java_technology_summary/blob/master/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md) 27 | 28 | - [数据库](https://github.com/valarchie/java_technology_summary/blob/master/%E6%95%B0%E6%8D%AE%E5%BA%93.md) 29 | 30 | - [计算机网络](https://github.com/valarchie/java_technology_summary/blob/master/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C.md) 31 | 32 | - [数据结构与算法](https://github.com/valarchie/java_technology_summary/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95.md) 33 | 34 | - [框架](https://github.com/valarchie/java_technology_summary/blob/master/%E6%A1%86%E6%9E%B6.md) 35 | 36 | - [架构](https://github.com/valarchie/java_technology_summary/blob/master/%E6%9E%B6%E6%9E%84.md) 37 | 38 | - [缓存](https://github.com/valarchie/java_technology_summary/blob/master/NoSql%E7%BC%93%E5%AD%98.md) 39 | 40 | #### Java基础脑图 41 | ![Java基础.png](https://img.hacpai.com/file/2020/04/Java基础-d3d9ba98.png) 42 | #### 计算机知识脑图 43 | ![计算机基础知识脑图.png](https://img.hacpai.com/file/2020/04/计算机基础知识脑图-84dba96c.png) 44 | #### 开发框架脑图 45 | ![开发框架.png](https://img.hacpai.com/file/2020/04/开发框架-84c50e1a.png) 46 | #### JVM知识脑图 47 | ![JVM知识脑图.png](https://img.hacpai.com/file/2020/04/JVM知识脑图-849b139c.png) 48 | #### 缓存知识脑图 49 | ![缓存知识脑图.png](https://img.hacpai.com/file/2020/04/缓存知识脑图-c3b30221.png) 50 | #### MQ知识脑图 51 | ![MQ知识脑图.png](https://img.hacpai.com/file/2020/04/MQ知识脑图-213d603e.png) 52 | #### 分布式知识脑图 53 | ![分布式服务知识脑图.png](https://img.hacpai.com/file/2020/04/分布式服务知识脑图-736fb6f6.png) 54 | #### ElasticSearch脑图 55 | ![ElasticSearch知识脑图.png](https://img.hacpai.com/file/2020/04/ElasticSearch知识脑图-3a73fc50.png) 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Web相关.md: -------------------------------------------------------------------------------- 1 | - 该部分内容已比较老旧 2 | 3 | ## 启动项目时如何实现不在链接里输入项目名就能启动? 4 | 修改Tomcat配置文件 server.xml。将Context标签内的path属性改为“/”即可。 5 | 6 | ## 一分钟之内只能处理1000个请求,你怎么实现,手撕代码? 7 | 每处理完一个请求,就往Application作用域内的一个LinkedBlockingQueue中添加一个时间戳。 8 | 每接收到一个请求的时候,就先判断Application作用域当中的LinkedBlockingQueue当中的size是否大于1000,判断前先remove 9 | 掉60秒之前的时间戳。 10 | 11 | 12 | ## 什么时候用assert? 13 | 在开发和测试过程中,很多时候我们需要对某些场景进行判断,当这个判断满足我们预期的答案,才继续往下执行,否则终止程序或者发出警告。 14 | 比较常用在单元测试。 15 | 16 | ## Java应用服务器有哪些? 17 | WebLogic, WebSphere, Jboss, Tomcat 18 | 19 | ## JSP内置对象及常用方法? 20 | 21 | 1. request对象。 22 | ``` 23 | resquest对象是javax.servlet.http.HttpServletRequest类的一个实例。客户端的请求信息封装在resquest中发送给服务器端。 24 | request的作用域是一次请求。 25 | 请求方式:request.getMethod() 26 | 请求的资源:request.getRequestURI() 27 | 请求用的协议:request.getProtocol() 28 | 请求的文件名:request.getServletPath() 29 | 请求的服务器的IP:request.getServerName() 30 | 请求服务器的端口:request.getServerPort() 31 | 客户端IP地址:request.getRemoteAddr() 32 | 客户端主机名:request.getRemoteHost() 33 | ``` 34 | 35 | 2. response对象。 36 | ``` 37 | response对象是javax.servlet.http.HttpServletResponse的一个实例。服务端的相应信息封装在response中返回。 38 | 重定向客户端请求 response.sendRedirect(index.jsp) 39 | ``` 40 | 41 | 3. session对象。 42 | ``` 43 | session对象是javax.servlet.http.HttpSession的一个实例。在第一个JSP页面被装载时自动创建,完成会话期管理。 44 | session对象内部使用Map类来保存数据,因此保存数据的格式为 “Key/value”。 45 | 获取Session对象编号 session.getId() 46 | 添加obj到Session对象 session.setAttribute(String key,Object obj) 47 | 获取Session值 session.getAttribute(String key) 48 | ``` 49 | 50 | 4. application对象。 51 | ``` 52 | application对象是javax.servlet.ServletContext的一个实例。 53 | 实现了用户间数据的共享,可存放全局变量。它开始于服务器的启动,直到服务器的关闭,在此期间,此对象将一直存在。 54 | 添加obj到Application对象 application.setAttribute(String key,Object obj) 55 | 获取Application对象中的值 application.getAttribute(String key) 56 | ``` 57 | 58 | 5. out 对象。 59 | ``` 60 | out对象是javax.servlet.jsp.jspWriter的一个实例。用于浏览器输出数据。 61 | 输出各种类型数据 out.print() 62 | 输出一个换行符 out.newLine() 63 | 关闭流 out.close() 64 | ``` 65 | 66 | 6. pageContext 对象。 67 | ``` 68 | pageContext 对象是javax.servlet.jsp.PageContext的一个对象。作用是取得任何范围的参数,通过它可以获取 JSP页面的out、request、reponse、session、application 等对象。 69 | ``` 70 | 71 | 7. config 对象。 72 | ``` 73 | config 对象是javax.servlet.ServletConfig的一个对象。主要作用是取得服务器的配置信息。通过 pageConext对象的 74 | getServletConfig() 方法可以获取一个config对象。 75 | ``` 76 | 77 | 8. cookie 对象。 78 | ``` 79 | cookie 对象是Web服务器保存在用户硬盘上的一段文本。唯一的记录了用户的访问信息。 80 | 将Cookie对象传送到客户端 Cookie c = new Cookie(username",john"); 81 | 读取保存到客户端的Cookie response.addCookie(c) 82 | ``` 83 | 84 | 9. exception 对象。 85 | ``` 86 | exception 对象的作用是显示异常信息,只有在包含 isErrorPage="true" 的页面中才可以被使用。 87 | ``` 88 | 89 | ## JSP的四个域对象的作用范围? 90 | application、session、request、page 91 | 92 | ## JSP 和 Servlet 有哪些相同点和不同点,他们之间的联系是什么? 93 | jsp和servlet的区别和联系: 94 | 1.jsp经编译后就变成了Servlet.(JSP的本质就是Servlet,JVM只能识别java的类,不能识别JSP的代码,Web容器将JSP的代码编译成 95 | JVM能够识别的java类) 96 | 2.jsp更擅长表现于页面显示,servlet更擅长于逻辑控制. 97 | 3.Servlet中没有内置对象,Jsp中的内置对象都是必须通过HttpServletRequest对象,HttpServletResponse对象以及HttpServlet 98 | 对象得到. 99 | Jsp是Servlet的一种简化,使用Jsp只需要完成程序员需要输出到客户端的内容,Jsp中的Java脚本如何镶嵌到一个类中,由Jsp容器 100 | 完成。而Servlet则是个完整的Java类,这个类的Service方法用于生成对客户端的响应。 101 | 联系:JSP是Servlet技术的扩展,本质上就是Servlet的简易方式。JSP编译后是“类servlet”。Servlet和JSP最主要的不同点在于, 102 | Servlet的应用逻辑是在Java文件中,并且完全从表示层中的HTML里分离开来。而JSP的情况是Java和HTML可以组合成一个扩展名为 103 | .jsp的文件。JSP侧重于视图,Servlet主要用于控制逻辑 104 | 105 | ## ★四种会话跟踪技术 106 | 107 | 1. 隐藏表单域:,非常适合步需要大量数据存储的会话应用。 108 | 2. URL重写: URL可以在后面附加参数抛回给客户端,客户端在下一次请求携带回给服务器。 109 | 3. Cookie:一个 Cookie 是一个小的,已命名数据元素。服务器使用 SET-Cookie 头标将它作为 HTTP 110 | 响应的一部分传送到客户端,客户端被请求保存 Cookie 值,在对同一服务器的后续请求使用一个 111 | Cookie 头标将之返回到服务器。与其它技术比较,Cookie 的一个优点是在浏览器会话结束后,甚至 112 | 在客户端计算机重启后它仍可以保留其值。 113 | 4. Session:使用 setAttribute(String str,Object obj)方法将对象捆绑到一个会话 114 | 115 | ## 说说weblogic中一个Domain的缺省目录结构?比如要将一个简单的helloWorld.jsp放入何目录下,然后在浏览器上就可打入主机? 116 | Domain目录\服务器目录\applications,将应用目录放在此目录下将可以作为应用访问,如果是web应用,应用目录需要满足Web应用 117 | 目录要求,jsp文件可以直接放在应用目录中,JavaBean需要放在应用目录的WEB-INF目录的classes目录中,设置服务器的缺省应用将 118 | 可以实现在浏览器上无需输入应用名。 119 | 120 | ## jsp有哪些动作?作用分别是什么? 121 | JSP 共有以下6种基本动作 122 | - jsp:include:在页面被请求的时候引入一个文件。 123 | - jsp:useBean:寻找或者实例化一个JavaBean。 124 | - jsp:setProperty:设置JavaBean的属性。 125 | - jsp:getProperty:输出某个JavaBean的属性。 126 | - jsp:forward:把请求转到一个新的页面。 127 | - jsp:plugin:根据浏览器类型为Java插件生成OBJECT或EMBED标记。 128 | 129 | ## 说一下表达式语言(EL)的隐式对象及其作用 130 | 131 | EL的隐式对象包括:pageContext、initParam(访问上下文参数)、param(访问请求参数)、paramValues、header(访问请求头)、 132 | headerValues、cookie(访问cookie)、applicationScope(访问application作用域)、sessionScope(访问session作用域)、 133 | requestScope(访问request作用域)、pageScope(访问page作用域)。 134 | 135 | 用法如下所示: 136 | - ${pageContext.request.method} 137 | - ${pageContext["request"]["method"]} 138 | - ${pageContext.request["method"]} 139 | - ${pageContext["request"].method} 140 | - ${initParam.defaultEncoding} 141 | - ${header["accept-language"]} 142 | - ${headerValues["accept-language"][0]} 143 | - ${cookie.jsessionid.value} 144 | - ${sessionScope.loginUser.username} 145 | 146 | ## 请说明一下JSP中的静态包含和动态包含的有哪些区别? 147 | 148 | 1.两者格式不同,静态包含:<%@ include file="文件" %>,而动态包含:。 149 | 2.包含时间不同,静态包含是先将几个文件合并,然后再被编译,缺点就是如果含有相同的标签,会出错。动态包含是页面被请求时编译, 150 | 将结果放在一个页面。 151 | 3.生成的文件不同,静态包含会生成一个包含页面名字的servlet和class文件;而动态包含会各自生成对应的servlet和class文件 152 | 4.传递参数不同,动态包含能够传递参数,而静态包含不能 153 | 154 | 155 | ## ★过滤器有哪些作用和用法? 156 | Java Web开发中的过滤器(filter)是从Servlet 2.3规范开始增加的功能,并在Servlet 2.4规范中得到增强。对Web应用来说, 157 | 过滤器是一个驻留在服务器端的Web组件,它可以截取客户端和服务器之间的请求与响应信息,并对这些信息进行过滤。当Web容器 158 | 接受到一个对资源的请求时,它将判断是否有过滤器与这个资源相关联。如果有,那么容器将把请求交给过滤器进行处理。在过滤器中, 159 | 你可以改变请求的内容,或者重新设置请求的报头信息,然后再将请求发送给目标资源。当目标资源对请求作出响应时候,容器同样会 160 | 将响应先转发给过滤器,在过滤器中你可以对响应的内容进行转换,然后再将响应发送到客户端。 161 | 162 | 常见的过滤器用途主要包括:对用户请求进行统一认证、对用户的访问请求进行记录和审核、对用户发送的数据进行过滤或替换、 163 | 转换图象格式、对响应内容进行压缩以减少传输量、对请求或响应进行加解密处理、触发资源访问事件、对XML的输出应用XSLT等。 164 | 165 | ## ★拦截器与过滤器的区别? 166 | 拦截器基于反射,过滤器基于函数回调。 167 | 拦截器依赖于controller,过滤器依赖于servlet。 168 | 总而言之就是请求进来 fileter-->servlet-->inteceptor-->controller 169 | 170 | 171 | ## javaweb当中的监听器的作用? 172 | 就是对项目起到监听的作用,它能感知到包括request(请求域),session(会话域)和applicaiton(应用程序)的初始化和属性的变化, 173 | 活动在整个request和response周期中;其实监听器就是一种观察者模式。Servlet监听器用于监听一些重要事件的发生,监听器对象 174 | 可以在事情发生前、发生后可以做一些必要的处理。下面将介绍几种常用的监听器 175 | 176 | 分类及介绍: 177 | ServletContextListener:用于监听WEB 应用启动和销毁的事件,监听器类需要实现javax.servlet.ServletContextListener 接口。 178 | ServletContextAttributeListener:用于监听WEB应用属性改变的事件,包括:增加属性、删除属性、修改属性,监听器类需要实现 179 | javax.servlet.ServletContextAttributeListener接口。 180 | HttpSessionListener:用于监听Session对象的创建和销毁,监听器类需要实现javax.servlet.http.HttpSessionListener接口或者 181 | javax.servlet.http.HttpSessionActivationListener接口,或者两个都实现。 182 | HttpSessionActivationListener:用于监听Session对象的钝化/活化事件,监听器类需要实现javax.servlet.http. 183 | HttpSessionListener接口或者javax.servlet.http.HttpSessionActivationListener接口,或者两个都实现。(没用过) 184 | HttpSessionAttributeListener:用于监听Session对象属性的改变事件,监听器类需要实现 185 | javax.servlet.http.HttpSessionAttributeListener接口。 (没用过) 186 | 187 | ## web.xml文件中可以配置哪些内容? 188 | web.xml用于配置Web应用的相关信息,如:监听器(listener)、过滤器(filter)、 Servlet、相关参数、会话超时时间、 189 | 安全验证方式、错误页面等。 190 | 191 | 192 | ## ★forward与redirect区别,说一下你知道的状态码,redirect的状态码是多少? 193 | forward是服务器内部转发,redirect是服务器让客户端转发。redirect的状态码是301/302. 194 | - 200:表示请求已成功,请求所希望的响应头或数据体将随此响应返回 195 | - 202:服务器已接受请求,但尚未处理 196 | - 301:被请求的资源已永久移动到新位置 197 | - 302:请求的资源临时从不同的URI响应请求,但请求者应继续使用原有位置来进行以后的请求。一般重定向都是这个状态码。 198 | - 304:自从上次请求后,请求的网页未修改过。如jq文件,logo 199 | - 403:服务器已经理解请求,但是拒绝执行它 200 | - 404:请求失败,请求所希望得到的资源未被在服务器上发现 201 | - 500:服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。一般是服务器的程序码出错时出现 202 | - 502: 作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。例如Nginx转发请求后,无法 203 | 收到或理解响应时,返回的状态码。 204 | - 503:由于临时的服务器维护或者过载,服务器暂时无法处理请求 205 | - 504: 作为网关或者代理工作的服务器尝试执行请求时,未能及时从上游服务器收到响应。例如当Nginx超过配置的超时时间 206 | 还没有收到响应时,就返回504错误。 207 | 208 | 209 | ## ★转发和重定向有什么区别? 210 | 转发是服务端的行为,重定向是客户端的行为。 211 | 212 | ## servlet生命周期,是否单例,为什么是单例。 213 | 生命周期:加载并实例化、初始化、请求处理、销毁。 是单例。为了有效利用JVM允许多个线程访问同一个实例的特性, 214 | 来提高服务器性能。在非分布式系统中,Servlet容器只会维护一个Servlet的实例。 215 | 216 | 217 | ## 什么是Servlet? 218 | servlet是运行于tomcat容器或其他servlet容器中的WEB组件,用于处理请求和响应请求。所以我们就不需要关心 219 | 如何接受请求和响应请求,只需要关心于我们的业务逻辑。 220 | 221 | ## 说出Servlet的生命周期,并说出Servlet和CGI的区别。 222 | Servlet 生命周期 223 | 1、加载 2、实例化 3、初始化 4、处理请求 5、销毁 224 | 区别: 225 | Servlet处于服务器进程中,它通过多线程方式运行其service方法,一个实例可以服务于多个请求,并且其实例一般不会销毁, 226 | 而CGI对每个请求都产生新的进程,服务完成后就销毁,所以效率上低于servlet。 227 | 228 | 229 | ## Servlet执行时一般实现哪几个方法? 230 | - public void init(ServletConfig config) 231 | - public ServletConfig getServletConfig() 232 | - public String getServletInfo() 233 | - public void service(ServletRequest request,ServletResponse response) 234 | - public void destroy() 235 | 236 | 237 | ## Servlet 3中的异步处理指的是什么? 238 | 不阻塞用户请求,请求立即得到响应,而处理结果在完成之后再返回给用户。 239 | 240 | ## 服务器收到用户提交的表单数据,到底是调用Servlet的doGet()还是doPost()方法? 241 | 根据用户请求的方法是Get还是POST。 242 | 243 | ## MVC 的各个部分都有那些技术来实现?如何实现? 244 | MVC 是 Model-View-Controller 的简写。 Model 代表的是应用的业务逻辑( 通过JavaBean, 245 | EJB 组件实现), View 是应用的表示面( 由 JSP 页面产生), Controller 是提供应用的处理 246 | 过程控制( 一般是一个 Servlet), 通过这种设计模型把应用逻辑, 处理过程和显示逻辑分成不同 247 | 的组件实现。 这些组件可以进行交互和重用。 248 | 249 | 250 | ## 什么是DAO模式? 251 | 252 | DAO 模式: 253 | DAO (DataAccessobjects 数据存取对象)是指位于业务逻辑和持久化数据之间实现对持久化数据的访问。 254 | 通俗来讲,就是将数据库操作都封装起来。 255 | 256 | 对外提供相应的接口 257 | 在面向对象设计过程中,有一些"套路”用于解决特定问题称为模式。 258 | 259 | DAO 模式提供了访问关系型数据库系统所需操作的接口,将数据访问和业务逻辑分离对上层提供面向对象的数据访问接口。 260 | 261 | 从以上 DAO 模式使用可以看出,DAO 模式的优势就在于它实现了两次隔离。 262 | 263 | 264 | 1、隔离了数据访问代码和业务逻辑代码。业务逻辑代码直接调用DAO方法即可,完全感觉不到数据库表的存在。 265 | 分工明确,数据访问层代码变化不影响业务逻辑代码,这符合单一职能原则,降低了耦合性,提高了可复用性。 266 | 2、隔离了不同数据库实现。采用面向接口编程,如果底层数据库变化,如由 MySQL 变成 Oracle 只要增加 267 | DAO 接口的新实现类即可,原有 MySQ 实现不用修改。这符合 "开-闭" 原则。该原则降低了代码的藕合性, 268 | 提高了代码扩展性和系统的可移植性。 269 | 270 | 一个典型的DAO 模式主要由以下几部分组成。 271 | 272 | 1、DAO接口: 把对数据库的所有操作定义成抽象方法,可以提供多种实现。 273 | 2、DAO 实现类: 针对不同数据库给出DAO接口定义方法的具体实现。 274 | 3、实体类:用于存放与传输对象数据。 275 | 4、数据库连接和关闭工具类: 避免了数据库连接和关闭代码的重复使用,方便修改。 276 | 277 | 278 | ## get和post区别? 279 | 在的HTTP规范中,Post用于上传数据、Get用于请求数据。Post将数据封装在body,Get讲数据直接放在url后面进行传输, 280 | 所以这一点上面get稍微比较不安全,但两者皆是明文传输。Get一般有数据长度的限制,但这个限制并不是Http制定的, 281 | 而是各大浏览器厂商制定的,而post的请求长度有服务端来配置。一般而言post可传输的数据比较大。 282 | 283 | ## ★cookies和session的区别? 284 | 简单的说,由于HTTP是无状态的,所以设计出两种会话方案。Cookie是客户端的会话技术,Session是服务端的会话技术。 285 | 一个储存在浏览器,一个存储在服务器内存中。Cookie非安全,可窃取到,session安全,不可窃取到。Cookies有大小数 286 | 量的限制,Session限制于服务器的内存。 287 | 288 | ## 如何设置请求的编码以及响应内容的类型? 289 | 通过请求对象(ServletRequest)的setCharacterEncoding(String)方法可以设置请求的编码,其实要彻底解决乱码 290 | 问题就应该让页面、服务器、请求和响应、Java程序都使用统一的编码,最好的选择当然是UTF-8;通过响应对象 291 | (ServletResponse)的setContentType(String)方法可以设置响应内容的类型,当然也可以通过HttpServletResponse 292 | 对象的setHeader(String, String)方法来设置。 293 | 294 | ## GBK和UTF8编码的区别? 295 | 1. GBK包含全部中文字符;UTF-8则包含全世界所有国家需要用到的字符。 296 | 2. GBK是在国家标准GB2312基础上扩容后兼容GB2312的标准(好像还不是国家标准); 297 | 而UTF-8编码的文字可以在各国各种支持UTF8字符集的浏览器上显示。 298 | 299 | ## 什么是WebService? 300 | WebService是一种跨编程语言和跨操作系统平台的远程调用技术。 301 | WebService就是一个应用程序向外界暴露出一个能通过Web进行调用的API,也就是说能用编程的方法通过Web来调用这个 302 | 应用程序。我们把调用这个WebService的应用程序叫做客户端,而把提供这个WebService的应用程序叫做服务端。 303 | WebService 即是HTTP + XML 的公共接口。 304 | 因为在很久之前,web上的网页都是之间生成整个网页抛回给浏览器。所以在当时规范了WebService这一协议, 305 | 在访问的时候,只返回没有经过渲染的核心数据。 306 | 发布WebService,可使用JWS包进行发布。xml的格式进行交换数据。 307 | 308 | -------------------------------------------------------------------------------- /基础知识点.md: -------------------------------------------------------------------------------- 1 | - 标★号的知识点为重要知识点 2 | 3 | ### ★为什么重写equals还要重写hashcode? 4 | 5 | 如果重写了equals的话,可以根据自己的规则进行判定两个对象是否相等。(比如学号姓名一样的学生实体即为相等) 6 | 但是,如果将实体存进与哈希有关的集合当中时,哈希地址是根据hashcode进行计算的。如果没有重写hashcode的时候,默认返回的是引用值。 7 | 这将会导致Hash表当中存入两个相同的学生。 8 | 所以所有有关hash的方法都会出问题。 9 | 10 | ### 两个对象值相同(x.equals(y) == true),但却可有不同的hashCode? 11 | 如果重写equals但未重写hashCode时的话会导致这样的问题。按照规范的话,两对象equals相同的话,hashCode也必须相同。 12 | 13 | 14 | ### Object默认的hashcode实现? 15 | Object类对于hashcode并没有具体的实现,调用的是jvm底层的c++代码进行计算。该方法返回的是一个int数字,底层的cpp代码中 16 | 一共有六种默认的实现方式。 17 | 18 | ### Object基类有哪些方法? 19 | 类中的方法:clone(),但是使用该方法必须实现Java.lang.Cloneable接口, 20 | equal()方法判断引用是否一致,指向同一对象,即相等于==,只有覆写了equals()方法之后, 21 | 才可以说不同。hashcode(),对象的地址, toString(), finalize()。 22 | 23 | ### ==比较的是什么? 24 | 在java中==符号比较的是两个对象在内存当中的引用值。 25 | 26 | ### 若对一个类不重写,它的equals()方法是如何比较的? 27 | 若不重写equals的话,将直接调用基类Object的equals方法,该方法比较的还是内存当中的引用值。 28 | 29 | ### ★java中的四种引用类型: 30 | - 强引用:java默认声明的就是强引用,只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收。 31 | - 软引用:在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。 32 | - 弱引用:弱引用的引用强度比软引用要更弱一些,无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。(常用作集合的key,避免内存泄露) 33 | - 虚引用:虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收。 34 | 35 | ### java的基础类型和字节大小。 36 | 一个字节是8位 37 | 整形数: byte 1字节 short 2字节 int 4字节 long 8字节 38 | 浮点数:float 4字节 double 8字节 39 | char:2字节 40 | boolean:1字节 41 | 42 | 43 | ### Java支持的数据类型有哪些?什么是自动拆装箱? 44 | - char 45 | - byte 46 | - short 47 | - boolean 48 | - long 49 | - float 50 | - double 51 | - int 52 | 自动拆装箱是在代码编译之后,自动进行 int i = Integer.valueOf(i) 的转换操作, 53 | 最早的1.5JDK之前没有自动拆装箱,所以当时是需要手动写拆箱装箱。 54 | 55 | 56 | ### 什么是自动拆装箱? 57 | Integer total = 99;这是自动装箱。int totalprim = total;这是自动拆箱。意思是将一个int基本类型放到一个需要 58 | Integer类型的地方会自动装箱,将一个Integer类型的包装类型放到需要int的地方会自动拆箱。包装类型当中,有一个类似 59 | 字符串常量池的常数常量池,数值大小在-127~128之间。 60 | 61 | ### int 和 Integer 有什么区别? 62 | - int是基本类型,Integer是int的包装类型。 63 | - int的默认值是0,Integer的默认值null。 64 | - Integer缓存了-128~127之间的数。 65 | 66 | 67 | ### java8的新特性。 68 | - **lambda表达式支持函数式编程,简化代码。** 69 | - **接口可有默认方法与静态方法。** 70 | - **方法引用的简便写法。** 71 | - **可重复注解。** 72 | - **扩展注解的使用范围:局部变量、泛型变量、异常。** 73 | - **可获取参数的名字。** 74 | - Optional对于Null优雅的处理。 75 | - **java集合中的Stream的增强处理(类似数据库),以及并行处理。** 76 | - **Date/Time API更好的支持。(duration特别好用)** 77 | - 内置javasrcipt引擎 78 | - Base64包 79 | - **JVM中的永久区被移除,换做元数据区** 80 | 81 | ### lambda表达式的优缺点(待讨论) 82 | - 优点: 83 | - 简洁,不再需要匿名内部类。 84 | - 并行计算在大数据量的情况下会比for循环更块 85 | - 缺点: 86 | - 普通情况下比for循环慢 87 | - 调试不方便 88 | - 类型转换要特殊处理 89 | 90 | ### 一个十进制的数在内存中是怎么存的? 91 | 以二进制补码形式存储,最高位是符号位,正数的补码是它的原码,负数的补码是它的反码加1,在求反码时符号位不变,符号位为1,其他位取反 92 | 93 | 94 | ### 浮点数为什么精度会发生丢失? 95 | 2进制的小数无法精确的表达10进制小数,计算机在计算10进制小数的过程中要先转换为2进制进行计算,这个过程中出现了误差。 96 | 例如0.125 二进制表示为0.001 但0.4转换为二进制的话,计算会出现无限小数。所以会导致精度丢失。 97 | 98 | 99 | ### ★浅析Java中的final关键字? 100 | 修饰方法表示不能重写,修饰类型表示不能继承,修饰变量表示需要初始化并赋值,并且不能重新赋值。 101 | (如果这个值是一个对象,对象内的数据可改变,但此对象的引用不能改变)。 102 | 103 | ### 什么是值传递和引用传递? 104 | 值传递代表的是将实参的值拷贝传给形参,形参的改变并不会影响到实参。 105 | 引用传递指的是将实参的引用值传给行参,方法内基于引用值找到对象并修改对象中的值是会生效的,但地址值依然是引用值的拷贝,无法生效。 106 | 107 | ### Java中方法参数的传递规则? 108 | java中是值传递的方式。意味着方法参数这个变量都不是真正要操作的变量,而是变量的拷贝。引用类型的地址值改变 109 | 也不会影响原来的变量,但修改引用类型中的值,可以生效,因为修改会直接作用到堆中的java对象。 110 | 111 | ### ★当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递? 112 | 值传递指的是传递进来的对象的引用值是一份拷贝。(比如将传递进方法的参数对象的引用值设为null的话, 113 | 在方法外部调用该对象的方法的话,是不会报空指针的。因为传递进来的是引用的拷贝值。) 114 | 115 | 116 | ### String和StringBuffer的区别? 117 | - String不可变,进行更改字符串的话相当于改了字符串的引用。 118 | - StringBuffer可变,修改字符串是基于原来的字符串进行修改,不会创建新的字符串。 119 | 120 | 121 | ### ★StringBuffer和StringBuilder的区别,从源码角度分析? 122 | StringBuffer源码中方法加了synchronized进行修饰,所以是线程同步的。而StringBuilder中的方法没有用 123 | synchronized进行修饰,所以不是线程同步的。 124 | 125 | 126 | ### String,Stringbuffer,StringBuilder的区别? 127 | String是字符串常量,编译之后其实是调用StringBuilder。 128 | StringBuilder是字符串变量,线程非安全,效率高。在循环拼接字符串中,推荐使用StringBuider. 129 | StringBuffer是字符串变量,线程安全,效率低。 130 | 131 | ### 如何输出一个某种编码的字符串? 132 | new String(str.getBytes("ISO-8859-1"), "GBK"); 通过添加字符编码进行转换。 133 | 134 | 135 | ### ★什么是字符串常量池? 136 | 在Java堆中,有一块专门放置字符串的池子不同JDK版本不一样,在编译期就存在的字符串将会 137 | 直接存入这个池中,或者在不同代码地方的相同的字符串将会直接引用同一个字符串,为什么能 138 | 这样引用是因为字符串的不可变性,java常量池的实现其实是享元模式的思想,可以节省创建的时间,并且节省空间。 139 | 140 | ### ★String方法intern()的作用是? 141 | 1. 当常量池中不存在"abc"这个字符串的引用,将这个对象的引用加入常量池,返回这个对象的引用。 142 | 2. 当常量池中存在"abc"这个字符串的引用,返回这个对象的引用; 143 | 144 | ### ★String为什么是不可变的? 145 | 因为常量池中的字符串会被多个地方引用到,如果字符串是可变的话,更改一个字符串将导致原先引用 146 | 该字符串的所有地方都被改变了。这个不可变是指堆中常量池的字符串本身不可变,代码中的引用是可变的。 147 | String类本身设计出不可变是为了防止客户程序员继承String类,造成破坏。 148 | 149 | ### 为什么String不可变,为什么是final? 150 | - 因为String类中持有的value数组使用final进行修饰的,所以它的数组引用就变成不可变的了。 151 | - 这么做的原因之一是因为String有常量池这个概念。比如一个String a在常量池中被很多变量 152 | - 引用了,如果String是可变的,当改变了a当中的字符,会造成所有引用这个字符变量的地方都跟着改变。 153 | - 但如果String是不可变的话,a原本的字符不变,将一个新的字符串赋予给a,这样就不会影响到其他引用之前a字符串的地方。 154 | 155 | ### String能继承吗? 156 | - 不能,因为String类是经过final修饰的。 157 | - 这么做的原因可能是因为String是底层的类,用final修饰的话,自然而然的方法也会被final修饰。因此在调用String的任何方法的时候,都采用JVM的内嵌机制,效率会有较大的提升。 158 | - 还有可能是出于安全的考虑,防止继承的子类改写String的方法,再将子类以String父类的形式进行调用。例如使用+号操作符重载? 159 | 160 | ### ★String s = new String("xyz");究竟产生了几个对象,从JVM角度谈谈? 161 | 当JVM执行到这一行的时候,会先去常量池中查看是否有xyz的字符串,没有的话,会新建这个字符串放入 162 | 常量池,如果有的话则不创建。new 操作符将会在对象堆中创建一个字符串对象。所以这个操作可能生成1 163 | 个或者2个对象。 164 | 165 | ### ★String拼接字符串效率低,你知道原因吗? 166 | 如果是“ab”+"cd"的话,并不会效率低,因为经过编译优化之后会自动变成“abcd”, 167 | 但如果是代码中动态拼接,或者循环拼接的话,比如a+b+c+d将会产生a,ab,abc这三个临时的字符串, 168 | 创建不必要的字符串是浪费性能和空间的,所以大部分的String拼接字符串效率会低。 169 | 其实+号,编译器转换成StringBuilder的append。有点类似于C++当中的操作符重载。 170 | 171 | 172 | ### String的常见API? 173 | - indexOf 检索子串的位置 174 | - substring 获取子串 175 | - trim 去除字符串前后空白 176 | - charAt 获取字符的位置 177 | - startWith\endWith 检测是否是指定字符串开头或者结尾 178 | - toUpperCase 转换成大写 179 | - toLowerCase 转换成小写 180 | - valueOf 将其他类型转为字符串 181 | - split 分隔字符串 182 | 183 | ### String.valueOf和Integer.toString的区别? 184 | 没有区别。String.valueOf(i),方法内部调用的也是Integer.toString(i)。 185 | 186 | ### ★Java中的subString()真的会引起内存泄露么? 187 | 假设A字符串很大,B=A.substring,在java1.6版本以前B会持有A字符串类型内置的字符数组, 188 | AB是共享同一个字符数组,就算A被回收了,但是B还是持有A字符串内很大的字符数组,在Java1.7 189 | 之后修复了这个问题,在substring中,会重新复制一份字符数组给B,性能会比1.6版本差一点, 190 | 但就不会导致内存泄露。假设A="123456789",B="9",但是其实B当中还是持有完整的"123456789"数组 191 | 192 | ### &和&&的区别? 193 | - 一个是按位与,一个是逻辑与。逻辑与会短路。按位与不会短路。 194 | 195 | ### 在Java中,如何跳出当前的多重嵌套循环? 196 | - 定义一个标号。然后在内层循环当中break 标号; 197 | 198 | ``` 199 | ok: 200 | 201 | for (int j = 0; j < 1000; j++) { 202 | for (int k = 0; k < 1000; k++) { 203 | System.out.println(k); 204 | break ok; 205 | } 206 | } 207 | 208 | ``` 209 | 210 | ### 你能比较一下Java和JavaSciprt吗? 211 | - java是编译型语言,强类型,面向对象。由JVM执行。 212 | - javascript是解释性语言,弱类型,一般面向过程。由浏览器引擎执行。 213 | 214 | 215 | ### 简述正则表达式及其用途。 216 | - 主要用来匹配出特定规则的字符串。(如电话号码) 217 | 218 | ### Java中是如何支持正则表达式操作的? 219 | - String类中的split\match\replace 等方法都支持正则。 220 | - Pattern类支持正则。 221 | 222 | ### 浅析Java中的static关键字? 223 | 修饰变量代表这是类的共享变量,修饰方法的话表示类的静态方法,可在不实例化对象的情况下进行方法调用, 224 | 修饰代码块的话表示类初始化会调用这些代码,还有一个非常少见的用法就是静态导入,调用方法就不用使用 225 | 类型.方法名的方式调用,而是直接使用方法名进行调用。 226 | 227 | - static可以修饰内部类,但是不能修饰普通类。静态内部类的话可以直接调用静态构造器(不用对象) 228 | - static修饰方法, static 方法就是没有 this 的方法。在 static 方法内部不能调用非静态方法,反过来是可以的。 229 | 而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用 static 方法。这实际上正是 static 方法的主要用途。 230 | 方便在没有创建对象的情况下来进行调用(方法/变量)。 231 | - static修饰变量,就变成了静态变量,随类加载一次,可以被多个对象共享。 232 | - static修饰代码块,形成静态代码块,用来优化程序性能,将需要加载一次的代码设置成随类加载,静态代码块可以有多个。 233 | 234 | ### ★你对Java中的volatile关键字了解多少? 235 | volatile关键字的作用在于内存可见性(更改变量导致各个线程的变量缓存失效,使其需要从主内存获取新的变量值)、 236 | 禁止指令重排序。volatile关键字并不能保证原子性。适合于修饰状态值,相当于一个轻量级的锁 237 | 238 | ### ★i++是线程安全的吗?如何解决线程安全性? 239 | i++操作包括三步,读取i,对i+1,写入i,所以这并不是原子性操作,并不能保证线程安全。可以对i用volatile关键字 240 | 进行修饰在对i的加操作用synchronized 或者 lock 或者 CAS 方式(AtomicInteger)进行操作。 241 | 242 | ### ★请谈谈什么是CAS? 243 | Compare And Swap 比较并更换 244 | 内存值 预期值 想修改的值 如果内存值等于预期的值那么修改为想修改的值。 245 | 底层是调用cpu指令集的cmpxchg指令来实现。 246 | AtomicInteger的原理就是使用volatile保证可见性,利用CAS保证原子性。 247 | 248 | ### ★从字节码角度深度解析 i++ 和 ++i 线程安全性原理? 249 | i++操作包括三步,读取i,对i+1,写入i,所以在各自的本地线程中i+1操作可能会丢失 250 | i++ 先将本地变量i读取到操作数栈,再进行++写会本地变量,所以操作数栈里的是原值。 251 | ++i 先将本地变量进行++,再读取到操作数栈,所以操作数栈里的值是+1后的值。 252 | 253 | 254 | ### Java有哪些特性,举个多态的例子。 255 | - 例如Animal类型都有move方法,Cat和Bird都集成了Animal类。但是Cat的move是用四肢跑。Bird的move使用翅膀飞。 256 | - 多态是一种动态方法绑定的机制。 257 | 258 | ### 类和对象的区别? 259 | - 好比猫属于生物中的一个物种(类)。而家里养了一只叫kitty的猫,kitty就是猫这个物种中一个具体并真实存在的对象。 260 | 261 | ### Object当中的主要方法? 262 | - getClass() : 获取对象所属类型 263 | - hashCode() : 获取对象的 264 | - equals() : 判断对象是否相等 265 | - toString() : 将对象转换为字符串 266 | - clone() : 克隆一个对象 267 | - wait()... : 暂停线程让出锁,进入锁池 268 | - notify() : 唤醒线程 269 | - notifyAll(): 唤醒所有线程 270 | - finalize() : 在对象回收时会执行的方法 271 | 272 | 273 | ### 重载和重写的区别?相同参数不同返回值能重载吗? 274 | 275 | - 重载是在同一个类中,方法名一样,但参数列表不一样的静态多态。虚拟机无法根据返回值的不同来判断使用哪个重载方法。 276 | - 重写是子类重写了父类的方法,方法签名一样,但实现不一样。 277 | - 重载其实是一种静态多态,在编译成字节码的时候已经完成绑定。 278 | - 重写其实是一种动态多态,在程序运行期间才完成绑定。 279 | 280 | 281 | ### Java中是否可以覆盖(override)一个private或者是static的方法? 282 | - Java中static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。 283 | - 还有私有的方法不能被继承,子类就没有访问权限,肯定也是不能被覆盖。 284 | 285 | 286 | ### 类加载机制的双亲委派模型,好处是什么? 287 | - 双亲委派模型是每次收到类加载请求时,先将请求委派给父类加载器完成,如果父类加载器无法完成加载,那么子类尝试自己加载。 288 | - 双亲委派机制可以避免加载子类自定义的Object类、String类等一些跟jdk命名相同的类。使得加载的类都是同一个。这样才安全。 289 | 290 | 当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。 291 | 292 | ### 静态变量存在哪里? 293 | - 存在方法区(永久代,在1.8之后是元数据区)当中。 294 | 295 | ### 什么是泛型? 296 | - 泛型是参数化类型。避免为不同类型参数的方法进行多次重载,方便开发。并且编译器会进行泛型的检查,在开发上更安全。 297 | - 但是泛型信息在运行时其实是擦除的。 298 | 299 | - 简单的说就是类型的参数化,就是可以把类型像方法的参数那样传递。 300 | - 泛型使编译器可以在编译期间对类型进行检查以提高类型安全,减少运行时由于对象类型不匹配引发的异常。 301 | - 并且所有的强制转换都是自动和隐式的,提高代码的重用率和性能。 302 | 303 | ### ★extends 和super 泛型限定符的区别? 304 | - extends表示的是所需类型是本类或其子类。 表示的是限定上界。 305 | - super表示的是所需类型是本类或其父类。 表示的是限定下界。 306 | 307 | ### ★是否可以在static环境中访问非static变量? 308 | - 因为静态的成员属于类,随着类的加载而加载到静态方法区内存,当类加载时,此时不一定有实例创建,没有实例,就不可以访问非静态的成员。 309 | 310 | ### 通过反射创建对象的方法? 311 | - 1.通过类对象的.newInstance方法。 312 | - 2.通过类对象的构造器对象的.newInstance方法。 313 | 314 | ### 如何通过反射获取和设置对象私有字段的值? 315 | - 先通过getDeclaredFields();方法获取属性列表。 316 | - 再通过field.setAccessible(true);打开访问权限。 317 | - 再通过field.set(obj, value);将值填入。 318 | 319 | ### ★反射的实现与作用? 320 | 在java中实现反射是通过Class对象,得到该对象后可通过该对象调用相应的方法获取该类中的属性或者方法,给属性赋值或 321 | 调用该类中的方法。 322 | 323 | 反射的用途我个人的理解主要是为了弥补Java静态语言不灵活的缺陷。而实现的一种动态编程的方法。 324 | 动态决定调用某些类,属性,方法。 325 | 326 | 作用如下: 327 | 1、java集成开发环境,每当我们敲入点号时,IDE便会根据点号前的内容,动态展示可以访问的字段和方法。 328 | 2、java调试器,它能够在调试过程中枚举某一对象所有字段的值。 329 | 3、web开发中,我们经常接触到各种配置的通用框架。为保证框架的可扩展性,他往往借助java的反射机制。 330 | 例如Spring框架的依赖反转(IOC)便是依赖于反射机制。 331 | 332 | 一般来说反射是用来做框架的,或者说可以做一些抽象度比较高的底层代码。 333 | 334 | ### java反射机制。 335 | 336 | 可以在运行时判断一个对象所属的类,构造一个类的对象,判断类具有的成员变量和方法,调用1个对象的方法。 337 | 4个关键的类:Class,Constructor,Field,Method。 338 | - getConstructor获得构造函数/getDeclardConstructor; 339 | - getField/getFields/getDeclardFields获得类所生命的所有字段; 340 | - getMethod/getMethods/getDeclardMethod获得类声明的所有方法, 341 | 正常方法是一个类创建对象,而反射是1个对象找到1个类。 342 | 343 | ### java支持多继承吗? 344 | - java不支持类的多继承,只支持接口的多继承。 345 | 346 | ### 接口和抽象类的区别是什么? 347 | - 抽象类可以有构造方法(可让子类调用),接口中不能有构造方法。 348 | - 抽象类中可以有普通成员变量,接口中没有普通成员变量。 349 | - 抽象类中可以包含非抽象普通方法(模板方法模式),接口中的所有方法必须都是抽象的。 350 | - 抽象类中的抽象方法的访问权限可以是 public、protected 和(默认类型子类不能继承),接口中的抽象方法只能是 public 类型的,并且默认即为 public abstract 类型 351 | - 抽象类中可以包含静态方法,在 JDK1.8 之前接口中不能不包含静态方法,JDK1.8 以后可以包含。 352 | - 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问权限可以是任意的,但接口中定义的变量只能是 public static final 类型的,并且默认即为 public static final 类型。 353 | - 一个类可以实现多个接口,用逗号隔开,但只能继承一个抽象类,接口不可以实现接口,但可以继承接口,并且可以继承多个接口,用逗号隔开。 354 | 355 | ### Comparable和Comparator的区别? 356 | 357 | - 简而言之 358 | - Comparable是一个接口比较器。类自己实现了排序规则,可直接调用集合类的sort 359 | - Comparator是一个外部比较器。通过定义一个独立的比较器,在集合进行排序的时候传入一个比较器。 360 | - 个人偏向于Comparator的做法,比较低耦合,可以应用不用的comparator进行不用的比较。 361 | 362 | ### 面向对象的特征有哪些方面? 363 | - 抽象、继承、封装、多态 364 | 365 | ### final、finally、finalize的区别? 366 | - final是修饰符。修饰变量、方法、类。 367 | - finally是异常处理块当中最后执行的语句。 368 | - finalize是在垃圾收集器将对象从内存中清除出去之前调用的方法。 369 | 370 | 371 | ### 静态内部类和普通内部类的区别? 372 | 内部类 : 373 | - 内部类中的变量和方法不能声明为静态的。(非静态内部类只有在实例化外部类的时候才会加载,如果内部类有静态变量的话,导致可以通过Out.Inner.static 访问到该内部类的静态变量,但此时内部类还未被加载) 374 | - 内部类实例化:B是A的内部类,实例化B:A.B b = new A().new B()。 375 | - 内部类可以引用外部类的静态或者非静态属性及方法。(因为内部类实例化的话,外部类必然已经实例化) 376 | 377 | 静态内部类 : 378 | - 静态内部类属性和方法可以声明为静态的或者非静态的。 379 | - 实例化静态内部类:B是A的静态内部类,A.B b = new A.B()。 380 | - 静态内部类只能引用外部类的静态的属性及方法。(因为引用外部类中的属性时,外部类未必实例化了) 381 | 382 | ### 内部类可以引用外部类的成员吗? 383 | - 普通内部类的话可以引用。静态内部类的话只能引用外部类的静态变量。 384 | 385 | 386 | ### Java的接口和C++的虚类的相同和不同处。 387 | - 相当于接口和抽象类的区别。 388 | 389 | ### JAVA语言如何进行异常处理,关键字:throws,throw,try,catch,finally分别代表什么意义?在try块中可以抛出异常吗? 390 | - throw 直接抛出一个异常。 391 | - throws 声明将要抛出的异常。 392 | - try包围可能出现异常的范围。 393 | - catch在异常出现时,执行的代码 394 | - finally不管异常有没有出现,都会执行的一段代码。 395 | 在try块中可以抛出异常。 396 | 397 | ### Java中throw和throws的区别是什么? 398 | throw是new 一个异常,要么自己捕获,要么声明继续传给调用者。 399 | throws是声明一个异常但是不处理,将异常的处理交给调用者。 400 | 401 | ### finally语句块你踩过哪些坑? 402 | 比较少,因为关于流的关闭操作都有放在finally中进行关闭,return操作很少会放入finally中。 403 | 程序最终执行到哪个块就在哪个有return的块中进行return。 404 | 405 | - finally块一定会执行. 406 | - finally前有return,会先执行return语句,并保存下来,再执行finally块,最后return。不会覆盖之前的返回值。 407 | - finally前有return、finally块中也有return,先执行前面的return,保存下来,再执行finally的return, 408 | 覆盖之前的结果,并返回 409 | 410 | return在finally中则会覆盖。但尽量不要乱写多个return。 411 | 412 | 413 | ### 谈一下面向对象的"六原则一法则"。 414 | - 单一职责原则 415 | - 开闭原则 416 | - 依赖倒置原则 417 | - 里氏替换原则 418 | - 接口隔离原则 419 | - 合成聚合复用原则 420 | - 迪米特法则 421 | 422 | 423 | ### 请问JDK和JRE的区别是什么? 424 | JRE是Java运行时环境。它是运行编译好的Java程序所必需的一切包,包括Java虚拟机(JVM)、Java类库、java命令和其他基础设施。 425 | 如果您只关心在计算机上运行Java程序,那么您将只安装JRE。 426 | JDK是Java开发工具包,功能齐全的SDKforJava。它是JRE的超集,包含编译器(javac)和一些开发工具(如javadoc和jdb)。 427 | 428 | 他们两个的明显区别就是一个用来运行程序。一个用来开发程序(开发程序当然需要能运行程序的环境)。 429 | 430 | 431 | ### Java中的LongAdder和AtomicLong的区别 432 | LongAdder类与AtomicLong类的区别在于高并发时前者将对单一变量的CAS操作分散为对数组cells中多个元素的CAS操作, 433 | 取值时进行求和;而在并发较低时仅对base变量进行CAS操作,与AtomicLong类原理相同。 434 | 435 | 436 | ### error和exception有什么区别? 437 | - Error类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。 438 | 对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。 439 | 440 | - Exception类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常, 441 | 使程序恢复运行,而不应该随意终止异常。 442 | 443 | ### 什么是java序列化,如何实现java序列化? 444 | 序列化:把Java对象转换为字节序列的过程。 445 | 反序列化:把字节序列恢复为Java对象的过程。 446 | 用途:把对象的字节序列持久化到硬盘上或者在网络上传输。 447 | 序列化的实现:将需要被序列化的类实现Serializable接口, 448 | 该接口没有需要实现的方法。Serializable只是标注该对象是可被序列化的, 449 | 然后使用一个输出流(如:FileOutputStream或者ByteArrayOutputStream)来构造一个ObjectOutputStream(对象流)对象, 450 | 接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态), 451 | 要恢复的话则用输入流。 452 | 453 | 其好处一是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里), 454 | 二是,利用序列化实现远程通信,即在网络上传送对象的字节序列。 455 | 当序列化ID不一致时,会导致序列化失败。 456 | 457 | 458 | ### ★对象克隆和实现方式 459 | 460 | 克隆的对象可能包含一些已经修改过的属性,而new1个对象属性都还是初始化时候的值,被复制克隆的类要实现Clonable接口,覆盖clone()方法, 461 | 访问修饰符为public,方法中调用super.clone()得到所需要的复制方法,类中的属性类也需要实现Clonable接口,覆写clone()方法,并在super 462 | 中也调用子属性类的clone()复制,才可以实现深拷贝。 463 | 464 | 或者写到流中序列化的方式来实现,不必考虑引用类型中还包含引用类型,直接用序列化来实现对象的深复制拷贝,即将对象写到流,再从流中读出来, 465 | 需要实现seriazation接口。 466 | 467 | ### 反射中,Class.forName和classloader的区别? 468 | 区别: 469 | - Class.forName除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。 470 | - 而classloader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。 471 | 472 | ClassLoader就是遵循双亲委派模型最终调用启动类加载器的类加载器,实现的功能是“通过一个类的全限定名来获取描述此类的二进制字节流”, 473 | 获取到二进制流后放到JVM中。Class.forName()方法实际上也是调用的CLassLoader来实现的。 474 | 475 | 476 | 477 | 478 | ### java 判断对象是否是某个类的类型方法? 479 | 480 | instanceof 运算符是用来在运行时指出对象是否是特定类的一个实例。instanceof通过返回一个布尔值来指出, 481 | 这个对象是否是这个特定类或者是它的子类的一个实例。 482 | getClass判断,如o.getClass().equals(ClassA.class)。(使用instanceof来判断一个对象是不是属于某个类, 483 | 但是有时候这个类是继承于一个父类的,所以,不能严格判断出是不是自己的类,而不是自己的父类。) 484 | 485 | 486 | 487 | ### ★Java内存泄露的问题调查定位:jmap,jstack的使用等等 488 | jstack 观测进程状态,可以查看死锁、阻塞、等待资源的异常进程。 489 | 先使用jps查看当前运行的java进程,然后使用 jstack id是观测进程内的死锁、阻塞、等待资源等异常情况。 490 | jmap 观测虚拟机内的内存使用情况以及检测频繁gc,jmap -histo:live 进程号 查看哪些数量异常高的实例进行分析。 491 | 或者jmap -dump:format=b,file=d:/heap.hprof 分析内存快照。如果是字符串的话可以分析其内的内容。 492 | 一般来说都是业务相关的数据。 493 | 494 | ### Arrays.sort实现原理和Collections.sort实现原理 495 | 事实上Collections.sort方法底层就是调用的array.sort方法,其中涉及legacyMergeSort(a):归并排序, 496 | ComparableTimSort.sort():Timsort排序。Timsort排序是结合了合并排序(merge sort)和插入排序(insertion sort)而得出的排序算法。 497 | 核心过程: 498 | TimSort 算法为了减少对升序部分的回溯和对降序部分的性能倒退,将输入按其升序和降序特点进行了分区。排序的输入的单位不是一个个单独的数字,而是一个个的块-分区。其中每一个分区叫一个run。针对这些 run 序列,每次拿一个 run 出来按规则进行合并。每次合并会将两个 run合并成一个 run。合并的结果保存到栈中。合并直到消耗掉所有的 run,这时将栈上剩余的 run合并到只剩一个 run 为止。这时这个仅剩的 run 便是排好序的结果。 499 | (0)如何数组长度小于某个值,直接用二分插入排序算法 500 | (1)找到各个run,并入栈 501 | (2)按规则合并run 502 | 503 | ### foreach和while的区别(编译之后) 504 | 增强for循环只是java提供的一个语法糖,在编译之后其实是利用Iterator和While进行遍历。如果foreach中进行增加移出操作之后又紧跟hasNext()操作会触发异常。这其中涉及到fail-fast(同步修改失败)和fail-safe(对副本进行修改)机制。 505 | https://www.cnblogs.com/luyu1993/p/7148765.html 506 | cusor比size大,导致认为还有next元素 507 | 508 | 509 | ### ★cloneable接口实现原理,浅拷贝or深拷贝 510 | cloneable接口实际上只是标记该类是否可以克隆,具体操作需要重写,一般在重写的方法内调用clone方法。如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,而没有真实的创建一个新的对象,则认为是浅拷贝。反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,则认为是深拷贝。 511 | 深拷贝有三种方式: 512 | - Cloneable接口实现深拷贝,实现clone方法,并且在clone方法内部,把该对象引用的其他对象也要clone一份,这就要求这个被引用的对象必须也要实现Cloneable接口并且实现clone方法。 513 | - 序列化实现深拷贝。 514 | - FastJSON或者利用反射依次复制值。 515 | 516 | 517 | 518 | 519 | ### 讲讲类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,当new的时候,他们的执行顺序。 520 | 父类静态变量、 521 | 父类静态代码块、 522 | 子类静态变量、 523 | 子类静态代码块、 524 | 父类非静态变量(父类实例成员变量)、 525 | 父类构造函数、 526 | 子类非静态变量(子类实例成员变量)、 527 | 子类构造函数。 528 | 529 | 530 | ### ★描述动态代理的几种实现方式,分别说出相应的优缺点。 531 | JDK动态代理:利用反射机制生成一个实现代理接口的Proxy类,在调用具体方法前调用InvokeHandler来处理。 532 | CGlib动态代理:利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。 533 | 534 | 区别:JDK代理只能对实现接口的类生成代理;CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。 535 | 536 | JDK动态代理 537 | 538 | 1、因为利用JDKProxy生成的代理类实现了接口,所以目标类中所有的方法在代理类中都有。 539 | 2、生成的代理类的所有的方法都拦截了目标类的所有的方法。而拦截器中invoke方法的内容正好就是代理类的各个方法的组成体。 540 | 3、利用JDKProxy方式必须有接口的存在。 541 | 4、invoke方法中的三个参数可以访问目标类的被调用方法的API、被调用方法的参数、被调用方法的返回类型。 542 | 543 | cglib动态代理 544 | 545 | 1、 CGlib是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。 546 | 2、 用CGlib生成代理类是目标类的子类。 547 | 3、 用CGlib生成 代理类不需要接口 548 | 4、 用CGLib生成的代理类重写了父类的各个方法。 549 | 5、 拦截器中的intercept方法内容正好就是代理类中的方法体 550 | 551 | ### CheckedException,RuntimeException的区别。 552 | Throwable有两个子类,一个Error,一个Exception。直接继承Exception的是受检异常,继承Exception的子类RuntimeException的是非受检异常。 553 | 554 | 受检异常:编译器强制必须捕获。不是程序本身的错误,只是外部因素如文件读写异常、数据库异常,为了保障程序的健壮性,必须要捕获。属于不可控的异常。但是为了对异常进行抛出、捕获和处理异常需要增加较多代码,会降低代码的可读性。 555 | 556 | 非受检查异常:不必须捕获,抛给虚拟机。一般是由于程序不严谨导致的错误。比如空指针异常或者算数异常等。这种属于一种程序的bug,按照道理来讲是程序员编程不当导致的,这种bug一旦发生应该消除。 557 | 558 | ### 请列出5个运行时异常。 559 | ClassCastException(类转换异常) 560 | IndexOutOfBoundsException(数组越界) 561 | NullPointerException(空指针) 562 | ArithmeticException (算数异常,除0) 563 | NumberFormatException (字符串转数字错误) 564 | 565 | ### 在自己的代码中,如果创建一个java.lang.String类,这个类是否可以被类加载器加载?为什么。 566 | 不会,因为类加载是双亲委托机制,String类会从顶层类加载器加载,轮不到自定义的加载器加载。 567 | 568 | 569 | ### tomcat结构,类加载器流程 570 | tomcat的WebClassLoader先尝试自己加载,加载不到的话,再交给父类进行双亲委托机制。这个加载器不会加载java核心类。 571 | 572 | ### 什么情况下会发生栈内存溢出。 573 | 栈溢出有两种,一种是stackoverflow,另一种是outofmemory,前者一般是因为方法递归没终止条件,后者一般是方法中线程启动过多。 574 | 575 | 576 | 577 | 578 | -------------------------------------------------------------------------------- /多线程.md: -------------------------------------------------------------------------------- 1 | - 标★号为重要知识点 2 | 3 | ## 介绍一下Syncronized锁,如果用这个关键字修饰一个静态方法,锁住了什么?如果修饰成员方法,锁住了什么? 4 | - Synchronized修饰成员方法的话锁住的是实例对象。修饰静态方法的话,锁住的是类对象。 5 | (可以类比数据库中的表锁和行锁的机制) 6 | 7 | 8 | ## ★请你介绍一下volatile? 9 | 10 | - 使用volatile修饰的变量会强制将修改的值立即写入主存,主存中值的更新会使缓存中的值失效 11 | - (非volatile变量不具备这样的特性,非volatile变量的值会被缓存,线程A更新了这个值, 12 | - 线程B读取这个变量的值时可能读到的并不是是线程A更新后的值)。 13 | - volatile会禁止指令重排 volatile具有可见性、有序性,不具备原子性。 注意,volatile不具备原子性。(不能保证并发累加问题) 14 | 15 | ## ★Synchronized和Lock的区别? 16 | 17 | - synchronized : 18 | - 是一个Java的关键字,JVM隐式锁。 19 | - 会自动释放锁。 20 | - 去获得锁的时候,会阻塞。 21 | - 不可获取锁的状态。 22 | - 可重入,不可中断,非公平锁 23 | - 性能没有Lock好。(但是随着JDK版本更迭,其实synchronized的性能也不错) 24 | - Lock: 25 | - 是java的一个类。 26 | - 需要手动释放锁。一般在finally中释放。(避免异常的时候没有释放锁) 27 | - 线程可以尝试获得锁,线程可以不用一直阻塞等待。 28 | - 可以获取锁的状态,通过trylock()方法。 29 | - 可重入,可中断,可公平。 30 | - 性能良好,适合大量并发。 31 | 32 | 33 | 34 | 35 | 36 | ## .概括的解释下线程的生命周期周期状态。 37 | 1.新建状态(New):new Thread(runable); new操作完成后,此时线程刚被创建是新建状态。 38 | 2.就绪状态(Runnable): 当调用thread.start()方法被调用的时候,线程进入就绪状态,可以开始抢占cpu 39 | 3.运行状态(Running): 当线程分配到了时间片之后,开始进入运行状态。 40 | 4.阻塞状态(Blocked): 当线程还未执行结束前,让出cpu时间片(主动或者被动的),线程进入阻塞状态 41 | 5.死亡状态(Dead):线程正常运行结束或者遇到一个未捕获的异常,线程进入死亡状态。 42 | 43 | 44 | ## 多线程的实现方式? 45 | 最基本的是两种:一、通过继承Thread类创建线程。二、通过实现Runnable接口创建线程执行代码,并放入线程类当中。 46 | 47 | ## 创建线程的方法,哪个更好,为什么? 48 | - 继承Thread类 49 | - 实现Runnable接口。较灵活。推荐使用。 50 | - 实现Callable接口。有返回值。 51 | 52 | 53 | 1.继承Thread类,重写run方法 54 | 2.实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target 55 | 3.通过Callable和FutureTask创建线程 56 | 4.通过线程池创建线程 57 | 58 | 以上其实本质上就是Thread和Runable的实现方式 59 | 60 | 61 | ## ★wait的底层实现? 62 | 63 | - lock.wait()方法最终通过ObjectMonitor的void wait(jlong millis, bool interruptable, TRAPS)方法实现 64 | 65 | 1. 将当前线程封装成ObjectWaiter对象node 66 | 67 | 2. 通过ObjectMonitor::AddWaiter方法将node添加到_WaitSet列表中 68 | 69 | 3. 通过ObjectMonitor::exit方法释放当前的ObjectMonitor对象,这样其它竞争线程就可以获取该ObjectMonitor对象 70 | 71 | 4. 最终底层的park方法会挂起线程 72 | 73 | 74 | 75 | ## ★多线程中的i++线程安全吗?为什么? 76 | 不安全。就算i变量加上volatile修饰符的话一样是线程不安全的,因为这里的线程不安全并不是内存可见性造成的。 77 | 是因为i++不是一个原子性操作。i++分为两步操作,一步是i+1,一步是把结果再赋值给i。假设两个线程同时并发执行 78 | i++操作的话,就会导致少加1的情况。 79 | 80 | ## ★AtomicInteger和volatile等线程安全操作的关键字的理解和使用 81 | volatile关键字保证了可见性。 82 | Atomicinteger利用volatile保证可见性再利用CAS机制来保证原子性。 83 | 84 | 85 | ## 什么叫线程安全?举例说明 86 | 线程安全是编程中的术语,指某个函数、函数库在并发环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。 87 | 88 | ## 说说线程安全问题,什么是线程安全,如何实现线程安全? 89 | 1. 使用synchronized或者Lock自己实现。 90 | 2. 使用线程安全的类。 91 | 3. 不使用全局变量。 92 | 4. 使用LocalThread类。 93 | 5. 使用CAS更新机制。 94 | 95 | 96 | ## BlockingQueue的使用。(take,poll的区别,put,offer的区别)。 97 | take和put是阻塞的。 98 | poll和offer是非阻塞的。 99 | 100 | ## 定时线程的使用 101 | 可使用Timer 也可使用 ScheduleThreadPool 102 | 103 | ## 如何线程安全的实现一个计数器? 104 | - 使用原子变量 105 | - 使用锁机制 106 | - 自己实现CAS操作 107 | 108 | ## 如何写一个线程安全的单例? 109 | ``` 110 | public class Singleton { 111 | 112 | private static volatile Singleton instance; 113 | 114 | private Singleton() {} 115 | 116 | public static Singleton getInstance() { 117 | if (instance == null) { 118 | synchronized (Singleton.class) { 119 | //再检查一次 120 | if (instance == null){ 121 | instance = new Singleton(); 122 | } 123 | } 124 | } 125 | return instance; 126 | } 127 | 128 | } 129 | 130 | ``` 131 | 或者使用枚举类型的单例,让JVM层面来保证单一实例。 132 | 133 | 134 | 135 | 136 | ## ★多线程同步的方法,如何进行线程间的同步? 137 | 使用synchronized修饰符 138 | 使用wait\notify机制 139 | 使用jdk自带的可重入锁Lock+Condition 140 | 141 | 142 | ## 介绍一下生产者消费者模式? 143 | 生产者生产商品进入线程安全的并发队列当中,消费者从这个队列中获取商品进行消费。 144 | 实现并发与解耦。本质上和我们的消息中间件MQ是一样的。 145 | 146 | ## 通过三种方式实现生产者消费者模式? 147 | 1. synchronized加非同步队列的锁 148 | 2. Lock加condition 149 | 3. 使用同步队列BlockingQueue 150 | 151 | ## 线程创建有很大开销时,应该怎么优化? 152 | 使用线程池进行优化。 153 | 154 | 155 | ## ★请简述一下线程池的运行流程,使用参数以及方法策略等 156 | - 运行流程 157 | 158 | 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。 159 | 如果此时线程池中的数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列。 160 | 如果此时线程池中的数量大于等于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize, 161 | 建新的线程来处理被添加的任务。 162 | 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize, 163 | 那么通过 handler所指定的策略来处理此任务。 164 | 当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样, 165 | 线程池可以动态的调整池中的线程数。 166 | 167 | 168 | - 使用参数 169 | 170 | CorePoolSize:核心线程池大小 171 | MaximumPoolSize:最大线程数 172 | WorkQueue:任务缓存队列 173 | ThreadFactory:线程工厂,主要用来创建线程 174 | keepAliveTime:线程最大的存活时间 175 | Handler:饱和处理策略 176 | 177 | - 饱和处理策略 178 | 179 | ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常;也是默认的处理方式。 180 | ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。 181 | ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) 182 | ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 183 | 184 | ## ★简述ThreadPoolExecutor内部工作原理? 185 | 186 | 先查看当前运行状态,如果不是RUNNING 状态会拒绝执行任务,如果是RUNNING状态,就会查看当前运行的线程数量,如果小于核心线程数, 187 | 会创建新的线程来执行这个任务,如果不小于核心线程,会将这个任务放到阻塞队列去等代执行,直到上一个任务执行完再来执行这个任务。 188 | 如果失败会创建一个非核心线程来执行这个任务如果当前线程数大于最大线程数,会直接拒绝该任务。 189 | 190 | ## ★常用的线程池模式以及不同线程池的使用场景 191 | newSingleThreadPool:串行化执行的任务 192 | newFixedThreadPool:希望固定线程数的任务,一般设置为Runtime.getRuntime().availableProcessors() 193 | newCachedThreadPool:适合异步小任务 194 | newScheduledThreadPool:适合周期性的任务 195 | 196 | ## newFixedThreadPool此种线程池如果线程数达到最大值后会怎么办。 197 | 放入等待队列,当线程池中有线程空闲就执行等待队列中的任务 198 | 199 | 200 | ## 一个线程池正在处理服务如果忽然断电该怎么办? 201 | 202 | 队列实现持久化储存,下次启动自动载入。 203 | 但是实际需要看情况,大体思路是这样。 204 | 添加标志位,未处理 0,处理中 1,已处理 2。每次启动的时候,把所有状态为 1 的,置为 0。或者定时器处理 205 | 关键性的应用就给电脑配个 UPS。 206 | 207 | ## ★讲一下AQS吧。 208 | AQS的英文是抽象队列同步器的意思。 209 | 首先AQS当中主要有三个东西,一个是state变量,一个是当前线程,一个是等待队列。 210 | 它的运作机制主要是线程想获取锁的话,先用CAS的机制将state加1,如果成功 211 | 的话,在将当前线程变量赋值为自身。由于AQS是可重入的,所以第二次加锁的时候 212 | 先判断当前线程变量是否是自身,如果是的话。state变量再进行加1。 213 | 在并发的时候,其他线程想加锁的时候,CAS操作state会失败,进入等待队列。 214 | 如果之前的线程执行完毕的话,会唤醒等待队列中的线程 215 | 216 | ## 通过AQS实现一个自定义的Lock? 217 | 自定义独占锁只需要定义一个非公开的内部类继承AbstractQueuedSynchronizer类 218 | 重写tryAcquire和tryRelease就可以了。 219 | 220 | 221 | 222 | 223 | ## ★Java中有几种线程池? 224 | 1.newFixedThreadPool 这个线程池的corePoolSize和maximumPoolSize大小是一样的 225 | 采用的是LinkedBlockingQueue无界阻塞队列。 226 | 2.newCachedThreadPool 227 | 这个线程池的corePoolSize是0,maximumPoolSize是int最大值 228 | 采用的是SynchronousQueue无缓冲等待队列。 229 | 3.newScheduledThreadPool 230 | 这个线程池采用的是DelayedWorkQueue延迟工作队列。 231 | 4.newSingleThreadExecutor 232 | 这个线程池corePoolSize和maximumPoolSize都是1, 233 | 采用的是LinkedBlockingQueue无界阻塞队列 234 | 235 | newFixedThreadPool是固定线程数的线程池,适用于执行长任务,固定线程数避免 236 | 线程切换带来的损耗。 237 | newCachedThreadPool是缓冲线程池,适用于执行小任务,这些小任务都可以在 238 | 一个时间片内执行完 239 | newScheduledThreadPool适用于周期性执行任务。 240 | newSingleThreadExecutor适用于串行化执行任务。 241 | 242 | 243 | ## ★SimpleDateFormat是线程安全的吗?如何解决? 244 | 不是线程安全的,解决方案是不要共享simpleDateFormat实例,而使用临时生成SimpleDateFormat实例, 245 | 或者通过ThreadLocal对每个线程绑定各自的SimpleDateFormat. 246 | 247 | ## ★threadlocal为什么会出现oom? 248 | 249 | 答:ThreadLocal里面使用了一个存在弱引用的map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例。这个Map的确使用了弱引用,不过弱引用只是针对key。每个key都弱引用指向threadlocal。 当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收。 250 | 但是,我们的value却不能回收,而这块value永远不会被访问到了,所以存在着内存泄露。因为存在一条从current thread连接过来的强引用。只有当前thread结束以后,current thread就不会存在栈中,强引用断开,Current Thread、Map value将全部被GC回收。最好的做法是将调用threadlocal的remove方法。 251 | 252 | 在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value,但是这些被动的预防措施并不能保证不会内存泄漏: 253 | 254 | (1)使用static的ThreadLocal,延长了ThreadLocal的生命周期,可能导致内存泄漏。 255 | (2)分配使用了ThreadLocal又不再调用get(),set(),remove()方法,那么就会导致内存泄漏,因为这块内存一直存在。 256 | 257 | ## ★线程池的好处? 258 | a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。 259 | b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。 260 | c. 提供定时执行、定期执行、单线程、并发数控制等功能。 261 | 262 | ## 线程池启动线程submit和execute有什么不同? 263 | execute提交的是runable接口的对象,获取不到线程的执行结果。 264 | submit提交的是callable接口的对象,可以获取到线程的执行结果,可以 265 | 通过Future.get抛出的异常进行捕获。 266 | 267 | ## cyclicbarrier和countdownlatch的区别 268 | CountDownLatch: 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行。 269 | 类似于赛跑运动中,所有运动员都跑完了才开始颁奖仪式。 270 | CyclicBrrier: N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。 271 | 类似于赛跑运动中,所有运动员互相等待其他所有人都准备好了,才可以起跑。这场跑完,下一场 272 | 也是需要所有运动员都准备好了,才开始下一轮起跑。(可重复) 273 | 274 | ## 如何理解Java多线程回调方法? 275 | java当中的多线程回调主要是利用Callable和Future这两个组件。 276 | 在A线程中调用了一个B线程执行一个操作,在这个操作还未执行完成之前,A线程先去做别的事情, 277 | 等到预估B线程快执行好了之后,A线程去调用get()方法阻塞获取B线程的执行结果。 278 | 279 | 280 | 281 | 282 | 283 | ## ★同步方法和同步代码块的区别是什么? 284 | 1锁粒度不同 285 | 2代码块里可以指定同步的标识 286 | 287 | 288 | ## ★在监视器(Monitor)内部,是如何做线程同步的? 289 | 290 | AQS的实现原理和底层的Monitor是相似的。 291 | Monitor是虚拟机底层用C++实现的。 292 | - _owner:指向持有ObjectMonitor对象的线程 293 | - _WaitSet:存放处于wait状态的线程队列 294 | - _EntryList:存放处于等待锁block状态的线程队列 295 | - _recursions:锁的重入次数 296 | - _count:用来记录该线程获取锁的次数 297 | 298 | 当多个线程同时访问一段同步代码时,首先会进入_EntryList队列中,当某个线程获取到对象的monitor后进入_Owner区域并把monitor中的_owner变量设置为当前线程,同时monitor中的计数器_count加1。即获得对象锁。 299 | 若持有monitor的线程调用wait()方法,将释放当前持有的monitor,_owner变量恢复为null,_count自减1,同时该线程进入_WaitSet集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便_EntryList队列中其他线程进入获取monitor(锁) 300 | 301 | 一旦方法或者代码块被 synchronized 修饰, 那么这个部分就放入了监视器的监视区域, 302 | 确保一次只能有一个线程执行该部分的代码, 线程在获取锁之前不允许执行该部分的代码 。 303 | 304 | ## ★请说明一下sleep() 和 wait() 有什么区别? 305 | sleep是让线程休眠让出cpu。在一定时间后会自动恢复运行。但是这个操作是不会释放锁操作的。 306 | wait是让线程等待,一定需要唤醒,才会继续后面的操作。wait是释放锁的。 307 | 这两个方法来自不同的类分别是Thread和Object,sleep方法属于Thread类中的静态方法,wait属于Object的成员方法, 308 | 对此对象调用wait方法导致本线程放弃对象锁。 309 | wait,notify和notifyAll只能在同步控制方法或者同步控制块(对某个对象同步,然后在块中调用wait或者notify)里面使用, 310 | 而sleep可以在任何地方使用(使用范围)。 311 | 312 | ## ★唤醒一个阻塞的线程 313 | 314 | 如因为Sleep,wait,join等阻塞,可以使用interrupted exception异常唤醒。 315 | 316 | 317 | ## 同步和异步有何异同,在什么情况下分别使用他们?举例说明。 318 | 同步:在同一个线程中,语句B必须等待语句A执行完之后,才能继续执行。 例如一条转账记录的生成。A先减钱,扣好再执行B加钱。 319 | 异步:在一个线程中,执行A语句用另外一个线程运行,A语句还未运行完成前,就直接运行语句B。 例如用户支付时,还未支付成功前 320 | 就直接返回客户端正在支付中的页面,而不是等待支付成功才返回支付成功的页面。 321 | 322 | 323 | ## 启动一个线程是用run()还是start()? 324 | start() 325 | 326 | 327 | ## 请说出你所知道的线程同步的方法 ? 328 | synchronized隐式锁 329 | Lock显式锁 330 | volatile可见性(但不保证原子性) 331 | ThreadLocal线程局部变量 332 | CAS机制 333 | 334 | ## ★★ThreadLocal什么时候会出现OOM的情况?为什么? 335 | 336 | 1. ThreadLocal的实现是这样的:每个Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 337 | 是 ThreadLocal实例本身,value 是真正需要存储的 Object。 338 | 339 | 2. 也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。 340 | 值得注意的是图中的虚线,表示 ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 341 | GC 时会被回收。 342 | 343 | 3. ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么 344 | 系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry, 345 | 就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value 346 | 就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。 347 | 348 | 4. 总的来说就是,ThreadLocal里面使用了一个存在弱引用的map, map的类型是ThreadLocal.ThreadLocalMap. Map 349 | 中的key为一个threadlocal实例。这个Map的确使用了弱引用,不过弱引用只是针对key。每个key都弱引用指向threadlocal。 350 | 当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收。 351 | 352 | 但是,我们的value却不能回收,而这块value永远不会被访问到了,所以存在着内存泄露。因为存在一条从current thread 353 | 连接过来的强引用。只有当前thread结束以后,current thread就不会存在栈中,强引用断开,Current Thread、Map value 354 | 将全部被GC回收。最好的做法是将调用threadlocal的remove方法,这也是等会后边要说的。 355 | 356 | 5. 其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove() 357 | 的时候都会清除线程ThreadLocalMap里所有key为null的value。这一点在上一节中也讲到过! 358 | 359 | 6、但是这些被动的预防措施并不能保证不会内存泄漏: 360 | (1)使用static的ThreadLocal,延长了ThreadLocal的生命周期,可能导致内存泄漏。 361 | (2)分配使用了ThreadLocal又不再调用get(),set(),remove()方法,那么就会导致内存泄漏,因为这块内存一直存在。 362 | 363 | 364 | ## ★谈谈对synchronized的偏向锁、轻量级锁、重量级锁的理解,以及升级过程? 365 | - 重量级锁:Monitor 同步成本高 366 | - 自旋锁: 自旋,不需要从用户态转换为核心态 367 | - 轻量级锁: CAS原理 368 | - 偏向锁: 消除数据在无竞争情况下的同步原语 369 | 370 | ## ★深入分析ThreadLocal的实现原理? 371 | 每一个线程里面都有一个TheadLocalMap的数据结构。threadLocal实例调用get的时候, 372 | 是先通过本线程为key获取当前线程的TheadLocalMap,再以threadLocal实例为key,去获取对应的值。 373 | 数据库连接、Session管理等等都使用到了TheadLocal。 374 | 375 | 376 | 377 | ## ★为什么线程的stop()和suspend()方法为何不推荐使用? 378 | stop方法不推荐使用的主要原因是stop停止线程的是非优雅停止。它会放弃锁,并立马停止线程。 379 | 如果线程当中正在执行一段事务性的操作。这个操作涉及两条语句A、B,在执行到语句A后stop,线程就立马停止,导致语句B未执行。 380 | 使得这个操作处于一种不一致的状态。 381 | suspend()方法容易发生死锁。因为一旦调用suspend的时候,线程会暂停,但不放弃锁。(这和wait()方法不一样。wait()方法 382 | 是放弃锁。)想要恢复线程的话必须调用resume()方法,但如果调用resume()方法前又需要获取之前的锁的话,这时候就引发了 383 | 死锁。 384 | 385 | ## ★出现死锁,如何排查定位问题? 386 | 推荐使用jstack jconsole或者jvisualvm来检测死锁。 387 | 388 | ## 线程的sleep()方法和yield()方法有什么区别? 389 | yield()方法是只会给相同优先级或更高优先级的线程以运行的机会。sleep让出运行的机会没有线程优先级的区别。 390 | sleep是进入阻塞状态,yield是进入就绪状态。(有可能下次就进去运行态) 391 | sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常。 392 | sleep()方法比yield()方法(跟操作系统CPU调度相关)具有更好的可移植性。(不是很理解) 393 | 394 | ## 当一个线程进入一个对象的synchronized方法A之后,其它线程是否可进入此对象的synchronized方法B? 395 | 不能,因为方法B也需要持有该对象的锁才能运行。所以在这期间只能运行该对象的非synchronized方法。 396 | 397 | 398 | 399 | 400 | ## ★JVM层面分析sychronized如何保证线程安全的? 401 | 需要从对象的锁头结构进行分析 402 | 403 | 每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。线程进入同步代码块或方法的时候会自动获得该锁, 404 | 在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。而Java 405 | 的内置锁又是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时, 406 | 线程A必须等待或者阻塞,知道线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。 407 | 从synchronized修饰的同步代码块编译的字节码,我们可以看到同步代码块前后加上monitorenter和monitorexit。 408 | 他们分别代表了代表了锁的获取和释放。 409 | 410 | 411 | 412 | ## 请说出与线程同步以及线程调度相关的方法。 413 | wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁; 414 | sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常; 415 | notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程, 416 | 而是由JVM确定唤醒哪个线程,而且与优先级无关; 417 | notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态; 418 | 419 | ## 说说线程的基本状态以及状态之间的关系? 420 | 线程有五个状态:new创建状态,Runnable就绪状态,Running运行状态,Dead消亡状态,Blocked阻塞状态。 421 | 创建线程通过start方法进入就绪状态,获取cpu的执行权进入运行状态,失去cpu执行权会回到就绪状态, 422 | 运行状态完成进入消亡状态,运行状态通过sleep方法和wait方法进入阻塞状态, 423 | 休眠结束或者通过notify方法或者notifyAll方法释放锁进入就绪状态 424 | 425 | 426 | ## 讲一下非公平锁和公平锁在Reentrantlock里的实现? 427 | ReentranLock的底层是AQS。通过一个自定义的同步器来实现锁的获取和释放。默认是非公平的。 428 | 公平锁在源码当中,获取锁的时候会去判断队列中是否有等待的线程,没有的话才会去获取锁。 429 | 非公平锁在源码中,是直接与所有线程去竞争获取锁。 430 | 431 | 公平锁是FIFO的,效率较非公平锁低,但为了防止线程一直饥饿。会采用公平锁。 432 | 非公平锁,效率较高,但线程获取锁是随机的,极端情况下会导致某个线程一直处于获取不到锁的情况。 433 | 434 | 435 | ## 请说明一下synchronized的可重入怎么实现。 436 | 底层是对象监视器。会在对象头部有个区域,专门记录锁信息。包括持有锁的线程,锁的计数器,锁的状态这些。 线程在尝试获取对象锁时,先看看锁计数器是不是为0,为零就说明锁还在,于是获取锁,计数器变成1,并记录下持有锁的线程,当有线程再来请求同步方法时,先看看是不是当前持有锁的线程,是的话,那就直接访问,锁计数器+1,如果不是的话就进入阻塞状态。当退出同步块时,计数器-1,变成0时,释放锁。 437 | 438 | 439 | ## 为什么wait, notify 和 notifyAll这些方法在Object类里面? 440 | 因为在java中每个对象都拥有锁机制对应的监视器,监视器记录了当前持有该对象锁的线程,通过这个对象锁,来协调多线程。所以wait,notify,notifyAll放置在Object类中。 441 | 442 | 443 | ## notify和notifyAll的区别? 444 | notify会选取等待池中的一个线程移入到锁池。 445 | notifyAll会将所有等待池中的线程移入到锁池。 446 | 447 | ## 什么是死锁(deadlock)? 448 | 两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象。 449 | 死锁发生的必要条件: 1.互斥 2.保持锁并请求锁(持有锁并等待) 3.不可抢夺 4.循环等待。 450 | 最重要的还是避免循环等待。 451 | 452 | 453 | ## 如何确保N个线程可以访问N个资源同时又不导致死锁? 454 | 破坏四个条件中其中一个即可。 455 | 1.破坏条件二:限制线程不能在持有锁的同时还等待锁。 456 | 2.破坏条件四:资源有序分配法。限制所有线程必须先请求A锁再请求B锁。 如果有的线程先请求A锁再请求B锁, 457 | 其他线程先请求B锁再请求A锁的话就会造成死锁。 458 | 459 | ## 请谈谈什么是进程,什么是线程? 460 | 好比电脑上开了一个QQ和一个迅雷。这两个属于进程。 461 | 而QQ中的聊天窗口,语音传输,文件传输相当于一个个线程。 462 | 主要是因为Cpu太快了,所以才会有线程这个粒度更小的执行单位。并行处理的效率才能更高。 463 | 464 | 465 | ## 启动线程是用start()方法还是run()方法? 466 | start是启动线程,run其实就是直接调用方法,并没有多线程的概念。 467 | 468 | 469 | 470 | ## java并发包下有哪些类? 471 | 472 | 答:ConcurrentHashMap,ConcurrentSkipListMap,ConcurrentNavigableMap 473 | 474 | CopyOnWriteArrayList 475 | 476 | BlockingQueue,BlockingDeque (ArrayBlockingQueue,LinkedBlockingDeque,LinkedBlockingQueue,DelayQueue,PriorityBlockingQueue,SynchronousQueue) 477 | 478 | ConcurrentLinkedDeque,ConcurrentLinkedQueue,TransferQueue,LinkedTransferQueue) 479 | 480 | CopyOnWriteArraySet,ConcurrentSkipListSet 481 | 482 | CyclicBarrier,CountDownLatch, Semaphore, Exchanger 483 | 484 | Lock(ReetrantLock,ReetrantReadWriteLock) 485 | 486 | Atomic包 487 | 488 | 489 | ## ★AQS,抽象队列同步器 490 | 491 | AQS定义2种资源共享方式:独占与share共享 492 | 493 | 独占:只能有1个线程运行 494 | 495 | share共享:多个线程可以同p执行如samphore/countdownlanch 496 | 497 | AQS负责获取共享state的入队和/唤醒出队等,AQS在顶层已经实现好了,AQS有几种方法:acquire()是独占模式下线程共享资源的顶层入口,如获取到资源,线程直接返回,否则进入等待队列,直到获取到资源为止。tryAcquire()将线程加入等待队列的尾部,并标志为独占。acquireQueued()使线程在等待队列中获取资源,一直到获取资源后不返回,如果过程被中断也返回true,否则false。 498 | 499 | 线程在等待过程中被中断是不响应的,获取资源才补上中断。将线程添加到队列尾部用了CAS自旋(死循环直到成功),类似于AutomicInteger的CAS自旋volatile变量。 500 | 501 | start->tryAcquire -> 入队 -> 找安全点 -> park等待状态 -> 当前节点成对头 -> End 502 | 503 | 504 | ## 多线程同步锁 505 | 506 | A,RentrantLock,可重入的互斥锁,可中断可限时,公平锁,必须在finally释放锁,而synchronize由JVM释放。可重入但是要重复退出,普通的lock()不能响应中断,lock.lockInterruptbly()可响应中断,可以限时tryLock(),超时返回false,不会永久等待构成死锁。 507 | 508 | B,Confition条件变量,signal唤醒其中1个在等待的线程,signalall唤醒所有在等待的线程await()等待并释放锁,与lock结合使用。 509 | 510 | C,semaphore信号量,多个线程比(额度=10)进入临界区,其他则阻塞在临界区外。 511 | 512 | D,ReadWriteLock,读读不互斥,读写互斥,写写互斥。 513 | 514 | E,CountDownLantch倒数计时器,countdown()和await() 515 | 516 | F,CyCliBarrier 517 | 518 | G,LockSupport,方法park和unpark 519 | 520 | ## ★ThreadLocal用过么,原理是什么,用的时候要注意什么 521 | Thread类中有一个threadLocals和inheritableThreadLocals都是ThreadLocalMap类型的变量,而ThreadLocalMap是一个定制化的Hashmap,默认每个线程中这个两个变量都为null,只有当前线程第一次调用了ThreadLocal的set或者get方法时候才会进行创建。其实每个线程的本地变量不是存放到ThreadLocal实例里面的,而是存放到调用线程的threadLocals变量里面。也就是说ThreadLocal类型的本地变量是存放到具体的线程内存空间的。ThreadLocal就是一个工具壳,它通过set方法把value值放入调用线程的threadLocals里面存放起来,当调用线程调用它的get方法时候再从当前线程的threadLocals变量里面拿出来使用。如果调用线程一直不终止那么这个本地变量会一直存放到调用线程的threadLocals变量里面,所以当不需要使用本地变量时候可以通过调用ThreadLocal变量的remove方法,从当前线程的threadLocals里面删除该本地变量。另外Thread里面的threadLocals为何设计为map结构那?很明显是因为每个线程里面可以关联多个ThreadLocal变量。 522 | 523 | 需要注意内存泄露问题: 524 | 由于ThreadLocalMap是以弱引用的方式引用着ThreadLocal,换句话说,就是ThreadLocal是被ThreadLocalMap以弱引用的方式关联着,因此如果ThreadLocal没有被ThreadLocalMap以外的对象引用,则在下一次GC的时候,ThreadLocal实例就会被回收,那么此时ThreadLocalMap里的一组KV的K就是null了,因此在没有额外操作的情况下,此处的V便不会被外部访问到,而且只要Thread实例一直存在,Thread实例就强引用着ThreadLocalMap,因此ThreadLocalMap就不会被回收,那么这里K为null的V就一直占用着内存。 525 | 综上,发生内存泄露的条件是 526 | ThreadLocal实例没有被外部强引用,比如我们假设在提交到线程池的task中实例化的ThreadLocal对象,当task结束时,ThreadLocal的强引用也就结束了 527 | ThreadLocal实例被回收,但是在ThreadLocalMap中的V没有被任何清理机制有效清理 528 | 当前Thread实例一直存在,则会一直强引用着ThreadLocalMap,也就是说ThreadLocalMap也不会被GC 529 | 也就是说,如果Thread实例还在,但是ThreadLocal实例却不在了,则ThreadLocal实例作为key所关联的value无法被外部访问,却还被强引用着,因此出现了内存泄露。 530 | 531 | 532 | ## ★concurrenthashmap具体实现及其原理,jdk8下的改版 533 | jdk7: 534 | 535 | 在jdk7中是以数组+分段锁(segement)+链表的方式实现,Segment继承自ReentrantLock 536 | 537 | 使用分段锁的方式是将数据分成一块一块的存储,然后给每一块数据配一把锁,当一个线程访问一块数据的时候,其它线程也可以访问其它块的数据,实现并发访问,大大提高并发访问的能力。 538 | 539 | jdk8: 540 | 541 | 在jdk8中是以数组+链表+红黑树的方式实现,彻底放弃Segement分段存储 542 | 其中内部大量采用synchronized和CAS操作,key-value值都是用volatile修饰,保证值的并发可见性 543 | 544 | 545 | ## ★cas是什么,他会产生什么问题 546 | CompareAndSet,底层需要依赖cpu的原子指令。 547 | 会产生ABA问题的解决,如加入修改次数、版本号。 548 | 549 | 550 | ## 简述ConcurrentLinkedQueue和LinkedBlockingQueue的用处和不同之处 551 | ConcurrentLinkedQueue基于CAS的无锁技术,不需要在每个操作时使用锁,所以扩展性表现要更加优异,在常见的多线程访问场景,一般可以提供较高吞吐量。 552 | LinkedBlockingQueue内部则是基于锁,并提供了BlockingQueue的等待性方法。 553 | 554 | 555 | ## ★concurrent包中使用过哪些类?分别说说使用在什么场景?为什么要使用? 556 | 原子类:提供原子累加的数值功能 557 | 锁类:提供ReentranLock锁和读写锁 558 | 并发集合类:提供Map或者List的并发实现 559 | 并发工具类:等待多线程完成的CountDownLatch、同步屏障CylicBarriar、控制并发数的Semaphore、线程间交换数据的 Exchanger 560 | 阻塞队列:提供多种形式的阻塞队列 561 | 562 | 563 | ## ★Condition接口及其实现原理 564 | 在经典的生产者-消费者模式中,可以使用Object.wait()和Object.notify()阻塞和唤醒线程,但是这样的处理下只能有一个等待队列。在可重入锁ReentrantLock中,使用AQS的condition提供了类似object.wait和notify的线程通信机制,可以实现设置多个等待队列,使用Lock.newCondition就可以生成一个等待队列。 565 | 566 | 567 | 568 | ## ★Fork/Join框架的理解 569 | ForkJoin框架的作用其实和并行流的作用类似。适合于分而治之的场景。比如累加1到100,开启10个线程,分别累加1-10,11-20....等10个段再进行汇总。ForkJoin的底层还有一个workStealing的思想。 570 | 571 | 572 | ## ★sleep和sleep(0)的区别。 573 | sleep的作用是在未来的n毫秒内,不参与到CPU竞争。 574 | sleep(0)的作用是触发操作系统立刻重新进行一次CPU竞争。 575 | 576 | ## ★JUC下研究过哪些并发工具,讲讲原理。 577 | CountDownLatch 闭锁 578 | CyclicBarrier 循环栅栏 579 | .Exchanger 线程交换器 580 | Semaphore 信号量 581 | 582 | ## 线程池的关闭方式有几种,各自的区别是什么。 583 | shutdown:(非阻塞等待所有线程执行完毕,此方法就已执行) 584 | 1、调用之后不允许继续往线程池内继续添加线程; 585 | 2、线程池的状态变为SHUTDOWN状态; 586 | 3、所有在调用shutdown()方法之前提交到ExecutorSrvice的任务都会执行; 587 | 4、一旦所有线程结束执行当前任务,ExecutorService才会真正关闭。 588 | 589 | shutdownNow(): 590 | 1、该方法返回尚未执行的 task 的 List; 591 | 2、线程池的状态变为STOP状态; 592 | 3、阻止所有正在等待启动的任务, 并且停止当前正在执行的任务。 593 | 594 | ## ★假如有一个第三方接口,有很多个线程去调用获取数据,现在规定每秒钟最多有10个线程同时调用它,如何做到。 595 | 可以使用ScheduledThreadPool的scheduleAtFixedRate方法 596 | 597 | 598 | ## ★countdowlatch和cyclicbarrier的内部原理和用法,以及相互之间的差别(比如countdownlatch的await方法和是怎么实现的)。 599 | 600 | CountDownLatch原理 601 | 602 | CountDownLatch是使用一组线程来等待其它线程执行完成,这个场景类似于一群人考试,先做的人先交了,但是在考试时间没到的前提下,老师必须额等待最后一个学生完成交卷老师才能走,CountDownLatch使用Sync继承AQS。构造函数很简单地传递计数值给Sync,并且设置了state,这个state的值就是倒计时的数值,每当一个线程完成了自己的任务(学生完成交卷),那么就使用CountDownLatch.countdown()方法来做一次state的减一操作,在内部是通过CAS完成这个更新操作,直到所有的线程执行完毕,也就是说计数值变成0,那么就然后在闭锁上等待的线程就可以恢复执行任务。 603 | 604 | CyclicBarrier原理 栅栏 605 | 606 | CyclicBarrier 的字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。线程进入屏障通过CyclicBarrier的await()方法。 607 | 实现原理:在CyclicBarrier的内部定义了一个Lock对象,其实就是ReenTrantLock对象,每当一个线程调用CyclicBarrier的await方法时,将剩余拦截的线程数减1,然后判断剩余拦截数是否为0,如果不是,进入Lock对象的条件队列等待。如果是,执行barrierAction对象的Runnable方法,然后将锁的条件队列中的所有线程放入锁等待队列中,这些线程会依次的获取锁、释放锁,接着先从await方法返回,再从CyclicBarrier的await方法中返回。 608 | 609 | 在CyclicBarrier类的内部有一个计数器,每个线程在到达屏障点的时候都会调用await方法将自己阻塞,此时计数器会减1,当计数器减为0的时候所有因调用await方法而被阻塞的线程将被唤醒。这就是实现一组线程相互等待的原理。 610 | 611 | 612 | ## ★用过读写锁吗,原理是什么,一般在什么场景下用。一般在读多写少的场景下进行使用。 613 | (1)只要没有线程占用写锁,那么任意数目的线程都可以持有这个读锁。 614 | (2)只要没有线程占用读写锁,那么才能为一个线程分配写锁。 615 | 读锁相当于一个共享锁,写锁i相当于独占锁。 616 | 617 | 618 | ## ★开启多个线程,如果保证顺序执行,有哪几种实现方式,或者如何保证多个线程都执行完再拿到结果。(或者用三个线程按顺序循环打印abc三个字母,比如abcabcabc) 619 | 使用synchronized, wait和notifyAll 620 | 使用Lock 和 Condition 621 | 使用Semaphore 622 | 使用AtomicInteger 623 | 624 | 可以使用CountDownLatch进行实现 625 | 626 | ## 延迟队列的实现方式,delayQueue和时间轮算法的异同。 627 | 延迟队列是无界阻塞队列,通过计算当前时间与任务时间的差值,小于0的话则取出。 628 | 而时间轮算法的是借鉴钟表盘的原理,由一个进程推进时间格前进,例如我们要在晚上八点执行任务的话,只需要表盘走过一圈,并到达第八格时,即可执行。 629 | 630 | ## 如何解决死锁? 631 | 可以使用有序资源分配法或者银行家算法进行避免死锁。 632 | 633 | 634 | ## 线程池如何调优 635 | 可以根据实际情况从最大线程数、等待队列、拒绝策略等参数进行调优。 636 | 637 | ## ★对AbstractQueuedSynchronizer了解多少,讲讲加锁和解锁的流程,独占锁和公平锁加锁有什么不同。 638 | AbstractQueuedSynchronizer会把所有的请求线程构成一个CLH队列,当一个线程执行完毕(lock.unlock())时会激活自己的后继节点,但正在执行的线程并不在队列中,而那些等待执行的线程全部处于阻塞状态. 639 | 线程的显式阻塞是通过调用LockSupport.park()完成,而LockSupport.park()则调用sun.misc.Unsafe.park()本地方法,再进一步,HotSpot在Linux中中通过调用pthread_mutex_lock函数把线程交给系统内核进行阻塞。 640 | -------------------------------------------------------------------------------- /操作系统.md: -------------------------------------------------------------------------------- 1 | - 标★号为重要知识点 2 | 3 | ## CentOS 和 Linux的关系? 4 | CentOS是RedHat的一个分支,RedHat是Linux的一个发行版本,RedHat与CentOS的区别在于, 5 | RedHat收费,CentOS免费 6 | 7 | ## 怎么杀死进程? 8 | 一般情况下,终止一个前台进程使用 Ctrl + C 就可以了。对于一个后台进程就须用 kill 命令来终止。 9 | 我们会先使用 ps、top 等命令获得进程的 PID,然后使用 kill 命令来杀掉该进程。 10 | 11 | 12 | ## 线程,进程区别? 13 | 进程是资源分配的最小单位,线程是CPU调度的最小单位。 14 | 15 | 16 | ## 系统线程数量上限是多少? 17 | 具体看内存,还有系统定义的线程数 /proc/sys/kernel/pid_max 32768 18 | 19 | 20 | 21 | ## 什么是页式存储? 22 | 连续存储管理不足: 23 | - 对空间要求高 24 | - 会形成很多碎片 25 | - 通过移动技术减少碎片会增加系统的开销 26 | 27 | 分页式存储管理的基本原理如下: 28 | - 页框:物理地址分成大小相等的许多区,每个区称为一块(又称页框 page frame); 29 | - 页面:逻辑地址分成大小相等的区, 区的大小与块的大小相等,每个区称一个页面(page)。 30 | - 逻辑地址形式:与此对应,分页存储器的逻辑地址由两部分组成:页号和单元号。 31 | - 用户进程在内存空间中的每个页框内的地址是连续的,但页框和页框之间的地址可以不连续 32 | - 页表和地址转换:在进行存储分配时,总是以块(页框)为单位进行分配,一个作业的信息有多少页,那么在把它 33 | 装入主存时就给它分配多少块。但是,分配给作业的主存块是可以不连续的,即作业的信息可按页分散存放在主存 34 | 的空闲块中,这就避免了为得到连续存储空间而进行的移动。 35 | 36 | 37 | ## ★操作系统里的内存碎片你怎么理解,有什么解决办法? 38 | 39 | 内存碎片通常分为内部碎片和外部碎片: 40 | ``` 41 | 1. 内部碎片是由于采用固定大小的内存分区,当一个进程不能完全使用分给它的固定内存区域时就会产生内部碎片, 42 | 通常内部碎片难以完全避免; 43 | 2. 外部碎片是由于某些未分配的连续内存区域太小,以至于不能满足任意进程的内存分配请求,从而不能被进程利用 44 | 的内存区域。 45 | 现在普遍采取的内存分配方式是段页式内存分配。将内存分为不同的段,再将每一段分成固定大小的页。通过页表机制, 46 | 使段内的页可以不必连续处于同一内存区域。 47 | ``` 48 | 49 | ## ★什么情况下会发生死锁,解决策略有哪些? 50 | 51 | 产生死锁的四个必要条件: 52 | 53 | 1. 互斥条件:一个资源每次只能被一个进程使用。 54 | 2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。 55 | 3. 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。 56 | 4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。 57 | 58 | 所以要根据产生死锁的条件进行预防,并且要尽量避免死锁。所以解决策略有: 59 | 60 | (1)破坏互斥条件: 61 | ``` 62 | 就是在系统里取消互斥。若资源不被一个进程独占使用,那么死锁是肯定不会发生的。 63 | 但一般来说在所列的四个条件中,“互斥”条件是无法破坏的。因此,在死锁预防里主要是破坏其他几个必要条件, 64 | 而不去涉及破坏“互斥”条件 65 | ``` 66 | (2)破坏“占有并等待”条件: 67 | ``` 68 | 破坏“占有并等待”条件,就是在系统中不允许进程在已获得某种资源的情况下,申请其他资源。即要想出一个办法, 69 | 阻止进程在持有资源的同时申请其他资源。 70 | 方法一:创建进程时,要求它申请所需的全部资源,系统或满足其所有要求,或么什么也不给它。这是所谓的“一次性分配”方案。 71 | 方法二:要求每个进程提出新的资源申请前,释放它所占有的资源。这样,一个进程在需要资源S时,须先把它先前占有的资源R 72 | 释放掉,然后才能提出对S的申请,即使它可能很快又要用到资源R。 73 | ``` 74 | (3)破坏“不可抢占”条件 75 | ``` 76 | 破坏“不可抢占”条件就是允许对资源实行抢夺。 77 | 方法一:如果占有某些资源的一个进程进行进一步资源请求被拒绝,则该进程必须释放它最初占有的资源,如果有必要,可再次 78 | 请求这些资源和另外的资源。 79 | 方法二:如果一个进程请求当前被另一个进程占有的一个资源,则操作系统可以抢占另一个进程,要求它释放资源。只有在任意两 80 | 个进程的优先级都不相同的条件下,方法二才能预防死锁。 81 | ``` 82 | (4)破坏“循环等待”条件 83 | ``` 84 | 破坏“循环等待”条件的一种方法,是将系统中的所有资源统一编号,进程可在任何时刻提出资源申请,但所有申请必须按照资源的编号 85 | 顺序(升序)提出。这样做就能保证系统不出现死锁。 86 | 避免死锁: 死锁的预防是通过破坏产生条件来阻止死锁的产生,但这种方法破坏了系统的并行性和并发性。 死锁产生的前三个条件是 87 | 死锁产生的必要条件,也就是说要产生死锁必须具备的条件,而不是存在这3个条件就一定产生死锁,那么只要在逻辑上回避了第四个 88 | 条件就可以避免死锁。 避免死锁采用的是允许前三个条件存在,但通过合理的资源分配算法来确保永远不会形成环形等待的封闭进程 89 | 链,从而避免死锁。该方法支持多个进程的并行执行,为了避免死锁,系统动态的确定是否分配一个资源给请求的进程。 90 | 总的思路来说,预防死锁就是避免死锁的最好方法!不过一旦预防不了而产生死锁就应该及时中断进程和破坏中断。最有效的两个做法 91 | 就是一,只运行需要的进程程序而禁用其他进程程序,二,重启机器来破坏中断是最直接有效的。 92 | ``` 93 | ## ★常用的linux下的命令 94 | 1、find 查找文件或目录 95 | find / -size +204800k //在根目录下查找大于200MB的文件 96 | find / -user username//在根目录下查找所有者为username的文件 97 | find / -name filename.txt //根据名称查找/目录下的filename.txt文件。 98 | 2、复制文件包括其子文件到自定目录 99 | cp -r sourceFolder targetFolder 100 | 3、查看一个程序是否运行 101 | ps –ef|grep tomcat //查看所有有关tomcat的进程 102 | 4、终止线程 103 | kill -9 19979 //终止线程号位19979的线程 104 | 5、查看文件,包含隐藏文件 105 | ls -al 106 | 6、当前工作目录 107 | pwd 108 | 7.创建目录 109 | mkdir newfolder 110 | 8.删除目录(此目录是空目录) 111 | rmdir deleteEmptyFolder 112 | 9.删除文件包括其子文件 113 | rm -rf deleteFile 114 | 10.移动文件 115 | mv /temp/movefile /targetFolder//扩展重命名 mv oldNameFile newNameFile 116 | 11.切换用户 117 | su -username 118 | 12.修改文件权限 119 | chmod 777 file.java //file.java的权限-rwxrwxrwx,r表示读、w表示写、x表示可执行 120 | 13.压缩文件 121 | tar -czf test.tar.gz /test1 /test2 122 | 14.列出压缩文件列表 123 | tar -tzf test.tar.gz 124 | 15.解压文件 125 | tar -xvzf test.tar.gz 126 | 16.查看文件头10行 127 | head -n 10 example.txt 128 | 17.查看文件尾10行 129 | tail -n 10 example.txt 130 | 18.查看日志文件 131 | tail -f exmaple.log //这个命令会自动显示新增内容,屏幕只显示10行内容的(可设置)。 132 | 19.启动Vi编辑器 133 | vi 134 | 20.查看系统当前时间 135 | date 136 | 137 | 138 | ## linux 中 统计多个关键字在某个文本中出现的次数,并按次数排序。 139 | cat test.log | awk {print'$n'} | sort -nr | uniq -c | sort -k1 -nr 140 | 其中 $n 为需要按出现次数排序的那一列 141 | sort -nr 先排序 142 | uniq -c 去重并计算出现次数 143 | sort -k1 -nr 按出现次数排序 144 | 145 | ## Linux系统下你关注过哪些内核参数,说说你知道的。 146 | 1、编辑文件或命令 147 | 148 | vim /etc/sysctl.conf 或 sysctl (参数-n 查询某个值,参数-w 设置) 149 | 150 | 2、参数及简单说明 151 | 152 | net.ipv4.tcp_fin_timeout = 2 #保持在FIN-WAIT-2状态的时间,使系统可以处理更多的连接。此参数值为整数,单位为秒。 153 | net.ipv4.tcp_tw_reuse = 1 #开启重用,允许将TIME_WAIT socket用于新的TCP连接。默认为0,表示关闭。 154 | net.ipv4.tcp_tw_recycle = 1 #开启TCP连接中TIME_WAIT socket的快速回收。默认值为0,表示关闭。 155 | net.ipv4.tcp_syncookies = 1 #开启SYN cookie,出现SYN等待队列溢出时启用cookie处理,防范少量的SYN攻击。默认为0,表示关闭。 156 | net.ipv4.tcp_keepalive_time = 600 #keepalived启用时TCP发送keepalived消息的拼度。默认位2小时。 157 | net.ipv4.tcp_keepalive_probes = 5 #TCP发送keepalive探测以确定该连接已经断开的次数。根据情形也可以适当地缩短此值。 158 | net.ipv4.tcp_keepalive_intvl = 15 #探测消息发送的频率,乘以tcp_keepalive_probes就得到对于从开始探测以来没有响应的连接杀除的时间。 159 | 默认值为75秒,也就是没有活动的连接将在大约11分钟以后将被丢弃。对于普通应用来说,这个值有一些偏大,可以根据需要改小.特别是web类服务器需要改小该 160 | 值。 161 | net.ipv4.ip_local_port_range = 1024 65000 #指定外部连接的端口范围。默认值为32768 61000。 162 | net.ipv4.tcp_max_syn_backlog = 262144 #表示SYN队列的长度,预设为1024,这里设置队列长度为262 144,以容纳更多的等待连接。 163 | net.ipv4.tcp_max_tw_buckets =5000 #系统同时保持TIME_WAIT套接字的最大数量,如果超过这个数值将立刻被清楚并输出警告信息。默认值为 164 | 180000。对于squid来说效果不是很大,但可以控制TIME_WAIT套接字最大值,避免squid服务器被拖死。 165 | net.ipv4.tcp_syn_retries = 1 #表示在内核放弃建立连接之前发送SYN包的数量。 166 | net.ipv4.tcp_synack_retries = 1 #设置内核放弃连接之前发送SYN+ACK包的数量。 167 | net.core.somaxconn = 16384 #定义了系统中每一个端口最大的监听队列的长度, 对于一个经常处理新连接的高负载 web服务环境来说, 168 | 默认值为128,偏小。 169 | net.core.netdev_max_backlog = 16384 #表示当在每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许发送到队列的数据包的最大数量。 170 | net.ipv4.tcp_max_orphans = 16384 #表示系统中最多有多少TCP套接字不被关联到任何一个用户文件句柄上。如果超过这里设置的数字,连接就 171 | 会复位并输出警告信息。这个限制仅仅是为了防止简单的DoS攻击。此值不能太小。 172 | 173 | 3、生效 174 | 175 | sysctl -p 176 | 177 | ## ★Linux下IO模型有几种,各自的含义是什么。 178 | 179 | 1. 阻塞IO: 180 | 181 | 简介:进程会一直阻塞,直到数据拷贝完成。应用程序调用一个IO函数,导致应用程序阻塞,等待数据准备好。 如果数据没有准备好, 182 | 一直等待….数据准备好了,从内核拷贝到用户空间,IO函数返回成功指示。 183 | 184 | 2. 非阻塞I/O模型: 185 | 186 | 创建socket套接字并返回对应的文件描述符。 187 | 简介:非阻塞IO通过进程反复调用IO函数(轮询,多次系统调用,并马上返回);在数据拷贝的过程中,进程是阻塞的; 188 | 我们把一个SOCKET接口设置为非阻塞就是告诉内核,当所请求的I/O操作无法完成时,不要将进程睡眠,而是返回一个错误(EWOULDBLOCK)。 189 | 这样我们的I/O操作函数将不断的测试数据是否已经准备好,如果没有准备好,继续测试,直到数据准备好为止。在这个不断测试的过程中, 190 | 会大量的占用CPU的时间。 191 | 192 | 3. I/O复用模型: 193 | 194 | 简介:主要是select和epoll;对一个IO端口,两次调用,两次返回,比阻塞IO并没有什么优越性;关键是能实现同时对多个IO端口进行监听; 195 | 调用阻塞在系统调用selcte上,而不是在recv真正得IO调用上面,内核数据准备好了,通知引用程序,然后调用recv将数据从内核空间拷贝到用户空间处理。 196 | 197 | 4. 信号驱动式I/O模型: 198 | 使用信号,让内核在描述符准备就绪的时候发送SIGIO信号通知我们,然后在信号回调函数里面,将数据拷贝到用户空间。 199 | 200 | 201 | 5. 异步I/O模型: 202 | 调用aio_read读取,这些函数的工作机制是:告知内核启动某个操作,并让内核在整个操作(包括将数据从内核复制到我们自己的缓冲区) 203 | 完成后通知我们。信号驱动式是有了内核缓冲区有了数据,才发出信号让我们调用IO函数去读。而这个就是数据有了将其已经复制到了应用程序, 204 | 然后通知我们,我们直接处理就可以了,不需要再次调用IO从缓冲区读到用户空间。 205 | 206 | 207 | 208 | ## select、poll和epoll之间的区别 209 | 1. select==>时间复杂度O(n) 210 | 211 | 它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流, 212 | 找出能读出数据,或者写入数据的流,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多, 213 | 无差别轮询时间就越长。 214 | 215 | 2. poll==>时间复杂度O(n) 216 | 217 | poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制, 218 | 原因是它是基于链表来存储的. 219 | 220 | 3. epoll==>时间复杂度O(1) 221 | 222 | epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。 223 | 所以我们说epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1)) 224 | select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪 225 | (一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要 226 | 在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责 227 | 把数据从内核拷贝到用户空间。 228 | 229 | 230 | ## 用一行命令查看文件的最后五行。 231 | tail -n 5 /path/filename 232 | 233 | 234 | ## 用一行命令输出正在运行的java进程。 235 | ps -A | grep java 236 | 237 | ## top 命令之后有哪些内容,有什么作用。 238 | 239 | 240 | https://blog.csdn.net/zhuoya_/article/details/81049967 241 | 242 | 243 | ## 线上CPU爆高,请问你如何找到问题所在。 244 | 1. 首先根据top命令找出cpu占用高的进程 PID 245 | 2. ps aux | grep PID 查看是什么进程 246 | 3. ps -mp pid -o THREAD,tid,time 显示进程内的线程 247 | 4. 其次将需要的线程ID转换为16进制格式:printf "%x\n" tid 248 | 5. jstack pid |grep tid -A 30 249 | 250 | ## ★总结下排查CPU故障的方法和技巧有哪些: 251 | 252 | 1. top命令:Linux命令。可以查看实时的CPU使用情况。也可以查看最近一段时间的CPU使用情况。 253 | 2. PS命令:Linux命令。强大的进程状态监控命令。可以查看进程以及进程中线程的当前CPU使用情况。属于当前状态的采样数据。 254 | 3. jstack:Java提供的命令。可以查看某个进程的当前线程栈运行情况。根据这个命令的输出可以定位某个进程的所有线程的当前 255 | 运行状态、运行代码,以及是否死锁等等。 256 | 4. pstack:Linux命令。可以查看某个进程的当前线程栈运行情况。 257 | 258 | 259 | ## ★linux利用哪些命令,查找哪里出了问题(例如io密集任务,cpu过度) 260 | - iotop命令可以找出io高负载的进程 261 | - top命令可以找出cpu高负载的进程 262 | 263 | -------------------------------------------------------------------------------- /数据库.md: -------------------------------------------------------------------------------- 1 | - 标★号为重要知识点 2 | 3 | ## ★有一个组合索引(A,B,C),可以有哪几种查询方式? 4 | 有A,AB,ABC三种查询方式。如果是AC的话,数据库会先利用索引查找到A索引的所有节点,接下来查找C节点时, 5 | 则没有使用索引。但并非使用了AC的查询就不走索引。但是如果是BC的话,则不走索引,因为在数据库的B树当中 6 | 是以A节点开始索引的。 7 | 8 | ## ★索引失效的原因有哪些?如何优化避免索引失效? 9 | 可以从索引的结构进行分析 10 | 11 | 1. 索引树不存null值,不走索引 12 | 2. 值变化太少的列,可能不走这个值的索引,或者直接走全表 13 | 3. 索引树按照索引字符串是从首字母开始查找,所以前模糊查找不生效(类似多列索引,查询字段用后面的字段造成不走索引) 14 | 4. 不确定的符号比较(无法在树中定位元素),比如<>、not in、not exist 之类的无法缩小查找范围的搜索。 15 | 5. mysql自己估计全表扫描效率更高就不走索引了。 16 | 6. 类型不匹配也会导致索引失效 17 | 7. 使用函数 18 | 19 | 所以判断为何索引失效主要要从索引的结构去分析。大部分不走索引的情况都是因为条件是发散的,而不是收敛的。 20 | 21 | ## 数据库水平切分,垂直切分? 22 | 水平切分指的是拆分一张表中的行。 23 | 垂直切分指的是拆分一张表中的列。 24 | 25 | ## 数据库索引介绍一下。介绍一下什么时候用Innodb什么时候用MyISAM? 26 | - 如果是读多写少的项目,可以考虑使用MyISAM,MYISAM索引和数据是分开的,而且其索引是压缩的,可以更好地利用内存。所以它的查询性能明显优于INNODB。压缩后的索引也能节约一些磁盘空间。MYISAM拥有全文索引的功能,这可以极大地优化LIKE查询的效率。 27 | - 如果你的应用程序一定要使用事务,毫无疑问你要选择INNODB引擎 28 | - 如果是用MyISAM的话,merge引擎可以大大加快应用部门的开发速度,他们只要对这个merge表做一些select 29 | count(*)操作,非常适合大项目总量约几亿的rows某一类型(如日志,调查统计)的业务表。 30 | 31 | ## ★数据库两种引擎 32 | 33 | 1. InnoDB支持事务与外键和行级锁,MyISAM不支持(最主要的差别) 34 | 2. MyISAM读性能要优于InnoDB,除了针对索引的update操作,MyISAM的写性能可能低于InnoDB,其他操作MyISAM的写性能也是优于InnoDB的,而且可以通过分库分表来提高MyISAM写操作的速度 35 | 3. MyISAM的索引和数据是分开的,而且索引是压缩的,而InnoDB的索引和数据是紧密捆绑的,没有使用压缩,所以InnoDB的体积比MyISAM庞大 36 | 4. MyISAM引擎索引结构的叶子节点的数据域,存放的并不是实际的数据记录,而是数据记录的地址。索引文件与数据文件分离,这样的索引称为“非聚簇索引”。其检索算法:先按照B+Tree的检索算法检索,找到指定关键字,则取出对应数据域的值,作为地址取出数据记录。InnoDB引擎索引结构的叶子节点的数据域,存放的就是实际的数据记录。这样的索引被称为“聚簇索引”,一个表只能有一个聚簇索引。 37 | 5. InnoDB 中不保存表的具体行数,也就是说,执行select count() from table时,InnoDB要扫描一遍整个表来计算有多少行,但是MyISAM只要简单的读出保存好的行数即可。注意的是,当count()语句包含 where条件时,两种表的操作是一样的。 38 | 6. DELETE FROM table时,InnoDB不会重新建立表,而是一行一行的删除。 39 | 7. innoDB表的行锁也不是绝对的,假如在执行一个SQL语句时MySQL不能确定要扫描的范围,InnoDB表同样会锁全表,例如updatetable set num=1 where name like “%aaa%” 在where条件没有主键时,InnoDB照样会锁全表 40 | 8. MySQL的innodb存储引擎支持行级锁,innodb的行锁是通过给索引项加锁实现的,这就意味着只有通过索引条件检索数据时,innodb才使用行锁,否则使用表锁。 41 | 42 | ## ★什么是行锁、表锁、读锁、写锁,说说它们各自的特性? 43 | 行锁和表锁是从锁的粒度上来讲。行锁并发高,表锁并发低。 44 | 因为行锁有多个,所以会出现互相等待锁的情况,导致死锁。 45 | 读锁是S锁,共享锁。写锁是X锁,排他锁。 46 | 悲观锁是认为数据极有可能被修改,所以每次都加锁。 47 | 乐观锁原理应该还是CAS,认为修改的操作不多,不加锁。再并发较小的情况下建议乐观锁。 48 | 49 | ## Mysql怎么分表后如果想按条件分页查询怎么办 50 | 51 | 水平分表分页:一定要限定分表条件,否则性能很慢。 52 | 垂直分表分页:尽量反模式将查询参数放入主表,非查询参数放入其他表。 53 | 54 | ## ★分表之后想让一个id多个表是自增的,效率实现 55 | - flicker实现:选择ID数据库的ID表的自增Id作为多个真实业务表的自增id,ID数据库可以多台,并设置自增步长。 56 | - twitter实现:雪花算法。 57 | - 缓存实现:redis或者memcached 58 | 59 | 60 | ## MySQL如何启动慢查询日志? 61 | mysql> set global slow_query_log=ON; 62 | mysql> set global slow_launch_time=5; 63 | 64 | ## union和union all的区别? 65 | union 有排重 union all 没有排重 66 | 67 | 68 | ## ★什么情况下行锁变表锁? 69 | 首先innodb的行锁是利用索引实现的,也就是锁信息在索引上。 70 | 更新操作要全表扫描的情况下,会锁表。 71 | 在某些情况下,例如索引失效(没有加引号的情况下),索引失效导致扫全表,进而锁全表。 72 | 73 | 74 | ## ★什么情况下会出现间隙锁? 75 | 更新操作涉及一个索引范围内的更新,新增的一条数据之前不存在这个索引,但也在这个索引范围内的话,会导致幻读,所以会出现间隙锁。间隙锁在InnoDB的唯一作用就是防止其它事务的插入操作,以此来达到防止幻读的发生,所以间隙锁不分什么共享锁与排它锁。 另外,在上面的例子中,我们选择的是一个普通(非唯一)索引字段来测试的,这不是随便选的,因为如果InnoDB扫描的是一个主键、或是一个唯一索引的话,那InnoDB只会采用行锁方式来加锁,而不会使用Next-Key Lock的方式,也就是说不会对索引之间的间隙加锁。 76 | 77 | 要禁止间隙锁的话,可以把隔离级别降为读已提交,或者开启参数innodb_locks_unsafe_for_binlog。 78 | 79 | 80 | ## ★谈谈你对MySQL的in和exists用法的理解? 81 | - exist在筛选出每行数据的时候都去判断是否存在。 82 | - In先计算出子查询中的数据,再根据这些数据去外层查询中筛选条件。 83 | 84 | 85 | 86 | ## ★什么是B-Tree? 87 | 88 | B-树的搜索,从根结点开始,对结点内的关键字(有序)序列进行二分查找,如果命中则结束,否则进入查询关键字所属范围的儿子结点; 89 | 重复,直到所对应的儿子指针为空,或已经是叶子结点; 90 | 91 | B-树的特性: 92 | 93 | 1. 关键字集合分布在整颗树中; 94 | 95 | 2. 任何一个关键字出现且只出现在一个结点中; 96 | 97 | 3. 搜索有可能在非叶子结点结束; 98 | 99 | 4. 其搜索性能等价于在关键字全集内做一次二分查找; 100 | 101 | 5. 自动层次控制; 102 | 103 | B-树的搜索,从根结点开始,对结点内的关键字(有序)序列进行二分查找,如果命中则结束,否则进入查询关键字所属范围的儿子结点; 104 | 重复,直到所对应的儿子指针为空,或已经是叶子结点; 105 | 106 | ## ★什么是B+Tree? 107 | 108 | B+树是B-树的变体,也是一种多路搜索树: 109 | 110 | 1. 其定义基本与B-树同,除了: 111 | 112 | 2. 非叶子结点的子树指针与关键字个数相同; 113 | 114 | 3. 非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(B-树是开区间); 115 | 116 | 5. 为所有叶子结点增加一个链指针; 117 | 118 | 6. 所有关键字都在叶子结点出现; 119 | 120 | B+的搜索与B-树也基本相同,区别是B+树只有达到叶子结点才命中(B-树可以在非叶子结点命中), 121 | 其性能也等价于在关键字全集做一次二分查找; 122 | 123 | B+的特性: 124 | 125 | 1. 所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的; 126 | 127 | 2. 不可能在非叶子结点命中; 128 | 129 | 3. 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层; 130 | 131 | 4. 更适合文件索引系统; 132 | 133 | 134 | ## ★B-树和B+树的区别?为什么需要使用B+树作为数据库的聚簇索引? 135 | 136 | 区别: 137 | 138 | 1. B+树内节点是不存储数据的。 139 | 2. 内节点的指针树不一样。B-树内节点关键字隔开指针而B+树内节点关键字树就是指针树。指针范围是闭区间。 140 | 3. B+叶子结点有链表可以有序。 141 | 142 | 原因: 143 | 1. 树高效率低。 144 | 2. B+树读取一个节点,便知道多个分路。 145 | 3. B+树的内节点采用稀疏索引使得内存能加载更多索引。 146 | 147 | 148 | 149 | ## 一条执行慢的SQL如何进行优化,如何通过Explain+SQL分析性能? 150 | 1. 调整sql尽量走索引。 151 | 2. 尽量减少扫描次数。 152 | 3. 尽量减少关联表次数。 153 | 154 | 尽量调整SQL让他走索引,尽量减少扫描次数,或者关联表次数,实在不行只能加索引 155 | 156 | ## ★数据库的隔离级别 157 | - 读未提交(Read Uncommitted):只处理更新丢失。如果一个事务已经开始写数据,则不允许其他事务同时进行写操作, 158 | 但允许其他事务读此行数据。可通过“排他写锁”实现。 159 | - 读提交(Read Committed):处理更新丢失、脏读。读取数据的事务允许其他事务继续访问改行数据,但是未提交的写事 160 | 务将会禁止其他事务访问改行。可通过“瞬间共享读锁”和“排他写锁”实现。 161 | - 可重复读取(Repeatable Read - Mysql的默认级别 ):处理更新丢失、脏读和不可重复读取。读取数据的事务将会禁止写事务,但允许读事务, 162 | 写事务则禁止任何其他事务。可通过“共享读锁”和“排他写锁”实现。 163 | - 序列化(Serializable):提供严格的事务隔离。要求序列化执行,事务只能一个接一个地执行,不能并发执行。 164 | 仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。 165 | 166 | DB的特性和隔离级别: 167 | 168 | 4大特性:原子性,一致性,分离性,持久性 169 | 170 | 171 | 隔离级别: 172 | - 读未提交:写事务允许读 173 | - 读提交:写事务禁止读 174 | - 可重复读:写事务禁止读事务,读禁止写 175 | - 序列化:全部禁止 176 | 177 | 详细说明:读提交1个事务开始写则全部禁止其他事务访问该行。读未提交1个事务开始写则不允许其他事务同时写,但可以读。 178 | 可重复读 读事务会禁止写事务,写事物则禁止其他任何事务。序列化性能最低,全部禁止,串行执行。 MYSQL默认的是可重复读。 179 | 180 | 181 | ## ★数据库的乐观锁和悲观锁? 182 | - 乐观锁适用于写少读多的情景,因为这种乐观锁相当于JAVA的CAS,所以多条数据同时过来的时候,不用等待,可以立即进行返回。 183 | - 悲观锁适用于写多读少的情景,这种情况也相当于JAVA的synchronized,reentrantLock等,大量数据过来的时候,只有一条数 184 | 据可以被写入,其他的数据需要等待。执行完成后下一条数据可以继续。 185 | 186 | - 乐观锁采用版本号的方式,即当前版本号如果对应上了就可以写入数据,如果判断当前版本号不一致,那么就不会更新成功, 187 | - 悲观锁实现的机制一般是在执行更新语句的时候采用for update方式。 188 | 189 | 190 | ## 数据库的三范式? 191 | - 第一范式:每一列不能再拆分原子数据项(不能表嵌套表,但我们开发中的,1,2,3,格式字段就属于反范式) 192 | - 第二范式:在第一范式的基础上属性完全依赖于主键,不能部分依赖。(比如学生课程分数表{学号,课程号,分数,学生姓名},这里 193 | 分数是完全依赖于学号和课程号的,但是学生姓名仅依赖于学号,所以学生姓名必须移出去。但是开发中有时候也会反范式) 194 | - 第三范式:在第二范式的基础之上,非主键列必须直接依赖于主键不能存在传递依赖。(比如学生信息表{学号,学生姓名,学院号, 195 | 学院名称},学院号依赖学号,学院名称又依赖学院号,存在传递依赖。这里和第二范式不一样,第二范式要求完全依赖,而第三范式要 196 | 求不能传递依赖。 197 | 198 | 199 | ## 数据库的ACID? 200 | 201 | - 原子性(atomicity): 一个事物必须被视为一个不可分割的最小工作单元,整个事物中的操作要么全部提交成功, 202 | 要么全部失败回滚,对于一个事物来说,不可能只执行其中的一部分操作,这就是的原子性。 203 | - 一致性(consistency): 数据库总是从一个一致性的状态转到另一个事务的一致性状态。 (假设用户A和用户B两者的钱 204 | 加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000) 205 | - 隔离性(isolation):一个事物所做的修改在最终提交前,对其他事物是不可见的。在前面的例子中,如果执行到第A账 206 | 户扣100,此时有另一个账户汇款,则其看见的A账户得余额并没有被减去100。 207 | - 持久性(durability): 一旦事物提交,则其所做的修改就会永远保存在数据库中。 208 | 209 | 210 | ## ★mysql的主从复制原理? 211 | 1. Slave上面的IO线程连接上Master,并请求从指定日志文件的指定位置(或者从最开始的日志)之后的日志内容; 212 | 2. Master接收到来自Slave的IO线程的请求后,通过负责复制的IO线程根据请求信息读取指定日志指定位置之后的日志信息, 213 | 返回给Slave端的 IO线程。返回信息中除了日志所包含的信息之外,还包括本次返回的信息在Master端的Binary Log文件的 214 | 名称以及在Binary Log中的位置; 215 | 3. Slave的IO线程接收到信息后,将接收到的日志内容依次写入到 Slave 端的RelayLog文件(mysql-relay-bin.xxxxxx) 216 | 的最末端,并将读取到的Master端的bin-log的文件名和位置记录到master-info文件中,以便在下一次读取的时候能够清楚 217 | 的告诉Master“我需要从某个bin-log的哪个位置开始往后的日志内容,请发给我”。 218 | 4. Slave的SQL线程检测到Relay Log中新增加了内容后,会马上解析该Log文件中的内容成为在Master 端真实执行时候的 219 | 那些可执行的Query语句,并在自身执行这些Query。这样,实际上就是在Master端和Slave端执行了同样的Query,所以两 220 | 端的数据是完全一样的。 221 | 222 | 223 | ## leftjoin和rightjoin的区别? 224 | - left join 按左表为基础表与右表连接,找不到对应连接的右表,设置为null 225 | - right join 按右表为基础表与左表连接,找不到对应连接的左表,设置为null 226 | - inner join 输出两表都存在对应连接的行数。 227 | 228 | ## 数据库的优化。 229 | - 软优化 230 | - 查询语句优化 231 | - 优化子查询 232 | - 使用索引 233 | - 分解表 234 | - 增加中间表 235 | - 增加冗余字段 236 | - 分析表、检查表、优化表 237 | - 硬优化 238 | - cpu、内存、硬盘 239 | - 参数设置 240 | - 分库分表、读写分离 241 | 242 | ## ★什么是索引?什么条件适合建立索引?什么条件不适合建立索引? 243 | - 索引就是构建出能快速定位出元素的数据结构,例如哈希或者树。 244 | - 适合做索引:主键、查询条件、关联字段、排序、分组 245 | - 不适合做索引:频繁更新、不会出现在where中,不进行关联,字段变化太少的字段 246 | 247 | ## 数据库连接池的工作原理? 248 | 由于程序每次创建和关闭数据库连接都比较耗费资源,所以引入数据库连接池。在程序启动时初始化多个数据库连接, 249 | 需要访问数据库时就在连接池中获取连接对象,使用完毕后释放连接对象。如果访问超过连接池数量则会等待。 250 | 251 | ## jdbc如何控制事务的? 252 | 自动提交事务:默认setAutocommit(true) 253 | 手动提交事务:setAutocommit(false) commit提交 rollback回滚 254 | 255 | 256 | 257 | ## Statement和PreparedStatement有什么区别?哪个性能更好? 258 | 259 | 与Statement相比: 260 | 261 | - PreparedStatement接口代表预编译的语句,它主要的优势在于可以减少SQL的编译错误并增加SQL的安全性 262 | (减少SQL注射攻击的可能性); 263 | - PreparedStatement中的SQL语句是可以带参数的,避免了用字符串连接拼接SQL语句的麻烦和不安全; 264 | - 当批量处理SQL或频繁执行相同的查询时,PreparedStatement有明显的性能上的优势,由于数据库可以将编译 265 | 优化后的SQL语句缓存起来,下次执行相同结构的语句时就会很快(不用再次编译和生成执行计划) 266 | 267 | 268 | ## count(*)、count(列名)和count(1)的区别? 269 | COUNT(常量) 和 COUNT(*)表示的是直接查询符合条件的数据库表的行数。而COUNT(列名)表示的是查询符合条件的列的值不为NULL的行数。 270 | 在官方文档中: 271 | InnoDB handles SELECT COUNT(*) and SELECT COUNT(1) operations in the same way. There is no performance difference. 272 | 表明count(*)和count(1)无区别 273 | 274 | 275 | 276 | ## mysql数据库锁表怎么解决? 277 | 278 | 查询锁表信息 279 | 280 | 当前运行的所有事务 281 | select * from information_schema.innodb_trx 282 | 当前出现的锁 283 | select * from information_schema.innodb_locks 284 | 锁等待的对应关系 285 | select * from information_schema.innodb_lock_waits 286 | 通过 select * from information_schema.innodb_trx 查询 trx_mysql_thread_id 287 | 然后执行 kill 线程ID 288 | KILL 8807;//后面的数字即时进程的ID 289 | 290 | 查询mysql中的线程: 291 | 292 | show processlist 293 | kill id 294 | 295 | 296 | ## ★聚集索引和非聚集索引的区别? 297 | 298 | 299 | 聚集索引: 300 | 索引中键值的逻辑顺序决定了表中相应行的物理顺序(索引中的数据物理存放地址和索引的顺序是一致的),可以这么理解: 301 | 只要是索引是连续的,那么数据在存储介质上的存储位置也是连续的。比方说:想要到字典上查找一个字,我们可以根据字典 302 | 前面的拼音找到该字,注意拼音的排列时有顺序的。聚集索引就像我们根据拼音的顺序查字典一样,可以大大的提高效率。 303 | 在经常搜索一定范围的值时,通过索引找到第一条数据,根据物理地址连续存储的特点,然后检索相邻的数据,直到到达条件截至项。 304 | 305 | 306 | 非聚集索引: 307 | 索引的逻辑顺序与磁盘上的物理存储顺序不同。非聚集索引的键值在逻辑上也是连续的,但是表中的数据在存储介质上的物理顺序是 308 | 不一致的,即记录的逻辑顺序和实际存储的物理顺序没有任何联系。索引的记录节点有一个数据指针指向真正的数据存储位置。 309 | 310 | 总结如下: 311 | - 如果一个主键被定义了,那么这个主键就是作为聚集索引 312 | - 如果没有主键被定义,那么该表的第一个唯一非空索引被作为聚集索引 313 | - 如果没有主键也没有合适的唯一索引,那么innodb内部会生成一个隐藏的主键作为聚集索引,这个隐藏的主键是一个6个字节的列, 314 | 该列的值会随着数据的插入自增。InnoDB引擎会为每张表都加一个聚集索引,而聚集索引指向的的数据又是以物理磁盘顺序来存储的, 315 | 自增的主键会把数据自动向后插入,避免了插入过程中的聚集索引排序问题。如果对聚集索引进行排序,这会带来磁盘IO性能损耗是非常大的。 316 | 317 | - 聚集索引:类似新华字典正文内容本身就是一种按照一定规则排列的目录,其实是数据的存储方式。 318 | - 非聚集索引:新华字典开头中的目录,需要根据页码来找到指定的字。这种才是传统意义上的索引。 319 | 320 | 321 | ## ★数据库的锁? 322 | 323 | - 从锁的粒度:行锁、表锁 324 | - 从事务的角度:共享锁(只能读)、排他锁(不能读不能写)、更新锁 325 | - 从程序的角度:悲观锁和乐观锁 326 | 327 | 更新锁不是很能理解 328 | 329 | 330 | ## ★MyIsam和InnoDB锁的不同 331 | MyISAM采用表级锁,对Myisam表读不会阻塞读,会阻塞同表写,对Myism写则会阻塞读和写,即一个线程获得1个表的写锁后, 332 | 只有持有锁的线程可以对表更新操作,其他线程的读和写都会等待。 333 | 334 | InnoDB,采用行级锁,支持事务,例如只对a列加索引,如果update ...where a=1 and b=2其实也会锁整个表, 335 | select 使用共享锁,update insert delete采用排它锁,commit会把锁取消,当然select by id for update也可以制定排它锁。 336 | 337 | 338 | ## ★Mysql怎么分表,以及分表后如果想按条件分页查询怎么办(如果不是按分表字段来查询的话,几乎效率低下,无解) 339 | 分表的话,可以使用mysql自带的partition功能,如果没有按照分表字段进行条件分页的话,没有很好的解决办法,需要借助大数据工具。 340 | 分表之后想让一个id多个表是自增的,效率实现 341 | 设置不同表的自增步长,或者自增id由另外一张表来专门生成。或者由redis来生成自增id 342 | 343 | 344 | ## ★什么是幻读。innodb如何解决幻读的? 345 | 幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。 346 | 幻读仅专指“新插入的行”。 347 | 348 | 当前读:在有索引的情况下,innodb会使用间隙锁来防止幻读。 349 | 普通读:普通读没有索引作为依据的情况下,innodb会使用MVVC防止幻读。 350 | 351 | 352 | 353 | ## 高并发下,如何做到安全的修改同一行数据。 354 | - 数据库本来的锁 355 | - redis的锁 356 | - zookeeper公平锁 357 | 358 | 359 | ## ★SQL优化的一般步骤是什么,怎么看执行计划,如何理解其中各个字段的含义。 360 | explain sql 361 | 362 | 主要字段有 363 | 364 | - type:all,index,range,ref,eq_ref,const,system 365 | - key:使用到的索引键 366 | - ref:显示索引的哪一列用于查找 367 | - rows:扫描的行数 368 | - extra:具体情况 369 | 370 | 具体各个字段如下: 371 | https://blog.csdn.net/qinaye/article/details/100077070 372 | 373 | 374 | ## MYsql的索引原理,索引的类型有哪些,如何创建合理的索引,索引如何优化。 375 | 376 | https://blog.csdn.net/qq_38149009/article/details/81779853 377 | 378 | 379 | 380 | ## ★select for update 是什么含义,会锁表还是锁行或是其他? 381 | InnoDb行锁是通过给索引上的索引项加锁来实现的,这一点mysql与oracle不同,后者是通过在数据块中对相应数据行加锁来实现的。 382 | InnoDB这种行锁实现的特点意味着: 只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁。 383 | 384 | 在实际应用中,要特别注意 InnoDB 行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。 385 | 在不通过索引条件查询的时候,InnoDB 确实使用的是表锁,而不是行锁。 386 | 由于 MySQL 的行锁是针对索引加的锁,不是针对记录加的锁,因此虽然是访问不同行的记录,但是如果是使用相同的索引键, 387 | 是会出现锁冲突的。应用设计的时候要注意这一点。 388 | 389 | 当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引还是普通索引, 390 | InnoDB 都会使用行锁来对数据加锁。即便在条件中使用了索引字段,但是否使用索引来检索数据是由 MySQL 通过判断不同的 391 | 执行计划的代价来决定的。如果 MySQL 认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下 InnoDB 392 | 将使用表锁,而不是行锁。因此,在分析锁冲突时,别忘了检查 SQL 的执行计划,以确认是否真正使用了索引, 393 | 394 | ## ★为什么要用Btree实现,它是怎么分裂的,什么时候分裂,为什么是平衡的,mysql索引为什么使用B+树。 395 | 396 | https://blog.csdn.net/qq_36520235/article/details/94317993 397 | 398 | 399 | ## ★某个表有近千万数据,CRUD比较慢,如何优化。 400 | https://blog.csdn.net/qq_37221991/article/details/87693639 401 | 402 | 403 | ## ★数据库自增主键可能的问题。(优缺点自增主键和UUID的) 404 | 使用自增长做主键的优点: 405 | 406 | 1. 很小的数据存储空间 407 | 2. 性能最好 408 | 3. 容易记忆 409 | 使用自增长做主键的缺点: 410 | 411 | 1. 如果存在大量的数据,可能会超出自增长的取值范围 412 | 2. 很难(并不是不能)处理分布式存储的数据表,尤其是需要合并表的情况下 413 | 3. 安全性低,因为是有规律的,容易被非法获取数据 414 | 415 | 使用UUID做主键的优点: 416 | 417 | 1. 它是独一无二的,出现重复的机会少 418 | 2. 适合大量数据中的插入和更新操作,尤其是在高并发和分布式环境下 419 | 3. 跨服务器数据合并非常方便 420 | 4. 安全性较高 421 | 422 | 使用UUID做主键的缺点: 423 | 424 | 1. 存储空间大(16 byte),因此它将会占用更多的磁盘空间 425 | 2. 会降低性能 426 | 3. 很难记忆 427 | 428 | 429 | 430 | 431 | ## MVCC的含义,如何实现的。 432 | MVCC即多版本并发控制。MySQL的大多数事务型存储引擎实现的都不是简单的行级锁。基于提升并发性能的考虑,它们一般 433 | 都同时实现了多版本并发控制可以认为MVCC是行级锁的一个变种,但是它在很多情况下避免了加锁操作,因此开销更低。大 434 | 多数的MVCC都实现了非阻塞的读操作,写操作也只锁定必要的行。 435 | 436 | MVCC的实现,是通过保存数据在某个时间点的快照来实现的。也就是说,不管需要执行多长时间,每个事务看到的数据是一 437 | 致的。根据事务开始的时间不同,每个事物对同一张表,同一时刻看到的数据可能是不一样的。 438 | 439 | InnoDB的MVCC是通过在每行记录后面保存两个隐藏的列来实现。这两个列,一个保存了行的创建时间,一个保存了行的过期 440 | 时间(删除时间)。并且存储的并不是真实的时间值,而是系统版本号(system version number)。每开始一个新的事务, 441 | 系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。 442 | 443 | MVCC只在REPEATABLE READ和READ COMMITTED两个隔离级别下工作。其他两个隔离级别都和MVCC不兼容,因为READ 444 | UNCOMMITTED总是读取最新的数据行,而不是符合当前事务版本的数据行,而SERIALIZABLE会对所有读取到的行都加锁。 445 | 446 | 447 | ## ★你做过的项目里遇到分库分表了吗,怎么做的,有用到中间件么,比如sharding jdbc等,他们的原理知道么。 448 | 分表主要从时间范围进行水平分表,还有将大字段拆出去进行垂直分表。分库的话,通过哈希或者其他策略将数据分 449 | 别存到不同的数据库当中。相关数据库中间件的介绍:https://blog.csdn.net/u011596455/article/details/84821559 450 | 原理无非就是多一层代理层进行sql解析执行并汇总。 451 | 452 | 453 | ## MySQL InnoDB存储的文件结构 454 | https://blog.csdn.net/john_lw/article/details/80306122 455 | 456 | ## ★MySQL的几种优化 457 | 1. SQL语句优化 458 | 2. 索引优化 459 | - 选择合适的列建立索引 460 | - 索引优化SQL的方法 461 | - 索引维护的方法 462 | 3. 数据库结构优化 463 | - 选择合适的数据类型 464 | - 范式优化 465 | - 反范式优化 466 | - 数据库表的垂直拆分 467 | - 数据库表的水平拆分 468 | 4. 系统配置优化 469 | - 数据库系统配置优化 470 | - MySQL配置文件优化 471 | - 第三方配置工具使用 472 | 5. 服务器硬件优化 473 | 474 | 475 | ## ★数据库锁表的相关处理 476 | 数据库锁表:在数据库里,同一个数据可能有多个人来读取或更改,为了防止更改的时候别人也同时更改,这里一般要锁住 477 | 表不让别人改。当然还有其它各种复杂情况。 478 | 479 | 数据库锁从类型上讲,有共享锁,意向锁,排他锁。从锁的粒度角度来说,可以分为为行、页键、键范围、索引、表或数据库 480 | 获取锁。(锁粒度是被封锁目标的大小,封锁粒度小则并发性高,但开销大,封锁粒度大则并发性低但开销小) 481 | 482 | 可能的原因有: 483 | 484 | - 字段不加索引:在执行事务的时候,如果表中没有索引,会执行全表扫描,如果这时候有其他的事务过来,就会发生锁表! 485 | - 事务处理时间长:事务处理时间较长,当越来越多事务堆积的时候,会发生锁表! 486 | - 关联操作太多:涉及到很多张表的修改等,在并发量大的时候,会造成大量表数据被锁! 487 | 488 | 出现锁表的解决方法有: 489 | 490 | - 通过相关的sql语句可以查出是否被锁定,和被锁定的数据! 491 | - 为加锁进行时间限定,防止无限死锁! 492 | - 加索引,避免全表扫描! 493 | - 尽量顺序操作数据! 494 | - 根据引擎选择合理的锁粒度! 495 | - 事务中的处理时间尽量短! 496 | 497 | ## INNODB的行级锁有哪2种,解释其含义 498 | - 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。 499 | - 排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。 500 | 501 | 502 | ## ★数据库会死锁吗,举一个死锁的例子,mysql怎么解决死锁 503 | 出现原因: 504 | 505 | 1、一个用户A 访问表A(锁住了表A),然后又访问表B;另一个用户B 访问表B(锁住了表B),然后企图访问表A;这时用户A由于用户B 506 | 已经锁住表B,它必须等待用户B释放表B才能继续,同样用户B要等用户A释放表A才能继续,这就死锁就产生了。 507 | 508 | 509 | 2、用户A查询一条纪录,然后修改该条纪录;这时用户B修改该条纪录,这时用户A的事务里锁的性质由查询的共享锁企图上升到独占锁, 510 | 而用户B里的独占锁由于A有共享锁存在所以必须等A释放掉共享锁,而A由于B的独占锁而无法上升的独占锁也就不可能释放共享锁,于是 511 | 出现了死锁。这种死锁由于比较隐蔽,但在稍大点的项目中经常发生。 512 | 513 | 该条有争议 514 | 515 | 3、如果在事务中执行了一条不满足条件的update语句,则执行全表扫描,把行级锁上升为表级锁,多个这样的事务执行后,就很容易产生 516 | 死锁和阻塞。类似的情况还有当表中的数据量非常庞大而索引建的过少或不合适的时候,使得经常发生全表扫描,最终应用系统会越来越慢, 517 | 最终发生阻塞或死锁。 518 | 519 | 520 | 一般更新模式由一个事务组成,此事务读取记录,获取资源(页或行)的共享 (S) 锁,然后修改行,此操作要求锁转换为排它 (X) 锁。 521 | 如果两个事务获得了资源上的共享模式锁,然后试图同时更新数据,则一个事务尝试将锁转换为排它 (X) 锁。共享模式到排它锁的转换必 522 | 须等待一段时间,因为一个事务的排它锁与其它事务的共享模式锁不兼容;发生锁等待。第二个事务试图获取排它 (X) 锁以进行更新。由 523 | 于两个事务都要转换为排它 (X) 锁,并且每个事务都等待另一个事务释放共享模式锁,因此发生死锁。 524 | 525 | 解决方法: 526 | 527 | 这种死锁比较常见,是由于程序的BUG产生的,除了调整的程序的逻辑没有其它的办法。仔细分析程序的逻辑,对于数据库的多表操作时, 528 | 尽量按照相同的顺序进行处理,尽量避免同时锁定两个资源,如操作A和B两张表时,总是按先A后B的顺序处理, 必须同时锁定两个资源时, 529 | 要保证在任何时刻都应该按照相同的顺序来锁定资源。 530 | 531 | 532 | 533 | -------------------------------------------------------------------------------- /数据结构与算法.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## 求二叉树中节点的最大距离 5 | 情况A: 路径经过左子树的最深节点,通过根节点,再到右子树的最深节点。 6 | 方案:计算两个节点到根节点的深度相加。 7 | 情况B: 路径不穿过根节点,而是左子树或右子树的最大距离路径,取其大者。 8 | 方案:计算两个节点到子树根节点的深度相加 9 | 10 | 11 | ## fibonacci数列的动态规划算法? 12 | 利用空间换时间。使用临时变量保存之前计算好的值。 13 | 14 | 15 | ## 分配饼干问题?每个孩子都有一个满足度,每个饼干都有一个大小,只有饼干的大小大于一个孩子的满足度,该孩子才会获得满足。求解最多可以获得满足的孩子数量。Input:[1,2], [1,2,3]。Output: 2 16 | 贪心算法。对饼干、孩子满足度进行排序。然后先用小饼干给满足度低的孩子。不造成浪费。 17 | 18 | 19 | 20 | ## 如何计算二叉树的深度 21 | ``` 22 | public static int treeDepth(TreeNode root) { 23 | if (root == null) { 24 | return 0; 25 | } 26 | // 计算左子树的深度 27 | int left = treeDepth(root.left); 28 | // 计算右子树的深度 29 | int right = treeDepth(root.right); 30 | // 树root的深度=路径最长的子树深度 + 1 31 | return left >= right ? (left + 1) : (right + 1); 32 | } 33 | ``` 34 | 35 | 36 | ## 如何打印二叉树每层的节点? 37 | - 借助一个队列,先把根节点入队,每打印一个节点的值时,也就是打印队列头的节点时, 38 | - 都会把它的的左右孩子入队,并且把该节点出队。直到队列为空。 39 | 40 | ## 二叉树的Z型遍历? 41 | 借助一个队列和一个栈,方法和打印层遍历类似,区别在于隔层利用栈来逆序遍历。 42 | 43 | 44 | ## 反转单链表? 45 | 1. 可以利用栈逆序拼接。 46 | 2. 利用三个指针引用,一次性遍历反转,在每次反转两个节点的时候,都需要保存下一个节点,以免丢失原链表。 47 | 48 | ## 随机链表的复制? 49 | 复制成 1->1'->2->2'->3->3' 的链表,然后再拆分出来 1'->2'->3' 50 | 51 | 52 | ## 链表-奇数位升序偶数位降序-让链表变成升序 53 | 第一种做法:先将链表拆分成奇数的链表,和偶数的链表,然后翻转偶数的链表,在合并两个链表。 54 | 55 | ## bucket如果用链表存储,它的缺点是什么? 56 | 不支持随机访问,查找的时间复杂度是O(n) 57 | 58 | ## 如何判断链表检测环? 59 | 准备两个指针,一个步长为1,一个步长为2,当遍历到的指针相同时,则判定出现环 60 | 61 | ## 寻找一数组中前K个最大的数? 62 | 最小堆算法 63 | 64 | 65 | ## 求一个数组中连续子向量的最大和 66 | 遍历数组,从第一个大于0的数字开始累加,保存最大值,如果累加后的值小于等于0的话,就抛这一下标, 67 | 从下一个下标开始累积,如果超过之前的最大值的话就进行替换。使用sum和max变量进行操作 68 | 69 | 70 | ## 找出数组中和为S的一对组合。 71 | 将所有数字放入数字对应下标的数组,有N个相同的数字,该下标的值就为N。 72 | 从i下标开始遍历,S-i下标对应值>0的话,就找到组合。 i=s-i的话,做特殊处理。 73 | 74 | 75 | 76 | ## 一个数组,除一个元素外其它都是两两相等,求那个元素? 77 | 所有值进行异或运算。最后的值再和0异或,得到原来的值。 78 | 79 | 80 | 81 | ## 将一个二维数组顺时针旋转90度,说一下思路。 82 | 先找出旋转90度,下标的变换规律。 然后从外圈向内圈旋转变换。 83 | 84 | 85 | 86 | ## 各类排序算法总结? 87 | 排序算法 | 时间复杂度 88 | --- | --- 89 | 冒泡排序 | O(n2) 90 | 选择排序 | O(n2) 91 | 插入排序 | O(n2) 92 | 希尔排序 | O(n1.5) 93 | 快速排序 | O(N*logN) 94 | 归并排序 | O(N*logN) 95 | 堆排序 | O(N*logN) 96 | 基数排序 | O(d(n+r)) 97 | 98 | 99 | 100 | 101 | 102 | ## 快排的原理是什么? 103 | 基本思想:(分治) 104 | - 先从数列中取出一个数作为key值; 105 | - 将比这个数小的数全部放在它的左边,大于或等于它的数全部放在它的右边; 106 | - 对左右两个小数列重复第二步,直至各区间只有1个数。 107 | 108 | ## 堆排序的原理是什么? 109 | - 利用数组建立一个大根堆(父亲比孩子的值大); 110 | - 把堆顶元素和堆尾元素互换; 111 | - 把堆(无序区)的尺寸缩小1,并调用siftDown(arr, 0,len-1)从新的堆顶元素开始进行堆调整; 112 | - 重复步骤,直到堆的大小为1; 113 | 114 | ## 归并排序的原理? 115 | 归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。 116 | 首先考虑下如何将2个有序数列合并。这个非常简单,只要从比较2个数列的第一个数,谁小就先取谁, 117 | 取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。 118 | 119 | 120 | 121 | ## 找出数据流的中位数? 122 | - 插入排序,然后找到中间位置。 123 | - 借鉴快排的思想,随机取一个数,大于它的排左边,小于它的排右边。检测所选数字是否位于数组中间。 124 | 是的话就返回,不是的话,小于数组中间位置就对右边递归,大于数组中间位置就对左边递归。 125 | 126 | 127 | ## 堆和栈的区别? 128 | - 堆可以被看成是一棵完全二叉树树,如:堆排序。 129 | - 一种先进后出的数据结构。 130 | 131 | 132 | ## Java优先级队列? 133 | PriorityQueue是一个基于优先级堆的无界队列,它的元素是按照自然顺序(natural order)排序的。 134 | 在创建的时候,我们可以给它提供一个负责给元素排序的比较器。PriorityQueue不允许null值,因为 135 | 他们没有自然顺序,或者说他们没有任何的相关联的比较器。最后,PriorityQueue不是线程安全的, 136 | 入队和出队的时间复杂度是O(log(n))。 137 | 138 | 139 | 140 | ## 为什么要设计后缀表达式,有什么好处? 141 | 从左到右遍历表达式的每个数字和符号,遇到是数字就进栈,遇到是符号,就将处于栈顶两个数字出栈, 142 | 进行运算,运算结果进栈,一直到最终获得结果。 143 | 对于表达式计算非常方便 144 | 145 | 146 | ## LRU算法的实现原理? 147 | 148 | 重写LinkedHashMap 149 | ``` 150 | public class LRUCache extends LinkedHashMap { 151 | private final int MAX_CACHE_SIZE; 152 | 153 | public LRUCache2(int cacheSize) { 154 | super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true); 155 | MAX_CACHE_SIZE = cacheSize; 156 | } 157 | 158 | @Override 159 | protected boolean removeEldestEntry(Map.Entry eldest) { 160 | return size() > MAX_CACHE_SIZE; 161 | } 162 | } 163 | ``` 164 | 165 | 166 | ## 使用随机算法产生一个数,要求把1-1000W之间这些数全部生成。(考察高效率,解决产生冲突的问题) 167 | 声明一个大小为10000000的数组,每次随机出来的数i,都去数组下标i-1的位置看是否为0,如果为0的话, 168 | 写入这个数字,并记录数组的真实大小,等数组的真实大小为10000000的时候,停止随机。 169 | 170 | ## 两个有序数组的合并排序 171 | 同时遍历两个数组,每次对比两个数组当前坐标的值哪个小,小的存入新的数组,数组下表往后移一位, 172 | 大的那边坐标不变,继续下次大小对比。依次循环。 173 | 174 | ## 一个数组的倒序 175 | 每次交换头尾的值,并坐标向中间靠拢。 176 | 177 | 178 | ## 二叉树的前序,中序,后序遍历算法 179 | 180 | 前序递归: 181 | ``` 182 | public void preOrderTraverse1(TreeNode root) { 183 | if (root != null) { 184 | System.out.print(root.val + "->"); 185 | preOrderTraverse1(root.left); 186 | preOrderTraverse1(root.right); 187 | } 188 | } 189 | ``` 190 | 前序非递归: 191 | ``` 192 | public void preOrderTraverse2(TreeNode root) { 193 | Stack stack = new Stack<>(); 194 | TreeNode node = root; 195 | while (node != null || !stack.empty()) { 196 | if (node != null) { 197 | System.out.print(node.val + "->"); 198 | stack.push(node); 199 | node = node.left; 200 | } else { 201 | TreeNode tem = stack.pop(); 202 | node = tem.right; 203 | } 204 | } 205 | } 206 | ``` 207 | 208 | 209 | 中序递归: 210 | ``` 211 | public void inOrderTraverse(TreeNode root) { 212 | if (root != null) { 213 | inOrderTraverse(root.left); 214 | System.out.print(root.val + "->"); 215 | inOrderTraverse(root.right); 216 | } 217 | } 218 | ``` 219 | 中序非递归: 220 | ``` 221 | public void inOrderTraverse(TreeNode root) { 222 | Stack stack = new Stack<>(); 223 | TreeNode node = root; 224 | while (node != null || !stack.isEmpty()) { 225 | if (node != null) { 226 | stack.push(node); 227 | node = node.left; 228 | } else { 229 | TreeNode tem = stack.pop(); 230 | System.out.print(tem.val + "->"); 231 | node = tem.right; 232 | } 233 | } 234 | } 235 | ``` 236 | 237 | 后序递归: 238 | ``` 239 | public void postOrderTraverse(TreeNode root) { 240 | if (root != null) { 241 | postOrderTraverse(root.left); 242 | postOrderTraverse(root.right); 243 | System.out.print(root.val + "->"); 244 | } 245 | } 246 | ``` 247 | 248 | 后序非递归: 249 | ``` 250 | public void postOrderTraverse(TreeNode root) { 251 | TreeNode cur, pre = null; 252 | 253 | Stack stack = new Stack<>(); 254 | stack.push(root); 255 | 256 | while (!stack.empty()) { 257 | cur = stack.peek(); 258 | if ((cur.left == null && cur.right == null) || (pre != null && (pre == cur.left || pre == cur.right))) { 259 | System.out.print(cur.val + "->"); 260 | stack.pop(); 261 | pre = cur; 262 | } else { 263 | if (cur.right != null) 264 | stack.push(cur.right); 265 | if (cur.left != null) 266 | stack.push(cur.left); 267 | } 268 | } 269 | } 270 | ``` 271 | 层次遍历: 272 | ``` 273 | public void levelOrderTraverse(TreeNode root) { 274 | if (root == null) { 275 | return; 276 | } 277 | Queue queue = new LinkedList(); 278 | queue.add(root); 279 | 280 | while (!queue.isEmpty()) { 281 | TreeNode node = queue.poll(); 282 | System.out.print(node.val + "->"); 283 | 284 | if (node.left != null) { 285 | queue.add(node.left); 286 | } 287 | if (node.right != null) { 288 | queue.add(node.right); 289 | } 290 | } 291 | } 292 | ``` 293 | 294 | 295 | ## DFS,BFS算法 296 | 深度优先算法:利用栈实现 297 | 广度优先算法:利用队列来实现 298 | 299 | 300 | 301 | ## 逆波兰计算器 302 | 利用栈与后缀表达式利于计算机方便计算 303 | 304 | 305 | ## Hoffman 编码 306 | 按权重将节点分布在一条右子树上,使得前缀不重复 307 | 308 | 309 | 310 | ## 平衡二叉树? 311 | 312 | - 平衡二叉树,左右高度之差不超过1,Add/delete可能造成高度>1,此时要旋转,维持平衡状态, 313 | - 避免二叉树退化为链表,让Add/Delete时间复杂度但控制在O(log2N),旋转算法2个方法,1是求树的高度, 314 | - 2是求2个高度最大值,1个空树高度为-1,只有1个根节点的树的高度为0,以后每一层+1,平衡树任意节点 315 | - 最多有2个儿子,因此高度不平衡时,此节点的2棵子树高度差为2。例如单旋转,双旋转,插入等。红黑树放弃 316 | - 完全平衡,追求大致平衡,保证每次插入最多要3次旋转就能平衡。 317 | 318 | 319 | 320 | ## 海量url去重类问题(布隆过滤器) 321 | 利用bit数组,通过不同的哈希函数定位数组中的坐标。 如果都存在的话,代表存在。有一定误判率。 322 | ## 数组和链表数据结构描述,各自的时间复杂度 323 | 数组查找是O(1),删除或者新增是O(N) 324 | 链表查找是O(N),删除或者新增是O(1) 325 | ## 二叉树遍历 326 | 递归遍历和循环遍历 327 | 328 | ## hash算法的有哪几种,优缺点,使用场景 329 | MD5和SHA256 330 | 1.保护密码。 331 | 2.防止文件篡改 332 | 3.数字签名(对整段内容加密比较费性能,所以只对哈希值进行加密) 333 | 4.文件秒传 334 | 335 | ## 实际场景问题解决,典型的TOP K问题 336 | 可以使用类似选择排序,类似快排,类似最大堆的算法 337 | 338 | 339 | ## 用三个线程按顺序循环打印abc三个字母,比如abcabcabc。 340 | 利用原子数字,对三取余,打印对应的abc。或者利用三个condition进行唤醒。 341 | 342 | ## 10亿个数字里里面找最小的10个。 343 | 分治,加最小堆 344 | ## 有1亿个数字,其中有2个是重复的,快速找到它,时间和空间要最优。 345 | 1b是8比特。 346 | 1kb是8192比特 347 | 大概12mb内存可以代表1亿数字的比特位 348 | 利用哈希来判断 349 | ## 2亿个随机生成的无序整数,找出中间大小的值。 350 | 准备一个大数组。依次放入数组。数字出现n次,对应下标值就为n,完成之后,遍历数组。 351 | 当累加数值为1亿时,对应下标即为中位数。时间复杂度即为O(N) 352 | 353 | 354 | ## 有3n+1个数字,其中3n个中是重复的,只有1个是不重复的,怎么找出来。 355 | 累加每个位置上的bit位%3 即为不重复的数字的bit位 356 | 357 | 358 | ## 写一个字符串(如hello)反转函数。 359 | 在原数组中对调首尾对称的位置 360 | 361 | ## 二分查找的时间复杂度,优势。 362 | log2N 优势是 性能平均 363 | 364 | 365 | ## 一个已经构建好的TreeSet,怎么完成倒排序。 366 | 递归更换左右子树即可 367 | 368 | 369 | ## 一个单向链表,删除倒数第N个数据。 370 | 准备两个指针,初始指向头结点, 1号指针先走n步,然后2号指针开始走,当1号指针走到尾节点时,2号指针即为倒数第N个数据。 371 | 372 | ## 200个有序的数组,每个数组里面100个元素,找出top20的元素。 373 | 依次遍历每个有序数组(假设数组从大到小),对比这200个数字,最大的放入top20数组,重复20次该操作,即可获得top20数组 374 | ### 单向链表,查找中间的那个元素。 375 | 一个步长为1的游标,一个步长为2的游标,当步长为2的游标到达链表末尾时,步长为1的游标即中间元素 376 | ## 10亿个数,找出最大的10个。 377 | 分割,或者流式读取,然后利用一个大小为10的小根堆。 378 | 或者分成100份,每份计算最大的10个数字,最后对比这个1000个数字 379 | ## 有几台机器存储着几亿淘宝搜索日志,你只有一台2g的电脑,怎么选出搜索热度最高的十个搜索关键词? 380 | 先划分成多个小文件,送进内存排序,然后再采用多路归并排序。 381 | ## 10万个数,输出从小到大? 382 | 快排等算法。 383 | ## 有十万个单词,找出重复次数最高十个? 384 | 先用遍历一次十万单词,放入hashmap中,key为单词,value为次数。然后再遍历hashmap,放入最小堆,可以使用priorityqueue或者大小为10的数组也行 385 | 386 | -------------------------------------------------------------------------------- /架构.md: -------------------------------------------------------------------------------- 1 | 2 | - 标★号为重要知识点 3 | 4 | ## id全局唯一且自增,如何实现? 5 | - Redis的 incr 和 increby 自增原子命令 6 | - 统一数据库的id发放 7 | - 美团Leaf Leaf——美团点评分布式ID生成系统(批发号段) 8 | - Twitter的snowflake算法 9 | - UUID 10 | 11 | 12 | 13 | ## ★如何设计算法压缩一段URL? 14 | 通过发号策略,给每一个过来的长地址,发一个号即可,小型系统直接用mysql的自增索引就搞定了。如果是大型应用,可以考虑各种分布式key-value系统做发号器。不停的自增就行了。第一个使用这个服务的人得到的短地址是http://xx.xx/0 第二个是 http://xx.xx/1 第11个是 http://xx.xx/a 第依次往后,相当于实现了一个62进制的自增字段即可。 15 | 16 | 常用的url压缩算法是短地址映射法。具体步骤是: 17 | 18 | 1. 将长网址用md5算法生成32位签名串,分为4段,,每段8个字符; 19 | 2. 对这4段循环处理,取每段的8个字符, 将他看成16进制字符串与0x3fffffff(30位1)的位与操作,超过30位的忽略处理; 20 | 3. 将每段得到的这30位又分成6段,每5位的数字作为字母表的索引取得特定字符,依次进行获得6位字符串; 21 | 4. 这样一个md5字符串可以获得4个6位串,取里面的任意一个就可作为这个长url的短url地址。 22 | 23 | ## ★Dubbo负载均衡策略? 24 | 随机、轮询、最少使用、一致性哈希(除了一致性哈希外,都有加权) 25 | 26 | ## 负载均衡算法? 27 | 28 | - 常见6种负载均衡算法:轮询,随机,源地址哈希,加权轮询,加权随机,最小连接数。 29 | 30 | - nginx5种负载均衡算法:轮询,weight轮询,ip_hash,fair(响应时间),url_hash 31 | 32 | - dubbo负载均衡算法:随机,轮询,最少活跃调用数,一致性Hash 33 | 34 | 35 | ## Dubbo中Zookeeper做注册中心,如果注册中心集群都挂掉,发布者和订阅者之间还能通信么? 36 | 可以,因为dubbo在注册中心挂掉之后,会从原先的缓存中读取连接地址。 37 | 38 | 39 | ## ★Dubbo完整的一次调用链路介绍? 40 | 41 | 调用方: 42 | 43 | 1. 将方法名方法参数传入InvokerInvocationHandler的invoke方法中,对于Object中的方法toString, hashCode, equals直接调用invoker的对应方法。 44 | 2. 然后进入(故障转移集群)MockClusterInvoker.invoke()方法中。三种调用策略:①不需要mock, 直接调用FailoverClusterInvoker。②强制mock,调用mock。③先调FailoverClusterInvoker,调用失败在mock. 45 | 3. FailoverClusterInvoker默认调用策略。①通过目录服务查找到所有订阅的服务提供者的Invoker对象。②路由服务根据策略(比如:容错策略)来过滤选择调用的Invokers。③通过负载均衡策略LoadBalance来选择一个Invoker 46 | 4. 执行选择的Invoker.invoker(invocation),经过监听器链,经过过滤器链,执行到远程调用的DubboInvoker。 47 | 5. DubboInvoker根据url 也就是根据服务提供者的长连接,这里封装成交互层对象ExchangeClient供这里调用,判断远程调用类型同步,异步还是oneway模式。ExchangeClient发起远程调用。 48 | 6.获取调用结果:①Oneway返回空RpcResult②异步,直接返回空RpcResult, ResponseFuture回调③同步, ResponseFuture模式同步转异步,等待响应返回 49 | 50 | 消费方: 51 | 52 | 1. 通过Invocation获取服务名和端口组成serviceKey=com.alibaba.dubbo.demo.DemoService:20880, 从DubboProtocol的exproterMap中获取暴露服务的DubboExporter, 在从dubboExporter 获取invoker返回 53 | 2. 经过过滤器链。 54 | 3. 经过监听器链。 55 | 4. 到达执行真正调用的invoker, 这个invoker由代理工厂ProxyFactory.getInvoker(demoService, DemoService.class, registryUrl)创建,具体请看代理那部分介绍。 56 | 5. 调用demoService实例方法,将结果封装成RpcResult返回。 57 | 58 | 59 | ## ★SpringCloud和Dubbo有什么不一样? 60 | 1.dubbo采用RPC的方式交互,SpringCloud采用Http,restful协议进行交互。 61 | 2.dubbo依赖zookeeper进行服务注册,Springloud自己拥有自己的服务注册中心。 62 | 3.dubbo需要强依赖,需要持有相同的类或者jar包,springcloud弱依赖,但需要通过接口文档进行约束。 63 | 4.C数据一致性,A服务可用性,P服务对网络分区故障的容错性,Zookeeper 保证的是CP,euraka保证的是AP。 64 | 65 | 66 | ## 使用Redis如何实现分布式锁? 67 | 68 | setnx指令,设置锁的有效时间防止死锁。设置一个随机值来标识锁的持有人,利用这个随机值来释放锁。 69 | 70 | 71 | ## Tomcat如何优化? 72 | - 虚拟机参数: 73 | 1. server模式。 74 | 2. 最大堆最小堆大小。 75 | 3. 年轻代和老年代的比例。 76 | 4. 开启优化。 77 | 5. 使用偏向锁。 78 | 6. gc年龄。 79 | 7. 合适的gc 80 | - tomcat参数: 81 | 1. maxThread。 82 | 2. minThread。 83 | 3. acceptCount。 84 | 4. connectionTimeout。 85 | 6. maxProcessors与minProcessors。 86 | 87 | 88 | 89 | ## ★幂等的处理方式? 90 | 91 | 1. 查询与删除操作是天然幂等 92 | 2. 唯一索引,防止新增脏数据 93 | 3. token机制,防止页面重复提交 94 | 4. 悲观锁 for update 95 | 5. 乐观锁(通过版本号/时间戳实现, 通过条件限制where avai_amount-#subAmount# >= 0) 96 | 6. 分布式锁 97 | 7. 状态机幂等(如果状态机已经处于下一个状态,这时候来了一个上一个状态的变更,理论上是不能够变更的,这样的话,保证了有限状态机的幂等。) 98 | 8. select + insert(并发不高的后台系统,或者一些任务JOB,为了支持幂等,支持重复执行) 99 | 9. redis存储订单号,如果已存在的话说明已经处理过 100 | 101 | 102 | ## 后台系统怎么防止用户恶意频繁访问? 103 | 设计一个数据结构,有用户id,当前秒数,调用次数。每次请求时对比当前秒数和该对象内的是否一致,一致的话累加调用次数。不一致的话,将当前秒数替换成新的,调用次数清0。 104 | 105 | ## ★请谈谈单点登录原理? 106 | 107 | - 同域下的单点登录,只需共享session即可。 108 | - 登录业务系统,跳转至SSO服务器,判断用户名密码正确,在sso域下种下cookie,在session中标记为登录,返回一个ticket,跳转到业务系统,业务系统再拿这个ticket跑去SSO服务器验证ticket是否有效,有效的话,在业务系统session中设置为已登录即可。 109 | 110 | 111 | 112 | - 相比于单系统登录,sso需要一个独立的认证中心,只有认证中心能接受用户的用户名密码等安全信息,其他系统不提供登录入口,只接受认证中心的间接授权。间接授权通过令牌实现,sso认证中心验证用户的用户名密码没问题,创建授权令牌,在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,即得到了授权,可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同。这个过程,也就是单点登录的原理,用下图说明 113 | 114 | - 单点登录自然也要单点注销,在一个子系统中注销,所有子系统的会话都将被销毁,用下面的图来说明 115 | 116 | ## ★谈谈项目中分布式事务应用场景? 117 | 118 | 1. 学勤系统与账户余额系统 119 | 2. 招生系统与账户余额系统 120 | 121 | 122 | ## ★MQ和数据库的一致性问题,MQ消息最终一致性。 123 | 124 | 1. 事务消息与普通消息的区别就在于消息生产环节,生产者首先预发送一条消息到MQ(这也被称为发送half消息) 125 | 126 | 2. MQ接受到消息后,先进行持久化,则存储中会新增一条状态为待发送的消息 127 | 128 | 3. 然后返回ACK给消息生产者,此时MQ不会触发消息推送事件 129 | 130 | 4. 生产者预发送消息成功后,执行本地事务 131 | 132 | 5. 执行本地事务,执行完成后,发送执行结果给MQ 133 | 134 | 6. MQ会根据结果删除或者更新消息状态为可发送 135 | 136 | 7. 如果消息状态更新为可发送,则MQ会push消息给消费者,后面消息的消费和普通消息是一样的 137 | 138 | 139 | ## ★正在处理的队列突然断电怎么办? 140 | 141 | - 正在处理的实现事务功能,下次自动回滚。 142 | - 队列实现持久化储存,下次启动自动载入。 143 | - 添加标志位,未处理 0,处理中 1,已处理 2。每次启动的时候,把所有状态为 1 的,置为 0。 144 | - 关键性的应用就给电脑配个 UPS。 145 | 146 | 147 | ## ★服务限流的方式 148 | 149 | - 漏桶:水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求。 150 | - 令牌桶算法:系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token,如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,如果没有Token就拒绝服务。 151 | - 基于redis实现的限流:假设每分钟访问次数不能超过10次,在Redis中创建一个键,过期60秒,对此服务接口的访问就把键值加1,在60秒内增加到10的时候,禁止访问服务接口。 152 | - 计数器,滑动窗口(假设窗口为10s,则建立一个大小为10的数组,然后每次让当前秒数除10,落到哪个格子就累加,每一时刻数组的和就是窗口的数值) 153 | 154 | - 令牌桶可以应对突发的大流量 155 | - 漏斗算法用于请求恒定速率通过 156 | 157 | 158 | ## RabbitMQ消息堆积怎么处理? 159 | 160 | 新建一个topic,partition是原来的10倍;然后写一个临时的分发数据的consumer程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的10倍数量的queue;接着临时征用10倍的机器来部署consumer,每一批consumer消费一个临时queue的数据;等快速消费完积压数据之后,得恢复原先部署架构,重新用原先的consumer机器来消费消息; 161 | 162 | 163 | ## ★kafka消息会不会丢失? 164 | 165 | Kafka消息发送分同步(sync)、异步(async)两种方式。默认是使用同步方式,可通过producer.type属性进行配置;Kafka保证消息被安全生产,有三个选项分别是0,1,-1。 166 | - 通过request.required.acks属性进行配置: 167 | - 0代表:不进行消息接收是否成功的确认(默认值); 168 | - 1代表:当Leader副本接收成功后,返回接收成功确认信息; 169 | - -1代表:当Leader和Follower副本都接收成功后,返回接收成功确认信息; 170 | 171 | 网络异常: 172 | 173 | acks设置为0时,不和Kafka集群进行消息接受确认,当网络发生异常等情况时,存在消息丢失的可能; 174 | 175 | 客户端异常: 176 | 177 | 异步发送时,消息并没有直接发送至Kafka集群,而是在Client端按一定规则缓存并批量发送。在这期间,如果客户端发生死机等情况,都会导致消息的丢失; 178 | 179 | 缓冲区满了: 180 | 181 | 异步发送时,Client端缓存的消息超出了缓冲池的大小,也存在消息丢失的可能; 182 | 183 | Leader副本异常: 184 | 185 | acks设置为1时,Leader副本接收成功,Kafka集群就返回成功确认信息,而Follower副本可能还在同步。这时Leader副本突然出现异常,新Leader副本(原Follower副本)未能和其保持一致,就会出现消息丢失的情况; 186 | 187 | 188 | 以上就是消息丢失的几种情况,在日常应用中,我们需要结合自身的应用场景来选择不同的配置。 189 | 想要更高的吞吐量就设置:异步、ack=0;想要不丢失消息数据就选:同步、ack=-1策略 190 | 191 | 192 | ## ★RabbitMQ的消息丢失解决方案? 193 | 194 | - 消息持久化:Exchange 设置持久化:durable:true; 195 | - Queue 设置持久化; 196 | - Message持久化发送。 197 | - ACK确认机制:消息发送确认;消息接收确认。 198 | 199 | ## ★kafka的leader副本选举? 200 | 201 | - 如果某个分区patition的Leader挂了,那么其它跟随者将会进行选举产生一个新的leader,之后所有的读写就会转移到这个新的Leader上,在kafka中,其不是采用常见的多数选举的方式进行副本的Leader选举,而是会在Zookeeper上针对每个Topic维护一个称为ISR(in-sync replica,已同步的副本)的集合,显然还有一些副本没有来得及同步。只有这个ISR列表里面的才有资格成为leader(先使用ISR里面的第一个,如果不行依次类推,因为ISR里面的是同步副本,消息是最完整且各个节点都是一样的)。 202 | - 通过ISR,kafka需要的冗余度较低,可以容忍的失败数比较高。假设某个topic有f+1个副本,kafka可以容忍f个不可用,当然,如果全部ISR里面的副本都不可用,也可以选择其他可用的副本,只是存在数据的不一致。 203 | 204 | 205 | ## kafka消息的检索? 206 | 207 | 其实很简单主要是用二分查找算法,比如我们要查找一条offest=10000的文件,kafka首先会在对应分区下的log文件里采用二分查看定位到某个记录该offest 208 | =10000这条消息的log,然后从相应的index文件定位其偏移量,然后拿着偏移量到log里面直接获取。这样就完成了一个消息的检索过程。 209 | 210 | 211 | ## ★RabbitMQ 集群方式? 212 | 213 | 1. 普通集群: 214 | 215 | 216 | - 以两个节点(rabbit01、rabbit02)为例来进行说明。rabbit01和rabbit02两个节点仅有相同的元数据,即队列的结构,但消息实体只存在于其中一个节点rabbit01(或者rabbit02)中。 217 | - 当消息进入rabbit01节点的Queue后,consumer从rabbit02节点消费时,RabbitMQ会临时在rabbit01、rabbit02间进行消息传输,把A中的消息实体取出并经过B发送给consumer。所以consumer应尽量连接每一个节点,从中取消息。即对于同一个逻辑队列,要在多个节点建立物理Queue。否则无论consumer连rabbit01或rabbit02,出口总在rabbit01,会产生瓶颈。当rabbit01节点故障后,rabbit02节点无法取到rabbit01节点中还未消费的消息实体。如果做了消息持久化,那么得等rabbit01节点恢复,然后才可被消费;如果没有持久化的话,就会产生消息丢失的现象。 218 | 219 | 2. 镜像集群: 220 | 221 | 222 | - 在普通集群的基础上,把需要的队列做成镜像队列,消息实体会主动在镜像节点间同步,而不是在客户端取数据时临时拉取,也就是说多少节点消息就会备份多少份。该模式带来的副作用也很明显,除了降低系统性能外,如果镜像队列数量过多,加之大量的消息进入,集群内部的网络带宽将会被这种同步通讯大大消耗掉。所以在对可靠性要求较高的场合中适用 223 | - 由于镜像队列之间消息自动同步,且内部有选举master机制,即使master节点宕机也不会影响整个集群的使用,达到去中心化的目的,从而有效的防止消息丢失及服务不可用等问题 224 | 225 | ## ★kafka高性能的原因? 226 | 227 | 1. Broker NIO异步消息处理,实现了IO线程与业务线程分离; 228 | 229 | 2. 磁盘顺序写; 230 | 231 | 3. 零拷贝(跳过用户缓冲区的拷贝,建立一个磁盘空间和内存的直接映射,数据不再复制到用户态缓冲区); 232 | 233 | 4. 分区/分段(每次文件操作都是对一个小文件的操作,非常轻便,同时也增加了并行处理能力); 234 | 235 | 5. 批量发送 (可以指定缓存的消息达到某个量的时候就发出去,或者缓存了固定的时间后就发送出去,大大减少服务端的I/O次数) 236 | 237 | 6. 数据压缩 238 | 239 | 240 | ## ★ZooKeeper分布式高可用 241 | 242 | - ZooKeeper 运行期间,集群中至少有过半的机器保存了最新数据。集群超过半数的机器能够正常工作,集群就能够对外提供服务。 243 | 244 | - zookeeper可以选出N台机器作主机,它可以实现M:N的备份;keepalive只能选出1台机器作主机,所以keepalive只能实现M:1的备份。 245 | 246 | - 通常有以下两种部署方案:双机房部署(一个稳定性更好、设备更可靠的机房,这个机房就是主要机房,而另外一个机房则更加廉价一些,例如,对于一个由 7 台机器组成的 ZooKeeper 集群,通常在主要机房中部署 4 台机器,剩下的 3 台机器部署到另外一个机房中);三机房部署(无论哪个机房发生了故障,剩下两个机房的机器数量都超过半数。在三个机房中都部署若干个机器来组成一个 ZooKeeper 集群。假设机器总数为 N,各机房机器数:N1 = (N-1)/2 ,N2=1~(N-N1)/2 ,N3 = N - N1 - N2 )。 247 | 248 | - 水平扩容就是向集群中添加更多机器,Zookeeper2种方式(不完美),一种是集群整体重启,另外一种是逐台进行服务器的重启。 249 | 250 | 251 | 252 | ## ★如何设计秒杀 253 | 254 | 1. 对于大促时候的秒杀活动,一般运营会配置静态的活动页面,配置静态活动页面主要有两个目的一方面是为了便于在各种社交媒体分发,另一方面是因为秒杀活动页的流量是大促期间最大的,通过配置成静态页面可以将页面发布在公有云上动态的横向扩展; 255 | 256 | 2. 将秒杀活动的静态页面提前刷新到CDN节点,通过CDN节点的页面缓存来缓解访问压力和公司网络带宽,CDN上缓存js、css和图片; 257 | 258 | 3. 将活动H5页面部署在公有云的web server上,使用公有云最大的好处就是能够根据活动的火爆程度动态扩容而且成本较低,同时将访问压力隔离在公司系统外部; 259 | 260 | 4. 在提供真正商品秒杀业务功能的app server上,需要进行交易限流、熔断控制,防止因为秒杀交易影响到其他正常服务的提供,我们在限流和熔断方面使用了hystrix,在核心交易的controller层通过hystrix进行交易并发限流控制,当交易流量超出我们设定的限流最大值时,会对新交易进行熔断处理固定返回静态失败报文。 261 | 262 | 5. 服务降级处理,除了上面讲到的限流和熔断控制,我们还设定了降级开关,对于首页、购物车、订单查询、大数据等功能都会进行一定程度的服务降级,例如我们会对首页原先动态生成的大数据页面布局降级为所有人看到的是一样的页面、购物车也会降级为不在一级页面的tabbar上的购物车图标上显示商品数量、历史订单的查询也会提供时间周期较短的查询、大数据商品推荐也会提供一样的商品推荐,通过这样的降级处理能够很好的保证各个系统在大促期间能够正常的提供最基本的服务,保证用户能够正常下单完成付款。 263 | 264 | 6. 上面介绍的都是如何保证能扛住高并发,下面介绍下整个方案中如何防止超卖现象的发生,我们日常的下单过程中防止超卖一般是通过在数据库上实施乐观锁来完成,使用乐观锁虽然比for update这种悲观锁方式性能要好很多,但是还是无法满足秒杀的上万并发需求,我们的方案其实也很简单实时库存的扣减在缓存中进行,异步扣减数据库中的库存,保证缓存中和数据库中库存的最终一致性。 265 | 266 | - 在这个方案中我们使用的分布式缓存是redis,使用了codis集群方案稳定性和高可用方面还是比较有保证的,因为redis是单线程写,所以也不用担心线程安全的问题,redis自身就能够保证数据的强一致性,在下单的事务中包含了实时扣减缓存中的库存和异步发送队列,由队列处理器再异步从队列中取出订单根据订单信息扣减库存系统数据库中的商品数量。 267 | 268 | 269 | 270 | 271 | ## 高性能统计UV的方式? 272 | 273 | (1)使用redis的set集合 274 | 275 | (2)使用redis的bitmap(注意内存消耗) 276 | 277 | 278 | ## ★缓存击穿的解决办法 279 | 280 | 1. 加载DB时同步,其他则等待;DB端做SQL合并,Queue合并排队处理; 281 | 2. 部分缓存设置为永不过期; 282 | 3. 读取数据时候则等待500ms,500ms缓存应该已经加载完成; 283 | 284 | ## ★后台系统怎么防止请求重复提交。 285 | 前端js,控制按钮。前端放置令牌。 286 | 数据库唯一索引。redis看key是否存在。或者数据库字段状态。 287 | 288 | 289 | ## 有没有遇到进线上GC,出现的症状是什么样的,怎么解决的? 290 | 利用堆快照,查看到底是哪些对象占用大量内存导致经常gc 291 | 292 | 293 | ## ★假如你的项目出现性能瓶颈了,你觉得可能会是哪些方面,怎么解决问题。 294 | DB层面,有可能是sql,索引,表过大,数据库压力。 295 | 缓存层面:有可能缓存命中率差,redis性能瓶颈,需要扩容 296 | 服务器压力:服务器处理瓶颈 297 | Java层面:代码写法 298 | 前端层面:cdn压力,页面压力 299 | 300 | ## 情景题:如果一个外卖配送单子要发布,现在有200个骑手都想要接这一单,如何保证只有一个骑手接到单子? 301 | 分布式锁,或者幂等接口,CAS乐观锁 302 | 303 | ## 场景题:美团首页每天会从10000个商家里面推荐50个商家置顶,每个商家有一个权值,你如何来推荐?第二天怎么更新推荐的商家? 304 | 可以借鉴下stackoverflow,视频网站等等的推荐算法。 305 | 306 | 307 | ## 场景题:微信抢红包问题 308 | 悲观锁,乐观锁,存储过程放在mysql数据库中。 309 | 310 | 311 | ## 场景题:1000个任务,分给10个人做,你怎么分配,先在纸上写个最简单的版本,然后优化。 312 | 全局队列,把1000任务放在一个队列里面,然后每个人都是取,完成任务。 313 | 分为10个队列,每个人分别到自己对应的队列中去取务。 314 | 315 | ## 分布式服务如何跟踪? 316 | 317 | 调用可以实现跟踪系统,可以在业务日志中添加调用链ID,各个环节RPC均添加调用时延,QPS等。 318 | 319 | 非业务组件应该少加入业务代码,服务调用采用买点,也会采用配置采样率方式,买点即当前节点的上下文信息,包含TraceId,RPCId,开始结束时间,类型,协议,调用方IP,端口,服务名等,以及其他异常信息,报文等扩展,日志采用离线+实时的如flume结合kafka等,应按照TraceId汇总日志后按RPCId顺序整理。 320 | 321 | 322 | ## Sentinel 工作原理? 323 | 324 | 1. 每个 Sentinel 以每秒钟一次的频率向它所知的 Master,Slave 以及其他 Sentinel 实例发送一个 PING 命令; 325 | 2 如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel 标记为主观下线; 326 | 3. 如果一个 Master 被标记为主观下线,则正在监视这个 Master 的所有 Sentinel 要以每秒一次的频率确认 Master 的确进入了主观下线状态; 327 | 4. 当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认 Master 的确进入了主观下线状态,则 Master 会被标记为客观下线; 328 | 5. 在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有 Master,Slave 发送 INFO 命令;当 Master 被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次; 329 | 6. 若没有足够数量的 Sentinel 同意 Master 已经下线, Master 的客观下线状态就会被移除; 330 | 7. 若 Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除。 331 | 332 | 333 | ## redis的主从? 334 | 监控( Monitoring ): Redis Sentinel 实时监控主服务器和从服务器运行状态; 335 | 自动故障转移:如果一个 master 不正常运行了,哨兵可以启动一个故障转移进程,将一个 slave 升级成为 master,其他的 slave 被重新配置使用新的 master,并且应用程序使用 Redis 服务端通知的新地址; 336 | 337 | 338 | ## 讲讲分布式唯一ID。 339 | 雪花算法: 340 | 341 | - 1 bit:不用,为啥呢?因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个 bit 统一都是 0。 342 | - 41 bit:表示的是时间戳,单位是毫秒。41 bit 可以表示的数字多达 2^41 - 1,也就是可以标识 2^41 - 1 个毫秒值,换算成年就是表示69年的时间。 343 | - 10 bit:记录工作机器 id,代表的是这个服务最多可以部署在 2^10台机器上哪,也就是1024台机器。但是 10 bit 里 5 个 bit 代表机房 id,5 个 bit 代表机器 id。意思就是最多代表 2^5个机房(32个机房),每个机房里可以代表 2^5 个机器(32台机器)。 344 | - 12 bit:这个是用来记录同一个毫秒内产生的不同 id,12 bit 可以代表的最大正整数是 2^12 - 1 = 4096,也就是说可以用这个 12 bit 代表的数字来区分同一个毫秒内的 4096 个不同的 id。 345 | 346 | 347 | 348 | 349 | ## ★什么是一致性hash 350 | 利用哈希环进行一致性哈希 351 | 352 | 353 | ## ★如何使用redis和zookeeper实现分布式锁?有什么区别优缺点,会有什么问题,分别适用什么场景。(延伸:如果知道redlock,讲讲他的算法实现,争议在哪里) 354 | 355 | Redis实现比较复杂,流程如下: 356 | 357 | - 根据lockKey区进行setnx(set not exist,顾名思义,如果key值为空,则正常设置,返回1,否则不会进行设置并返回0)操作,如果设置成功,表示已经获得锁,否则并没有获取锁。 358 | - 如果没有获得锁,去Redis上拿到该key对应的值,在该key上我们存储一个时间戳(用毫秒表示,t1),为了避免死锁以及其他客户端占用该锁超过一定时间(5秒),使用该客户端当前时间戳,与存储的时间戳作比较。 359 | - 如果没有超过该key的使用时限,返回false,表示其他人正在占用该key,不能强制使用;如果已经超过时限,那我们就可以进行解锁,使用我们的时间戳来代替该字段的值。 360 | - 但是如果在setnx失败后,get该值却无法拿到该字段时,说明操作之前该锁已经被释放,这个时候,最好的办法就是重新执行一遍setnx方法来获取其值以获得该锁。 361 | 362 | - 缺点:有可能master崩溃,导致多节点获取到锁。 363 | 364 | 365 | 从实现难度上来说,Zookeeper实现非常简单,实现分布式锁的基本逻辑: 366 | 367 | - 客户端调用create()方法创建名为“locknode/guid-lock-”的节点,需要注意的是,这里节点的创建类型需要设置为EPHEMERAL_SEQUENTIAL。 368 | - 客户端调用getChildren(“locknode”)方法来获取所有已经创建的子节点。 369 | - 客户端获取到所有子节点path之后,如果发现自己在步骤1中创建的节点是所有节点中序号最小的,那么就认为这个客户端获得了锁。 370 | - 如果创建的节点不是所有节点中需要最小的,那么则监视比自己创建节点的序列号小的最大的节点,进入等待。直到下次监视的子节点变更的时候,再进行子节点的获取,判断是否获取锁。 371 | 372 | 区别: 373 | 374 | - Redis分布式锁,需要自己不断去尝试获取锁,比较消耗性能 375 | - ZooKeeper分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小 376 | 377 | - 如果Redis获取锁的那个客户端挂了,那么只能等待超时时间之后才能释放锁 378 | - 而对于ZooKeeper,因为创建的是临时znode,只要客户端挂了,znode就没了,此时就自动释放锁 379 | 380 | 381 | redlock算法实现: 382 | 383 | 假设有5个完全独立的redis主服务器 384 | 385 | 1. 获取当前时间戳 386 | 2. client尝试按照顺序使用相同的key,value获取所有redis服务的锁,在获取锁的过程中的获取时间比锁过期时间短很多,这是为了不要过长时间等待已经关闭的redis服务。并且试着获取下一个redis实例。比如:TTL为5s,设置获取锁最多用1s,所以如果一秒内无法获取锁,就放弃获取这个锁,从而尝试获取下个锁 387 | 3. client通过获取所有能获取的锁后的时间减去第一步的时间,这个时间差要小于TTL时间并且至少有3个redis实例成功获取锁,才算真正的获取锁成功 388 | 4. 如果成功获取锁,则锁的真正有效时间是 TTL减去第三步的时间差 的时间;比如:TTL 是5s,获取所有锁用了2s,则真正锁有效时间为3s(其实应该再减去时钟漂移); 389 | 5. 如果客户端由于某些原因获取锁失败,便会开始解锁所有redis实例;因为可能已经获取了小于3个锁,必须释放,否则影响其他client获取锁 390 | 391 | redlock的争议点:(fgc导致的问题) 392 | 对于提升效率的场景下,RedLock 太重。 393 | 对于对正确性要求极高的场景下,RedLock 并不能保证正确性。 394 | 395 | ## 2pc 3pc 的区别,解决了哪些问题。 396 | 3pc 将2pc中的一阶段拆为 canCommit和prepareCommit 397 | 398 | 399 | 二阶段提交有几个缺点: 400 | 401 | - 同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。单点故障。由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。 402 | 二阶段无法解决的问题:协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。 403 | 404 | - 3pc比2pc 减少事务阻塞范围 。3pc在超时后会自动提交。相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。 405 | 406 | 407 | ## 什么是paxos算法, 什么是zab协议。 408 | paxos算法的推导, 409 | 如果只有一个人投票的话,那么每个人必须接受第一个提议。 410 | 这样又会导致三个人分别投不同的票,形不成大多数。 411 | 推导出可以选多次票,但多次票有可能投自己又投别人,形成多个大多数,所以又加了限制条件,只能同意之前同意过的。 412 | 再推导出当两个机子重连的话,机子必须接受第一个发给他的提案,这样就违背了之前选好的。 413 | 所以当服务器重连的时候,必须发给他之前同意好的提案。 414 | https://blog.51cto.com/12615191/2086264 415 | 416 | zab协议: 417 | 418 | 原子广播: 419 | 420 | - ZAB 协议的消息广播过程使用的是一个原子广播协议,类似一个 二阶段提交过程。对于客户端发送的写请求,全部由 Leader 接收,Leader 将请求封装成一个事务 Proposal,将其发送给所有 Follwer ,然后,根据所有 Follwer 的反馈,如果超过半数成功响应,则执行 commit 操作(先提交自己,再发送 commit 给所有 Follwer)。 421 | 崩溃恢复: 422 | 423 | - 针对这些问题,ZAB 定义了 2 个原则: 424 | - ZAB 协议确保那些已经在 Leader 提交的事务最终会被所有服务器提交。 425 | - ZAB 协议确保丢弃那些只在 Leader 提出/复制,但没有提交的事务。 426 | 427 | - 如果让 Leader 选举算法能够保证新选举出来的 Leader 服务器拥有集群总所有机器编号(即 ZXID 最大)的事务,那么就能够保证这个新选举出来的 Leader 一定具有所有已经提交的提案。 428 | - 当 Follower 链接上 Leader 之后,Leader 服务器会根据自己服务器上最后被提交的 ZXID 和 Follower 上的 ZXID 进行比对,比对结果要么回滚,要么和 Leader 同步。 429 | 430 | ## ★Dubbo的原理,有看过源码么,数据怎么流转的,怎么实现集群,负载均衡,服务注册和发现,重试转发,快速失败的策略是怎样的 。 431 | 432 | 433 | - 第一层:service 层,接口层,给服务提供者和消费者来实现的 434 | - 第二层:config 层,配置层,主要是对 dubbo 进行各种配置的 435 | - 第三层:proxy 层,服务代理层,无论是 consumer 还是 provider,dubbo 都会给你生成代理,代理之间进行网络通信 436 | - 第四层:registry 层,服务注册层,负责服务的注册与发现 437 | - 第五层:cluster 层,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务 438 | - 第六层:monitor 层,监控层,对 rpc 接口的调用次数和调用时间进行监控 439 | - 第七层:protocal 层,远程调用层,封装 rpc 调用 440 | - 第八层:exchange 层,信息交换层,封装请求响应模式,同步转异步 441 | - 第九层:transport 层,网络传输层,抽象 mina 和 netty 为统一接口 442 | - 第十层:serialize 层,数据序列化层 443 | 444 | 445 | ## ★一次RPC请求的流程是什么。 446 | 1. 服务消费方(client)调用以本地调用方式调用服务; 447 | 2. client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体; 448 | 3. client stub找到服务地址,并将消息发送到服务端; 449 | 4. server stub收到消息后进行解码; 450 | 5. server stub根据解码结果调用本地的服务; 451 | 6. 本地服务执行并将结果返回给server stub; 452 | 7. server stub将返回结果打包成消息并发送至消费方; 453 | 8. client stub接收到消息,并进行解码; 454 | 9. 服务消费方得到最终结果。 455 | 456 | 457 | ## ★解释什么是MESI协议(缓存一致性)。 458 | MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。 459 | (另外一种硬件层面的解决是总线锁) 460 | 461 | 462 | ## Zookeeper的用途,选举的原理是什么,适用场景。 463 | 用途:类似文件系统的分布式协调服务。 464 | 465 | 选举原理: 466 | 467 | 1. Zookeeper集群中只有超过半数以上的服务器启动,集群才能正常工作; 468 | 2. 在集群正常工作之前,myid小的服务器给myid大的服务器投票,直到集群正常工作,选出Leader; 469 | 3. 选出Leader之后,之前的服务器状态由Looking改变为Following,以后的服务器都是Follower。 470 | 适用场景: 471 | 1. 命名服务 472 | 2. 配置管理 473 | 3. 集群管理 474 | 4. 分布式锁 475 | 5. 队列管理 476 | 477 | ## Zookeeper watch机制原理。 478 | 1. 客户端注册Watcher到服务端; 479 | 2. 服务端发生数据变更; 480 | 3. 服务端通知客户端数据变更; 481 | 4. 客户端回调Watcher处理变更应对逻辑; 482 | 483 | ## 什么叫数据一致性,你怎么理解数据一致性。 484 | - 一致性又可以分为强一致性与弱一致性。 485 | - 强一致性可以理解为在任意时刻,所有节点中的数据是一样的。同一时间点,你在节点A中获取到key1的值与在节点B中获取到key1的值应该都是一样的。 486 | - 弱一致性包含很多种不同的实现,目前分布式系统中广泛实现的是最终一致性。 487 | - 所谓最终一致性,就是不保证在任意时刻任意节点上的同一份数据都是相同的,但是随着时间的迁移,不同节点上的同一份数据总是在向趋同的方向变化。也可以简单的理解为在一段时间后,节点间的数据会最终达到一致状态。 488 | 489 | 490 | 491 | ## 请思考一个方案,实现分布式环境下的countDownLatch。 492 | zookeeper,判断某个节点下的子节点到达一定数目后,则执行,否则等待。 493 | 494 | ## ★用过哪些MQ,怎么用的,和其他mq比较有什么优缺点,MQ的连接是线程安全的吗 495 | | 特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka | 496 | |---|---|---|---|---| 497 | | 单机吞吐量 | 万级,比 RocketMQ、Kafka 低一个数量级 | 同 ActiveMQ | 10 万级,支撑高吞吐 | 10 万级,高吞吐,一般配合大数据类的系统来进行实时数据计算、日志采集等场景 | 498 | | topic 数量对吞吐量的影响 | | | topic 可以达到几百/几千的级别,吞吐量会有较小幅度的下降,这是 RocketMQ 的一大优势,在同等机器下,可以支撑大量的 topic | topic 从几十到几百个时候,吞吐量会大幅度下降,在同等机器下,Kafka 尽量保证 topic 数量不要过多,如果要支撑大规模的 topic,需要增加更多的机器资源 | 499 | | 时效性 | ms 级 | 微秒级,这是 RabbitMQ 的一大特点,延迟最低 | ms 级 | 延迟在 ms 级以内 | 500 | | 可用性 | 高,基于主从架构实现高可用 | 同 ActiveMQ | 非常高,分布式架构 | 非常高,分布式,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 | 501 | | 消息可靠性 | 有较低的概率丢失数据 | 基本不丢 | 经过参数优化配置,可以做到 0 丢失 | 同 RocketMQ | 502 | | 功能支持 | MQ 领域的功能极其完备 | 基于 erlang 开发,并发能力很强,性能极好,延时很低 | MQ 功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用 | 503 | 504 | 505 | ## MQ系统的数据如何保证不丢失 506 | 发送消息后和接收消息后 确认机制 加上持久化 507 | 508 | ## MQ有可能发生重复消费,如何避免,如何做到幂等。 509 | 唯一主键,或者使用redis做id, 510 | 511 | ## 异步模式的用途和意义。 512 | 避免阻塞 513 | 514 | 515 | ## 使用kafka有没有遇到什么问题,怎么解决的。 516 | https://www.cnblogs.com/leaves1024/p/11073191.html 517 | https://blog.csdn.net/chizizhixin/article/details/78563595 518 | https://blog.csdn.net/lsh2366254/article/details/84910011 519 | 520 | 521 | ## 如何保证消息的有序性。消息处理的有序性。 522 | 使用同一个queue 523 | 524 | 525 | 526 | ## 消息的重发补发策略 527 | - 实时队列采用双队列模式,生产者将行为记录写入Queue1,worker服务从Queue1消费新鲜数据,如果异常则写入Queue2(主要保存异常数据),RetryWorker会监听Queue2,消费异常数据,如果还未处理成功按照一定的策略等待或者将异常数据再写入Queue2,如果数据发生积压可以调整worker的消费游标,从最新数据重新开始消费,保证了最新data得到处理,中间未处理的一段则可以启动backupWorker指定起止游标在消费完指定区间的数据后,backupWorker会自动停止。 528 | 529 | - DB降级开关后,可直接写入redis(storm),同时将数据写入一份到Retry队列,在开启DB降级开关后消费Retry队列中的数据,从而把数据写入到mysql中,达到最终一致性。MYSQL切分为分片为2的N次方,例如原来分为两个库d0和d1均放在s0服务器上,s0同时有备机s1,扩容只要几步骤:确保s0到s1服务器同步顺利,没有明显延迟;s0暂时关闭读写权限;确保s1已经完全同步到s0更新;s1开放读写权限;d1的dns由s0切换到s1;s0开放读写权限。 530 | -------------------------------------------------------------------------------- /框架.md: -------------------------------------------------------------------------------- 1 | - 标★号为重要知识点 2 | 3 | ## 说一下IOC和AOP? 4 | IOC是依赖反转,通过依赖注入DI实现IOC。主要思想是面向抽象编程而不是面向具体实现编程。反转的意思是从依赖具体 5 | 实现变为依赖抽象。 6 | 7 | AOP是面向切面编程,通过代理对代码无侵入的添加功能,实现切面编程。比如事务、日志等模块。 8 | 代理机制主要有jdk提供的动态代理和cglib提供的字节码动态代码。 9 | 10 | 11 | ## ★Spring中Bean的生命周期。 12 | 1. 首先这个bean在进行开始实例化的时候会先进行调用该类的构造函数,默认是单例的 13 | 2. 然后去注入属性中的bean 14 | 3. 如果bean实现了BeanNameAware接口,spring将bean的id传给setBeanName()方法; 15 | 4. 如果bean实现了BeanFactoryAware接口,spring将调用setBeanFactory方法,将BeanFactory实例传进来;使bean获得访问Spring容器的能力。 16 | 5. 如果bean实现了ApplicationContextAware接口,它的setApplicationContext()方法将被调用,将应用上下文的引用传入到bean中;使得bean获取访问对应上下文的能力。 17 | 6. 如果bean实现了BeanPostProcessor接口,它的postProcessBeforeInitialization方法将被调用;如AutowiredAnnotationBeanPostProcessor 18 | 7. 如果bean实现了InitializingBean接口,spring将调用它的afterPropertiesSet接口方法,类似的如果bean使用了init-method属性声明了初始化方法,该方法也会被调用; 19 | 8. 如果bean实现了BeanPostProcessor接口,它的postProcessAfterInitialization接口方法将被调用; 20 | 9. 此时bean已经准备就绪,可以被应用程序使用了,他们将一直驻留在应用上下文中,直到该应用上下文被销毁; 21 | 10. 若bean实现了DisposableBean接口,spring将调用它的distroy()接口方法。同样的,如果bean使用了destroy-method属性声明了销毁方法,则该方法被调用; 22 | 23 | ## Spring中注解Autowired和Resource的区别? 24 | 25 | 共同点: 26 | 27 | 两者都可以写在字段和setter方法上。两者如果都写在字段上,那么就不需要再写setter方法。 28 | 29 | 不同点 : 30 | 31 | (1)@Autowired 为Spring提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired; 32 | 只按照byType注入。 @Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值, 33 | 可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用。 34 | 35 | (2)@Resource 默认按照ByName自动注入,由J2EE提供,需要导入包javax.annotation.Resource。@Resource有两个重要的属性: 36 | name和type,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以,如果使用name属性, 37 | 则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射 38 | 机制使用byName自动注入策略。如果按name找不到对应的bean的话回退到ByType的自动注入策略。 39 | 40 | ## @Controller和@RestController的区别? 41 | @Controller 42 | 43 | 方法的返回值。默认是视图页面的跳转路径。 44 | 如果想返回json对象,必须在方法的上面加@ResponseBody 45 | 46 | @RestController 47 | 48 | 方法返回值,默认是json对象,也就是相当于@Controller里面的方法上添加了@ResponseBody 49 | 如果方法返回值需要跳转视图页面的话,那么方法的返回类型必须是View 或者ModelAndView. 50 | 51 | 52 | # 5.依赖注入的方式有哪几种? 53 | 54 | 1. set方法注入 55 | 2. 构造器注入 56 | 3. 注解注入 57 | 58 | ## ★Spring中IOC的原理? 59 | 第一个过程是Resource定位过程。这个Resource定位指的是BeanDefinition的资源定位,它由ResourceLoader通过统一的Resource接口来完成,这个Resource对各种形式的BeanDefinition的使用都提供了统一接口。对于这些BeanDefinition的存在形式,相信大家都不会感到陌生。比如,在文件系统中的Bean定义信息可以使用FileSystemResource来进行抽象;在类路径中的Bean定义信息可以使用前面提到的ClassPathResource来使用,等等。这个定位过程类似于容器寻找数据的过程,就像用水桶装水先要把水找到一样。 60 | 61 | 第二个过程是BeanDefinition的载入。这个载入过程是把用户定义好的Bean表示成IoC容器内部的数据结构,而这个容器内部的数据结构就是BeanDefinition。下面介绍这个数据结构的详细定义。具体来说,这个BeanDefinition实际上就是POJO对象在IoC容器中的抽象,通过这个BeanDefinition定义的数据结构,使IoC容器能够方便地对POJO对象也就是Bean进行管理。在下面的章节中,我们会对这个载入的过程进行详细的分析,使大家对整个过程有比较清楚的了解。 62 | 63 | 第三个过程是向IoC容器注册这些BeanDefinition的过程。这个过程是通过调用BeanDefinitionRegistry接口的实现来完成的。这个注册过程把载入过程中解析得到的BeanDefinition向IoC容器进行注册。通过分析,我们可以看到,在IoC容器内部将BeanDefinition注入到一个HashMap中去,IoC容器就是通过这个HashMap来持有这些BeanDefinition数据的。值得注意的是,这里谈的是IoC容器初始化过程,在这个过程中,一般不包含Bean依赖注入的实现。在Spring IoC的设计中,Bean定义的载入和依赖注入是两个独立的过程。依赖注入一般发生在应用第一次通过getBean向容器索取Bean的时候。但有一个例外值得注意,在使用IoC容器时有一个预实例化的配置,通过这个预实例化的配置(具体来说,可以通过为Bean定义信息中的lazyinit属性),用户可以对容器初始化过程作一个微小的控制,从而改变这个被设置了lazyinit属性的Bean的依赖注入过程。 64 | 65 | 66 | ## Spring容器如何创建? 67 | 在web.xml文件中配置ServletContextListener为spring的环境启动监听器。 68 | 然后启动的时候,读取配置好的xml文件,解析这个xml文件,根据xml文件中的bean定义,生成实例,填入到Bean容器中。之后就是注入了。 69 | 70 | 71 | ## SpringMVC容器和Spring容器的区别? 72 | Spring是web.xml中注册启动监听器时,读取spring相关的xml去生成bean,放入容器。 73 | SringMVC是web.xml中注册的一个Servlet,一般来说所有请求都经过这个servlet,这个serlvet在初始化的时候 74 | 读取springmvc.xml中的数据,扫描指定的包,注册好所有Controller,解析请求对应的方法,放入一个Map中,当 75 | 有请求进来的时候就从这个map中取对应的Controller。 76 | 77 | ## SpringMVC的控制器是不是单例模式,如果是,有什么问题,怎么解决? 78 | 是单例的。如何保证并发安全:1.不使用controller中的实例变量或类变量。2.或者在controller注解上加@Scope("prototype"),使之成为非单例的。3.使用ThreadLocal变量。 79 | 80 | 81 | ## ★Spring中BeanFactory和ApplicationContext的区别? 82 | 83 | - BeanFactory和ApplicationContext都是接口,并且ApplicationContext是BeanFactory的子接口。 84 | 85 | - BeanFactory是Spring中最底层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能。 86 | 87 | - 而ApplicationContext是Spring的一个更高级的容器,提供了更多的有用的功能。提供更多面向应用的功能。 88 | 89 | - ApplicationContext提供的额外的功能:国际化的功能、消息发送、响应机制、统一加载资源的功能、强大的事件机制、对Web应用的支持等等。 90 | 91 | - 事件传播 :载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层,更易于创建实际应用。 92 | 93 | - 加载方式的区别:BeanFactory采用的是延迟加载的形式来注入Bean;ApplicationContext则相反的,它是在Ioc启动时就一次性创建所有的Bean,好处是可以马上发现Spring配置文件中的错误,坏处是启动时间会比较耗时。 94 | 95 | 96 | ## 什么是IoC和DI?DI是如何实现的? 97 | - IOC是控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,遵循了依赖倒置的原则。可以用来减低计算机代码之间的耦合度。控制指的是对实现类的控制,反转指的是这种控制权从调用类中移除,交给第三方(容器)决定。 98 | - 而控制反转其中最常见实现方式叫做依赖注入(Dependency Injection,简称DI)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递(注入)给它。 99 | - 还有一种ioc的实现方式叫“依赖查找”(Dependency Lookup),但Martin Fowler认为DI的实现方式更灵活解耦。 100 | 101 | 102 | ## ★请问Spring中Bean的作用域有哪些? 103 | 1. singleton为默认值,IOC容器中仅存在一个Bean实例,Bean都以单例模式存在 104 | 2. prototype,在每次请求获取Bean的时候,都会创建一个新的实例,它在容器初始化的时候不会创建实例,采用的是延迟加载的形式注入 105 | Bean,当你使用的时候,才会进行实例化,每次实例化获取的对象都不是同一个 就像BeanFactory的实例化模式 实例不唯一 106 | 3. request,在每一次http请求时会创建一个实例,该实例仅在当前http request有效 107 | 4. session,在每一次http请求时会创建一个实例,该实例仅在当前http session有效 108 | 5. globalSession,全局Session,仅供基于Porlet的web环境进行使用。(很少使用到) 109 | 110 | ## ★Spring容器对Bean组件是如何管理的? 111 | 1. Bean对象创建的时机:懒加载或饥饿加载 112 | 2. Bean对象创建的个数:原型或者单例 113 | 3. Bean对象初始化和销毁:可配置初始化或者销毁调用的方法。 114 | 4. 实例化Bean地方时:构造器、静态工厂、实例工厂 115 | 116 | ## 请谈一谈Spring中自动装配的方式有哪些? 117 | 118 | 1. no:不进行自动装配,手动设置Bean的依赖关系。 119 | 2. byName:根据Bean的名字进行自动装配。 120 | 3. byType:根据Bean的类型进行自动装配。 121 | 4. constructor:类似于byType,不过是应用于构造器的参数,如果正好有一个Bean与构造器的参数类型相同则可以自动装配,否则会导致错误。 122 | 5. autodetect:如果有默认的构造器,则通过constructor的方式进行自动装配,否则使用byType的方式进行自动装配 123 | 124 | 125 | ## aop的应用场景? 126 | 1. 事务 127 | 2. 权限 128 | 3. 日志 129 | 4. 缓存 130 | 5. 性能统计 131 | 132 | 133 | ## Spring AOP的配置 134 | 135 | SpringAOP,XML配置,切面切点,连接切点和通知方法等,注解可以直接使@before执行方法@after ,@before(“pointcut()”) ,@after("pointcut"), @Aroud("excutete()),@AfteReturning,@AfterThrowing,可作日志事务,权限等待,AOP即通过把具体的类创建对应的 代理类,从代理类来对具体进行操作。 136 | 137 | 目标实现了接口,默认采用JDK实现AOP,也可以强制使用CGlib来实现AOP,目标没有实现接口的话,则必须采用CGlib,Spring自动在JDK和CGlib切换。如果要求spring强制使用CGlib实现AOP,则可以配置,添加Cglib库。。。jar, Spring配置文件中加入 138 | 139 | 140 | ## AOP的原理? 141 | 142 | 143 | ``` 144 | Spring AOP中的代理使用的默认策略是: 145 | 如果目标对象实现了接口,则默认采用JDK动态代理 146 | 如果目标对象没有实现接口,则采用CgLib进行动态代理 147 | 如果目标对象实现了接口,且强制CgLib代理,则采用CgLib进行动态代理 148 | ``` 149 | 150 | 151 | ## ★你如何理解AOP中的连接点(Joinpoint)、切点(Pointcut)、增强(Advice)、引介(Introduction)、织入(Weaving)、切面(Aspect)这些概念? 152 | 153 | 154 | - 连接点(Joinpoint):程序执行的某个特定位置(如:某个方法调用前、调用后,方法抛出异常后)。一个类或一段程序代码拥有一些具有边界性质的特定点,这些代码中的特定点就是连接点。Spring仅支持方法的连接点。 155 | - 切点(Pointcut):如果连接点相当于数据中的记录,那么切点相当于查询条件,一个切点可以匹配多个连接点。Spring AOP的规则解析引擎负责解析切点所设定的查询条件,找到对应的连接点。 156 | - 增强(Advice):增强是织入到目标类连接点上的一段程序代码。Spring提供的增强接口都是带方位名的,如:BeforeAdvice、AfterReturningAdvice、ThrowsAdvice等。很多资料上将增强译为“通知”,这明显是个词不达意的翻译,让很多程序员困惑了许久。 157 | 说明: Advice在国内的很多书面资料中都被翻译成”通知”,但是很显然这个翻译无法表达其本质,有少量的读物上将这个词翻译为”增强”,这个翻译是对Advice较为准确的诠释,我们通过AOP将横切关注功能加到原有的业务逻辑上,这就是对原有业务逻辑的一种增强,这种增强可以是前置增强、后置增强、返回后增强、抛异常时增强和包围型增强。 158 | - 引介(Introduction):引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过引介功能,可以动态的为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。 159 | - 织入(Weaving):织入是将增强添加到目标类具体连接点上的过程,AOP有三种织入方式:1.编译期织入:需要特殊的Java编译期(例如AspectJ的ajc);2.装载期织入:要求使用特殊的类加载器,在装载类的时候对类进行增强;3.运行时织入:在运行时为目标类生成代理实现增强。Spring采用了动态代理的方式实现了运行时织入,而AspectJ采用了编译期织入和装载期织入的方式。 160 | - 切面(Aspect):切面是由切点和增强(引介)组成的,它包括了对横切关注功能的定义,也包括了对连接点的定义。 161 | 162 | ## 请问Spring支持的事务管理类型有哪些? 163 | 164 | 1. 声明式事务 165 | 本质使用AOP,将业务和事务管理分离,降低耦合度和提高事务的复用能力。声明式事务可以通过注解@Transactional和配置来管理事务,操作简单。 166 | 167 | 2. 编程式事务 168 | 编程式事务是在代码中使用TransactionTemplate进行硬编码,与业务的耦合度高,难以复用。但控制事务的范围比较灵活。 169 | 170 | ## Spring事务的传播特性? 171 | 172 | 1.PROPAGATION_REQUIRED 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。 173 | 2.PROPAGATION_SUPPORTS 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。 174 | 3.PROPAGATION_MANDATORY 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。 175 | 4.PROPAGATION_REQUIRES_NEW 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。 176 | 5.PROPAGATION_NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。 177 | 6.PROPAGATION_NEVER 总是非事务地执行,如果存在一个活动事务,则抛出异常 178 | 7.PROPAGATION_NESTED如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行 179 | 180 | 181 | ## Spring事务的隔离级别? 182 | ISOLATION_READ_UNCOMMITTED 读未提交 183 | ISOLATION_READ_COMMITTED 读已提交 184 | ISOLATION_REPEATABLE_READ 可重复读 185 | ISOLATION_SERIALIZABLE 串行读 186 | 187 | ## Spring的通知类型有哪些? 188 | 前置通知、正常返回通知、异常返回通知、返回通知、环绕通知 189 | 190 | ## Spring的优点? 191 | - 降低了组件之间的耦合性,实现了软件各层之间的解耦 192 | - 可以使用容易提供的众多服务,如事务管理,消息服务等 193 | - 容器提供单例模式支持 194 | - 容器提供了 AOP 技术,利用它很容易实现如权限拦截,运行期监控等功能 195 | - 容器提供了众多的辅助类,能加快应用的开发 196 | - spring 对于主流的应用框架提供了集成支持,如 hibernate,JPA,Struts 等 197 | - spring 属于低侵入式设计,代码的污染极低 198 | - 独立于各种应用服务器 199 | - spring 的 DI 机制降低了业务对象替换的复杂性 200 | - Spring 的高度开放性,并不强制应用完全依赖于 Spring,开发者可以自由选择 spring 的部分或全部 201 | 202 | 203 | 204 | ## 请阐述一下Hibernate实体对象的三种状态是什么?以及对应的转换关系是什么?? 205 | ``` 206 | 瞬时态,持久态和游离态。 207 | 瞬时态没有id,对象刚new出来的时候处于瞬时态。 208 | 持久态有id(与数据库中的主键关联),通过get()或者load()方法获取的对象处于持久态。 209 | 游离态:持久态的对象脱离了session的管理变成游离态,处于游离态的对象如果不被其它对象引用则会被垃圾回收机制回收。 210 | 转化: 211 | 瞬时转持久:Session对象的save()或saveOrUpdate()方法保存对象 212 | 持久转瞬时,调用delete()方法 213 | 持久转游离:调用Session对象的clear()/close()/evict() 214 | 游离转瞬时:调用delete()方法 215 | 游离转持久: 调用update()或者saveOrUpdate() 216 | ``` 217 | 218 | ## Hibernate中SessionFactory是线程安全的吗?Session是线程安全的吗(两个线程能够共享同一个Session吗)? 219 | ``` 220 | SessionFactory对应Hibernate的一个数据存储的概念,它是线程安全的,可以被多个线程并发访问。 221 | SessionFactory一般只会在启动的时候构建。对于应用程序,最好将SessionFactory通过单例模式进行封装以便于访问。 222 | Session是一个轻量级非线程安全的对象(线程间不能共享session),它表示与数据库进行交互的一个工作单元。 223 | Session是由SessionFactory创建的,在任务完成之后它会被关闭。Session是持久层服务对外提供的主要接口。 224 | Session会延迟获取数据库连接(也就是在需要的时候才会获取)。为了避免创建太多的session,可以使用ThreadLocal 225 | 将session和当前线程绑定在一起,这样可以让同一个线程获得的总是同一个session。Hibernate 3中SessionFactory的 226 | getCurrentSession()方法就可以做到。 227 | ``` 228 | 229 | ## Hibernate中Session的load和get方法的区别是什么? 230 | 1. 如果没有找到符合条件的记录,get方法返回null,load方法抛出异常。 231 | 2. get方法直接返回实体类对象,load方法返回实体类对象的代理(如果lazy='true' ,就使用延迟加载,返回的是代理,不是真实对象)。 232 | 3. 在Hibernate 3之前,get方法只在一级缓存中进行数据查找,如果没有找到对应的数据则越过二级缓存,直接发出SQL语句完成数据读取;load方法则可以从二级缓存中获取数据;从Hibernate 3开始,get方法不再是对二级缓存只写不读,它也是可以访问二级缓存的。 233 | 234 | ## 请问Query接口的list方法和iterate方法有什么区别? 235 | - list()方法返回的每个对象都是完整的,而iterator()方法所返回的对象中仅包含了主键值(标识符)。 236 | - 只有当你对iterator中的对象进行操作时,Hibernate才会向数据库再次发送SQL语句来获取该对象的属性值。相当于延迟加载。 237 | 238 | ## 如何理解Hibernate的延迟加载机制?在实际应用中,延迟加载与Session关闭的矛盾是如何处理的? 239 | 240 | 延迟加载就是并不是在读取的时候就把数据加载进来,而是等到使用时再加载。Hibernate使用了虚拟代理机制实现 241 | 延迟加载,我们使用Session的load()方法加载数据或者一对多关联映射在使用延迟加载的情况下从一的一方加载多 242 | 的一方,得到的都是虚拟代理,简单的说返回给用户的并不是实体本身,而是实体对象的代理。代理对象在用户调用 243 | getter方法时才会去数据库加载数据。但加载数据就需要数据库连接。而当我们把会话关闭时,数据库连接就同时关 244 | 闭了。 245 | 246 | 延迟加载与session关闭的矛盾一般可以这样处理: 247 | 1. 关闭延迟加载特性。这种方式操作起来比较简单,因为Hibernate的延迟加载特性是可以通过映射文件或者注解进行配置的, 248 | 但这种解决方案存在明显的缺陷。首先,出现"no session or session was closed"通常说明系统中已经存在主外键关联, 249 | 如果去掉延迟加载的话,每次查询的开销都会变得很大。 250 | 2. 在session关闭之前先获取需要查询的数据,可以使用工具方法Hibernate.isInitialized()判断对象是否被加载,如果没 251 | 有被加载则可以使用Hibernate.initialize()方法加载对象。 252 | 3. 使用拦截器或过滤器延长Session的生命周期直到视图获得数据。Spring整合Hibernate提供的OpenSessionInViewFilter和 253 | OpenSessionInViewInterceptor就是这种做法。 254 | 255 | ## hibernate和ibatis的区别 256 | - Hibernate是完全自动的,利用配置好的对象关系模型,不用写任何sql语句就能实现数据库的操作。 257 | - 而Mybatis是半自动的,仅自动映射字段,但基本的sql是需要自己编写。 258 | - Hibernate使用完全自动的映射操作数据库以及hql语句使得数据库移植性更强。 259 | - Mybatis在sql的直接优化上优于Hibernate。 260 | 261 | ## 解释一下MyBatis中命名空间(namespace)的作用。 262 | 在大型项目中,可能存在大量的SQL语句,这时候为每个SQL语句起一个唯一的标识(ID)就变得并不容易了。 263 | 为了解决这个问题,在MyBatis中,可以为每个映射文件起一个唯一的命名空间,这样定义在这个映射文件中 264 | 的每个SQL语句就成了定义在这个命名空间中的一个ID。只要我们能够保证每个命名空间中这个ID是唯一的, 265 | 即使在不同映射文件中的语句ID相同,也不会再产生冲突了。 266 | 267 | ## MyBatis中的动态SQL是什么意思? 268 | MyBatis的动态SQL是基于OGNL表达式的,它可以帮助我们方便的在SQL语句中实现某些逻辑。 269 | 270 | ## MyBatis中#和$的区别? 271 | $通过直接拼接进sql中,#是预编译再填入参数。所以$不安全,#较安全。 272 | 273 | ## MyBatis一级缓存原理以及失效情况? 274 | 一级缓存的作用域在session级别,在一个事务中,两次查询用的是一个sqlsession,非事务中,两次查询用的是不同的sqlsession。配置正确的情况下,失效的原因一般都是没有在同一个session中。 275 | 276 | 277 | ## MyBatis二级缓存的使用? 278 | 二级缓存是针对Mapper的namespace的,同namespace的Mapper中不同sqlsession可以获取到缓存,但发生更新操作的时候,会清除缓存。在查询操作远远多于增删改操作的情况下可以使用二级缓存。因为任何增删改操作都将刷新二级缓存,对二级缓存的频繁刷新将降低系统性能。最好在单表上使用二级缓存。 279 | 280 | ## springmvc和spring-boot区别? 281 | SpringMVC是MVC框架,Spring Boot是快速开发基于Spring的应用.Spring Boot 通过stater的方式简化 282 | 整个Spring生态配置, SpringMVC, 对应的starter就是spring-boot-starter-web, 开发体验上来说, 283 | 没有很多xml配置,并内置了tomcat. 284 | 285 | ## ★看过MyBatis源码吗,请说说它的工作流程? 286 | 1.加载配置文件。 287 | 2.创建会话工厂。 288 | 3.创建会话。 289 | 4.创建执行器。(可能会执行拦截器) 290 | 5.输入映射封装到sql,去数据库查询,返回结果集封装给应用。 291 | 292 | ## ★MyBatis拦截器原理? 293 | - mybatis给Executor、StatementHandler、ResultSetHandler、ParameterHandler提供了拦截器功能, 294 | - Executor提供了增删改查的接口. 295 | - StatementHandler负责处理Mybatis与JDBC之间Statement的交互. 296 | - ResultSetHandler负责处理Statement执行后产生的结果集,生成结果列表. 297 | - ParameterHandler是Mybatis实现Sql入参设置的对象。 298 | - 拦截器采用了责任链模式,把请求发送者和请求处理者分开,各司其职。 299 | 300 | 301 | 302 | 303 | ## ★Spring MVC完整工作流程 304 | 1. 用户发起请求到前端控制器(DispatcherServlet),该控制器会过滤出哪些请求可以访问Servlet、哪些不能访问。就是url-pattern的作用,并且会加载springmvc.xml配置文件。 305 | 2. 前端控制器会找到处理器映射器(HandlerMapping),通过HandlerMapping完成url到controller映射的组件,简单来说,就是将在springmvc.xml中配置的或者注解的url与对应的处理类找到并进行存储,用map这样的方式来存储。 306 | 3. HandlerMapping有了映射关系,并且找到url对应的处理器,HandlerMapping就会将其处理器(Handler)返回,在返回前,会加上很多拦截器。 307 | 4. DispatcherServlet拿到Handler后,找到HandlerAdapter(处理器适配器),通过它来访问处理器,并执行处理器。 308 | 5. 执行处理器 309 | 6. 处理器会返回一个ModelAndView对象给HandlerAdapter 310 | 7. 通过HandlerAdapter将ModelAndView对象返回给前端控制器(DispatcherServlet) 311 | 8. 前端控制器请求视图解析器(ViewResolver)去进行视图解析,根据逻辑视图名解析成真正的视图(jsp),其实就是将ModelAndView对象中存放视图的名称进行查找,找到对应的页面形成视图对象 312 | 9. 返回视图对象到前端控制器。 313 | 10. 视图渲染,就是将ModelAndView对象中的数据放到request域中,用来让页面加载数据的。 314 | 11. 通过第8步,通过名称找到了对应的页面,通过第10步,request域中有了所需要的数据,那么就能够进行视图渲染了。最后将其返回即可。 315 | 316 | 317 | ## SpringMVC如何处理JSON数据? 318 | 在xml中配置转换Json的类,运行时就会调用这个类进行转换。 319 | 320 | 321 | ## SpringMVC常见注解有哪些? 322 | @Controller 、@RequestMapping、@Autowired、@Resource、@PathVariable、@RequestParam、 323 | @ModelAttribute、@SessionAttribute 324 | 325 | ## Hibernate继承映射策略 326 | 继承关系的映射策略有三种: 327 | - 每个继承结构一张表(table per class hierarchy),不管多少个子类都用一张表。 328 | - 每个子类一张表(table per subclass),公共信息放一张表,特有信息放单独的表。 329 | - 每个具体类一张表(table per concrete class),有多少个子类就有多少张表。 330 | 331 | 332 | ## SpringMVC拦截器原理,如何自定义拦截器? 333 | 跟web.xml的过滤器链差不多。通过统一的Servlet入口进入,针对的是Controller,在Controller前后做一些增强性的操作。 334 | 335 | 336 | 337 | ## SpringMVC如何将请求映射定位到方法上面?结合源码阐述? 338 | SpringMVC根据请求的路径,在一个类似于Map的结构中,以路径为键值找到对应的Handler。 339 | 340 | 341 | ## ★请说说SpringBoot自动装配原理? 342 | 自动装配的过程: 343 | 1. 通过各种注解+继承,引入包含自动装配核心方法的类 344 | 2. SpringApplication.run(Application.class, args)在运行时,调用自动装配方法 345 | 3. 自动装配方法会读取spring-boot-autoconfigure.jar里面的spring.factories配置文件,配置文件中有所有自动装配类的配置类的类名 346 | 4. 生成对应功能的Configuration类,这些功能配置类要生效的话,会去classpath中找是否有该类的依赖类(也就是pom.xml必须有对应功能的jar包才行) 347 | 5. 配置类里再通过判断生成最后的功能类,并且配置类里面注入了默认属性值类,功能类可以引用并赋默认值。生成功能类的原则是自定义优先,没有自定义时才会使用自动装配类。 348 | 349 | 综上所述,要想自动装配一个类需要满足2个条件: 350 | 351 | 1. spring.factories里面有这个类的配置类(一个配置类可以创建多个围绕该功能的依赖类) 352 | 2 .pom.xml里面需要有对应的jar包 353 | 354 | 自动装配的结果: 355 | 356 | 1. 根据各种判断和依赖,最终生成了业务需要的类并且注入到IOC容器当中了 357 | 2. 自动装配生成的类赋予了一些默认的属性值 358 | 359 | ## SpringBoot的优点? 360 | 361 | 快速构建项目,极大的提高了开发、部署效率。 362 | 对主流开发框架的无配置集成。 363 | 项目可独立运行,无须外部依赖Servlet容器。 364 | 提供运行时的应用监控。 365 | 366 | 367 | ## ★Spring MVC和 Spring IOC的生命周期 368 | 369 | 370 | SpirngMVC的生命周期 : 371 | 372 | - A,-》DispatcherSerlvet(前端控制器) 373 | 374 | - B,-》HandlerMapping(处理器映射器),根据xml注解查找对应的Hander -》 返回Handler 375 | 376 | - C,-》处理器适配器去执行Handler 377 | 378 | - D,-》Handler执行完成后给处理器适配器返回ModelAndView 379 | 380 | - E,-》前端控制器请求视图解析器去执行视图解析,根据逻辑视图名解析成真正的视图JSP,向前端控制器返回view 381 | 382 | - F,-》前端控制器进行视图渲染,将模型数据放到request-》返回给用户 383 | 384 | SpringBean的生命周期: 385 | 386 | 387 | Instance实例化-》设置属性值-》调用BeanNameAware的setBeanName方法-》调用BeanPostProsessor的预初始化方法-》调用InitializationBean的afterPropertiesSet()的方法-》调用定制的初始化方法callCustom的init-method-》调用BeanPostProsessor的后初始化方法-》Bean可以使用了 -》 容器关闭-》 调用DisposableBean的destroy方法-》调用定制的销毁方法CallCustom的destroy-method。 388 | 389 | 390 | ## ★Spring+MyBatis实现读写分离简述? 391 | 392 | 方案一:通过MyBatis配置文件创建读写分离两个DataSource,每个SqlSessionFactoryBean对象的mapperLocations属性制定两个读写数据源的配置文件。将所有读的操作配置在读文件中,所有写的操作配置在写文件中。 393 | 方案二:通过Spring AOP在业务层实现读写分离,在DAO层调用前定义切面,利用Spring的AbstractRoutingDataSource解决多数据源的问题,实现动态选择数据源 394 | 方案三:通过Mybatis的Plugin在业务层实现数据库读写分离,在MyBatis创建Statement对象前通过拦截器选择真正的数据源,在拦截器中根据方法名称不同(select、update、insert、delete)选择数据源。 395 | 方案四:通过spring的AbstractRoutingDataSource和mybatis Plugin拦截器实现非常友好的读写分离,原有代码不需要任何改变。推荐第四种方案 396 | 397 | 398 | ## Quartz实现原理? 399 | 400 | A、scheduler是一个计划调度器容器(总部),容器里面可以盛放众多的JobDetail和trigger,当容器启动后,里面的每个JobDetail都会根据trigger按部就班自动去执行。 401 | B、JobDetail是一个可执行的工作,它本身可能是有状态的。 402 | C、Trigger代表一个调度参数的配置,什么时候去调。 403 | D、当JobDetail和Trigger在scheduler容器上注册后,形成了装配好的作业(JobDetail和Trigger所组成的一对儿),就可以伴随容器启动而调度执行了。 404 | E、scheduler是个容器,容器中有一个线程池,用来并行调度执行每个作业,这样可以提高容器效率。 405 | 406 | ## springmvc用到的注解,作用是什么? 407 | - @Controller或@RestController标记该类是一个控制器 408 | - @RequestMapping通过这个注解可以定义不同的处理器映射规则,即为控制器指定可以处理哪些URL请求。 409 | - @RequestBody用于读取http请求的内容(字符串),通过springMVC提供的HttpMessageConverter接口将读取到的内容转换为json、xml等格式的数据,再转换为java对象绑定到Controller类方法的参数上。 410 | - @ResponseBody 注解表示该方法的返回的结果直接写入 HTTP 响应正文(ResponseBody)中,一般在异步获取数据时使用,通常是在使用 @RequestMapping 后,返回值通常解析为跳转路径,加上 @Responsebody 后返回结果不会被解析为跳转路径,而是直接写入HTTP 响应正文中。 411 | - @ModelAndAttribute 绑定请求参数到指定对象\暴露表单引用对象为模型数据 \暴露@RequestMapping方法返回值为模型数据 412 | - @RequestParam 处理简单类型的绑定 413 | - @PathVariable 绑定URL占位符到入参。 414 | - @ExceptionHandler 注解到方法上, 出现异常时会执行该方法。 415 | - @ControllerAdvice 使一个Controller成为全局的异常处理类, 类中用ExceptinHandler方法注解的方法可以处理所有Controller发生的异常。 416 | - @Autowired 它可以对类成员变量、方法以及构造函数进行标注,完成自动装配的工作。自动装配的意思就是让Spring从应用上下文中找到对应的bean的引用,并将它们注入到指定的bean。 417 | 418 | ## ★springboot启动机制。 419 | 420 | 1. 如果我们使用的是SpringApplication的静态run方法,那么,这个方法里面首先要创建一个SpringApplication对象实例,然后调用这个创建好的SpringApplication的实例方法。在SpringApplication实例初始化的时候,它会提前做几件事情:根据classpath里面是否存在某个特征类(org.springframework.web.context.ConfigurableWebApplicationContext)来决定是否应该创建一个为Web应用使用的ApplicationContext类型。使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer。使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener。推断并设置main方法的定义类。 421 | 2. SpringApplication实例初始化完成并且完成设置后,就开始执行run方法的逻辑了,方法执行伊始,首先遍历执行所有通过SpringFactoriesLoader可以查找到并加载的SpringApplicationRunListener。调用它们的started()方法,告诉这些SpringApplicationRunListener,“嘿,SpringBoot应用要开始执行咯!”。 422 | 3. 创建并配置当前Spring Boot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile)。 423 | 4. 遍历调用所有SpringApplicationRunListener的environmentPrepared()的方法,告诉他们:“当前SpringBoot应用使用的Environment准备好了咯!”。 424 | 5. 如果SpringApplication的showBanner属性被设置为true,则打印banner。 425 | 6. 根据用户是否明确设置了applicationContextClass类型以及初始化阶段的推断结果,决定该为当前SpringBoot应用创建什么类型的ApplicationContext并创建完成,然后根据条件决定是否添加ShutdownHook,决定是否使用自定义的BeanNameGenerator,决定是否使用自定义的ResourceLoader,当然,最重要的,将之前准备好的Environment设置给创建好的ApplicationContext使用。 426 | 7. ApplicationContext创建好之后,SpringApplication会再次借助Spring-FactoriesLoader,查找并加载classpath中所有可用的ApplicationContext-Initializer,然后遍历调用这些ApplicationContextInitializer的initialize(applicationContext)方法来对已经创建好的ApplicationContext进行进一步的处理。 427 | 8. 遍历调用所有SpringApplicationRunListener的contextPrepared()方法。 428 | 9. 最核心的一步,将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的IoC容器配置加载到已经准备完毕的ApplicationContext。 429 | 10. 遍历调用所有SpringApplicationRunListener的contextLoaded()方法。 430 | 11. 调用ApplicationContext的refresh()方法,完成IoC容器可用的最后一道工序。 431 | 11. 查找当前ApplicationContext中是否注册有CommandLineRunner,如果有,则遍历执行它们。 432 | 13. 正常情况下,遍历执行SpringApplicationRunListener的finished()方法、(如果整个过程出现异常,则依然调用所有SpringApplicationRunListener的finished()方法,只不过这种情况下会将异常信息一并传入处理) 433 | 434 | ## ★srpingMVC启动原理 435 | 1. 解析键值对 436 | 2. 创建一个application对象即ServletContext,servlet上下文,用于全局共享 437 | 3. 将键值对放入ServletContext中,web应用全局共享 438 | 4. 读取标签,创建监听器,一般使用ContextLoaderListener,如果使用了ContextLoaderListener,Spring就会创建一个WebApplicationContext对象,这个就是IOC容器, ContextLoaderListener创建的IOC容器是全局共享的,并将其放在ServletContext中, 键名为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, 读取web.xml文件里的contextConfigLocation配置中的xml文件来创建bean 439 | 5. listener创建完毕后如果有Filter会去创建Filter 440 | 6. 初始化Servlet,一般使用DispatchServlet类 441 | 7. DispatchServlet的父类FrameworkServlet会重写其父类的initServletBean方法,并调用initWebApplicationContext()以及onRefresh()方法 442 | 8. initWebApplicationContext()方法会创建一个当前servlet的一个IOC子容器,如果存在上述的全局WebApplicationContext则将其设置为父容器,如果不存在上述全局的则父容器为null。 443 | 9. 读取标签的配置的xml文件并加载相关Bean 444 | 10. onRefresh()方法创建Web应用相关组件 445 | 446 | ## ★AOP的原理详解 447 | AOP实现中,可以看到三个主要的步骤,一个是代理对象的生成,然后是切面的织入,然后是执行拦截器链。 448 | 1. Spring AOP代理对象的生成: 449 | 450 | Spring提供了两种方式来生成代理对象: JDKProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。Spring使用JDK来生成代理对象,具体的生成代码放在JdkDynamicAopProxy这个类中。 451 | 2. 切面的织入: 452 | 453 | 获取可以应用到此方法上的通知链,Advised中配置能够应用到连接点或者目标类的Advisor全部被转化成了MethodInterceptor. 454 | 3. 执行拦截器链: 455 | 456 | 如果得到的拦截器链为空,则直接反射调用目标方法,否则创建MethodInvocation,调用其proceed方法,触发拦截器链的执行, 457 | 458 | 注意: 459 | 460 | - CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。 461 | - JDK代理三要素Proxy.newProxyInstance(类加载器,目标接口,InvocationHandler实现),JDKDynaimicAopProxy实现了InvocationHandler接口,并使用了AdvisedSupport中存在的目标接口,及设置的类加载器,进行JDK的动态代理。 462 | - CGLIB创建代理主要是创建Enhancer enhancer,并通过AdvisedSupport设置相应的属性,比如目标类rootClass,如果由接口合并接口给代理类,最主要的是设置Callback集合和CallbackFilter,使用CallBackFilter可以根据方法的不同使用不同的Callback进行拦截和增强方法。其中最主要的使用于AOP的Callback是DynamicAdvisedInterceptor。 463 | 464 | 465 | ## ★spring中循环注入的方式 466 | - 能成功的循环注入方式都是因为能先创建出实例放入到临时创建Bean池当中。 467 | - 构造器循环依赖(失败) 468 | - setter方法循环注入(成功) 469 | - setter方法注入 单例模式(scope=singleton)(成功) 470 | - setter方法注入 非单例模式(scope=prototype)(失败)原型bean的话,不会放入到临时创建Bean池当中。 471 | 472 | ## ★Spring的BeanFactory和FactoryBean的区别 473 | 474 | BeanFactory接口用来生产Bean,它处理生产bean的接口体系的最顶层,它为其他具体的IOC容器提供了最基本的规范,例如DefaultListableBeanFactory,XmlBeanFactory,ApplicationContext 等具体的容器都是实现了BeanFactory,再在其基础之上附加了其他的功能。 475 | FactoryBean接口用来定制Bean的生产过程,getObject方法中可以实现自定义过程。MyBatis的SqlSessionFactoryBean中,Spring会调用SqlSessionFactoryBean这个实现了FactoryBean的工厂Bean 同时加载dataSource,Mapper文件的路径,对sqlSessionFactory进行初始化。 476 | FactoryBean比BeanFactory在生产Bean的时候灵活,还能修饰对象,带有工厂模式和装饰模式的设计思想在里面,不过它的存在还是以Bean的形式存在。BeanFactory因为是核心接口,编写复杂逻辑很容易接触到其他不必要的接口,不好实现。 477 | 478 | ## spring boot特性,优势,适用场景等 479 | 1. 约定优于配置思想 480 | 2. 专注与业务逻辑之间思维切换 481 | 3. 基于Spring的开发提供更快入门体验 482 | 4. 开箱即用,没有代码生成,无需XML配置。 483 | 5. 支持修改默认配置满足特定需求 484 | 6. 提供大型项目中常见的非功能性特性,如嵌入Tomcat服务器、安全、指标、健康检测、外部配置等 485 | 很契合微服务场景。 486 | 487 | ## ★Mybatis的底层实现原理 488 | 1. 加载mybatis全局配置文件(数据源、mapper映射文件等),解析配置文件,MyBatis基于XML配置文件生成Configuration,和一个个MappedStatement(包括了参数映射配置、动态SQL语句、结果映射配置),其对应着