CopyOnWriteArrayListjava
了解写时复制机制、了解其适用场景、思考为何没有ConcurrentArrayListredis
内部持有一个ReentrantLock的可冲入锁,在增长、删除是加锁,使用try finally来在finally的最后语句块当中解锁,在增长、删除时候复制出来一个新的数组,来完成增长、删除的操做,提升查询的效率,查询的时候查询原来的数组,操做完成后将新数组的引用,赋值给原来的引用。底层数组使用volatile transient修饰的数组,采用volatile transient修饰的缘由,用transient修饰的数据不会被序列化,对于volatile,从java内存模型的角度,每一个线程都有本身的工做内存,处理数据时候获取中内存数据的拷贝,volatile修饰以后,就禁止了这种拷贝,直接获取主内存,保证的指令可见性,而不是数据的可见性,还会禁止jvm底层对数据的重排序,使用场景,好比说CopyOnArrayList的成员变量,这种状况有多个线程并发同时访问的变量,并且既不是常量,也没有在synchronzed()的同步代码块中。CopyOnArrayList的使用场景:适用于读多写少的并发场景。为何没有ConcurrentArrayList这样的数据结构?由于保证线程安全会有各类各样的手段,可是在保证线程安全的前提下,还要保证性能瓶颈,这就很难了,对于List这样的数据结构是作不到的,好比说在contains()这样的方法中,遍历的时候必定要锁住整个list的,采用分段锁也不合适,总不能数组有多少长度就用多少个分段锁吧,concurrentHahsMap的分段锁一旦肯定后,即时数组扩容,也不会变了。即时线程安全的CopyOnArrayList也只规避了读操做的并发瓶颈。算法
ConcurrentHashMapspring
了解实现原理、扩容时作的优化、与HashTable对比。数据库
concurrentHashMap是基于分段锁来实现的,默认是16,有一个参数叫作concurrentLevel,该值说明有多少个分段锁,segment继承自ReentrantLock,1.8采用cas来实现避免加锁提升性能,只有内存值与当前值一致,才会修改为功。Cas是乐观锁,synchronized等等都是悲观锁。Cas有一个问题,会产生ABA的问题,好比线程A B都从内存当中取了值,这是线程2修改了值,作了一些操做有把值修改回去了,这时候线程1cas仍是成功的,可是线程2的操做就被忽略了,因此能够考虑加版本号,修改时除了比较当前值还要校验版本号。HashTable是直接在方法上的synchronized来保证安全的,并发是会有性能瓶颈的。扩容时候的优化能够考虑从避免rehash来回答。新建数组为原来长度的一倍,把原来的数据遍历放进来。数组
常见问题缓存
使用细粒度的锁分段锁来实现线程安全、以及CAS来保证线程安全,不会有性能并发瓶颈。安全
数组链表加红黑树,以前是头插,如今是尾插,链表的最后一个数据的next为null,并发扩容不会next不会产生死循环。优化角度避免重复hash。服务器
前者基于链表、后者基于优先级队列。数据结构
新增、修改、删除,复制一个数组来完成操做,完成后指向原来的引用,读操做不会影响性能瓶颈。ReentrantLock可冲入锁。
ThreadLocal
了解ThreadLocal使用场景和内部实现
ThreadLocal的内部实现:ThreadLocal底层是有静态内部类ThreadLocalMap来实现的,ThreadLocal有一个静态内部类叫作ThreadLocalMap,ThreadLocalMap有一个静态内部类叫作Entry,entry是弱引用机制的,entry有一个属性是value,存储数据,key是当前threadLocal对象,Thread持有ThreadLocalMap引用。每一个线程保存变量的副本。
使用场景:某次请求,同一个线程下数据的传递,不然只能靠方法的返回值,解耦操做。在某些框架上,若是不用threadLocal,会有耦合的问题。还能够透传全局上下文,经过设置Thread构造函数的最后一个参数initThreadLocal,设置为true。
缺点:线程池服用Thread对象,会有脏数据的问题,static 修饰会有内存溢出的问题,原本entry实现弱引用,想要经过GC来回收,value,static修饰方法区当中会有引用,致使没法回收。解决办法:remove()。
解决线上日志上那台服务器上去找?
ThreadLocal存储当前请求的traceID,根据id去找对应的日志文件。Value存IP。
ThreadPoolExecutor
了解线程池的工做原理以及几个重要参数的设置
1:核心线程数:若是为0,线程结束后全部线程都会被销毁。若是不为0,假设是n,会保留n个线程做为核心线程。
2:最大线程数:线程池中最多有当前数量的并发线程。
3:阻塞队列:LinkedBlockQueue,存储待执行的任务,使用锁来保证入队出队的原子性。
4:超时时间:多长时间回收空闲线程。核心线程默认是不会被回收的,有一个参数叫作容许核心线程超时设置为true,就能够回收了。
5:时间单位:时间单位
6:线程工厂:
7:拒绝策略:
l 丢弃任务,抛出异常(默认)
l 丢弃任务
l 丢弃最近最少使用的任务最久的
l 调用任务的run方法绕过线程池执行
线程池有几种类型:
l new FixedThreadPool()建立固定大小的线程池
l new singleThreadExecutor()建立单个线程池
l new ScheduledThreadPool()建立可调度的线程池
l new CheThreadPool()可伸缩的线程池
l new WorkStealingPool() jdk8新出的
原理:主要是execute()、addWork()这两个方法:
Execute执行任务,addWork()是用来建立线程,内部由32位的二进制数来表示,高三位表示线程池的状态,一共有五种状态,runnable:能够接受任务,shutdown:不能接受任务,能够执行正在处理任务stop:不能接受任务,中止正在执行的任务。Tyding:全部任务都已被终止Terminated:已经清理完现场。首先会判断线程数是否小于核心线程数,若是是,建立线程。不然判断线程池是不是runnable状态,若是是置于队列,不是从队列移除。
Execute方法的执行流程,先判断工做线程数是否大于核心线程数,若是没有,那么就建立线程,不然判断线程是不是可运行状态,放到阻塞队列中。若是不是从阻塞队列中删除。在不知足可运行状态、或者缓存满了,达到了最大线程数,执行拒绝策略。
达到空闲时间以后会被回收
任务执行完成以后,会设置空闲时间,到时间以后回收。
引用
了解Java中的软引用、弱引用、虚引用的适用场景以及释放机制
强引用:不会被在可以被根节点可达的状况下,不会被回收。Object object = new Object();即时系统立刻OOM了,也不会被回收。
软引用:有这样一个类,softRefferrence来定义,在系统OOM以前会被回收。适用于缓存,或者服务器计算的中间结果。
弱引用:YGC年轻代GC时会被回收,有WeakReferrence来调用,在引用的对象置为null时,能够主动断开链接,这是弱引用的使命。
虚引用:每一种引用都有一个对应的类来描述,定义以后就没法指向获取引用的对象。设置虚引用的目的是为了回收是获取一个系统通知。
常见问题
OOM
YGC,当引用的对象设置为null,会自动断开引用
类加载
了解双亲委派机制
加载一个类时候,判断父类有没有加载,若是父类加载过了,就无需加载。若是没有加载,而且加载不了,就委托子类加载。
常见问题
避免重复加载类,在内存中有不少重复类的字节码,因此有父类加载优先加载,若是已经加载过了,子类就不能再加载了。防止内存中字节码爆炸。自定义的类加载器加载的特定位置的类,自定义类加载器加载特殊位置的类。
继承classloader,重写findclass方法,找到自定义目录下的文件,生成二进制流,调用defineClass()生成Class。何时须要自定义加载器?好比扩展加载源,好比数据库的驱动,系统自带的类加载确定没法加载。
jvm
GC
垃圾回收基本原理、几种常见的垃圾回收器的特性、重点了解CMS(或G1)以及一些重要的参数
内存区域
能说清jvm的内存划分
Jvm的体系划分:
本地方法栈:
封装的须要调用的本地方法,好比System.currentMillions()
虚拟栈:
方法的执行就是栈帧入栈出栈的过程,正在执行的方法就是栈顶的栈帧。
操做变量表:相似于槽,存储标识地几个变量的值。存储方法的局部变量,参数,没有准备阶段,因此必需要立刻初始化。
操做数栈:压栈入栈去作计算
动态连接:栈帧当中包含对常量池的引用。
方法出口:方法的返回值,包括正常返回,异常返回
堆:年轻代老年代。Eden survior1 surviior2 8:1:1。新建立的对象分配在Eden中,若是满了会触发YGC,把活着的对象放到未使用的输入survior中,每一个对象都有一个计数器,每经历一次YGC,就加1,默认达到15次,就放到老年代。MaxTuringThreshold默认15次。若是一个对象elden区放不下,就会YGC,还放不下,就会放到老年代,还放不下,就会FULL GC,还放不下,就会报错。OOM。String字符串常量是在堆中。
程序计数器:CPU时间片切换到执行的不一样线程,等在回到记录上一个线程执行的位置。
元数据区:方法信息、静态属性,常量,类信息、常量池
常见问题
Cms利用三种颜色来完成标记。黑:可以被GCroot直接找到的没有引用白色节点的。白色:须要回收的对象。灰色:可以被GCroot可达,可是没有扫描完成的。
Cms有四个阶段:
a) 初始标记:从GCroot出发,找到全部直接引用的节点。Stw
b) 并发标记:以上一阶段的节点为根节点,并发遍历标记。Stw
c) 从新标记:并发标记GCroot可能有引用关系的变化,因此须要从新标记。从根节点,GCroot遍历,从新标记。
d) 并发清理:并发清理全部对象。标记清理:会有空间碎片的问题。
回收的原则:浮动垃圾,以前有引用,如今没有引用了,发现不了,回收原则:可达的对象绝对不能回收,能够有垃圾没有回收。
1:老年代使用率达到某个阈值,开启fullGC。
2:执行了多少次的不压缩的full gc 来一次压缩的full gc。
ConcurrentModelFilure:有大量对象从elden升级到老年代,老年代放不下,报的错误。解决办法:开启串行老年代收集器,回收整个老年代。
ParNew promotion failed:晋升担保失败:空间碎片的问题,来一次标记整理算法回收。
缺点:有空间碎片化的问题,这是须要内存整理
优势:减小stw的次数。配置执行了多少次的full gc,采起作内存整理。
好比说:设置jvm的启动参数:XMS XMX最小堆内存与最大堆内存,设置成同样的大小,不然服务器会不断地收缩与扩容,增长系统的压力。调节elden区域surior区的交换次数参数,maxTurningThreshold,将年轻代的转为老年代。或者配置参数UseG1Gc启动新的G1垃圾回收器。
对象的声明周期不一样。
采用复制算法回收,须要额外得存储区域作担保。
须要频繁的作对象的建立于回收,对象声明周期较短。
老年代声明周期比较长,若是采用复制算法须要其余内存做担保,保留存活的对象。年轻代老年代分配比例是3:8。
对外内存:java.nio.DirectorByteBuffer分配的内存是堆外内存,优势:提升io效率。缺点:堆外内存回收相对堆外内存来讲耗时间。底层:unsafe.allocateMemory()来分配。每个DirctorByteBuffer初始化都会有一个Cleaner对象调用cleaner()来回收。
spring
bean的生命周期、循环依赖问题、spring cloud(如项目中有用过)、AOP的实现、spring事务传播
循环依赖问题:
好比说spring当中的自动装配,A依赖了B,B依赖了A,这个时候在实例化A类的这个对象时候,须要依赖B,A没法实例化,在实例化B的对象时候,依赖了A对象,B对象也没法完成实例化,须要在AutowiredBeanPostProcessor后置处理器中完成属性的注入。
Spring是这样处理的,他设计了一级缓存,已经实例化单例对象的缓存、二级缓存,正在建立中的对象的缓存、三级缓存,单例对象工厂的缓存,这样的map,在refresh()方法当中,倒数第二个方法实例化bean,首先实例化时候从一级缓存中获取,以及缓存存储已经实例化完成的单例对象,若是没有,从二级缓存中获取,二级缓存存储的是正在建立中的对象,二级缓存若是没有,有一个暴露对象的参数,true,能够从三级缓存中获取,这个时候是有的。就能够经过AutowiredBeanPostProcessor自动装配的后置处理器干扰bean的实例化过程。
当前没有事务,建立事务,有事务,就加入到这个事务中来等等。
或者就直接新建事务。
Spring当中完整的bean的声明周期,从构造函数初始化开始,实例化一个AnnotationConfigApplicationContext开始,接下来就能够从容器中获取bean,说明,在构造函数当中就已经完成了spring当中的bean的初始化,以及准备spring的环境。首先会调用this()函数,初始化defaultListableBeanFactory,在实例化一个reader以及scanner,实例化reader会添加5个beanpostProcessor以及一个beanFactoryPostProcessor。接下来会注册beanDefinition到beanFactory当中。Refresh()方法完成spring容器的准备,bean的实例化。初始化一个applicationcontextAware后置处理器,调用后置处理器,以及实例化bean。
构造器注入会有循环依赖的问题。属性注入就是setter注入,由于构造函数立刻就会建立对象,没法完成依赖注入。属性注入,能够利用到缓存。一级缓存、二级缓存、三级缓存。
l Spring如何实例化对象的?
经过反射,首先经过工厂方法来实例化对象,在经过构造函数,依据参数的类型去实例化,最后经过默认的构造函数,spring底层作了一个这样的判断。
redis
redis工做模型、redis持久化、redis过时淘汰机制、redis分布式集群的常见形式、分布式锁、缓存击穿、缓存雪崩、缓存一致性问题
@PostConstruct:缓存预热,在构造函数初始化以后执行
@PreDestroy:在bean销毁以后执行
常见问题
基于内存数据库,数据结构简单,没有磁盘io,主要是io模型牛逼。
Redis的config配置文件中配置最大内存,超过以后,会有6种拒绝策略。
1:noeviction: 不删除策略, 达到最大内存限制时, 若是须要更多内存, 直接返回错误信息。 大多数写命令都会致使占用更多的内存(有极少数会例外, 如 DEL )。
2:allkeys-lru: 全部key通用; 优先删除最近最少使用(less recently used ,LRU) 的 key。
3:volatile-lru: 只限于设置了 expire 的部分; 优先删除最近最少使用(less recently used ,LRU) 的 key。
4:allkeys-random: 全部key通用; 随机删除一部分 key。
5:volatile-random: 只限于设置了 expire 的部分; 随机删除一部分 key。
6:volatile-ttl: 只限于设置了 expire 的部分; 优先删除剩余时间(time to live,TTL) 短的key。
RDB:将持久化的数据写到文件中。
AOF:记录执行的指令。
List 、set、 string、 hash、 sortedSet
哨兵模式:sentinel,监控master slave,若是master挂了,把slave升级为slave。
查询若是缓存没有就查库,更新数据库,是缓存失效。
加锁:
解锁: