总结文档

普通集合

  • Arrays.asList()返回的是视图(ArrayList 内部类对象,只提供了替换数据的方法,其底层依旧是原数组数据)
    • subList:返回的 List 是 ArrayList 中某段数据的一个视图,不可在使用时对原对 象进行操作,否则会出现 CME 异常
  • HashMap

并发集合

  • LongAdder 与 AtomicLong
  • LongAdder(用空间换时间)
    • 继承自 Striped64,内部的 Cell 类被 Contended 注解修饰,其目的是为了解决伪缓存共享的问题(争抢缓存行的所有权,两个变量被分配到了同一个缓存行中)
    • 对于低竞争的情况下,直接采用 CAS 操作更新 base 变量
    • 高竞争情况下,将每个线程操作 hash 到不同的 cells 数组中,从而将 AtomicLong 更新一个 value 的行为分散到多个 value 中,如果要获取值的话,直接将 Cell 数组中的各个值相加再加上 base 值就得到
  • 如何实现原子操作的:调用 UnSafe.compareAndswapInt,底层指令确保操作执行原子性(比较更新操作)
    iii. 原子数组(AtomicIntegerArray) - 数组通过其构造器传入,然后 AtomicIntegerArray 会将数组进行一次 copy 操作,因此对原有的数组没有任何影响
  • 存在 ABA 问题,如何解决:版本号机制
  • ConcurrentLinkedQueue(优秀博文:https://juejin.im/entry/5b4dde4b5188251af6621e13)
  • 哨兵节点:next 域指向了自己的节点(通常是待删除或者是一个空间节点),哨兵节点的设置(lazySetNext)
  • 允许 tail、head 的更新存在滞后的情况出现
    • 为什么允许更新滞后的情况出现
      • 如果让 tail 永远为队列的尾节点,则每次都需要使用循环 CAS 更新 tail 节点,如果能减少更新 tail 节点的次数,入队的性能更高
      • head 节点不一定就是队列的第一个含有元素的节点,也不是每次获取元素后就更新 head 节点,只有当 head 中的元素为空的时候才更新 head 节点,这和添加 offer() 方法中更新 tail 节点类似,减少 CAS 更新 head 节点的次数,出队的效率会更高
    • 示例图
  • CopyOnWriteList
  • 类似操作系统的 COW 机制
  • 执行写操作时,复制出一份副本用于写操作,当写操作执行完毕后,采用 volatile 写语义将副本替换旧数据,实现并发安全的写操作,但是缺点是无法读取到最新的数据
  • Disruptor
  • 使用环形数组结构,为了应对 Java 内存回收机制,采用数组而非链表(为什么);同时数组对处理器的缓存机制更加友好——元素位置定位
  • 核心思想:CAS 锁操作尽量代替原有的 Lock 操作
  • 生产者
    • 申请写入 m 个元素,若有 m 个元素可以写入,则返回最大序列号(判断是否会覆盖未读的元素)
    • 遇到多个生产者重复写一个 queue 时,会为每个线程分配不同的一段数组空间进行操作
    • 防止读到未写入的元素:新创建一个与 ring buffer 大小相同的 buffer——avaliable buffer,某个位置写入成功,相应位置置位标记为写入成功,读取时遍历 avaliable buffer
  • 消费者等待策略
    • 项目遇到的策略设置错误问题:YieldingWaitStrategy 很可能导致 CPU 负载 100%
  • ConcurrentHashMap
  • 不同 java 版本的实现区别
    • Java1.7 版本中,采用 segment 分段锁技术,segment[]数组中每个 segment 对应一段 table 数据,每个 segment 可以看做一个锁,这个锁负责一部分 Map 数据的读写
    • Java1.8 版本中,直接用 table 数组的每个元素进行分段锁(个人觉得),通过提高锁的细粒度,更好的避免了并发冲突,cas 乐观锁更新机制
      • CAS:
        • CAS 的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址,返回值是 valueOffset。另外 value 是一个 volatile 变量,在内存中可见
  • 扩容的实现(将扩容任务分给多个线程去完成,每个线程认领各自的桶区间,对各自负责的桶区间进行 resize 操作——核心也是并发高效的原因之一)
    • 计算每个线程可以处理的桶区间,默认 16
      • 让多个线程分摊 Map 的扩容操作,每个线程负责一部分(没有重叠部分,也就避免了资源的竞争问题)
      • 实现代码,除以 CPU 核数是为了求出每个 CPU 处理的桶的个数,并让每个线程处理的桶的个数相同,避免出现转移任务不均匀的现象
      • (if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) stride = MIN_TRANSFER_STRIDE;)
    • 新旧 Table,正在进行 resize 时,原 table 对应的槽位会放置一个 ForwardingNode 节点,将 find 请求转发至新的 table 中;当别的线程发现槽位的是 fwd 类 的节点时,就自动跳过
      • 对 putVal 可感知
        1
        2
        3
        else if ((fh = f.hash) == MOVED)
        tab = helpTransfer(tab, f);
        else {…}
      • 转发查询请求的代码
      • 根据 Node 的 hash 值进行判断
      • 如果对应的槽位存在实际值,则对该槽位进行加锁进行扩容操作,此时处理桶的行为是同步的
        • 如果桶是链表的,则根据 hash 结果拆分成两个链表(高低位)
        1
        2
        3
        4
        5
        6
        7
        8
        for (Node<K,V> p = f; p != lastRun; p = p.next) {
        int ph = p.hash; K pk = p.key; V pv = p.val;
        // 如果与运算结果是 0,那么就还在低位
        if ((ph & n) == 0) // 如果是0 ,那么创建低位节点
        ln = new Node<K,V>(ph, pk, pv, ln);
        else // 1 则创建高位
        hn = new Node<K,V>(ph, pk, pv, hn);
        }
      • 然后通过 CAS 操作更新新表高低槽位,将新生成的两个链表放入,同时更新旧表中对应槽位的占位符为 ForwardingNode 节点
    • 通过 sizeCtl 进行显示当前有多少个线程正在进行扩容操作(被 volatile 修饰)
    • 优秀博客:https://juejin.im/post/5b00160151882565bd2582e0
    • 有这么一个问题,ConcurrentHashMap,有三个线程,A 先 put 触发了扩容,扩容时间很长,此时 B 也 put 会怎么样?此时 C 调用 get 方法会怎么样?C 读取到的元素是旧桶中的元素还是新桶中的
      • A 先触发扩容,ConcurrentHashMap 迁移是在锁定旧桶的前提下进行迁移的,并没有去锁定新桶。
        • 在某个桶的迁移过程中,别的线程想要对该桶进行 put 操作怎么办?一旦某个桶在迁移过程中了,必然要获取该桶的锁,所以其他线程的 put 操作要被阻塞。因此 B 被阻塞。
        • 某个桶已经迁移完成(其他桶还未完成),别的线程想要对该桶进行 put 操作怎么办?该线程会首先检查是否还有未分配的迁移任务,如果有则先去执行迁移任务,如果没有即全部任务已经分发出去了,那么此时该线程可以直接对新的桶进行插入操作(映射到的新桶必然已经完成了迁移,所以可以放心执行操作)

字节流与字符流

  • 字节流(InputStream、WriteStream)、字符流(Read、Write)
  • 字节流向字符流编码的桥梁(InputStreamReader、InputStreamWriter)
  • 本质上流动的都是字节,字符流只是进行了相应的编码操作
  • 二者操作的基本单位不一样,字节流操作的是字节(byte=8bit),默认不使用缓冲区;而字符流操作的是 Unicode 码元,默认使用缓冲区
  • 序列化
  • Kryo:无需使用 Class 即可反序列化,可以在序列化时选择是否将 Class 信息一并序列化

static、final、super、this 关键字(this、super 不能用在 static 方法中)以及泛型

  • inal(变量、方法、类)
    • 对于一个 final 变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
    • 当用 final 修饰一个类时,表明这个类不能被继承。final 类中的所有成员方法都会被隐式地指定为 final 方法。
    • 使用 final 方法的原因有两个。一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的 Java 实现版本中,会将 final 方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的 Java 版本已经不需要使用 final 方法进行这些优化了)。类中所有的 private 方法都隐式地指定为 final。
  • static(变量、方法、类)
    • 修饰成员变量和成员方法: 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被 static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。调用格式:类名.静态变量名、类名.静态方法名()
    • 静态代码块: 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次。静态代码块对于定义在它之后的静态变量,可以赋值,但是不能访问。
    • 静态内部类(static 修饰类的话只能修饰内部类): 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非 static 成员变量和方法。
    • 静态导包(用来导入类中的静态资源,1.5 之后的新特性): 格式为:import static 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。
  • this(引用类的当前实例——对象),并且在构造函数中,this 会隐式的充当第一个参数传入
  • super(用于从子类访问父类的变量和方法)
  • 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享。而 this 代表对本类对象的引用,指向本类对象;而 super 代表对父类对象的引用,指向父类对象;所以, this 和 super 是属于对象范畴的东西,而静态方法是属于类范畴的东西。
  • 泛型
    • (上界通配符,只能拿不能存,谁继承了T类型) 与 (下界通配符,只能存不能拿,谁是T类型的父类)以及 (某种特定类型的非原生List,既不能拿元素也不能放元素,但是可以通过迭代器获取存储的元素),List(持有任何Object类型的原生List)

      异常体系

      • 顶层都是 Throwable,其中分为 Exception(还可以挽救)以及 Error(无法恢复)
      • 受检异常与运行时异常:非 RuntimeException 的为受检异常
      • throws 用于方法签名中,throw 相当于 return,抛出一个异常
      • 其中一个线程抛出 OOM,其他线程会受影响吗

      ava 的 IO

      • 常见的 IO 模型
      • NIO(本质还是同步 I/O,即只会通知你 IO 可读,在读的过程中,还是阻塞的,只不过实现了复用机制、事件机制,通过事件注册以及事件轮询)
        • Reactor 模式
          • 缓冲区 Buffer:所有数据都是用缓冲区处理,读取时直接读到缓冲区中,写入数据时,直接写入到缓冲区,缓冲区实际上是一个字节数组(增强的字节数组)
          • 通道 channel:双向的,用于读与写,并且读写操作可以同时进行
          • 使用堆外内存,然后通过 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作,避免了数据在堆外内存以及堆内内存来回复制使用
          • 多路复用器:不断轮询在其注册的 channel,当 channel 上有新事件时,chanel 处于就绪状态,此时被 selector 轮询出来
        • Proactor 模式
          • Proactor 调用 aoi_write 后立刻返回,由内核负责写操作,写完后调用相应的回调函数处理后续逻辑
          • 主动的事件分离和分发模型;这种设计允许多个任务并发的执行,从而提高吞吐量;并可执行耗时长的任务(各个任务间互不影响)
        • 两种模式的不同:Reactor 框架中用户定义的操作是在实际操作之前调用的。比如你定义了操作是要向一个 SOCKET 写数据,那么当该 SOCKET 可以接收数据的时候,你的操作就会被调用;而 Proactor 框架中用户定义的操作是在实际操作之后调用的。比如你定义了一个操作要显示从 SOCKET 中读入的数据,那么当读操作完成以后,你的操作才会被调用。
      • AIO(异步式 I/O、还未实践过)
        • 异步回调通知类,任务完成后带着结果回调 complete 方法,由线程池负责回调并驱动读写
        • 新增的方法
          • AsynchronousFileChannel: 用于文件异步读写;
          • AsynchronousSocketChannel: 客户端异步 socket;
          • AsynchronousServerSocketChannel: 服务器异步 socket。
      • BIO
        • 同步并阻塞式的 IO,服务器实现一个链接一个线程(典型例子:Tomcat,早期的 Tomcat 在还没有异步的 servlet 时是 BIO 形式的,或者底层采用线程池,尽可能的减少线程的大量创建)
        • 线程模型

      java 对象如何判断是否可以回收(注意,此处仅仅为判断对象是否可达,不一定判断对象是否可以回收)

      • 对象引用计数器
        • 方式:对象被引用时计数器加一、引用失效时计数器减一
        • 优点:简单
        • 缺点:面对两个对象相互循环引用,则无法回收,此时对象的引用计数器都为一(内存泄漏)
        • 使用例子
          • Netty 自己的内存管理实现
          • Redis 的内存管理实现
      • 根搜索算法
        • 方法:引用链(以一系列 GC Roots 为起点。从节点向下搜索,走过的路径成为引用链)、当一个对象无法到达 GCRoot 时,则证明该对象不可用,可被回收
        • 可做 GC Roots 的对象
          • 虚拟机栈中的引用变量
          • 方法区中的常量引用对象
          • 方法区中类静态属性引用的对象
          • 本地方法栈(JNI)中引用的对象
      • 四种对象引用(如何通过软、弱引用提升 JVM 性能:https://www.javazhiyin.com/13546.html、http://blogxin.cn/2017/09/16/java-reference/、-XX:+PrintReferenceGC)
        • 强引用
          • 获取对象的方式为直接调用
          • 如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误(仅抛出 OOM 的线程停止工作,其他线程照常执行)
        • 软引用
          • 获取对象的方式为 get()
          • 如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存
          • 使用价值:如果中间涉及大量的中间计算结果,由于 new Object() 是直接在堆上分配内存的,如果采用软引用的方式,可以尽快的解决内存不足的问题;可以认为是一个 LRUCache 缓存的实现
          • 回收条件:SoftReference 中有一个全局变量 clock 代表最后一次 GC 的时间点,有一个属性 timestamp,每次访问 SoftReference 时,会将 timestamp 其设置为 clock 值。
            • 根据 clock-timestamp 得知对象大概有多久没有被访问
            • 内存空间的大小
            • SoftRefLRUPolicyMSPerMB 常量值(每 1M 空闲空间可保持的 SoftReference 对象生存的时长(单位 ms))
            • 判断是否保留软引用对象:clock - timestamp <= freespace * SoftRefLRUPolicyMSPerMB
        • 弱引用
          • 获取对象的方式为 get()
          • 只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存;只能存活到下一次垃圾回收发生之前
          • 使用价值(数据失效时自动更新对应的数据文件,无需人工进行代码干预)
        • 虚引用
          • 无法取得
          • 虚引用主要用来跟踪对象被垃圾回收器回收的活动
          • 虚引用必须和引用队列 (ReferenceQueue)联合使用
          • 典型使用例子:com.mysql.jdbc.NetworkResources 的 ConnectionPhantomReference
      • 对象生存 or 消亡
        • 宣告一个对象的消亡,需要经历两次的标记过程
        • 两次标记过程(两次标记走完后才可以确定对象是否可被回收)
          • GC Roots 的可达判断——>第一次标记并且进行一次筛选(判断对象是否有必要执行 finalize 方法,当对象无覆盖 finalize 或者已经调用过时,没必要执行 finalize)——>需要执行 finalize 方法的对象入队列(F-Queue)——>finalizer 线程执行(触发每个对象的 finalize 方法)——>如果重新与引用链上的对象建立关联随即逃脱消亡操作(examp:重写 finalizer 方法,将 this 对象泄露可以避免本次的对象消亡,但是仅一次)——>对对象进行第二次标记——>移除即将回收集合——>对象消亡
          • 任何对象的 finalize 方法只会被系统调用一次

      java 中的 SPI

      • 当接口属于调用方时,我们就将其称为 spi,全称为:service provider interface
      • 动态替换发现机制
      • 服务发现核心类:java.util.ServiceLoader
      • 服务提供者需要在 classpath 下的 META-INF/services/目录里创建一个以服务接口命名的文件
      • 服务发现主要代码
        1
        2
        ServiceLoader<ObjectSerializer> serializers = ServiceLoader.load(ObjectSerializer.class);
        final Optional<ObjectSerializer> serializer = StreamSupport.stream(serializers.spliterator(), false) .findFirst();
      • 使用场景
        • dubbo 的服务扩展是采用 spi 的机制
        • 数据库驱动的注册目前是采用 spi 机制

      Java 虚拟机

      • 虚拟机前导知识(虚拟机的实现分为 Stack(栈)以及 Register(寄存器)两种实现,Java 的 JVM 属于栈虚拟机)
      • 虚拟机结构图
      • class 文件
        • 每个 class 文件的头四个字节成为魔数,用于确定这个文件是否可以被 java 虚拟机接受(在 Travis-CI 中曾经遇到 JDK7 环境编译 JDK8 的 class 文件,导致无法接受)
        • 第 5 ~ 8 个字节是 class 版本号,第 5、6 个字节是次版本号,第 7、8 字节是主版本号
        • 一切方法调用在 Class 中都只是符号引用,方法调用阶段唯一的任务就是确定方法调用的版本,如果方法在运行之前就有一个确定的版本,那么可以直接从符号引用转为直接引用
        • 常量池
          • 常量池容器计数器从 1 开始(由于常量池中元素的数量是不固定的)
          • 存放字面常量(文本字符串以及 final 常量)以及符号引用(类和接口的全限定名、字段的名称和描述符、方法的名称和描述符)
        • 字节码
          • 创建一个对象的大致流程
          • 类加载校验
          • 执行 static 代码块
          • 为对象分配堆内存
          • 对成员变量进行初始化(对象的实例字段在可以不赋初始值就直接使用,而局部变量中如果不赋值就直接使用,因为没有这一步操作,不赋值是属于未定义的状态,编译器会直接报错)
          • 调用初始化代码块
          • 调用构造器函数(可见构造器函数在初始化代码块之后执行)
        • new、dup、invokespecial 中存在 dup 指令的原因,如果 new 指令执行完,那么就会出栈,如果不加一个 dup 指令就找不到对象实例了
        • Java 多态的基石——vtable
          • ava 虚拟机需要根据调用者的动态类型,来确定虚方法调用的目标方法,这个过程称之为动态绑定;相对于静态绑定的非虚方法调用来说,虚方法调用更加耗时
          • Java 虚拟机采用了空间换时间的方式来实现动态绑定,为每个类生成一张方法表,用以快速定位目标方法;方法表本质就是个数组,每个数组元素指向一个当前类及其祖先类中非私有的实例方法
            • 性质
              • 子类方法表中包含父类方法表中的所有方法了
              • 子类方法在方法表中的索引值,与他所重写的父类方法的索引值相同
            • Java 子类会继承父类的 vtable。Java 所有的类都会继承 java.lang.Object 类,Object 类有 5 个虚方法可以被继承和重写。当一个类不包含任何方法时,vtable 的长度也最小为 5,表示 Object 类的 5 个虚方法
            • final 和 static 修饰的方法不会被放到 vtable 方法表里
            • 当子类重写了父类方法,子类 vtable 原本指向父类的方法指针会被替换为子类的方法指针
            • 子类的 vtable 保持了父类的 vtable 的顺序
            • 示例图
          • ry-catch-finally 实现的本质
            • 本质采用了 goto 语句进行代码的跳转
            • 当程序出现异常时,Java 虚拟机会从上至下遍历异常表中所有的条目。当触发异常的字节码索引值在某个异常条目的[from, to)范围内,则会判断抛出的异常与该条目想捕获的异常是否匹配。如果匹配,Java 虚拟机会将控制流跳转到 target 指向的字节码;如果不匹配则继续遍历异常表;如果遍历完所有的异常表,还未匹配到异常处理器,那么该异常将蔓延到调用方(caller)中重复上述的操作。最坏的情况下虚拟机需要遍历该线程 Java 栈上所有方法的异常表
            • finally 功能的实现(本质是将 finally 的函数进行 copy 到各个部分)
            • 代码展示
            • ava 反射
              • 在 JDK 中,对于 Java 的反射调用存在一个阈值,当调用反射的次数低于阈值时,直接使用 Java 原生的反射 API(native)进行反射操作,如果超过阈值,则使用 ASM 字节码工具创建一个新的类实现新的反射调用机制
      • 编译期
        • 数据及控制流分析
          • 检查程序局部变量在使用前是否有复值等等
      • 虚拟机类加载
        • 类的生命周期:加载——>验证——>准备——>解析——>初始化——>使用——>卸载
        • 子类引用父类的静态字段不会导致子类的初始化,只有直接定义这个字段的类才会被初始化;接口在初始化时并不要求父接口也要初始化,只有在使用到父接口时才会去初始化父接口
        • 加载(用户可参与类加载的控制、仅在此阶段用户可以参与(字节码的的工作方式,相比 java 的 proxy 性能高,不需要设计反射,直接产生一个继承的 class)
          • 通过类的全限定名获取定义此类的二进制字节流(多种方式获取二进制字节流)
          • 字节流所代表的静态数据结构转化为运行时数据结构
          • 在 java 堆中为这个 class 生成 java.lang.Class 对象,作为方法区数据入口
          • 加载时的验证流程:文件格式验证——元数据验证——字节码验证——符号引用验证
        • 准备
          • 正式为类变量分配内存并初始化(零值初始化)(类变量指的是被 static 修饰的变量,而不是指实例变量),但是如果是 static final 修饰的话,会直接初始化为所期望初始化的值
        • 解析
          • 符号引用:一组符号描述所引用的目标
          • 常量池内的符号引用(引用目标不一定在内存中存在)替换为直接引用(引用目标必定在内存中存在)
          • 符号引用替换为直接引用
          • 类或接口的解析、字段的解析、类方法的解析、接口方法解析
        • 初始化
          • 主动引用会触发类的初始化
          • 父类静态变量—>父类静态代码块—>子类静态变量—>子类静态代码块—>父类非静态变量(父类实例成员变量)—>父类构造函数—>子类非静态变量(子类实例成员变量)—>子类构造函数
        • 类加载器
          • 类+类加载器确定 java 类在虚拟机中的唯一性(可以根据此特性设计出一个可以加载两个版本的 jar 包)
            • ar 包依赖的隔离实现,可以使得在同一个 App 中同时存在两个 Jar 包,仅仅是版本不同而已
          • 启动类加载器(Bootstrap Classloader:负责从 classpath 加载类,优先级最高,加载 rt.jar)<= 扩展类加载器(Extension Classloader:负责加载扩展文件夹(jre/lib)中的类)<= 应用程序类加载器(Application Classloader:负责加载应用级 classpath 和环境变量指向的路径下的类)
          • 比较两个类的前提是要在两个类位于同一 classloader 下,如果一个 class 文件被两个 classloader 所加载,那么所产生的类也是不同的
          • 溯源委托类加载(优先级的层次关系、java 推荐的机制、并不强制要求)
            • 除了顶层类加载器,其余类加载器都有自己的父类加载器
            • 流程:一个类加载器收到类加载的请求,不会自己尝试去加载这个类,而是把这个请求委派给父类加载器去完成,所有的类加载请求都应该传送到顶层类加载器完成,只有当父类反馈自己无法完成这个请求时,子类加载器才会自己去完成类的加载
            • 好处:优先级的层次关系,通过这种层级关系可以避免类的重复加载(类的唯一性确定的机制)
            • 可以不采用溯源委托加载:重写 loadClass 方法;如果不想破坏溯源委托机制,只需要重写 findClass 方法
          • 线程上下文加载器(Thread Local ClassLoader)——使用场景:java 的 spi
          • Object 类在程序的各种类加载器环境中都是一个类——Bootstrap ClassLoader 加载
        • JVM 虚拟机调试
          • Native Heap 区被打散为 sub-pools(为应用系统在多核心 CPU 和多 Sockets 环境中高伸缩性提供了一个动态内存分配的特性增强)
          • ChunkPool:堆外内存申请,创建一个堆外内存池,降低内存的 malloc/free 的系统开销
      • Java 内存
        • 内存模型
          • Java 内存模型的实现是通过内存屏障(memory-barrier)来禁止重排序的
          • 线程之间的共享变量存储在主内存中,每一个线程都一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本
          • 编译器以及处理器对指令进行重排序操作,而 JMM 会在某些情况下通过插入内存屏障(volatile)的方式来限制指令重排序
          • 顺序一致性模型:在顺序一致性模型中,所有操作均完全按照程序的顺序串行执行;保证对所有的内存读写操作都具有原子性
          • Happen Before 原则(如果一个操作要对另外一个操作可见,则必须满足 Happen Before 原则)
            • Happens-Before 与 JMM 的关系图
          • 抽象示意图
        • Java 对象在内存中的存储布局
          • Java 对象头:每个 Java 对象都有一个对象头,保存对象的系统信息,对象头存在一个称为 Mark World 的部分,他是锁实现的关键,存放着对象的 hash 值、对象的年龄、锁的指针信息;一个对象是否使用锁、占用哪个锁,都记录在这个信息里面了
          • 实例数据:对象真正存储的有效信息
          • 对齐填充:占位符的作用,仅仅为了将内存占用空间凑为 8 字节的整数倍
        • Java 虚拟机运行时数据区(通俗来说就是堆栈)
          • 方法区(所有线程共享的区域、常量池(java8 已被移动到堆中)、被虚拟机加载的类的信息、常量、静态变量、对象引用)、虚拟机栈(线程私有、执行 java 方法、StackOverFlow 或者 OOM,与栈内存申请有关)、本地方法栈(为 Native 方法服务,StackOverFlow 或者 OOM,与栈内存申请有关)、堆(所有线程共享的一块内存区域、存放对象实例、虚拟机启动时创建、垃圾收集器管理的主要区域、对堆内部的划分只是为了更好的回收内存与分配内存,如果分配对象时空间不足,OOM)、程序计数器(线程私有、指令跳转、如果执行 native 方法时为 undefined,唯一不报 OOM)、直接内存(Java 直接操作堆外内存,如果使用不当会导致 OOM)
          • 堆、方法区和栈的关系
            • OOM:在抛出 OOM 之前,JVM 会触发一次垃圾收集的动作(Full GC),尽可能的去清理出空间;尝试回收软引用指向的对象
          • 栈帧是方法运行时期的基础数据结构
            • 栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,栈帧随着方法调用而创建,随着方法结束而销毁,栈帧的存储空间分配在 Java 虚拟机栈中,每个栈帧拥有自己的局部变量表(Local Variables)、操作数栈(Operand Stack)  和   指向运行时常量池的引用
            • 每个栈帧内部都包含一组称为局部变量表(Local Variables)的变量列表,局部变量表的大小在编译期间就已经确定。Java 虚拟机使用局部变量表来完成方法调用时的参数传递,当一个方法被调用时,它的参数会被传递到从 0 开始的连续局部变量列表位置上。当一个实例方法(非静态方法)被调用时,第 0 个局部变量是调用这个实例方法的对象的引用(也就是我们所说的 this )
            • 操作数栈
              • 每个栈帧内部都包含了一个称为操作数栈的后进先出(LIFO)栈,栈的大小同样也是在编译期间确定。Java 虚拟机提供的一些字节码指令用来从局部变量表或者对象实例的字段中复制常量或者变量到操作数栈,也有一些指令用于从操作数栈取走数据、操作数据和把操作结果重新入栈。在方法调用时,操作数栈也用来准备调用方法的参数和接收方法返回的结果。
          • java 堆
            • 划分方式
              • 新生代(eden 空间、from survivor 空间、to survivor 空间)、老年代(对象存活周期长)
                • 需要两个 survivor 空间的原因:降低老年代 GC 的频率、降低内存空间的碎片化(主要原因,新生代采用复制回收算法)
                  • 如果没有 Survivor,Eden 区每进行一次 Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发 Major GC
                  • Survivor 的存在意义,就是减少被送到老年代的对象
                • 为什么新生代垃圾回收算法采用复制回收算法
                  • 简单、高效,并且新生代的对象大多都是朝生暮死,生命周期短,mirror gc 频率高,因此对于垃圾回收算法的选择,越简单高效越好
                  • survivor 空间的存在,from1 与 from2,使得可以使用复制回收算法
                • 为什么老年代采用标记整理、标记清除算法
                  • 首先老年代的对象存活的时间都比较长,存活率基本都很高,同时需要预留较多得到内存,因此就不是适用复制回收算法
              • 小 trip:PermGen(永久代),在 java1.8 版本中,String 常量池已经从方法区移到了堆中
            • java 堆可以处于物理上不连续的内存空间,只要逻辑上是连续的即可
          • 方法区(包括运行时常量池)
            • 存储信息:类信息、常量、静态变量、编译器编译的代码
          • 在 JDK 1.8 中移除整个永久代,取而代之的是一个叫元空间(Metaspace)的区域(永久代使用的是 JVM 的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制)。
        • java 对象的访问
          • 句柄访问方式:句柄池、稳定的句柄地址、开销多(多一次指针定位时间开销)
          • 直接指针访问方式
        • 无用类的三个必要条件(类可以被回收,不是一定被回收)
          • 该类的所有实例已被回收
          • 加载该类的 classloader 已被回收
          • 该类对应的 java.lang.Class 对象没有在任何地方被引用、无法通过反射访问该类方法
        • 虚拟机会报错的异常
          • 除了程序计数器外,java 虚拟机运行时数据区都有可能出现 OOM 异常
          • StackOverFlowError: 若 Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 异常。
          • OutOfMemoryError: 若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出 OutOfMemoryError 异常。
      • java 垃圾收集算法
        • 标记-清除法(可用于老年代)
          • 方法:标记需要被回收的对象——>标记完毕——>统一回收
          • 特点:标记与回收的效率不高、容易产生大量的不连续的内存碎片、容易触发另一次垃圾收集动作(分配对象无法找到较大的连续空间时触发)
        • 复制算法(可用于新生代、内存被缩小为原来的一半)
          • 方法:内存按容量对半划分、只使用其中的一块——>用完一块,将对象复制到另一块——>清理内存空间
          • 特点:移动堆顶指针,按序分配内存、将内存缩小为原来的一半(内存可用量降低)
        • 标记-整理算法(压缩算法)(可用于老年代)
          • 方法:标记需要被回收的对象——>标记完毕——>存活对象向一堆移动——>内存清理
          • 特点:需要标记所有的对象,同时还要整理存活对象的内存地址,效率是一个问题
        • 分代收集算法
          • 根据对象的存活周期,选择上述合适的算法进行内存收集
        • 如何加快新生代的垃圾回收
          • 卡表:维护一个 Card Table,比特位记录老年代是否持有某新生代的对象引用,可以避免扫描老年代对象
        • 虚拟机为线程分配空间的注意点(相同的理念——redis 的内存分配机制也是有类似的思想)
          • 优先在一块叫做 TLAB 的区域,对于体积不大的对象,直接在此处分配,失去了在老年代分配对象的机会
          • TLAB(Thread Local Allocation Buffer):线程本地分配缓存,线程专用的内存分配区域
            • TLAB 只是让每个线程有私有的分配指针,但底下存对象的内存空间还是给所有线程访问的,只是其它线程无法在这个区域分配而已。当一个 TLAB 用满(分配指针 top 撞上分配极限 end 了),就新申请一个 TLAB,而在老 TLAB 里的对象还留在原地什么都不用管——它们无法感知自己是否是曾经从 TLAB 分配出来的,而只关心自己是在 Eden 里分配的
            • 每次分配  TLAB  的大小不是固定的,而是每个线程根据该线程启动开始到现在的历史统计信息来自己单独调整的。如果一个线程上跑的代码的内存分配速率非常高,则该线程会选择使用更大的  TLAB  以达到均摊同步开销的效果,反之亦然;同时它还会统计浪费比例,并且将其放入计算新  TLAB  大小的考虑因素当中,把浪费比例控制在一定范围内
            • 均摊对 GC 堆(Eden 区)里共享的分配指针做更新而带来的同步开销
            • 为了加速对象分配而产生的
            • 本身的占用了 Eden 内存区域
      • Java 垃圾收集器(算法的实现)
        • Serial 收集器(可收集新生代)
          • 单线程收集器、复制算法、进行垃圾收集时,必须暂停其他所有的工作环境线程直到垃圾收集结束
        • ParNew 收集器(复制算法)
          • 可以看作是 Serial 收集器的多线程版本
        • CMS 收集器(可收集老年代)
          • 初始标记:标记 GC Roots 能直接关联到的对象(存在停顿);并发标记:进行 GC Roots Tracing 的过程;重新标记(停顿):修正并发标记期间用户程序继续运作而导致标记产生变动的那一部分对象的标记记录
          • 并发收集器、标记-清除算法、让垃圾收集线程与用户工作线程同时工作
          • 获取最短回收停顿时间为目标、重视响应速度
          • 对 CPU 资源敏感、无法处理浮动垃圾(由于垃圾收集线程与用户工作线程并发执行的后果、可能导致 Full GC 动作)
          • 可以设置参数——多少次 CMS 后执行一次内存压缩的操作避免内存碎片
          • 由于在垃圾收集阶段,用户线程还需要运行,因此需求预留出足够的内存空间给用户线程使用,因此 CMS 收集器不能像其他收集器那样等到老年代几乎被完全填满了再收集,如果预留的空间无法满足需求,则会触发 CMF
        • Parallel Scavenge 收集器(复制算法、吞吐量优先收集器)
          • 使用复制算法、并行多线程、可控制的吞吐量
          • 相比 ParNew 收集器的重要区别是存在自适应调节策略(不需要设定新生代大小、Eden 与 Survivor 区的比例、晋升老年代的对象年龄)
        • Serial Old 收集器(可收集老年代)
          • 单线程收集器、标记-整理算法
        • Parallel Old 收集器(可收集老年代)
          • 多线程、标记-整理算法
        • G1 收集器
          • 新生代与老年代不再物理隔离
          • 标记-整理算法、不产生内存碎片、可以精确的控制停顿
          • 将 java 堆划分为多个独立的 region(分区算法),跟踪这些 region 的垃圾堆积程度、维护优先列表、优先回收垃圾最多的 region(根据允许的收集时间)
            • 采用增量回收,每次回收一些区块,而不是整堆回收
          • 混合回收,既执行正常的年轻代 GC,也会选取一些被标记的老年代区域进行回收(Full GC 依旧是单线程执行的)
          • 如果 G1 垃圾回收器出现 Full GC,注意,此时的 Full GC 是单线程的,非常耗时
        • Java 中的 stop the world(STW)
          • 为了让垃圾回收器正常且高效的执行,大部分情况下会要求系统进入一个停顿的状态(终止所有应用线程的执行,才不会有新的垃圾;同时保证了系统在某一瞬间的一致性;使垃圾回收器更好的标记垃圾对象)
          • 停止其他非垃圾回收线程的工作,直到完成垃圾回收;Java 中的 Stop-the-worldd 是通过安全点机制来实现的
      • Java 对象内存分配策略
        • 优先在新生代 Eden 区分配——>Minor GC(触发条件:当 Eden 区满时),频繁、速度快(因此垃圾回收算法的选择更适合选用标记-复制算法)
          • 空间分配担保
            • 发生 mirror gc 之前,会去判断老年代的最大可用连续空间是否大于新生代的所有对象总空间,条件成立的话,那么 mirror gc 发生可以确保是安全的;否则执行一次 or 多次的 full gc
        • 大对象直接进入老年代(需要大量连续空间的 java 对象)
          • 程序应避免朝生夕灭的大对象创建
        • 长期存活的对象进入老年代
          • 对象拥有对象年龄计数器:第一次 Minor GC 后 Eden 中的对象存活并且可以被 Survivor 容纳,对象移动到 Survivor,对象年龄加一,在 Survivor 区中每经历一次 Minor GC(频繁、速度快),对象年龄加一,一定程度后晋升至老年代
        • 动态对象年龄判定
          • 若 Survivor 空间中相同年龄的对象大小总和等于 Survivor 空间的一半,则对象年龄大于等于该年龄进入老年代
          • 老年代的 Full GC(触发条件:老年代空间已满、System.gc、方法区空间不足)
        • 卡表技术
          • 场景:老年代的对象可能引用新生代的对象,在标记存活对象的时候,需要扫描老年代的对象,如果该对象拥有对新生代对象的引用,那么这个引用也会被称为 GC Roots——因此有做了一次全表扫描
          • 解决方案:
            • 将整个堆划分为一个个大小为 512 字节的卡,并维护一个卡表,用来存储每张卡的一个标识位,这个标识位代表对应的卡是否可能存有指向新生代对象的引用,如果存在,则代表这张卡是脏的;在进行 Minor GC 的时候,可以不用扫描整个老年代,而是在卡表中寻找脏卡,将脏卡中的对象加入到 Minor GC 的 GC Roots 中,完成所有的脏卡扫描后,Java 虚拟机便会将所有的脏卡的标识位清零
            • Minor GC 伴随着存活对象的复制,而复制需要更新指向该对象的引用,因此,在更新引用的同时,又会设置引用所在的卡的标识位,为了确保每个可能指向新生代对象引用的卡都被标记为脏卡,那么 Java 虚拟机需要截获每个引用型实例变量的写操作,并作出对应的写标识位操作(即时编译器生成的机器代码中 ,需要插入额外的逻辑——写屏障,写屏障不会判断更新后的引用是否指向新生代中的对象,而是一律当做指向新生代对象的引用)
      • JVM 相关虚拟机参数
        • 垃圾回收:-XX:(+PrintGCDetails、+PrintAHeapAtGC 打印 GC 前后的堆情况、+PrintGCTimeStamps 分析 GC 发生的时间)、-Xloggc:log/gc.log 设置 GC 打印到文件中
        • 类的加载与卸载:-XX:+TraceClass[Loading/Unloading](特别是用于观察动态生成类的加载、卸载的过程)、-XX:PrintClassHistogram 查看系统类的分布情况
          iii. 堆的配置参数:-Xms20m 指定堆的大小、-Xmx 指定堆的最大空间大小(注意,实际可用的空间大小与-Xmx 参数配置的存在偏差)、-Xmn 设置新生代的大小、-XX:SurvivorRatio:设置新生代中 eden 空间和 from/to 空间的比例关系、-XX:NewRatio 设置新生代与老年代的比例
        • 内存错误:-XX:+HeapDumpOnOutOfMemoryError 内存溢出时导出整个堆信息、-XX:HeapDumpPath 指定信息到处存放的路径
        • 栈配置:-Xss 指定线程的栈大小
        • 堆参数的配置
          • 对分配参数示意图
          • 虚拟机会尽可能的维持在初始堆大小下进行运行,如果内存不够用,则会扩展到设置的最大堆内存(-Xmx)(建议将-Xmx 与-Xms 设置为相同大小,避免因为堆伸缩带来的损失)
        • 非堆内存参数设置
          • 设置最大直接内存大小(Java 堆外内存),如果超出设置,依旧会导致系统 OOM,另外,该内存区域也存在垃圾回收,并且直接内存不适用于频繁申请空间的场景,更适合于申请次数较少,访问频繁的场景
        • 虚拟机的工作模式
          • Client、Server
        • 查看 Java 进程:jps、查看虚拟机参数设置:jinfo、导出堆到文件:jmap(对象统计信息、(-heap)当前堆快照信息、查看 finalizer 队列中的垃圾对象)、jhat(自带的堆分析工具,对*.hprof 文件进行分析,直接在 http 访问分析的结果)
          x. 查看虚拟机运行时命令:jstat(-gc pid 打印 GC 相关的堆信息)(-gccause pid 打印最近一次 GC,以及导致 GC 的原因)
        • 查看线程堆栈信息:jstack(jstack -l pid > a.txt)

      Java 锁机制

      • Java 虚拟机在释放锁时,同样会强制刷新缓存,使得当前线程所修改的内存对其他线程可见

      • Lock 锁

        • Lock 锁与 jvm 的 synchronized 相比,Lock 是可中断的锁、可超时获取锁、尝试非阻塞的获取锁,而 synchornized 是不可中断的
          • 什么是可中断:如果某一线程 A 正在执行锁中的代码,另一线程 B 正在等待获取该锁,可能由于等待时间过长,线程 B 不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁
        • ReentrantLock
          • 支持获取锁时是采用公平原则还是非公平原则
            • 公平原则获取锁时,多了 hasQueuedPredecessors()方法判断,判断当前节点是否有前驱节点,如果有,等待前驱节点获取并释放锁后才可以获取锁
          • 支持锁的重入(锁被线程 A 所持有,当线程 A 再次进入时可以再次获取到锁、再次获取锁时只是更新同步状态值、对当前线程进行锁持有者的判断、假设线程 A 重复 n 次获取锁,那么最终释放锁时也需要释放 n 次)
            • 对于独占锁,内部存在一个变量记录当前持有独占锁锁的线程
            • 锁支持重入的原因:内部有与线程相关的计数器,获取一次锁,计数器加一;释放一次锁,计数器减一
        • ReentrantReadWriteLock
          • 定义了读锁与写锁两个方法
          • 读锁之间不互斥,只要有写锁就会产生互斥,保证了写操作对读操作的可见性,适用于读多写少的应用场景
          • 对 int32 进行按位划分,高 16 位为读锁状态,低 16 位为写锁状态,通过位运算进行判断
          • 每个线程的各自获取读锁的次数信息保存在 ThreadLocal 上
          • 写锁的降级(读优先于写、数据实时连续性:当获取到最新的数据时需要马上根据最新的数据进行处理)
        • Condition(golang 的 cond 以及 lock)
          • condition 依赖于 Lock 对象、定义了等待/通知两种类型
          • 每个 condition 对象包含一个队列(FIFO)、没有采用 cas 保证更新过程,因为已经采用了锁来保证了(依赖于 Lock 对象,在 condition 使用之前必须 lock.lock()上锁)
      • Synchronized(偏向锁->轻量级锁->重量级锁(重量级锁涉及用户态与内核态的切换),锁的重量逐渐递增)

        • 偏向锁与轻量级锁的图示

        • 对象头信息

          • Object Header 分为两部分:存储对象自身运行时数据(哈希码、GC 分代年龄;Mark Word:实现轻量级锁与偏向锁的关键)、存储指向方法区对象类型数据的指针(数组对象的话还会有一个额外的部分存储数组的长度)
            • 对象头信息与对象自身定义的属性无关
        • 偏向锁

          • 该类型的锁会偏向于第一个获得它的线程,当持有该偏向锁的线程进入同步块时,虚拟机可以不再进行任何同步操作
          • 一旦有其他线程去尝试获取这个锁,偏向模式就宣告结束
          • 不适合竞争激烈的场景,对象头会记录获得锁的线程信息
        • 轻量级锁

          • 使用时会先备份对象的原有的对象头信息,然后采用 CAS 操作将 Basiclock 的地址对对象头进行替换操作,如果替换成功则加锁成功,否则轻量级锁有可能膨胀为重量级锁;如果要判断某一线程是否持有对象的锁,仅需简单的判断对象头的指针是否在当前线程栈空间的地址范围内
        • 自旋锁:执行一个空循环,在若干个空循环后线程如果可获得锁,则继续执行,否则线程将会被挂起

        • 出现异常锁自动释放、可重入锁

        • 静态 synchronized 锁的对应的 class 类

        • 监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的

          • synchronized 同步语句块的情况:synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令;会自动加上异常 try-catch -finally,在 finally 中加上锁的释放操作
          • synchronized 修饰方法的的情况:synchronized 修饰的方法是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
        • 修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁

        • 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁(因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁)

        • 单例模式的实现

          • 内部类(类加载以及初始化的机制)
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          	public class SingletonIniti {
                  private SingletonIniti() {
                  }

                  private static class SingletonHolder {
                          private static final SingletonIniti INSTANCE = newSingletonIniti();
                   }

                  public static SingletonIniti getInstance() {
                          return SingletonHolder.INSTANCE;
                  }
          }
          ```
          - 双重校验锁
          ```java
          public class Singleton {
          private volatile static Singleton uniqueInstance;

          private Singleton() {
          }

          public static Singleton getUniqueInstance() {
          if (uniqueInstance == null) {
          synchronized (Singleton.class) {
          if (uniqueInstance == null) {
          uniqueInstance = new Singleton();
          }
          }
          }
          return uniqueInstance;
          }
          }
          ```

      java 多线程

      • 在 HotSpot VM 中,Java 线程被一对一映射为本地操作系统线程
      • 变量值在线程间传递需要通过主内存来完成(java 内存模型的要求)
      • 核心——AQS(https://juejin.im/post/5a4a4530518825697078553e)
        • 构建锁以及同步器的框架(本质就是一个队列——CLH)
          • 原始的 CLH 采用 locked 自旋,而 ASQ 中的 CLH 在每个 node 里面使用一个状态字段来控制阻塞,而不是自旋
          • 为了可以处理 timeout 和 cancel 操作,每个 node 维护一个指向前驱的指针。如果一个 node 的前驱被 cancel,这个 node 可以前向移动使用前驱的状态字段
          • 中断补偿的原因:由于 Lock 设计成了可以响应中断的获取锁,因此通过当前获取锁的方式是否支持中断响应来决定是否进行相应的中断补偿
        • 共享资源采用 volatile 修饰(线程间的可见性);对于资源的占用方式采用独占或者共享两种模式
        • 核心思想:被请求的共享资源空闲,将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态
        • 线程节点 Node 中存储着当前线程的等待状态信息:CANCELLED 等待超时/被中断、SIGNAL 待唤醒状态、CONDITION 处于等待队列中、PROPAGATE 共享模式有关
        • 如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断 selfInterrupt(),将中断补偿
        • 核心对象——Node
          • 前驱节点:pre,用于直接跳过那些被 cancle 的 node;后继结点:next,用于当调用 releaseLock 时进行后继节点的唤醒,而后继节点在前继节点上打上 SIGNAL 标识, 来提醒他 release lock 时需要唤醒
          • 相应的几个重要的队列
            • Sync Queue
            • Condition Queue
        • acquireQueued(Node,int):用于线程资源申请
          • 会根据前驱节点以及自己位置进行判断是否可以去获取资源,否则将自己置为休息状态,等待被唤醒
          • 置为休息状态时首先需要判断是否真的可以进行休息(进入 waiting),如果已通知前驱线程节点获取资源后通知自己,那么可以进入休息状态;如果前驱节点的状态为放弃,那么需要向前查找一个正常状态的线程,排在他后面;如果前驱节点状态正常,设置前驱节点状态为 SIGNAL,告诉他获取资源时通知自己
          • 整个资源申请代码流程图
      • Volatile(解决数据的可见性,对 volatile 的读写是原子性的,但是不保证复合操作是原子性)
        • 多线程访问 volatile 关键字不会发生阻塞
        • 由于内存写操作同时会无效化其他处理器所持有的、指向同一内存地址的缓存行,因此可以认为其他处理器能够立即见到该 volatile 字段的最新 值。
        • 保证变量对所有线程的可见性、新值对于其他线程是立即可知的(其他线程去读时将被强制要求去读主内存中的变量值,线程拷贝的值被过期)
        • 写一个 volatile 变量时,JVM 会把线程对应的本地内存中的共享变量刷新到主内存
        • 读一个 volatile 变量时,JVM 会把线程对应的本地内存置为无效,使得线程必须从主内存中读取共享变量最新的值
        • 如果 volatile 修饰的是数组,那么仅仅保证对象获取数组的地址具有可见性,数组内的元素不具有可见性
        • 会限制指令重排序
          • 典型示例
            • 单例模式中,java 对象的创建分为三步骤:1.为对象分配内存空间——>2.初始化对象——>3.将对象指向分配的内存地址(2、3 步骤存在指令重排序)
      • CountDownLatch
        • 类似 thread 的 join 方法,可以设置等待的 n 个线程或者 n 个步骤(可用于控制多个线程同时运行)
        • countDown():消费一个 cnt,await():等待 cnt==0
        • 只允许使用一次
      • CyclicBarrier(同步屏障)
        • 线程执行 await 方法告知已到达屏障,当规定的 n 个线程到达屏障时,屏障解放,不阻塞线程
        • 可调用 rest 方法进行重置重复使用
      • Semaphore(信号量)
        • 控制同时访问资源的线程数量(类似于流量控制)
        • acquire 获取资源、release 方法释放资源
      • Yeild 使用
        • 让出当前 CPU 资源,让其他线程来竞争;当大量的线程执行 yeild 时,导致大量的线程在竞争资源,因此会导致 CPU 利用率高达 100%
      • 线程
        • 如果直接调用 run 方法则不是异步执行,而是又回到了最初的顺序执行;只有调用 Thread 的 start 方法才是开启另一个线程去执行
        • 线程优先级具有继承性(A 线程启动 B 线程,B 线程优先级与 A 线程一样)
        • 非线程安全存在于实例变量中,方法内部的私有变量则不存在线程安全的问题(线程的方法栈)
        • 守护线程(Daemo),为其他线程提供便利服务(JVM 中的 GC 线程就是一个守护线程),当一个 Java 程序只存在守护线程时,程序随即退出;
        • Thread.interrupt
          • 如果一个线程的 run() 方法执行一个无限循环,并且没有执行 sleep() 等会抛出 InterruptedException 的操作,那么调用线程的 interrupt() 方法就无法使线程提前结束。但是调用 interrupt() 方法会设置线程的中断标记,此时调用 interrupted() 方法会返回 true。因此可以在循环体中使用 interrupted() 方法来判断线程是否处于中断状态,从而提前结束线程。
        • Leader-Follower 线程模型
          • 在 Leader-follower 线程模型一开始会创建一个线程池,并且会选取一个线程作为 leader 线程,leader 线程负责监听网络请求,其它线程为 follower 处于 waiting 状态,当 leader 线程接受到一个请求后,会释放自己作为 leader 的权利,然后从 follower 线程中选择一个线程进行激活,然后激活的线程被选择为新的 leader 线程作为服务监听,然后老的 leader 则负责处理自己接受到的请求(现在老的 leader 线程状态变为了 processing),处理完成后,状态从 processing 转换为。Follower
          • 这种模式下接受请求和进行处理使用的是同一个线程,这避免了线程上下文切换和线程通讯数据拷贝
          • ScheduledThreadPoolExecutor 中的 DelayedWorkQueue 的实现中采用了此线程模式
        • 线程状态
          • 新建、运行、无限期等待、限期等待、阻塞、结束
        • ThreadLocal
          • 创建只能被同一个线程读写的变量(每个线程拥有一个自己的共享变量、每个线程绑定自己的值、存放每个线程的私有数据)
          • ThreadLocal 的特殊性保证了其能够满足事务的实现、保证当前线程操作的都是同一个 Connection
          • 内部数据 key-value 形式的存放由 ThreadLocalMap 对象实现
            • Map 的 key 是每个线程引用的 ThreadLocal 对象
            • ThreadLocalMap 的内部有一个 Entry 对象(继承了 WeakReference)
            • ThreadLocalMap 的 key 是 LocalThread 对象本身,value 则是要存储的对象,ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value
          • 副作用
            • 存在脏数据以及内存泄露(常见于线程池中的线程使用 ThreadLocal)
              • 脏数据:thread 的复用,可能导致线程读取到上一个线程缓存的信息
              • 内存泄露:由于 thread 持有 threadlocal 引用,因此触发弱引用机制回收就显的不现实
            • 解决方案:根据业务场景在适合的地方执行 remove 方法,进行清除数据
          • 应用场景:多源数据库读写的切换,为了确保每个线程连接的数据库源不被外部所影响,用 ThreadLoacl 保存该线程所连接的数据库源标识信息
        • InheritableThreadLocal(父线程传递本地变量到子线程)
          • 开源项目:https://github.com/alibaba/transmittable-thread-local
          • Thread 维护了两个变量:ThreadLocal 以及 InheritableThreadLocal
          • 在线程的构造函数中有一个 init(…)函数,会获取创建该线程的父线程信息,进行父线程信息同步给子线程;但是在线程池模式下,存在线程复用的情况,那么这个时候就无法再次执行 init 函数将父线程的信息赋值给子线程
      • 线程间通信
        • Object 类方法的 notify、notifyAll、wait 方法(wait()/notify()时,必须拥有该对象的同步锁)
          • notify:通知一个在对象上等待的线程,使其从 wait 方法返回(前提是该线程获取了该对象的锁)
          • notifyAll:通知所有在对象上等待的线程
          • wait:使线程进入 waiting 状态
          • 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。  当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。  优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
        • 管道输入、输出流
          • 字节管道 PipedOutputStream、PipedInputStream,字符管道 PipedReader、PipedWriter
          • 输入、输出管道需要进行连接(out.connect(in))
        • Thread.join
          • 等待 join 的线程终止后从 join 返回
          • 其内部实现依旧是依靠 Object 类的等待/通知机制实现,线程终止时会调用 notifyAll
          • 大致代码

            1
            2
            3
            4
            5
            6
            7
            public final synchronized void join()throws InterruptedException {
            条件不满足,继续等待
            while(isAlive()){
            wait(0)
            }
            条件符合,返回返回
            }
      • 死锁
        • 当使用 Future 模式时,可能存在自己把自己挂起导致线程死锁的问题
      • 线程池
        • java 线程池会将守护线程转为用户线程进行运行
        • 线程池参数
          • corePoolSize(核心线程数量)
          • runnableTaskQueue(任务队列、当线程数达到核心线程数量时,任务进入队列中等待调度执行,如果队列是有界的,则当队列有界时,判断当前的最大线程数量与当前线程数量的关系决定是否可以创建新的线程执行任务)
            • ArrayBlockingQueue:必须要有初始队列大小
            • LinkedBlockingQueue
            • SynchronousQueue:不是一个真正的队列,而是一种在线程之间移交的机制。要将一个元素放入 SynchronousQueue 中, 必须有另一个线程正在等待接受这个元素. 如果没有线程等待,并且线程池的当前大小小于最大值,那么 ThreadPoolExecutor 将创建 一个线程, 否则根据饱和策略,这个任务将被拒绝
            • PriorityBlockingQueue:优先级队列,有界队列,内部通过比较器实现
            • DelayedWorkQueue:延迟的工作队列,无界队列
          • maximumPoolSize(线程池最大数量,当任务队列满时,判断线程数量是否到达此值,创建临时线程跑任务)
          • ThreadFactory(设置创建线程的工厂、当任务抛出异常时,线程相当于停止——即 worker 消亡)
          • RejectedExecutionHandler(饱和策略、直接抛出异常、只用调用者所在的线程执行任务、丢弃队列里最近一个任务并执行当前任务、抛弃任务)
          • keeyAliveTime(线程活动保持时间,终止前多余的空闲线程等待新任务的最长时间)
          • TimeUnit(线程活动保持时间单位)
        • 线程池的处理流程
          • 底层调度执行
          • 合理的配置
            • 对于 CPU 密集型尽可能的配置小的线程(Ncpu + 1);而 IO 密集型,由于不是一直在执行任务,因此可以尽可能多的配置线程(2 * Ncpu)
        • 任务提交方式
          • execute:用于提交不需要返回值的任务
          • submit:用于提交需要返回值的任务(feature 对象)
        • Executor 框架(异步任务框架)
          • 两级调度模型:任务通过 Executor 框架映射到 java 线程,java 线程通过操作系统映射到硬件处理器、Executor 框架(用户级调度器)控制上层的调度、下层的调度由操作系统内核控制,下层的调度不受应用程序的控制
          • 几种实现
            • FixedThreadPool:固定线程数目
            • SingleThreadExecutor:单个线程,适用于顺序执行各个任务
            • CacheThreadPool:大小无界的线程池、执行短期异步任务的小程序
            • ScheduledThreadPoolExecutor:若干线程的周期任务
            • SingleThreadScheduledExecutor:单线程的周期任务
            • CompletionService:带有完成任务队列的任务提交池,能够获取完成的任务(内部存在这样的一个已完成任务队列)
        • Future
          • 链式问题
            • CompletableFuture 能够较好的解决 future 之间的存在数据联系时的链式调用
        • ForkJoin 框架(适用于将一个任务变为并行的数个小任务)
      • Java 中的安全模型
        • 本地代码默认为可信任的(可访问一切本地资源),而远程代码为不可信任的(安全依赖于沙箱机制,将代码限定于 JVM 的特定运行访问内)
        • 增加了代码签名。不论本地代码或是远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机中权限不同的运行空间,来实现差异化的代码执行权限控制
        • 引入了域 (Domain) 的概念。虚拟机会把所有代码加载到不同的系统域和应用域,系统域部分专门负责与关键资源进行交互,而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域 (Protected Domain),对应不一样的权限 (Permission)。存在于不同域中的类文件就具有了当前域的全部权限
        • 访问控制上下文的继承问题。当一个线程创建另一个新线程时,会同时创建新的堆栈。如果创建新线程时没有保留当前的安全上下文,也就是线程相关的安全信息,则新线程调用 AccessController.checkPermission 检验权限时,安全访问控制机制只会根据新线程的上下文来决定安全性问题,而不会考虑其父线程的相应权限

      Spring

      • 事务管理(TransactionAspectSupport 类)
        • 使用了大量的 ThreadLocal 类进行保存当前线程的信息,当事务切换时,本质上就是获取一个新的数据库连接然后把事务同步管理器中的 ThreadLocal 变量替换掉
        • 优秀博客:https://huzb.me/2019/03/28/Spring-AOP%E6%BA%90%E7%A0%81%E6%B5%85%E6%9E%90%E2%80%94%E2%80%94%E4%BA%8B%E5%8A%A1%E7%9A%84%E5%AE%9E%E7%8E%B0/
        • readOnly:设置当前事务是否为只读事务
        • rollbackFor:设置需要回滚的异常类型
        • propagation:设置事务传播行为
          • 支持当前事务,如果当前没有事务,则新建一个事务
          • 支持当前事务,如果当前没有事务,则以非事务方式运行
          • 支持当前事务,如果当前没有事务,则抛出异常
          • 新建事务,如果存在当前事务,则当前事务挂起
          • 非事务执行,如果当前存在事务,则当前事务挂起
          • 非事务执行,如果当前存在事务,则抛出异常
          • 当前存在事务,则在嵌套事务内执行:嵌套事务的本质是对外部事物做一次 save point 机制,内部事务的回滚都是回滚到保存点
        • isolation:设置底层数据库的事务隔离级别
        • timeout:设置事务超时秒数
        • 执行带有事务注解的方法
        • spring 之所以能够接管数据库的事务管理,是因为提供了接口:org.springframework.transaction.PlatformTransactionManager ,继承该接口,各个平台实现自己的事务管理
      • SpringMVC 路由<=>方法(DispatcherHandler)
        • HttpWebHandlerAdaper、SimpleHandlerAdapter、ExceptionHandlingWebHandler、WebHandlerDecorator
        • 流程
          • 接收到 Http 请求时进入 HttpWebHandlerAdaper
          • 将请求包装为 ServerWebExchange:ServerWebExchange exchange = createExchange(request, response);
          • 将 ServerWebExchange 送入到 WebHandlerDecorator
          • 接着送入 ExceptionHandlingWebHandler
          • 进入 ReactorHttpHandlerAdapter
        • 注册(将几个 Handler 的实现进行注册到 Spring 中)
        • 进行 URL 与 handler 的匹配(lookupHandlerMethod)
      • SpringBoot 相关
        • @SpringBootApplication
          • 由三大注解的组合:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan
            • @SpringBootConfiguration:将当前类标注为配置类,并且将当前类里以@Bean 注解标记的方法实例注入到 Spring 容器中
            • @EnableAutoConfiguration:启动自动配置功能,将所有符合条件的@Configuration 配置都加载到当前的 IOC 容器中(通过 spring.factories 文件进行自动配置)
              • 借助 AutoConfigurationImportSelector ,通过实现 selectImports()导出 Configuration,依赖于 SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader())读取 classpath 下的 spring.factories 文件来导出所有的类
            • @ComponentScan:扫描包下的所有类查看是否被标注了特定的注解,进而进行 Bean 的生成以及相应的 IOC
          • 获取当前 main 方法所在的类
            • new RuntimeException().getStackTrace()获取 StackTraceElements[]数组,然后遍历比较方法名,通过 StackTraceElement.getClassName 获取当前 main 方法的类名,然后利用反射获取 main 方法所在类的 Class 对象
          • 启动流程
            • 获取并创建 SpringApplicationRunListener 并且由其通知 starting——>创建参数,配置 Environment——>SpringApplicationRunListener 通知 environmentPrepared——>创建 ApplicationContext——>初始化 ApplicationContext(设置 application-context 类型)、设置 Environment、加载相关配置——>SpringApplicationRunListener 通知 contextPrepared、contextLoaded(告知 Spring 应用使用的 Application 已经装填完毕)——>refresh ApplicationContext——>SpringApplicationRunListener 通知 started——>完成最终程序启动
            • 代码
              • 自动化配置的关键代码
                • refreshContext(context);
                • afterRefresh(context, applicationArguments);
              • 启动后的相关拓展(可以实现自己的业务处理)
                • ApplicationRunner、CommandLineRunner 接口或者 Application 事件监听器(期望在哪个阶段执行那些任务,就监听对应的事件即可)
              • 应用退出的优雅接口
                • org.springfamework.boot.ExitCodeGenerator,返回一个特定的返回码
      • IOC(控制反转)
        • Bean 管理
          • ApplicationContext 容器管理 bean
          • java 的代理类机制实现(Proxy)
          • CGLib 的动态字节码库代理实现(相比 java 的代理机制实现更为强大,不需要实现接口,相当于生成一个新的类,将类的字节码装入虚拟机而不需要通过反射)
            iv. Spring Aware:使得 Bean 意识到 Spring 容器的存在,使得调用 Spring 所提供的资源
          • BeanFactory 与 ApplicationContext 的联系以及区别
            • 区别
              • BeanFactory 是延迟加载,使用到才会去创建 Bean,ApplicationContext 会在初始化的时候就加载并且检查
            • 联系
              • ApplicationContext 继承 BeanFactory,并且提供了更多面向应用的功能,面向的是 Spring 的开发者;BeanFactory 是 Spring 的基础设施,更多的是面向 Spring
        • ApplicationContextInitialize 执行
        • ConfigurationClassPostProcessor 执行(优先执行)
          • postProcessBeanDefinitionRegistry(如果存在 Aware,则优先执行 Aware 回调,然后再执行)
          • postProcessBeanFactory
        • Bean 的注入
          • 注册与解析 BeanDefintion
            • 注册和解析 BeanDefinition,发生在 AnnotationConfigApplicationContext#register 流程中,其方法内部使用了 AnnotatedBeanDefinitionReader#register 来实现 BeanDefinition 的解析和注册;而且在实例化 AnnotatedBeanDefinitionReader 后,立即向 container 注册了多个 BeanPostProcessor 的 BeanDefinition
          • 准备 BeanFactory
            • 在 AnnotationConfigApplicationContext 内部,组合了 DefaultListableBeanFactory。在 prepareBeanFactory(beanFactory)方法的调用过程中,向 beanfactory 注入了环境变量、环境属性等。而且注入了多个 BeanPostProcessor
          • 调用 BeanFactoryProcessor(此时所有的 BeanDefinition 都已加载完毕)
            • 此时 container 已经注册了一系列的 BeanFactoryPostProcessor、BeanPostProcessor 和应用层相关的 bean 的 BeanDefinition。由于此时所有的 bean(包括 BeanFactoryPostProcessor、BeanPostProcessor 已经应用层的 bean)都是以 BeanDefinition 存在于 container 中,并未实例化
          • 注册 BeanPostProcessor
            • 对注册到 BeanFactory 中的 BeanPostProcessor 进行实例化,添加到 BeanFactory 中的 BeanPostProcessor 处理队列中
          • 真正实例化和初始化 Bean
            • 与加载所有已注册的所有 Bean
            • BeanFactory 中,对一个 Bean 调用 getBean 方法才会进行 Bean 的加载,而 ApplicationContext 则是直接触发其内部的 BeanFactory 加载所有定义好的 Bean;在加载 bean 的过程中,涉及三个步骤
              • 实例化、填充属性、初始化
        • 解决类与类之间的依赖关系、将类的管理移交给 Spring 框架
        • 依赖于 Java 的反射机制来实现
        • BeanDefinition(容器实现依赖反转功能的核心数据结构)
        • Bean 的三级缓存结构(解决循环依赖问题:AbstractBeanFactory.doGetBean)
          • 博客地址:https://segmentfault.com/a/1190000015221968
          • 缓存结构定义于 DefaultSingletonBeanRegistry 类中
          • singletonFactories:单例对象工厂 cache(存放 bean 工厂对象,用于解决循环依赖、三级缓存)
          • earlySingletonObjects:提前曝光的单例对象的 cache(此缓存是由于解决循环依赖的重要部分)、正在构建但是构建未完成的对象、构造函数已经执行(属性尚未填充、被称为早期引用)
          • singletonObjects:单例对象的 cache(一级缓存、由于存在并发情况,采用 ConcurrentHashMap,完全初始化好的 Java 对象)
          • 三级缓存获取到对象后,从三级对象移除(因为 ObjectFactory 调用 getObject()会创建 bean)并放入二级缓存中
        • Bean 的生命周期
          • Bean 容器找到配置文件中 Spring Bean 的定义。
          • Bean 容器利用 Java Reflection API 创建一个 Bean 的实例。
          • 如果涉及到一些属性值 利用 set 方法设置一些属性值。
          • 如果 Bean 实现了 BeanNameAware 接口,调用 setBeanName()方法,传入 Bean 的名字。
          • 如果 Bean 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader()方法,传入 ClassLoader 对象的实例。
          • 如果 Bean 实现了 BeanFactoryAware 接口,调用 setBeanClassLoader()方法,传入 ClassLoader 对象的实例。
          • 与上面的类似,如果实现了其他*Aware 接口,就调用相应的方法。
          • 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行 postProcessBeforeInitialization()方法。
          • 如果 Bean 实现了 InitializingBean 接口,执行 afterPropertiesSet()方法。
          • 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
          • 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行 postProcessAfterInitialization()方法。
          • 当要销毁 Bean 的时候,如果 Bean 实现了 DisposableBean 接口,执行 destroy()方法。
          • 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。
      • AOP(切面编程)
        • 环绕通知与前置、后置通知的不同:环绕通知可以决定是否调用目标方法,而前置、后置通知,其目标方法一定会执行
        • ava 原生 AOP 的实现
          • Proxy.newProxyInstance(classloader,class[]接口数组,InvocationHandler 接口的实现),创建一个代理类
          • Implement InvocationHandler 接口,重写 invoke(动态代理类的引用,方法对象的引用,参数数组)方法
        • CGLib 字节码库,创建一个继承的 Class 对象然后拦截方法执行
        • AOP 下的消息执行过程
      • Application 事件和监听器(可以在容器加载过程中执行自己的业务代码)
        • 运行开始(除监听器注册和初始化以外)=>ApplicationStartedEvent
        • Environment 将被用于已知的上下文,但在上下文被创建前 =>ApplicationEnvironmentPreparedEvent
        • refresh 之前,bean 定义已被加载后 =>ApplicationPreparedEvent
        • refresh 之后,相关回调处理完 =>ApplicationReadyEvent
      • Spring Cloud 如何实现配置刷新
        • 原理示意图

      Mybatis

      • SqlSessionFactoryBuilder
        • 唯一的作用就是创建 SqlSessionFactory
        • 生命周期仅仅局限于方法内部
      • SqlSessionFactory
        • 具有两个默认实现、创建 SqlSession
        • 通过文件流获取.xml 的配置文件,将配置文件信息缓存到 Configuration 对象,然后创建 SqlSessionFactory 对象
      • SqlSession
        • 是一个会话,类似于 JDBC 的 connection 对象,生命周期为请求数据库处理事务的过程中
        • 线程不安全对象
        • 控制数据库事务(提交、回滚)
        • 运行总结
      • SQLMapper
        • 框架生成 MapperMethod 对象
        • 执行一条条 sql 语句
        • 仅仅是一个接口(java 中的动态代理需要接口对象进行实现),不包含具体的逻辑实现
        • 由 java 自带的动态代理 or CGLib 字节码库创建出的代理类去具体实现每个接口的方法
        • 映射器内部组成(核心)
          • MappedStatement:保存映射器的节点
          • SqlSource:根据参数以及规则组装 sql
          • BoundSql:建立 SQL 和参数的地方
            • parameterMappings:是一个 List 对象,用于描述参数的具体信息,可以结合 PreparedStatement 找到 parameterObject 设置参数
            • parameterObject:参数本身、如果是多个参数会自动专为 Map<String,Object>对象(如果没有@Param 注解时 String 值为“1”或者“param1”),如果存在@Param 注解时 String 的值为@Param 的值
            • sql:书写在映射器的 sql 语句
          • Executor 执行器调度 StatementHandler、ParameterHandler、ResultHandler
            • 通过 Configure 对象创建,同时需要事务对象 final Executor executor = configuration.newExecutor(tx, execType);
            • Executor:真正执行 java 和数据库交互的东西(三种执行器:SIMPLE、REUSE、BATCH)
            • StatementHandler:专门处理数据库会话,真实对象为 RoutingStatementHandler(而其下又分为三种 Handler:SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler),根据上下文进行创建期望的 Handler
            • ParameterHandler:参数处理器,对预编译的 sql 语句进行参数设置
            • ResultHandler:结果集处理器
            • 查询操作大致流程
              • instantiateStatement(connection)进行 sql 预编译,设置超时时间、获取的最大行数
              • parameterize(statement)设置 sql 参数——>调用 ParameterHandler 进行参数设置
              • 执行 sql 语句
      • 级联关系
        • 一对一·:association(父方设置该属性、同时设置 select 语句查询)
        • 一对多:collection(在一的一方设置该属性)
        • 鉴别器:discriminator(根据实际情况,例如 person 对象有男女区分、类似于 Java 中的 switch 语句)
      • 缓存
        • 系统缓存(一级缓存、二级缓存)
          • 同一个 Mapper+同参数+同 sql——>SqlSession 第一次执行后将其放入缓存中
          • 一级缓存是 SqlSession 级别的,SqlSession 之间缓存不共享;二级缓存在 SqlSessionFactory 层面共享,返回的 POJO 必须是可序列化的
          • 默认使用 LRU(最近最少使用)算法回收
      • 动态 SQL 语句
        • swith-case-default 的解决方案——>choose-when-otherwise
        • foreach:
          • 遍历集合,支持数组、List、Set
          • 参数:collections(集合的参数名称)、item(循环中当前的元素)、index(位置下标)、open 和 close(以什么符号将元素包装起来)、separator(各个元素的间隔符)
        • bind:自定义一个上下文变量,更多的是 sql 的参数连接操作
      • 延迟加载问题(对返回类的进行动态代理,拦截相应的方法)
        • 延迟加载的目的(使用数据才进行加载、避免无用数据获取造成性能损失)
        • aggressiveLazyLoading 为 true 时默认为层级加载(数据同层则对数据进行加载)、为 false 时为延迟加载(直到使用时才会去使用数据)
        • 具有局部加载设置,在级联关键字的属性中有 fetchType 进行设置(两个设置:eager 和 lazy)
        • 延迟加载的原理参考 java 的动态代理(javassist)
        • 具体方法拦截实现
          • EnhancedResultObjectProxyImpl
          • 源码截图
      • 与 Spring 的结合
        • 实现 javax.sql.DataSource 接口

      Apache HttpClient

      • 设计模式:责任链模式,继承 ClientExecChain,根据各自的作用,将所有实现链起来,将请求发送到职责链上即可
      • rewriteRequestURI(request, route):如果设置了 HttpRoute,会自动开启 URI 的重写,HttpRoute 对象是 immutable 的
      • 终止请求调用 abort()方法
      • HttpEntity 的不同实现决定了是否可以复用内容:ByteArrayEntity、StringEntity、FileEntity 为可重复获得使用
        • ByteArrayEntity 其内部的 getContent 方法的实现采用调用—创建一个新的 InputStream 对象,将数据 copy 到新的 InputStream 对象中,实现重复使用
      • HttpContext
        • Http 请求的上下文,发起 Http 请求时带上 HttpConext 将共享保存的信息(类似于 Session 机制)
        • MainClientExec(The last request executor in the HTTP request execution chain)
      • 大致请求流程
        • http 请求链接的建立(socket 链接建立);消息的发送执行者(路由的选择、可能的重定向、消息的鉴权、链接的分配回收、链接相关设置[链接是否保持、相应的 encoding、保持连接的时间设置、重试规则的设置、相关代理路由的设置])
      文章作者: Estom
      文章链接: https://estom.github.io/2022/11/26/Java/09%20Java%E9%9D%A2%E8%AF%95%E6%80%BB%E7%BB%93/%E6%80%BB%E7%BB%93%E6%96%87%E6%A1%A3/
      版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Estom的博客