零碎知识点整理
进程间通信方式
- 管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
- 命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
- 消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
- 信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
- 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
虚拟内存与页面置换算法
- 在内存记录页号与偏移量,为每个线程实现从页号到物理块号的地址映射,减少寻址次数。
- 算法: FIFO(先进先出),LRU(最近最少被使用)
线程为什么比进程快
进程切换和线程切换的区别最主要的一个区别在于进程切换涉及虚拟地址空间的切换而线程不会。因为每个进程都有自己的虚拟地址空间,而线程是共享所在进程的虚拟地址空间的,因此同一个进程中的线程进行线程切换时不涉及虚拟地址空间的转换。有的同学可能还是不太明白,为什么虚拟地址空间切换会比较耗时呢?现在我们已经知道了进程都有自己的虚拟地址空间,把虚拟地址转换为物理地址需要查找页表,页表查找是一个很慢的过程,因此通常使用Cache来缓存常用的地址映射,这样可以加速页表查找,这个cache就是TLB(translation Lookaside Buffer,我们不需要关心这个名字只需要知道TLB本质上就是一个cache,是用来加速页表查找的)。由于每个进程都有自己的虚拟地址空间,那么显然每个进程都有自己的页表,那么当进程切换后页表也要进行切换,页表切换后TLB就失效了,cache失效导致命中率降低,那么虚拟地址转换为物理地址就会变慢,表现出来的就是程序运行会变慢,而线程切换则不会导致TLB失效,因为线程线程无需切换地址空间,因此我们通常说线程切换要比较进程切换块,原因就在这里。
KILL信号量
- KILL 操作系统释放SIGTERM信号,优雅退出,程序可选择立即退出,释放内存,响应请求。
- KILL -9 操作系统释放SIGKILL 立即退出
TCP与UDP的区别:
- 基于连接与无连接;
- 对系统资源的要求(TCP较多,UDP少);
- UDP程序结构较简单;
- 流模式与数据报模式 ;
- TCP保证数据正确性,UDP可能丢包;
- TCP保证数据顺序,UDP不保证。
TCP有序性保证:
- 数据包编号
- 确认和重发机制(发送方缓存)
查看某个端口是否被占用
netstat -aon|findstr “8080”
TCP 10.100.5.149:55120 59.37.96.203:8080 ESTABLISHED 19068
TCP 10.100.5.149:57259 183.3.224.146:8080 ESTABLISHED 12536
lsof -i:8080
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 22022 root 15u IPv6 1549072 0t0 TCP *:webcache (LISTEN)
zookeeper
是一个分布式应用程序协调服务
ZAB一致性 广播 position
-
崩溃恢复
-
原子广播
-
在Client向Follwer发出一个写的请求
-
Follwer把请求发送给Leader
-
Leader接收到以后开始发起投票并通知Follwer进行投票
-
Follwer把投票结果发送给Leader
-
Leader将结果汇总后如果需要写入,则开始写入同时把写入操作通知给Leader,然后commit;
-
Follwer把请求结果返回给Client
Follwer
- 向Leader发送请求(PING消息、REQUEST消息、ACK消息、REVALIDATE消息);
- 接收Leader消息并进行处理;
- 接收Client的请求,如果为写请求,发送给Leader进行投票;
- 返回Client结果。
RabbitMQ
AMQP(broker:exchange、queue、binding)
支持广播 点对点 正则
缓存
现象描述
- 缓存穿透是指缓存和数据库中都没有的数据
- 缓存击穿指缓存中没有但数据库中有的数据
- 缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机
Redis
- string
- list
- set
- hash
- sort set
数据结构实现
- ziplist压缩列表(数组)
- linkedlist编码 (多级链表,优化查询,复杂度logn)
- hashtable编码(String,String)
使用压缩列表的优点:
- 节约内存,减少内存开销,Redis是内存型数据库,所以一定情况下减少内存开销是非常有必要的。
- 减少内存碎片,压缩列表的内存块是连续的,并分配内存的次数一次即可。
- 压缩列表的新增、删除、查找操作的平均时间复杂度是O(N),在N再一定的范围内,这个时间几乎是可以忽略的,并且N的上限值是可以配置的。
- 数据对象都有两种编码结构,灵活性增加。
Redis OOM
Redis大key问题key超过1G导致数据倾斜
Codis根据key进行散列要保证流量均匀打在分布式机器上
- key值足够随机
- value大小均匀
Redis过期策略
- noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。
- allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。
- allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。
- volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。
- volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
- volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。
Redis持久化 RDB与AOF
-
RDB:
文件快照,重写快照,丢失两次快照之间的数据,数据不敏感场景 -
AOF:
copy-on-write对磁盘IO依赖较高
官方建议两个策略同时使用
数据库事务
- “读未提及”级别下,没有一致性视图
- “读已提交”级别下,会在 每个SQL开始执行的时候 创建一致性视图
- “可重复读”级别下,会在 每个事务开始的时候 创建一致性视图
- “串行化”级别下,直接通过加锁避免并发问题
间隙锁解决RR级别下非唯一索引
某个事务执行中另一个事务同步修改或者新增导致的问题
会导致其他事务阻塞
bin log 与 redo log
bin log 比 redo log后执行
redo log | binlog | |
---|---|---|
文件大小 | redo log 的大小是固定的。 | binlog 可通过配置参数 max_binlog_size 设置每个binlog 文件的大小。 |
实现方式 | redo log 是 InnoDB 引擎层实现的,并不是所有引擎都有。 | binlog 是 Server 层实现的,所有引擎都可以使用 binlog 日志 |
记录方式 | redo log 采用循环写的方式记录,当写到结尾时,会回到开头循环写日志。 | binlog通过追加的方式记录,当文件大小大于给定值后,后续的日志会记录到新的文件上 |
适用场景 | redo log 适用于崩溃恢复(crash-safe) | binlog 适用于主从复制和数据恢复 |
索引失效的情景
- 存储为NULL或者查询为NULL
- or条件索引没有完全覆盖
- like以“%”开头
- 组合索引没有使用第一列
- 语句索引字段类型隐式转换
- not、<>、或!=
- 当全表扫描速度比索引速度快时,mysql会使用全表扫描,此时索引失效
synchronized
- 偏向锁 对象头记录线程id
- 轻量级锁 对象头记录栈中锁记录的指针
- 重量级锁 monitorenter monitorexit 用户态->内核态->用户态
可重入
AbstractQueuedSynchronizer 抽象队列同步器
- 获取锁操作 维护一个State CAS修改State 记录当前线程
- 头尾节点双向队列 未获取到锁放入FIFO队列中
- 队列节点自旋判断前驱是否为头结点,如果为头结点尝试获取锁
子类
- ReentrantLock
- ReentrantReadWriteLock
- Semaphore
- CountDownLatch
- ThreadPoolExecotor
// 尝试获取 独占锁
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
// 尝试释放 独占锁
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
//尝试获取 共享锁
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
//尝试释放 共享锁
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
//判断是否时当前线程在持有锁
protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}
JAVA代理
- 静态代理,编码实现
- 动态代理,proxy反射reflect
- cglib,基于字节码修改class文件
java.lang.instrument.Instrumentation
可以修改已加载类的类库 依赖 JVMTI 的 Attach API 机制实现
JAVA ClassLoader同一classLoader不能加载同名class
redefineClasses 是自己提供字节码文件替换掉已存在的 class 文件
retransformClasses 是在已存在的字节码文件上修改后再替换
The redefinition must not add, remove or rename fields or
methods, change the signatures of methods, or change inheritance.
重新定义不能移除或重命名属性和方法,修改签名和方法,或者修改继承关系。
直接操作字节码
ASM框架,cglib、动态代理、Spring 等框架中对于字节码的操作就建立在ASM之上
- 提供接口可以让我们方便地操作字节码文件
- 进行注入修改类的方法
- 动态创造一个新的类
BTRACE
安全的动态加载追踪器
- 不允许创建对象
- 不允许创建数组
- 不允许抛异常
- 不允许 catch 异常
- 不允许随意调用其他对象或者类的方法,只允许调用 com.sun.btrace.BTraceUtils 中提供的静态方法(一些数据处理和信息输出工具)
- 不允许改变类的属性
- 不允许有成员变量和方法,只允许存在 static public void 方法
- 不允许有内部类、嵌套类
- 不允许有同步方法和同步块
- 不允许有循环
- 不允许随意继承其他类(当然,java.lang.Object 除外)
- 不允许实现接口
- 不允许使用 assert
- 不允许使用 Class 对象
Java 的 Instruments 给运行时的动态追踪留下了希望,Attach API 则给运行时
动态追踪提供了“出入口”,ASM 则大大方便了“人类”操作 Java 字节码的操作。
基于 Instruments 和 Attach API 前辈们创造出了诸如 JProfiler、Jvisualvm、
BTrace、Arthas 这样的工具。以 ASM 为基础发展出了 cglib、动态代理,继而是应
用广泛的 Spring AOP。
Java 是静态语言,运行时不允许改变数据结构。然而,Java 5 引入 Instru-
ments,Java 6 引入 Attach API 之后,事情开始变得不一样了。虽然存在诸多限
制,然而,在前辈们的努力下,仅仅是利用预留的近似于“只读”的这一点点狭小
的空间,仍然创造出了各种大放异彩的技术,极大地提高了软件开发人员定位问题
的效率。
常量池
常量池包括计数和常量池
字面常量 final修饰
符号引用
- 类和接口的全局限定名
- 字段的名称和描述符
- 方法的名称和描述符
类加载
- 加载(Loading)
- 链接(Linking)
- 初始化(Initialization)
其中链接(Linking)又可以分为: 验证(Verification)、准备(Preparation)、解析(Resolution)。
SpringBean创建过程
- createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
- populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
- initializeBean:调用spring xml中的init 方法。
JVM优化
- -Xms和-Xmx的值设置成相等,堆大小默认为-Xms指定的大小,默认空闲堆内存小于40%时,JVM会扩大堆到-Xmx指定的大小;空闲堆内存大于70%时,JVM会减小堆到-Xms指定的大小。如果在Full GC后满足不了内存需求会动态调整,这个阶段比较耗费资源。
- 新生代尽量设置大一些,让对象在新生代多存活一段时间,每次Minor GC 都要尽可能多的收集垃圾对象,防止或延迟对象进入老年代的机会,以减少应用程序发生Full GC的频率。
- 老年代如果使用CMS收集器,新生代可以不用太大,因为CMS的并行收集速度也很快,收集过程比较耗时的并发标记和并发清除阶段都可以与用户线程并发执行。方法区大小的设置,1.6之前的需要考虑系统运行时动态增加的常量、静态变量等,1.7只要差不多能装下启动时和后期动态加载的类信息就行。
代码实现方面,性能出现问题比如程序等待、内存泄漏除了JVM配置可能存在问题,代码实现上也有很大关系:
- 避免创建过大的对象及数组:过大的对象或数组在新生代没有足够空间容纳时会直接进入老年代,如果是短命的大对象,会提前出发Full GC。
- 避免同时加载大量数据,如一次从数据库中取出大量数据,或者一次从Excel中读取大量记录,可以分批读取,用完尽快清空引用。当集合中有对象的引用,这些对象使用完之后要尽快把集合中的引用清空,这些无用对象尽快回收避免进入老年代。
- 可以在合适的场景(如实现缓存)采用软引用、弱引用,比如用软引用来为ObjectA分配实例:SoftReference objectA=new SoftReference(); 在发生内存溢出前,会将objectA列入回收范围进行二次回收,如果这次回收还没有足够内存,才会抛出内存溢出的异常。
- 避免产生死循环,产生死循环后,循环体内可能重复产生大量实例,导致内存空间被迅速占满。尽量避免长时间等待外部资源(数据库、网络、设备资源等)的情况,缩小对象的生命周期,避免进入老年代,如果不能及时返回结果可以适当采用异步处理的方式等。
ConcurrnetHashMap
整体结构
1.7:Segment + HashEntry + Unsafe
1.8: 移除Segment,使锁的粒度更小,Synchronized + CAS + Node + Unsafe
put()
1.7:先定位Segment,再定位桶,put全程加锁,没有获取锁的线程提前找桶的位置,并最多自旋64次获取锁,超过则挂起。
1.8:由于移除了Segment,类似HashMap,可以直接定位到桶,拿到first节点后进行判断,1、为空则CAS插入;2、为-1则说明在扩容,则跟着一起扩容;3、else则加锁put(类似1.7)
get()
基本类似,由于value声明为volatile,保证了修改的可见性,因此不需要加锁。
resize()
1.7:跟HashMap步骤一样,只不过是搬到单线程中执行,避免了HashMap在1.7中扩容时死循环的问题,保证线程安全,Segment个数concurrencyLevel(16)不会变化。
1.8:支持并发扩容,HashMap扩容在1.8中由头插改为尾插(为了避免死循环问题),ConcurrentHashmap也是,迁移也是从尾部开始,扩容前在桶的头部放置一个hash值为-1的节点,这样别的线程访问时就能判断是否该桶已经被其他线程处理过了。
size()
1.7:计算两次,如果不变则返回计算结果,若不一致,则锁住所有的Segment求和。
1.8:CAS获取值,如果一直失败用数组countCells存储当前数量,最终用baseCount来统计总数。
Volite
- 可见性
线程模型的变量会从主存中刷新 - 有序性
禁止指令重排序
Integer的赋值操作
1、分配内存
2、初始化成员变量
3、指向分配的内存空间
2、3两个步骤顺序不一定 需要用volite修饰
Synchronized和ReenterLock
- Synchronized是非公平锁
- Synchronized不支持中断操作,monitorenter,monitorexit
- Synchronized、ReenterLock都是可重入锁
- Synchronized是JAVA关键字由JVM实现
- Synchronized升级策略: 偏向锁(对象头记录线程id CAS)->轻量级锁(指向线程 CAS)->重量级锁(monitor)
- ReenterLock继承AQS,维护state信号量和头尾节点双向队列,只需要重写获取锁和释放锁操作,有公平和非公平两种实现方式
- 可重入实现方式:信号量,记录访问线程
解决循环依赖的三级缓存
- SingletonFactories : 单例对象工厂的 cache
- EarlySingletonObjects :提前暴光的单例对象的
- CachesingletonObjects:单例对象的cache
不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”
因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决