# Java 核心

# 集合

# hashmap

  • 实现 1.7是数组+链表
    1.8是数组+链表+红黑树 当链表元素大于8 更换为红黑树、当缩减元素小于6更换为链表
    默认数组长度:16
    默认负载因子:0.75 在0.75负载因子下 大多数情况下是均匀分布的

处理hash冲突的方式 就是在hash数组对应的链表或者红黑树添加新的节点
计算hash使用hashCode 但是冲突的时候会使用equals去比较 判断是否添加到新节点 index=(hash(key) % 数组长度) 当计算的index相等的时候 则进行equals比较 添加进链表或者红黑树中

jdk1.7 使用hash表实现 基于冲突链表方式实现 当出现hash冲突 会在在对应的冲突链表中增加一个节点 在迭代的时候 会遍历hashtable 和冲突链表
初始容量(inital capacity) 和负载系数(load factor)影响hashMap性能
初始容量影响hashTable的大小 负载系数来指定自动扩容的临界值
当 节点数量 > 初始容量*负载因子 会触发hashMap自动扩容 这里是大于 举例 如果要插入N条数据 不触发自动扩容 需要 初始容量*负载因子+1 = N
jdk1.7HashMap结构图

jdk8及其之后主要是在冲突链表元素超过8的时候 会转换为红黑树 当元素从超过8减少少于6会转换为链表
参考 java.util.HashMap.TREEIFY_THRESHOLD java.util.HashMap.UNTREEIFY_THRESHOLD 变量 jdk8及其之后HashMap实现

  • 扩容
    jdk1.7: 建立一个新数组 把旧数组所有的元素 经过hash计算重新分配到新数组中 JDK1.7中rehash的时候,旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置 jdk1.8: 建立新数组 长度为原数组的2倍(2n) 那么原来的元素要么在原index要么是原来的index+n (只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成"原索引+oldCap")

  • 其他关联子类

    • HashMap
      基于数组作为hash bucket
    • LinkedHashMap
      记录key顺序的hashmap
    • TreeMap: key排序的map
    • ConcurrentHashMap: 多线程安全的hashMap实现
  • 线程安全的Hashmap有哪些

    • HashTable
      历史遗留集合 在需要考虑并发情况下的hashmap 直接使用concurrentHashMap
    • ConcurrentHashMap
      jdk1.7使用锁分段 Segment
      jdk1.8使用cas+synchronized

# stream集合常用操作符

基本上真正常用的 就map flatmap filter collect summary skip reduce findAny

# stream和parallelStream

parallelStream 是多线程并发处理 但是这个默认情况是使用jvm识别的核心数 性能并不高
而且限制颇多
如果遇到非要并发处理 建议还是使用线程池+自定义task 来做 可以更加精细的控制并发

# list

# linkedList 和 arrayList 区别

实现的不同 linkedList基于双向链表实现 方便插入和删除元素 顺序访问速度快 arrayList基于动态数组在插入的时候 需要移动元素

# arrayList 扩容机制

原理: 向数组添加元素会检测是否超出当前数组长度 超出了 会通过 ensureCapacity()去扩容
数组扩容讲老数组元素复制到新数组并且扩充容量为原来的1.5倍
arrayList扩容示例

# arrayList Fail-Fast机制

arrayList采用快速失败机制 通过记录modCount参数面对并发修改的时候 会快速失败

# 并发处理

# volatile 关键字

强制cpu读取主存 不从l1 l2 l3之类的cpu缓存中读写数据 保证volatile修饰的内容一致性

JVM内存屏障插入策略: 每个volatile写操作的前面插入一个StoreStore屏障
在每个volatile读操作的后面插入一个LoadStore屏障

# juc包详解

juc=java.util.concurrent
参考文档: https://segmentfault.com/a/1190000015558984

# 锁框架

java.util.concurrent.locks包下得类
主要就是各种锁 如 ReentrantLock

# 原子类框架

java.util.concurrent.atomic包下的类
提供一些线程安全类的基础数据类型 如AtomicInteger 通过无锁算法达到线程安全

# 同步器框架

java.util.concurrent包下有部分是同步器

名称 功能 备注
CountDownLatch 倒数计数器,构造时设定计数值 当计数值归0 所有阻塞线程恢复执行 内部实现aqs框架 设定一个数字n 每一个线程过来n-1 直到n=1 恢复所有阻塞线程
CyclicBarrier 循环栅栏,构造时候指定线程数,当所有线程到达栅栏后 栅栏放行,内部通过ReentrtantLock和Condition实现同步 设定一个阈值n 当有n个线程过来之后统一放行
Semaphore 信号量,类似令牌 控制共享资源访问的数量,内部实现aqs框架 标识当前资源有多少访问者
Exchanger 交换器,类似双向栅栏 用于线程之间配对和数据交换 内部根据并发情况分为 单槽交换和多槽交换 暂时不太理解 感觉像是个交流数据的一个地方 只是限制并发数量
Phaser 多阶段栅栏,CyclicBarrier升级版本 提供分阶段任务并发控制 支持树形结构 减少并发竞争 ??? 应该是提供类似循环栅栏的操作 只是中间优化了针对多阶段任务的处理

# 集合框架

java.util.concurrent下得集合类

常用的 就是 queue 分有界无界 和一些特殊队列
经常见得 ConcurrentHashMap CopyOnWriteArrayList

# 执行器框架

java.util.concurrent下得执行器框架

线程池、future、fork/join框架

# JVM

# GC算法有哪些

参考资料: https://blog.csdn.net/weixin_42045591/article/details/80629449

引用计数:
给对象添加一个引用计数器,每过一个引用计数器值就+1,少一个引用就-1。当它的引用变为0时,该对象就不能再被使用。它的实现简单,但是不能解决互相循环引用的问题。
根搜索:
以一系列叫"GC Roots"的对象为起点开始向下搜索,走过的路径称为引用链(Reference Chain),当一个对象没有和任何引用链相连时,证明此对象是不可用的,用图论的说法是不可达的。那么它就会被判定为是可回收的对象。
标记清除:
它是现代GC算法的思想基础,分为标记和清除两个阶段:先把所有活动的对象标记出来,然后把没有被标记的对象统一清除掉。但是它有两个问题,一是效率问题,两个过程的效率都不高。二是空间问题,清除之后会产生大量不连续的内存。
复制:
复制算法是将原有的内存空间分成两块,每次只使用其中的一块。在GC时,将正在使用的内存块中的存活对象复制到未使用的那一块中,然后清除正在使用的内存块中的所有对象,并交换两块内存的角色,完成一次垃圾回收。它比标记-清除算法要高效,但不适用于存活对象较多的内存,因为复制的时候会有较多的时间消耗。它的致命缺点是会有一半的内存浪费。
标记整理:
标记整理算法适用于存活对象较多的场合,它的标记阶段和标记-清除算法中的一样。整理阶段是将所有存活的对象压缩到内存的一端,之后清理边界外所有的空间。它的效率也不高。

# java常用的gc

cms:

参考资料:
https://www.iteye.com/blog/zhanjia-2435266 cms回收的步骤 https://www.jianshu.com/p/d6441e775dd9 cms常规优化方式

1.7常用
采用内存分代
划分新生代 老年代 永久代 每次执行回收会标记 计算对象是晋升还是清理 高版本jdk已经不再使用

g1:

参考资料:https://www.cnblogs.com/yufengzhang/p/10571081.html

1.8常用
划分区域 + 内存分代 把内存划分成相同大小的区域 每个区域单一职责 可以为 新生代 老年代或者永久代

zgc:

参考资料:https://www.cnblogs.com/coderw/p/14281528.html

11常用
通过多重映射 多个虚拟地址映射一个物理地址 来实现不同回收阶段高效的gc 通过染色指针 来记录gc信息 标记信息、分代信息 读屏障 来控制并发操作的时候 等待引用对象处理

zgc流程: 初始标记--> 并发标记 对象重定位 -->再标记 --> 并发转移准备-->初始转移-->并发转移
zgc stw 只存在初始标记,再标记,初始转移。

# gc在不同场景下选择

最后更新时间: 2026-05-06 17:25:23