Interview

CopyOnWriteArrayList

 

了解写时复制机制、了解其适用场景、思考为什么没有ConcurrentArrayList

      内部持有一个ReentrantLock的可冲入锁,在增加、删除是加锁,使用try finally来在finally的最后语句块当中解锁,在增加、删除时候复制出来一个新的数组,来完成增加、删除的操作,提高查询的效率,查询的时候查询原来的数组,操作完成后将新数组的引用,赋值给原来的引用。底层数组使用volatile transient修饰的数组,采用volatile transient修饰的原因,用transient修饰的数据不会被序列化,对于volatile,从java内存模型的角度,每个线程都有自己的工作内存,处理数据时候获取中内存数据的拷贝,volatile修饰之后,就禁止了这种拷贝,直接获取主内存,保证的指令可见性,而不是数据的可见性,还会禁止jvm底层对数据的重排序,使用场景,比如说CopyOnArrayList的成员变量,这种情况有多个线程并发同时访问的变量,而且既不是常量,也没有在synchronzed()的同步代码块中。CopyOnArrayList的使用场景:适用于读多写少的并发场景。为什么没有ConcurrentArrayList这样的数据结构?因为保证线程安全会有各种各样的手段,但是在保证线程安全的前提下,还要保证性能瓶颈,这就很难了,对于List这样的数据结构是做不到的,比如说在contains()这样的方法中,遍历的时候一定要锁住整个list的,采用分段锁也不合适,总不能数组有多少长度就用多少个分段锁吧,concurrentHahsMap的分段锁一旦确定后,即时数组扩容,也不会变了。即时线程安全的CopyOnArrayList也只规避了读操作的并发瓶颈。

ConcurrentHashMap

了解实现原理、扩容时做的优化、与HashTable对比。

  concurrentHashMap是基于分段锁来实现的,默认是16,有一个参数叫做concurrentLevel,该值说明有多少个分段锁,segment继承自ReentrantLock,1.8采用cas来实现避免加锁提高性能,只有内存值与当前值一致,才会修改成功。Cas是乐观锁,synchronized等等都是悲观锁。Cas有一个问题,会产生ABA的问题,比如线程A B都从内存当中取了值,这是线程2修改了值,做了一些操作有把值修改回去了,这时候线程1cas还是成功的,但是线程2的操作就被忽略了,所以可以考虑加版本号,修改时除了比较当前值还要校验版本号。HashTable是直接在方法上的synchronized来保证安全的,并发是会有性能瓶颈的。扩容时候的优化可以考虑从避免rehash来回答。新建数组为原来长度的一倍,把原来的数据遍历放进来。

常见问题

  • ConcurrentHashMap是如何在保证并发安全的同时提高性能?

使用细粒度的锁分段锁来实现线程安全、以及CAS来保证线程安全,不会有性能并发瓶颈。

  • ConcurrentHashMap是如何让多线程同时参与扩容?

数组链表加红黑树,之前是头插,现在是尾插,链表的最后一个数据的next为null,并发扩容不会next不会产生死循环。优化角度避免重复hash。

  • LinkedBlockingQueue、DelayQueue是如何实现的?

前者基于链表、后者基于优先级队列。

  • CopyOnWriteArrayList是如何保证线程安全的?

新增、修改、删除,复制一个数组来完成操作,完成后指向原来的引用,读操作不会影响性能瓶颈。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方法绕过线程池执行

 

线程池有几种类型:

new FixedThreadPool()创建固定大小的线程池

new singleThreadExecutor()创建单个线程池

new ScheduledThreadPool()创建可调度的线程池

new CheThreadPool()可伸缩的线程池

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,会自动断开引用

类加载

了解双亲委派机制

加载一个类时候,判断父类有没有加载,如果父类加载过了,就无需加载。如果没有加载,并且加载不了,就委托子类加载。

常见问题

  • 双亲委派机制的作用?

避免重复加载类,在内存中有很多重复类的字节码,所以有父类加载优先加载,如果已经加载过了,子类就不能再加载了。防止内存中字节码爆炸。自定义的类加载器加载的特定位置的类,自定义类加载器加载特殊位置的类。

  • Tomcat的classloader结构
  • 如何自己实现一个classloader打破双亲委派

  继承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 GC回收分为哪几个阶段?分别做了什么事情?

 

Cms利用三种颜色来完成标记。黑:能够被GCroot直接找到的没有引用白色节点的。白色:需要回收的对象。灰色:能够被GCroot可达,但是没有扫描完成的。

 

Cms有四个阶段:

 

a)          初始标记:从GCroot出发,找到所有直接引用的节点。Stw

 

b)          并发标记:以上一阶段的节点为根节点,并发遍历标记。Stw

 

c)           重新标记:并发标记GCroot可能有引用关系的变化,所以需要重新标记。从根节点,GCroot遍历,重新标记。

 

d)          并发清理:并发清理所有对象。标记清理:会有空间碎片的问题。

 

回收的原则:浮动垃圾,之前有引用,现在没有引用了,发现不了,回收原则:可达的对象绝对不能回收,可以有垃圾没有回收。

 

  • CMS有哪些重要参数?

 

1:老年代使用率达到某个阈值,开启fullGC。

 

2:执行了多少次的不压缩的full gc 来一次压缩的full gc。

 

  • Concurrent Model Failure和ParNew promotion failed什么情况下会发生?

 

  ConcurrentModelFilure:有大量对象从elden升级到老年代,老年代放不下,报的错误。解决办法:开启串行老年代收集器,回收整个老年代。

 

  ParNew promotion failed:晋升担保失败:空间碎片的问题,来一次标记整理算法回收。

 

  • CMS的优缺点?

 

缺点:有空间碎片化的问题,这是需要内存整理

 

优点:减少stw的次数。配置执行了多少次的full gc,采取做内存整理。

 

 

 

  • 有做过哪些GC调优?

 

比如说:设置jvm的启动参数:XMS XMX最小堆内存与最大堆内存,设置成一样的大小,否则服务器会不断地收缩与扩容,增加系统的压力。调节elden区域surior区的交换次数参数,maxTurningThreshold,将年轻代的转为老年代。或者配置参数UseG1Gc启动新的G1垃圾回收器。

 

  • 为什么要划分成年轻代和老年代?

 

对象的声明周期不同。

 

  • 年轻代为什么被划分成eden、survivor区域?

 

采用复制算法回收,需要额外得存储区域做担保。

 

  • 年轻代为什么采用的是复制算法?

 

需要频繁的做对象的创建于回收,对象声明周期较短。

 

  • 老年代为什么采用的是标记清除、标记整理算法

 

老年代声明周期比较长,如果采用复制算法需要其他内存作担保,保留存活的对象。年轻代老年代分配比例是3:8。

 

  • 什么情况下使用堆外内存?要注意些什么?

 

对外内存:java.nio.DirectorByteBuffer分配的内存是堆外内存,优点:提高io效率。缺点:堆外内存回收相对堆外内存来说耗时间。底层:unsafe.allocateMemory()来分配。每一个DirctorByteBuffer初始化都会有一个Cleaner对象调用cleaner()来回收。

 

  • 堆外内存如何被回收?
  • jvm内存区域划分是怎样的?

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事务传播

当前没有事务,创建事务,有事务,就加入到这个事务中来等等。

或者就直接新建事务。

  • spring中bean的生命周期是怎样的?

Spring当中完整的bean的声明周期,从构造函数初始化开始,实例化一个AnnotationConfigApplicationContext开始,接下来就可以从容器中获取bean,说明,在构造函数当中就已经完成了spring当中的bean的初始化,以及准备spring的环境。首先会调用this()函数,初始化defaultListableBeanFactory,在实例化一个reader以及scanner,实例化reader会添加5个beanpostProcessor以及一个beanFactoryPostProcessor。接下来会注册beanDefinition到beanFactory当中。Refresh()方法完成spring容器的准备,bean的实例化。初始化一个applicationcontextAware后置处理器,调用后置处理器,以及实例化bean。

  • 属性注入和构造器注入哪种会有循环依赖的问题?

构造器注入会有循环依赖的问题。属性注入就是setter注入,因为构造函数马上就会创建对象,无法完成依赖注入。属性注入,可以利用到缓存。一级缓存、二级缓存、三级缓存。

Spring如何实例化对象的?

通过反射,首先通过工厂方法来实例化对象,在通过构造函数,依据参数的类型去实例化,最后通过默认的构造函数,spring底层做了一个这样的判断。

redis

redis工作模型、redis持久化、redis过期淘汰机制、redis分布式集群的常见形式、分布式锁、缓存击穿、缓存雪崩、缓存一致性问题

@PostConstruct:缓存预热,在构造函数初始化之后执行

@PreDestroy:在bean销毁之后执行

常见问题

  • redis性能为什么高?

基于内存数据库,数据结构简单,没有磁盘io,主要是io模型牛逼。

  • 单线程的redis如何利用多核cpu机器?
  • redis的缓存淘汰策略?

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。

  • redis如何持久化数据?

RDB:将持久化的数据写到文件中。

AOF:记录执行的指令。

  • redis有哪几种数据结构?

List 、set、 string、 hash、 sortedSet

  • redis集群有哪几种形式?

哨兵模式:sentinel,监控master slave,如果master挂了,把slave升级为slave。

  • 有海量key和value都比较小的数据,在redis中如何存储才更省内存?
  • 如何保证redis和DB中的数据一致性?

查询如果缓存没有就查库,更新数据库,是缓存失效。

  • 如何解决缓存穿透和缓存雪崩?
  • 如何用redis实现分布式锁?

加锁:

解锁:

 

 

 

posted on 2019-05-20 21:15  心里向阳-无惧悲伤°  阅读(390)  评论(1)    收藏  举报

导航