面试

跳槽时时刻刻都在发生,但是我建议大家跳槽之前,先想清楚为什么要跳槽。切不可跟风,看到同事一个个都走了,自己也盲目的面试起来(期间也没有准备充分),到底是因为技术原因(影响自己的发展,偏移自己规划的轨迹),还是钱给少了,不受重视。

准备不充分的面试,完全是浪费时间,更是对自己的不负责(如果title很高,当我没说)。

今天给大家分享下chenssy在这次跳槽中整理的Java面试大纲,其中大部分都是面试过程中的面试题,可以对照这查漏补缺,当然了,这里所列的肯定不可能覆盖全部方式。

项目介绍
大部分情况,这是一场面试的开门题,面试官问这个问题,主要是考察你的概述能力和全局视野。有的人经常抱怨自己每天在堆业务,但没有成长。事实上,很多情况下确实在堆业务,但并不是没有成长的。并非做中间件或者技术架构才是成长,例如我们的需求分析能力,沟通协作能力,产品思维能力,抽象建模能力等都是一个非常重要的硬实力。

好的,现在进入正文。

1、明确项目是做什么的

2、明确项目的价值。(为什么做这个项目,它解决了用户什么痛点,它带来什么价值?)

3、明确项目的功能。(这个项目涉及哪些功能?)

4、明确项目的技术。(这个项目用到哪些技术?)

5、明确个人在项目中的位置和作用。(你在这个项目的承担角色?)

6、明确项目的整体架构。

7、明确项目的优缺点,如果重新设计你会如何设计。

8、明确项目的亮点。(这个项目有什么亮点?)

9、明确技术成长。(你通过这个项目有哪些技术成长?)

Java基础

List 、 Set、Map 的区别

List、Set、Map

HashSet 是如何保证不重复的

List、Set、Map

HashMap 是线程安全的吗,为什么不是线程安全的(最好画图说明多线程环境下不安全)?

List、Set、Map

HashMap 的扩容过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
// 超过最大值就不再扩充了,就只好随你碰撞去吧
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 没超过最大值,就扩充为原来的2倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 计算新的resize上限
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
// 把每个bucket都移动到新的buckets中
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // 链表优化重hash的代码块
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
// 原索引
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
// 原索引+oldCap
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
// 原索引放到bucket里
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
// 原索引+oldCap放到bucket里
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}

Java 8系列之重新认识HashMap
HashMap原理-1.8

HashMap 1.7 与 1.8 的 区别,说明 1.8 做了哪些优化,如何优化的?

  1. 引入了红黑树
  2. 扩容hash的优化,利用扩容后的位置的特性,不需要像JDK1.7的实现那样重新计算hash。
  3. resize的时候,不想1.7那样需要倒置元素

Java 8系列之重新认识HashMap
Java源码分析:HashMap 1.8 相对于1.7 到底更新了什么?
HashMap原理-1.8

final finally finalize

final:用于修饰类、成员变量和成员方法。final修饰的类,不能被继承(String、StringBuilder、StringBuffer、Math,不可变类);
Final修饰的方法不能被重写,但是子类可以用父类中final修饰的方法;
Final修饰的成员变量是不可变的,如果成员变量是基本数据类型,初始化之后成员变量的值不能被改变,
如果成员变量是引用类型,那么它只能指向初始化时指向的那个对象,不能再指向别的对象,但是对象当中的内容是允许改变的。

finally:用于异常代码块执行完成之后执行,通常用于关闭资源

finalize:object类中的一个方法,Java虚拟机在垃圾回收之前会先调用垃圾对象的finalize方法用于使对象释放资源(如关闭连接、关闭文件),
之后才进行垃圾回收,这个方法一般不会显示的调用,在垃圾回收时垃圾回收器会主动调用。
并且,虚拟机并不承诺等待它允许结束,是为了避免其中一个执行缓慢,导致整个内存回收系统崩溃。

强引用 、软引用、 弱引用、虚引用

类型 生命周期 用途
强引用 不会被GC
SoftReference 直到内存不足时 二级缓存
WeakReference 下次GC 缓存(WeakHashMap)
PhantomReference 下次GC 堆外内存管理

关于java内存泄露的总结–引用的类型:强引用,弱引用,软引用
从面试题中看Java的Reference(引用)
ThreadLocal内存泄露

Java反射

说说 Java 反射机制
深入分析Java方法反射的实现原理
Java反射机制应用实践

应用:

  • AOP分离业务代码(jdk动态代理)
  • 获取注解
  • 泛型擦除
  • eclipse等IDE的代码智能提示

Arrays.sort 实现原理和 Collection 实现原理

LinkedHashMap的应用

LinkedHashMap 能够做到按照插入顺序或者访问顺序进行迭代,这样在我们以后的开发中遇到相似的问题,才能想到用 LinkedHashMap 来解决,否则就算对其内部结构非常了解,不去使用也是没有什么用的。

List、Set、Map

cloneable接口实现原理

克隆规则:
1、基本类型
如果变量是基本类型,则拷贝其值,比如int、float等。
2、 对象
如果变量是一个实例对象,则拷贝其地址引用,也就是说新对象和原来对象是共用实例变量的。
3、 String字符串
若变量为String字符串,则拷贝其地址引用。但是在修改时,它会从字符串池中重新生成一个新的字符串,原有的对象保持不变。

使用:
Object.java的clone()是一个native方法,当需要克隆时,子类实现Cloneable接口后,重写clone(),调用super.clone()。需要注意涉及到深、浅拷贝。

实现深克隆:
1、先对对象进行序列化,紧接着马上反序列化出
2、先调用super.clone()方法克隆出一个新对象来,然后在子类的clone()方法中手动给克隆出来的非基本数据类型(引用类型)赋值,比如ArrayList的clone()方法:
3、在clone方法里面,递归克隆非基本类型的成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Administrator implements Cloneable {
private User user;
private Boolean editable;
public Administrator(User user, Boolean editable) {
this.user = user;
this.editable = editable;
}

@Override
protected Object clone() throws CloneNotSupportedException {
Bean bean = (Bean) super.clone();

// 实现深克隆,需要User实现了Cloneable接口
bean.user = (ChildBean) bean.user.clone();
return bean;
}
@Override
public int hashCode() {
// 老规矩
}
@Override
public boolean equals(Object obj) {
// 老规矩
}
// 老规矩
}

Java中的clone()和Cloneable接口

Java创建对象的几种方式

(1) 用new语句创建对象,这是最常见的创建对象的方法。
(2) 运用反射手段,调用java.lang.Class或者java.lang.reflect.Constructor类的newInstance()实例方法。
(3) 调用对象的clone()方法。
(4) 运用反序列化手段,调用java.io.ObjectInputStream对象的 readObject()方法。

(1)和(2)都会明确的显式的调用构造函数 ;(3)是在内存上对已有对象的影印,所以不会调用构造函数 ;(4)是从文件中还原类的对象,也不会调用构造函数。

异常分类以及处理机制

mage-20180319210533

mage-20180319211637

  • Error是无法处理的异常,比如OutOfMemoryError,一般发生这种异常,JVM会选择终止程序。因此我们编写程序时不需要关心这类异常。
  • Exception,也就是我们经常见到的一些异常情况,这些异常是我们可以处理的异常,是所有异常类的父类。
  • unchecked exception(非受查异常),包括Error和RuntimeException,比如常见的NullPointerException、IndexOutOfBoundsException。对于RuntimeException,java编译器不要求必须进行异常捕获处理或者抛出声明,由程序员自行决定。
  • checked exception(受查异常),也称非运行时异常(运行时异常以外的异常就是非运行时异常),由代码能力之外的因素导致的运行时错误。java编译器强制程序员必须进行捕获处理,比如常见的有IOExeption和SQLException。如果不进行捕获或者抛出声明处理,编译都不会通过。
  • 典型的RuntimeException包括NullPointerException、IndexOutOfBoundsException、IllegalArgumentException等。
  • 典型的非RuntimeException包括IOException、SQLException等。

mage-20180319210826

mage-20180319210836

mage-20180319210844

mage-20180319210928

wait和sleep的区别

wait:调用后,必须被通知才能重新运行,且释放锁资源。

sleep:睡眠一定时间后继续执行,且不释放锁资源。

  • 首先,要记住这个差别,“sleep是Thread类的方法,wait是Object类中定义的方法”。尽管这两个方法都会影响线程的执行行为,但是本质上是有区别的。
  • Thread.sleep不会导致锁行为的改变,如果当前线程是拥有锁的,那么Thread.sleep不会让线程释放锁。如果能够帮助你记忆的话,可以简单认为和锁相关的方法都定义在Object类中,因此调用Thread.sleep是不会影响锁的相关行为。
  • Thread.sleep和Object.wait都会暂停当前的线程,对于CPU资源来说,不管是哪种方式暂停的线程,都表示它暂时不再需要CPU的执行时间。OS会将执行时间分配给其它线程。区别是,调用wait后,需要别的线程执行notify/notifyAll才能够重新获得CPU执行时间。
  • 线程的状态参考 Thread.State的定义。新创建的但是没有执行(还没有调用start())的线程处于“就绪”,或者说Thread.State.NEW状态。
  • Thread.State.BLOCKED(阻塞)表示线程正在获取锁时,因为锁不能获取到而被迫暂停执行下面的指令,一直等到这个锁被别的线程释放。BLOCKED状态下线程,OS调度机制需要决定下一个能够获取锁的线程是哪个,这种情况下,就是产生锁的争用,无论如何这都是很耗时的操作。

数组在内存中如何分配

对象在堆上分配连续空间。

Java 并发

synchronized 的实现原理以及锁优化?

JVM基于进入和退出Monitor对象来实现方法同步和代码同步。使用monitorenter和monitorexit指令实现。

每个对象有一个监视器锁(monitor);

偏向锁:使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点,没有字节码在执行)。

轻量级锁:轻量级锁是由偏向所升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁;

重量级锁。

mage-20180319220645

volatile 的实现原理?

通过lock前缀实现,底层是通过总线锁定和缓存锁定来实现。

Lock前缀指令会引起处理器缓存回写到内存。一个处理器的缓存回写到内存会导致其他处理器的缓存无效。

Java 的信号灯?

控制并发线程数量。

通过AQS来实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class SemaphoreTest {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final Semaphore sp = new Semaphore(3);
for(int i=0;i<10;i++){
Runnable runnable = new Runnable(){
public void run(){
try {
sp.acquire();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() +
"进入,当前已有" + (3-sp.availablePermits()) + "个并发");
try {
Thread.sleep((long)(Math.random()*10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() +
"即将离开");
sp.release();
//下面代码有时候执行不准确,因为其没有和上面的代码合成原子单元
System.out.println("线程" + Thread.currentThread().getName() +
"已离开,当前已有" + (3-sp.availablePermits()) + "个并发");
}
};
service.execute(runnable);
}
}
}

synchronized 在静态方法和普通方法的区别?

静态方法和实例方法不是同一把锁

怎么实现所有线程在等待某个事件的发生才会去执行?

CountDownLatch、CyclicBarrier

CAS?CAS 有什么缺陷,如何解决?

CompareAndSwap,不用加锁

存在ABA问题,通过添加版本号来区分(AtomicStampedReference);循环时间开销大;

synchronized 和 lock 有什么区别?

  • lock能够非阻塞获取锁、中断地获取锁、超时获取锁、更加灵活。
  • 悲观锁与乐观锁
  • synchronized由jvm自动释放,lock需要手动释放
  • 在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;

Synchronized与Lock锁的区别

Hashtable 是怎么加锁的 ?

synchronized

HashMap 的并发问题?

死循环导致CPU100%使用

HashMap的死循环

ConcurrenHashMap 介绍?1.8 中为什么要用红黑树?

彻底看懂 so called 红黑树
红黑树深入剖析及Java实现

AQS

抽象队列同步器,AbstractQueueSynchronizer。

  • 模板方法
  • volatile int 状态变量
  • CAS
  • 同步队列(FIFO双向队列)
  • 共享、独占获取同步状态

如何检测死锁?怎么预防死锁?

检测:

  1. cpu使用率低
  2. io使用率低
  3. jstack

预防:

  1. 资源使用顺序
  2. 增加资源
  3. 超时退出资源

Java 内存模型?

Java内存模型定义了多线程之间共享变量的可见性以及如何在需要的时候对共享变量进行同步。

Java内存模型定义了volatile和synchronized的行为,更重要的是保证了同步的java程序在所有的处理器架构下面都能正确的运行。

mage-20180319231811

  1. happens-before规则
  2. 重排序、内存屏障

如何保证多线程下 i++ 结果正确?

synchronized、lock

线程池的种类,区别和使用场景?

  1. newCachedThreadPool:适用于执行很多的短期异步任务的小程序,或者负载比较轻的服务器;是一个根据需要创建线程的线程池
  2. newFixedThreadPool:FixedThreadPool适用于为了满足管理资源的需求,而需要限制当前线程数量的应用场景,它适用于负载比较重的服务器。
  3. newSingleThreadExecutor:适用于需要保证顺序地执行各个任务,并且在任意时间点不会有多个线程在活动的场景。
  4. newScheduledThreadPool:适用于需要在多个后台线程执行周期任务,同时为了满足资源管理需求需要限制后台线程数量的应用场景。

分析线程池的实现原理和线程的调度过程?

worker从队列中不断取任务执行,当任务队列为空时,worker线程阻塞;

线程池如何调优,最大数目如何确认?

取决于任务的类型,CPU密集型可以coreNum + 1;IO密集型可以2coreNum;

ThreadLocal原理,用的时候需要注意什么?

ThreadLocal

CountDownLatch 和 CyclicBarrier 的用法,以及相互之间的差别?

区别:CyclicBarrier可以重复使用,CountDownLatch只能使用一次。

LockSupport工具

LockSupport

Condition接口及其实现原理

底层依赖LockSupport方法

condition

Fork/Join框架的理解

需要通过ForkJoinPool来提交任务。任务一般通过使用ForkJoinTask的子类来实现:

  1. RecursiveAction:用于没有返回结果的任务
  2. RecursiveTask:用于有返回结果的任务
1
2
ForkJoinPool forkJoinPool = newForkJoinPool();
Future result = forkJoinPool.submit(task);

任务的切割:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 分割任务
task.fork();

public final ForkJoinTask<V> fork() {
Thread t;
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
// ForkJoinPool.WorkQueue workQueue,workQueue是ForkJoinPool的全局变量
// 所有分割出来的任务都在一个queue中
((ForkJoinWorkerThread)t).workQueue.push(this);
else
ForkJoinPool.common.externalPush(this);
return this;
}

分段锁的原理,锁力度减小的思考

八种阻塞队列以及各个阻塞队列的特性

ArrayBlockingQueue :由数组结构组成的有界阻塞队列。
LinkedBlockingQueue :由链表结构组成的有界阻塞队列。
PriorityBlockingQueue :支持优先级排序的无界阻塞队列。
DelayQueue:使用优先级队列实现的无界阻塞队列。
SynchronousQueue:不存储元素的阻塞队列。
LinkedTransferQueue:链表结构组成的无界阻塞队列。
LinkedBlockingDeque:链表结构组成的双向阻塞队列。

方法/处理方式 抛出异常 返回特殊值 一直阻塞 超时退出
插入 add(e) offer(e) put(e) offer(e, time, unit)
移除 remove() poll () take() poll(time, unit)
检查 element() peek() 不可用 不可用

如果是无界阻塞队列,队列不可能会出现满的情况,所以使用put或offer方法永远不会被阻塞,而且使用offer方法时,该方法永远返回rue。

实现:

通过Lock和Condition实现,插入市判断容量调用LockSupport的await或signal方法

JVM

详细jvm内存模型

jvm内存模型

讲讲什么情况下回出现内存溢出,内存泄漏?

内存溢出:指程序申请内存时,没有足够的内存空间使用

内存泄漏:指程序申请了内存后(new),用完的内存没有释放(delete),一直被某个或某些实例所持有却不再被使用导致 GC 不能回收

https://www.jianshu.com/p/e97ed5d8a403
关于java内存泄露的总结–引用的类型:强引用,弱引用,软引用

说说Java线程栈

https://blog.csdn.net/hust_superman/article/details/39402087

JVM 年轻代到年老代的晋升过程的判断条件是什么呢?

  1. 大对象直接进入老年代
  2. 存活一定时间的年轻代晋升老年代
  3. 同一年代的对象在monitor gc后,占用内存大于Survivor的二分之一,晋升老年代

JVM 出现 fullGC 很频繁,怎么去线上排查问题?

类加载为什么要使用双亲委派模式,有没有什么场景是打破了这个模式?

http://www.cnblogs.com/lanxuezaipiao/p/4138511.html

解决基础类的统一问题

打破场景:

  1. 线程上下文类加载器(Thread Context Classloader)
  2. OSGI

类的实例化顺序

https://blog.csdn.net/zd836614437/article/details/64126826

https://segmentfault.com/a/1190000004527951

JVM垃圾回收机制,何时触发MinorGC等操作

Minor GC触发条件:当Eden区满时,触发Minor GC。

Full GC触发条件:

(1)调用System.gc时,系统建议执行Full GC,但是不必然执行

(2)老年代空间不足

(3)方法区空间不足

(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存

(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

JVM 中一次完整的 GC 流程(从 ygc 到 fgc)是怎样的

答:对象优先在Eden区中分配,若没有足够空间,Minor GC;
大对象(需要大量连续内存空间)直接进入老年态;长期存活的对象进入老年态。如果对象在新生代出生并经过第一次MGC后仍然存活,年龄+1,若年龄超过一定限制(15),则被晋升到老年态。

https://blog.csdn.net/zd836614437/article/details/64126826

各种回收器,各自优缺点,重点CMS、G1

mage-20180328230626

名称 优点 缺点
Serial client机器上、简单而高效 单线程;STW
ParNew 多线程 STW
Parallel Scavenge 时间可控
Serial Old 单线程;STW
Parallel Old
CMS 占用用户时间少;并发收集、低停顿 CPU资源非常敏感;无法处理浮动垃圾;空间碎片
G1 无空间碎片;低停顿;并发执行;支持大内存; 是的

CMS收集器和G1收集器优缺点
g1 gc

各种回收算法

  1. 标记 - 清除
  2. 标记 - 整理
  3. 复制
  4. 分代收集算法

OOM错误,stackoverflow错误,permgen space错误

https://my.oschina.net/liting/blog/476918

JVM 之 OopMap 和 RememberedSet

OopMap:记录了从栈到堆的引用关系,以避免全栈扫描,加快枚举根节点的速度。它的另外一个更根本的作用是,可以帮助 HotSpot 实现准确式 GC。

RememberSet:记录老年代对象引用新生代对象。

Spring

BeanFactory 和 FactoryBean?

BeanFactory:

BeanFactory,以Factory结尾,表示它是一个工厂类(接口),用于管理Bean的一个工厂。在Spring中,BeanFactory是IOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。

Spring为我们提供了许多易用的BeanFactory实现,XmlBeanFactory就是常用的一个,该实现将以XML方式描述组成应用的对象及对象间的依赖关系。XmlBeanFactory类将持有此XML配置元数据,并用它来构建一个完全可配置的系统或应用。

1
2
3
4
5
6
7
8
9
// No.1
Resource resource = new FileSystemResource("beans.xml");
BeanFactory factory = new XmlBeanFactory(resource);
// No.2
ClassPathResource resource = new ClassPathResource("beans.xml");
BeanFactory factory = new XmlBeanFactory(resource);
// No.3
ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"applicationContext.xml", "applicationContext-part2.xml"});
BeanFactory factory = (BeanFactory) context;

FactoryBean:

实现 FactoryBean 的类表明此类也是一个Bean,类型为工厂Bean(Spring中共有两种bean,一种为普通bean,另一种则为工厂bean)。顾名思义,它也是用来管理Bean的,而它本身由spring管理。

FactoryBean管理的bean实际上也是由spring进行配置、实例化、管理,因此由FactoryBean管理的bean不能再次配置到spring配置文件中(xml、java类配置、注解均不可以),否则会报异常。

Spring IOC 的理解,其初始化过程?

1
2
ApplicationContext appContext = new ClassPathXmlApplicationContext("cjj/models/beans.xml");
Person p = (Person)appContext.getBean("person");

上面代码中,在创建ApplicationContext实例对象过程中会创建一个spring容器,该容器会读取配置文件”cjj/models/beans.xml”,并统一管理由该文件中定义好的所有bean实例对象,如果要获取某个bean实例,使用getBean方法就行了。例如我们只需要将Person提前配置在beans.xml文件中(可以理解为注入),之后我们可以不需使用new Person()的方式创建实例,而是通过容器来获取Person实例,这就相当于将Person的控制权交由spring容器了,差不多这就是控制反转的概念。

Spring中有两个主要的容器系列:

  1. 实现BeanFactory接口的简单容器;
  2. 实现ApplicationContext接口的高级容器。ApplicationContext比较复杂,它不但继承了BeanFactory的大部分属性,还继承其它可扩展接口,扩展的了许多高级的属性。

初始化过程

  1. Resource定位(Bean的定义文件定位)
  2. 将Resource定位好的资源载入到BeanDefinition
  3. 将BeanDefiniton注册到容器中

mage-20180320234405

容器初始化的时候会预先对单例和非延迟加载的对象进行预先初始化。其他的都是延迟加载是在第一次调用getBean 的时候被创建。

第一步 Resource定位

可以通过先获取resource,再获取beanFactory

1
2
Resource resource = new FileSystemResource("beans.xml");
BeanFactory factory = new XmlBeanFactory(resource);

  FileSystemResource:以文件的绝对路径方式进行访问资源,效果类似于Java中的File;
  ClassPathResourcee:以类路径的方式访问资源,效果类似于this.getClass().getResource(“/“).getPath();
  ServletContextResource:web应用根目录的方式访问资源,效果类似于request.getServletContext().getRealPath(“”);
  UrlResource:访问网络资源的实现类。例如file: http: ftp:等前缀的资源对象;
  ByteArrayResource: 访问字节数组资源的实现类。

也可以直接创建applicationContext对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
//super方法为容器设置好Bean资源加载器
//该方法最终会调用到AbstractApplicationContext的无参构造方法
//这里会默认设置解析路径的模式为Ant-style
super(parent);
//设置Bean定义资源文件的路径
setConfigLocations(configLocations);
if (refresh) {
//调用容器的refresh,载入BeanDefinition的入口
refresh();
}
}

注:ApplicationContext的所有实现类都实现RecourceLoader接口,因此可以直接调用getResource(参数)获取Resoure对象不同的ApplicatonContext实现类使用getResource方法取得的资源类型不同,例如:FileSystemXmlApplicationContext.getResource获取的就是FileSystemResource实例;ClassPathXmlApplicationContext.gerResource获取的就是ClassPathResource实例;XmlWebApplicationContext.getResource获取的就是ServletContextResource实例,另外像不需要通过xml直接使用注解@Configuation方式加载资源的AnnotationConfigApplicationContext等等。

第二步 通过返回的resource对象,进行BeanDefinition的载入

总之,BeanDefinition相当于一个数据结构,这个数据结构的生成过程是根据定位的resource资源对象中的bean而来的,这些bean在Spirng IoC容器内部表示成了的BeanDefintion这样的数据结构,IoC容器对bean的管理和依赖注入的实现都是通过操作BeanDefinition来进行的。

1.构造BeanFactory时,首先调用的是BeanDefinitionReader类型的reader属性的loadBeanDefinitions()方法,是整个资源加载的切入点。

  • 封装资源文件:当进入BeanDefinitionReader后首先对参数Resource进行EncodedResource类进行封装
  • 获取输入流:从Resource中获取InputStream并构造InputSource
  • 通过构造器的InputSource实例和Resource实例继续调用loadBeanDefinitions.

2.loadBeanDefinition调用doLoadBeanDefinitons方法,完成以下三个方法

  • 对XML文档的验证模式
  • 用DocumentLoader处理资源文件,生成Document
  • 根据返回的Document信息注册bean信息

第三步,将BeanDefiniton注册到容器中

最终Bean配置会被解析成BeanDefinition并与beanName,Alias一同封装到BeanDefinitionHolder中,之后beanFactory.registerBeanDefinition(beanName, bdHolder.getBeanDefinition()),注册到DefaultListableBeanFactory.beanDefinitionMap中。之后客户端如果要获取Bean对象,Spring容器会根据注册的BeanDefinition信息进行实例化。

参考1 参考2

BeanFactory 和 ApplicationContext?

ApplicationContext 继承BeanFactory,添加了一些属性和方法。

  • BeanFactory容器中,不会调用ApplicationContextAware接口的setApplicationContext()方法,
  • BeanPostProcessor接口的postProcessBeforeInitialzation()方法和postProcessAfterInitialization()方法不会自动调用,必须自己通过代码手动注册
  • BeanFactory容器启动的时候,不会去实例化所有Bean,包括所有scope为singleton且非懒加载的Bean也是一样,而是在调用的时候去实例化。

Spring Bean 的生命周期,如何被管理的?

https://www.jianshu.com/p/3944792a5fff

Spring Bean 的加载过程是怎样的?

https://segmentfault.com/a/1190000012887776

http://www.cnblogs.com/xrq730/p/6285358.html

如果要你实现Spring AOP,请问怎么实现?

如果要你实现Spring IOC,你会注意哪些问题?

Spring 是如何管理事务的,事务管理机制?

  1. 编程式事务管理
    1. TransactionDefinition
    2. PlatformTransactionManager
    3. TransactionStatus
  2. 声明式事务管理(AOP和IOC)
    1. DataSource
    2. TransactionManager

https://blog.csdn.net/justloveyou_/article/details/73733278

Spring 的不同事务传播行为有哪些,干什么用的?

浅析Spring事务传播行为

Spring中事务的隔离级别

[MySQL事务隔离级别和Spring事务关系介绍

Spring 中用到了那些设计模式?

https://www.cnblogs.com/yuefan/p/3763898.html

http://www.uml.org.cn/j2ee/201301074.asp

设计模式之创建型模式

Spring MVC 的工作原理?

SpringMVC工作原理

Spring 循环注入的原理?

Spring AOP的理解,各个术语,他们是怎么相互工作的?

分离业务逻辑和系统逻辑;
有各种各样的常见的很好的方面的例子,如日志记录、审计、声明式事务、安全性和缓存等

原理:jdk动态代理、cglib动态代理
与AspectJ的静态代理不同,Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

https://wiki.jikexueyuan.com/project/spring/aop-with-spring.html
http://www.importnew.com/24305.html

Spring 如何保证 Controller 并发的安全?

https://blog.csdn.net/hejingyuan6/article/details/50363647

http://www.cnblogs.com/duanxz/p/5051916.html

Netty

BIO、NIO和AIO

Netty 的各大组件

【死磕Netty】—–Netty的核心组件

Netty的线程模型

Netty 源码分析之 三 我就是大名鼎鼎的 EventLoop(一)
Netty概述

TCP 粘包/拆包的原因及解决方法

TCP粘包,拆包及解决方法
Netty概述

了解哪几种序列化协议?包括使用场景和如何去选择

影响序列化性能的关键因素:
1、序列化后的码流大小(网络带宽的占用);
2、序列化的性能(CPU资源占用);
3、是否支持跨语言(异构系统的对接和开发语言切换)。

1、json
2、xml
3、Protobuf
4、Avro
5、Thrift

Netty概述

Netty的零拷贝实现

Netty的零拷贝体现在三个方面:

  1. Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。
  2. Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象,用户可以像操作一个Buffer那样方便的对组合Buffer进行操作,避免了传统通过内存拷贝的方式将几个小Buffer合并成一个大的Buffer。
  3. Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题。

netty相关概念

Netty的高性能表现在哪些方面

1、并发高
nio、reactor模型
2、传输快
zero copy
3、封装好
api简单

Netty入门教程——认识Netty

分布式相关

Zookeeper的用途,选举的原理是什么?

zookeeper原理和适用场景

zookeeper watch机制

zookeeper的选举策略

redis/zk节点宕机如何处理

如何做一个分布式锁

Dubbo的底层实现原理和机制

Dubbo的服务请求失败怎么处理

描述一个服务从发布到被消费的详细过程

分布式系统怎么做服务治理

接口的幂等性的概念

重连机制会不会造成错误

如何实现负载均衡,有哪些算法可以实现?

用过哪些MQ,怎么用的,和其他mq比较有什么优缺点,MQ的连接是线程安全的吗

消息中间件如何解决消息丢失问题

MQ系统的数据如何保证不丢失

数据的垂直拆分水平拆分。

列举出你能想到的数据库分库分表策略;分库分表后,如何解决全表查询的问题

对分布式事务的理解

分布式集群下如何做到唯一序列号

全局ID

数据库

mysql分页有什么优化

https://blog.csdn.net/bingduanlbd/article/details/51767850

https://my.oschina.net/No5stranger/blog/158202

两阶段锁协议

https://www.cnblogs.com/zszmhd/p/3365220.html

悲观锁、乐观锁

悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。

乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。 乐观锁不能解决脏读的问题。

MySQL 乐观锁与悲观锁

深入理解乐观锁与悲观锁

MySQL InnoDB中使用悲观锁
要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交。set autocommit=0;

组合索引,最左原则

mysql索引及查询优化

mysql 的表锁、行锁

行锁:
使用select…for update会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB默认行级锁。行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,这点需要注意。

mysql 性能优化

1、建索引
2、查询优化
3、数据类型(比如用ENUM代替VARCHAR等)

mysql索引及查询优化

mysql的索引分类:B+,hash;什么情况用什么索引

MySQL索引背后的数据结构及算法原理

事务的特性和隔离级别

  1. 原子性
  2. 一致性
  3. 持久性
  4. 隔离性
  1. Serializable(串行化):可避免脏读、不可重复读、幻读的发生。(级别最高)
  2. Repeatable-read(可重复读):可避免脏读、不可重复读的发生。
  3. Read-committed(读已提交):可避免脏读的发生。
  4. Read-uncommitted(读未提交):最低级别,任何情况都无法保证。(级别最低)

https://blog.csdn.net/lamp_yang_3533/article/details/79344736

http://blog.sina.com.cn/s/blog_8020e4110101bfc6.html

Redis

Redis用过哪些数据数据,以及Redis底层怎么实现

String: int、SDS
list: linkedlist、ziplist
map: hashtable、ziplist
set: hashtable、intset
sorted-set:ziplist、skiplist + hashtable

Redis缓存穿透,缓存雪崩

redis缓存

如何使用Redis来实现分布式锁

  1. setNX、getSET、get
  2. redlock

Redis的并发竞争问题如何解决

Redis的并发通过队列模式,编程串行模式执行

客户端实现:

  1. synchronized

redis服务端实现:

  1. setNX锁
  2. watch + 事务

Redis持久化的几种方式,优缺点是什么,怎么实现的

AOF:丢失数据少;文件大、恢复慢、有bug;
RDB:文件小、恢复快;丢失数据多
redis持久化

Redis的缓存过期策略

Redis的缓存过期策略

Redis集群,高可用,原理

Redis缓存分片

优点

  • 性能的提升,单机Redis的网络I/O能力和计算资源是有限的,将请求分散到多台机器,充分利用多台机器的计算能力可网络带宽,有助于提高Redis总体的服务能力。
  • 存储的横向扩展,即使Redis的服务能力能够满足应用需求,但是随着存储数据的增加,单台机器受限于机器本身的存储容量,将数据分散到多台机器上存储使得Redis服务可以横向扩展。

缺点

  • 多键操作是不被支持的,比如我们将要批量操作的键被映射到了不同的Redis实例中。
  • 多键的Redis事务是不被支持的。
  • 分区的最小粒度是键,因此我们不能将关联到一个键的很大的数据集映射到不同的实例。
  • 当应用分区的时候,数据的处理是非常复杂的,比如我们需要处理多个rdb/aof文件,将分布在不同实例的文件聚集到一起备份。
  • 添加和删除机器是很复杂的,例如Redis集群支持几乎运行时透明的因为增加或减少机器而需要做的rebalancing,然而像客户端和代理分区这种方式是不支持这种功能的。

分布方式

  • 范围分区
  • hash分区

Pre-Sharding:
我们可以开启多个Redis实例,尽管是一台物理机器,我们在刚开始的时候也可以开启多个实例。我们可以从中选择一些实例,比如32或64个实例来作为我们的工作集群。当一台物理机器存储不够的时候,我们可以将一般的实例移动到我们的第二台物理机上,依次类对,我们可以保证集群中Redis的实例数不变,又可以达到扩充机器的目的。

Redis分区实现原理

Redis的数据淘汰策略

volatile-lru -> Evict using approximated LRU among the keys with an expire set.
allkeys-lru -> Evict any key using approximated LRU.
volatile-lfu -> Evict using approximated LFU among the keys with an expire set.
allkeys-lfu -> Evict any key using approximated LFU.
volatile-random -> Remove a random key among the ones with an expire set.
allkeys-random -> Remove a random key, any key.
volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
noeviction -> Don’t evict anything, just return an error on write operations.

Redis数据淘汰机制

redis的过期策略如何实现

redis的过期策略如何实现

缓存

如何保证缓存与数据库的双写一致性

如何保证缓存与数据库的双写一致性

Kafka

为什么Kafka不支持读写分离

为什么Kafka不支持读写分离

消息传递模型

为什么Kafka不支持读写分离

Push vs. Pull

对于消息的消费,ActiveMQ使用PUSH模型,而Kafka使用PULL模型,两者各有利弊,对于PUSH,broker很难控制数据发送给不同消费者的速度,而PULL可以由消费者自己控制,但是PULL模型可能造成消费者在没有消息的情况下盲等,这种情况下可以通过long polling机制缓解,而对于几乎每时每刻都有消息传递的流式系统,这种影响可以忽略。

消息持久化

很多系统、组件为了提升效率一般恨不得把所有数据都扔到内存里,然后定期flush到磁盘上;可实际上,现代操作系统也是这样,所有的现代操作系统都乐于将空闲内存转作磁盘缓存(页面缓存),想不用都难;对于这样的系统,他的数据在内存中保存了一份,同时也在OS的页面缓存中保存了一份,这样不但多了一个步骤还让内存的使用率下降了一半;因此,Kafka决定直接使用页面缓存;但是随机写入的效率很慢,为了维护彼此的关系顺序还需要额外的操作和存储,而线性的写入可以避免这些,实际上,线性写入(linear write)的速度大约是300MB/秒,但随即写入却只有50k/秒,其中的差别接近10000倍。这样,Kafka以页面缓存为中间的设计在保证效率的同时还提供了消息的持久化,每个消费者自己维护当前读取数据的offser(也可委托给zookeeper),以此可同时支持在线和离线的消费。

消息投递可靠性

一个消息如何算投递成功,Kafka提供了三种模式:

  • 第一种是啥都不管,发送出去就当作成功,这种情况当然不能保证消息成功投递到broker;
  • 第二种是Master-Slave模型,只有当Master和所有Slave都接收到消息时,才算投递成功,这种模型提供了最高的投递可靠性,但是损伤了性能;
  • 第三种模型,即只要Master确认收到消息就算投递成功;实际使用时,根据应用特性选择,绝大多数情况下都会中和可靠性和性能选择第三种模型

消息在broker上的可靠性,因为消息会持久化到磁盘上,所以如果正常stop一个broker,其上的数据不会丢失;但是如果不正常stop,可能会使存在页面缓存来不及写入磁盘的消息丢失,这可以通过配置flush页面缓存的周期、阈值缓解,但是同样会频繁的写磁盘会影响性能,又是一个选择题,根据实际情况配置。

消息消费的可靠性,Kafka提供的是“At least once”模型,因为消息的读取进度由offset提供,offset可以由消费者自己维护也可以维护在zookeeper里,但是当消息消费后consumer挂掉,offset没有即时写回,就有可能发生重复读的情况,这种情况同样可以通过调整commit offset周期、阈值缓解,甚至消费者自己把消费和commit offset做成一个事务解决,但是如果你的应用不在乎重复消费,那就干脆不要解决,以换取最大的性能。

Kafka服务器能接收到的最大信息是多少

Kafka服务器可以接收到的消息的最大大小是1000000字节。

解释Kafka的Zookeeper是什么?我们可以在没有Zookeeper的情况下使用Kafka吗?

Zookeeper是一个开放源码的、高性能的协调服务,它用于Kafka的分布式应用。不,不可能越过Zookeeper,直接联系Kafka broker。一旦Zookeeper停止工作,它就不能服务客户端请求。

  • Zookeeper主要用于在集群中不同节点之间进行通信
  • 在Kafka中,它被用于提交偏移量,因此如果节点在任何情况下都失败了,它都可以从之前提交的偏移量中获取
  • 除此之外,它还执行其他活动,如: leader检测、分布式同步、配置管理、识别新节点何时离开或连接、集群、节点实时状态等等。

原文 原文