java面试题总结面向后端

  1. JVM
    JVM = 类加载器 classloader + 执行引擎 execution engine + 运行时数据区域 runtime data area。

1.1. JVM内存模型(重要)

程序计数器:一块较小的内存空间,存放当前线程执行的字节码的行号指示器,每个线程都需要一个独立的程序计数器;[没有OOM(OutofMemory)]

Java虚拟机栈:与程序计数器一样,Java虚拟机栈也是线程私有,他的生命周期与线程相同,栈的特点是先进后出;这个就是局部变量的存储;

本地方法栈:java的native方法,可以看文件(File)的接口,定义了好多native方法,比如:(UnixFileSystem)
native方法通过JNI调用操作系统库函数来实现;

Java堆:也叫“GC”堆,是GC的主要内存区域,所有线程共享该内存空间,在虚拟机启动时创建;
1) 空间块包括:Yong+Old,永久代在方法区
2) Yong中细化成Eden+2个Survivor,默认8:1:1,通过复制算法回收空间;
3) 大量的GC都是发生在这个区域的;

方法区:和Java堆一样,也是线程共享区域,用来存储已被虚拟机加载的类信息、常量、静态变量、即使编译器编译后的代码等数据(为了和java堆区分,也叫“非堆”),GC同样也会对方法区进行搜集(属于永久代的一部分);

方法区:方法区包括两个区域,一个是永久代(Permanent Generation),另一块是数据区域。
1) 永久代存放了类定义、结构、字段、方法(数据及代码)以及常量在内的类相关数据,在1.8之后废弃了,换成了元数据空间;
2) 数据区域存储的是每个class的信息,包括类加载器引用、运行时常量池、字段数据、方法数据、方法代码。其中运行时常量池部分在1.7之后改在堆上分配了;

[补充]
1) 1.8之后,永久区被废弃,PermGen的空间部分被metaspace代替,从JMM的内存中迁移到native memory中。
2) 放在native 中,虽然没有OOM,但是如果出现问题,就会出现swapping,后果只能更坏;
3) 元数据的生命周期是和它的类加载器的生命周期一致,只要类的类加载器是存活的,在Metaspace中的类元数据也是存活的,不能被释放(不再单独回收某个类);
4) 元空间里的对象的位置是固定的,通过C++维护,同样会触发GC;
2. 垃圾搜集器(重要)
怎么判定对象已死,可以被回收,算法:
1) 引用计数算法:每引用一次变量计数器+1,引用失效时-1,为0的时候回收(很难解决对象之间的相互循环引用问题)
2) 根搜索算法:通过“GC ROOT”的对象(很多个)为起始点,桉树的搜索算法向下搜索,所搜过的路径称为“引用链”,当一个对象没有引用链的时候,将会被回收(现在的JVM用的是这个算法)[每次全部对象扫描,性能很差,放在OopMap中]
[补充]
可以作为Root的对象:
1) 虚拟机栈中的引用对象:Thread中对象
2) 方法区中类静态属性引用的对象:持久的对象
3) 方法区中常量引用对象: 持久的对象
4) 本地方法栈中JNI引用对象:持久的对象

以上是判定这个对象是否可以被回收,接下来是怎么回收的算法,也就是“垃圾回收算法”;
标记-清除算法:当JVM使用根搜索算法得到所有可以回收的对象后,对这些对象打上标记,然后删除;(缺点是会有很多的内存碎片,因为被回收的对象往往不是内存连续的,有了内存碎片那么,明明你还有200M内存,这时候有一个50M的对象要放进去,可能放不进去,因为200M的内存可能分了10个碎片,每个碎片只有20M)
复制算法:把内存分为两块相等的,每次使用一块,每次把还存活的对象copy到另一块内存,然后把死亡的清除掉(能解决内存碎片问题,但是内存只能使用50%,内存浪费)
复制算法的改进版:把内存分为一个Eden空间和两个Survivor(默认比例是8:1:1)空间,每次使用一个Eden空间和一个Survivor空间,每次把存活的移到另一块Survivor空间(因为新生代98%都是朝生夕死的,根据实际情况在性能调优的时候可以更改这个比例,市面上的JVM新生代的搜集算法用的是这个)
标记-整理算法:和标记-清除算法有点像,只不过再清楚完了后堆内存做了一次整理,解决内存碎片的事(性能比较差,不过回收效果好,适合那种存活率比较高的对象内存,比如老年代,市面上好多JVM老年代的搜集算法用的这个)
分代搜集算法:新生代和老年代由于对象的存活率不一样,因此用同一种垃圾搜集算法就不合适了,所以把新生代和老年代搜集算法区分开来,比如,新生代用复制算法,老年代用标记整理算法
以上是垃圾搜集算法,接下来是实现了这些算法的一些垃圾搜集器

Serial收集器:新生代搜集器,复制算法,单线程串行搜集器,进行垃圾回收时,必须暂停其他所有线程工作(Stop the world)(优点:简单高效,没有CPU开销,适用于Client模式下的虚拟机,比如桌面应用,他能与CMS搜集器一起配合工作)
Serial Old收集器:老年代搜集器,标记-整理算法,Serial的老年代版本,适合Client模式下的JVM
ParNew收集器:新生代搜集器,复制算法,Serial收集器的多线程版本,多运行在Sever模式下,蘑菇街的JVM就使用的这个搜集器,还有个好处的他能与CMS搜集器一起配合工作;
Parallel Scavenge搜集器:新生代搜集器,复制算法,并行的搜集器(吞吐量优先)
Parallel Old搜集器: 老年代搜集器,标记-整理算法,Parallel Scavenge的老年代版本
CMS搜集器:老年代搜集器,标记-清除算法,可以与用户线程并发执行,不会stop the world(响应优先),优点:响应时间短,用户体验好,缺点:对CPU敏感,内存碎片;
[补充:CMS并发标记收集器]
1 标记初始化 - stop
2 并发标记
3 再标记 - stop
4 清除
G1搜集器:新生代老年代共同使用的搜集器,标记-整理算法
1 将内存空间划分成相等的ragen,不再区分新生代、老年代;
2 标记初始化;
3 并发标记
4 再标记;
5 筛选清楚价值最高的ragen清除,追求最低的stop时间;

[补充]
1)ParallelScavenge与CMS不能搭配;
HotSpot VM里多个GC有部分共享的代码。有一个分代式GC框架,Serial/Serial Old/ParNew/CMS都在这个框架内;在该框架内的young collector和old collector可以任意搭配使用,所谓的“mix-and-match”。
而ParallelScavenge与G1则不在这个框架内,而是各自采用了自己特别的框架。
2)Metaspace原理不同,也不用搭配使用;
3. JVM内存分配策略(一般)
A、 对象优先在Eden分配
B、 大对象直接进入老年代:
C、 长期存活的对象将进入老年代:默认15岁
D、 动态对象年龄判定:当新生代中某个年龄段的对象大于整体50%,则全部晋升到老年代;
E、 空间分配担保:su0+Edgn 回收后 > su1空间
F、 TLAB仅作用于新生代的Eden Space
G、
4. JDK命令(一般,一般问性能优化的时候会问你用了什么命令)
A、 jps:JVM Process Status Tool,显示指定系统内所有的Hotspot虚拟机进程
B、 jstat:用于搜集Hotspot虚拟机各方面的运行数据(重要)
https://www.cnblogs.com/lizhonghua34/p/7307139.html
[补充]
1) jstat -class 2060:类加载统计
2) jstat -compiler 2060:编译统计
3) jstat -gc 2060:垃圾回收统计
4) jstat -gccapacity 2060:堆内存统计
5) jstat -gcnew 7172:新生代垃圾回收统计
6) jstat -gcnewcapacity 7172:新生代内存统计
7) jstat -gcold 7172:老年代垃圾回收统计
8) jstat -gcoldcapacity 7172:老年代内存统计
9) jstat -gcmetacapacity 7172:元数据空间统计(替代永久区PermGen,分配在Native memory ),不回收,没有jstat –gcmeta 命令;
10) jstat -gcutil 7172:总结垃圾回收统计,当前使用比例
11) jstat -printcompilation 7172:JVM编译方法统计

C、 jmap:生成虚拟机的内存转储快照(heapdump文件)(重要),一般与jhat配合使用
D、 jhat:用于分析heapdump文件(重要)
E、 jstack:显示虚拟机的线程快照
4.1. JDK可视化工具(一般,一般问性能优化的时候会问你用了什么工具)
JConsole:JVM自带(了解)
VisualVM:JVM自带(了解)
5. JVM内存管理参数(一般)
-Xms 表示的是初始化堆的大小(优化的时候可以和Xmx,这样堆大小就是固定的,不会自动扩容,提高性能)
-Xmx 表示的是设置堆的最大大小
-XX:PermSize:设置永久代的初始大小
-XX:MaxPermSize:设置永久代的最大大小
-XX:NewSize 设置新生代的初始大小
-XX:MaxNewSize 设置新生代的最大大小
-XX:+PrintGC:开启简单GC日志模式 为每一次新生代的GC和每一次Full GC打印一行信息
[GC 246656K->243120K(376320K), 0.0929090 secs]
[Full GC 243120K->241951K(629760K), 1.5589690 secs]
-XX:PrintGCDetails:开启了详细GC日志模式 此模式下的日志格式和GC算法有关
使用Throughput垃圾收集器在新生代中生成的日志
[GC
[PSYoungGen: 142816K->10752K(142848K)] 246648K->243136K(375296K), 0.0935090 secs
]
[Times: user=0.55 sys=0.10, real=0.09 secs]

(-XX:+UseSerialGC:激活串行垃圾收集器 例如单线程面向吞吐量的垃圾收集器 推荐用于只有单个CPU的JVM
-XX:+UseParallelGC:使用多线程并行执行年轻代垃圾收集
-XX:+UseParallelOldG:除了激活年轻代并行垃圾收集,也激活了年老代并行垃圾收集
-XX:ParallelGCThreads:-XX:ParallelGCThreads= 指定并行垃圾收集的线程数量)-知道可以设置搜集器类型就好就好
6. JVM类加载过程(重要)
1) 加载[将class文件加载到内存 -> 方法区数据结构->方法区生成class对象]
2) 验证[验证:类是否有结构异常]
3) 准备[静态变量(static filed)在方法区分配内存,静态常量直接赋值]
4) 解析 [符号引用换为直接引用,class、method、field 信息]
5) 初始化[静态变量赋值,优先加载父类]
[初始化场景]new、reflect、父类优先、main、MethodHandle
[被动初始化]
1 通过子类引用父类静态字段,不初始化子类
2 数组引用不会初始化对象
3 引用静态常量不初始化对象(常量池)

6) 使用

7) 卸载
6.1. 类加载器(重要)
双亲委派模型:

启动类加载器:Bootstrap ClassLoader,负责加载存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数指定的路径中,并且是虚拟机识别的类库加载到虚拟机内存中;
扩展类加载器:Extension ClassLoader,加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库;[动态字节码技术应用在此层,java agent]
应用程序类加载器:Application ClassLoader,类名是AppClassLoader,程序员一般继承这个类来实现自己的类加载机制;
  1. 举出一个JVM优化的例子(重要,问到的概率很大)
    准备一个case,一般这么描述,
    碰到的问题:比如碰到了交易“毛刺”现象,日志没有任何错误信息,但是过一段时间,交易的RT就突然升高,然后恢复正常;
    解决的过程:比如先查业务,再查机器Load,网络,DB,下游服务,死锁…最后怀疑是JVM,然后打开GC日志,观察GC过程,有没有发现fullGC?多久GC一次,使用VisualVM观察虚拟器内存状态,使用jmap dump内存,用jhat观察等等等
    结果:最后发现比如存在fullGC,或者堆空间太小(新生代大小太小,GC比较频繁,每隔一段时间新生代暴增)等等
    (case网上找一个也行,套用到自己的系统)
    真没有的话就说IDE(eclipse,idea)的调优
  2. 多线程怎么保证可见性与原子性(重要)
    先看JMM内存模型,JMM定义了8种操作实现工作内存到主存同步:
    1) lock(锁定):作用于主内存,它把一个变量标记为一条线程独占状态;
    2) unlock(解锁):作用于主内存,它将一个处于锁定状态的变量释放出来,释放后的变量才能够被其他线程锁定;
    3) read(读取):作用于主内存,它把变量值从主内存传送到线程的工作内存中,以便随后的load动作使用;
    4) load(载入):作用于工作内存,它把read操作的值放入工作内存中的变量副本中;
    5) use(使用):作用于工作内存,它把工作内存中的值传递给执行引擎,每当虚拟机遇到一个需要使用这个变量的指令时候,将会执行这个动作;
    6) assign(赋值):作用于工作内存,它把从执行引擎获取的值赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的指令时候,执行该操作;
    7) store(存储):作用于工作内存,它把工作内存中的一个变量传送给主内存中,以备随后的write操作使用;
    8) write(写入):作用于主内存,它把store传送值放到主内存中的变量中。
    可见性
    [补充]
    内存中可见性是指多个线程访问同一个共享变量,从内存模型中可以通过两个方法实现:1) 总线锁;2) 一致性协议MESI,强制使其他寄存器中值失效。
    JMM中的可见性基于内存模型实现两种方式:
    1) synchronized和Lock规定,线程在加锁时,先清空工作内存→在主内存中拷贝最新变量的副本到工作内存→执行完代码→将更改后的共享变量的值刷新到主内存中→释放互斥锁;
    2) volatile变量执行写操作时,编译后会自动生成LOCK前缀,行程内存屏障(Load-Store),强制刷新主存,并失效其他线程种数据。
    3)final :final修饰的字段在构造器中一旦初始化完成,并且构造器没有把"this"的引用传递出去(未逃逸),那么在其他线程中就能看见final字段的值,基于happen-before原则实现。

原子性
[补充]
由Java内存模型来直接保证的原子性操作包括read、load、assign、use、store和write,我们大致可以认为基本数据类型的访问读写是具备原子性的。如果应用场景需要一个更大方位的原子性保证,Java内存模型还提供了lock和unlock操作来满足这种需求,尽管虚拟机未把lock和unlock操作直接开放给用户使用,但是却提供了更高层次的字节码指令monitorenter和monitorexit来隐式的使用这两个操作,这两个字节码指令反应到Java代码中就是同步块–synchronized关键字,因此在synchronized块之间的操作也具备原子性。
JMM中实现:
1 Synchronized 通过monitorenter和monitorexit保证,本质调用lock和unlock实现,别的线程不能更改变量值,所有具有原子性;
2 volatile不保证原子性,通过atomic 类实现原子操作,底层通过unsafe的cas方法保证原子更新;

Synchronized和Volatile的比较
1)Synchronized保证内存可见性和操作的原子性
2)Volatile只能保证内存可见性
3)Volatile不需要加锁,比Synchronized更轻量级,并不会阻塞线程(volatile不会造成线程的阻塞;synchronized会造成线程的阻塞。)
4)volatile标记的变量不会被编译器优化,而synchronized标记的变量可以被编译器优化(如编译器重排序的优化).
5)volatile是变量修饰符,仅能用于变量,而synchronized是一个方法或块的修饰符。
volatile本质是在告诉JVM当前变量在寄存器中的值是不确定的,使用前,需要先从主存中读取,因此可以实现可见性。而对n=n+1,n++等操作时,volatile关键字将失效,不能起到像synchronized一样的线程同步(原子性)的效果。
9. Java有哪几种方式的锁机制(重要)
[补充]锁实现类型
1) 偏向锁:线程持有锁不释放,等待别人竞争;
2) 轻量锁:CAS更新锁对应空间,成功后获取锁,释放后唤醒等待线程;
3) 重量锁:monitor
4) 自旋锁:CAS,CPU敏感,不切换线程。自适应自旋锁;

Synchronized、Lock、Semaphore
[补充]
Synchronized通过字节码生成monitor 屏障;
Lock通过代码实现;
Semaphore 实现多个资源并发访问,做流量控制,如果只有一个信号,可以当锁用;

[补充]锁类型 - 锁优化
Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。

自旋锁:线程不阻塞,CPU空消耗一段;
自适应自旋锁:上次成功增加次数,失败减少直至不自旋;
轻量级锁:无锁状态增加lock record,CAS抢占锁,CAS释放锁,释放失败唤醒;
偏向锁:减少不必要的轻量级锁CAS,不主动释放,等待竞争,撤销在安全点;
重量锁:monitor,赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高;

  1. Synchronized、Lock区别于原理(重要)
    Synchronized:JDM提供,可重入,不可中断 非公平,悲观锁(排他锁)
    原理:https://blog.csdn.net/shandian000/article/details/54927876
    Lock:比如ReentrantLock,ReadWriteLock;Java类实现,可重入 可判断 可公平(两者皆可)、可try,不阻塞太久,CPU敏感
    原理:
    ReentrantLock,源码级别:https://blog.csdn.net/albertfly/article/details/52403508
    ReadWriteLock,源码级别:https://www.jianshu.com/p/9f98299a17a5

对比:
竞争激烈,Synchronized性能好,反之Lock性能好,因为竞争激励的时候,Lock会不停的竞争资源,发生自旋,而Synchronized线程直接等待了;
11. Volatile原理(重要)
Volatile只能解决可见性,不能保证原子性;
https://www.cnblogs.com/dolphin0520/p/3920373.html

[补充]
内存模型:CPU - 高速缓存- 内存
1)通过在总线加LOCK#锁的方式
2)通过缓存一致性协议MESI协议:写共享变量时,强制失效其他缓存中的值

多线程概念:
原子性、可见性、顺序性(指令重排)

java 内存模型JMM:主存+工作内存(抽象的概念),主存即内存,工作内存是线程私有。
原子性:默认基础类型读取和赋值是原子的,其他需要synchronized和Lock来实现;
可见性:volatile、synchronized和Lock来实现(final也具有一点可见性);
有序性:volatile、synchronized和Lock来实现,另外还有happens-before 原则:
1 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
2 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
3 volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
4 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序,在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
3)不保证原子性。

实现原理:汇编语言在volatile关键字前,加入lock前缀指令,即内存屏障:
1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
  2)它会强制将对缓存的修改操作立即写入主存;
  3)如果是写操作,它会导致其他CPU中对应的缓存行无效。

使用场景:
1)对变量的写操作不依赖于当前值:因为不能保证原子性
2)该变量没有包含在具有其他变量的不变式中:因为不能保证原子性

  1. ThreadLocal原理(一般)
    http://www.cnblogs.com/dolphin0520/p/3920407.html

ThreadLocal是一个关系户,在每个Thread里面持有一个ThreadLocalMap的成员变量,用来实际存储数据,它自己是一个入口,或者一个壳子。
/* ThreadLocal values pertaining to this thread. This map is maintained

  • by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

但是注意,它不是静态变量,不是public变量,说明这个变量的访问入口不在自己(Thread),而是ThreadLocal。
ThreadLocal.ThreadLocalMap内部数据通过Entry extends WeakReference<ThreadLocal<?>>存储,是一个弱引用,二次GC的时候会自动回收。

使用完注意调用remove()方法来释放数据,否则即使ThreadLocal = null数据不会自动回收,只有Thread结束才会回收,如果在线程池中可能导致内存泄漏。

A) 强引用:只要有引用,不回收
B) 软引用:只要不OOM,不回收
C) 弱引用:下一次垃圾回收,才回收
D) 虚引用:只要垃圾回收,就回收,用来监控某个对象确实被回收了

  1. LinkedBlockingQueue和ConcurrentLinkedQueue的区别及用法(重要)
    LinkedBlockingQueue原理(源码):http://www.importnew.com/25583.html
    [补充]
  1. 单向阻塞队列,基于锁(putLock和takeLock)来保证,每次只有一个读写线程操作链;
    1) put从last操作,take从head操作,保证不会并发;
  2. 队列为空时,阻塞take线程,等到有put操作,唤醒一个线程;
  3. 队列满时,阻塞put线程,等到有take操作,判断容量,唤醒一个线程;

ConcurrentLinkedQueue原理(源码)http://www.importnew.com/25668.html
[补充]
2) 单向无界阻塞队列,基于CAS保证原子性,volatile保证可见性,从而保证并发下线程安全;
3) 空队列poll时,直接返回null;
4) 删除时遍历所有队列,删除匹配的第一个值,未加锁导致可能删错;
5) size方法同样不准;
对比:
1) LinkedBlockingQueue是通过锁实现的,可重入,但是读写都只能单线程;ConcurrentLinkedQueue是通过CAS实现的,不阻塞(自旋);
2) LinkedBlockingQueue的size是通过原子变量count实现的,比较准;ConcurrentLinkedQueue的size是通过遍历实现的,而且没有同步,导致可能不准;
3) LinkedBlockingQueue的删除方法是通过双锁实现的,比较准确;ConcurrentLinkedQueue的删除没有加锁,可能删错;

Queue的take、put、offer、pull方法有什么用以及区别
take、put:(锁但会阻塞)继承至Queue,take空时阻塞,put满时阻塞
offer、poll;(锁但不会阻塞)继承至Queue,可带时间,可中断;
add,remove:继承至Collection
14. ThreadPoolExecutor原理(一般)
http://www.importnew.com/16797.html
[补充]
1) corePoolSize
2) maximumPoolSize:最大线程数,再大就舍弃
3) workQueue:达到corePoolSize后放任务,默认LinkedBlockingQueue,运行中的任务,会放HashSet中保存;
4) keepAliveTime:默认0,空闲等待时间;
5) threadFactory:线程工厂,统一创建线程;
6) handler:舍弃策略RejectedExecutionHandler,默认AbortPolicy(异常)、CallerRunsPolicy(用调用者执行)、DiscardOldestPolicy (舍弃最老)、DiscardPolicy(舍弃最新)

7) 先用corePoolSize的线程,如果满了,用workQueue,如果满了,用maxPoolSize的线程,如果满了,走reject策略;
15. Runable和Callable (FutureTask)的区别(重要)
FutureTask:http://www.importnew.com/25286.html
https://blog.csdn.net/zhangzhaokun/article/details/6615454
[补充]
1) 返回线程结果,支持取消、支持阻塞获取结果
2) FutureTask实现了Future、Runnable接口,包装了callable的返回值,提供取消、判断等方法
3) 可以用RunnableAdapter包装Runable ,但是无有效返回值
4) FutureTask内部增加了7个状态和runner和waitter,通过CAS设置状态转化和持有的线程,没有AQS;
5) 支持多次get();
6) 一般FutureTask委托给线程池(ExecutorService)执行,当然自己也可以run();

  1. HashMap、ConCurrentHashMap、HashSet、TreeSet 、TreeMap、HashTable、ArrayList、LinkedList数据结构和实现原理(重要)
    A) HashMap原理(源码-线程不安全):
    https://www.cnblogs.com/dijia478/p/8006713.html
    HashMap默认分配16长度空间,超过0.75(也就是12的时候自动扩容,还需要reindex,性能比较低),所以一般情况性能优化的时候我们固定HashMap的size,比如HashMap h = new HashMap(256);
    HashMap在JDK1.7数据结构是数组+链表,在JDK1.8以后变成数组+红黑树+链表,(当长度大于8时,自动切换成红黑树);红黑树也要了解一下最差复杂度由O(N)变成了O(logN);
    [补充]
    1) 先用数组存储索引,hash(key)%size,查询复杂的是O(1);
    2) 通过单向链表解决冲突,如果链表长度超过8,转成红黑树,最差复杂度由O(N)变成了O(logN);
    3) 老版本(1.8之前)在rehash的时候,需要倒序扩容,并发现形成循环,多线程下get方法会死锁(死循环);
    4) String,Integer等包装类hashcode固定,有利于减少碰撞,equals固定,有利于查询;
    5) 红黑树适用于多查询少写入场景,因为左旋右旋都需要消耗CPU;

B) ConCurrentHashMap原理(源码-线程安全)
https://www.cnblogs.com/chengxiao/p/6842045.html
(HashMap线程不安全,ConCurrentHashMap线程安全,还有JDK为什么要换红黑树,红黑树的优点是查询效率高,插入效率低,因为本身HashMap就是线程不安全的,因此,对于存在大并发写HashMap本身就不合适,也就是HashMap本身的设计就更倾向与高并发读,因此更多的去考虑提高读的性能)
[补充]
1) 1.8之后已经不用Segment实现了,通过Node:Entry数组;
2) put方法如果是空Node,CAS设置值;如果不空且是Node,同步Node操作;
3) 如果链表长度大于8,同样转成红黑树,不用synchronized同步,而是用CAS对root节点加状态锁实现同步,同时持有waiter,waiter可以用来中断自己;
4) 通过分段加锁(Node),而不是整个map,所以效率比hashtable高;
5) Collections.synchronizeMap(hashMap)通过容器同步,效率也不高;

C) HashSet原理(源码-线程不安全):
https://blog.csdn.net/guoweimelon/article/details/50804799
[补充]
1) 实现的是Set和Collection接口,不是key value;
2) 内部通过HashMap的Key实现存储的,value是一个静态对象;
3) 线程不安全,且set 顺序不保证;
4) 依赖于key对象重写hashcode方法和equals方法,可以为null(table[0]);

D) TreeMap原理(源码-线程不安全)
https://blog.csdn.net/zhangyuan19880606/article/details/51234420
[补充]
1) 实现的是Map和SortedMap接口,支持排序;
2) 基于红黑树实现的存储,排序默认按照key值进行升序排序;

E) TreeSet原理(源码-线程不安全)
https://www.cnblogs.com/ningvsban/archive/2013/05/06/3062535.html
[补充]
1) 实现的是Set和Collection还有NavigableSet接口,支持排序;
2) 必须指定算法,NavigableMap(算法),默认是TreeMap;
3) 构造器中的Comparator会委托给TreeMap去处理;

F) HashTable原理(源码-线程安全)
http://wiki.jikexueyuan.com/project/java-collection/hashtable.html
[补充]
1) 实现了Map接口,继承了Dictionary,支持clone和序列化;
2) 内部通过Entry+单链实现;index = (key.hashCode() & 0x7FFFFFFF) % tab.length; length 默认用11;
3) put、remove 方法通过synchronized保证同步,效率低;

G) ArrayList原理(源码-线程不安全)
https://www.cnblogs.com/chenpi/p/5247946.html
[补充]

  1. 实现List、Collection接口;
  2. 内部通过Object[]实现存储,线程不安全,默认容量10,自动扩容(Arrays.copyOf,性能没问题)

H) CopyOnWriteArrayList(源码-线程安全)
https://blog.csdn.net/linsongbin1/article/details/54581787
[补充]
1) 实现List接口,add、remove方法加锁,保证线程安全,读取方法没有加锁;
2) add、remove方法通过数据拷贝实现,消耗大量内存;
3) 读写分离、最重一致、空间换时间;

I) LinkedList原理(源码-线程不安全):
https://blog.csdn.net/lzjlzjlzjlzjlzjlzj/article/details/52278461
(ArrayList等怎么才能线程安全,可以使用Collections来创建SynchronizedList 包装在容器里:SynchronizedList)
[补充]
1) 实现了List, Deque,Collection接口;
2) 内部通过Node实现双向列表,线程不安全;
3) add方法默认从尾部增加;remove默认遍历,删除匹配到的第一个值(equals);
4) 查找时优先从靠近的地方查询(基于index实现),随机查询效率不高;
17. StringBuffer和StringBuilder原理、区别(重要)
https://www.cnblogs.com/su-feng/p/6659064.html
StringBuffer线程安全,性能比StringBuilder低
StringBuilder线程不安全
[补充]
1) 效率:StringBuilder > StringBuffer > String;
2) 线程安全:StringBuilder是线程不安全的,而StringBuffer是线程安全的;
3) StringBuffer所有方法都synchronized,效率低是有原因的;
18. Spring IOC和AOP是什么(重要)
IOC:Inverse of control:控制反转,原来用用户管理的bean交给Spring容器管理
https://www.cnblogs.com/ITtangtang/p/3978349.html
[补充]
1) 定义工厂:BeanFactory;
2) 定义类:文件Resource,Class BeanDefinition
3) 初始化容器:refresh() 读Resource,解析生成BeanDefinition,放入Map;
4) getBean:接口用JDK反射,Class用CGLIB发序列化;
5) populateBean:属性填充;
6) layz-init:refresh时自动getBean;

AOP:面向切面编程
https://www.cnblogs.com/liuruowang/p/5711563.html
[补充]
1) 基于动态代理实现,接口用JDK,类用CGLIB;
2) 切面类:用来定义切入点和通知,是一个Class;
3) 切点:带通知的连接点,用通配符定义通知时机;
4) 通知:在切点前后增强逻辑,包括:Before,AfterReturning、AfterThrowing、After、Around;
19. Spring bean生命周期(一般)
https://www.cnblogs.com/zrtqsk/p/3735273.html
不需要全部记,大概的就行
[补充]
1) 工厂后处理器接口方法:AspectJWeavingEnabler, ConfigurationClassPostProcessor, CustomAutowireConfigurer
2) 容器级生命周期接口方法:InstantiationAwareBeanPostProcessor 和 BeanPostProcessor 这两个接口实现
3) Bean级生命周期接口方法:BeanNameAware、BeanFactoryAware、InitializingBean和DisposableBean
4) Bean自身的方法:init-method和destroy-method

  1. 什么是动态代理(重要)
    代理分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP为代表;实现动态代理有两种方式:cglib和Proxy+InvocationHandler
    [补充]
    1) 静态代理:就是代理模式,或者委托模式
    a) 明确要代理的对象:接口定义;
    b) 定义代理类:同样实现目标接口;
    c) 声明代理类,传入被代理对象;
    d) 使用代理类调用目标方法,实质上是调用被代理对象的方法;
    2) JDK动态代理
    a) 明确要代理的对象:接口定义
    b) 声明代理实现类:InvocationHandler,重写了invoke方法(还动态个毛毛啊,但是可以一个代理多个类);
    c) 调用Proxy的静态方法newProxyInstance方法生成代理实例,invoke方法中的proxy对象没啥用;
    d) 用新生成的代理实例调用某个方法实现功能;
    3) CGLIB动态代理
    a) Enhancer enchancer = new Enhancer();//字节码增强器
    b) enchancer.setSuperclass(User.class);//设置被代理类为父类
    c) enchancer.setCallback(new UserInterceptor());//设置代理类
    d) User user = (User)enchancer.create();//创建代理实例
    e) user.eat(“葡萄”);//调用代理方法

  2. Spring声明式事务管理原理(一般)
    https://blog.csdn.net/u011726984/article/details/45421151/
    事务特性:
    原子性、一致性、隔离性、持久性
    [补充]
    1) 事务通过AOP方式实现;
    2) 显式定义事务管理器,手动提交[FundTrade];

21.1. 事务的隔离机制和传播机制(重要)
隔离机制:Read uncommitted 、Read committed 、Repeatable read 、Serializable
传播机制:https://blog.csdn.net/yuanlaishini2010/article/details/45792069
[补充]
1) 原子性:要么全成功,要么全失败;
2) 一致性:事务执行过程中,数据库状态是一致性的;
3) 隔离性:并发事务之间不影响;
4) 持久性:事务提交成功,数据库状态就变成永久的,灾难可恢复;

单独说隔离性,隔离级别:
1) Read uncommitted :未提交读,可以读到别的事务未提交的数据(脏读);
2) Read committed:提交读,只能读取别的事务已经提交的数据,但是允许重复读,同一个读事务中,可能读到不同的值,即读不锁(防止脏读,但是不可重复读);
3) Repeatable read:可重复读,读事务加锁,单事务中读到的值是一样的(防止脏读、防止不可重复读);
4) Serializable :串行化,锁表,避免脏读、不可重复读、幻读;

[继续补充mysql - innoDB锁]
1) 排它锁:select for update;
2) 共享锁:select lock in share mode;
3) 意向拍它锁:加排它锁时有排它锁,系统自动加;
4) 意向共享锁:加共享锁时有排它锁,系统自动加;
5) mysql行锁是加在索引上的,走上同一个索引条件的排它锁会死锁,执行计划会自动优化索引,死锁;
6) 间隙锁:select where ID >100 for update; 范围锁行程GAP,next-key锁,避免幻读;
21.2. 分布式事务XA、TCC(重要)
XA和TCC
https://mp.weixin.qq.com/s/sy_PKHc_km6uZ3TgJYfRnw
[补充]
XA(两阶段提交)

1) DTP 模型中包含一个全局事务管理器(TM,Transaction Manager)和多个资源管理器(RM,Resource Manager)。全局事务管理器负责管理全局事务状态与参与的资源,协同资源一起提交或回滚;资源管理器则负责具体的资源操作。
2) 应用程序(AP,Application)向 TM 申请开始一个全局事务;
3) 针对要操作的 RM,AP 会先向 TM 注册(TM 负责记录 AP 操作过哪些 RM,即分支事务),TM 通过 XA 接口函数通知相应 RM 开启分布式事务的子事务,接着 AP 就可以对该 RM 管理的资源进行操作;
4) 当 AP 对所有 RM 操作完毕后,AP 根据执行情况通知 TM 提交或回滚该全局事务,TM 通过 XA 接口函数通知各 RM 完成操作。TM 会先要求各个 RM 做预提交,所有 RM 返回成功后,再要求各 RM 做正式提交,XA 协议要求,一旦 RM 预提交成功,则后续的正式提交也必须能成功;如果任意一个 RM 预提交失败,则 TM 通知各 RM 回滚;
5) 所有 RM 提交或回滚完成后,全局事务结束;
6) XA 协议并没有定义怎么实现全局的 Snapshot,一个基本思路是用一个集中式或者逻辑上单调递增的东西来控制生成全局 Snapshot,每个事务或者每条 SQL 执行时都去获取一次,从而实现不同隔离级别下的一致性;
TCC:三阶段提交

1) 初步操作 Try:完成所有业务检查,预留必须的业务资源;
2) 确认操作 Confirm:真正执行的业务逻辑,不作任何业务检查,只使用 Try 阶段预留的业务资源。因此,只要 Try 操作成功,Confirm 必须能成功。另外,Confirm 操作需满足幂等性,保证一笔分布式事务有且只能成功一次。
3) 取消操作 Cancel:释放 Try 阶段预留的业务资源。同样的,Cancel 操作也需要满足幂等性;
4) 业务活动管理器:业务活动管理器管理控制整个业务活动,包括记录维护 TCC 全局事务的事务状态和每个从业务服务的子事务状态,并在业务活动提交时调用所有从业务服务的 Confirm 操作,在业务活动取消时调用所有从业务服务的 Cancel 操作;

21.3. 强一致性和最终一致性(只要)
有这个概念就好,系统设计的时候到底遵循哪个原则,蘑菇街都是最终一致性

21.4. Spring事务能不能作用在private方法(一般)
不能,而且使用aop的话必须作用在接口方法上才能生效,一个带了事务的方法调了另一个不带事务的方法,事务还能不能生效,诸如此类问题都用事务传播机制去解释。
[补充]
Spring的事务传播级别:
1) PROPAGATION_REQUIRED:有就用,没有就加;
2) PROPAGATION_SUPPORTS:有就用,没有就算;
3) PROPAGATION_MANDATORY:有就用,没有就异常;
4) PROPAGATION_REQUIRES_NEW:新建事务,有则挂起;
5) PROPAGATION_NOT_SUPPORTED:不用事务,有则挂起;
6) PROPAGATION_NEVER:不用事务,有则异常;
7) PROPAGATION_NESTED:有则嵌套,无则新建;

[补充]
MySql索引类型:
1) 聚集索引:主键,全表唯一,非null,一般查询(除了走全索引),最终都通过聚集索引定位数据;
2) 普通索引:业务Key,通过自身B+ 树定位到主键,然后再查具体数据;
3) 组合索引:联合索引,多业务栏位,最左原则;
4) 唯一索引:允许有null
5) 全文索引:

MySQL索引结构:
1) hash索引:系统自建,优化查询,自适应hash索引,不支持范围查找O(1);
2) BTree索引:叶子节点中带有索引数据,最差O(LogN);
3) RTREE索引:
4)
22. CAP和BASE理论(一般,更多会在问你系统设计的时候顺便问道)
CAP原则又称CAP定理,指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼 。
BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的缩写。BASE理论是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结, 是基于CAP定理逐步演化而来的。BASE理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性
https://www.cnblogs.com/duanxz/p/5229352.html
(尤其在对分布式系统设计的时候,尤其需要考虑,如何取舍,比如蘑菇街的系统考虑的就是Availability(可用性)、Partition tolerance(分区容错性)+最终一致性)
[补充]
1) 异地多活:保证可用性,分区容错性,但是放弃数据一致性;
2) 两地三中心:解决数据备份;
23. 数据库索引(了解就好,应该不会问)
B+树
https://www.cnblogs.com/aspwebchh/p/6652855.html

  1. 内节点不存储data,只存储key;叶子节点不存储指针,存索引数据;
  2. 叶子节点间增加顺序访问指针,优化查询效率;
  1. 分布式缓存(一般,也是在项目中顺便问到)
    缓存一致性
    https://blog.csdn.net/chenxiaochan/article/details/71036497
    [补充]
    1) 缓存集中失效:导致雪崩
    2) 缓存穿透:无效Key打穿缓存,可以采用同时缓存无效Key;
    3) key名不重
    4) 一致性hash算法:增加节点尽量少改变key映射;
    5) 降级方案:不能因为缓存跪了,服务直接不可用;
  2. 自己做过的项目(非常非常重要)
    一般会问的非常细,主要有几方面内容:
    1) 做这个项目的背景以及能带来什么价值
    2) 系统的设计思想,怎么保证系统的性能,数据的一致性(异常、差错处理),技术选型,怎么避免系统雪崩,为什么这么分库分表,系统的支撑性,扩展性,限流、并发死锁、热点等等(这里就会有分布式系统CAP、BASE理论、最终一致性这些点的考虑了,设计的时候就需要考虑到)
    3) 系统、业务的前景和发展
    (还有一点是自己的系统当前平均QPS,峰值QPS,数据量需要知道,为了考察你对自己的系统是否了如指掌,这种可能问到,还有你在项目中的角色,曾经碰到的问题,如何解决问题,性能调优,压力测试(系统最高单机QPS)等等)
    4) 可能会有拓展题,比如,加入现在要你的系统性能提高100倍,或者说假如现在QPS提高了100倍,你的系统哪里可能存在瓶颈,出现这个瓶颈后你要怎么解决
  3. 蘑菇街Tesla、Pegion、Corgi、MoguCache大概了解下(一般)
    主要是RPC框架核心机制了解下
    1) 通过客户端、服务端代理机制,简化用户使用;
    2) 优雅停机,多版本共存:
    3) 线程池配置:
    4) 异步化:服务端异步、客户端异步,不需要起线程,通过NIO的回调机制保证;
    5) 服务分组:基于服务端机器做地址映射,区别于客户端路由表;
    6) 快速降级:默认failover
    7) 熔断:目前不支持
    8) 服务治理:注册中心,客户端路由表
  4. 热点账户问题(金融业务问到概率不大)
    异步记账
  5. TCP3次握手和4次回收 & NIO(了解)
    https://blog.csdn.net/letunihao/article/details/78455860
    https://segmentfault.com/a/1190000012316621
    [补充]
    Netty的高效并发编程主要体现在如下几点:
  1. volatile的大量、正确使用;

  2. CAS和原子类的广泛使用;

  3. 线程安全容器的使用;

  4. 通过读写锁提升并发性能。
    Netty除了使用reactor来提升性能,当然还有
    1、零拷贝,IO性能优化,堆外内存分配
    2、通信上的粘包拆包
    2、同步的设计
    3、高性能的序列化

  1. 一般的问题都是从你的项目出发,问到最细的一个点上
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值