导图社区 后端技术面试题目
此处整合网上各处面试题,主要来源https://github.com/gsjqwyl/JavaInterview,包括知乎上一些面经,如有侵权,请联系删除。
编辑于2021-05-09 22:21:20面试题目
JAVA
JavaSE核心
基础
基本语法
匿名内部类是什么?如何访问在其外面定义的变量?
匿名内部类也就是没有名字的内部类,匿名内部类只能使用一次,它通常用来简化代码编写。
匿名内部类只能访问外部类的Final变量. Java 8更加智能:如果局部变量被匿名内部类访问,那么该局部变量相当于自动使用了final修饰
private修饰的方法可以通过反射访问,那么private的意义是什么
首先private的存在并不是为了实现Java程序的完全安全,Java的安全机制是层层相扣、十分复杂的。private只是为了Java的正常开发过程中的一种约束,也是符合OOP(面向对象编程)的封装,实现内部的部分不可见。就好比超市仓库会挂一个牌子“闲人免进”,但是如果非要进去的话也不是不行。而且在java开发过程中有时候确实会需要用的反射机制的功能,比如测试/性能等场景下
fanal fanally fanalize 区别
final用于声明属性,方法和类,分别表示属性不可交变,方法不可覆盖,类不可继承。
finally是异常处理语句结构的一部分,表示总是执行。
finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,供垃圾收集时的其他资源回收,例如关闭文件等
局部变量使用前需要显式地赋值,否则编译通过不了,为什么这么设计
其赋成员变量值和取值的顺序具有不确定性,可以在方法调用(取值)前赋值,也可以在方法调用(取值)后赋值,其具体顺序是在运行时发生的,编译器确定不了。而局部变量,赋值、取值顺序确定。所以局部变量赋值使用,是一种设计约束,减少犯错的可能性。如果局部变量默认赋值了,可能会有很多忘记赋值的,从而出现不可预期的情况。
不用final还可以用什么办法使得这个类不被继承
让普通类不可被继承有两种方法:一种是通过final关键字;第二种是私有化构造器,让子类无可用的“父类”构造器可用,也就无法继承。
而java中内部类却可以打破上述第二种方法中所描述的限制,因此想要内部类之间不能被继承的话,只有一种方法:final关键字
子主题
String a = "ab"; String b = "a" + "b"; a == b 是否相等,为什么
相等,String变量池内,相同字符串指向同一个地址
final修饰类能继承吗
不能
java初始化的顺序
静态变量->静态初始化块->变量->初始化块->构造器
JIT
JIT编译器(just in time 即时编译器),当虚拟机发现某个方法或代码块运行特别频繁时,就会把这些代码认定为(Hot Spot Code 热点代码,为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各层次的优化,完成这项任务的正是JIT编译器
https://www.yuque.com/go/doc/33512966
有没有阅读过序列化之后的数据
1.什么是序列化和反序列化?
Java序列化是指把Java对象转换为字节序列的过程;
Java反序列化是指把字节序列恢复为Java对象的过程;
2. 为什么要序列化?
其实我们的对象不只是存储在内存中,它还需要在传输网络中进行传输,并且保存起来之后下次再加载出来,这时候就需要序列化技术。
一般Java对象的生命周期比Java虚拟机端,而实际开发中如果需要JVM停止后能够继续持有对象,则需要用到序列化技术将对象持久化到磁盘或数据库。
在多个项目进行RPC调用时,需要在网络上传输JavaBean对象,而网络上只允许二进制形式的数据进行传输,这时则需要用到序列化技术。 Java的序列化技术就是把 对象转换成一串由二进制字节组成的数组 ,然后将这二进制数据保存在磁盘或传输网络。而后需要用到这对象时,磁盘或者网络接收者可以通过反序列化得到此对象,达到 对象持久化 的目的。
Java Serialization
16进制编码
Fastjson
JSON格式字符串
Java bin包下面的工具用过哪些?
javac 编译工具
java工具用于启动一个java应用程序,这个java应用程序必须要与入口函数main方法
jar 这个工具用于打包、更新、解包java应用程序。实际开发中,我们都会将应用程序打包成一个jar包来运行,jar会将全部class文件以及资源文件,配置文件放到一个文件里打个包(有的说是压缩,事实上这个工具仅仅是打包,并不包含压缩操作)
javadoc是JDK提供给程序员的一个文档生成工具
javap是一个反编译工具,也可以查看class文件中的信息
jdb是一个断点工具,类似于gdb
jps用于查看运行的JVM实例以及进程号
jstat用于查看运行的JVM实例的运行数据
jvisualvm是JDK提供的图形化jstat工具
jinfo用于打印特定JVM实例的配置信息
jmap用于查看JVM的so对象内存占用情况或指定的JVM实例堆内存情况
jhat和 jmap配合使用,通过jmap生成的dump文件可以通过jhat解析浏览
jstack用于打印指定进程的调用堆栈信息
switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上
在 Java 5 以前,switch(expr)中,expr 只能是 byte、short、char、int。从 Java5 开始,Java 中引入了枚举类型,expr 也可以是 enum 类型,从 Java 7 开始,expr 还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。
用最有效率的方法计算 2 乘以 8
2 << 3(左移 3 位相当于乘以 2 的 3 次方,右移 3 位相当于除以 2 的 3 次方)。
short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗
对于 short s1 = 1; s1 = s1 + 1;由于 1 是 int 类型,因此 s1+1 运算结果也是 int型,需要强制转换类型才能赋值给 short 型。
而 short s1 = 1; s1 += 1;可以正确编译,因为 s1+= 1;相当于 s1 = (short(s1 + 1);其中有隐含的强制类型转换。
抽象类能使用 final 修饰吗?
不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类
equals与==的区别
== 比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同
equals用来比较的是两个对象的内容是否相等,由于所有的类都是继承自java.lang.Object类的,所以适用于所有对象,如果没有对该方法进行覆盖的话,调用的仍然是Object类中的方法,而Object中的equals方法返回的却是==的判断
String、String StringBuffer 和 StringBuilder 的区别是什么?以及什么场景使用哪个类型
可变性 String类中使用字符数组保存字符串,private final char value[],所以string对象是不可变的。StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[] value,这两种对象都是可变的。 线程安全性 String中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。 性能 每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StirngBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
对于三者使用的总结 如果要操作少量的数据用 = String 单线程操作字符串缓冲区 下操作大量数据 = StringBuilder 多线程操作字符串缓冲区 下操作大量数据 = StringBuffer
Java的四种引用,强弱软虚
强引用
强引用是平常中使用最多的引用,强引用在程序内存不足(OOM)的时候也不会被回收,使用方式:String str = new String("str");
软引用
软引用在程序内存不足时,会被回收,使用方式: // 注意:wrf这个引用也是强引用,它是指向SoftReference这个对象的, // 这里的软引用指的是指向new String("str")的引用,也就是SoftReference类中T SoftReference<String> wrf = new SoftReference<String>(new String("str")); 可用场景: 创建缓存的时候,创建的对象放进缓存中,当内存不足时,JVM就会回收早先创建的对象
弱引用
弱引用就是只要JVM垃圾回收器发现了它,就会将之回收,使用方式:WeakReference<String> wrf = new WeakReference<String>(str); 可用场景: Java源码中的java.util.WeakHashMap中的key就是使用弱引用,我的理解就是,一旦我不需要某个引用,JVM会自动帮我处理它,这样我就不需要做其它操作。
虚引用
虚引用的回收机制跟弱引用差不多,但是它被回收之前,会被放入ReferenceQueue中。注意哦,其它引用是被JVM回收后才被传入ReferenceQueue中的。由于这个机制,所以虚引用大多被用于引用销毁前的处理工作。还有就是,虚引用创建的时候,必须带有ReferenceQueue,使用例子: PhantomReference<String> prf = new PhantomReference<String>(new String("str"), new ReferenceQueue<>()); 可用场景: 对象销毁前的一些操作,比如说资源释放等。**Object.finalize()虽然也可以做这类动作,但是这个方式即不安全又低效
Java 中 WeakReference 与 SoftReference的区别?
软引用在程序内存不足时,会被回收,弱引用就是只要JVM垃圾回收器发现了它,就会将之回收
有没有可能两个不相等的对象有相同的hashcode
有可能.在产生hash冲突时,两个不相等的对象就会有相同的 hashcode 值.当hash冲突产生时
深拷贝和浅拷贝的区别是什么?
浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
深拷贝:被复制对象的所有变量都含有与原来的对象相同的值.而那些引用其他对象的变量将指向被复制过的新对象.而不再是原有的那些被引用的对象.换言之.深拷贝把要复制的对象所引用的对象都复制了一遍.
示意图
a=a+b与a+=b有什么区别吗?
+=操作符会进行隐式自动类型转换,此处a+=b隐式的将加操作的结果类型强制转换为持有结果的类型,而a=a+b则不会自动进行类型转换.如: byte a = 127; byte b = 127; b = a + b; // 报编译错误:cannot convert from int to byte b += a; 以下代码是否有错,有的话怎么改? short s1= 1; s1 = s1 + 1; 有错误.short类型在进行运算时会自动提升为int类型,也就是说s1+1的运算结果是int类型,而s1是short类型,此时编译器会报错. 正确写法: short s1= 1; s1 += 1; +=操作符会对右边的表达式结果强转匹配左边的数据类型,所以没错.
try catch finally,try里有return,finally还执行么?
执行,并且finally的执行早于try里面的return
finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
不管有木有出现异常,finally块中代码都会执行;
finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;
为什么 Java 中只有值传递
值传递和引用传递有什么区别
值传递:指的是在方法调用时,传递的参数是按值的拷贝传递,传递的是值的拷贝,也就是说传递后就互不相关了。 引用传递:指的是在方法调用时,传递的参数是按引用进行传递,其实传递的引用的地址,也就是变量所对应的内存空间的地址。传递的是值的引用,也就是说传递前和传递后都指向同一个引用(也就是同一个内存空间)。
String有哪些特性
不变性:String 是只读字符串,是一个典型的 immutable 对象,对它进行任何操作,其实都是创建一个新的对象,再把引用指向该对象。不变模式的主要作用在于当一个对象需要被多线程共享并频繁访问时,可以保证数据的一致性。 常量池优化:String 对象创建之后,会在字符串常量池中进行缓存,如果下次创建同样的对象时,会直接返回缓存的引用。 final:使用 final 来定义 String 类,表示 String 类不能被继承,提高了系统的安全性。
String为什么是不可变的吗?
简单来说就是String类利用了final修饰的char类型数组存储字符
String真的是不可变的吗?
按照正常的使用,是不可变的,但是可以通过反射修改String里面的value
是否可以继承 String 类
String 类是 final 类,不可以被继承。
String str="i"与 String str=new String(“i”)一样吗?
不一样,因为内存的分配方式不一样。String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。
String s = new String(“xyz”);创建了几个字符串对象
两个对象,一个是静态区的"xyz",一个是用new创建在堆上的对象。
如何将字符串反转?
使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。
String 类的常用方法都有那些?
indexOf():返回指定字符的索引。 charAt():返回指定索引处的字符。 replace():字符串替换。 trim():去除字符串两端空白。 split():分割字符串,返回一个分割后的字符串数组。 getBytes():返回字符串的 byte 类型数组。 length():返回字符串长度。 toLowerCase():将字符串转成小写字母。 toUpperCase():将字符串转成大写字符。 substring():截取字符串。 equals():字符串比较。
自动装箱与拆箱
装箱:将基本类型用它们对应的引用类型包装起来; 拆箱:将包装类型转换为基本数据类型;
Integer a= 127 与 Integer b = 127相等吗
对于对象引用类型:==比较的是对象的内存地址。 对于基本数据类型:==比较的是值。 如果整型字面量的值在-128到127之间,那么自动装箱时不会new新的Integer对象,而是直接引用常量池中的Integer对象,超过范围 a1==b1的结果是false
详细代码
public static void main(String[] args) { Integer a = new Integer(3); Integer b = 3; // 将3自动装箱成Integer类型 int c = 3; System.out.println(a == b); // false 两个引用没有引用同一对象 System.out.println(a == c); // true a自动拆箱成int类型再和c比较 System.out.println(b == c); // true Integer a1 = 128; Integer b1 = 128; System.out.println(a1 == b1); // false Integer a2 = 127; Integer b2 = 127; System.out.println(a2 == b2); // true }
字符型常量和字符串常量的区别?
形式 : 字符常量是单引号引起的一个字符,字符串常量是双引号引起的 0 个或若干个字符 含义 : 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置) 占内存大小 : 字符常量只占 2 个字节; 字符串常量占若干个字节 (注意: char 在 Java 中占两个字节), 字符封装类 Character 有一个成员常量 Character.SIZE 值为 16,单位是bits,该值除以 8(1byte=8bits)后就可以得到 2 个字节
Java 泛型了解么?
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
什么是类型擦除?
Java 的泛型是伪泛型,这是因为 Java 在编译期间,所有的泛型信息都会被擦掉,这也就是通常所说类型擦除 。
介绍一下常用的通配符?
常用的通配符为: T,E,K,V,?
? 表示不确定的 java 类型
T (type) 表示具体的一个 java 类型
K V (key value) 分别代表 java 键值中的 Key Value
E (element) 代表 Element
为什么说 Java 语言“编译与解释并存”?
高级编程语言按照程序的执行方式分为编译型和解释型两种。简单来说,编译型语言是指编译器针对特定的操作系统将源代码一次性翻译成可被该平台执行的机器码;解释型语言是指解释器对源程序逐行解释成特定平台的机器码并立即执行。比如,你想阅读一本英文名著,你可以找一个英文翻译人员帮助你阅读, 有两种选择方式,你可以先等翻译人员将全本的英文名著(也就是源码)都翻译成汉语,再去阅读,也可以让翻译人员翻译一段,你在旁边阅读一段,慢慢把书读完。 Java 语言既具有编译型语言的特征,也具有解释型语言的特征,因为 Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要先经过编译步骤,生成字节码(\*.class 文件),这种字节码必须由 Java 解释器来解释执行。因此,我们可以认为 Java 语言编译与解释并存。
面向对象
Java为什么说是面向对象的?
java是一门面向对象的语言,那对面向对象和面向过程不是很清楚的请看看下面的内容,说不定对你有帮助: 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
如何通俗易懂地举例说明「面向对象」和「面向过程」有什么区别? - 无缺草的回答 - 知乎 https://www.zhihu.com/question/27468564/answer/757646167
面向对象的三大特性
多态
指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
多态分为编译时多态和运行时多态。其中编辑时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。
封装
把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。
继承
是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。
Java的三大特性?如果说有两个方法,同名同参数但不同返回值,问是重载吗?
继承、多态、封装
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求,不能根据返回类型进行区分。
Java面向对象特性介绍一下,类能否多继承?子类继承父类,会继承父类的所有属性吗?
单继承
继承所有非私有的方法和属性
面向对象五大基本原则是什么
单一职责原则SRP(Single Responsibility Principle)
类的功能要单一,不能包罗万象,跟杂货铺似的
开放封闭原则OCP(Open-Close Principle)
一个模块对于拓展是开放的,对于修改是封闭的,想要增加功能热烈欢迎,想要修改,哼,一万个不乐意
里式替换原则LSP(the Liskov Substitution Principle LSP)
子类可以替换父类出现在父类能够出现的任何地方。比如你能代表你爸去你姥姥家干活
依赖倒置原则DIP(the Dependency Inversion Principle DIP)
高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。就是你出国要说你是中国人,而不能说你是哪个村子的。比如说中国人是抽象的,下面有具体的xx省,xx市,xx县。你要依赖的抽象是中国人,而不是你是xx村的。
接口分离原则ISP(the Interface Segregation Principle ISP)
设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好。就比如一个手机拥有打电话,看视频,玩游戏等功能,把这几个功能拆分成不同的接口,比在一个接口里要好的多。
重载和重写
重载(Overload)和重写(Override)的区别
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分
重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏代换原则);如果父类方法访问修饰符为private则子类中就不是重写。
重载入参为object和String,调用方法并且入参是字符串的时候,会调用哪个方法
会调用String,调用形参更具体(子类)的方法
反射
java反射讲一下
JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。
反射机制优缺点
优点:运行期类型的判断,动态加载类,提高代码灵活度。
缺点:1 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的 java 代码要慢很多。2安全问题,让我们可以动态操作改变类的属性同时也增加了类的安全隐患。
Class.forName(name)
Java获取反射的三种方法
1.通过new对象实现反射机制 2.通过路径实现反射机制 3.通过类名实现反射机制
注解
@Autowired放在接口上,如何找到实现类(通过类名)
@Autowired先按类型找,然后再按id为属性名去找 他会帮你按UserService的类型去容器中找唯一bean对象 容器没有该类型的对象:报错 容器中有该类型的唯一bean对象,就将该唯一bean对象赋值给该属性 容器中有多个【两个及以上】该类型的唯一bean对象, 它会再根据该属性名去容器中找, 看看容器中的哪个bean对象的id值和该属性名一致, 如果有,就将容器中该对象赋值给该属性,如果没有报错。 然后通过多态的向上转型就赋值成功。等价于之前手动赋值
注解可以继承吗?
@ResponseBody @RequestBody的作用
@Responsebody 注解表示该方法的返回的结果直接写入 HTTP 响应正文(ResponseBody)中,一般在异步获取数据时使用,通常是在使用 @RequestMapping 后,返回值通常解析为跳转路径,加上 @Responsebody 后返回结果不会被解析为跳转路径,而是直接写入HTTP 响应正文中。 该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。
@RequestBody 注解则是将 HTTP 请求正文插入方法中,使用适合的 HttpMessageConverter 将请求体写入某个对象。
数据类型
Java有哪些数据类型
float f=3.4;是否正确
不正确。3.4 是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换float f =(float)3.4; 或者写成 float f =3.4F;
浮点数为什么会精度不准(浮点数的二进制表示)
浮点数的小数部分转换二进制存储的方式是:将小数乘以2,取整数部分作为二进制的值,然后再将小数乘以2(进制数),再取整数部分,以此往复循环,直到小数为0。按照这个逻辑,会出现无限循环的情况,比如0.1转换之后为0001 1001 1001 … float会保留23位有效部分,故实际上并不能精确的表示0.1
Float和Double区别
单精度浮点数bai占用4个字节(32位)存储空间来存储一个浮点数,包括符号位1位,阶码8位,尾数23位。
而双精度浮点数使用 8个字节(64位)存储空间来存储一个浮点数,包括符号位1位,阶码11位,尾数52位。
基础类型与包装类有什么区别?
包装类是对象,对象分配在堆中,基本类型分配在常量池中
代理
Java proxy和cglib的区别(使用场景和限制)
https://www.yuque.com/go/doc/32879934
异常
RuntimeException和非RuntimeException?各举几个例子?比方说文件读写的时候会有什么异常?怎么实现序列化?除了Java原生序列化方法,序列化还有什么格式?
Exception :受检查的异常,这种异常是强制我们catch或throw的异常。你遇到这种异常必须进行catch或throw,如果不处理,编译器会报错。比如:IOException。
RuntimeException:运行时异常,这种异常我们不需要处理,完全由虚拟机接管。比如我们常见的NullPointerException,我们在写程序时不会进行catch或throw。
RuntimeException也是继承自Exception的,只是虚拟机对这两种异常进行了区分
OOM你遇到过哪些情况,SOF你遇到过哪些情况
OutOfMemoryError异常
除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError(OOM)异常的可能。 1.Java Heap 溢出: 一般的异常信息:java.lang.OutOfMemoryError:Java heap spacess。 java堆用于存储对象实例,我们只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,就会在对象数量达到最大堆容量限制后产生内存溢出异常。 出现这种异常,一般手段是先通过内存映像分析工具(如Eclipse Memory Analyzer)对dump出来的堆转存快照进行分析,重点是确认内存中的对象是否是必要的,先分清是因为内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。 如果是内存泄漏,可进一步通过工具查看泄漏对象到GCRoots的引用链。于是就能找到泄漏对象是通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收。 如果不存在泄漏,那就应该检查虚拟机的参数(-Xmx与-Xms)的设置是否适当。 2,虚拟机栈和本地方法栈溢出 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常 这里需要注意当栈的大小越大可分配的线程数就越少。 3,运行时常量池溢出 异常信息:java.lang.OutOfMemoryError:PermGenspace 如果要向运行时常量池中添加内容,最简单的做法就是使用String.intern()这个Native方法。该方法的作用是:如果池中已经包含一个等于此String的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。由于常量池分配在方法区内,我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池的容量。 4,方法区溢出 方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。也有可能是方法区中保存的class对象没有被及时回收掉或者class信息占用的内存超过了我们配置。 异常信息:java.lang.OutOfMemoryError:PermGenspace 方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收,判定条件是很苛刻的。在经常动态生成大量Class的应用中,要特别注意这点。
堆栈溢出StackOverflow
StackOverflowError 的定义:当应用程序递归太深而发生堆栈溢出时,抛出该错误。 因为栈一般默认为1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1m而导致溢出。 栈溢出的原因:递归调用,大量循环或死循环,全局变量是否过多,数组、List、map数据过大。
throw 和 throws 的区别是什么?
Java 中的异常处理除了包括捕获异常和处理异常之外,还包括声明异常和拋出异常,可以通过 throws 关键字在方法上声明该方法要拋出的异常,或者在方法内部通过 throw 拋出异常对象。 throws 关键字和 throw 关键字在使用上的几点区别如下: throw 关键字用在方法内部,只能用于抛出一种异常,用来抛出方法或代码块中的异常,受查异常和非受查异常都可以被抛出。 throws 关键字用在方法声明上,可以抛出多个异常,用来标识该方法可能抛出的异常列表。一个方法用 throws 标识了可能抛出的异常列表,调用该方法的方法中必须包含可处理异常的代码,否则也要在方法签名中用 throws 关键字声明相应的异常。
集合框架
综合
平常有用哪些集合类?Concurrent包有用吗?
ArrowyLlist、HashMap、CurrentHashMap等
JUC包中包含并发相关类,包括AtomicInteger、ReentrantLock等
如何实现synchronized一样的效果?
使用ReentrantLock?
并发容器有哪些,并发容器和同步容器的区别
同步容器:Vector、Stack、HashTable 并发容器:ConcurrentHashMap、ConcurrentLinkedDeque、CopyOnWriteArrayList
并发容器和同步容器
同步容器也叫线程安全容器,是通过syncrhoized关键字对线程不安全的操作进行加锁来保证线程安全的 其中同步容器主要包括: 1.Vector、Stack、HashTable 2.Collections 工具类中提供的同步集合类 Collections类是一个工具类,相当于Arrays类对于Array的支持,Collections类中提供了大量对集合或者容器进行排序、查找的方法。它还提供了几个静态方法来创建同步容器类: 3 并发容器 java.util.concurrent提供了多种线程安全容器,大多数是使用系统底层技术实现的线程安全,也叫并发容器,类似native。Java8中使用CAS。
Collection包结构,与Collections的区别
*Collection是集合类的上级接口,子接口有 Set、List、LinkedList、ArrayList、Vector、Stack、Set; *Collections是集合类的一个帮助类, 它包含有各种有关集合操作的静态多态方法,用于实现对各种集合的搜索、排序、线程安全化等操作。此类不能实例化,就像一个工具类,服务于Java的Collection框架。
常用的集合类有哪些?
Map接口和Collection接口是所有集合框架的父接口: Collection接口的子接口包括:Set接口和List接口 Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等 Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等 List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
完整结构图
集合框架底层数据结构
Collection List Arraylist: Object数组 Vector: Object数组 LinkedList: 双向循环链表 Set HashSet(无序,唯一):基于 HashMap 实现的,底层采用 HashMap 来保存元素 LinkedHashSet: LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。 TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树。) Map HashMap: JDK1.8之前HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间 LinkedHashMap:LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。 HashTable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的 TreeMap: 红黑树(自平衡的排序二叉树)
哪些集合类是线程安全的?
vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。 statck:堆栈类,先进后出。 hashtable:就比hashmap多了个线程安全。 enumeration:枚举,相当于迭代器。
List
可以用for循环直接删除ArrayList的特定元素吗?可能会出现什么问题?怎样解决
首先java的foreach循环其实就是根据list对象创建一个Iterator迭代对象,用这个迭代对象来遍历list,相当于list对象中元素的遍历托管给了Iterator,你如果要对list进行增删操作,都必须经过Iterator,否则Iterator遍历时会乱,所以直接对list进行删除时,Iterator会抛出ConcurrentModificationException异常,这个异常是在next方法的checkForComodification中抛出的,抛出原因是modCount != expectedModCount modCount是指这个list对象从new出来到现在被修改次数,当调用List的add或者remove方法的时候,这个modCount都会增加; expectedModCount是Iterator类中特有的变量,指现在期望这个list被修改的次数是多少次,这个值在调用list.iterator()创建iterator的时候初始化为modCount,该值在iterator初始化直到使用结束期间不会改变。 iterator创建的时候modCount被赋值给了expectedModCount,但是调用list的add和remove方法的时候不会同时修改expectedModCount,这样就导致下次取值时检查到两个count不相等,从而抛出异常。
如果用的是增强for循环/forEach循环方式,使用List的remove()则不可以,会报ConcurrentModificationException异常,传统for循环不会报错,建议采用Iterator,使用iterator.remove()
不可行
List<String>ll=new LinkedList<String>(); ll.add("first"); ll.add("second"); ll.add("third"); ll.add("fourth"); for(Iterator<String>iter=ll.iterator();iter.hasNext();) { String str=(String)iter.next(); System.out.println(str); if(str.equals("second")) { ll.add("five"); } }
可行
Iterator<String> iterator = ll.iterator(); while (iterator.hasNext()) { String str = (String) iterator.next(); System.out.println(str); iterator.remove(); }
List是怎么实现Iteratable接口
内部实现Iterator接口
有什么线程安全的List?(CopyOnWriteArrayList)讲一下怎么实现线程安全的?(读时复制,写时共享,加锁机制)
Vector
List有什么关键的方法
add()
remove()
removeAll()
ArrayList和LinkedList区别?
数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。 随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。 增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。 内存空间占用:LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。 线程安全:ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全; 综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。 补充:数据结构基础之双向链表 双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。
ArrayList是有序的吗
是的
ArrayList和LinkedList是线程安全的吗?为什么说他们不是线程安全的,举实际场景?
都不是线程安全
poll() 方法和 remove() 方法的区别?
poll() 和 remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常。
Vector是一个线程安全类吗?
Vector 是用同步方法来实现线程安全的,
迭代器 Iterator 是什么?
Iterator 接口提供遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允许调用者在迭代过程中移除元素。
Iterator 怎么使用?有什么特点?
Iterator 使用代码如下: List<String> list = new ArrayList<>(); Iterator<String> it = list. iterator(); while(it. hasNext()){ String obj = it. next(); System. out. println(obj); } Iterator 的特点是只能单向遍历,但是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。
如何边遍历边移除 Collection 中的元素?
边遍历边修改 Collection 的唯一正确方式是使用 Iterator.remove() 方法,如下: Iterator<Integer> it = list.iterator(); while(it.hasNext()){ *// do something* it.remove(); }
一种最常见的错误代码如下: for(Integer i : list){ list.remove(i) } 运行以上错误代码会报 ConcurrentModificationException 异常。这是因为当使用 foreach(for(Integer i : list)) 语句时,会自动生成一个iterator 来遍历该 list,但同时该 list 正在被 Iterator.remove() 修改。Java 一般不允许一个线程在遍历 Collection 时另一个线程修改它。
Iterator 和 ListIterator 有什么区别?
Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。 Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历)。 ListIterator 实现 Iterator 接口,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。
说一下 ArrayList 的优缺点
ArrayList的优点如下: ArrayList 底层以数组实现,是一种随机访问模式。ArrayList 实现了 RandomAccess 接口,因此查找的时候非常快。 ArrayList 在顺序添加一个元素的时候非常方便。 ArrayList 的缺点如下: 删除元素的时候,需要做一次元素复制操作。如果要复制的元素很多,那么就会比较耗费性能。 插入元素的时候,也需要做一次元素复制操作,缺点同上。 ArrayList 比较适合顺序添加、随机访问的场景。
Arrays.asList()为什么不能remove?
ArrayList 和 Vector 的区别是什么?
这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合 线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。 性能:ArrayList 在性能方面要优于 Vector。 扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%。 Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间。 Arraylist不是同步的,所以在不需要保证线程安全时时建议使用Arraylist。
插入数据时,ArrayList、LinkedList、Vector谁速度较快?阐述 ArrayList、Vector、LinkedList 的存储性能和特性?
ArrayList、LinkedList、Vector 底层的实现都是使用数组方式存储数据。数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。 Vector 中的方法由于加了 synchronized 修饰,因此 Vector 是线程安全容器,但性能上较ArrayList差。 LinkedList 使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但插入数据时只需要记录当前项的前后项即可,所以 LinkedList 插入速度较快。
多线程场景下如何使用 ArrayList?
ArrayList 不是线程安全的,如果遇到多线程场景,可以通过 Collections 的 synchronizedList 方法将其转换成线程安全的容器后再使用。例如像下面这样: List<String> synchronizedList = Collections.synchronizedList(list); synchronizedList.add("aaa"); synchronizedList.add("bbb"); for (int i = 0; i < synchronizedList.size(); i++) { System.out.println(synchronizedList.get(i)); }
List 和 Set 的区别
List , Set 都是继承自Collection 接口 List 特点:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。 Set 特点:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。 另外 List 支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。 Set和List对比 Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。 List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变
如何正确的将数组转换为ArrayList?
自己动手实现(教育目的)
最简便的方法(推荐)
List list = new ArrayList<>(Arrays.asList("a", "b", "c"))
使用 Java8 的Stream(推荐)
Integer [] myArray = { 1, 2, 3 }; List myList = Arrays.stream(myArray).collect(Collectors.toList()); //基本类型也可以实现转换(依赖boxed的装箱操作) int [] myArray2 = { 1, 2, 3 }; List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList());
Map
经典面试过程视频
HashMap实现原理/内部数据结构
HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 HashMap的数据结构: 在Java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。 HashMap 基于 Hash 算法实现的 当我们往Hashmap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标 存储时,如果出现hash值相同的key,此时有两种情况。(1)如果key相同,则覆盖原始值;(2)如果key不同(出现冲突),则将当前的key-value放入链表中 获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。 理解了以上过程就不难明白HashMap是如何解决hash冲突的问题,核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。 需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn)
什么是哈希?
Hash,一般翻译为“散列”,也有直接音译为“哈希”的,这就是把任意长度的输入通过散列算法,变换成固定长度的输出,该输出就是散列值(哈希值);这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
什么是哈希冲突?
当两个不同的输入值,根据同一散列函数计算出相同的散列值的现象,我们就把它叫做碰撞(哈希碰撞)。
HashMap是怎么解决哈希冲突的?
1. 使用链地址法(使用散列表)来链接拥有相同hash值的数据;
2. 使用2次扰动函数(hash函数)来降低哈希冲突的概率,使得数据分布更平均; 在1.7中的4次位运算,5次异或运算(9次扰动),在1.8中,只进行了1次位运算和1次异或运算(2次扰动);
3. 引入红黑树进一步降低遍历的时间复杂度,使得遍历更快;
为什么解决哈希冲突中使用了两次扰动函数?
加大哈希值低位的随机性,使得分布更均匀,从而提高对应数组存储下标位置的随机性&均匀性,最终减少Hash冲突,两次就够了,已经达到了高位低位同时参与运算的目的;
HashMap在JDK1.7和JDK1.8中有哪些不同?HashMap的底层实现
详细比较(内容较长,放到后一级)
在Java中,保存数据有两种比较简单的数据结构:数组和链表。数组的特点是:寻址容易,插入和删除困难;链表的特点是:寻址困难,但插入和删除容易;所以我们将数组和链表结合在一起,发挥两者各自的优势,使用一种叫做拉链法(链地址法)的方式可以解决哈希冲突。 JDK1.8之前 JDK1.8之前采用的是拉链法。拉链法:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。 JDK1.8之后 相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。 JDK1.7 VS JDK1.8 比较 JDK1.8主要解决或优化了一下问题: *resize 扩容优化 *引入了红黑树,目的是避免单条链表过长而影响查询效率,红黑树算法请参考 *解决了多线程死循环问题,但仍是非线程安全的,多线程时可能会造成数据丢失问题。
HashMap的put方法的具体流程?
详细流程
当我们put的时候,首先计算 key的hash值,这里调用了 hash方法,hash方法实际是让key.hashCode()与key.hashCode()>>>16进行异或操作,高16bit补0,一个数和0异或不变,所以 hash 函数大概的作用就是:高16bit不变,低16bit和高16bit做了一个异或,目的是减少碰撞。按照函数注释,因为bucket数组大小是2的幂,计算下标index = (table.length - 1) & hash,如果不做 hash 处理,相当于散列生效的只有几个低 bit 位,为了减少散列的碰撞,设计者综合考虑了速度、作用、质量之后,使用高16bit和低16bit异或来简单处理减少碰撞,而且JDK8中用了复杂度 O(logn)的树结构来提升碰撞下的性能。 ①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容; ②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③; ③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals; ④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤; ⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可; ⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。 putVal方法执行流程图
代码
HashMap的扩容操作是怎么实现的?
①.在jdk1.8中,resize方法是在hashmap中的键值对大于阀值时或者初始化时,就调用resize方法进行扩容; ②.每次扩展的时候,都是扩展2倍; ③.扩展后Node对象的位置要么在原位置,要么移动到原偏移量两倍的位置。 在putVal()中,我们看到在这个函数里面使用到了2次resize()方法,resize()方法表示的在进行第一次初始化时会对其进行扩容,或者当该数组的实际大小大于其临界值值(第一次为12),这个时候在扩容的同时也会伴随的桶上面的元素进行重新分发,这也是JDK1.8版本的一个优化的地方,在1.7中,扩容之后需要重新去计算其Hash值,根据Hash值对其进行分发,但在1.8版本中,则是根据在同一个桶的位置中进行判断(e.hash & oldCap)是否为0,重新进行hash分配后,该元素的位置要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位置上
负载因子是做什么的?
负载因子,可以理解成一辆车可承重重量超过某个阀值时,把货放到新的车上。 那么在 HashMap 中,负载因子决定了数据量多少了以后进行扩容。这里要提到上面做的 HashMap 例子,我们准备了 7 个元素,但是最后还有 3 个位置空余,2 个位置存放了 2 个元素。 所以可能即使你数据比数组容量大时也是不一定能正正好好的把数组占满的,而是在某些小标位置出现了大量的碰撞,只能在同一个位置用链表存放,那么这样就失去了 Map 数组的性能。 所以,要选择一个合理的大小下进行扩容,默认值 0.75 就是说当阀值容量占了 3/4s 时赶紧扩容,减少 Hash 碰撞。 同时 0.75 是一个默认构造值,在创建 HashMap 也可以调整,比如你希望用更多的空间换取时间,可以把负载因子调的更小一些,减少碰撞。
为什么负载因子是0.75
当负载因子是1.0的时候,也就意味着,只有当数组的8个值(这个图表示了8个)全部填充了,才会发生扩容。这就带来了很大的问题,因为Hash冲突时避免不了的。当负载因子是1.0的时候,意味着会出现大量的Hash的冲突,底层的红黑树变得异常复杂。对于查询效率极其不利。这种情况就是牺牲了时间来保证空间的利用率。 因此一句话总结就是负载因子过大,虽然空间利用率上去了,但是时间效率降低了。 2、负载因子是0.5 负载因子是0.5的时候,这也就意味着,当数组中的元素达到了一半就开始扩容,既然填充的元素少了,Hash冲突也会减少,那么底层的链表长度或者是红黑树的高度就会降低。查询效率就会增加。 但是,兄弟们,这时候空间利用率就会大大的降低,原本存储1M的数据,现在就意味着需要2M的空间。 一句话总结就是负载因子太小,虽然时间效率提升了,但是空间利用率降低了。 3、负载因子0.75 经过前面的分析,基本上为什么是0.75的答案也就出来了,这是时间和空间的权衡。 /* <p>As a general rule, the default load factor (.75) offers a good * tradeoff between time and space costs. Higher values decrease the * space overhead but increase the lookup cost (reflected in most of * the operations of the <tt>HashMap</tt> class, including * <tt>get</tt> and <tt>put</tt>). The expected number of entries in * the map and its load factor should be taken into account when * setting its initial capacity, so as to minimize the number of * rehash operations. If the initial capacity is greater than the * maximum number of entries divided by the load factor, no rehash * operations will ever occur.*/ 大致意思就是说负载因子是0.75的时候,空间利用率比较高,而且避免了相当多的Hash冲突,使得底层的链表或者是红黑树的高度比较低,提升了空间效率。
传入的initialCapacity如果不是2的倍数,比如new Hashmap(17) 它会怎么处理?
阀值threshold,通过方法tableSizeFor进行计算,是根据初始化来计算的 这个方法也就是要寻找比初始值大的,最小的那个 2 进制数值。比如传了 17,我应该找到的是 32
计算阈值的源码
static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }
为什么要用红黑树?查询性能提升了多少?修改性能提升了没?
每次遍历一个链表,平均查找的时间复杂度是 O(n),n 是链表的长度。红黑树有和链表不一样的查找性能,由于红黑树有自平衡的特点,可以防止不平衡情况的发生,所以可以始终将查找的时间复杂度控制在 O(log(n))。最初链表还不是很长,所以可能 O(n) 和 O(log(n)) 的区别不大,但是如果链表越来越长,那么这种区别便会有所体现。所以为了提升查找性能,需要把链表转化为红黑树的形式。
为什么不直接用红黑树
通过查看源码可以发现,默认是链表长度达到 8 就转成红黑树,而当长度降到 6 就转换回去,这体现了时间和空间平衡的思想. 最开始使用链表的时候,空间占用是比较少的,而且由于链表短,所以查询时间也没有太大的问题。可是当链表越来越长,需要用红黑树的形式来保证查询的效率。对于何时应该从链表转化为红黑树,需要确定一个阈值,这个阈值默认为 8,并且在源码中也对选择 8 这个数字做了说明
数组、链表和红黑树的时间复杂度
什么时候树化,为什么是8
当链表长度大于或等于阈值(默认为 8)的时候,如果同时还满足容量大于或等于 MIN_TREEIFY_CAPACITY(默认为 64)的要求,就会把链表转换为红黑树。 同样,后续如果由于删除或者其他原因调整了大小,当红黑树的节点小于或等于 6 个以后,又会恢复为链表形态。 为什么是8的原因:如果 hashCode 分布良好,也就是 hash 计算的结果离散好的话,那么红黑树这种形式是很少会被用到的,因为各个值都均匀分布,很少出现链表很长的情况。在理想情况下,链表长度符合泊松分布,各个长度的命中概率依次递减,当长度为 8 的时候,概率仅为 0.00000006。这是一个小于千万分之一的概率,通常我们的 Map 里面是不会存储这么多的数据的,所以通常情况下,并不会发生从链表向红黑树的转换。
为什么HashMap使用红黑树而不使用AVL树
红黑树和AVL树都是最常用的平衡二叉搜索树,它们的查找、删除、修改都是O(lgn) time AVL树和红黑树有几点比较和区别: (1)AVL树是更加严格的平衡,因此可以提供更快的查找速度,一般读取查找密集型任务,适用AVL树。 (2)红黑树更适合于插入修改密集型任务。 (3)通常,AVL树的旋转比红黑树的旋转更加难以平衡和调试。 总结: (1)AVL以及红黑树是高度平衡的树数据结构。它们非常相似,真正的区别在于在任何添加/删除操作时完成的旋转操作次数。 (2)两种实现都缩放为a O(lg N),其中N是叶子的数量,但实际上AVL树在查找密集型任务上更快:利用更好的平衡,树遍历平均更短。另一方面,插入和删除方面,AVL树速度较慢:需要更高的旋转次数才能在修改时正确地重新平衡数据结构。 (3)在AVL树中,从根到任何叶子的最短路径和最长路径之间的差异最多为1。在红黑树中,差异可以是2倍。 (4)两个都给O(log n)查找,但平衡AVL树可能需要O(log n)旋转,而红黑树将需要最多两次旋转使其达到平衡(尽管可能需要检查O(log n)节点以确定旋转的位置)。旋转本身是O(1)操作,因为你只是移动指针。
红黑树是平衡二叉树吗?
算是
二叉树、平衡二叉树、红黑树介绍
http://leewf.xyz/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/2020/02/data_structure_tree.html
红黑树
红黑树为什么插入效率高
AVL描述一下,和红黑树的区别
红黑树讲一下,五个特性,插入删除操作,时间复杂度?
B+树和B树的区别,和红黑树的区别
什么情况下用HashMap,什么情况用ConcurrentHashMap/HashMap 和 ConcurrentHashMap 的区别?
*ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。(JDK1.8之后*ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。) HashMap的键值对允许有null,但是ConCurrentHashMap都不允许
Hashtable和 ConcurrentHashMap 的区别?
*底层数据结构: JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的; *实现线程安全的方式(重要): ① 在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。) 到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。
HashMap和HashTable的区别
两者父类不同:HashMap是继承自AbstractMap类,而Hashtable是继承自Dictionary类
对null的支持不同: Hashtable:key和value都不能为null。 HashMap:key可以为null,但是这样的key只能有一个,因为必须保证key的唯一性;可以有多个key值对应的value为null。
安全性不同: *HashMap是线程不安全的,在多线程并发的环境下,可能会产生死锁等问题,因此需要开发人员自己处理多线程的安全问题。 *Hashtable是线程安全的,它的每个方法上都有synchronized 关键字,因此可直接用于多线程中。 *虽然HashMap是线程不安全的,但是它的效率远远高于Hashtable,这样设计是合理的,因为大部分的使用场景都是单线程。当需要多线程操作的时候可以使用线程安全的ConcurrentHashMap。 *ConcurrentHashMap虽然也是线程安全的,但是它的效率比Hashtable要高好多倍。因为ConcurrentHashMap使用了分段锁,并不对整个数据进行锁定
能否使用任何类作为 Map 的 key?
可以使用任何类作为 Map 的 key,然而在使用之前,需要考虑以下几点: 如果类重写了 equals() 方法,也应该重写 hashCode() 方法。 类的所有实例需要遵循与 equals() 和 hashCode() 相关的规则。 如果一个类没有使用 equals(),不应该在 hashCode() 中使用它。 用户自定义 Key 类最佳实践是使之为不可变的,这样 hashCode() 值可以被缓存起来,拥有更好的性能。不可变的类也可以确保 hashCode() 和 equals() 在未来不会改变,这样就会解决与可变相关的问题了。
如果使用Object作为HashMap的Key,应该怎么办呢?
重写hashCode()和equals()方法
重写hashCode()是因为需要计算存储数据的存储位置,需要注意不要试图从散列码计算中排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的Hash碰撞; 重写equals()方法,需要遵守自反性、对称性、传递性、一致性以及对于任何非null的引用值x,x.equals(null)必须返回false的这几个特性,目的是为了保证key在哈希表中的唯一性;
为什么HashMap中String、Integer这样的包装类适合作为K?
String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性,能够有效的减少Hash碰撞的几率 都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不同的情况 内部已重写了equals()、hashCode()等方法,遵守了HashMap内部的规范(不清楚可以去上面看看putValue的过程),不容易出现Hash值计算错误的情况;
HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标?
hashCode()方法返回的是int整数类型,其范围为-(2 ^ 31)~(2 ^ 31 - 1),约有40亿个映射空间,而HashMap的容量范围是在16(初始化默认值)~2 ^ 30,HashMap通常情况下是取不到最大值的,并且设备上也难以提供这么多的存储空间,从而导致通过hashCode()计算出的哈希值可能不在数组大小范围内,进而无法匹配存储位置; 那怎么解决呢? HashMap自己实现了自己的hash()方法,通过两次扰动使得它自己的哈希值高低位自行进行异或运算,降低哈希碰撞概率也使得数据分布更平均; 在保证数组长度为2的幂次方的时候,使用hash()运算之后的值与运算(&)(数组长度 - 1)来获取数组下标的方式进行存储,这样一来是比取余操作更加有效率,二来也是因为只有当数组长度为2的幂次方时,h&(length-1)才等价于h%length,三来解决了“哈希值与数组大小范围不匹配”的问题;
HashMap 的长度为什么是2的幂次方
从key映射到HashMap数组的对应位置需要一个Hash函数: index = Hash("hangzhou") 如何实现一个尽量分布均匀的hash函数呢?我们使用key的hashcode做某种运算: index = hashCode("hangzhou") & (Length - 1) 其中,Length为HashMap的长度,下面来演示整个过程: 1、“hangzhou”的hashcode为4740586,二进制表示为100 1000 0101 0101 1110 1010 2、假定HashMap的长度为默认的16,则Length - 1为15,也就是二进制的1111 3、把以上两个结果做与运算,得到的结果为1010,也就是index为10 可以说,Hash算法最终得到的index结果完全取决于hashCode的最后几位。 假设,HashMap的长度为10,则Length - 1为9,也就是二进制的1001,通过Hash算法得到的最终index为8,当只有一个元素的时候这没问题。但是我们再来试一个hashCode:100 1000 0101 0101 1110 1110时,通过Hash算法得到的最终的index也是8,另外还有100 1000 0101 0101 1110 1000得到的index也是8。也就是说,即使我们把倒数第二、三位的0、1变换,得到的index仍旧是8,说明有些index结果出现的几率变大!!而有些index结果永远不会出现,比如二进制0000. 这样,显然不符合Hash算法均匀分布的要求。 反观,长度16或其他2的幂次方,Length - 1的值的二进制所有的位均为1,这种情况下,Index的结果等于hashCode的最后几位。只要输入的hashCode本身符合均匀分布,Hash算法的结果就是均匀的。 一句话,HashMap的长度为2的幂次方的原因是为了减少Hash碰撞,尽量使Hash算法的结果均匀分布。
ConcurrentHashMap 底层具体实现知道吗?实现原理是什么?
详细实现
JDK1.7 首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。 在JDK1.7中,ConcurrentHashMap采用Segment + HashEntry的方式进行实现,结构如下: 一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。 该类包含两个静态内部类 HashEntry 和 Segment ;前者用来封装映射表的键值对,后者用来充当锁的角色; Segment 是一种可重入的锁 ReentrantLock,每个 Segment 守护一个HashEntry 数组里得元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 锁。 JDK1.8 在JDK1.8中,放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。
Set
HashSet原理
HashSet 是基于 HashMap 实现的,HashSet的值存放于HashMap的key上,HashMap的value统一为PRESENT,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。
HashSet如何检查重复?HashSet是如何保证数据不可重复的?
向HashSet 中add ()元素时,判断元素是否存在的依据,不仅要比较hash值,同时还要结合equles 方法比较。 HashSet 中的add ()方法会使用HashMap 的put()方法。 HashMap 的 key 是唯一的,由源码可以看出 HashSet 添加进去的值就是作为HashMap 的key,并且在HashMap中如果K/V相同时,会用新的V覆盖掉旧的V,然后返回旧的V。所以不会重复( HashMap 比较key是否相等是先比较hashcode 再比较equals )。
并发编程
锁相关
volatile
volatile原理
volatile是通过编译器在生成字节码时,在指令序列中添加“内存屏障”来禁止指令重排序的。 volatile在字节码层面:就是使用访问标志:ACC_VOLATILE来表示,供后续操作此变量时判断访问标志是否为ACC_VOLATILE,来决定是否遵循volatile的语义处理。 JVM源码层面:lock前缀,会保证某个处理器对共享内存(一般是缓存行cacheline,这里记住缓存行概念,后续重点介绍)的独占使用。它将本处理器缓存写入内存,该写入操作会引起其他处理器或内核对应的缓存失效。通过独占内存、使其他处理器缓存失效,达到了“指令重排序无法越过内存屏障”的作用 汇编层面:lock addl指令
volatile的作用?
1、保证可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的,volatile关键字会强制将修改的值立即写入主存。
2、禁止进行指令重排序
Volatile关键词是线程安全的吗?
因为volatile不能保证变量操作的原子性,所以试图通过volatile来保证线程安全性是不靠谱的。
不用Lock和Sychronized如何保证原子性
使用原子类
原子类底层是unsafe类
sychronized
说一说自己对于 synchronized 关键字的了解
定义
众所周知 synchronized 关键字是解决并发问题常用解决方案
使用方式
同步普通方法,锁的是当前对象。 同步静态方法,锁的是当前 Class 对象。 同步块,锁的是 () 中的对象。
原理
JVM 是通过进入、退出对象监视器( Monitor )来实现对方法、同步块的同步的。 synchronized 同步语句块的情况 具体实现是在编译之后在同步方法调用前加入一个 monitor.enter 指令,在退出方法和异常处插入 monitor.exit 的指令。 其本质就是对一个对象监视器( Monitor )进行获取,而这个获取过程具有排他性从而达到了同一时刻只能一个线程访问的目的 而对于没有获取到锁的线程将会阻塞到方法入口处,直到获取锁的线程 monitor.exit 之后才能尝试继续获取锁 synchronized 修饰方法的的情况 synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
详细说明和流程图
synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。 另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
缺点
syncronized会带来的缺点,锁的释放不能跨越方法
sychronized 和 reentrantlock 实现原理和区别
相似点: 这两种同步方式有很多相似之处, *它们都是加锁方式同步, *而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的. 区别: *这两种方式最大区别就是对于Synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要jvm实现。而ReentrantLock它是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成。 Synchronized进过编译,会在同步块的前后分别形成monitorenter和monitorexit这个两个字节码指令。在执行monitorenter指令时,首先要尝试获取对象锁。如果这个对象没被锁定,或者当前线程已经拥有了那个对象锁,把锁的计算器加1,相应的,在执行monitorexit指令时会将锁计算器就减1,当计算器为0时,锁就被释放了。如果获取对象锁失败,那当前线程就要阻塞,直到对象锁被另一个线程释放为止。 由于ReentrantLock是java.util.concurrent包下提供的一套互斥锁,相比Synchronized,ReentrantLock类提供了一些高级功能,主要有以下3项: 1.等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。 2.公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁,ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。 3.锁绑定多个条件,一个ReentrantLock对象可以同时绑定对个对象。
AQS和CAS原理synchronized底层实现原理
synchronized 修饰静态方法时,锁的是类对象,如 Object.class。修饰非静态方法时,锁的是对象,即 this。多个线程是可以同时执行同一个synchronized实例方法的,只要它们访问的对象是不同的。 synchronized 锁住的是对象而非代码,只要访问的是同一个对象的 synchronized 方法,即使是不同的代码,也会被同步顺序访问。
怎么实现线程安全?有哪几种方式(syncronized和reentranLock)他们的区别?
什么是重入锁?
可重入性描述这样的一个问题:一个线程在持有一个锁的时候,它内部能否再次(多次)申请该锁。如果一个线程已经获得了锁,其内部还可以多次申请该锁成功。那么我们就称该锁为可重入锁
volatile和synchronized比较
volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。 volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。 volatile仅能实现变量的修改可见性,并不能保证原子性;synchronized则可以保证变量的修改可见性和原子性。 volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。 volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
synchronized 和 ReentrantLock 区别是什么?
synchronized 竞争锁时会一直等待;ReentrantLock 可以尝试获取锁,并得到获取结果 synchronized 获取锁无法设置超时;ReentrantLock 可以设置获取锁的超时时间 synchronized 无法实现公平锁;ReentrantLock 可以满足公平锁,即先等待先获取到锁 synchronized 控制等待和唤醒需要结合加锁对象的 wait() 和 notify()、notifyAll();ReentrantLock 控制等待和唤醒需要结合 Condition 的 await() 和 signal()、signalAll() 方法 synchronized 是 JVM 层面实现的;ReentrantLock 是 JDK 代码层面实现 synchronized 在加锁代码块执行完或者出现异常,自动释放锁;ReentrantLock 不会自动释放锁,需要在 finally{} 代码块显示释放
说说 JDK1.6 之后的 synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗
JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。 锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。
Lock
Java 有什么锁类型?
公平锁/非公平锁 可重入锁 独享锁/共享锁 互斥锁/读写锁 乐观锁/悲观锁 分段锁 偏向锁/轻量级锁/重量级锁 自旋锁
悲观锁、乐观锁锁的实现?
乐观锁/悲观锁不是指具体类型的锁,而是看待并发的角度。 悲观锁认为存在很多并发更新操作,采取加锁操作,如果不加锁一定会有问题 乐观锁认为不存在很多的并发更新操作,不需要加锁。数据库中乐观锁的实现一般采用版本号,Java中可使用CAS实现乐观锁。
产生死锁的四个条件:
1.互斥条件(进程独占资源) 2.请求与保持(进程因请求资源而阻塞时,对已获得的资源保持不放) 3.不剥夺条件(进程已获得的资源,在末使用完之前,不能强行剥夺) 4.循环等待(若干进程之间形成一种头尾相接的循环等待资源关系)
如何避免线程死锁?
我们只要破坏产生死锁的四个条件中的其中一个就可以了。 破坏互斥条件 这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。 破坏请求与保持条件 一次性申请所有的资源。 破坏不剥夺条件 占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。 破坏循环等待条件 靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
Java的lock的底层实现?
经过源码分析,AQS底层使用CAS方式对资源获取,调用方法unsafe.compareAndSwapInt
AtomicInteger怎么实现原子修改的?
循环 CAS
AtomicInteger.incrementAndGet的实现用了乐观锁技术,调用了类sun.misc.Unsafe库里面的 CAS算法,用CPU指令来实现无锁自增。所以,AtomicInteger.incrementAndGet的自增比用synchronized的锁效率倍增
java如何处理死锁
打破死锁产生的条件
有一个Safer对象,用来管理对象,调用里面的方法,不是线程安全的,怎么处理?
使用同步、乐观锁;再问:同步并发量不行,乐观锁对多个对象字段的情况不行,除同步和乐观锁之外的方法?
ThreadLocal
ReadWriteLock读写之间互斥吗?Semaphore拿到执行权的线程之间是否互斥
读锁不互斥
信号量机制中,如果存在资源争夺,则存在互斥
自旋锁 是公平吗?怎么才能公平?
普通自旋锁无法保证多线程竞争的公平性
自旋公平锁有多种实现,CLH队列锁和MCSLock锁,还有TicketLock锁
TicketLock 第一种方法就是按先来后到的顺序为每个线程编个号,按编号顺序来分配锁。这就类似于银行挂号或者医院挂号一样,按照先来后到的顺序为每个问诊者排个号,医生按号依次为问诊者服务 缺点 Ticket Lock 虽然解决了公平性的问题,但是多处理器系统上,每个进程/线程占用的处理器都在读写同一个变量serviceNum ,每次读写操作都必须在多个处理器缓存之间进行缓存同步,这会导致繁重的系统总线和内存的流量,大大降低系统整体的性能。
int a = 1; 是原子性操作吗
在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。但是像i++、i+=1等操作字符就不是原子性的,它们是分成读取、计算、赋值几步操作,原值在这些步骤还没完成时就可能已经被赋值了,那么最后赋值写入的数据就是脏数据,无法保证原子性
AQS
什么是AQS
AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。 AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面
AQS 原理分析
AQS 原理概览 AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
原理图
原子操作类
什么是原子操作?在 Java Concurrency API 中有哪些原子类(atomic classes)?
原子操作(atomic operation)意为”不可被中断的一个或一系列操作” 。
int++并不是一个原子操作,所以当一个线程读取它的值并加 1 时,另外一个线程有可能会读到之前的值,这就会引发错误。为了解决这个问题,必须保证增加操作是原子的,在 JDK1.5 之前我们可以使用同步技术来做到这一点。到 JDK1.5,java.util.concurrent.atomic 包提供了 int 和long 类型的原子包装类,它们可以自动的保证对于他们的操作是原子的并且不需要使用同步。
原子类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference 原子数组:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray 原子属性更新器:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater 解决 ABA 问题的原子类:AtomicMarkableReference(通过引入一个 boolean来反映中间有没有变过),AtomicStampedReference(通过引入一个 int 来累加来反映中间有没有变过)
说一下 atomic 的原理?
Atomic包中的类基本的特性就是在多线程环境下,当有多个线程同时对单个(包括基本类型及引用类型)变量进行操作时,具有排他性,即当多个线程同时对该变量的值进行更新时,仅有一个线程能成功,而未成功的线程可以向自旋锁一样,继续尝试,一直等到执行成功。 AtomicInteger 类的部分源码: // setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用) private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。 CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址,返回值是 valueOffset。另外 value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值
并发工具之Semaphore与Exchanger
Semaphore 有什么作用
Semaphore 就是一个信号量,它的作用是限制某段代码块的并发数。Semaphore有一个构造函数,可以传入一个 int 型整数 n,表示某段代码最多只有 n 个线程可以访问,如果超出了 n,那么请等待,等到某个线程执行完毕这段代码块,下一个线程再进入。由此可以看出如果 Semaphore 构造函数中传入的 int 型整数 n=1,相当于变成了一个 synchronized 了。 Semaphore(信号量)-允许多个线程同时访问: synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。
线程
线程安全
什么是线程安全
线程安全就是说多线程访问同一代码,不会产生不确定的结果。
线程A和线程B同时针对一个共享变量进行操作,如何实现线程安全?
加锁
线程为什么会不安全?
多线程并发对共享资源进行获取和操作的时候可能会产生错误的结果
如何保证多线程下变量可见?原理是什么?
使用关键字:voliate
原理
volatile是通过编译器在生成字节码时,在指令序列中添加“内存屏障”来禁止指令重排序的。 volatile在字节码层面:就是使用访问标志:ACC_VOLATILE来表示,供后续操作此变量时判断访问标志是否为ACC_VOLATILE,来决定是否遵循volatile的语义处理。 JVM源码层面:lock前缀,会保证某个处理器对共享内存(一般是缓存行cacheline,这里记住缓存行概念,后续重点介绍)的独占使用。它将本处理器缓存写入内存,该写入操作会引起其他处理器或内核对应的缓存失效。通过独占内存、使其他处理器缓存失效,达到了“指令重排序无法越过内存屏障”的作用 汇编层面:lock addl指令
线程使用
实现线程的几种方法,常用哪种方法
(1)继承Thread类,重写run函数 (2)实现Runnable接口,重写run函数 (3)实现Callable接口,重写call函数 常用继承Thread和实现Runnable这两种其一
如何停止一个正在运行的线程
1、使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。 2、使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的方法。 3、使用interrupt方法中断线程。
有三个线程T1,T2,T3,如何保证顺序执行(线程如何实现同步)
在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。 实际上先启动三个线程中哪一个都行, 因为在每个线程的run方法中用join方法限定了三个线程的执行顺序。
线程池怎么唤醒?
其他线程notify()和notifyAll()
notify()和notifyAll()有什么区别?
notify可能会导致死锁,而notifyAll则不会 任何时候只有一个线程可以获得锁,也就是说只有一个线程可以运行synchronized 中的代码 使用notifyall,可以唤醒所有处于wait状态的线程,使其重新进入锁的争夺队列中,而notify只能唤醒一个。 wait() 应配合while循环使用,不应使用if,务必在wait()调用前后都检查条件,如果不满足,必须调用notify()唤醒另外的线程来处理,自己继续wait()直至条件满足再往下执行。 notify() 是对notifyAll()的一个优化,但它有很精确的应用场景,并且要求正确使用。不然可能导致死锁。正确的场景应该是 WaitSet中等待的是相同的条件,唤醒任一个都能正确处理接下来的事项,如果唤醒的线程无法正确处理,务必确保继续notify()下一个线程,并且自身需要重新回到WaitSet中.
进程和线程的区别
广义上的区别: 资源: 进程是资源分配的基本单位,线程不拥有资源,但可以共享进程资源。 调度: 线程是CPU调度的基本单位,同一进程中的线程切换,不会引起进程切换;不同进程中的线程切换,会引起进程切换。 系统开销: 进程的创建和销毁时,系统都要单独为它分配和回收资源,开销远大于线程的创建和销毁;进程的上下文切换需要保存更多的信息,线程(同一进程中)的上下文切换系统开销更小。 通信方式: 进程拥有各自独立的地址空间,进程间的通信需要依靠IPC;线程共享进程资源,线程间可以通过访问共享数据进行通信。 Linux系统中进程和线程的区别: 在Linux系统中,内核调度的单元是struct task_struct,每个进程对应一个task_struct。 2.6以前的内核中没有线程的概念,内核将线程视为轻量级进程(LWP),并为每一个线程分配一个task_struct。 2.6以后的内核中出现了线程组的概念,同一个进程中的线程放入一个线程组中;内核仍然视线程为轻量级进程,每个task_struct对应一个进程或者线程组中的一个线程。 如果线程完全在内核态中实现(内核线程,KLT),内核调度的单元是线程。此时,进程与线程的区别非常微妙。 如果线程完全在用户态实现(用户线程,ULT),内核调度的单元是进程,内核对用户线程一无所知。内核只负责分配CPU给进程,进程得到CPU会后再分配给内部的线程
线程切换的时候保存哪些状态
程在切换的过程中需要保存当前线程Id、线程状态、堆栈、寄存器状态等信息
缓冲线程池(newCachedThreadPool)的存储结构,有上限吗
CachedThreadPool 的corePoolSize 被设置为空(0),maximumPoolSize被设置为 Integer.MAX.VALUE,即它是无界的,这也就意味着如果主线程提交任务的速度高于 maximumPool 中线程处理任务的速度时,CachedThreadPool 会不断创建新的线程。极端情况下,这样会导致耗尽 cpu 和内存资源。
缓冲线程池和定长线程池,有啥区别
Java通过Executors提供四种线程池,分别为: newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。 newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
定长的线程池是说固定数量的线程同时运行 缓冲的是可变的,开始的线程数比较少,随着任务的增加,逐步增加线程个数
平常用线程主要是怎么写的,会用一些线程框架吗?(没有用框架)Java线程池的概念?线程池有哪些?线程池工厂有哪些线程池类型,及其线程池参数是什么?
主要是用ThreadPoolExecutor创建线程池,指定核心线程数、最大线程数、存活时间和阻塞队列配置
其他类型的线程池还有: newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。 newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
两个线程交替打印数字
public class PrintABAtomic implements IPrintAB { // 打印何时结束需要设置一个上限,打印到100结束; private static final int MAX_PRINT_NUM = 100; private static final AtomicInteger atomicInteger = new AtomicInteger(0); @Override public void printAB() { new Thread(() -> { while (atomicInteger.get() < MAX_PRINT_NUM) { // 打印奇数. if (atomicInteger.get() % 2 == 0) { log.info("num:" + atomicInteger.get()); atomicInteger.incrementAndGet(); } } }).start(); new Thread(() -> { while (atomicInteger.get() < MAX_PRINT_NUM) { // 打印偶数. if (atomicInteger.get() % 2 == 1) { log.info("num:" + atomicInteger.get()); atomicInteger.incrementAndGet(); } } }).start(); } }
协程中的Future和Promise机制
JUC了解哪些
JDK中自带的工具类,其包含三个包 java.util.concurrent java.util.concurrent.atomic java.util.concurrent.locks
里面包含一些常用类,比如锁ReentrantLock,AQS核心类AbstractQueuedSynchonizer、并发集合CurrentHashMap、线程池类ThreadPoolExecutor以及原子类AtomicInteger等
sleep()和wait() 有什么区别?
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。 sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。 当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。
Thread 类中的start() 和 run() 方法有什么区别?
start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。
Thread类中的yield方法有什么作用?
Yield方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法而且只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU,执行yield()的线程有可能在进入到暂停状态后马上又被执行。
为什么线程通信的方法 wait(), notify()和 notifyAll()被定义在 Object 类里?
Java中,任何对象都可以作为锁,并且 wait(),notify()等方法用于等待对象的锁或者唤醒线程,在 Java 的线程中并没有可供任何对象使用的锁,所以任意对象调用方法一定定义在Object类中。 wait(), notify()和 notifyAll()这些方法在同步代码块中调用 有的人会说,既然是线程放弃对象锁,那也可以把wait()定义在Thread类里面啊,新定义的线程继承于Thread类,也不需要重新定义wait()方法的实现。然而,这样做有一个非常大的问题,一个线程完全可以持有很多锁,你一个线程放弃锁的时候,到底要放弃哪个锁?当然了,这种设计并不是不能实现,只是管理起来更加复杂。 综上所述,wait()、notify()和notifyAll()方法要定义在Object类中。
为什么 wait(), notify()和 notifyAll()必须在同步方法或者同步块中被调用?
当一个线程需要调用对象的 wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的 notify()方法。同样的,当一个线程需要调用对象的 notify()方法时,它会释放这个对象的锁,以便其他在等待的线程就可以得到这个对象锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。
并发与并行的区别
并发:一个处理器同时处理多个任务。 并行:多个处理器或者是多核的处理器同时处理多个不同的任务.
线程池
简述一下你对线程池的理解
合理利用线程池能够带来三个好处。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
JAVA线程池有哪些参数,如果自己设计一个线程池要考虑哪些问题?
corePollSize最大线程数:maximumPoolSize线程空闲时间:keepAliveTime阻塞队列大小:queueCapacity任务拒绝处理器 :rejectedExceptionHandler
要考虑的问题:合理配置线程池大小
拒绝策略及阻塞队列的大小
描述下线程池的处理流程?
1、如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(执行这一步骤需要获取全局锁)。 2、如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。 3、如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(执行这一步骤需要获取全局锁) 4、如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用 RejectedExecutionHandler.rejectedExecution()方法。
为什么线程的创建和销毁是一个开销比较大的操作
虽然我们程序员创建一个线程很容易,直接使用 new Thread() 创建就可以了,但是操作系统做的工作会多很多,它需要发出 系统调用,陷入内核,调用内核 API 创建线程,为线程分配资源等,这一些操作有很大的开销。
Java线程池中submit() 和 execute()方法有什么区别?
两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。
1、submit在执行过程中与execute不一样,不会抛出异常而是把异常保存在成员变量中,在FutureTask.get阻塞获取的时候再把异常抛出来。 2、Spring的@Schedule注解的内部实现就是使用submit,因此,如果你构建的任务内部有未检查异常,你是永远也拿不到这个异常的。 3、execute直接抛出异常之后线程就死掉了,submit保存异常线程没有死掉,因此execute的线程池可能会出现没有意义的情况,因为线程没有得到重用。而submit不会出现这种情况。
常用的线程池有哪些?
newSingleThreadExecutor:创建一个单线程的线程池,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
newFixedThreadPool:创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
newCachedThreadPool:创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
newScheduledThreadPool:创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。
newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。
如何创建线程池
《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险** Executors 返回线程池对象的弊端如下: FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。 CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。
ThreadPoolExecutor 中阻塞队列介绍一下
BlockingQueue 接口的实现类都在 J.U.C (java.util.concurrent)包中,本章将介绍以下 5 种常见的实现类 ArrayBlockingQueue LinkedBlockingQueue SynchronousQueue PriorityBlockingQueue DelayQueue
1.ArrayBlockingQueue ArrayBlockingQueue 是最典型的有界队列,其内部是用数组存储元素的,利用 ReentrantLock 实现线程安全,使用 Condition 来阻塞和唤醒线程 我们在创建它的时候就需要指定它的容量,之后也不可以再扩容了,在构造函数中我们同样可以指定是否是公平的enfeng520/article/details/107142566
2.LinkedBlockingQueue 从命名可以看出,这是一个内部用链表实现的 BlockingQueue。如果我们不指定它的初始容量,那么它容量默认就为整型的最大值 Integer.MAX_VALUE,由于这个数非常大,我们通常不可能放入这么多的数据,所以 LinkedBlockingQueue 也被称作无界队列,代表它几乎没有界限。 其他特点: 同样也利用 ReentrantLock 实现线程安全,使用 Condition 来阻塞和唤醒线程 无法设置 ReentrantLock 的公平非公平,默认是非公平 也可以设置固定大小 默认无参构造函数如下,默认最大值 Integer.MAX_VALUE
3.SynchronousQueue SynchronousQueue 最大的不同之处在于,它的容量为 0,所以没有一个地方来暂存元素,导致每次取数据都要先阻塞,直到有数据被放入;同理,每次放数据的时候也会阻塞,直到有消费者来取
4.PriorityBlockingQueue ArrayBlockingQueue 和 LinkedBlockingQueue 都是采用先进先出的顺序进行排序,可是如果有的时候我们需要自定义排序怎么办呢?这时就需要使用 PriorityBlockingQueue。 PriorityBlockingQueue 是一个支持优先级的无界阻塞队列,可以通过自定义类实现 compareTo() 方法来指定元素排序规则,或者初始化时通过构造器参数 Comparator 来指定排序规则。同时,插入队列的对象必须是可比较大小的,也就是 Comparable 的,否则会抛出 ClassCastException 异常。
5.DelayQueue DelayQueue 这个队列比较特殊,具有“延迟”的功能。我们可以设定让队列中的任务延迟多久之后执行,比如 10 秒钟之后执行,这在例如“30 分钟后未付款自动取消订单”等需要延迟执行的场景中被大量使用。 它是无界队列,放入的元素必须实现 Delayed 接口,而 Delayed 接口又继承了 Comparable 接口,所以自然就拥有了比较和排序的能力
概念
ThreadLocal是什么?应用场景?
通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢? JDK 中提供的ThreadLocal类正是为了解决这样的问题。 ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。 如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是ThreadLocal变量名的由来。他们可以使用 get() 和 set() 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。 再举个简单的例子: 比如有两个人去宝屋收集宝物,这两个共用一个袋子的话肯定会产生争执,但是给他们两个人每个人分配一个袋子的话就不会出现这样的问题。如果把这两个人比作线程的话,那么 ThreadLocal 就是用来避免这两个线程竞争的。 经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。
简述线程、程序、进程的基本概念,以及他们之间关系是什么
线程
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程
程序
是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码
进程
是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如 CPU 时间,内存空间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。
线程有哪些基本状态?
New:初始状态,线程被构建,但是还没有调用start()方法
Runnable:运行状态,Java线程将操作系统中就绪和运行两种状态笼统称作”运行中“
Blocked:阻塞状态,表示线程阻塞于锁
Waiting:等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断)
Time_Waiting:超时等待状态,该状态不同于WAITING,它是可以在指定的时间自行返回的
Terminated:终止状态,表示当前线程已经执行完毕
状态流转图
并发编程的三个重要特性
原子性 : 一个的操作或者多次操作,要么所有的操作全部都得到执行并且不会收到任何因素的干扰而中断,要么所有的操作都执行,要么都不执行。synchronized 可以保证代码片段的原子性。 可见性 :当一个变量对共享变量进行了修改,那么另外的线程都是立即可以看到修改后的最新值。volatile 关键字可以保证共享变量的可见性。 有序性 :代码在执行的过程中的先后顺序,Java 在编译器以及运行期间的优化,代码的执行顺序未必就是编写代码时候的顺序。volatile 关键字可以禁止指令进行重排序优化。
网络编程
IO框架
NIO 讲一讲
同步IO和异步IO的区别?
NIO和BIO区别?NIO怎么写?阻塞和非阻塞,同步和异步区别?
Nio有哪些类
举例常用的字节流
创建字节流后,关闭有几种方式
如何优化可以提高文件的读写速度、封装成Buffer可以提升速度的原因
Java的IO
三种IO的特点
最了解那一种IO?讲一下FileInputStream/FileOutputStream
怎么文件的读写?具体过程
文件IO的时候有遇到过爆内存的情况吗?怎么监控?
文本文件,如何读出来?(BufferReader用readline())用到了什么设计模式?装饰者模式
假设有100个连接,采用NIO的方式要服务端要分配几个线程,采用BIO的方式呢
哪些库或者框架用到NIO
同步IO和异步IO的区别
JAVA网络编程中:BIO、NIO、AIO的区别和联系
java 中 IO 流分为几种?
按照流的流向分,可以分为输入流和输出流; 按照操作单元划分,可以划分为字节流和字符流; 按照流的角色划分为节点流和处理流。 Java Io流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0流的40多个类都是从如下4个抽象类基类中派生出来的。 InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。 OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
完整IO类
BIO,NIO,AIO 有什么区别?
简答 BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。 NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。 AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。
详细回答
BIO (Blocking I/O): 同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。 NIO (New I/O): NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发 AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。
设计模式
java 设计模式, jdk里用到了哪些设计模式
设计模式熟悉哪些。适配器与代理模式的区别
适配器模式有哪几种实现方式,使用适配器模式的优点
如何创建单例模式?说了双重检查,他说不是线程安全的。如何高效的创建一个线程安全的单例?
一种是通过枚举,一种是通过静态内部类。
JVM
GC
简述Java垃圾回收机制?
在Java中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。
java GC算法
GC最基础的算法有三种: 标记 -清除算法、复制算法、标记-压缩算法,我们常用的垃圾回收器一般都采用分代收集算法。
标记 -清除算法,“标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。
复制算法,“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
标记-压缩算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
分代收集算法,“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法
Eden区垃圾回收用什么算法?为什么用这个算法?
年轻代采用复制算法,因为年轻代对象的特点是生命周期短、存活率低、回收频繁,那么其需要复制的对象就少,因此适合复制算法,而复制算法内存利用率不高的问题,通过hotspot中的两个幸存区得到缓解
GC Roots有哪些?
• 虚拟机栈(栈帧中的本地变量表)中引用的对象,比如:各个线程被调用的方法中使用到的参数、局部变量等 • 本地方法栈(Native 方法)中引用的对象 • 方法区中类静态属性引用的对象,比如:Java类的引用类型静态变量 • 方法区中常量引用的对象,比如:字符串常量池(String Table)里的引用 • 所有被同步锁synchronized持有的对象 • Java虚拟机内部的引用、基本数据类型对应的class对象,一些常驻的异常对象(NullPointerException、OutOfMemoryError),系统类加载器 • 反应Java虚拟机内部情况的本地代码缓存等
怎么判断回收的时候交叉引用的对象
虚拟机采用一种记忆集(Remembered Set)的数据结构,用以避免全局扫描,记忆集是用于记录从非收集区执行收集区域的指针集合的抽象数据结构,目前最常用的实现是”卡表“的方式,卡表记录精确到每一块内存区域,该区域内存对象含有跨代指针,卡表是”卡精度“,除此之外还有”字长精度“和“对象精度”。卡表使用了字节数组来实现,每个元素都对应着内存区域中一块特定大小的内存块,这个内存块被称为“卡页”,一般来说,卡页的大小都是2的N次幂的字节数,hotspot中是2的9次幂,即512字节。卡页中记录跨代指针的数量,大于0时称这个元素变脏,在垃圾收集时只要筛选卡表中变脏的元素,就能知道那些卡页内存块中包含跨代指针。 对于卡表的维护,也就是卡表更新操作,Hotspot通过写屏障来解决交叉引用问题,写屏障可以看作虚拟机层面对“引用类型字段赋值“这个动作的AOP切面,在引用对象赋值时会产生一个环形通知,供程序执行额外的动作。分为写前屏障和写后屏障,更新卡表在写后屏障中
实现一个程序,使得该程序循环出现“五次minor gc,五次full gc”
配置-Xmx10m -Xms10m 然后使用循环定义字节数组,并且字节数组的大小为1M,然后等待内存逐步增长,随后便会出现五次minor gc
标记清除算法和复制算法的区别,用在什么场合?
标记清除算法会产生内存碎片,复制算法不会,前者适合在老年代,后者适合在年轻代
CMS、G1和ZGC的区别
CMS主导低延迟,作用于老年代,采用标记清除算法; G1是延迟可控情况下实现高吞吐量,作用于年轻代和老年代,标记压缩和复制算法都有使用,并且是区域分代式 ZGC收集器是一款基于Region内存布局的,(暂时)不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-压缩算法的,以低延迟为首要目标的一块垃圾收集器
什么时候会触发minor gc,什么时候会触发full gc?
新生代内存不够用时候发生MGC也叫YGC,JVM内存不够的时候发生FGC 除直接调用System.gc外,触发Full GC执行的情况有如下四种。 1. 旧生代空间不足 旧生代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出如下错误: java.lang.OutOfMemoryError: Java heap space 为避免以上两种状况引起的FullGC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。 2. Permanet Generation(持久代)空间满 PermanetGeneration中存放的为一些class的信息等,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息: java.lang.OutOfMemoryError: PermGen space 为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。 3. CMS GC时出现promotion failed和concurrent mode failure 对于采用CMS进行旧生代GC的程序而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure两种状况,当这两种状况出现时可能会触发Full GC。 promotionfailed是在进行Minor GC时,survivor space放不下、对象只能放入旧生代,而此时旧生代也放不下造成的;concurrent mode failure是在执行CMS GC的过程中同时有对象要放入旧生代,而此时旧生代空间不足造成的。 应对措施为:增大survivorspace、旧生代空间或调低触发并发GC的比率,但在JDK 5.0+、6.0+的版本中有可能会由于JDK的bug29导致CMS在remark完毕后很久才触发sweeping动作。对于这种状况,可通过设置-XX:CMSMaxAbortablePrecleanTime=5(单位为ms)来避免。 4. 统计得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间 这是一个较为复杂的触发情况,Hotspot为了避免由于新生代对象晋升到旧生代导致旧生代空间不足的现象,在进行Minor GC时,做了一个判断,如果之前统计所得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间,那么就直接触发Full GC。 例如程序第一次触发MinorGC后,有6MB的对象晋升到旧生代,那么当下一次Minor GC发生时,首先检查旧生代的剩余空间是否大于6MB,如果小于6MB,则执行Full GC。 当新生代采用PSGC时,方式稍有不同,PS GC是在Minor GC后也会检查,例如上面的例子中第一次Minor GC后,PS GC会检查此时旧生代的剩余空间是否大于6MB,如小于,则触发对旧生代的回收。 除了以上4种状况外,对于使用RMI来进行RPC或管理的Sun JDK应用而言,默认情况下会一小时执行一次Full GC。可通过在启动时通过- java-Dsun.rmi.dgc.client.gcInterval=3600000来设置Full GC执行的间隔时间或通过-XX:+ DisableExplicitGC来禁止RMI调用System.gc。
讲一下gc,公司用的是什么垃圾回收器
使用G1回收器
对象是否存活/如何判断对象可以被回收
引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。
可达性分析(Reachability Analysis):从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对象。
对象分配规则
*对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。 *大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。 *长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,知道达到阀值对象进入老年区。 *动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。 *空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。
JVM的永久代中会发生垃圾回收么?
垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。请参考下Java8:从永久代到元数据区 (注:Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区)
你有没有遇到过OutOfMemory问题?你是怎么来处理这个问题的?处理 过程中有哪些收获?
常见的原因 *内存加载的数据量太大:一次性从数据库取太多数据; *集合类中有对对象的引用,使用后未清空,GC不能进行回收; *代码中存在循环产生过多的重复对象; *启动参数堆内存值小。
说一下 JVM 有哪些垃圾回收器?
用于回收新生代的收集器包括Serial、PraNew、Parallel Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,还有用于回收整个Java堆的G1收集器。不同收集器之间的连线表示它们可以搭配使用。
详细图片
详细说明
Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效; ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现; Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景; Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本; Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本; CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。 G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。
详细介绍一下 CMS 垃圾回收器?
CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。 CMS 使用的是标记-清除的算法实现的,所以在 gc 的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。
新生代垃圾回收器和老年代垃圾回收器都有哪些?有什么区别?
新生代回收器:Serial、ParNew、Parallel Scavenge 老年代回收器:Serial Old、Parallel Old、CMS 整堆回收器:G1 新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。
简述分代垃圾回收器是怎么工作的?
分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。 新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下: 把 Eden + From Survivor 存活的对象放入 To Survivor 区; 清空 Eden 和 From Survivor 分区; From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor。 每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代。 老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程
简述java内存分配与回收策略以及Minor GC和Major GC
所谓自动内存管理,最终要解决的也就是内存分配和内存回收两个问题。前面我们介绍了内存回收,这里我们再来聊聊内存分配。 对象的内存分配通常是在 Java 堆上分配(随着虚拟机优化技术的诞生,某些场景下也会在栈上分配,后面会详细介绍),对象主要分配在新生代的 Eden 区,如果启动了本地线程缓冲,将按照线程优先在 TLAB 上分配。少数情况下也会直接在老年代上分配。总的来说分配规则不是百分百固定的,其细节取决于哪一种垃圾收集器组合以及虚拟机相关参数有关,但是虚拟机对于内存的分配还是会遵循以下几种「普世」规则: 对象优先在 Eden 区分配 多数情况,对象都在新生代 Eden 区分配。当 Eden 区分配没有足够的空间进行分配时,虚拟机将会发起一次 Minor GC。如果本次 GC 后还是没有足够的空间,则将启用分配担保机制在老年代中分配内存。 这里我们提到 Minor GC,如果你仔细观察过 GC 日常,通常我们还能从日志中发现 Major GC/Full GC。 Minor GC 是指发生在新生代的 GC,因为 Java 对象大多都是朝生夕死,所有 Minor GC 非常频繁,一般回收速度也非常快; Major GC/Full GC 是指发生在老年代的 GC,出现了 Major GC 通常会伴随至少一次 Minor GC。Major GC 的速度通常会比 Minor GC 慢 10 倍以上。 大对象直接进入老年代 所谓大对象是指需要大量连续内存空间的对象,频繁出现大对象是致命的,会导致在内存还有不少空间的情况下提前触发 GC 以获取足够的连续空间来安置新对象。 前面我们介绍过新生代使用的是标记-清除算法来处理垃圾回收的,如果大对象直接在新生代分配就会导致 Eden 区和两个 Survivor 区之间发生大量的内存复制。因此对于大对象都会直接在老年代进行分配。 长期存活对象将进入老年代 虚拟机采用分代收集的思想来管理内存,那么内存回收时就必须判断哪些对象应该放在新生代,哪些对象应该放在老年代。因此虚拟机给每个对象定义了一个对象年龄的计数器,如果对象在 Eden 区出生,并且能够被 Survivor 容纳,将被移动到 Survivor 空间中,这时设置对象年龄为 1。对象在 Survivor 区中每「熬过」一次 Minor GC 年龄就加 1,当年龄达到一定程度(默认 15) 就会被晋升到老年代。
简单的介绍一下强引用、软引用、弱引用、虚引用(虚引用与软引用和弱引用的区别、使用软引用能带来的好处)。
1.强引用(StrongReference) 以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。 2.软引用(SoftReference) 如果一个对象只具有软引用,那就类似于可有可无的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中。 3.弱引用(WeakReference) 如果一个对象只具有弱引用,那就类似于可有可无的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。 4.虚引用(PhantomReference) "虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。 虚引用主要用来跟踪对象被垃圾回收的活动。 虚引用与软引用和弱引用的一个区别在于: 虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
如何判断一个常量是废弃常量
假如在常量池中存在字符串 “abc”,如果当前没有任何String对象引用该字符串常量的话,就说明常量 “abc” 就是废弃常量,如果这时发生内存回收的话而且有必要的话,“abc” 就会被系统清理出常量池。
如何判断一个类是无用的类
1.该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。 2.加载该类的 ClassLoader 已经被回收。 3.该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
垃圾收集有哪些算法,各自的特点?
基本算法
HotSpot 为什么要分为新生代和老年代?
进一步划分的目的是更好地回收内存,或者更快地分配内存。
常见的垃圾回收器有哪些?
新生代回收器:Serial、ParNew、Parallel Scavenge 老年代回收器:Serial Old、Parallel Old、CMS 整堆回收器:G1
介绍一下 CMS G1 收集器。
CMS主导低延迟,作用于老年代,采用标记清除算法; G1是延迟可控情况下实现高吞吐量,作用于年轻代和老年代,标记压缩和复制算法都有使用,并且是区域分代式
Minor Gc 和 Full GC 有什么不同呢?
Minor GC指年轻代(Eden)的回收事件 Full GC指堆和方法区的回收事件
G1如何建立可靠的停顿预测模型
用户在启动Java程序时可以通过-XX:MaxGCPauseMillis指定停顿时间的最大期望值,在垃圾收集过程中,G1收集每个Region的回收耗时,再根据历史数据的偏差、置信度等统计数据,由哪些Region组成的回收集合才能达到期望停顿值之内的最高收益。
如果G1产生FGC,你应该做什么?
1. 扩内存 2. 提高CPU性能(回收的快,业务逻辑产生对象的速度固定,垃圾回收越快,内存空间越大) 3. 降低MixedGC触发的阈值,让MixedGC提早发生(默认是45%)
内存管理
jvm内存模型/介绍下Java内存区域/JVM有哪些区,分别干什么的?
Java堆(Heap),是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。 方法区(Method Area),方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 程序计数器(Program Counter Register),程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。 JVM栈(JVM Stacks),与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。 本地方法栈(Native Method Stacks),本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。
内存模型图
Java内存占用过大怎么查看?Java内存溢出有碰到过吗?Java内存溢出怎么定位?
可以通过监控服务指标,也可以通过JDK自带的命令来进行监控,比如jstat,jmap等,还有可视化的工具如JVisualVM、JProfilter和Jconsole
内存溢出会爬出OOM异常,排查可通过dump内存来进行分析
如果给一个类,里面只有一个main方法,方法里面只有一句System.out.println(“helloworld”),问运行这个类会在Java内存模型里发生什么? “helloworld”存储在哪里?
应该是字符串常量池内
内存泄漏常发生在jvm的哪?
堆
main方法放在哪一个内存区?为什么?
栈
在一台16G内存的机器上,JVM默认内存空间多大?
jdk1.8,堆内存默认是物理内存的1/64,而最大堆内存不能超过物理内存1/4或者1G
堆和栈的区别
最主要的区别就是栈内存用来存储局部变量和方法调用。 而堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中
堆是线程共享,栈是线程独有
对方法区和永久区的理解以及它们之间的关系
Java虚拟机规范中定义了方法区的作用,而永久区是JDK1.8之前对于方法区的实现,该实现是归属运行时区域,到了1.8及以上的版本中,hotspot使用元空间来替代永久代的作用,并且该实现归属直接内存,不受JVM内存影响
内存泄漏与内存溢出的区别
内存泄漏指的是用户已经不需要的对象,但是GC不能进行回收,导致对象一直存在,最终导致内存不足,当创建对象时,如果Full GC之后没有足够的内存存放,则会出现内存溢出,前者可导致后者出现
Java 中堆和栈有什么区别?
JVM 中堆和栈属于不同的内存区域,使用目的也不同。栈常用于保存方法帧和局部变量,而对象总是在堆上分配。栈通常都比堆小,也不会在多个线程之间共享,而堆被整个 JVM 的所有线程共享。
JVM内存分配策略,新生代对象晋升到老年代的年龄阀值默认是多少?
15
怎么查看Java程序堆的信息
命令行 jmap -heap
可视化监控工具VisualVM
进程所占用的虚存大于了虚拟机的堆栈设置参数,为什么不报错?
(可能是元空间的关系)
64 位 JVM 中,int 的长度是多数?
4个字节占32位
JDK 1.8之后Perm Space(永久代)有哪些变动? MetaSpace(元空间)⼤⼩默认是⽆限的么? 还是你们会通过什么⽅式来指定⼤⼩?
DK 1.8后用元空间替代了 Perm Space;字符串常量存放到堆内存中。 MetaSpace大小默认没有限制,一般根据系统内存的大小。JVM会动态改变此值。 -XX:MetaspaceSize:分配给类元数据空间(以字节计)的初始大小(Oracle逻辑存储上的初始高水位,the initial high-water-mark)。此值为估计值,MetaspaceSize的值设置的过大会延长垃圾回收时间。垃圾回收过后,引起下一次垃圾回收的类元数据空间的大小可能会变大。 -XX:MaxMetaspaceSize:分配给类元数据空间的最大值,超过此值就会触发Full GC,此值默认没有限制,但应取决于系统内存的大小。JVM会动态地改变此值。
JVM内存相关的几个核心参数图解
举例栈溢出的情况?(StackOverflowError)
通过-Xss设置栈大小
死循环递归
调整栈大小,就能保证不出现溢出吗?
不能保证
比如遇到递归
分配的栈内存越大越好吗?
不是,会导致其他线程的空间变小
垃圾回收是否会涉及到虚拟机栈?
不会的
方法中定义的局部变量是否线程安全?
具体问题具体分析,逃逸就不安全
JDK7和JDK8中的堆内存/内存设计上有什么不同
比较大的不同是堆中的永久区改为元空间
为什么实际的可用堆内存大小会小于配置的大小,比如配置-Xmx=600m,实际通过Runtime.getRuntime().maxMemory()的大小是575m
原因是幸存区1和幸存区2只能有一个使用
经常遇到的异常(包括异常和错误)
OOM
新生代中的内存结构比例,eden、survivor1和survivor2的比例默认是8:1:1吗
是
堆内存中,移动到老年代的默认阈值是多少,是否可配置
默认阈值是15,可以通过-XX:MaxTenuringThreshold配置
对象在堆中的GC过程
堆是分配对象存储的唯一选择吗?
如何让发生逃逸的变量不逃出?
如何快速的判断是否发生了逃逸分析
就看new的对象是否有可能在方法外被调用
JDK6、JDK7和JDK8的内存设计有什么不同?
JDK6,静态变量放在方法区/永久代,StringTable放在运行时常量池中,运行时常量池放在方法区中,而到了JDK7,静态变量和StringTable都放在了堆中(提高回收效率),到了JDK8,元空间替代了方法区
为什么JDK7要把StringTable放到堆中?
JDK6放在永久区中,但是因为永久区回收效率比较低,需要老年代空间不足、永久代空间不足触发full gc的时候才会回收,JDK7放到堆中,能提高回收的效率
静态变量对象存在哪里?
不论哪个版本JDK,静态变量的对象都是存在堆中,引用会有不同,JDK6中放在永久代中,JDK7之后都放在堆中
方法区/永久代是否有垃圾收集行为?
规范中其实不要求一定要有,hotspot是有的
方法区都存放些什么数据?
类型信息、常量、静态变量、即时编译器编译后的代码缓存、方法信息、域信息(JDK7之后,Stringtable和静态变量的引用都放到了堆中)
常量池的回收策略是什么?
只要常量池中的常量没有被任何地方引用,就可以被回收
为什么要两个Survivor区?Eden和Survior的比例分配?
年轻代采用复制算法,使用幸存者区能提高内存利用率,设置两个Survivor区最大的好处就是解决了碎片化 比例是8:1
为什么要有新生代、老年代和持久代?
提高垃圾回收效率
对象在JVM中是怎么存储的?(对象的内存布局)
在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域:对象头、实例数据和对齐填充。 Hotspot 虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据(哈希码、GC 分代年龄、锁状态标志等等),另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。 实例数据部分是对象真正存储的有效信息,也是在程序中所定义的各种类型的字段内容。 对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用
对象头信息里面都有什么东西?
Hotspot 虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据(哈希码、GC 分代年龄、锁状态标志等等),另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。
类加载机制
什么是类加载器,类加载器有哪些?
实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。 主要有一下四种类加载器: 启动类加载器(Bootstrap ClassLoader)用来加载java核心类库,无法被java程序直接引用。 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。 用户自定义类加载器,通过继承 java.lang.ClassLoader类的方式实现。
java class的加载过程
加载、验证、准备、解析和初始化
加载
“加载”(Loading)阶段是整个“类加载”(Class Loading)过程中的一个阶段,希望读者没有混淆这两个看起来很相似的名词。在加载阶段,Java虚拟机需要完成以下三件事情: 1)通过一个类的全限定名来获取定义此类的二进制字节流。 2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
验证
验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。
从整体上看,验证阶段大致上会完成下面四个阶段的检验动作:文件格式验证、元数据验证、字节码验证和符号引用验证。
准备
准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段,从概念上讲,这些变量所使用的内存都应当在方法区中进行分配,但必须注意到方法区本身是一个逻辑上的区域,在JDK 7及之前,HotSpot使用永久代来实现方法区时,实现是完全符合这种逻辑概念的;而在JDK 8及之后,类变量则会随着Class对象一起存放在Java堆中,这时候“类变量在方法区”就完全是一种对逻辑概念的表述了
解析
解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程,符号引用在第6章讲解Class文件格式的时候已经出现过多次,在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现,那解析阶段中所说的直接引用与符号引用又有什么关联呢? ·符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定是已经加载到虚拟机内存当中的内容。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须都是一致的,因为符号引用的字面量形式明确定义在《Java虚拟机规范》的Class文件格式中。 ·直接引用(Direct References):直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局直接相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在虚拟机的内存中存在。
初始化
类的初始化阶段是类加载过程的最后一个步骤,之前介绍的几个类加载的动作里,除了在加载阶段用户应用程序可以通过自定义类加载器的方式局部参与外,其余动作都完全由Java虚拟机来主导控制。直到初始化阶段,Java虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程序。
类加载机制,一个类加载到虚拟机中一共有几个步骤,这些步骤的顺序哪些是固定的,哪些是不固定的,为什么不固定
加载(装载)、验证、准备、初始化和卸载这五个阶段顺序是固定的,类的加载过程必须按照这种顺序开始。解析阶段不一定,它在某些情况下可以在初始化之后再开始,这是为了运行时动态绑定特性(JIT(准时制生产方式,又称作无库存生产方式)接口只在调用的时候才知道具体实现的是哪个子类)
Jdk中rt目录下的类是哪个加载器加载的
Bootstrap类加载器加载的
C++ main()返回值的含义,Java中main方法返回void如何判断程序是否正常退出,用static修饰有什么作用,为什么要用static,用JVM启动的时候,为什么不可以创建一个实例再去调用main的方法
什么是双亲委派模型?
为了安全,比如定义一个String类,最终加载的是JDK的
APP->EXC->BOOT(最终执行)
如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。
描述一下JVM加载class文件的原理机制?
JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。 由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后JVM对类进行初始化,包括:1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;2)如果类中存在初始化语句,就依次执行这些初始化语句。 类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。从Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM更好的保证了Java平台的安全性,在该机制中,JVM自带的Bootstrap是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java程序提供对Bootstrap的引用。下面是关于几个类加载器的说明: Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar); Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap; System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。
一个java文件有3个类,编译后有几个class文件
3个
Java对象创建过程
1.JVM遇到一条新建对象的指令时首先去检查这个指令的参数是否能在常量池中定义到一个类的符号引用。然后加载这个类(类加载过程在后边讲) 2.为对象分配内存。一种办法“指针碰撞”、一种办法“空闲列表”,最终常用的办法“本地线程缓冲分配(TLAB)” 3.将除对象头外的对象内存空间初始化为0 4.对对象头进行必要设置
尚硅谷视频中的步骤
类的生命周期
一个Java类从开始到结束整个生命周期会经历7个阶段:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)。其中验证、准备、解析三个部分又统称为连接(Linking)
详细说明
加载:加载过程就是把class字节码文件载入到虚拟机中,至于从哪儿加载,虚拟机设计者并没有限定,你可以从文件、压缩包、网络、数据库等等地方加载class字节码。 通过类的全限定名来获取定义此类的二进制字节流 将此二进制字节流所代表的静态存储结构转化成方法区的运行时数据结构 在内存中生成代表此类的java.lang.Class对象,作为该类访问入口. 验证:验证的目的是确保class文件的字节流中信息符合虚拟机的要求,不会危害虚拟机安全,使得虚拟机免受恶意代码的攻击,这一步至关重要。 文件格式验证 源数据验证 字节码验证 符号引用验证 准备:准备阶段的工作就是为类的静态变量分配内存并设为jvm默认的初值,对于非静态的变量,则不会为它们分配内存。静态变量的初值为jvm默认的初值,而不是我们在程序中设定的初值。(仅包含类变量,不包含实例变量). 解析:虚拟机将常量池中的符号引用替换为直接引用,解析动作主要针对类或接口,字段,类方法,方法类型等等。 初始化:在该阶段,才真正意义上的开始执行类中定义的java程序代码,该阶段会执行类构造器,并且在Java虚拟机规范中有明确的规定,在下面5种情况下必须对类进行初始化: 遇到new、getstatic、putstatic、invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。 使用java.long.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。 当初始化一个类的时候,如果发现其父类没有进行过初始化,则需要先触发其父类的初始化。 当虚拟机启动时,需要制定一个执行的主类(即main方法的类),虚拟机必须先初始化这个类。 使用动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后解析结果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应的类没有进行初始化,则需要先触发其初始化。 使用:使用该类所提供的功能,其中包括主动引用和被动引用。 主动引用: 通过new关键字实例化对象、读取或设置类的静态变量、调用类的静态方法。 通过反射方式执行以上三种行为。 初始化子类的时候,会触发父类的初始化。 作为程序入口直接运行时(也就是直接调用main方法)。 被动引用: 引用父类的静态字段,只会引起父类的初始化,而不会引起子类的初始化。 定义类数组,不会引起类的初始化。 引用类的常量,不会引起类的初始化。 卸载:从内存中释放,在我之前写的垃圾回收机制(GC)总结一文中有介绍到方法区内存回收中对类的回收条件,这里再贴出来一下: 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例; 加载该类的ClassLoader已经被回收; 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
Java的对象结构
Java对象由三个部分组成:对象头、实例数据、对齐填充。 对象头由两部分组成,第一部分存储对象自身的运行时数据:哈希码、GC分代年龄、锁标识状态、线程持有的锁、偏向线程ID(一般占32/64 bit)。第二部分是指针类型,指向对象的类元数据类型(即对象代表哪个类)。如果是数组对象,则对象头中还有一部分用来记录数组长度。 实例数据用来存储对象真正的有效信息(包括父类继承下来的和自己定义的) 对齐填充:JVM要求对象起始地址必须是8字节的整数倍(8字节对齐)
Java程序是如何执行的
Java 源代码 -> 词法分析器 -> 语法分析器 -> 语义分析器 -> 字符码生成器 -> 最终生成字节码 ->类加载过程(加载、验证、准备、解析和初始化)
详细说明和流程图
先把 Java 代码编译成字节码,也就是把 .java 类型的文件编译成 .class 类型的文件。这个过程的大致执行流程:Java 源代码 -> 词法分析器 -> 语法分析器 -> 语义分析器 -> 字符码生成器 -> 最终生成字节码,其中任何一个节点执行失败就会造成编译失败; 把 class 文件放置到 Java 虚拟机,这个虚拟机通常指的是 Oracle 官方自带的 Hotspot JVM; Java 虚拟机使用类加载器(Class Loader)装载 class 文件; 类加载完成之后,会进行字节码效验,字节码效验通过之后 JVM 解释器会把字节码翻译成机器码交由操作系统执行。但不是所有代码都是解释执行的,JVM 对此做了优化,比如,以 Hotspot 虚拟机来说,它本身提供了 JIT(Just In Time)也就是我们通常所说的动态编译器,它能够在运行时将热点代码编译为机器码,这个时候字节码就变成了编译执行。Java 程序执行流程图如下:
JVM性能调优
调优命令有哪些?
Sun JDK监控和故障处理命令有jps jstat jmap jhat jstack jinfo jps,JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程。 jstat,JVM statistics Monitoring是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。 jmap,JVM Memory Map命令用于生成heap dump文件 jhat,JVM Heap Analysis Tool命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看 jstack,用于生成java虚拟机当前时刻的线程快照。 jinfo,JVM Configuration info 这个命令作用是实时查看和调整虚拟机运行参数。
调优工具
常用调优工具分为两类,jdk自带监控工具:jconsole和jvisualvm,第三方有:MAT(Memory Analyzer Tool)、GChisto。 jconsole,Java Monitoring and Management Console是从java5开始,在JDK中自带的java监控和管理控制台,用于对JVM中内存,线程和类等的监控 jvisualvm,jdk自带全能工具,可以分析内存快照、线程快照;监控内存变化、GC变化等。 MAT,Memory Analyzer Tool,一个基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗 GChisto,一款专业分析gc日志的工具
你知道哪些JVM性能调优
?
JVM性能调优都做了什么?
性能监控、问题排查、参数调整
从SQL、JVM、架构、数据库四个方面讲讲优化思路
JVM的编译优化
如何理解内存泄漏问题?有哪些情况会导致内存泄漏?如何解决?
JVM如何调优?参数怎么调?
生产环境发生了内存溢出该如何处理?
生产环境应该给服务器分配多少内存合适?
如何对垃圾回收器的性能进行调优?
生产环境CPU负载飙高该如何处理?
生产环境应该给应用分配多少线程合适?
不加log,如何确定请求是否执行了某一行代码?
不加log,如何试试产看某个方法的入参与返回值?
为什么要调优?
防止出现OOm
解决OOM
减少Full GC出现的频率
你熟悉的JVM调优参数,使用过哪些调优工具?
JVM相关的分析工具有使用过哪些?具体的性能调优步骤吗?
如何监控和诊断JVM堆内和堆外内存使用?
生产环境中能够随随便便的dump吗?
小堆影响不大,大堆会有服务暂停或卡顿(加live可以缓解),dump前会有FGC
能否对JVM调优,让其几乎不发生Full GC?
可以,把年轻代的空间放大,并且设置大对象的上线设置大一些
JavaWeb
Spring
概论
Spring框架中都用到了哪些设计模式?
(1)工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例; (2)单例模式:Bean默认为单例模式。 (3)代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术; (4)模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。 (5)观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现--ApplicationListener。
提到的Aware相关接口指的是什么?平常会自己会用Aware相关接口吗?Bean生命周期这么长是为什么?
XXXAware在spring里表示对XXX可以感知,通俗点解释就是:如果在某个类里面想要使用spring的一些东西,就可以通过实现XXXAware接口告诉spring,spring看到后就会给你送过来,而接收的方式是通过实现接口唯一的方法set-XXX。比如,有一个类想要使用当前的ApplicationContext,那么我们只需要让它实现ApplicationContextAware接口,然后实现接口中唯一的方法void setApplicationContext(ApplicationContext applicationContext)就可以了,spring会自动调用这个方法将applicationContext传给我们,我们只需要接收就可以了
Spring框架的设计目标,设计理念,和核心是什么
Spring设计目标:Spring为开发者提供一个一站式轻量级应用开发平台; Spring设计理念:在JavaEE开发中,支持POJO和JavaBean开发方式,使应用面向接口开发,充分支持OO(面向对象)设计方法;Spring通过IoC容器实现对象耦合关系的管理,并实现依赖反转,将对象之间的依赖关系交给IoC容器,实现解耦; Spring框架的核心:IoC容器和AOP模块。通过IoC容器管理POJO对象以及他们之间的耦合关系;通过AOP以动态非侵入的方式增强服务。 IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。
BeanFactory和ApplicationContext的关系?
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。
(1)BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:
(2)①BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化 ②ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。 ③相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
什么是spring?
Spring是一个轻量级Java开发框架,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题
AOP
Spring的IOC和AOP机制?
spring的IoC容器是spring的核心,spring AOP是spring框架的重要组成部分。
IOC:控制反转也叫依赖注入。利用了工厂模式 将对象交给容器管理,你只需要在spring配置文件总配置相应的bean,以及设置相关的属性,让spring容器来生成类的实例对象以及管理对象。在spring容器启动的时候,spring会把你在配置文件中配置的bean都初始化好,然后在你需要调用的时候,就把它已经初始化好的那些bean分配给你需要调用这些bean的类(假设这个类名是A),分配的方法就是调用A的setter方法来注入,而不需要你在A里面new这些bean了
AOP:面向切面编程。(Aspect-Oriented Programming)
AOP可以说是对OOP的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。 将程序中的交叉业务逻辑(比如安全,日志,事务等),封装成一个切面,然后注入到目标对象(具体业务逻辑)中去。 实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码.
Spring的AOP理解
OOP(Object-Oriented Programming)面向对象编程,允许开发者定义纵向的关系,但并适用于定义横向的关系,导致了大量代码的重复,而不利于各个模块的重用。 AOP(Aspect-Oriented Programming),一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。
AOP中用到了哪些注解?怎么做切面的优先级(先走切面2,再走切面1)?
Spring AOP实现原理?AOP应用场景?拦截器用来做什么业务?
动态代理+反射
比如日志、权限认证、事务处理等
拦截器也可用于像权限认证的操作
解释一下Spring AOP里面的几个名词
(1)切面(Aspect):切面是通知和切点的结合。通知和切点共同定义了切面的全部内容。 在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。 (2)连接点(Join point):指方法,在Spring AOP中,一个连接点 总是 代表一个方法的执行。 应用可能有数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。 (3)通知(Advice):在AOP术语中,切面的工作被称为通知。 (4)切入点(Pointcut):切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。 (5)引入(Introduction):引入允许我们向现有类添加新方法或属性。 (6)目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。它通常是一个代理对象。也有人把它叫做 被通知(adviced) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。 (7)织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有多少个点可以进行织入: 编译期:切面在目标类编译时被织入。AspectJ的织入编译器是以这种方式织入切面的。 类加载期:切面在目标类加载到JVM时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入就支持以这种方式织入切面。 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面。
IOC
Spring的IOC理解
(1)IOC就是控制反转,是指创建对象的控制权的转移,以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。DI依赖注入,和控制反转是同一个概念的不同角度的描述,即 应用程序在运行时依赖IoC容器来动态注入对象需要的外部资源。 (2)最直观的表达就是,IOC让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。 (3)Spring的IOC有三种注入方式 :构造器注入、setter方法注入、根据注解注入。 IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。
Spring IoC中Bean的生命周期?谁来管理Bean的生命周期?
首先说一下Servlet的生命周期:实例化,初始init,接收请求service,销毁destroy; Spring上下文中的Bean生命周期也类似,如下: (1)实例化Bean: 对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。 (2)设置对象属性(依赖注入): 实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成依赖注入。 (3)处理Aware接口: 接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean: ①如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的就是Spring配置文件中Bean的id值; ②如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。 ③如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文; (4)BeanPostProcessor: 如果想对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。 (5)InitializingBean 与 init-method: 如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。 (6)如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术; 以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。 (7)DisposableBean: 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法; (8)destroy-method: 最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。
BeanFactory来管理Bean的生命周期
依赖注入的方式有几种/SpringIOC注入bean有几种方式
一、构造器注入 将被依赖对象通过构造函数的参数注入给依赖对象,并且在初始化对象的时候注入。
二、setter方法注入 IoC Service Provider通过调用成员变量提供的setter函数将被依赖对象注入给依赖类
三、接口注入 依赖类必须要实现指定的接口,然后实现该接口中的一个函数,该函数就是用于依赖注入。该函数的参数就是要注入的对象。
IOC的优点是什么?
IOC 或 依赖注入把应用的代码量降到最低。 它使应用容易测试,单元测试不再需要单例和JNDI查找机制。 最小的代价和最小的侵入性使松散耦合得以实现。 IOC容器支持加载服务时的饿汉式初始化和懒加载。
如果在一个系统中有很多不同包下的bean名字是一样的,怎么解决注入时的冲突问题?
IoC 的实现机制/实现原理?
工厂模式加反射机制
什么是Spring的依赖注入?
控制反转IoC是一个很大的概念,可以用不同的方式来实现。其主要实现方式有两种:依赖注入和依赖查找 依赖注入:相对于IoC而言,依赖注入(DI)更加准确地描述了IoC的设计理念。所谓依赖注入(Dependency Injection),即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。
事务
Spring支持的事务管理类型, spring 事务实现方式有哪些?
Spring支持两种类型的事务管理: 编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。 声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。
Spring事务的实现方式和实现原理
Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。
说一下Spring的事务传播行为
spring事务的传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。 ① PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。 ② PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。 ③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。 ④ PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。 ⑤ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 ⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。 ⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。
说一下 spring 的事务隔离?
spring 有五大隔离级别,默认值为 ISOLATION_DEFAULT(使用数据库的设置),其他四个隔离级别和数据库的隔离级别一致: ISOLATION_DEFAULT:用底层数据库的设置隔离级别,数据库设置的是什么我就用什么; ISOLATION_READ_UNCOMMITTED:未提交读,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读); ISOLATION_READ_COMMITTED:提交读,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复读),SQL server 的默认级别; ISOLATION_REPEATABLE_READ:可重复读,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读),MySQL 的默认级别; ISOLATION_SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。 脏读 :表示一个事务能够读取另一个事务中还未提交的数据。比如,某个事务尝试插入记录 A,此时该事务还未提交,然后另一个事务尝试读取到了记录 A。 不可重复读 :是指在一个事务内,多次读同一数据。 幻读 :指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录,但是第二次同等条件下查询却有 n+1 条记录,这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据,同一个记录的数据内容被修改了,所有数据行的记录就变多或者变少了。
使用
Spring基于xml注入bean的几种方式
(1)Set方法注入; (2)构造器注入:①通过index设置参数的位置;②通过type设置参数类型; (3)静态工厂注入; (4)实例工厂;
请求到SSH框架的流程图画一下?
Spring的自动扫描怎么实现的?谁实现的?
开启自动扫描配置<context:component-scan base-package="com.wisely.highlight_spring4.ch1"/>
由ComponentScanBeanDefinitionParser的parse方法进行解析
真正的逻辑其实是在ClassPathBeanDefinitionScanner的doScan方法
Spring的优缺点是什么?
优点 *方便解耦,简化开发 Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护,交给Spring管理。 *AOP编程的支持 Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能。 *声明式事务的支持 只需要通过配置就可以完成对事务的管理,而无需手动编程。 *方便程序的测试 Spring对Junit4支持,可以通过注解方便的测试Spring程序。 *方便集成各种优秀框架 Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的直接支持(如:Struts、Hibernate、MyBatis等)。 *降低JavaEE API的使用难度 Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低。 缺点 *Spring明明一个很轻量级的框架,却给人感觉大而全 *Spring依赖反射,反射影响性能 *使用门槛升高,入门Spring需要较长时间
Spring中Autowired和Resource关键字的区别?
@Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用
@Resource默认按照ByName自动注入,@Resource有两个重要的属性:name和type,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略。
说一下你自己实现的动态代理是怎么写的
bean
Bean的默认作用范围是什么?其他的作用范围?
Spring容器中的bean可以分为5个范围: (1)singleton:默认,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。 (2)prototype:为每一个bean请求提供一个实例。 (3)request:为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。 (4)session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。 (5)global-session:全局作用域,global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中。全局作用域与Servlet中的session作用域效果相同。
Spring加载Bean的过程?
Bean的初始化加载过程,第一次由getBean方法开启,内部调用doGetBean方法,doGetBean方法主要流程有以下几个: 1 转换beanName,因为我们传入的name不一定是beanName,还可以传入别名aliasName 2 尝试从缓存中加载单例Bean,如果失败,则继续往下 3 进行Bean的实例化 4 原型模式的bean的依赖检查,Spring只会解决单例模式的循环依赖,对于原型模式的循环依赖都是直接抛异常 5 尝试从parentBeanFactory中获取Bean实例 6 依赖检查,如果依赖其他Bean,则需要先加载依赖的Bean 7 对不同的scope进行处理 8 实例化后进行类型转换
beanfacotry和facotrybean有什么区别?
BeanFactory是接口,提供了OC容器最基本的形式,给具体的IOC容器的实现提供了规范 FactoryBean也是接口,为IOC容器中Bean的实现提供了更加灵活的方式,FactoryBean在IOC容器的基础上给Bean的实现加上了一个简单工厂模式和装饰模式(如果想了解装饰模式参考:修饰者模式(装饰者模式,Decoration) 我们可以在getObject()方法中灵活配置。其实在Spring源码中有很多FactoryBean的实现类. 区别:BeanFactory是个Factory,也就是IOC容器或对象工厂,FactoryBean是个Bean。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似
Spring怎样解决循环依赖的问题?
循环依赖问题就是A->B->A,spring在创建A的时候,发现需要依赖B,因为去创建B实例,发现B又依赖于A,又去创建A,因为形成一个闭环,无法停止下来就可能会导致cpu计算飙升 如何解决这个问题呢?spring解决这个问题主要靠巧妙的三层缓存,所谓的缓存主要是指这三个map,singletonObjects主要存放的是单例对象,属于第一级缓存;singletonFactories属于单例工厂对象,属于第三级缓存;earlySingletonObjects属于第二级缓存,如何理解early这个标识呢?它表示只是经过了实例化尚未初始化的对象。Spring首先从singletonObjects(一级缓存)中尝试获取,如果获取不到并且对象在创建中,则尝试从earlySingletonObjects(二级缓存)中获取,如果还是获取不到并且允许从singletonFactories通过getObject获取,则通过singletonFactory.getObject()(三级缓存)获取。如果获取到了则移除对应的singletonFactory,将singletonObject放入到earlySingletonObjects,其实就是将三级缓存提升到二级缓存,这个就是缓存升级。spring在进行对象创建的时候,会依次从一级、二级、三级缓存中寻找对象,如果找到直接返回。由于是初次创建,只能从第三级缓存中找到(实例化阶段放入进去的),创建完实例,然后将缓存放到第一级缓存中。下次循环依赖的再直接从一级缓存中就可以拿到实例对象了。
使用@Autowired注解自动装配的过程是怎样的?
使用@Autowired注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置,<context:annotation-config />。 在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean: 如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据; 如果查询的结果不止一个,那么@Autowired会根据名称来查找; 如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。
代理
JDK动态代理和CGLIB动态代理的区别
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理: JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。 如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。 静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。
Spring 的 Controller 是单例还是多例?怎么保证并发的安全?
SpringMVC
什么是Spring MVC?简单介绍下你对Spring MVC的理解?
Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把模型-视图-控制器分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。
Servlet规范了解吗?Servlet的整个业务流程?session和cookie的区别?session怎么变成cookie,怎么变回session?谁来实现整个流程?
Spring MVC的优点?
(1)可以支持各种视图技术,而不仅仅局限于JSP; (2)与Spring框架集成(如IoC容器、AOP等); (3)清晰的角色分配:前端控制器(dispatcherServlet) , 请求到处理器映射(handlerMapping), 处理器适配器(HandlerAdapter), 视图解析器(ViewResolver)。 (4) 支持各种请求资源的映射策略
Spring MVC流程?
详细流程图
详细流程
1、 用户发送请求至前端控制器DispatcherServlet。 2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器。 3、 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。 4、 DispatcherServlet调用HandlerAdapter处理器适配器。 5、 HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。 6、 Controller执行完成返回ModelAndView。 7、 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。 8、 DispatcherServlet将ModelAndView传给ViewReslover视图解析器。 9、 ViewReslover解析后返回具体View。 10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。 11、 DispatcherServlet响应用户。
简约版
①根据request找到Handler;②根据Handler找到对应的HandlerAdapter;③用HandlerAdapter处理Handler;④调用processDispatchResult方法处理上面处理之后的结果
SpringMVC有什么重要的组件?
前端控制器(DispatcherServlet):接收请求,响应结果,相当于电脑的CPU。 处理器映射器(HandlerMapping):根据URL去查找处理器 处理器(Handler):(需要程序员去写代码处理逻辑的) 处理器适配器(HandlerAdapter):会把处理器包装成适配器,这样就可以支持多种类型的处理器,类比笔记本的适配器(适配器模式的应用) 视图解析器(ViewResovler):进行视图解析,多返回的字符串,进行处理,可以解析成对应的页面
SpringMVC怎么样设定重定向和转发的?
(1)转发:在返回值前面加"forward:",譬如"forward:user.do?name=method4"
(2)重定向:在返回值前面加"redirect:",譬如"redirect:http://www.baidu.com"
SpringMVC常用的注解有哪些?
@RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。
@RequestBody:注解实现接收http请求的json数据,将json转换为java对象。
@ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。
dispatchServlet怎样分发任务的
SpringBoot
什么是SpringBoot?为什么要用SpringBoot
Spring Boot 优点非常多,如: 一、独立运行 Spring Boot而且内嵌了各种servlet容器,Tomcat、Jetty等,现在不再需要打成war包部署到容器中,Spring Boot只要打成一个可执行的jar包就能独立运行,所有的依赖包都在一个jar包内。 二、简化配置 spring-boot-starter-web启动器自动依赖其他组件,简少了maven的配置。 三、自动配置 Spring Boot能根据当前类路径下的类、jar包来自动配置bean,如添加一个spring-boot-starter-web启动器就能拥有web的功能,无需其他配置。 四、无代码生成和XML配置 Spring Boot配置过程中无代码生成,也无需XML配置文件就能完成所有配置工作,这一切都是借助于条件注解完成的,这也是Spring4.x的核心功能之一。 五、应用监控 Spring Boot提供一系列端点可以监控服务及应用,做健康检测。
Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?
启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解: @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。 @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。 @ComponentScan:Spring组件扫描。
运行Spring Boot有哪几种方式?
1)打包用命令或者放到容器中运行 2)用 Maven/Gradle 插件运行 3)直接执行 main 方法运行
如何理解 Spring Boot 中的 Starters?
Starters是什么: Starters可以理解为启动器,它包含了一系列可以集成到应用里面的依赖包,你可以一站式集成Spring及其他技术,而不需要到处找示例代码和依赖包。如你想使用Spring JPA访问数据库,只要加入spring-boot-starter-data-jpa启动器依赖就能使用了。Starters包含了许多项目中需要用到的依赖,它们能快速持续的运行,都是一系列得到支持的管理传递性依赖。 Starters命名: Spring Boot官方的启动器都是以spring-boot-starter-命名的,代表了一个特定的应用类型。第三方的启动器不能以spring-boot开头命名,它们都被Spring Boot官方保留。一般一个第三方的应该这样命名,像mybatis的mybatis-spring-boot-starter。
Spring Boot 需要独立的容器运行吗?
可以不需要,内置了 Tomcat/ Jetty 等容器。
Spring Boot中的监视器是什么?
Spring boot actuator是spring启动框架中的重要功能之一。Spring boot监视器可帮助您访问生产环境中正在运行的应用程序的当前状态。有几个指标必须在生产环境中进行检查和监控。即使一些外部应用程序可能正在使用这些服务来向相关人员触发警报消息。监视器模块公开了一组可直接作为HTTP URL访问的REST端点来检查状态。
springboot常用的starter有哪些
spring-boot-starter-web 嵌入tomcat和web开发需要servlet与jsp支持
spring-boot-starter-data-jpa 数据库支持
spring-boot-starter-data-redis redis数据库支持
spring-boot-starter-data-solr solr支持
mybatis-spring-boot-starter 第三方的mybatis集成starter
SpringBoot 实现热部署有哪几种方式?
主要有两种方式: Spring Loaded Spring-boot-devtools
如何重新加载Spring Boot上的更改,而无需重新启动服务器?
这可以使用DEV工具来实现。通过这种依赖关系,您可以节省任何更改,嵌入式tomcat将重新启动。 Spring Boot有一个开发工具(DevTools)模块,它有助于提高开发人员的生产力。Java开发人员面临的一个主要挑战是将文件更改自动部署到服务器并自动重启服务器。 开发人员可以重新加载Spring Boot上的更改,而无需重新启动服务器。这将消除每次手动部署更改的需要。Spring Boot在发布它的第一个版本时没有这个功能。 这是开发人员最需要的功能。DevTools模块完全满足开发人员的需求。该模块将在生产环境中被禁用。它还提供H2数据库控制台以更好地测试应用程序。 org.springframework.boot spring-boot-devtools true
Spring Boot 的核心配置文件有哪几个?它们的区别是什么?
核心配置文件是 application 和 bootstrap 配置文件。 application 配置文件这个容易理解,主要用于 Spring Boot 项目的自动化配置。 bootstrap 配置文件有以下几个应用场景。 使用 Spring Cloud Config 配置中心时,这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息; 一些固定的不能被覆盖的属性; 一些加密/解密的场景;
Spring Boot、Spring MVC 和 Spring 有什么区别?
1、Spring Spring最重要的特征是依赖注入。所有 SpringModules 不是依赖注入就是 IOC 控制反转。 当我们恰当的使用 DI 或者是 IOC 的时候,我们可以开发松耦合应用。松耦合应用的单元测试可以很容易的进行。 2、Spring MVC Spring MVC 提供了一种分离式的方法来开发 Web 应用。通过运用像 DispatcherServelet,MoudlAndView 和 ViewResolver 等一些简单的概念,开发 Web 应用将会变的非常简单。 3、SpringBoot Spring 和 SpringMVC 的问题在于需要配置大量的参数。Spring Boot 通过一个自动配置和启动的项来目解决这个问题。为了更快的构建产品就绪应用程序,Spring Boot 提供了一些非功能性特征。
SpringBoot和SpringCloud的区别?
SpringBoot专注于快速方便的开发单个个体微服务。 SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来, 为各个微服务之间提供,配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等集成服务 SpringBoot可以离开SpringCloud独立使用开发项目, 但是SpringCloud离不开SpringBoot ,属于依赖的关系. SpringBoot专注于快速、方便的开发单个微服务个体,SpringCloud关注全局的服务治理框架。
什么是 YAML?
YAML 是一种人类可读的数据序列化语言。它通常用于配置文件。与属性文件相比,如果我们想要在配置文件中添加复杂的属性,YAML 文件就更加结构化,而且更少混淆。可以看出 YAML 具有分层配置数据。
Spring Boot 中如何解决跨域问题 ?
之前我们是在 XML 文件中配置 CORS ,现在可以通过实现WebMvcConfigurer接口然后重写addCorsMappings方法解决跨域问题。
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowCredentials(true) .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .maxAge(3600); } }
SpringCloud
Spring Cloud用到什么东西?如何实现负载均衡?服务挂了注册中心怎么判断?
JPA
JPA规范
Mybatis
Mybatis整合Springboot做表的操作时,环境搭建过程
什么是MyBatis
(1)Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态sql,可以严格控制sql执行性能,灵活度高。 (2)MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。 (3)通过xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过java对象和 statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。(从执行sql到返回result的过程)。
MyBatis的优点和缺点
优点
(1)基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。
(2)与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;
(3)很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)。
(4)能够与Spring很好的集成;
(5)提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。
缺点
(1)SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。
(2)SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
#{}和${}的区别是什么?
#{}是预编译处理,${}是字符串替换。 Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值; Mybatis在处理${}时,就是把${}替换成变量的值。 使用#{}可以有效的防止SQL注入,提高系统安全性。
当实体类中的属性名和表中的字段名不一样 ,怎么办 ?
第1种: 通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
t id=”selectorder” parametertype=”int” resultetype=”me.gacl.domain.order”> select order_id id, order_no orderno ,order_price price form orders where order_id=#{id}; </select>
第2种: 通过来映射字段名和实体类属性名的一一对应的关系。
<select id="getOrder" parameterType="int" resultMap="orderresultmap"> select * from orders where order_id=#{id} </select> <resultMap type=”me.gacl.domain.order” id=”orderresultmap”> <!–用id属性来映射主键字段–> <id property=”id” column=”order_id”> <!–用result属性来映射非主键字段,property为实体类属性名,column为数据表中的属性–> <result property = “orderno” column =”order_no”/> <result property=”price” column=”order_price” /> </reslutMap>
Mybatis是如何进行分页的?分页插件的原理是什么?
Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。 它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。 当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。
Mybatis的一级、二级缓存
1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。 2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置 ; 3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear 掉并重新更新,如果开启了二级缓存,则只根据配置判断是否刷新。
mybatis缓存在分布式环境下的一致性如何解决
操作系统
CPU
select、poll和epoll
DCL单例(double Check Lock)到底需不需要volatile?
需要,因为CPU会乱序执行,第二次判断的时候可能达到的是第一次的半初始化状态
volatile的作用是禁止重排序
CPU层面如何禁止重排序?
内存屏障,对某部分内存做操作时前后添加的屏障,屏障前后的操作不可以乱序执行
如何实现内存屏障?(有序性保障)
CPU
使用三个汇编原语:lfence mfence sfence
使用总线锁
lock汇编指令
JVM层面的有效性保障
JSR内存屏障
volatile的实现细节
在读写之前都加屏障
happends-before 8个原则,不能进行指令重排(Java里的)
hotspot实现是依赖汇编指令:lock:addl 该指令能实现可见性和禁止重排序
线程
说一说进程所占的虚拟内存和物理内存是什么样的
进程和线程的区别?平常的开发环境是Windows还是Linux?Linux命令知道哪些?要在多个文本文件中找一个关键词用什么命令?(grep)网络相关的命令是什么?
进程和线程的区别
进程是分配资源单位,线程是执行单位(调度单位)
解释:新建进程的时候,会分配对应的内存空间,而新建线程进行执行的时候不会 ,它只能使用对应进程的资源
什么是纤程/协程/绿色线程
线程的线程
JVM层级模拟出来的线程
Fiber VS Thread
JDK还不支持 Fiber,需要引用第三方类库
协程相对线程,速度更快,效率更高
一核CPU,写多线程是否有意义
有,比如在等待数据的时候,CPU处于空闲,如果利用起来能提高效率
多线程配置,是否线程数越多越好
不是,线程切换需要消耗资源
为什么Fiber效率更高(为啥和内核态打交道的线程效率低)?
程序调用内核态需要有发出0x80中断或调用sysenter原语
线程数应该配置多少
什么是线程撕裂者/超线程
一个计算单元(ALU)对应两个寄存器,相当于一核对应两个线程
锁
CPU都有哪些锁
1 处理器自动保证基本内存操作的原子性 首先处理器会自动保证基本的内存操作的原子性。处理器保证从系统内存当中读取或者写入一个字节是原子的,意思是当一个处理器读取一个字节时,其他处理器不能访问这个字节的内存地址。奔腾6和最新的处理器能自动保证单处理器对同一个缓存行里进行16/32/64位的操作是原子的,但是复杂的内存操作处理器不能自动保证其原子性,比如跨总线宽度,跨多个缓存行,跨页表的访问。但是处理器提供总线锁定和缓存锁定两个机制来保证复杂内存操作的原子性。
2 使用总线锁保证原子性 第一个机制是通过总线锁保证原子性。如果多个处理器同时对共享变量进行读改写(i++就是经典的读改写操作)操作,那么共享变量就会被多个处理器同时进行操作,这样读改写操作就不是原子的,操作完之后共享变量的值会和期望的不一致,举个例子:如果i=1,我们进行两次i++操作,我们期望的结果是3,但是有可能结果是2。如下图 原因是有可能多个处理器同时从各自的缓存中读取变量i,分别进行加一操作,然后分别写入系统内存当中。那么想要保证读改写共享变量的操作是原子的,就必须保证CPU1读改写共享变量的时候,CPU2不能操作缓存了该共享变量内存地址的缓存。 处理器使用总线锁就是来解决这个问题的。所谓总线锁就是使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,那么该处理器可以独占使用共享内存。
3 使用缓存锁保证原子性 第二个机制是通过缓存锁定保证原子性。在同一时刻我们只需保证对某个内存地址的操作是原子性即可,但总线锁定把CPU和内存之间通信锁住了,这使得锁定期间,其他处理器不能操作其他内存地址的数据,所以总线锁定的开销比较大,最近的处理器在某些场合下使用缓存锁定代替总线锁定来进行优化。 频繁使用的内存会缓存在处理器的L1,L2和L3高速缓存里,那么原子操作就可以直接在处理器内部缓存中进行,并不需要声明总线锁,在奔腾6和最近的处理器中可以使用“缓存锁定”的方式来实现复杂的原子性。所谓“缓存锁定”就是如果缓存在处理器缓存行中内存区域在LOCK操作期间被锁定,当它执行锁操作回写内存时,处理器不在总线上声言LOCK#信号,而是修改内部的内存地址,并允许它的缓存一致性机制来保证操作的原子性,因为缓存一致性机制会阻止同时修改被两个以上处理器缓存的内存区域数据,当其他处理器回写已被锁定的缓存行的数据时会起缓存行无效,在例1中,当CPU1修改缓存行中的i时使用缓存锁定,那么CPU2就不能同时缓存了i的缓存行。 但是有两种情况下处理器不会使用缓存锁定。第一种情况是:当操作的数据不能被缓存在处理器内部,或操作的数据跨多个缓存行(cache line),则处理器会调用总线锁定。第二种情况是:有些处理器不支持缓存锁定。对于Inter486和奔腾处理器,就算锁定的内存区域在处理器的缓存行中也会调用总线锁定。 以上两个机制我们可以通过Inter处理器提供了很多LOCK前缀的指令来实现。比如位测试和修改指令BTS,BTR,BTC,交换指令XADD,CMPXCHG和其他一些操作数和逻辑指令,比如ADD(加),OR(或)等,被这些指令操作的内存区域就会加锁,导致其他处理器不能同时访问它。
轻量级锁一定比重量级锁效率高?
轻量级和重量级的最重要的区别
轻量级锁需要消耗CPU资源,重量级锁不需要
两种情况下,轻量级锁和重量级锁的效率比较
第一种
拥有锁的指令执行时间断,下一条等待的指令如果采用的是轻量级锁,会迅速响应获取锁,占用的CPU资源较少,此时应该用轻量级锁
第二种
拥有锁的指令执行时间长,后面所有的指令数量较多并且采用的是轻量级锁,会造成CPU极大的资源消耗,此时应该用重量级锁
打开偏向锁是否效率一定会提升,为什么?
不一定,比如在知道某个对象一开始创建的时候就会有并发情况,导致锁会逐级升级,这个过程相对直接关闭偏向锁会慢,因为会有一个撕掉标签(偏向锁撤销)的过程
主线程启动多个子线程,当其中一个返回false之后,其他子线程取消
内存管理
Linux有4G的物理内存,问开一个Java进程可以给8G内存吗?物理地址和虚拟地址的区别?
文件管理
Linux常用命令
查Java进程的命令,查文件中关键字向下500行的命令
Linux下一些根目录的命名规范介绍一下
介绍一下常用的Linux命令
Linux里查看CPU占用的命令?怎么看CPU占用?
远程调用Shell脚本用到哪些命令?
Linux 从一个文件夹中找到文件名包含某些key的文件的命令
Linux /etc /usr这两个目录下分别存放的什么文件
用什么命令打开大文件,比如5G这么大(不应该用vim,应该用grep范围查)
Linux查看CPU核数命令
查看文件头10行用那个命令
如果不熟悉一个命令怎么办(当然是-h或者--help啦)
其他
手写程序运行过程及涉及到的计算机组件
局部性原理
空间局部性原理
读取使用要给变量的时候,很大可能接下里的指令马上要用到
时间局部性原理
Centos和Linux的关系?
Centos是Linux众多的发行版本之一,其开源免费
网络
IO
OSI七层模型
OSI7层次模型
OSI七层模型
什么是OSI七层模型
Open System Interconnect的缩写,意为开放式系统互联,是ISO组织制定的用于计算机和通信系统间互联的标准体系
OSI分别是哪七层模型
应用层、表示层、会话层、传输控制层、网络层、链路层、物理层
每层的功能、数据单元以及对应的设备、每层都有那些协议
TCP/IP协议
TCP三次握手过程中什么阶段容易被攻击?DDos攻击?
第二阶段,此时处于半连接状态,等到client响应第三次握手,如果无响应,则需要等待超时,会消耗连接资源(泛洪攻击)
TCP SYN 泛洪
对于TCP协议,当客户端向服务器发起连接请求并初始化时,服务器一端的协议栈会留一块缓冲区来处理“握手”过程中的信息交换。请求建立连接时发送的数据包的包头SYN位就表明了数据包的顺序,攻击者可以利用在短时间内快速发起大量连接请求,以致服务器来不及响应。同时攻击者还可以伪造源IP地址。也就是说攻击者发起大量连接请求,然后挂起在半连接状态,以此来占用大量服务器资源直到拒绝服务。虽然缓冲区中的数据在一段时间内(通常是三分钟)都没有回应的话,就会被丢弃,但在这段时间内,大量半连接足以耗尽服务器资源。
TCP的(快重传)慢启动
TCP 在刚建立连接完成后,首先是有个慢启动的过程,这个慢启动的意思就是一点一点的提高发送数据包的数量,如果一上来就发大量的数据,这不是给网络添堵吗?
慢启动的算法记住一个规则就行:当发送方每收到一个 ACK,拥塞窗口 cwnd 的大小就会加 1。
TCP怎么优化丢包重传
超时重传
快速重传
SACK
D-SACK
TCP 协议与 UDP 协议有什么区别?
TCP(Tranfer Control Protocol)的缩写,是一种面向连接的保证传输的协议,在传输数据流前,双方会先建立一条虚拟的通信道。可以很少差错传输数据。 UDP(User DataGram Protocol)的缩写,是一种无连接的协议,使用UDP传输数据时,每个数据段都是一个独立的信息,包括完整的源地址和目的地,在网络上以任何可能的 路径传到目的地,因此,能否到达目的地,以及到达目的地的时间和内容的完整性都不能保证。 所以TCP必UDP多了建立连接的时间。相对UDP而言,TCP具有更高的安全性和可靠性。 TCP协议传输的大小不限制,一旦连接被建立,双方可以按照一定的格式传输大量的数据,而UDP是一个不可靠的协议,大小有限制,每次不能超过64K。
TCP协议的三次握手和四次挥手过程?
TCP (Transmission Control Protocol,传输控制协议)
什么是TCP协议?
面向连接的可靠的传输协议
连接的关键?
建立关联并且分配资源,重点是资源,因为请求不是持续的(虚无的)
三次握手
什么是三次握手
指建立一个 TCP 连接时需要客户端和服务器端总共发送三个包以确认连接的建立
具体的握手流程
四次挥手
什么是四次挥手
断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开
如何追踪握手和挥手过程
strace
tcpdump
网络寻址的过程
TCP连接是全双工的吗?
是
什么是单工、半双工、全双工
单工
数据只在一个方向上传输,不能实现双方通信
电视、广播
半双工
允许数据在两个方向上传输,但是同一时间数据只能在一个方向上传输,其实际上是切换的单工
对讲机
全双工
允许数据在两个方向上同时传输
手机
示意图
什么是SDN
Http和Https
发起一个HTTP请求的过程?刚才提到DNS,DNS用的什么传输层协议?为什么说用的是TCP协议?(我说其实我不清楚是用的什么协议)他说那让你设计的话用什么协议比较合适?(思考了一下,说觉得UDP比较合适,比较轻量不占用服务器带宽,查了一下TCP和UDP都有,用在不同情境下)
什么是DNS?
DNS (Domain Name System 的缩写)的作用非常简单,就是根据域名查出IP地址
DNS使用什么传输协议?
TCP和UDP都有使用
分别在什么情况下用这两种协议?为什么?
DNS在区域传输的时候使用TCP协议,其他时候使用UDP协议
DNS区域传输的时候使用TCP协议: 1.辅域名服务器会定时(一般3小时)向主域名服务器进行查询以便了解数据是否有变动。如有变动,会执行一次区域传送,进行数据同步。区域传送使用TCP而不是UDP,因为数据同步传送的数据量比一个请求应答的数据量要多得多。 2.TCP是一种可靠连接,保证了数据的准确性。 域名解析时使用UDP协议: 客户端向DNS服务器查询域名,一般返回的内容都不超过512字节,用UDP传输即可。不用经过三次握手,这样DNS服务器负载更低,响应更快。理论上说,客户端也可以指定向DNS服务器查询时用TCP,但事实上,很多DNS服务器进行配置的时候,仅支持UDP查询包。
HTTPS中双方如何协商加密协议
SSL/TSL中使用Diffie-Hellman密钥加密算法
HTTP1.0/1.1/2.0的区别有哪些
http协议版本
HTTP/0.9:1991年发布,极其简单,只有一个get命令;
HTTP/1.0:1996年5月发布,增加了大量内容;
请求与响应支持头域
响应对象以一个响应状态行开始
响应对象不只限于超文本
开始支持客户端通过POST方法向Web服务器提交数据,支持GET、HEAD、POST方法
(短连接)每一个请求建立一个TCP连接,请求完成后立马断开连接。这将会导致2个问题:连接无法复用,head of line blocking。连接无法复用会导致每次请求都经历三次握手和慢启动。三次握手在高延迟的场景下影响较明显,慢启动则对文件类请求影响较大。head of line blocking会导致带宽无法被充分利用,以及后续健康请求被阻塞
HTTP/1.1:1997年1月发布,进一步完善HTTP协议,是目前最流行的版本;
Persistent Connection(keepalive连接):允许HTTP设备在事务处理结束之后将TCP连接保持在打开的状态,以便未来的HTTP请求重用现在的连接,直到客户端或服务器端决定将其关闭为止。在HTTP1.0中使用长连接需要添加请求头 Connection: Keep-Alive,而在HTTP 1.1 所有的连接默认都是长连接,除非特殊声明不支持( HTTP请求报文首部加上Connection: close )。服务器端按照FIFO原则来处理不同的Request。
chunked编码传输:该编码将实体分块传送并逐块标明长度,直到长度为0块表示传输结束,这在实体长度未知时特别有用(比如由数据库动态产生的数据)
字节范围请求:HTTP1.1支持传送内容的一部分。比方说,当客户端已经有内容的一部分,为了节省带宽,可以只向服务器请求一部分。该功能通过在请求消息中引入了range头域来实现,它允许只请求资源的某个部分。在响应消息中Content-Range头域声明了返回的这部分对象的偏移值和长度。如果服务器相应地返回了对象所请求范围的内容,则响应码206(Partial Content)
Pipelining(请求流水线)
请求消息和响应消息都支持Host头域:在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。因此,Host头的引入就很有必要了。
新增了一批Request method:HTTP1.1增加了OPTIONS,PUT, DELETE, TRACE, CONNECT方法
缓存处理:HTTP/1.1在1.0的基础上加入了一些cache的新特性,引入了实体标签,一般被称为e-tags,新增更为强大的Cache-Control头
SPDY :2009年谷歌发布SPDY协议,主要解决HTTP/1.1效率不高的问题;
HTTP/2 :2015年借鉴SPDY的HTTP/2发布。
多路复用(二进制分帧)。HTTP 2.0最大的特点:不会改动HTTP 的语义,HTTP 方法、状态码、URI 及首部字段,等等这些核心概念上一如往常,却能致力于突破上一代标准的性能限制,改进传输性能,实现低延迟和高吞吐量。而之所以叫2.0,是在于新增的二进制分帧层。在二进制分帧层上, HTTP 2.0 会将所有传输的信息分割为更小的消息和帧,并对它们采用二进制格式的编码 ,其中HTTP1.x的首部信息会被封装到Headers帧,而我们的request body则封装到Data帧里面。
HTTP 2.0 通信都在一个连接上完成,这个连接可以承载任意数量的双向数据流。相应地,每个数据流以消息的形式发送,而消息由一或多个帧组成,这些帧可以乱序发送,然后再根据每个帧首部的流标识符重新组装。
头部压缩:当一个客户端向相同服务器请求许多资源时,像来自同一个网页的图像,将会有大量的请求看上去几乎同样的,这就需要压缩技术对付这种几乎相同的信息。
随时复位:HTTP1.1一个缺点是当HTTP信息有一定长度大小数据传输时,你不能方便地随时停止它,中断TCP连接的代价是昂贵的。使用HTTP2的RST_STREAM将能方便停止一个信息传输,启动新的信息,在不中断连接的情况下提高带宽利用效率。
服务器端推流:Server Push。客户端请求一个资源X,服务器端判断也许客户端还需要资源Z,在无需事先询问客户端情况下将资源Z推送到客户端,客户端接受到后,可以缓存起来以备后用。
优先权和依赖:每个流都有自己的优先级别,会表明哪个流是最重要的,客户端会指定哪个流是最重要的,有一些依赖参数,这样一个流可以依赖另外一个流。优先级别可以在运行时动态改变,当用户滚动页面时,可以告诉浏览器哪个图像是最重要的,你也可以在一组流中进行优先筛选,能够突然抓住重点流。
讲一下HTTPS?HTTPS连接的认证过程
https交互流程
http和https的区别,http1.x和http2.0的区别,SSL和TSL之间的区别
GET请求和POST请求的区别?
实质都是TCP连接,大多限制是在浏览器层做的,比较的的区别:GET产生一个TCP数据包;POST产生两个TCP数据包,
对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)
HTTP常见错误码?
http常见请求码
1xx(临时响应,表示临时响应并需要请求者继续执行操作的状态代码)
100 (继续) 请求者应当继续提出请求。服务器返回此代码表示已收到请求的第一部分,正在等待其余部分。
101 (切换协议) 请求者已要求服务器切换协议,服务器已确认并准备切换。
2xx (成功,表示成功处理了请求的状态代码)
200 (成功) 服务器已成功处理了请求。通常,这表示服务器提供了请求的网页。
201 (已创建) 请求成功并且服务器创建了新的资源。
202 (已接受) 服务器已接受请求,但尚未处理。
203 (非授权信息) 服务器已成功处理了请求,但返回的信息可能来自另一来源。
204 (无内容) 服务器成功处理了请求,但没有返回任何内容。
205 (重置内容) 服务器成功处理了请求,但没有返回任何内容。
206 (部分内容) 服务器成功处理了部分 GET 请求。
3xx (重定向)
300 (多种选择) 针对请求,服务器可执行多种操作。服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择。
301 (永久移动) 请求的网页已永久移动到新位置。服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。
302 (临时移动) 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。
303 (查看其他位置) 请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码。
304 (未修改) 自从上次请求后,请求的网页未修改过。服务器返回此响应时,不会返回网页内容。
305 (使用代理) 请求者只能使用代理访问请求的网页。如果服务器返回此响应,还表示请求者应使用代理。
307 (临时重定向) 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求
4xx(请求错误)
400 (错误请求) 服务器不理解请求的语法。
401 (未授权) 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。
403 (禁止) 服务器拒绝请求。
404 (未找到) 服务器找不到请求的网页。
405 (方法禁用) 禁用请求中指定的方法。
406 (不接受) 无法使用请求的内容特性响应请求的网页。
407 (需要代理授权) 此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理。
408 (请求超时) 服务器等候请求时发生超时。
409 (冲突) 服务器在完成请求时发生冲突。服务器必须在响应中包含有关冲突的信息。
410 (已删除) 如果请求的资源已永久删除,服务器就会返回此响应。
411 (需要有效长度) 服务器不接受不含有效内容长度标头字段的请求。
412 (未满足前提条件) 服务器未满足请求者在请求中设置的其中一个前提条件。
413 (请求实体过大) 服务器无法处理请求,因为请求实体过大,超出服务器的处理能力。
414 (请求的 URI 过长) 请求的 URI(通常为网址)过长,服务器无法处理。
415 (不支持的媒体类型) 请求的格式不受请求页面的支持。
416 (请求范围不符合要求) 如果页面无法提供请求的范围,则服务器会返回此状态代码。
417 (未满足期望值) 服务器未满足"期望"请求标头字段的要求。
5xx(服务器错误)
500 (服务器内部错误) 服务器遇到错误,无法完成请求。
501 (尚未实施) 服务器不具备完成请求的功能。例如,服务器无法识别请求方法时可能会返回此代码。
502 (错误网关) 服务器作为网关或代理,从上游服务器收到无效响应。
503 (服务不可用) 服务器目前无法使用(由于超载或停机维护)。通常,这只是暂时状态。
504 (网关超时) 服务器作为网关或代理,但是没有及时从上游服务器收到请求。
505 (HTTP 版本不受支持) 服务器不支持请求中所用的 HTTP 协议版本。
GET长度
浏览器限制,如谷歌限制长度为2MB
HTTP协议是什么?HTTP格式?
HTTP协议是超文本传输协议的缩写,英文是Hyper Text Transfer Protocol。它是从WEB服务器传输超文本标记语言(HTML)到本地浏览器的传送协议。
如何实现编解码及序列化
自定义报告协议及序列化方式
介绍自定义的rpc协议
基于netty等网络通信框架自定义协议开发
Websocket协议
websocket实现原理以及和别的通信方式的区别
SSH
安全
跨域
DDos攻击
DDos的前身 DoS (DenialofService)攻击,其含义是拒绝服务攻击,这种攻击行为使网站服务器充斥大量的要求回复的信息,消耗网络带宽或系统资源,导致网络或系统不胜负荷而停止提供正常的网络服务。而DDoS分布式拒绝服务,则主要利用 Internet上现有机器及系统的漏洞,攻占大量联网主机,使其成为攻击者的代理。当被控制的机器达到一定数量后,攻击者通过发送指令操纵这些攻击机同时向目标主机或网络发起DoS攻击,大量消耗其网络带和系统资源,导致该网络或系统瘫痪或停止提供正常的网络服务。由于DDos的分布式特征,它具有了比Dos远为强大的攻击力和破坏性。
防护策略
完全杜绝DDoS目前是不可能的,但通过适当的措施抵御大多数的DDoS攻击是可以做到的,基于攻击和防御都有成本开销的缘故,若通过适当的办法增强了抵御DDoS的能力,也就意味着加大了攻击者的攻击成本,那么绝大多数攻击者将无法继续下去而放弃,也就相当于成功的抵御了DDoS攻击。
采用高性能的网络设备
尽量避免NAT的使用
充足的网络带宽保证
升级主机服务器硬件
把网站做成静态页面
数据库
mysql
SQL
SQL优化
怎么去监控MySQL的性能问题?
linux操作系统层面指标监控
数据库监控软件
慢SQL日志分析
如何定位一个慢查询,一个服务有多条SQL你怎么快速定位
有哪些SQL优化需要注意的点
1、查询语句中不要使用select *
2、尽量减少子查询,使用关联查询(left join%2cright join%2cinner join)替代
3、减少使用IN或者NOT IN %2c使用exists,not exists或者关联查询语句替代
4、or 的查询尽量用 union或者union all 代替(在确认没有重复数据或者不用剔除重复数据时,union all会更好)
5、应尽量避免在 where 子句中使用!=或%26lt;%26gt;操作符,否则将引擎放弃使用索引而进行全表扫描。
6、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如: select id from t where num is null 可以在num上设置默认值0,确保表中num列没有null值,然后这样查询: select id from t where num=0
针对表结构对查询性能的影响,在建表时可以优化的点
*尽量使用TINYINT、SMALLINT、MEDIUM_INT作为整数类型而非INT,如果非负则加上UNSIGNED *VARCHAR的长度只分配真正需要的空间 *使用枚举或整数代替字符串类型 *尽量使用TIMESTAMP而非DATETIME, *单表不要有太多字段,建议在20以内 *避免使用NULL字段,很难查询优化且占用额外索引空间 *用整型来存IP
MySQL的慢sql优化一般如何来做?
1、没有索引。解决办法: 根据 where 和 order by 使用比较频繁的字段创建索引,提高查询效率 索引不宜过多,单表最好不要超过 6 个。索引过多会导致占用存储空间变大;insert、update 变慢 删除未使用的索引 2、索引未生效。解决办法: 避免在 where 子句中对字段进行 null 值判断,创建表默认值是 NULL。尽量使用 NOT NULL,或使用特殊值,如 0、-1 避免在 where 子句中使用 != 或 <> 操作符, MySQL 只有对以下操作符才使用索引:<、<=、=、>、>=、BETWEEN、IN、非 % 开头的 LIKE 避免在 where 子句中使用 or 来连接条件,可以使用 UNION 进行连接 能用 union all 就不用 union,union 过滤重复数据要耗费更多的 CPU 资源 避免部分 like 查询,如 '%ConstXiong%' 避免在索引列上使用计算、函数 in 和 not in 慎用,能用 between 不要用 in select 子句中避免使用 * 3、单表数据量太大。解决办法: 分页查询(在索引上完成排序分页操作、借助主键进行关联) 单表数据过大,进行分库分表 考虑使用非关系型数据库提高查询效率 全文索引场景较多,考虑使用 ElasticSearch、solr 提升性能的一些技巧: 尽量使用数字型字段 只需要一行数据时使用 limit 1 索引尽量选择较小的列 不需要的数据在 GROUP BY 之前过滤掉 大部分时候 exists、not exists 比 in、not in 效率(除了子查询是小表的情况使用 in 效率比 exists 高) 不确定长度的字符串字段使用 varchar/nvarchar,如使用 char/nchar 定长存储会带来空间浪费 不要使用 select *,去除不需要的字段查询 避免一次性查询过大的数据量 使用表别名,减少多表关联解析时间 多表 join 最好不超过 5 个,视图嵌套最好不超过 2 个 or 条件查询可以拆分成 UNION 多个查询 count(1) 比 count(*) 有效 判断是否存在数据使用 exists 而非 count,count 用来获取数据行数 ———————————————— 版权声明:本文为CSDN博主「ConstXiong」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/meism5/article/details/106725163
MYSQL优化,Explain 有哪些信息
id:选择标识符 select_type:表示查询的类型。 table:输出结果集的表 partitions:匹配的分区 type:表示表的连接类型 possible_keys:表示查询时,可能使用的索引 key:表示实际使用的索引 key_len:索引字段的长度 ref:列与索引的比较 rows:扫描出的行数(估算的行数) filtered:按表条件过滤的行百分比 Extra:执行情况的描述和说明
大表如何优化?(该题涉及内容较多, 必须要认真阅读博客)
当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下: 1. 限定数据的范围 务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内; 2. 读/写分离 经典的数据库拆分方案,主库负责写,从库负责读; 3. 垂直分区 根据数据库里面数据表的相关性进行拆分。 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。 简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。 如下图所示,这样来说大家应该就更容易理解了 垂直拆分的优点: 可以使得列数据变小,在查询时减少读取的Block数,减少I/O次数。此外,垂直分区可以简化表的结构,易于维护。 垂直拆分的缺点: 主键会出现冗余,需要管理冗余列,并会引起Join操作,可以通过在应用层进行Join来解决。此外,垂直分区会让事务变得更加复杂; 4. 水平分区 保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分可以支撑非常大的数据量。 水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。举个例子:我们可以将用户信息表拆分成多个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。 水平拆分可以支持非常大的数据量。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以 水平拆分最好分库 。 水平拆分能够 支持非常大的数据量存储,应用端改造也少,但 分片事务难以解决 ,跨节点Join性能较差,逻辑复杂。《Java工程师修炼之道》的作者推荐 尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度 ,一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,尽量选择客户端分片架构,这样可以减少一次和中间件的网络I/O。 下面补充一下数据库分片的两种常见方案: 客户端代理: 分片逻辑在应用端,封装在jar包中,通过修改或者封装JDBC层来实现。 当当网的 Sharding-JDBC 、阿里的TDDL是两种比较常用的实现。 中间件代理: 在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。 我们现在谈的 Mycat 、360的Atlas、网易的DDB等等都是这种架构的实现。
语法
给一个数据库表,ID、score两个字段分别代表学生ID和成绩,写SQL语句求ID=?的学生排第几名?
SELECT t.rankNum from (SELECT u.id,(@rank :=@rank+1) AS rankNum from t_score u,(SELECT @rank:=0) a ORDER BY u.score) t WHERE t.id=1 ;
添加索引的SQL语句?给一个abc三个字段的索引,where a=0 order by c能用到索引吗?where a=0 and b大于0 order by c能用到索引吗?Hash索引和B树索引的区别?Hash索引有区间查询吗?有没有用nosql?
手写mysql左连接查询,把表也模拟写出来,查询结果也写出来
sql里面的关键字,on、where、having 分别有什么作用?可以出现在同一条sql里面吗?
简单说一说drop、delete与truncate的区别?
SQL中的drop、delete、truncate都表示删除,但是三者有一些差别 delete和truncate只删除表的数据不删除表的结构 速度,一般来说: drop> truncate >delete delete语句是dml,这个操作会放到rollback segement中,事务提交之后才生效; 如果有相应的trigger,执行的时候将被触发. truncate,drop是ddl, 操作立即生效,原数据不放到rollback segment中,不能回滚. 操作不触发trigger.
什么是视图?
视图是一种虚拟的表,具有和物理表相同的功能。可以对视图进行增,改,查,操作,试图通常是有一个表或者多个表的行或列的子集。对视图的修改不影响基本表。它使得我们获取数据更容易,相比多表查询。
什么是内联接、左外联接、右外联接?
内联接(Inner Join):匹配2张表中相关联的记录。 左外联接(Left Outer Join):除了匹配2张表中相关联的记录外,还会匹配左表中剩余的记录,右表中未匹配到的字段用NULL表示。 右外联接(Right Outer Join):除了匹配2张表中相关联的记录外,还会匹配右表中剩余的记录,左表中未匹配到的字段用NULL表示。在判定左表和右表时,要根据表名出现在Outer Join的左右位置关系。
mysql有关权限的表都有哪几个
MySQL服务器通过权限表来控制用户对数据库的访问,权限表存放在mysql数据库里,由mysql_install_db脚本初始化。这些权限表分别user,db,table_priv,columns_priv和host。下面分别介绍一下这些表的结构和内容: user权限表:记录允许连接到服务器的用户帐号信息,里面的权限是全局级的。 db权限表:记录各个帐号在各个数据库上的操作权限。 table_priv权限表:记录数据表级的操作权限。 columns_priv权限表:记录数据列级的操作权限。 host权限表:配合db权限表对给定主机上数据库级操作权限作更细致的控制。这个权限表不受GRANT和REVOKE语句的影响。
SQL语句主要分为哪几类
数据定义语言DDL(Data Ddefinition Language)CREATE,DROP,ALTER 主要为以上操作 即对逻辑结构等有操作的,其中包括表结构,视图和索引。 数据查询语言DQL(Data Query Language)SELECT 这个较为好理解 即查询操作,以select关键字。各种简单查询,连接查询等 都属于DQL。 数据操纵语言DML(Data Manipulation Language)INSERT,UPDATE,DELETE 主要为以上操作 即对数据进行操作的,对应上面所说的查询操作 DQL与DML共同构建了多数初级程序员常用的增删改查操作。而查询是较为特殊的一种 被划分到DQL中。 数据控制功能DCL(Data Control Language)GRANT,REVOKE,COMMIT,ROLLBACK 主要为以上操作 即对数据库安全性完整性等有操作的,可以简单的理解为权限控制等。
六种关联查询
交叉连接(CROSS JOIN) 内连接(INNER JOIN) 外连接(LEFT JOIN/RIGHT JOIN) 联合查询(UNION与UNION ALL) 全连接(FULL JOIN) 交叉连接(CROSS JOIN)
UNION与UNION ALL的区别?
如果使用UNION ALL,不会合并重复的记录行 效率 UNION 高于 UNION ALL
练习
四个表 记录成绩,每个大约十万条记录,如何找到成绩最好的同学
数据类型
mysql有哪些数据类型
整数类型
TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT,分别表示1字节、2字节、3字节、4字节、8字节整数。任何整数类型都可以加上UNSIGNED属性,表示数据是无符号的,即非负整数。 长度:整数类型可以被指定长度,例如:INT(11)表示长度为11的INT类型。长度在大多数场景是没有意义的,它不会限制值的合法范围,只会影响显示字符的个数,而且需要和UNSIGNED ZEROFILL属性配合使用才有意义。 例子,假定类型设定为INT(5),属性为UNSIGNED ZEROFILL,如果用户插入的数据为12的话,那么数据库实际存储数据为00012。
实数类型
FLOAT、DOUBLE、DECIMAL。 DECIMAL可以用于存储比BIGINT还大的整型,能存储精确的小数。 而FLOAT和DOUBLE是有取值范围的,并支持使用标准的浮点进行近似计算。 计算时FLOAT和DOUBLE相比DECIMAL效率更高一些,DECIMAL你可以理解成是用字符串进行处理。
字符串类型
VARCHAR、CHAR、TEXT、BLOB VARCHAR用于存储可变长字符串,它比定长类型更节省空间。 VARCHAR使用额外1或2个字节存储字符串长度。列长度小于255字节时,使用1字节表示,否则使用2字节表示。 VARCHAR存储的内容超出设置的长度时,内容会被截断。 CHAR是定长的,根据定义的字符串长度分配足够的空间。 CHAR会根据需要使用空格进行填充方便比较。 CHAR适合存储很短的字符串,或者所有值都接近同一个长度。 CHAR存储的内容超出设置的长度时,内容同样会被截断。
枚举类型(ENUM)
把不重复的数据存储为一个预定义的集合。 有时可以使用ENUM代替常用的字符串类型。 ENUM存储非常紧凑,会把列表值压缩到一个或两个字节。 ENUM在内部存储时,其实存的是整数。 尽量避免使用数字作为ENUM枚举的常量,因为容易混乱。 排序是按照内部存储的整数
日期和时间类型
尽量使用timestamp,空间效率高于datetime, 用整数保存时间戳通常不方便处理。 如果需要存储微妙,可以使用bigint存储。 看到这里,这道真题是不是就比较容易回答了。
所有类型截图
存储引擎
innodb 和 mysalm的区别(mysql数据库默认存储引擎,有什么优点)
1. 事务支持 > MyISAM:强调的是性能,每次查询具有原子性,其执行数 度比 InnoDB 类型更快,但是不提供事务支持。 > InnoDB:提供事 务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚 (rollback)和崩溃修复能力(crash recovery capabilities)的事务安全 (transaction-safe (ACID compliant))型表。 2. InnoDB 支持行级锁,而 MyISAM 支持表级锁. >> 用户在操作 myisam 表时,select,update,delete,insert 语句都会给表自动 加锁,如果加锁以后的表满足 insert 并发的情况下,可以在表的尾部插 入新的数据。 3. InnoDB 支持 MVCC, 而 MyISAM 不支持 4. InnoDB 支持外键,而 MyISAM 不支持 5. 表主键 > MyISAM:允许没有任何索引和主键的表存在,索引都是保 存行的地址。 > InnoDB:如果没有设定主键或者非空唯一索引,就会 自动生成一个 6 字节的主键(用户不可见),数据是主索引的一部分,附 加索引保存的是主索引的值。 6. InnoDB 不支持全文索引,而 MyISAM 支持。 7. 可移植性、备份及恢复 > MyISAM:数据是以文件的形式存储,所以 在跨平台的数据转移中会很方便。在备份和恢复时可单独针对某个表进 行操作。 > InnoDB:免费的方案可以是拷贝数据文件、备份 binlog,或者用 mysqldump,在数据量达到几十 G 的时候就相对痛 苦了 8. 存储结构 > MyISAM:每个 MyISAM 在磁盘上存储成三个文件。第一 个文件的名字以表的名字开始,扩展名指出文件类型。.frm 文件存储表 定义。数据文件的扩展名为.MYD (MYData)。索引文件的扩展名 是.MYI (MYIndex)。 > InnoDB:所有的表都保存在同一个数据文件 中(也可能是多个文件,或者是独立的表空间文件),InnoDB 表的大 小只受限于操作系统文件的大小,一般为 2GB。
MySQL数据库引擎和应用场景/如何选择引擎?
MyISAM:以读写插入为主的应用程序,比如博客系统、新闻门户网站。 Innodb:更新(删除)操作频率也高,或者要保证数据的完整性;并发量高,支持事务和外键。比如OA自动化办公系统。
MySQL采用了什么存储引擎,为什么?
默认采用了innoDB
1. 事务支持 > MyISAM:强调的是性能,每次查询具有原子性,其执行数 度比 InnoDB 类型更快,但是不提供事务支持。 > InnoDB:提供事 务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚 (rollback)和崩溃修复能力(crash recovery capabilities)的事务安全 (transaction-safe (ACID compliant))型表。 2. InnoDB 支持行级锁,而 MyISAM 支持表级锁. >> 用户在操作 myisam 表时,select,update,delete,insert 语句都会给表自动 加锁,如果加锁以后的表满足 insert 并发的情况下,可以在表的尾部插 入新的数据。 3. InnoDB 支持 MVCC, 而 MyISAM 不支持 4. InnoDB 支持外键,而 MyISAM 不支持 5. 表主键 > MyISAM:允许没有任何索引和主键的表存在,索引都是保 存行的地址。 > InnoDB:如果没有设定主键或者非空唯一索引,就会 自动生成一个 6 字节的主键(用户不可见),数据是主索引的一部分,附 加索引保存的是主索引的值。 6. InnoDB 不支持全文索引,而 MyISAM 支持。 7. 可移植性、备份及恢复 > MyISAM:数据是以文件的形式存储,所以 在跨平台的数据转移中会很方便。在备份和恢复时可单独针对某个表进 行操作。 > InnoDB:免费的方案可以是拷贝数据文件、备份 binlog,或者用 mysqldump,在数据量达到几十 G 的时候就相对痛 苦了 8. 存储结构 > MyISAM:每个 MyISAM 在磁盘上存储成三个文件。第一 个文件的名字以表的名字开始,扩展名指出文件类型。.frm 文件存储表 定义。数据文件的扩展名为.MYD (MYData)。索引文件的扩展名 是.MYI (MYIndex)。 > InnoDB:所有的表都保存在同一个数据文件 中(也可能是多个文件,或者是独立的表空间文件),InnoDB 表的大 小只受限于操作系统文件的大小,一般为 2GB。
innodb索引种类
从数据结构角度
1、B+树索引(O(log(n))):关于B+树索引,可以参考 MySQL索引背后的数据结构及算法原理
2、hash索引:
3、FULLTEXT索引(现在MyISAM和InnoDB引擎都支持了)
4、R-Tree索引(用于对GIS数据类型创建SPATIAL索引)
从物理存储角度
1、聚集索引(clustered index)
2、非聚集索引(non-clustered index)
从逻辑角度
1、主键索引:主键索引是一种特殊的唯一索引,不允许有空值
2、普通索引或者单列索引
3、多列索引(复合索引):复合索引指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用复合索引时遵循最左前缀集合
4、唯一索引或者非唯一索引
5、空间索引:空间索引是对空间数据类型的字段建立的索引,MYSQL中的空间数据类型有4种,分别是GEOMETRY、POINT、LINESTRING、POLYGON。
数据库引擎有哪些
通过show engines;命令,可以查看mysql支持的存储引擎有9中,主要常用的有MyIsam、Innodb、Memory和Merge,另外还有CSV等不常用的引擎
MYISAM:全表锁,拥有较高的执行速度,不支持事务,不支持外键,并发性能差,占用空间相对较小,对事务完整性没有要求,以select、insert为主的应用基本上可以使用这引擎 Innodb:行级锁,提供了具有提交、回滚和崩溃回复能力的事务安全,支持自动增长列,支持外键约束,并发能力强,占用空间是MYISAM的2.5倍,处理效率相对会差一些 Memory:全表锁,存储在内容中,速度快,但会占用和数据量成正比的内存空间且数据在mysql重启时会丢失,默认使用HASH索引,检索效率非常高,但不适用于精确查找,主要用于那些内容变化不频繁的代码表
事务
MYSQL如何避免幻读?如何实现SERIALIZABLE?
mysql利用间隙锁来防止幻读
在Seriable隔离级别下,mysql的读要加共享锁阻止其他人的修改
什么是事务?
多条sql语句,要么全部成功,要么全部失败
事务的四大特性?
原子性(Atomic)、一致性(Consistency)、隔离性(Isolation)、持久性(Durabiliy)。简称ACID
详细说明
原子性:组成一个事务的多个数据库操作是一个不可分割的原子单元,只有所有操作都成功,整个事务才会提交。任何一个操作失败,已经执行的任何操作都必须撤销,让数据库返回初始状态。 一致性:事务操作成功后,数据库所处的状态和它的业务规则是一致的。即数据不会被破坏。如A转账100元给B,不管操作是否成功,A和B的账户总额是不变的。 隔离性:在并发数据操作时,不同的事务拥有各自的数据空间,它们的操作不会对彼此产生干扰 持久性:一旦事务提交成功,事务中的所有操作都必须持久化到数据库中。
并发事务带来哪些问题?
在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对同一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题。 脏读(Dirty read): 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。 丢失修改(Lost to modify): 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。 不可重复读(Unrepeatableread): 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。 幻读(Phantom read): 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
脏读,不可重复读,幻读
脏读 在一个事务中,读取了其他事务未提交的数据
不可重复读 在一个事务中,同一行记录被访问了两次却得到了不同的结果。
幻读 在一个事务中,同一个范围内的记录被读取时,其他事务向这个范围添加了新的记录。
Mysql怎么解决脏读,怎么解决幻读
事务隔离级别
ACID
本质是通过共享锁和互斥锁来控制
Mysql都有哪几种隔离级别?
READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。 READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。 REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。 SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
幻读和不可重复读的区别?
不可重复读的重点是修改比如多次读取一条记录发现其中某些列的值被修改,幻读的重点在于新增或者删除比如多次读取一条记录发现记录增多或减少了。
mysql默认的隔离级别?
可重复读(Repeated Read),可以通过SELECT @@tx_isolation;命令来查看
MySQL的事务隔离级别分别解决什么问题?
隔离级别 脏读 不可重复读 幻影读 READ-UNCOMMITTED √ √ √ READ-COMMITTED × √ √ REPEATABLE-READ × × √ SERIALIZABLE × × ×
索引
什么是索引
索引是对数据库表中一个或多个列的值进行排序的结构,建立索引有助于快速获取信息。
MySQL数据库,给一个用户表格,ID、用户名、性别、用户信息…,假设经常对性别字段进行查询,问怎么建立索引?为什么?假设用户名需要是唯一的,问怎么建索引?
性别不需要建立索引,因为性别不是个高选择性字段,用户名可以设置唯一索引,提高查询效率
MySQL里主要有哪些索引结构?哈希索引和B+树索引比较?
如果是等值查询,那么哈希索引明显有绝对优势,因为只需要经过一次算法即可找到相应的键值;当然了,这个前提是,键值都是唯一的。如果键值不是唯一的,就需要先找到该键所在位置,然后再根据链表往后扫描,直到找到相应的数据; 从示意图中也能看到,如果是范围查询检索,这时候哈希索引就毫无用武之地了,因为原先是有序的键值,经过哈希算法后,有可能变成不连续的了,就没办法再利用索引完成范围查询检索; 同理,哈希索引也没办法利用索引完成排序,以及like ‘xxx%’ 这样的部分模糊查询(这种部分模糊查询,其实本质上也是范围查询); 哈希索引也不支持多列联合索引的最左匹配规则; B+树索引的关键字检索效率比较平均,不像B树那样波动幅度大,在有大量重复键值情况下,哈希索引的效率也是极低的,因为存在所谓的哈希碰撞问题。
聚簇索引和非聚簇索引区别
InnoDB聚集索引的叶子节点存储行记录,因此, InnoDB必须要有,且只有一个聚集索引: (1)如果表定义了PK,则PK就是聚集索引; (2)如果表没有定义PK,则第一个not NULL unique列是聚集索引; (3)否则,InnoDB会创建一个隐藏的row-id作为聚集索引; 画外音:所以PK查询非常快,直接定位行记录。 InnoDB普通索引的叶子节点存储主键值。 画外音:注意,不是存储行记录头指针,MyISAM的索引叶子节点存储记录指针
非聚簇索引需要回表
MySQL平常有索引优化吗?怎么去知道一个SQL语句需不需要优化?一个表,建立了索引(B,A),问where A=1 and B=2索引是否能够生效?
*有 *使用执行计划查看执行情况 *可以生效
Mysql会使索引失效的情况
like 以%开头,索引无效;当like前缀没有%,后缀有%时,索引有效
or语句前后没有同时使用索引。当or左右查询字段只有一个是索引,该索引失效,只有当or左右查询字段均为索引时,才会生效
组合索引,不是使用第一列索引,索引失效。
数据类型出现隐式转化。如varchar不加单引号的话可能会自动转换为int型,使索引无效,产生全表扫描。
在索引字段上使用not,<>,!=。不等于操作符是永远不会用到索引的,因此对它的处理只会产生全表扫描。 优化方法: key<>0 改为 key>0 or key<0。
对索引字段进行计算操作、字段上使用函数。
当全表扫描速度比索引速度快时,mysql会使用全表扫描,此时索引失效。
MySQL索引的实现,innodb的索引,b+树索引是怎么实现的,为什么用b+树做索引节点,一个节点存了多少数据,怎么规定大小,与磁盘页对应
https://www.jianshu.com/p/c455be855e82
InnoDB存储引擎最小的存储单元是(页), 每一页的大小是16k(即16384个字节),每一行数据大概就是1k左右,那么一页就可以存16条数据 那么在InnoDb中2层的高度的B+树能存多少条数据,我们来分析一下: 在InnoDB中每个指针为6个字节,一个键值4-8个字节(如:Id为主键 -> bigInt类型是8字节)那么加起来就是14个字节, 那么一页就能存16384 / 14 =1170个指针, 所以2层的B+树能存1170*16=18720条数据 在InnoDB在B+树高度一般为3层所以1170 * 1170 * 16= 21902400 条数据,能存千万级别的数据 作者:爱健身的程序员 链接:https://www.jianshu.com/p/c455be855e82 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Mysql都有什么类型的索引
主键索引(PRIMARY) 数据列不允许重复,不允许为NULL,一个表只能有一个主键。 唯一索引(UNIQUE) 数据列不允许重复,允许为NULL值,一个表允许多个列创建唯一索引。 可以通过 ALTER TABLE table_name ADD UNIQUE (column); 创建唯一索引 可以通过 ALTER TABLE table_name ADD UNIQUE (column1,column2); 创建唯一组合索引 普通索引(INDEX) 可以通过ALTER TABLE table_name ADD INDEX index_name (column);创建普通索引 可以通过ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);创建组合索引 全文索引(FULLTEXT) 可以通过ALTER TABLE table_name ADD FULLTEXT (column);创建全文索引
是否越多索引越好
索引并非是越多越好,创建索引也需要耗费资源,一是增加了数据库的存储空间,二是在插入和删除时要花费较多的时间维护索引
什么是最左前缀原则?什么是最左匹配原则
顾名思义,就是最左优先,在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。 最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。 =和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式
锁
MySQL行锁是否会有死锁的情况?
会
举例MySQL发生死锁的情况
两个线程互相等待对方已锁的字段
Mysql什么时候触发表锁,什么时候触发行锁?
InnoDB基于索引的行锁 InnoDB行锁是通过索引上的索引项来实现的,这一点MySQL与Oracle不同,后者是通过在数据中对相应数据行加锁来实现的。InnoDB这种行锁实现特点意味者:只有通过索引条件检索数据,InnoDB才会使用行级锁,否则,InnoDB将使用表锁 在MySQL中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。 在UPDATE、DELETE操作时,MySQL不仅锁定WHERE条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的next-key locking。详细参考https://www.yuque.com/arrowy/htykn6/mabt05
一致性
如果自己实现一个事物的插入操作和删除操作的原子性,你怎么实现?(往mysql实现想)
使用undo(回滚)日志
什么是事务原子性?
原子性是指一个事务是一个不可分割的工作单位,其中的操作要么都做,要么都不做;如果事务中一个sql语句执行失败,则已执行的语句也必须回滚,数据库退回到事务前的状态。
分区分表
了解MySQL分区、分表吗?
分区就是把一个数据表的文件和索引分散存储在不同的物理文件中。 mysql支持的分区类型包括Range、List、Hash、Key,其中Range比较常用: RANGE分区:基于属于一个给定连续区间的列值,把多行分配给分区。 LIST分区:类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择。 HASH分区:基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL 中有效的、产生非负整数值的任何表达式。 KEY分区:类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL服务器提供其自身的哈希函数。必须有一列或多列包含整数值。
分表和分区类似,区别是,分区是把一个逻辑表文件分成几个物理文件后进行存储,而分表则是把原先的一个表分成几个表。进行分表查询时可以通过union或者视图。 分表又分垂直分割和水平分割,其中水平分分割最为常用。水平分割通常是指切分到另外一个数据库或表中。例如对于一个会员表,按对3的模进行分割:
分表的方式说一下
利用merge存储引擎 如果要把已有的大数据量表分开比较痛苦,最痛苦的事就是改代码,因为程序里面的sql语句已经写好了。用merge存储引擎来实现分表, 这种方法比较适合.
使用水平拆分(记录按照取模分表)和垂直拆分(10个字段分成两张五个字段的表)
对于分库分表的中间件有很多,Shardingsphere,Tddl,MyCat,cobar。从架构上分,主要分为两种:JDBC应用方式和Proxy模式。
数据库分库分表一般数据量多大才需要?
周期性测试系统性能,根据对系统的性能要求判断是否已经达到单机Mysql的性能瓶颈,然后判断是否需要分库分表,在已有的经验中,使用千万级还是没有问题的
分库分表之后,id 主键如何处理?
因为要是分成多个表之后,每个表都是从 1 开始累加,这样是不对的,我们需要一个全局唯一的 id 来支持。 生成全局 id 有下面这几种方式: UUID:不适合作为主键,因为太长了,并且无序不可读,查询效率低。比较适合用于生成唯一的名字的标示比如文件的名字。 数据库自增 id : 两台数据库分别设置不同步长,生成不重复ID的策略来实现高可用。这种方式生成的 id 有序,但是需要独立部署数据库实例,成本高,还会有性能瓶颈。 利用 redis 生成 id : 性能比较好,灵活方便,不依赖于数据库。但是,引入了新的组件造成系统更加复杂,可用性降低,编码更加复杂,增加了系统成本。 Twitter的snowflake算法 :Github 地址:https://github.com/twitter-archive/snowflake。 美团的Leaf分布式ID生成系统 :Leaf 是美团开源的分布式ID生成器,能保证全局唯一性、趋势递增、单调递增、信息安全,里面也提到了几种分布式方案的对比,但也需要依赖关系数据库、Zookeeper等中间件。感觉还不错。美团技术团队的一篇文章:https://tech.meituan.com/2017/04/21/mt-leaf.html 。
同步
mysql同步机制原理,有哪几种同步方法
数据库主从同步如何实现,事务如何实现
ACID
1. Atomicity
1.1. 原子性
1.1.1. 想要保证事务的原子性,就需要在异常发生时,对已经执行的操作进行回滚,而在 MySQL 中,恢复机制是通过回滚日志(undo log)实现的,所有事务进行的修改都会先记录到这个回滚日志中,然后在对数据库中的对应行进行写入。
1.1.2. 回滚日志(undo log)
2. Consistency
3. Isolation
3.1. 事务隔离级别
3.1.1. 并发控制
3.1.1.1. MySQL 和常见数据库中的锁都分为两种,共享锁(Shared)和互斥锁(Exclusive),前者也叫读锁,后者叫写锁
3.1.1.2. MVCC
4. Durability
4.1. 一旦事务被提交,那么数据一定会被写入到数据库中并持久存储起来。
4.2. 与原子性一样,事务的持久性也是通过日志来实现的,MySQL 使用重做日志(redo log)实现事务的持久性,重做日志由两部分组成,一是内存中的重做日志缓冲区,因为重做日志缓冲区在内存中,所以它是易失的,另一个就是在磁盘上的重做日志文件,它是持久的。
两个MySQL数据库怎么来做数据同步?
主从复制
datax等数据库同步工具
冷备份:导出导入,navicat数据同步工具
MySQL主从复制的实现流程
其他
CAS
ABA问题
ABA:如果另一个线程修改V值假设原来是A,先修改成B,再修改回成A。当前线程的CAS操作无法分辨当前V值是否发生过变化。
关于ABA问题我想了一个例子:在你非常渴的情况下你发现一个盛满水的杯子,你一饮而尽。之后再给杯子里重新倒满水。然后你离开,当杯子的真正主人回来时看到杯子还是盛满水,他当然不知道是否被人喝完重新倒满。解决这个问题的方案的一个策略是每一次倒水假设有一个自动记录仪记录下,这样主人回来就可以分辨在她离开后是否发生过重新倒满的情况。这也是解决ABA问题目前采用的策略。
乐观锁,悲观锁,行锁读写锁,表锁
MySQL的读写分离配置过程用到了什么注解
读写分离Spirngboot方案
mysql配置主从配置
springBoot设置主从数据源
使用AOP,根据操作的方法名来切换使用的数据源
了解连接池吗?有哪些连接池?
优化服务器应用程序的性能,提高程序执行效率和降低系统资源开销
c3p0 dbcp druid HikariCP
MYSQL日志种类 undolog redolog分别是做什么的?
undo logo是为了实现原子性的 redo logo是为了实现持久性
Mysql锁的算法
Record Lock
Gap Lock
Next-Key Lock
数据库的三范式是什么
第一范式:列不可再分 第二范式:行可以唯一区分,主键约束 第三范式:表的非主属性不能依赖与其他表的非主属性 外键约束 且三大范式是一级一级依赖的,第二范式建立在第一范式上,第三范式建立第一第二范式上。
Redis
持久化
聊一聊Redis的持久化机制
Redis是一个支持持久化的内存数据库,通过持久化机制把内存中的数据同步到硬盘文件来保证数据持久化。当Redis重启后通过把硬盘文件重新加载到内存,就能达到恢复数据的目的。 实现:单独创建fork()一个子进程,将当前父进程的数据库数据复制到子进程的内存中,然后由子进程写入到临时文件中,持久化的过程结束了,再用这个临时文件替换上次的快照文件,然后子进程退出,内存释放。 RDB是Redis默认的持久化方式。按照一定的时间周期策略把内存的数据以快照的形式保存到硬盘的二进制文件。即Snapshot快照存储,对应产生的数据文件为dump.rdb,通过配置文件中的save参数来定义快照的周期。( 快照可以是其所表示的数据的一个副本,也可以是数据的一个复制品。) AOF:Redis会将每一个收到的写命令都通过Write函数追加到文件最后,类似于MySQL的binlog。当Redis重启是会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。 当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。
RDB原理及优缺点
原理是redis会单独创建(fork) 一个与当前进程一模一样的子进程来进行持久化,这个子进程的所有数据(变量。环境变量,程序程序计数器等)都和原进程一模一样,会先将数据写入到一个临时文件中,待持久化结束了,再用这个临时文件替换上次持久化好的文件,整个过程中,主进程不进行任何的i。操作,这就确保了极高的性能
RDB:是Redis DataBase缩写快照 RDB是Redis默认的持久化方式。按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb。通过配置文件中的save参数来定义快照的周期 优点: 1、只有一个文件 dump.rdb,方便持久化。 2、容灾性好,一个文件可以保存到安全的磁盘。 3、性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能 4.相对于数据集大时,比 AOF 的启动效率更高。 缺点: 1、数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候) 2、AOF(Append-only file)持久化方式: 是指所有的命令行记录以 redis 命令请 求协议的格式完全持久化存储)保存为 aof 文件。
AOF原理及优缺点
将Redis的操作日志以追加的方式写入文件,读操作是不记录的
写回策略
AOF持久化(即Append Only File持久化),则是将Redis执行的每次写命令记录到单独的日志文件中,当重启Redis会重新将持久化的日志中文件恢复数据。 当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。 优点: 1、数据安全,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次 命令操作就记录到 aof 文件中一次。 2、通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题。 3、AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前(文件过大时会对命令 进行合并重写),可以删除其中的某些命令(比如误操作的 flushall)) 缺点: 1、AOF 文件比 RDB 文件大,且恢复速度慢。 2、数据集大的时候,比 rdb 启动效率低。 优缺点是什么? AOF文件比RDB更新频率高,优先使用AOF还原数据。 AOF比RDB更安全也更大 RDB性能比AOF好 如果两个都配了优先加载AOF
redis宕机后恢复数据的方式
持久化
RDB
启动fork线程按照一定规则进行rdb文件备份,规则可自定义
优缺点
优点
适合大规模的数据恢复
适合数据完整性要求不高的
缺点
需要一定的时间间隔进程操作,如果redis意外宕机,这个最后一次修改数据就没有了
fork进程会占用一定的内存空间
AOF
将所有的命令都执行下来,恢复的时候把所有的命令执行一便
Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?
持久化
AOF RDB 区别
redis的持久化的方式
持久化
RDB
启动fork线程按照一定规则进行rdb文件备份,规则可自定义
优缺点
优点
适合大规模的数据恢复
适合数据完整性要求不高的
缺点
需要一定的时间间隔进程操作,如果redis意外宕机,这个最后一次修改数据就没有了
fork进程会占用一定的内存空间
AOF
将所有的命令都执行下来,恢复的时候把所有的命令执行一便
Redis提供了RDB持久化方案,为什么还要aof?
优化数据丢失问题,RDB会丢失最后一次快照的数据,aof丢失不会超过2秒的数据
如果rdb和aof同时开启,听谁的
aof,官方建议两个同时开启,优先使用aof持久化机制
数据结构
Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?
一共五种 (一)String 这个其实没啥好说的,最常规的set/get操作,value可以是String也可以是数字。一般做一些复杂的计数功能的缓存。 (二)hash 这里value存放的是结构化的对象,比较方便的就是操作其中的某个字段。博主在做单点登录的时候,就是用这种数据结构存储用户信息,以cookieId作为key,设置30分钟为缓存过期时间,能很好的模拟出类似session的效果。 (三)list 使用List的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用lrange命令,做基于redis的分页功能,性能极佳,用户体验好。本人还用一个场景,很合适—取行情信息。就也是个生产者和消费者的场景。LIST可以很好的完成排队,先进先出的原则。 (四)set 因为set堆放的是一堆不重复值的集合。所以可以做全局去重的功能。为什么不用JVM自带的Set进行去重?因为我们的系统一般都是集群部署,使用JVM自带的Set,比较麻烦,难道为了一个做一个全局去重,再起一个公共服务,太麻烦了。 另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。 (五)sorted set sorted set多了一个权重参数score,集合中的元素能够按score进行排列。可以做排行榜应用,取TOP N操作。
redis zset set区别
redis数据结构?
主从、哨兵和集群
Redis 集群模式的工作原理能说一下么?在集群模式下,Redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?如何动态增加和删除一个节点?
寻址:hash(key)%(N),N是节点个数
一致性Hash算法解决的是当集群中有机器变动,不至于发生大量的数据移动问题
原理:Hash环,环上有2^32个点,服务器按照hash(IP+编号)%2^32作为节点,数据按照hash(key)%2^32存放
该操作会有数据倾斜问题(大量数据存储在少量服务器)
添加虚拟节点
生产环境中的 Redis 是怎么部署的?
大多数是哨兵
谈谈Redis哨兵、复制、集群
Redis哨兵模式和集群模式区别
哨兵每个节点还是全量存储,集群则是部分存储
哨兵解决了主从模式手动选主的问题,提高了效率,集群解决的是单机容量有限的问题
如何保证 Redis 高并发、高可用?Redis 的主从复制原理能介绍一下么?Redis 的哨兵原理能介绍一下么?
Redis集群会有写操作丢失吗?为什么?
Redis并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作。
三种模式的实现原理
缓存问题
缓存雪崩
定义
由于原有缓存失效,新缓存未到期间 (例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃
解决办法
大多数系统设计者考虑用加锁( 最多的解决方案)或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上
将缓存失效时间分散开。
缓存穿透
定义
缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题
解决办法
最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力
另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库
缓存预热
定义
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据
解决办法
1、直接写个缓存刷新页面,上线时手工操作下; 2、数据量不大,可以在项目启动的时候自动进行加载; 3、定时刷新缓存;
缓存更新
定义
除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:
解决办法
(1)定时去清理过期的缓存; (2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。 两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡
缓存降级
定义
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。 降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。
解决办法
以参考日志级别设置预案: (1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级; (2)警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警; (3)错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级; (4)严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。
redis的过期策略以及内存淘汰机制
redis采用的是定期删除+惰性删除策略。 为什么不用定时删除策略? 定时删除,用一个定时器来负责监视key,过期则自动删除。虽然内存及时释放,但是十分消耗CPU资源。在大并发请求下,CPU要将时间应用在处理请求,而不是删除key,因此没有采用这一策略. 定期删除+惰性删除是如何工作的呢? 定期删除,redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。 于是,惰性删除派上用场。也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。 采用定期删除+惰性删除就没其他问题了么? 不是的,如果定期删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效。这样,redis的内存会越来越高。那么就应该采用内存淘汰机制。 在redis.conf中有一行配置 maxmemory-policy volatile-lru 该配置就是配内存淘汰策略的(什么,你没配过?好好反省一下自己) volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰 volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰 volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰 allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰 allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰 no-enviction(驱逐):禁止驱逐数据,新写入操作会报错 ps:如果没有设置 expire 的key, 不满足先决条件(prerequisites); 那么 volatile-lru, volatile-random 和 volatile-ttl 策略的行为, 和 noeviction(不删除) 基本上一致。
Redis得过期策略
我们都知道,Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。 过期策略通常有以下三种: 定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。 定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。 (expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。) Redis中同时使用了惰性过期和定期过期两种过期策略
Redis的内存用完了会发生什么?
如果达到设置的上限,Redis的写命令会返回错误信息(但是读命令还可以正常返回。)或者你可以配置内存淘汰机制,当Redis达到内存上限时会冲刷掉旧的内容。
如何保证缓存与数据库的双写一致性?
回答:
首先,缓存不一致有三种情况, 1. 数据库有数据,缓存没有数据; 2. 数据库有数据,缓存也有数据,数据不相等; 3. 数据库没有数据,缓存有数据。 针对这三种情况,比较常见得策略叫做Cache Aside Patern(缓存预留模式)简而言之,就是 1. 首先尝试从缓存读取,读到数据则直接返回;如果读不到,就读数据库,并将数据会写到缓存,并返回。 2. 需要更新数据时,先更新数据库,然后把缓存里对应的数据失效掉(删掉)。 为什么该方案为什么不先删除缓存,然后再更新数据库?这么做引发的问题是,如果A,B两个线程同时要更新数据,并且A,B已经都做完了删除缓存这一步,接下来,A先更新了数据库,C线程读取数据,由于缓存没有,则查数据库,并把A更新的数据,写入了缓存,最后B更新数据库。那么缓存和数据库的值就不一致了。 那为什么最后是把缓存的数据删掉,而不是把更新的数据写到缓存里?这么做引发的问题是,如果A,B两个线程同时做数据更新,A先更新了数据库,B后更新数据库,则此时数据库里存的是B的数据。而更新缓存的时候,是B先更新了缓存,而A后更新了缓存,则缓存里是A的数据。这样缓存和数据库的数据也不一致。 但是缓存预留模式也还是会存在不一致的情况,比如数据库更新成功,缓存删除失败,这个时候就会导致缓存还是旧数据(脏数据),数据库是新数据 解决方案大概有以下几种: 1.读写分离:读请求只访问缓存,写请求只修改数据库和缓存,写请求修改数据库和缓存是事务性动作,如果更新数据库成功,更新缓存失败,则回滚数据库,保证缓存与数据库数据强一致。这样实现了读写分离,不仅提高了读的响应速度,由写请求负责缓存与数据库一致,只有写请求成功才会影响到缓存的内容,时效性大大增强。 2.对一致性要求较高的数据比如库存,可以使用分布式锁将数据库操作和缓存删除作为一个事务操作,确保两者强一致性 3.还可以借助第三方工具,比如阿里巴巴的数据库日志增量解析组件Cannal,通过解析数据库的日志信息,来检测数据库中表结构和数据的变化,从而更新缓存,即做到了解耦也做到准实时性
初级(低并发)
更新的时候,先更新数据库,然后再删除缓存
问题流程
如果先更新数据库,再更新缓存,则会出现一种情况,数据库更新成功,缓存更新失败,就会出现不一致情况
中级(读写并发)
问题流程
解决方案
更新与读取串行化(队列)
高级(高并发)
使用分布式锁实现更新与读取串行化
性能(单线程)
在多核cpu下redis单线程浪费?
线程切换耗费资源
redis在增删改查时为什么单线程 还那么快?io模型?
CPU IO多路服用
Redis 的并发竞争问题是什么?如何解决这个问题?了解 Redis 事务的 CAS 方案吗?
假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?
使用keys指令可以扫出指定模式的key列表。 对方接着追问:如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题? 这个时候你要回答redis关键的一个特性:redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。
Redis并发量上限
写:8万/秒 读:11万/秒
实测:100个线程,100000并发
redis性能为什么这么高
纯内存KV操作
内部是单程实现的(不需要创建/销毁线程,避免上下文切换,无并发资源竞争的问题)
异步非阻塞的I/O(多路复用)
为什么Redis的操作是原子性的,怎么保证原子性的?
因为Redis是单线程的
锁
redis分布式锁的实现
实现无超时问题的分布式锁,解决本支问题需要用续命锁,现有框架Redisson
Zookeeper同样能实现分布式锁,并且不存在主从同步时差问题,zookeeper会先同步所有从节点再返回给客户端,劣势就是性能没有redis高
Redlock方案也能实现,但是市面上存在争议,不推荐使用
事务
Redis事务的概念
Redis 事务的本质是通过MULTI、EXEC、WATCH等一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。 总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。
Redis事务支持隔离性吗
Redis 是单进程程序,并且它保证在执行事务时,不会对事务进行中断,事务可以运行直到执行完所有事务队列中的命令为止。因此,Redis 的事务是总是带有隔离性的。
Redis的事务总是具有ACID中的一致性和隔离性,其他特性是不支持的。当服务器运行在AOF持久化模式下,并且appendfsync选项的值为always时,事务也具有耐久性。
Redis事务保证原子性吗,支持回滚吗
Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。
其他
Nosql引擎用的什么存储结构,关系型数据库和NoSQL各自的优劣点是什么,如何技术选型?
访问量太大redis支撑不住怎么办?
如何利用Redis处理热点数据
根据具体场景和需求设置缓存策略
Redis主要消耗什么物理资源?
内存。
架构
分布式/微服务
组件
消息队列
kafka
如何获取 topic 主题的列表
bin/kafka-topics.sh --list --zookeeper localhost:2181
生产者和消费者的命令行是什么?
生产者在主题上发布消息: bin/kafka-console-producer.sh --broker-list 192.168.43.49:9092 --topicHello-Kafka 注意这里的 IP 是 server.properties 中的 listeners 的配置。接下来每个新行就是输入一条新消息。 消费者接受消息: bin/kafka-console-consumer.sh --zookeeper localhost:2181 --topicHello-Kafka --from-beginning
consumer 是推还是拉?
Kafka 最初考虑的问题是,customer 应该从 brokes 拉取消息还是 brokers 将消息推送到 consumer,也就是 pull 还 push。在这方面,Kafka 遵循了一种大部分消息系统共同的传统的设计:producer 将消息推送到 broker,consumer 从broker 拉取消息
pull模式的优劣
Pull 模式的另外一个好处是 consumer 可以自主决定是否批量的从 broker 拉取数据 。Push 模式必须在不知道下游 consumer 消费能力和消费策略的情况下决定是立即推送每条消息还是缓存之后批量推送。如果为了避免 consumer 崩溃而采用较低的推送速率,将可能导致一次只推送较少的消息而造成浪费。Pull 模式下,consumer 就可以根据自己的消费能力去决定这些策略。 Pull 有个缺点是,如果 broker 没有可供消费的消息,将导致 consumer 不断在循环中轮询,直到新消息到 t 达。为了避免这点,Kafka 有个参数可以让 consumer阻塞知道新消息到达(当然也可以阻塞知道消息的数量达到某个特定的量这样就可以批量发送)。
讲讲 kafka 维护消费状态跟踪的方法
大部分消息系统在 broker 端的维护消息被消费的记录:一个消息被分发到consumer 后 broker 就马上进行标记或者等待 customer 的通知后进行标记。这样也可以在消息在消费后立马就删除以减少空间占用。 但是这样会不会有什么问题呢?如果一条消息发送出去之后就立即被标记为消费过的,旦 consumer 处理消息时失败了(比如程序崩溃)消息就丢失了。为了解决这个问题,很多消息系统提供了另外一个个功能:当消息被发送出去之后仅仅被标记为已发送状态,当接到 consumer 已经消费成功的通知后才标记为已被消费的状态。这虽然解决了消息丢失的问题,但产生了新问题,首先如果 consumer处理消息成功了但是向 broker 发送响应时失败了,这条消息将被消费两次。第二个问题时,broker 必须维护每条消息的状态,并且每次都要先锁住消息然后更改状态然后释放锁。这样麻烦又来了,且不说要维护大量的状态数据,比如如果消息发送出去但没有收到消费成功的通知,这条消息将一直处于被锁定的状态,Kafka 采用了不同的策略。Topic 被分成了若干分区,每个分区在同一时间只被一个 consumer 消费。这意味着每个分区被消费的消息在日志中的位置仅仅是一个简单的整数:offset。这样就很容易标记每个分区消费状态就很容易了,仅仅需要一个整数而已。这样消费状态的跟踪就很简单了。 这带来了另外一个好处:consumer 可以把 offset 调成一个较老的值,去重新消费老的消息。这对传统的消息系统来说看起来有些不可思议,但确实是非常有用的,谁规定了一条消息只能被消费一次呢?
讲一下主从同步
Kafka允许topic的分区拥有若干副本,这个数量是可以配置的,你可以为每个topci配置副本的数量。Kafka会自动在每个个副本上备份数据,所以当一个节点down掉时数据依然是可用的。 Kafka的副本功能不是必须的,你可以配置只有一个副本,这样其实就相当于只有一份数据。
为什么需要消息系统,mysql 不能满足需求吗?
(1)解耦: 允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。 (2)冗余: 消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的”插入-获取-删除”范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。 (3)扩展性: 因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。 (4)灵活性 & 峰值处理能力: 在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见。如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。 (5)可恢复性: 系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。 (6)顺序保证: 在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。(Kafka 保证一个 Partition 内的消息的有序性) (7)缓冲: 有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况。 (8)异步通信: 很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。
数据传输的事务定义有哪三种?
和 MQTT 的事务定义一样都是 3 种。 (1)最多一次: 消息不会被重复发送,最多被传输一次,但也有可能一次不传输 (2)最少一次: 消息不会被漏发送,最少被传输一次,但也有可能被重复传输. (3)精确的一次(Exactly once): 不会漏传输也不会重复传输,每个消息都传输被一次而且仅仅被传输一次,这是大家所期望的
Kafka 判断一个节点是否还活着有那两个条件?
1)节点必须可以维护和 ZooKeeper 的连接,Zookeeper 通过心跳机制检查每个节点的连接 (2)如果节点是个 follower,他必须能及时的同步 leader 的写操作,延时不能太久
Kafka 与传统 MQ 消息系统之间有三个关键区别
(1).Kafka 持久化日志,这些日志可以被重复读取和无限期保留 (2).Kafka 是一个分布式系统:它以集群的方式运行,可以灵活伸缩,在内部通过复制数据提升容错能力和高可用性 (3).Kafka 支持实时的流式处理
讲一讲 kafka 的 ack 的三种机制
request.required.acks 有三个值 0 1 -1(all) 0:生产者不会等待 broker 的 ack,这个延迟最低但是存储的保证最弱当 server 挂掉的时候就会丢数据。 1:服务端会等待 ack 值 leader 副本确认接收到消息后发送 ack 但是如果 leader挂掉后他不确保是否复制完成新 leader 也会导致数据丢失。 -1(all):服务端会等所有的 follower 的副本受到数据后才会受到 leader 发出的ack,这样数据不会丢失
kafka 分布式(不是单机)的情况下,如何保证消息的顺序消费?
Kafka 分布式的单位是 partition,同一个 partition 用一个 write ahead log 组织,所以可以保证 FIFO 的顺序。不同 partition 之间不能保证顺序。但是绝大多数用户都可以通过 message key 来定义,因为同一个 key 的 message 可以保证只发送到同一个 partition。 Kafka 中发送 1 条消息的时候,可以指定(topic, partition, key) 3 个参数。partiton 和 key 是可选的。如果你指定了 partition,那就是所有消息发往同 1个 partition,就是有序的。并且在消费端,Kafka 保证,1 个 partition 只能被1 个 consumer 消费。或者你指定 key( 比如 order id),具有同 1 个 key 的所有消息,会发往同 1 个 partition。
kafka 的高可用机制是什么?
这个问题比较系统,回答出 kafka 的系统特点,leader 和 follower 的关系,消息读写的顺序即可。
kafka 如何减少数据丢失
Kafka到底会不会丢数据(data loss)? 通常不会,但有些情况下的确有可能会发生。下面的参数配置及Best practice列表可以较好地保证数据的持久性(当然是trade-off,牺牲了吞吐量)。 block.on.buffer.full = true acks = all retries = MAX_VALUE max.in.flight.requests.per.connection = 1 使用KafkaProducer.send(record, callback) callback逻辑中显式关闭producer:close(0) unclean.leader.election.enable=false replication.factor = 3 min.insync.replicas = 2 replication.factor > min.insync.replicas enable.auto.commit=false 消息处理完成之后再提交位移
kafka 如何不消费重复数据?比如扣款,我们不能重复的扣。
其实还是得结合业务来思考,我这里给几个思路: 比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update 一下好吧。 比如你是写 Redis,那没问题了,反正每次都是 set,天然幂等性。 比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的 id,类似订单 id 之类的东西,然后你这里消费到了之后,先根据这个 id 去比如 Redis 里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个 id 写 Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。 比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。
RocketMq
异步队列是怎么解决的写压力大的问题
队列中消息是允许延迟吗,如果延迟的话怎么保证消息不被重复消费
怎么保证缓存和数据库双写一致
AQS大致说一下 同步队列,等待队列
阿里系中间件metaQ及原理与现有的kafka有什么异同
项目中消息队列怎么用的?使用哪些具体业务场景?
rabbitmq
MQ
MQ优缺点
在特殊场景下有其对应的好处,解耦、异步、削峰。 缺点有以下几个: 系统可用性降低 系统引入的外部依赖越多,越容易挂掉。万一 MQ 挂了,MQ 一挂,整套系统崩溃,你不就完了? 系统复杂度提高 硬生生加个 MQ 进来,你怎么保证消息没有重复消费?怎么处理消息丢失的情况?怎么保证消息传递的顺序性?问题一大堆。 一致性问题 A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,咋整?你这数据就不一致了。
四大消息队列对比
如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?
消息积压处理办法:临时紧急扩容: 先修复 consumer 的问题,确保其恢复消费速度,然后将现有 cnosumer 都停掉。 新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量。 然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的 10 倍数量的 queue。 接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据。 等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的 consumer 机器来消费消息。 MQ中消息失效:假设你用的是 RabbitMQ,RabbtiMQ 是可以设置过期时间的,也就是 TTL。如果消息在 queue 中积压超过一定的时间就会被 RabbitMQ 给清理掉,这个数据就没了。那这就是第二个坑了。这就不是说数据会大量积压在 mq 里,而是大量的数据会直接搞丢。我们可以采取一个方案,就是批量重导,这个我们之前线上也有类似的场景干过。就是大量积压的时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上12点以后,用户都睡觉了。这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入 mq 里面去,把白天丢的数据给他补回来。也只能是这样了。假设 1 万个订单积压在 mq 里面,没有处理,其中 1000 个订单都丢了,你只能手动写程序把那 1000 个订单给查出来,手动发到 mq 里去再补一次。 mq消息队列块满了:如果消息积压在 mq 里,你很长时间都没有处理掉,此时导致 mq 都快写满了,咋办?这个还有别的办法吗?没有,谁让你第一个方案执行的太慢了,你临时写程序,接入数据来消费,消费一个丢弃一个,都不要了,快速消费掉所有的消息。然后走第二个方案,到了晚上再补数据吧。
设计MQ的思路
比如说这个消息队列系统,我们从以下几个角度来考虑一下: 首先这个 mq 得支持可伸缩性吧,就是需要的时候快速扩容,就可以增加吞吐量和容量,那怎么搞?设计个分布式的系统呗,参照一下 kafka 的设计理念,broker -> topic -> partition,每个 partition 放一个机器,就存一部分数据。如果现在资源不够了,简单啊,给 topic 增加 partition,然后做数据迁移,增加机器,不就可以存放更多数据,提供更高的吞吐量了? 其次你得考虑一下这个 mq 的数据要不要落地磁盘吧?那肯定要了,落磁盘才能保证别进程挂了数据就丢了。那落磁盘的时候怎么落啊?顺序写,这样就没有磁盘随机读写的寻址开销,磁盘顺序读写的性能是很高的,这就是 kafka 的思路。 其次你考虑一下你的 mq 的可用性啊?这个事儿,具体参考之前可用性那个环节讲解的 kafka 的高可用保障机制。多副本 -> leader & follower -> broker 挂了重新选举 leader 即可对外服务。 能不能支持数据 0 丢失啊?可以的,参考我们之前说的那个 kafka 数据零丢失方案。
ELK
负载均衡
Nginx原理了解吗?
用到了Nginx,用的七层还是四层Nginx? HTTP还是TCP?负载均衡策略用的是什么?
ip_hash的优缺点?
1. Nginx和其他负载均衡框架对比过吗?
常见的负载均衡的策略
常见的负载均衡算法有哪些
Git
Maven
Zookeeper
Zookeeper中的ZAB协议,选主算法
Tomcat
Tomcat集群Session共享问题?
一个请求到Tomcat容器了,怎么到SSM代码中去?
ElasticSearch
elasticsearch 了解多少,说说你们公司 es 的集群架构,索引数据大小,分片有多少
调优手段
仅索引层面调优手段: 1.1、设计阶段调优 (1)根据业务增量需求,采取基于日期模板创建索引,通过 roll over API 滚动索引; (2)使用别名进行索引管理; (3)每天凌晨定时对索引做 force_merge 操作,以释放空间; (4)采取冷热分离机制,热数据存储到 SSD,提高检索效率;冷数据定期进行 shrink操作,以缩减存储; (5)采取 curator 进行索引的生命周期管理; (6)仅针对需要分词的字段,合理的设置分词器; (7)Mapping 阶段充分结合各个字段的属性,是否需要检索、是否需要存储等。…….. 1.2、写入调优 (1)写入前副本数设置为 0; (2)写入前关闭 refresh_interval 设置为-1,禁用刷新机制; (3)写入过程中:采取 bulk 批量写入; (4)写入后恢复副本数和刷新间隔; (5)尽量使用自动生成的 id。 1.3、查询调优 (1)禁用 wildcard; (2)禁用批量 terms(成百上千的场景); (3)充分利用倒排索引机制,能 keyword 类型尽量 keyword; (4)数据量大时候,可以先基于时间敲定索引再检索; (5)设置合理的路由机制。 1.4、其他调优 部署调优,业务调优等。 上面的提及一部分,面试者就基本对你之前的实践或者运维经验有所评估了。
elasticsearch 的倒排索引是什么
面试官:想了解你对基础概念的认知。 解答:通俗解释一下就可以。 传统的我们的检索是通过文章,逐个遍历找到对应关键词的位置。 而倒排索引,是通过分词策略,形成了词和文章的映射关系表,这种词典+映射表即为倒排索引。有了倒排索引,就能实现 o(1)时间复杂度的效率检索文章了,极大的提高了检索效率。 学术的解答方式: 倒排索引,相反于一篇文章包含了哪些词,它从词出发,记载了这个词在哪些文档中出现过,由两部分组成——词典和倒排表。 加分项:倒排索引的底层实现是基于:FST(Finite State Transducer)数据结构。 lucene 从 4+版本后开始大量使用的数据结构是 FST。FST 有两个优点: (1)空间占用小。通过对词典中单词前缀和后缀的重复利用,压缩了存储空间; (2)查询速度快。O(len(str))的查询时间复杂度。
elasticsearch 是如何实现 master 选举的
(1)Elasticsearch 的选主是 ZenDiscovery 模块负责的,主要包含 Ping(节点之间通过这个 RPC 来发现彼此)和 Unicast(单播模块包含一个主机列表以控制哪些节点需要 ping 通)这两部分; (2)对所有可以成为 master 的节点(node.master: true)根据 nodeId 字典排序,每次选举每个节点都把自己所知道节点排一次序,然后选出第一个(第 0 位)节点,暂且认为它是 master 节点。 (3)如果对某个节点的投票数达到一定的值(可以成为 master 节点数 n/2+1)并且该节点自己也选举自己,那这个节点就是 master。否则重新选举一直到满足上述条件。 (4)补充:master 节点的职责主要包括集群、节点和索引的管理,不负责文档级别的管理;data 节点可以关闭 http 功能*。
ES中query和fetch的区别是什么?
ES这个组件由哪些关键模块组成?
ES的分布式特性体现在哪里?
ES怎么保证数据不丢失
ES的几个重要概念?(我回答倒排索引,但面试官想知道的是索引-类型-文档-域这些概念,幸好也说得出)
ES的域有哪些类型?String型和TEXT型的区别?分词阶段在ES叫什么?(这三个问题环环相扣) 答:keyword/text两种最常用,还有支持数据类型、数组类型、对象类型、时间类型等。其中string类型已经取消不再用。
项目中使用的ES版本
Elasticsearch 在部署时,对 Linux 的设置有哪些优化方法
面试官:想了解对 ES 集群的运维能力。 解答: (1)关闭缓存 swap; (2)堆内存设置为:Min(节点内存/2, 32GB); (3)设置最大文件句柄数; (4)线程池+队列大小根据业务需要做调整; (5)磁盘存储 raid 方式——存储有条件使用 RAID10,增加单节点性能以及避免单节点存储故障。
详细描述一下 Elasticsearch 搜索的过程?
(1)搜索被执行成一个两阶段过程,我们称之为 Query Then Fetch; (2)在初始查询阶段时,查询会广播到索引中每一个分片拷贝(主分片或者副本分片)。 每个分片在本地执行搜索并构建一个匹配文档的大小为 from + size 的优先队列。 PS:在搜索的时候是会查询 Filesystem Cache 的,但是有部分数据还在 MemoryBuffer,所以搜索是近实时的。 (3)每个分片返回各自优先队列中 所有文档的 ID 和排序值 给协调节点,它合并这些值到自己的优先队列中来产生一个全局排序后的结果列表。 (4)接下来就是 取回阶段,协调节点辨别出哪些文档需要被取回并向相关的分片提交多个 GET 请求。每个分片加载并 丰 富 文档,如果有需要的话,接着返回文档给协调节点。一旦所有的文档都被取回了,协调节点返回结果给客户端。 (5)补充:Query Then Fetch 的搜索类型在文档相关性打分的时候参考的是本分片的数据,这样在文档数量较少的时候可能不够准确,DFS Query Then Fetch 增加了一个预查询的处理,询问 Term 和 Document frequency,这个评分更准确,但是性能会变差。*
在并发情况下,Elasticsearch 如果保证读写一致?
(1)可以通过版本号使用乐观并发控制,以确保新版本不会被旧版本覆盖,由应用层来处理具体的冲突; (2)另外对于写操作,一致性级别支持 quorum/one/all,默认为 quorum,即只有当大多数分片可用时才允许写操作。但即使大多数可用,也可能存在因为网络等原因导致写入副本失败,这样该副本被认为故障,分片将会在一个不同的节点上重建。 (3)对于读操作,可以设置 replication 为 sync(默认),这使得操作在主分片和副本分片都完成后才会返回;如果设置 replication 为 async 时,也可以通过设置搜索请求参数_preference 为 primary 来查询主分片,确保文档是最新版本。
绍下你们电商搜索的整体技术架构。
如何监控 Elasticsearch 集群状态?
Marvel 让你可以很简单的通过 Kibana 监控 Elasticsearch。你可以实时查看你的集群健康状态和性能,也可以分析过去的集群、索引和节点指标。
是否了解字典树?
ES中query和fetch的区别是什么?
ES这个组件由哪些关键模块组成?
ES的分布式特性体现在哪里?
ES怎么保证数据不丢失
ES的几个重要概念?(我回答倒排索引,但面试官想知道的是索引-类型-文档-域这些概念,幸好也说得出)
ES的域有哪些类型?String型和TEXT型的区别?分词阶段在ES叫什么?(这三个问题环环相扣) 答:keyword/text两种最常用,还有支持数据类型、数组类型、对象类型、时间类型等。其中string类型已经取消不再用。
项目中使用的ES版本
Netty
Netty 是什么?
Netty是 一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。Netty是基于nio的,它封装了jdk的nio,让我们使用起来更加方法灵活。
Netty 的特点是什么?
*高并发:Netty 是一款基于 NIO(Nonblocking IO,非阻塞IO)开发的网络通信框架,对比于 BIO(Blocking I/O,阻塞IO),他的并发性能得到了很大提高。 *传输快:Netty 的传输依赖于零拷贝特性,尽量减少不必要的内存拷贝,实现了更高效率的传输。 *封装好:Netty 封装了 NIO 操作的很多细节,提供了易于使用调用接口。
Netty 的优势有哪些?
*使用简单:封装了 NIO 的很多细节,使用更简单。 *功能强大:预置了多种编解码功能,支持多种主流协议。 *定制能力强:可以通过 ChannelHandler 对通信框架进行灵活地扩展。 *性能高:通过与其他业界主流的 NIO 框架对比,Netty 的综合性能最优。 *稳定:Netty 修复了已经发现的所有 NIO 的 bug,让开发人员可以专注于业务本身。 *社区活跃:Netty 是活跃的开源项目,版本迭代周期短,bug 修复速度快。
Netty使用场景
典型的应用有:阿里分布式服务框架 Dubbo,默认使用 Netty 作为基础通信组件,还有 RocketMQ 也是使用 Netty 作为通讯的基础。
Netty 高性能表现在哪些方面?
*IO 线程模型:同步非阻塞,用最少的资源做更多的事。 *内存零拷贝:尽量减少不必要的内存拷贝,实现了更高效率的传输。 *内存池设计:申请的内存可以重用,主要指直接内存。内部实现是用一颗二叉查找树管理内存分配情况。 *串形化处理读写:避免使用锁带来的性能开销。 *高性能序列化协议:支持 protobuf 等高性能序列化协议。
BIO、NIO和AIO的区别?
BIO:一个连接一个线程,客户端有连接请求时服务器端就需要启动一个线程进行处理。线程开销大。 伪异步IO:将请求连接放入线程池,一对多,但线程还是很宝贵的资源。 NIO:一个请求一个线程,但客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。 AIO:一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理, BIO是面向流的,NIO是面向缓冲区的;BIO的各种流是阻塞的。而NIO是非阻塞的;BIO的Stream是单向的,而NIO的channel是双向的。 NIO的特点:事件驱动模型、单线程处理多任务、非阻塞I/O,I/O读写不再阻塞,而是返回0、基于block的传输比基于流的传输更高效、更高级的IO函数zero-copy、IO多路复用大大提高了Java网络应用的可伸缩性和实用性。基于Reactor线程模型。 在Reactor模式中,事件分发器等待某个事件或者可应用或个操作的状态发生,事件分发器就把这个事件传给事先注册的事件处理函数或者回调函数,由后者来做实际的读写操作。如在Reactor中实现读:注册读就绪事件和相应的事件处理器、事件分发器等待事件、事件到来,激活分发器,分发器调用事件对应的处理器、事件处理器完成实际的读操作,处理读到的数据,注册新的事件,然后返还控制权。
NIO的组成?
Buffer:与Channel进行交互,数据是从Channel读入缓冲区,从缓冲区写入Channel中的 flip方法 : 反转此缓冲区,将position给limit,然后将position置为0,其实就是切换读写模式 clear方法 :清除此缓冲区,将position置为0,把capacity的值给limit。 rewind方法 : 重绕此缓冲区,将position置为0 DirectByteBuffer可减少一次系统空间到用户空间的拷贝。但Buffer创建和销毁的成本更高,不可控,通常会用内存池来提高性能。直接缓冲区主要分配给那些易受基础系统的本机I/O 操作影响的大型、持久的缓冲区。如果数据量比较小的中小应用情况下,可以考虑使用heapBuffer,由JVM进行管理。 Channel:表示 IO 源与目标打开的连接,是双向的,但不能直接访问数据,只能与Buffer 进行交互。通过源码可知,FileChannel的read方法和write方法都导致数据复制了两次! Selector可使一个单独的线程管理多个Channel,open方法可创建Selector,register方法向多路复用器器注册通道,可以监听的事件类型:读、写、连接、accept。注册事件后会产生一个SelectionKey:它表示SelectableChannel 和Selector 之间的注册关系,wakeup方法:使尚未返回的第一个选择操作立即返回,唤醒的 原因是:注册了新的channel或者事件;channel关闭,取消注册;优先级更高的事件触发(如定时器事件),希望及时处理。 Selector在Linux的实现类是EPollSelectorImpl,委托给EPollArrayWrapper实现,其中三个native方法是对epoll的封装,而EPollSelectorImpl. implRegister方法,通过调用epoll_ctl向epoll实例中注册事件,还将注册的文件描述符(fd)与SelectionKey的对应关系添加到fdToKey中,这个map维护了文件描述符与SelectionKey的映射。 fdToKey有时会变得非常大,因为注册到Selector上的Channel非常多(百万连接);过期或失效的Channel没有及时关闭。fdToKey总是串行读取的,而读取是在select方法中进行的,该方法是非线程安全的。 Pipe:两个线程之间的单向数据连接,数据会被写到sink通道,从source通道读取 NIO的服务端建立过程:Selector.open():打开一个Selector;ServerSocketChannel.open():创建服务端的Channel;bind():绑定到某个端口上。并配置非阻塞模式;register():注册Channel和关注的事件到Selector上;select()轮询拿到已经就绪的事件
Netty解决粘包的几种方式
TCP是以流的方式来处理数据,一个完整的包可能会被TCP拆分成多个包进行发送,也可能把小的封装成一个大的数据包发送。 TCP粘包/分包的原因: 应用程序写入的字节大小大于套接字发送缓冲区的大小,会发生拆包现象,而应用程序写入数据小于套接字缓冲区大小,网卡将应用多次写入的数据发送到网络上,这将会发生粘包现象; 进行MSS大小的TCP分段,当TCP报文长度-TCP头部长度>MSS的时候将发生拆包 以太网帧的payload(净荷)大于MTU(1500字节)进行ip分片。 解决方法 消息定长:FixedLengthFrameDecoder类 包尾增加特殊字符分割: 行分隔符类:LineBasedFrameDecoder 或自定义分隔符类 :DelimiterBasedFrameDecoder 将消息分为消息头和消息体:LengthFieldBasedFrameDecoder类。分为有头部的拆包与粘包、长度字段在前且有头部的拆包与粘包、多扩展头部的拆包与粘包。
基于netty实现通信,使用了哪些tcp优化参数
网络编程nio和netty相关,netty的线程模型,零拷贝实现
什么是 Netty 的零拷贝?
Netty 的零拷贝主要包含三个方面: Netty 的接收和发送 ByteBuffer 采用 DIRECT BUFFERS,使用堆外直接内存进行 Socket 读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行 Socket 读写,JVM 会将堆内存 Buffer 拷贝一份到直接内存中,然后才写入 Socket 中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。 Netty 提供了组合 Buffer 对象,可以聚合多个 ByteBuffer 对象,用户可以像操作一个 Buffer 那样方便的对组合 Buffer 进行操作,避免了传统通过内存拷贝的方式将几个小 Buffer 合并成一个大的 Buffer。 Netty 的文件传输采用了 transferTo 方法,它可以直接将文件缓冲区的数据发送到目标 Channel,避免了传统通过循环 write 方式导致的内存拷贝问题。
Netty 中有哪种重要组件?
Channel:Netty 网络操作抽象类,它除了包括基本的 I/O 操作,如 bind、connect、read、write 等。 EventLoop:主要是配合 Channel 处理 I/O 操作,用来处理连接的生命周期中所发生的事情。 ChannelFuture:Netty 框架中所有的 I/O 操作都为异步的,因此我们需要 ChannelFuture 的 addListener()注册一个 ChannelFutureListener 监听事件,当操作执行成功或者失败时,监听就会自动触发返回结果。 ChannelHandler:充当了所有处理入站和出站数据的逻辑容器。ChannelHandler 主要用来处理各种事件,这里的事件很广泛,比如可以是连接、数据接收、异常、数据转换等。 ChannelPipeline:为 ChannelHandler 链提供了容器,当 channel 创建时,就会被自动分配到它专属的 ChannelPipeline,这个关联是永久性的。
默认情况 Netty 起多少线程?何时启动?
Netty 默认是 CPU 处理器数的两倍,bind 完之后启动。
了解哪几种序列化协议?
Netty 和 Tomcat 的区别?
作用不同:Tomcat 是 Servlet 容器,可以视为 Web 服务器,而 Netty 是异步事件驱动的网络应用程序框架和工具用于简化网络编程,例如TCP和UDP套接字服务器。 协议不同:Tomcat 是基于 http 协议的 Web 服务器,而 Netty 能通过编程自定义各种协议,因为 Netty 本身自己能编码/解码字节流,所有 Netty 可以实现,HTTP 服务器、FTP 服务器、UDP 服务器、RPC 服务器、WebSocket 服务器、Redis 的 Proxy 服务器、MySQL 的 Proxy 服务器等等。
NIOEventLoopGroup源码?
Docker
Docker为什么比VM快
对比图
详细原因
docker有比虚拟机更少的抽象层。docker不需要Hypervisor实现硬件资源虚拟化,运行在docker容器上的程序直接使用的是实际物理机的硬件资源。因此在cpu、内存利用率上docker将会在效率上有明显的优势。 docker利用的是宿主机的内核,而不需要Guest OS。因此创建一个容器时,不需要和虚拟机一样重新加载一个操作系统内核。从而避免引寻、加载操作系统内核返回时耗时耗资源的过程,当新建一个虚拟机时,虚拟机软件需要加载Guest OS,返回新建过程是分钟级别的。而新建一个docker容器只需要几秒钟。 Docker和JVM相比: Docker运行几乎没有额外性能损失,JVM操作系统额外的CPU、内存消耗 Docker移植性轻便、灵活、适应于Linux,而JVM笨重,和虚拟机耦合度高 Docker存储的镜像小,便于存储和传输。JVM镜像庞大。
微服务
微服务概念
什么是微服务
微服务架构是一种架构模式或者说是一种架构风格,它提倡将单一应用程序划分为一组小的服务,每个服务运行在其独立的自己的进程中,服务之间相互协调、互相配合,为用户提供最终价值。服务之间采用轻量级的通信机制互相沟通(通常是基于HTTP的RESTful API),每个服务都围绕着具体的业务进行构建,并且能够被独立的构建在生产环境、类生产环境等。另外,应避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建,可以有一个非常轻量级的集中式管理来协调这些服务,可以使用不同的语言来编写服务,也可以使用不同的数据存储。
注册中心
常用的服务注册中心
Zookeeper和Eureka分别是满足CAP中的哪些
Spring Cloud用到什么东西?如何实现负载均衡?服务挂了注册中心怎么判断?
Eureka和zookeeper都可以提供服务注册与发现的功能,请说说两个的区别?
Zookeeper保证了CP(C:一致性,P:分区容错性),Eureka保证了AP(A:高可用) 1.当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的信息,但不能容忍直接down掉不可用。也就是说,服务注册功能对高可用性要求比较高,但zk会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新选leader。问题在于,选取leader时间过长,30 ~ 120s,且选取期间zk集群都不可用,这样就会导致选取期间注册服务瘫痪。在云部署的环境下,因网络问题使得zk集群失去master节点是较大概率会发生的事,虽然服务能够恢复,但是漫长的选取时间导致的注册长期不可用是不能容忍的。
2.Eureka保证了可用性,Eureka各个节点是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点仍然可以提供注册和查询服务。而Eureka的客户端向某个Eureka注册或发现时发生连接失败,则会自动切换到其他节点,只要有一台Eureka还在,就能保证注册服务可用,只是查到的信息可能不是最新的。除此之外,Eureka还有自我保护机制,如果在15分钟内超过85%的节点没有正常的心跳,那么Eureka就认为客户端与注册中心发生了网络故障,此时会出现以下几种情况: ①、Eureka不在从注册列表中移除因为长时间没有收到心跳而应该过期的服务。 ②、Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点上(即保证当前节点仍然可用) ③、当网络稳定时,当前实例新的注册信息会被同步到其他节点。
因此,Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像Zookeeper那样使整个微服务瘫痪
Zookeeper
ZooKeeper 是什么?
ZooKeeper 是一个开放源码的分布式协调服务,它是集群的管理者,监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终,将简单易用的接口和性能高效、功能稳定的系统提供给用户。 分布式应用程序可以基于 Zookeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。 Zookeeper 保证了如下分布式一致性特性: (1)顺序一致性 (2)原子性 (3)单一视图 (4)可靠性 (5)实时性(最终一致性) 客户端的读请求可以被集群中的任意一台机器处理,如果读请求在节点上注册了监听器,这个监听器也是由所连接的 zookeeper 机器来处理。对于写请求,这些请求会同时发给其他 zookeeper 机器并且达成一致后,请求才会返回成功。因此,随着 zookeeper 的集群机器增多,读请求的吞吐会提高但是写请求的吞吐会下降。 有序性是 zookeeper 中非常重要的一个特性,所有的更新都是全局有序的,每个更新都有一个唯一的时间戳,这个时间戳称为 zxid(Zookeeper Transaction Id)。而读请求只会相对于更新有序,也就是读请求的返回结果中会带有这个zookeeper 最新的 zxid。
四种类型的数据节点 Znode
(1)PERSISTENT-持久节点 除非手动删除,否则节点一直存在于 Zookeeper 上 (2)EPHEMERAL-临时节点 临时节点的生命周期与客户端会话绑定,一旦客户端会话失效(客户端与zookeeper 连接断开不一定会话失效),那么这个客户端创建的所有临时节点都会被移除。 (3)PERSISTENT_SEQUENTIAL-持久顺序节点 基本特性同持久节点,只是增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。 (4)EPHEMERAL_SEQUENTIAL-临时顺序节点 基本特性同临时节点,增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。
ZAB 协议?
ZAB 协议是为分布式协调服务 Zookeeper 专门设计的一种支持崩溃恢复的原子广播协议。 ZAB 协议包括两种基本的模式:崩溃恢复和消息广播。 当整个 zookeeper 集群刚刚启动或者 Leader 服务器宕机、重启或者网络故障导致不存在过半的服务器与 Leader 服务器保持正常通信时,所有进程(服务器)进入崩溃恢复模式,首先选举产生新的 Leader 服务器,然后集群中 Follower 服务器开始与新的 Leader 服务器进行数据同步,当集群中超过半数机器与该 Leader服务器完成数据同步之后,退出恢复模式进入消息广播模式,Leader 服务器开始接收客户端的事务请求生成事物提案来进行事务请求处理。
Zookeeper 怎么保证主从节点的状态同步?
Zookeeper 的核心是原子广播机制,这个机制保证了各个 server 之间的同步。实现这个机制的协议叫做 Zab 协议。Zab 协议有两种模式,它们分别是恢复模式和广播模式。 恢复模式 当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数 server 完成了和 leader 的状态同步以后,恢复模式就结束了。状态同步保证了 leader 和 server 具有相同的系统状态。 广播模式 一旦 leader 已经和多数的 follower 进行了状态同步后,它就可以开始广播消息了,即进入广播状态。这时候当一个 server 加入 ZooKeeper 服务中,它会在恢复模式下启动,发现 leader,并和 leader 进行状态同步。待到同步结束,它也参与消息广播。ZooKeeper 服务一直维持在 Broadcast 状态,直到 leader 崩溃了或者 leader 失去了大部分的 followers 支持。
Zookeeper Watcher 机制 -- 数据变更通知
Zookeeper 允许客户端向服务端的某个 Znode 注册一个 Watcher 监听,当服务端的一些指定事件触发了这个 Watcher,服务端会向指定客户端发送一个事件通知来实现分布式的通知功能,然后客户端根据 Watcher 通知状态和事件类型做出业务上的改变。 工作机制: (1)客户端注册 watcher (2)服务端处理 watcher (3)客户端回调 watcher Watcher 特性总结: (1)一次性 无论是服务端还是客户端,一旦一个 Watcher 被 触 发 ,Zookeeper 都会将其从相应的存储中移除。这样的设计有效的减轻了服务端的压力,不然对于更新非常频繁的节点,服务端会不断的向客户端发送事件通知,无论对于网络还是服务端的压力都非常大。 (2)客户端串行执行 客户端 Watcher 回调的过程是一个串行同步的过程。 (3)轻量 3.1、Watcher 通知非常简单,只会告诉客户端发生了事件,而不会说明事件的具体内容。 3.2、客户端向服务端注册 Watcher 的时候,并不会把客户端真实的 Watcher 对象实体传递到服务端,仅仅是在客户端请求中使用 boolean 类型属性进行了标记。 (4)watcher event 异步发送 watcher 的通知事件从 server 发送到 client 是异步的,这就存在一个问题,不同的客户端和服务器之间通过 socket 进行通信,由于网络延迟或其他因素导致客户端在不通的时刻监听到事件,由于 Zookeeper 本身提供了 ordering guarantee,即客户端监听事件后,才会感知它所监视 znode发生了变化。所以我们使用 Zookeeper 不能期望能够监控到节点每次的变化。Zookeeper 只能保证最终的一致性,而无法保证强一致性。 (5)注册 watcher getData、exists、getChildren (6)触发 watcher create、delete、setData (7)当一个客户端连接到一个新的服务器上时,watch 将会被以任意会话事件触发。当与一个服务器失去连接的时候,是无法接收到 watch 的。而当 client 重新连接时,如果需要的话,所有先前注册过的 watch,都会被重新注册。通常这是完全透明的。只有在一个特殊情况下,watch 可能会丢失:对于一个未创建的 znode的 exist watch,如果在客户端断开连接期间被创建了,并且随后在客户端连接上之前又删除了,这种情况下,这个 watch 事件可能会被丢失。
客户端注册 Watcher 实现
(1)调用 getData()/getChildren()/exist()三个 API,传入 Watcher 对象 (2)标记请求 request,封装 Watcher 到 WatchRegistration (3)封装成 Packet 对象,发服务端发送 request (4)收到服务端响应后,将 Watcher 注册到 ZKWatcherManager 中进行管理 (5)请求返回,完成注册。
服务端处理 Watcher 实现
(1)服务端接收 Watcher 并存储 接收到客户端请求,处理请求判断是否需要注册 Watcher,需要的话将数据节点的节点路径和 ServerCnxn(ServerCnxn 代表一个客户端和服务端的连接,实现了 Watcher 的 process 接口,此时可以看成一个 Watcher 对象)存储在WatcherManager 的 WatchTable 和 watch2Paths 中去。 (2)Watcher 触发 以服务端接收到 setData() 事务请求触发 NodeDataChanged 事件为例: 2.1 封装 WatchedEvent 将通知状态(SyncConnected)、事件类型(NodeDataChanged)以及节点路径封装成一个 WatchedEvent 对象 2.2 查询 Watcher 从 WatchTable 中根据节点路径查找 Watcher 2.3 没找到;说明没有客户端在该数据节点上注册过 Watcher 2.4 找到;提取并从 WatchTable 和 Watch2Paths 中删除对应 Watcher(从这里可以看出 Watcher 在服务端是一次性的,触发一次就失效了) (3)调用 process 方法来触发 Watcher 这里 process 主要就是通过 ServerCnxn 对应的 TCP 连接发送 Watcher 事件通知。
客户端回调 Watcher
客户端 SendThread 线程接收事件通知,交由 EventThread 线程回调 Watcher。 客户端的 Watcher 机制同样是一次性的,一旦被触发后,该 Watcher 就失效了
ACL 权限控制机制
UGO(User/Group/Others) 目前在 Linux/Unix 文件系统中使用,也是使用最广泛的权限控制方式。是一种粗粒度的文件系统权限控制模式。 ACL(Access Control List)访问控制列表 包括三个方面: 权限模式(Scheme) (1)IP:从 IP 地址粒度进行权限控制 (2)Digest:最常用,用类似于 username:password 的权限标识来进行权限配置,便于区分不同应用来进行权限控制 (3)World:最开放的权限控制方式,是一种特殊的 digest 模式,只有一个权限标识“world:anyone” (4)Super:超级用户 授权对象 授权对象指的是权限赋予的用户或一个指定实体,例如 IP 地址或是机器灯。 权限 Permission (1)CREATE:数据节点创建权限,允许授权对象在该 Znode 下创建子节点 (2)DELETE:子节点删除权限,允许授权对象删除该数据节点的子节点 (3)READ:数据节点的读取权限,允许授权对象访问该数据节点并读取其数据内容或子节点列表等 (4)WRITE:数据节点更新权限,允许授权对象对该数据节点进行更新操作 (5)ADMIN:数据节点管理权限,允许授权对象对该数据节点进行 ACL 相关设置操作
Chroot 特性
3.2.0 版本后,添加了 Chroot 特性,该特性允许每个客户端为自己设置一个命名空间。如果一个客户端设置了 Chroot,那么该客户端对服务器的任何操作,都将会被限制在其自己的命名空间下。 通过设置 Chroot,能够将一个客户端应用于 Zookeeper 服务端的一颗子树相对应,在那些多个应用公用一个 Zookeeper 进群的场景下,对实现不同应用间的相互隔离非常有帮助。
会话管理
分桶策略:将类似的会话放在同一区块中进行管理,以便于 Zookeeper 对会话进行不同区块的隔离处理以及同一区块的统一处理。 分配原则:每个会话的“下次超时时间点”(ExpirationTime) 计算公式: ExpirationTime_ = currentTime + sessionTimeout ExpirationTime = (ExpirationTime_ / ExpirationInrerval + 1) * ExpirationInterval , ExpirationInterval 是指 Zookeeper 会话超时检查时间间隔,默认 tickTime
服务器角色
Leader (1)事务请求的唯一调度和处理者,保证集群事务处理的顺序性 (2)集群内部各服务的调度者 Follower (1)处理客户端的非事务请求,转发事务请求给 Leader 服务器 (2)参与事务请求 Proposal 的投票 (3)参与 Leader 选举投票 Observer (1)3.0 版本以后引入的一个服务器角色,在不影响集群事务处理能力的基础上提升集群的非事务处理能力 (2)处理客户端的非事务请求,转发事务请求给 Leader 服务器 (3)不参与任何形式的投票
Zookeeper 下 Server 工作状态
服务器具有四种状态,分别是 LOOKING、FOLLOWING、LEADING、OBSERVING。 (1)LOOKING:寻 找 Leader 状态。当服务器处于该状态时,它会认为当前集群中没有 Leader,因此需要进入 Leader 选举状态。 (2)FOLLOWING:跟随者状态。表明当前服务器角色是 Follower。 (3)LEADING:领导者状态。表明当前服务器角色是 Leader。 (4)OBSERVING:观察者状态。表明当前服务器角色是 Observer。
数据同步
整个集群完成 Leader 选举之后,Learner(Follower 和 Observer 的统称)回向Leader 服务器进行注册。当 Learner 服务器想 Leader 服务器完成注册后,进入数据同步环节。 数据同步流程:(均以消息传递的方式进行) Learner 向 Learder 注册 数据同步 同步确认 Zookeeper 的数据同步通常分为四类: (1)直接差异化同步(DIFF 同步) (2)先回滚再差异化同步(TRUNC+DIFF 同步) (3)仅回滚同步(TRUNC 同步) (4)全量同步(SNAP 同步) 在进行数据同步前,Leader 服务器会完成数据同步初始化: peerLastZxid: · 从 learner 服务器注册时发送的 ACKEPOCH 消息中提取 lastZxid(该Learner 服务器最后处理的 ZXID) minCommittedLog: · Leader 服务器 Proposal 缓存队列 committedLog 中最小 ZXIDmaxCommittedLog: · Leader 服务器 Proposal 缓存队列 committedLog 中最大 ZXID直接差异化同步(DIFF 同步) · 场景:peerLastZxid 介于 minCommittedLog 和 maxCommittedLog之间先回滚再差异化同步(TRUNC+DIFF 同步) · 场景:当新的 Leader 服务器发现某个 Learner 服务器包含了一条自己没有的事务记录,那么就需要让该 Learner 服务器进行事务回滚--回滚到 Leader服务器上存在的,同时也是最接近于 peerLastZxid 的 ZXID仅回滚同步(TRUNC 同步) · 场景:peerLastZxid 大于 maxCommittedLog 全量同步(SNAP 同步) · 场景一:peerLastZxid 小于 minCommittedLog · 场景二:Leader 服务器上没有 Proposal 缓存队列且 peerLastZxid 不等于 lastProcessZxid
zookeeper 是如何保证事务的顺序一致性的?
zookeeper 采用了全局递增的事务 Id 来标识,所有的 proposal(提议)都在被提出的时候加上了 zxid,zxid 实际上是一个 64 位的数字,高 32 位是 epoch( 时期; 纪元; 世; 新时代)用来标识 leader 周期,如果有新的 leader 产生出来,epoch会自增,低 32 位用来递增计数。当新产生 proposal 的时候,会依据数据库的两阶段过程,首先会向其他的 server 发出事务执行请求,如果超过半数的机器都能执行并且能够成功,那么就会开始执行。
分布式集群中为什么会有 Master?
在分布式环境中,有些业务逻辑只需要集群中的某一台机器进行执行,其他的机器可以共享这个结果,这样可以大大减少重复计算,提高性能,于是就需要进行leader 选举。
zk 节点宕机如何处理?
Zookeeper 本身也是集群,推荐配置不少于 3 个服务器。Zookeeper 自身也要保证当一个节点宕机时,其他节点会继续提供服务。 如果是一个 Follower 宕机,还有 2 台服务器提供访问,因为 Zookeeper 上的数据是有多个副本的,数据并不会丢失; 如果是一个 Leader 宕机,Zookeeper 会选举出新的 Leader。 ZK 集群的机制是只要超过半数的节点正常,集群就能正常提供服务。只有在 ZK节点挂得太多,只剩一半或不到一半节点能工作,集群才失效。 所以 3 个节点的 cluster 可以挂掉 1 个节点(leader 可以得到 2 票>1.5) 2 个节点的 cluster 就不能挂掉任何 1 个节点了(leader 可以得到 1 票<=1)
zookeeper 负载均衡和 nginx 负载均衡区别
zk 的负载均衡是可以调控,nginx 只是能调权重,其他需要可控的都需要自己写插件;但是 nginx 的吞吐量比 zk 大很多,应该说按业务选择用哪种方式。
Zookeeper 有哪几种几种部署模式?
机模式、伪集群模式、集群模式。
集群最少要几台机器,集群规则是怎样的?
集群规则为 2N+1 台,N>0,即 3 台。
集群支持动态添加机器吗?
其实就是水平扩容了,Zookeeper 在这方面不太好。两种方式: 全部重启:关闭所有 Zookeeper 服务,修改配置之后启动。不影响之前客户端的会话。 逐个重启:在过半存活即可用的原则下,一台机器重启不影响整个集群对外提供服务。这是比较常用的方式。 3.5 版本开始支持动态扩容。
ookeeper 对节点的 watch 监听通知是永久的吗?为什么不是永久的?
不是。官方声明:一个 Watch 事件是一个一次性的触发器,当被设置了 Watch的数据发生了改变的时候,则服务器将这个改变发送给设置了 Watch 的客户端,以便通知它们。 为什么不是永久的,举个例子,如果服务端变动频繁,而监听的客户端很多情况下,每次变动都要通知到所有的客户端,给网络和服务器造成很大压力。 一般是客户端执行 getData(“/节点 A”,true),如果节点 A 发生了变更或删除,客户端会得到它的 watch 事件,但是在之后节点 A 又发生了变更,而客户端又没有设置 watch 事件,就不再给客户端发送。 在实际应用中,很多情况下,我们的客户端不需要知道服务端的每一次变动,我只要最新的数据即可。
Zookeeper 的 java 客户端都有哪些?
java 客户端:zk 自带的 zkclient 及 Apache 开源的 Curator。
chubby 是什么,和 zookeeper 比你怎么看?
chubby 是 google 的,完全实现 paxos 算法,不开源。zookeeper 是 chubby的开源实现,使用 zab 协议,paxos 算法的变种。
说几个 zookeeper 常用的命令。
常用命令:ls get set create delete 等。
ZAB 和 Paxos 算法的联系与区别?
相同点: (1)两者都存在一个类似于 Leader 进程的角色,由其负责协调多个 Follower 进程的运行 (2)Leader 进程都会等待超过半数的 Follower 做出正确的反馈后,才会将一个提案进行提交 (3)ZAB 协议中,每个 Proposal 中都包含一个 epoch 值来代表当前的 Leader周期,Paxos 中名字为 Ballot 不同点: ZAB 用来构建高可用的分布式数据主备系统(Zookeeper),Paxos 是用来构建分布式一致性状态机系统。
Zookeeper 的典型应用场景
Zookeeper 是一个典型的发布/订阅模式的分布式数据管理与协调框架,开发人员可以使用它来进行分布式数据的发布和订阅。 通过对 Zookeeper 中丰富的数据节点进行交叉使用,配合 Watcher 事件通知机制,可以非常方便的构建一系列分布式应用中年都会涉及的核心功能,如: (1)数据发布/订阅 (2)负载均衡 (3)命名服务 (4)分布式协调/通知 (5)集群管理 (6)Master 选举 (7)分布式锁 (8)分布式队列
配置中心
负载均衡
负载平衡的意义什么?
负载平衡旨在优化资源使用,最大化吞吐量,最小化响应时间并避免任何单一资源 的过载。使用多个组件进行负载平衡而不是单个组件可能会通过冗余来提高可靠性和可用性
简述一下什么是Nginx,它有什么优势和功能?
Nginx是一个web服务器和反向代理服务器,用于HTTP、HTTPS、SMTP、POP3和IMAP协议。因它的稳定性、丰富的功能集、示例配置文件和低系统资源的消耗而闻名。 Nginx---Ngine X,是一款免费的、自由的、开源的、高性能HTTP服务器和反向代理服务器;也是一个IMAP、POP3、SMTP代理服务器;Nginx以其高性能、稳定性、丰富的功能、简单的配置和低资源消耗而闻名。 也就是说Nginx本身就可以托管网站(类似于Tomcat一样),进行Http服务处理,也可以作为反向代理服务器 、负载均衡器和HTTP缓存。 Nginx 解决了服务器的C10K(就是在一秒之内连接客户端的数目为10k即1万)问题。它的设计不像传统的服务器那样使用线程处理请求,而是一个更加高级的机制—事件驱动机制,是一种异步事件驱动结构。 优点: (1)更快 这表现在两个方面:一方面,在正常情况下,单次请求会得到更快的响应;另一方面,在高峰期(如有数以万计的并发请求),Nginx可以比其他Web服务器更快地响应请求。 (2)高扩展性,跨平台 Nginx的设计极具扩展性,它完全是由多个不同功能、不同层次、不同类型且耦合度极低的模块组成。因此,当对某一个模块修复Bug或进行升级时,可以专注于模块自身,无须在意其他。这使得第三方模块一样具备极其优秀的性能,充分利用Nginx的高并发特性,因此,许多高流量的网站都倾向于开发符合自己业务特性的定制模块。 (3)高可靠性:用于反向代理,宕机的概率微乎其微 高可靠性是我们选择Nginx的最基本条件,因为Nginx的可靠性是大家有目共睹的,很多家高流量网站都在核心服务器上大规模使用Nginx。Nginx的高可靠性来自于其核心框架代码的优秀设计、模块设计的简单性;另外,官方提供的常用模块都非常稳定,每个worker进程相对独立,master进程在1个worker进程出错时可以快速“拉起”新的worker子进程提供服务。 (4)低内存消耗 一般情况下,10 000个非活跃的HTTP Keep-Alive连接在Nginx中仅消耗2.5MB的内存,这是Nginx支持高并发连接的基础。 (5)单机支持10万以上的并发连接 这是一个非常重要的特性!随着互联网的迅猛发展和互联网用户数量的成倍增长,各大公司、网站都需要应付海量并发请求,一个能够在峰值期顶住10万以上并发请求的Server,无疑会得到大家的青睐。理论上,Nginx支持的并发连接上限取决于内存,10万远未封顶。当然,能够及时地处理更多的并发请求,是与业务特点紧密相关的。 (6)热部署 master管理进程与worker工作进程的分离设计,使得Nginx能够提供热部署功能,即可以在7×24小时不间断服务的前提下,升级Nginx的可执行文件。当然,它也支持不停止服务就更新配置项、更换日志文件等功能。 (7)最自由的BSD许可协议 这是Nginx可以快速发展的强大动力。BSD许可协议不只是允许用户免费使用Nginx,它还允许用户在自己的项目中直接使用或修改Nginx源码,然后发布。这吸引了无数开发者继续为Nginx贡献自己的智慧。 以上7个特点当然不是Nginx的全部,拥有无数个官方功能模块、第三方功能模块使得Nginx能够满足绝大部分应用场景,这些功能模块间可以叠加以实现更加强大、复杂的功能,有些模块还支持Nginx与Perl、Lua等脚本语言集成工作,大大提高了开发效率。这些特点促使用户在寻找一个Web服务器时更多考虑Nginx。 选择Nginx的核心理由还是它能在支持高并发请求的同时保持高效的服务
Nginx是如何处理一个HTTP请求的呢?
Nginx 是一个高性能的 Web 服务器,能够同时处理大量的并发请求。它结合多进程机制和异步机制 ,异步机制使用的是异步非阻塞方式 ,接下来就给大家介绍一下 Nginx 的多线程机制和异步非阻塞机制 。 1、多进程机制 服务器每当收到一个客户端时,就有 服务器主进程 ( master process )生成一个 子进程( worker process )出来和客户端建立连接进行交互,直到连接断开,该子进程就结束了。 使用进程的好处是各个进程之间相互独立,不需要加锁,减少了使用锁对性能造成影响,同时降低编程的复杂度,降低开发成本。其次,采用独立的进程,可以让进程互相之间不会影响 ,如果一个进程发生异常退出时,其它进程正常工作, master 进程则很快启动新的 worker 进程,确保服务不会中断,从而将风险降到最低。 缺点是操作系统生成一个子进程需要进行 内存复制等操作,在资源和时间上会产生一定的开销。当有大量请求时,会导致系统性能下降 。 2、异步非阻塞机制 每个工作进程 使用 异步非阻塞方式 ,可以处理 多个客户端请求 。 当某个 工作进程 接收到客户端的请求以后,调用 IO 进行处理,如果不能立即得到结果,就去 处理其他请求 (即为 非阻塞 );而 客户端 在此期间也 无需等待响应 ,可以去处理其他事情(即为 异步 )。 当 IO 返回时,就会通知此 工作进程 ;该进程得到通知,暂时 挂起 当前处理的事务去 响应客户端请求 。
请解释Nginx服务器上的Master和Worker进程分别是什么?
主程序 Master process 启动后,通过一个 for 循环来 接收 和 处理外部信号 ;
主进程通过 fork() 函数产生 worker 子进程 ,每个子进程执行一个 for循环来实现Nginx服务器对事件的接收和处理 。
请解释代理中的正向代理和反向代理
首先,代理服务器一般指局域网内部的机器通过代理服务器发送请求到互联网上的服务器,代理服务器一般作用在客户端。例如:GoAgent翻墙软件。我们的客户端在进行翻墙操作的时候,我们使用的正是正向代理,通过正向代理的方式,在我们的客户端运行一个软件,将我们的HTTP请求转发到其他不同的服务器端,实现请求的分发。
反向代理服务器作用在服务器端,它在服务器端接收客户端的请求,然后将请求分发给具体的服务器进行处理,然后再将服务器的相应结果反馈给客户端。Nginx就是一个反向代理服务器软件。
正向代理和反向代理的区别
虽然正向代理服务器和反向代理服务器所处的位置都是客户端和真实服务器之间,所做的事情也都是把客户端的请求转发给服务器,再把服务器的响应转发给客户端,但是二者之间还是有一定的差异的。 1、正向代理其实是客户端的代理,帮助客户端访问其无法访问的服务器资源。反向代理则是服务器的代理,帮助服务器做负载均衡,安全防护等。 2、正向代理一般是客户端架设的,比如在自己的机器上安装一个代理软件。而反向代理一般是服务器架设的,比如在自己的机器集群中部署一个反向代理服务器。 3、正向代理中,服务器不知道真正的客户端到底是谁,以为访问自己的就是真实的客户端。而在反向代理中,客户端不知道真正的服务器是谁,以为自己访问的就是真实的服务器。 4、正向代理和反向代理的作用和目的不同。正向代理主要是用来解决访问限制问题。而反向代理则是提供负载均衡、安全防护等作用。二者均能提高访问速度。
熔断降级与限流
什么是服务熔断?什么是服务降级?
熔断机制是应对雪崩效应的一种微服务链路保护机制。当某个微服务不可用或者响应时间太长时,会进行服务降级,进而熔断该节点微服务的调用,快速返回“错误”的响应信息。当检测到该节点微服务调用响应正常后恢复调用链路。在SpringCloud框架里熔断机制通过Hystrix实现,Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内调用20次,如果失败,就会启动熔断机制。
服务降级,一般是从整体负荷考虑。就是当某个服务熔断之后,服务器将不再被调用,此时客户端可以自己准备一个本地的fallback回调,返回一个缺省值。这样做,虽然水平下降,但好歹可用,比直接挂掉强。
什么是Hystrix?它如何实现容错?
Hystrix是一个延迟和容错库,旨在隔离远程系统,服务和第三方库的访问点,当出现故障是不可避免的故障时,停止级联故障并在复杂的分布式系统中实现弹性。
使用Hystrix定义了一个回退方法,这种后备方法应该具有与公开服务相同的返回类型。如果暴露服务中出现异常,则回退方法将返回一些值。
链路追踪
网关路由
服务路由机制是怎么实现的
权限认证
消息总线
服务监控
DEVOPS
分布式
理论
CAP
CAP定义
CAP图
在理论计算机科学中,CAP定理(CAP theorem),又被称作布鲁尔定理(Brewer’s theorem),它指出对于一个分布式计算系统来说,不可能同时满足以下三点:
CAP定理的证明
详细说明
关于CAP这三个特性我们就介绍完了,接下来我们试着证明一下为什么CAP不能同时满足。 为了简化证明的过程,我们假设整个集群里只有两个N1和N2两个节点,如下图: N1和N2当中各自有一个应用程序AB和数据库,当系统满足一致性的时候,我们认为N1和N2数据库中的数据保持一致。在满足可用性的时候,我们认为无论用户访问N1还是N2,都可以获得正确的结果,在满足分区容错性的时候,我们认为无论N1还是N2宕机或者是两者的通信中断,都不影响系统的运行。 我们假设一种极端情况,假设某个时刻N1和N2之间的网络通信突然中断了。如果系统满足分区容错性,那么显然可以支持这种异常。问题是在此前提下,一致性和可用性是否可以做到不受影响呢? 我们做个假象实验,如下图,突然某一时刻N1和N2之间的关联断开: 有用户向N1发送了请求更改了数据,将数据库从V0更新成了V1。由于网络断开,所以N2数据库依然是V0,如果这个时候有一个请求发给了N2,但是N2并没有办法可以直接给出最新的结果V1,这个时候该怎么办呢? 这个时候无法两种方法,一种是将错就错,将错误的V0数据返回给用户。第二种是阻塞等待,等待网络通信恢复,N2中的数据更新之后再返回给用户。显然前者牺牲了一致性,后者牺牲了可用性。 这个例子虽然简单,但是说明的内容却很重要。在分布式系统当中,CAP三个特性我们是无法同时满足的,必然要舍弃一个。三者舍弃一个,显然排列组合一共有三种可能。
数据一致性算法
分布式一致性协议raft,paxos 了解吗
分布式与集群的区别是什么?
分布式: 一个业务分拆多个子业务,部署在不同的服务器上 集群: 同一个业务,部署在多个服务器上。比如之前做电商网站搭的redis集群以及solr集群都是属于将redis服务器提供的缓存服务以及solr服务器提供的搜索服务部署在多个服务器上以提高系统性能、并发量解决海量存储问题。
BASE
BASE理论
BASE 是 Basically Available(基本可用) 、Soft-state(软状态) 和 Eventually Consistent(最终一致性) 三个短语的缩写。BASE理论是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于CAP定理逐步演化而来的,它大大降低了我们对系统的要求
BASE理论的核心思想
即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。也就是牺牲数据的一致性来满足系统的高可用性,系统中一部分数据不可用或者不一致时,仍需要保持系统整体“主要可用”。
BASE理论三要素
1. 基本可用 基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性。但是,这绝不等价于系统不可用。 比如: 响应时间上的损失:正常情况下,一个在线搜索引擎需要在0.5秒之内返回给用户相应的查询结果,但由于出现故障,查询结果的响应时间增加了1~2秒 系统功能上的损失:正常情况下,在一个电子商务网站上进行购物的时候,消费者几乎能够顺利完成每一笔订单,但是在一些节日大促购物高峰的时候,由于消费者的购物行为激增,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面 2. 软状态 软状态指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时 3. 最终一致性 最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
RPC
分布式锁
分布式锁的实现你知道的有哪些?具体详细谈一种实现方式
取代redis分布式锁方案
分布式存储
分布式ID解决方案
分布式会话
什么是分布式session问题
cookie和seesion,session如何维持HTTP的无状态型?
session的生命周期是多久
分布式事务
微服务架构下,如果有一个订单系统,一个库存系统,怎么保证事务?
分布式定时任务
分布式日志搜索系统
系统工具
日志框架
单元测试框架
持续集成
Jenkins
开发规范
高并发设计
项目性能有没有考虑过?(我是做服务端的,主要考虑请求并发量)数据库性能呢?
高并发情况下的解决策略
高并发情况下的解决策略
分布式系统概念
控制QPS
高并发的应用场景,技术需要涉及到哪些?怎样来架构设计?
秒杀模块怎么设计的
如何防止超卖
项目架构,项目如何解决并发量
多个RPC请求进来,服务器怎么处理并发呢
如何做压测,抗压手段
架构设计
介绍一下觉得做得最好的项目?画一下项目的框架结构图?Nginx用来做什么?采用了什么负载均衡策略?万一某一个服务器挂掉怎么办?(一致性hash)如果添加一个节点呢?
项目整个架构 优化思路
遇到最大困难是什么?怎么克服?
线上的服务器监控指标,你认为哪些指标是最需要关注的?为什么?
项目QPS?数据来源和传输形式?
请画一个完整大型网站的分布式服务器集群部署图
谈谈你对SOA和微服务的理解,以及分布式架构从应用层面涉及到的调整和挑战。
单点登录
系统性能排查及优化
cpu 100%怎样定位
算法/数据结构
其他
拜占庭问题
一致性哈希
NP 问题
生产者-消费者 模型
你的技术栈规划是怎么样的?
觉得自己做得最好的项目?项目技术架构?
十亿个IP,获得访问次数最多的十个
五个单词,在一个长文本中查找是否存在
Github有用吗?平常有看一些博客和书籍吗?平常有用脚本语言做一些方便自己的事情吗?
一个五升桶,一个三升桶,怎么倒出四升水,水可以无限量使用
从4亿qq号码中,统计出出现次数top 10的号码
滴滴派发订单功能设计?
英雄联盟H5小游戏排名,实时动态更新,如何实现?
杭州G20形象工程需要擦窗子,估计杭州擦窗子要多少费用?
字符串数目从 0开始,一直往后递增,字符串大小不需要考虑,只是字符串的数量不断增加,在高并发的情况下, QPS十几万时,怎么生成这个确定的唯一 id,还可以保证下次查询时高效率的查到
你知道的开源协议有哪些?
处理日志,获取error的日志,去重,排序(本意是让用shell写的,但我不会,就用Java写了)
爬虫用的是什么?有用框架吗?有用多线程爬虫吗?
编程题
输入一个数组和一个整数,数组的长度、数组元素的范围、整数的范围都是1~20000,求数组中的某几项加起来等于整数的可能性组合数?
数组顺时针旋转90度
输入一个字符串String str,求重复次数最多的字符的第一个索引值
判断树对称
用wait和notify模拟生产者消费者模式
手写快排
写链表翻转(代码)
合并k个有序数组(代码)
生产者消费者模型的实现(堵塞队列,代码)
大数据排序
给定一个矩阵,从左上角开始只能往下或者右走,求到达右下角的最小权值路径
LRU写下大概的代码,单链表的回文判断要求O1空间
一道大数据量的题目,A文件有3T,里面放的是uid+uname,B文件2T,里面放的是uid+unage,找出相同的uid并写成uid+uname+uage的样子,限制内存2G
求一棵树的镜像,给我一个List ,User有自己的id和父亲的id,要我转成一棵树
有序链表的合并
手写hashmap的put方法(不考虑扩容红黑的情况),单链表的反转,单链表每k个翻转(看我写太快加了一道,1-2-3-4-5-6-7,k为3的话要变成3-2-1-6-5-4-7,我的思路是每K个截断,挨个翻转最后合并,On的空间,面试官说可以的,后来看leetcode有O1的空间,逻辑比较绕)
罗马数字字符串转阿拉伯数字
写一个你认为最好的单例模式B树和B+树是解决什么样的问题的,怎样演化过来,之间区别
写一个生产者消费者模式
写一个死锁
写一段代码在遍历 ArrayList 时移除一个元素
手写一下 LRU 代码实现?
快排
算法
堆排序
堆排序的时间复杂度、空间复杂度、排序的的过程
二叉树
paxos算法
合并区间 快排
.二叉树 转 链表
跳台阶
手撕 LRU
链表结构
红黑树、二叉树的算法
算法题:红蓝两种球,总共N个, N>2, 排列组合,连续3个颜色一样是非法的,求合法的排列数量
最大堆中求前k个最大值的时间复杂度
海岛面积计算题,给一个矩阵,0表示海水,相连的1表示海岛,上下左右表示相连
排序算法了解哪些?Java里内置的是用什么排序方法?快排是稳定的吗?快排排对象的时候有什么问题?(提示我,三个字段,第一个字段和第二个字段做hash,第三个不做,还是不太懂)
买卖股票的最佳时机
手撕约瑟夫环
红黑树、二叉树的算法
快排的时间复杂度,最坏情况呢,最好情况呢,堆排序的时间复杂度呢,建堆的复杂度是多少
如何判断链表是否有环
海量数据,找重复数量前几的数据
选择排序原理,时间、空间复杂度
排序算法
冒泡排序
选择排序
插入排序
归并排序
快速排序
希尔排序
贪心算法
分金条问题
IPO问题
路灯问题
字典序问题
NIM博弈
暴力递归
汉诺塔问题
八皇后问题
牛吃草问题
扑克牌问题
字符串全排
查找算法
线性表查找
散列表查找
树结构查找
算法思想
枚举算法
分治算法
回溯分析
摊还分析
数据结构
线性表结构
数组
查找数组中第二小的元素:https://www.geeksforgeeks.org/to-find-smallest-and-second-smallest-element-in-an-array/
查找第一个没有重复的数组元素: https://www.geeksforgeeks.org/non-repeating-element/
合并 2 个排序好的数组:https://www.geeksforgeeks.org/merge-two-sorted-arrays/
重新排列数组中的正数和负数: https://www.geeksforgeeks.org/rearrange-positive-and-negative-numbers-publish/
链表
倒转 1 个链表:https://www.geeksforgeeks.org/reverse-a-linked-list/
检查链表中是否存在循环:https://www.geeksforgeeks.org/detect-loop-in-a-linked-list/
返回链表倒数第 N 个元素:https://www.geeksforgeeks.org/nth-node-from-the-end-of-a-linked-list/
移除链表中的重复元素:https://www.geeksforgeeks.org/remove-duplicates-from-an-unsorted-linked-list/
队列
使用队列实现栈:https://www.geeksforgeeks.org/implement-stack-using-queue/
倒转队列的前 K 个元素:https://www.geeksforgeeks.org/reversing-first-k-elements-queue/
使用队列将 1 到 n 转换为二进制: https://www.geeksforgeeks.org/interesting-method-generate-binary-numbers-1-n/
栈
使用栈计算后缀表达式:https://www.geeksforgeeks.org/stack-set-4-evaluation-postfix-expression/
使用栈为栈中的元素排序:https://www.geeksforgeeks.org/sort-stack-using-temporary-stack/
检查字符串中的括号是否匹配正确:https://www.geeksforgeeks.org/check-for-balanced-parentheses-in-an-expression/
Hash 散列表结构
查找数组中对称的组合: https://www.geeksforgeseks.org/given-an-array-of-pairs-find-all-symmetric-pairs-in-it/
确认某个数组的元素是否为另一个数组元素的子集: https://www.geeksforgeeks.org/find-whether-an-array-is-subset-of-another-array-set-1/
确认给定的数组是否互斥: https://www.geeksforgeeks.org/check-two-given-sets-disjoint/
堆结构
二叉堆
小顶堆
大顶堆
树结构
二叉树
查找二叉平衡树中第 K 大的元素:https://www.geeksforgeeks.org/kth-largest-element-in-bst-when-modification-to-bst-is-not-allowed/
查找树中与根节点距离为 k 的节点:https://www.geeksforgeeks.org/print-nodes-at-k-distance-from-root/
查找二叉树中某个节点所有祖先节点:https://www.geeksforgeeks.org/print-ancestors-of-a-given-node-in-binary-tree/
7. 前缀树
计算树的高度:https://www.geeksforgeeks.org/write-a-c-program-to-find-the-maximum-depth-or-height-of-a-tree/
红黑树
BST
B树
AVL树
B+树
图结构
实现广度优先搜索: https://www.geeksforgeeks.org/breadth-first-search-or-bfs-for-a-graph/
实现深度优先搜索: https://www.geeksforgeeks.org/depth-first-search-or-dfs-for-a-graph/
检查图是否为树: https://www.geeksforgeeks.org/check-given-graph-tree/
统计图中边的个数:https://www.geeksforgeeks.org/count-number-edges-undirected-graph/
使用 Dijkstra 算法查找两个节点之间的最短距离: https://www.geeksforgeeks.org/dijkstras-shortest-path-algorithm-greedy-algo-7/
其他
重点问题(来自儒猿技术交流群)
Java
ConcurrentHashMap源码分析
Volatile硬件底层原理
CPU负载过高,如何优化JVM参数
线上是否遇到OOM情况,如何去排查
线程池的工作原理和线程池的应用场景
JVM内存管理、类加载、GC
mysql
mysql buffer pool的机构组成
mysql通过什么机制来保证事务数据不丢失
mysql的索引结构,为什么不适用B树而使用B+树,三层索引大概能支持
ql如何优化,覆盖索引,回表查询,索引下推等概念
mysql的锁有哪些,如何分析死锁问题,死锁如何解决
现在有一个场景需要保证数据库和redis的双写一致性,你会如何去做
redis
redis数据结构类型有哪些,平时在哪些场景下用过
redis过期淘汰策略,内存淘汰策略,持久化方式
redis主从集群+哨兵,redisCluster工作原理,一致性hash的好处
redis的master和slave的集群可能出现主从之间网络实例抖动,导致重连失败一直需要进行全量复制你觉得是什么原因?
缓存雪崩,缓存穿透,缓存击穿如何解决,平时生产
fork耗时导致高并发请求下延时(最好控制在10g中fork子进程需要拷贝父进程的空间内页表
避免复制风暴 别一个master挂多个slave导致都全量复制的时候,占用带宽
匹配key keys指令导致的高cpu问题(禁止) 如果必须通过scan扩容不会重复(高位累加)&缩容的时候会重复
危险命令 flushall:清空所有数据。 flushdb:清空本库数据。 请确认在没有请求的情况下再执行以上操作。(深夜更新时)另外,redis 提供了异步清空命令 flushall async 和flushdb async 异步清空数据,不会阻塞当前线程
分布式锁和微服务
分布式锁使用过哪些,使用场景
能说一下springcloud netfix中feign和hystrix工作原理
分布式事务有使用过吗?在哪些场景下?说一下byteTcc的方案?seata有了解过?saga的原理有了解过吗?
网络
说一下http和https的请求流程
bio和nio说一下,netty对比原生的nio好在哪里
设计类
统计一个ip访问次数 在不加锁的情况下ConcurrentHashMap+putIfAbsent+Atomic
自研的网关的功能 这里说的是基础架构自研的,自己了解一点基于netty实现,参考zuul的前,中,后拦截器思路实现,自定义组件:appId认证,加密解密,加签验签,监控等
设计模型的了解和应用场景 type=1调用类a执行,type=2类b执行,type=3类c执行通过策略模式来进行优化 工作场景说了下库存更新:模板方法+命令模式+工厂模式,权限删除访问者模式,在介绍了一些开源中间件的源码中设计模式
让你设计一个秒杀系统你怎么设计
如何设计一个消息中间件,消息中间件事务如何去设计,平时使用消息中间件需要考虑什么问题
算法
十二: 第十部分算法: 1.算法一般在一面和二面可能有 2.我平时没怎么刷属于放弃,个人认为时间成本和回报不成正比,算法其实也挺邪门的,我整个三四十次面试下来就出算法题5-6个,但是也有同学面试出很多算法 3.如果不喜欢刷算法的同学,建议算一种类型比如链表,同时面试的时候适当卖惨,比如说自己只会链表类型算法,其次算法很菜,大部分面试官听到后会直接会给你出链表类型题目 4.如果算法没做出来还让你继续面的话一定要通过技术来征服面试官,最后可以问一下面试官对算法的态度 5.如果要刷算法建议每天控制在1个小时左右把自己会的题刷个5-10遍
其他
十一: 第九部分开放题: 0.每次换工作的原因 1.你想换一个什么样岗位 2.工作什么样的事情给你带来成就感 3.现在有两个工作你怎么选择,工作A做的事情是高并发的场景他的业务上可能没有起色在走下坡路;另外一个场景并发量没有那么高技术上有数据量分库分表,分布式事务等场景,业务上给公司带来的价值更高 4.平时工作中技术和业务怎么权衡 5.一个需求需求需要做你们平时开发流程是什么样 5.一个需求需求需要做你们平时开发流程是什么样 6.你目前薪资多少,期望薪资多少,多久能到岗 7.你有什么要问我的吗 8.对我们团队的工作有了解吗
十三: 第十一部分项目: 技术上的问题大概就是上面列举出来的, 一面聊基础会问的很深和很细,二面和三面主要看你项目 我本次面试的重点说的项目是自研分布式文件系统(石杉老师架构班项目),订单服务,消息推送服务开发等 要想进比如京东 百度 美团 阿里这样的大厂,技术深度和广度都需要很扎实,同时项目需要有含金量跟面试官有的聊,平时自己准备时:
自研或二次开发中间件项目: 1.自研的东西对比其他开源的内容有什么好处(可以从技术架构上,公司数据存储上,业务需求等方面考虑) 2.自研的带来的收益有哪些(最后表现出来的结果) 3.做这件事技术上最重要的点是哪些(用到的技术会被深问) 4.哪个地方做的比较完美,哪些地方后续还需要扩展(明白自己系统的优势和劣势)
业务项目 1.项目整理技术架构 2.你负责的项目的核心业务流程,需要有亮点比如熔断限流降级,分布式事务,分布式锁,分库分表,消息中间件异步化等 共性的内容: 1.项目每天的访问量 2.日活跃用户 3.平时和高峰期的qps 4.日交易量 5.服务部署多少台每台机器的配置 6.生产出现过的问题,如何去排查问题的
小结: 我本次面试独角兽+大厂大概面了十几家左右,面试一般是4轮,前3轮技术面试,第四轮hrbp简单聊工作履历和离职原因 新的岗位是否了解等,最后聊一下谈薪资和股票事情,聊薪资注意一下:不要把自己所有offer都告诉hr,这样hr会按照总包的价格来压低你的基础工资,在就是不管去不去先拿offer再说不要不好意思,这样你之后面试才有底气谈工资
学习方法 1. 第一遍主要是看资料学习(比如老师讲源码看源码分析的笔记,比如老师写代码过一遍老师的代码在脑海中把流程给弄明白),看的过程可以记录一下重点的知识点(不建议跟着敲效率为0) 2. 第二遍自己写代码(卡主了在回头看一遍老师代码继续撸)或者写demo分析源码画流程图 3. 第三遍总结自己笔记(不建议用老师的笔记不是说老师笔记不好作为参考) 4. 第四遍第二天早上可以默写出昨天学习知识点核心的流程 5. 第五遍反复实战项目应用(比如比较重要项目建议敲两遍以上,重要技术点视频学两遍以上) 6. 第六遍跟看过的人交流或没看过的人,让你自己描述的东西能让别人听懂并能回答他问的问题
HR面试
你感觉大学期间最有成就感的事情是什么?
你有遇到过什么特别大的挫折吗?
你父母对你有什么期望吗?
你未来的职业规划是如何?
你喜欢什么样的工作?
加班压力能承受吗?
为什么报了这个部门?
你觉得自己有什么优势,能给这个部门带来什么?
讲自己的三个优点?
说一下自己的不足?
**项目介绍一下是做什么的?讲一讲学到什么东西?
目前有offer吗?
业余活动?日常时间安排?
平时的学习方式
最近关注的开源项目
主要讲讲做了什么和擅长什么
你怎么评价你之前的3轮面试
你怎么看待你自己,你最大的核心竞争力是什么
对阿里技术氛围有什么样的理解,用过哪些阿里的开源库
期望的薪资是多少
面试技巧
项目经验,解决的问题,优化的 方向设计成自己精通的知识点,逐层深入,引导面试官深入提问
待实践问题
图像存数据库
SpringMVC 实现
事务注解作用域
JVM内容完整学习
Set、List、Map自实现
突击面试视频和博客
中华石衫
JAVA面试突击第一季
体验一下面试官对于分布式搜索引擎的4个连环炮
分布式缓存
先平易近人的随口问你一句分布式缓存的第一个问题
消息队列
完了!生产事故!几百万消息在消息队列里积压了几个小时!(16分钟)
09_总结一下消息队列相关问题的面试技巧(3分钟)
如果让你来开发一个消息队列中间件,你会怎么设计架构?(4分钟)
我该怎么保证从消息队列里拿到的数据按顺序执行?(29分钟)
我为什么在消息队列里消费到了重复的数据?(23分钟)
我发到消息队列里面的数据怎么不见了?(43分钟)
引入消息队列之后该如何保证其高可用性?(39分钟)
知其然而知其所以然:如何进行消息队列的技术选型?(100分钟)
体验一下面试官对于消息队列的7个连环炮视频(13分钟)
图灵学院
(150集)一套搞定面试!B站最系统的Java面试基础到实战全套视频-超详细讲解-跳槽面试必看的保姆级Java课程(35个小时)
图灵_诸葛老师
2020年大厂面试100%必问的Java面试题详解教程(找Java工作必备)(39小时)
京东Java架构师
这可能是B站最好的Java面试刷题教程了(14小时)
安全
CSRF
什么是 CSRF 攻击?
CSRF 代表跨站请求伪造。这是一种攻击,迫使最终用户在当前通过身份验证的Web 应用程序上执行不需要的操作。CSRF 攻击专门针对状态改变请求,而不是数据窃取,因为攻击者无法查看对伪造请求的响应。
待学习项
基础必须精通
jvm
Java并发编程
JUC
hashmap源码
volatile原理
Java8新特性
设计模式
Spring源码
Tomcat源码
Spring MVC源码
Spring Boot源码
Netty源码
Rocket源码
Redis
Mysql
Nginx源码
Zookeeper源码
分布式锁
缓存和数据库一致性问题
基本算法
熟练
设计秒杀系统
学习mall架构
实操
tomcat的bio、Nio、APR运行模式下的效率比较