春节学习的知识点汇总
java内存区域
- 线程私有:程序计数器,java虚拟机栈,本机方法栈
- 共享内存:java堆,方法区(jdk8的metaSpace)
垃圾收集器
- 垃圾搜集算法
- 标记-清除算法
- 标记-复制算法
- 标记-整理算法
- 经典垃圾收集器
- Serial
- ParNew
- Parallel Scavenge
- Serial Old
- Parallel Old
- CMS
- Garbage First(G1)
- 低延迟垃圾收集器
- Shenandoah
- ZGC
java类文件结构
- 类加载过程
加载->验证->准备->解析->初始化->使用->销毁
加载:加载字节码文件到方法区(部分和文件格式验证一起交叉进行)
验证:文件格式、元数据、字节码(用javac编译是的类型检查替代验证时的类型推导)、符号引用
准备,赋零值,static final修饰的变量赋值为constantValue
解析:符号引用替换为直接应用,类和接口解析、字段解析、方法解析、接口方法解析
初始化:执行<clinit>()方法,该方法是有编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的
- 类加载器
- 三层类加载器:启动类加载器、扩展加载器、应用加载器
- 双亲委派机制,先由父类加载
- 破坏双亲委派模型
- loadClass
- 线程上下文类加载器(Thread Context ClassLoader)
- “代码热替换”,比如OSGi
- 了解了下java9开始的模块化系统
了解了一些JVM字节码技术
待续
java内存模型与线程
- 主内存和工作内存的模式
主内存所有线程可见,工作内存线程私有
- 内存间的交互操作
lock、unlock、read、load、use、assign、store、write
- volatile
- 内存可见性,每次修改都同步到主内存,每次读取都从主内存读取
- 禁止指令重排序
- 原子性
通过字节码指令read、load、use、assign、store、write原子操作保证原子性,所以基本数据类型的访问、读写都具备原子性(例外的就是long和double的非原子协定),还有通过字节码指令monitorenter和monitorexit,即synchronized包含的代码块保证原子性
- 可见性
通过volatile修饰的变量,还有synchronized和final修饰的变量
- 有序性
volatile禁止指令重排序,synchronized保证访问代码块的线程串行进入
-
先行发生原则,Happens-Before
-
java线程是内核线程
-
协程的复苏,loom项目
十大排序算法
-
选择排序
-
插入排序
-
冒泡排序
-
希尔排序
-
归并排序
-
快速排序
-
堆排序
-
计数排序
-
桶排序
-
基数排序
HashMap的原理
- HashMap实现了Map接口,是线程不安全的
- 基于数组+链表+红黑树(jdk8开始)实现
- key的hashcode获取方式在jdk8中有变更,具体实现代码如下:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
先获取key的hashcode,再将hashcode高16位与低16位异或,变相保留高位的比特位,将高16位也参与hash,尽量均匀分布,降低碰撞概率
- key得到hash后,如何判断分布在哪个数组下标中
通过计算index = (n - 1) & hash,得到数组下标
- putVal
赋值操作
1、node数组tab为空则扩容初始化
2、hash值不存在则newNode赋值到数组对应的下标上
3、如果key一样,则替换旧值
4、如果是treeNode,则使用putTreeVal插入
5、使用尾插法,插入到链表中
6、如果此时链表长度大于等8-1,则转化为红黑树
7、如果key已存在,则覆盖旧值,并返回旧值
8、如果++size大于threshold,则进行扩容操作
- resize
扩容操作
通过左移2为>>1,是容量扩容为2被,并设置新的扩容阈值threshold
threshold=newCap*loadFactor
将数组的值赋值到新数组oldTab->newTab
ConcurrenHashMap的原理
- 1.8使用CAS+Synchronized取代Segment+ReentrantLock
- 为什么jdk8中ConcurrenHashMap并发安全实现,用CAS+Synchronized取代Segment+ReentrantLock?
先比较下jdk7和jdk8中concunrrentHashMap的结构
- jdk7:首先在初始化ConcurrentHashMap的时候,会初始化一个Segment数组,容量为16,而每个Segment呢,都继承了ReentrantLock类,也就是说每个Segment类本身就是一个锁,之后Segment内部又有一个table数组,而每个table数组里的索引数据呢,又对应着一个Node链表。利用AQS的cas操作实现锁
- jdk8:锁住单个node,通过自旋锁、偏向锁、轻量级锁提升性能
jdk7中是对单个Segment上锁,Segment默认有16个,则并发度理论上支持16,
在jdk8中是对单个node上锁,锁细化了,ReentrantLock相比Synchronized会多出线程上下文切换代价,即工作态到内存态再到工作态,Synchronized因为自旋锁,偏向锁,轻量级锁的原因,不用将等待线程挂起,偏向锁甚至不用自旋,所以在这种情况下要比ReentrantLock高效
AQS
- AQS是java.util.concurrent.locks下提供的除了Synchronized外的锁机制
AQS就是基于CLH队列,用volatile修饰共享变量state,线程通过CAS去改变状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒。
- 注意:AQS是自旋锁,在等待唤醒的时候,经常会使用自旋(while(!cas()))的方式,不停地尝试获取锁,直到被其他线程获取成功
- 实现了AQS的锁有:自旋锁、互斥锁、读锁写锁、条件产量、信号量、栅栏都是AQS的衍生物
如图示,AQS维护了一个volatile int state和一个FIFO线程等待队列,多线程争用资源被阻塞的时候就会进入这个队列。state就是共享资源,其访问方式有如下三种:getState();setState();compareAndSetState();
ArrayList
- ArrayList是基于数组实现的列表,实际上是一个动态数组,可以通过数组下面操作元素,相比LinkedList,更适合查多改少的场景。
- ArrayList空参数构造函数默认数组容量为10,但是不是在初始化数组时设置的默认容量,而是在add操作时设置。
- 在ArrayList中增加或者是删除元素,要调用System.arraycopy这种效率很低的操作,如果遇到了需要频繁插入或者是删除的时候,你可以选择其他的Java集合,比如LinkedList。
java中的fast-fail机制
- 在Java集合类中很多地方都用到了该机制进行设计,一旦使用不当,触发fail-fast机制设计的代码,就会发生非预期情况。我们通常说的Java中的fail-fast机制,默认指的是Java集合的一种错误检测机制。当多个线程对部分集合进行结构上的改变的操作时,有可能会触发该机制时,之后就会抛出并发修改异常ConcurrentModificationException.当然如果不在多线程环境下,如果在foreach遍历的时候使用add/remove方法,也可能会抛出该异常。参考fast-fail机制,这里简单做个总结
- 之所以会抛出ConcurrentModificationException异常,是因为我们的代码中使用了增强for循环,而在增强for循环中,集合遍历是通过iterator进行的,但是元素的add/remove却是直接使用的集合类自己的方法。这就导致iterator在遍历的时候,会发现有一个元素在自己不知不觉的情况下就被删除/添加了,就会抛出一个异常,用来提示可能发生了并发修改!所以,在使用Java的集合类的时候,如果发生ConcurrentModificationException,优先考虑fail-fast有关的情况,实际上这可能并没有真的发生并发,只是Iterator使用了fail-fast的保护机制,只要他发现有某一次修改是未经过自己进行的,那么就会抛出异常。
zookeeper
- zookeeper满足CAP理论中的CP,即一致性和分区容错性
由于zk保证的是cp,在A(Available)可用性上会有以下问题:
不能保证每次服务请求的可用性
进行leader选举时集群都是不可用
- zookeeper的基本概念
1、Architecture(架构)
2、Hierarchical namespace(层次命名空间)
3、Session(会话)
4、Watches(监视)
-
zookeeper的应用场景
-
分布式协调
对业务进行解耦,比如可以A创建节点,B异步修改节点,A去监听节点的变化得到B操作的结果
- 分布式锁
通过创建同一个znode返回成功与否判定是否拥有锁
- 元数据/配置信息管理
作为注册中心,对服务进行注册发现和负载均衡,比如dubbo就可以使用zk作为注册中心,kafka也使用zk作为元数据、配置信息的管理
- 分布式唯一ID
zookeeper主要通过其znode数据版本来生成序列号,可以生成32位和64位的数据版本号,客户端可以使用这个版本号来作为唯一的序列号。
- HA高可用性
通过创建临时节点和注册监听器,实现主备切换
kafka
待续