1. JAVA
1.1 基础知识
1.1.1 JVM
jvm内存结构
堆:线程共享。所有的对象实例以及数组都要在堆上分配。GC主要管理的对象
新生代
- 分为Eden、from survivor、to survivor
- 所有新生成的对象首先都是放在新生代eden区,也是垃圾回收的主要操作区域;
- 2个surivivor区是对称的,总有一个是空的,用于保存从eden区经过回收后还存活的对象。
-Xms设置对的额最小空间大小,
-Xmx设置对的最大空间大小
-XX:NewSize设置新生代最小空间
-XX:MaxNewSize设置新生代最大空间
老年代
方法区:线程共享。存储类信息,常量,静态变量,即时编译后的代码。
-XX:PermSize设置最小空间
-XX:MaxPermSize设置最大空间
方法栈:线程私有。局部变量表、操作栈、动态链接、方法出口、对象指针
-Xss控制每个线程栈的大小
本地方法栈:线程私有。为虚拟机使用native方法服务
程序计数器:线程私有。当前线程所执行的字节码行号指示器。指向下一条要执行的指令。
OOM分析
根据OutOfMemoryError后面提示信息判断错误出现区域
- Java heap space: 对象不能被分配到堆内存中
- PermGen space: 类或者方法不能被加载到老年代。比如一个程序加载了很多类(引用了很多外部第三方库)
- Requested array size exceeds vm limit: 创建的数组大于堆内存的空间
- request bytes for . Out of swap space: 分配本地堆失败。JNI、本地库、虚拟机都会从本地堆中分配内存空间
- (Native method): 本地方法内存分配失败
GC算法,
判断对象是否存活方法:
- 引用记数法(无法解决循环依赖引用的问题)
- 可达性分析(主流分析法,与GC Roots直接或间接关联的对象是有效对象)
可作为GC Roots的对象:
- 虚拟机栈中引用的对象
- 方法区中类静态属性应用的对象
- 方法区中常量引用的对象
- 本地方法栈中native方法引用的对象
收集算法:
标记-清除算法
复制算法
标记整理算法
效率: 复制 > 标记-整理 > 标记-清除
内存整理率: 复制 = 标记-整理 > 标记-清除
内存利用率: 标记-整理 = 标记-清除 > 复制
分代收集算法
把java堆分成新生代和老年代,根据各个对象的年代采用最合适的收集算法 针对新生代,采用灵活的复制算法 针对老年代,使用标记清除/标记整理算法
CMS收集算法
CMS全程Concurrent Mark adn Sweep, 用于对老年代进行回收,目的是尽量减少应用的暂停时间,减少full gc的几率,通过并发的方式来标记清除老年代。
收集周期:
CMS-initial-mark 初始标记
CMS-concurrent-mark 并发标记
CMS-concurrent-preclean 执行预清理
CMS-concurrent-abortable-preclean 执行可中止预清理
CMS-remark 重新标记
CMS-concurrent-sweep 并发请求
CMS-concurrent-reset 并发重设状态等待下次CMS的触发
其中CMS-initial-mark和CMS-remark会stop-the-world
新生代如何进入老年代
- 大对象: 需要大量连续内存空间,如长字符串及数组,当对象大于-XX:PretenureSizeThreshold值,对象直接在老年代分配空间
- 长期存活的对象: 虚拟机给每个对象定义了一个对象年龄计数器,新生代在eden区创建的对象每经过一次Minor GC仍存活,并能被Survivor容纳的话,就会被移动到survivor空间,并且年龄增加1,当超过默认值15就会晋升到老年代
- 相同年龄的对象内存总和过半: 如果survivor空间中相同年龄所有对象大小的总和大于survivor空间的一半,年龄大于等于该对象的就直接进入老年代
频繁full GC的可能原因
- 代码主动调用System.gc()
- 老年代空间不足, 创建大对象的时候,老年代不足
- 方法区空间不足,当加载的类、反射的类、和调用的方法过多时
- 当进行Minor gc survivor放不下,然后转到老年代,老年代也放不下就会full GC
- CMS GC时,当前的浮动垃圾过多导致暂时性空间不足,会触发Full GC
- 新生代晋升到老年代,老年代空间不足的时候触发
线上OOM,日志很大,怎么定位
OOM常见原因为:
- 有可能时内存分太小,正常业务使用了大量内存
- 某一对象被频繁申请,却没有释放,内存不断泄露,导致内存耗尽
- 某一资源被频繁申请,系统资源耗尽,比如不断创建线程,不断发起网络连接
- 确认是不是内存本身就分配太小
jmap -heap [进程pid]
- 找到最耗内存的对象
jmap -histo:live [进程pid] | more
- 确认是否时资源耗尽
- netstat/pstree
- ll /proc/[pid]/fd 列出句柄
- ll /proc/[pid]/fd | wc -l 统计句柄
- ll /proc/[pid]/task 列出线程
- ll /proc/[pid]/task | wc -l 统计线程
1.1.2 基础包知识
java中有几种基本数据类型
| 类型 | 位 | 默认值 |
|---|---|---|
| boolean | 1 | false |
| byte | 8 bit | 0 |
| char | 16 bit | \u0000 |
| short | 16 bit | 0 |
| int | 32 bit | 0 |
| float | 1 | 0 |
| long | 64 bit | 0 |
| double | 64 bit | 0.0 |
char为何是2个字节
因为char能支持一个中文字符,中文占2个字节
Object有哪些方法
- getClass()
- hashCode()
- equals()
- toString()
- clone()
- wait()...
- notify()
- notifyAll()
- finalize()
final修饰变量、方法、类的作用
- 修饰变量,不可变,定义和初始化可以分开,一旦赋值就不可改变
- 修饰局部变量,必须初始化才可使用,修饰方法参数无需再初始化
- 修饰方法,表示不能被字类重写,可以被重载。
- 修饰类,不能被继承
ArrayList的父类有哪些
为什么覆盖equals方法必须覆盖hashcode方法
很简单因为,equals方法只是确定了需要的属性相等,但是hash不一定相同; 假设把对象作为key放到Map中,Map通过key的hashcode寻找,就会出现2个相同对象找不到的情况。
反射的应用场景和缺点
反射的目的就是为了扩展未知的应用,可用于动态类加载,运行期类型判断,动态代理 缺点性能差,无法在编译器发现问题。需要运行期再加载class文件。
自定义线程池的参数以及意义
ThreadPoolExecutor 参数
- corePoolSize: 核心线程数
- 核心线程会一直存活,即使没有任务需要执行
- 当线程数小于核心线程时,即使有线程空闲,线程池也会优先创建新线程处理
- 设置allowCoreThreadTimeout=true时,核心线程会超时关闭
- queueCapacity: 任务队列容量(阻塞队列)
- 当核心线程数达到最大时,新任务就会放到队列中排队等待执行
- maxPoolSize: 最大线程数
- 当线程数 > corePoolSize, 且任务队列已满时,线程池会创建新线程来处理任务
- 当线程数 = maxPoolSize, 且任务队列已满时,线程池会拒绝处理任务而抛出异常
- keepAliveTime: 线程空闲时间
- 当线程空闲时间达到时,线程会退出,直到线程数量=corePoolSize
- 如果allowCoreThreadTimeout=true,则会直到线程数量=0
- allCoreThreadTimeout: 允许核心线程超时
- rejectedExecutionHandler: 任务拒绝处理器
- 两种情况会拒绝处理任务:
- 当线程已经达到maxPoolSize, 且队列已满,会拒绝新任务
- 当线程池呗调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown,这之间会拒绝新任务
- 线程池会调用rejectedExecutionHandler处理这个任务。如果没有设置默认是AbordPolicy,抛出异常
- 两种情况会拒绝处理任务:
线程池的4中拒绝策略
- AbordPolicy 丢弃任务,抛出异常
- CallerRunsPolicy 执行任务
- DiscardPolicy 忽视,什么都不会发生
- DisabilityOldestPolicy 从队列中剔除最先进入队列的任务
同步与异步、阻塞与非阻塞
BIO、NIO、AIO区别
Netty
假如要处理100个连接,用BIO、NIO需要多少个线程
synchronized的原理、偏向锁、轻量级锁、重量级锁,sleep/wait区别,线程状态,线程之间通信方式
ReentrantLock原理和synchronized区别
AtomicInteger原理
volatile能不能保证线程安全
线程安全的单例模式
hashmap、hashtable、concurrenthashmap区别
HashMap实现原理
-
HashMap可以接受null键值和值,而HashTable不能,HashMap是非线程安全的;
-
HashMap底层数据结构是Node类型的数组,而Node对象是一个内部类,数据结构为链表;
-
HashMap默认初始大小为16;
-
HashMap默认负载因子是0.75,当已使用的容量大于当前大小 * 负载因子时,自动扩容一倍空间;
-
HashMap有个TREEIFY_THRESHOLD参数=8,当Node链表长度=8时,Node数据结构修改为红黑树,优化查询时间
-
HashMap插入值的逻辑:对key进行hash运算,然后再与当前map最后一个下标与运算确定其在数组中的位置(也就是取模运算);确定下标之后,如果当前位置为空,直接赋值;如果不为空,则放到下一个节点;如果当前为链表且添加元素后长度=8,则将链表转换为红黑树;如果节点值已存在,直接覆盖已有的value值;
-
HashMap获取值的逻辑:对key进行hash运算,然后计算出所在数组下标位置;循环链表或者红黑树,获取值
红黑树原理
- 红黑树有效条件:
- 节点是红色或黑色
- 根节点是黑色
- 每个叶子节点(NiL, NULL)是黑色
- 每个红色节点的两个子节点是黑色,也就是没有两个相连的红节点
- 从任何一个节点到其下面的每个叶子节点路径上都包含相同数目的黑色节点
- 新增元素
- 红黑树是基于二叉树的,所以新增元素必然在叶子上,按照排序添加到对应的叶子下
- 红黑树新增的元素必须是红色的,这是为了不破坏最后一个条件
- 树的第一个元素,直接成为根,需转为黑色;
- 加到黑点下面,没有破坏树结构;
- 加到红色点下面,破坏了特性,需要做一些处理; (父节点是红的,那么祖父节点肯定是黑色的,那么只要处理叔节点是红色或者黑色的情况了)
- 删除元素
- 删除元素可能破坏路径上黑点数目相同的特性
- 比如要删除的一个节点,将该节点左子树的最大值替换掉要删除的点(或者右子树最小值)[只覆盖值]
- 执行上面操作之后,待删除的点就到了左子树中的最大值(或右子树最小值),而且这个节点不可能同时存在2个有值的子节点,只可能[2个叶子都是null] [一个有值,一个null],那么根据红黑树特性[黑节点数目相同],如果存在儿子,那么儿子一定是红色,删除点肯定是黑色[不能有相连的红点]
- 那么就简单了,第二种情况,有叶子节点值存在的就容易处理,直接交换值,删除红点即可
- 针对第一种情况,分为两种情况,
- 删除点是红色,直接删除
- 删除点是黑色,那么直接删除就不平衡了 (1: 减少其他路径上的一个黑点,也就是变成红色,达到平衡; 2:借一个兄弟节点上的红点,然后设置为黑色)
1.2 Spring Cloud
1.2.1 分布式
事务的原理、事务的特性、事务的传播行为、事务的隔离级别
分布式事务,二阶段提交,三阶段提交,tcc保证事务一致性
CAP, BASE理论,最终一致性概念
一致性hash节点分布不均匀怎么办
1.2.2 集群
redis集群-哨兵
zookeeper集群
kafka集群
2. 数据库
2.1 关系型
2.1.1 mysql
mysql分库分表策略,一致性哈希的优缺点
mycat与jdbc-sharding的区别,优缺点
索引原理,索引失效原因,联合索引实际创建了几个索引
NySIAM和InnoDB区别
锁表、锁行
乐观锁、排他锁、间隙锁、悲观锁
3.中间件
3.1 消息中间件
MQTT协议
RabbitMQ与kafka的区别
1.RabbitMQ作为交易数据的传输管道,主要考虑到是否存在丢数据可能,具有较高的可靠性,数据丢失可能性更小,具备更高的实时性;
2.kafka主要体现在兔吞吐量上,kafka保证每条消息最少送达一次,有较小几率出现重复发送的情况 ;
RabbitMQ、kafka怎么保证数据不丢失
- 持久化交换机和队列,将交换机和队列的durable属性设置为true
- 在消息发送前把delivery mode选项设置为2来把消息标记成持久化。
- 当发布消息到持久化交换机时,RabbitMQ会在消息提交到日志文件后才发送响应给消费者
- 当交换机将消息路由到非持久化队列的话,他会自动从持久化日志中移除
- 当交换机将消息路由到 持久化队列的话,从队列中消费了一个持久化消息并且确认之后,RabbitMQ会在持久化日志中把这条消息标记为等待垃圾收集。
- 虽然持久化可以保证消息不丢失,但会严重影响性能,吞吐量降低10倍不止。