大量面试题
面向对象的特征:继承、封装和多态
继承:增加程序的重用性。比如一个学生类继承人类,人类的许多属性就不需要再次定义。
封装:将对象属性封装起来,只暴露需要的接口给其他类使用。对类进行修改时,不改变接口声明,保证了模块之间的相对独立性。
多态:实现多态的两个方法是覆盖和重载。许多设计模式都是利用了多态这一特性。
final, finally, finalize 的区别
final:用final修饰类表示该类不能继承,修饰方法表示方法不可覆盖,修饰属性表示属性不能被第二次赋值。
finally:在异常处理时被finally包裹的操作是否捕获异常都一定会执行。
finalize:finalize是一个方法名,在gc时可能会被调用,其调用条件为类重写了这个方法且未被调用过。
Exception、Error、运行时异常与一般异常有何异同
Exception和Error都继承于Throwable: Error一般与程序无关,而是java程序运行时系统的内部错误;Exception分为RuntimeException运行时异常和普通异常:运行时异常一般是程序编写错误,而普通异常与程序无关,而是其他问题例如IO导致的异常,需要用try-catch包裹。
请写出5种常见到的runtime exception
常见的运行时异常:
ClassNotFoundException
NullPointerException
NumberFormatException
IndexOutOfBoundsException
ClassCastException
int 和 Integer 有什么区别,Integer的值缓存范围
int是基本数据类型,采用值传递方式,Integer是int的包装类型,采用引用传递方式。缓存范围为 -128~127.
比较两个Integer大小时:
-
如果都是new出来的变量,则两个变量==比较结果一定为false,因为==比较的是两个变量的地址值;
-
如果一个是new出来的变量,一个是直接赋值,则两个变量==比较一定为false;
-
一个Integer和一个int比较,只要数值相等,==结果为true,因为此时Integer对象为被自动拆箱为int。
-
两个直接赋值的Integer比较,若值相等且在缓存范围内,==结果为true。
包装类,装箱和拆箱
包装类是对八大基本数据类型(byte,short,boolean,char,int,long,float,double)的包装。
装箱是指将基本类型转换为包装类型;拆箱是指将包装类型转换为基本类型。
以下几种情况会发生自动装箱或自动拆箱:
-
Integer integer = 100;发生自动装箱,即Integer integer = Integer.valueOf(100);
-
int i = integer;发生自动拆箱
-
遇到算数运算符,如:++,--,>>等,对包装类自动拆箱
-
遇到除==,!=的关系运算符,如:>,<=等,对包装类自动拆箱
-
遇到==,!=关系运算符,遇到数字常量或算数表达式时,对包装类型自动拆箱。
String、StringBuilder、StringBuffer
String:不可变类,适用于对字符串改变不频繁的场景。
StringBuffer:线程安全,适用于对字符串改变频繁的多线程场景。
StringBuilder:线程不安全,适用于对字符串改变频繁的单线程场景。
重载和重写的区别
-
重载发生在同一个类当中,重写发生在子类和父类当中
-
重载必须保证方法参数不同,而重写的方法参数相同。
抽象类和接口有什么区别
-
一个类只能继承一个抽象类而可以实现多个接口。
-
抽象类中的方法可以是抽象方法也可以是被实现的普通方法,而接口中的方法不能被实现。
-
一个类继承抽象类表示is-a关系,而实现一个接口表示is-like-a关系,所以关注一个类的本质时选择继承抽象类,而关注方法时去实现接口。
-
接口中的属性默认为static类型,而抽象类中不是。
说说反射的用途及实现
反射类在java.lang.reflect包里面,主要有三个用途:
- 通过反射创建对象
- 通过反射获得类中的所有属性
- 通过反射调用类中的方法。
可以通过反射实现动态代理。
其本质就是通过获取JVM内(一般在方法区)的信息获得对象信息。
HTTP请求的GET与POST方式的区别
Get:直接把请求参数凡在URL中,用户可见,有注入危险,有长度限制。
Post:将请求参数放在请求体中。
Session与Cookie区别
Session在服务端,安全,但增加服务端压力。
Cookie在客户端,不安全。
MVC设计思想
分层思想,减小程序间的耦合度。
M:model,一般是指业务逻辑层。
V:view(视图),一般是指jsp部分,用于对响应结果的渲染,然后展示给用户。
C:controller(控制),一般指action控制层,用于接收客户端请求并分析请求参数然后交给业务逻辑层处理。
equals与==的区别
==直接比较两个对象的地址值;equals是java方法,重写后一般用来比较两个对象里面的具体值。
hashCode和equals方法的区别与联系
hashCode求对象的哈希值,equals方法一般用于比较两个对象值是否相等:HashCode相等的对象equals()结果不一定为true,反之equals()结果为true的两个对象hashCode值一定相等。
在HashMap等容器中,进行put操作时,会求对象key的hashcode值,hashCode一样的对象放在table的同一个索引中,进一步执行equals方法,若所得结果为true,则会用新的value覆盖原来的value值。
什么是Java序列化和反序列化,如何实现Java序列化?或者请解释Serializable 接口的作用
Java的序列化就是将java对象按照一定规则转换为二进制字节数组,反序列化就是将二进制字节数组按照规则重新转换为java对象。
Serializable接口是一个标识接口,内部没有任何方法。实现了Serializable接口的对象可以被序列化,从而在网络中得以传输。
Object类中常见的方法,为什么wait notify会放在Object里边?
常见方法:wait,notify,equals,toString,hashCode
因为wait和notify方法由锁对象调用,而任何对象都可以作为锁对象,所以wait,notify会放在Object里边
Java的平台无关性如何体现出来的
其他语言如c语言之所以没有平台无关性是因为c语言会被编译为机器语言指令供硬件执行,而不同操作系统又有着不同的指令。而java虚拟机则实现了硬件到操作系统的缓冲。Java实现了一处编译,到处运行。其编译出的字节码文件可以在不同操作系统的jvm上运行。
JDK和JRE的区别
JRE是java程序的运行环境,包括JVM和java类库的class文件,面向java程序的使用者
JDK 是java程序的开发工具,包括了JRE,面向Java程序的开发者。
Java 8有哪些新特性
https://blog.csdn.net/u014470581/article/details/54944384
https://blog.csdn.net/54powerman/article/details/73188951
Set和hashCode以及equals方法的联系
HashSet底层是通过hashmap来实现的,调用add方法时实际上就是调用了hashmap的put方法。Hashmap调用put方法时会先计算key元素的哈希值来确定其索引位置,然后通过equals将key元素和该索引位置的每个节点的key元素依次比较,若结果都为false,则插入尾部,否则覆盖。
HashMap 的工作原理及代码实现,什么时候用到红黑树(面了5家,被问4次)
Hashmap底层原来使用数组加链表的方式实现,Jdk1.8开始,hashmap底层改为数组加链表或者红黑树,当某一索引位置的节点个数大于8时,就会采用红黑树。
ConcurrentHashMap 的工作原理及代码实现,如何统计所有的元素个数(ConcurrentHashMap也是高频题)
ConcurrentHashMap中有一个modCount变量,用于记录对segment中元素个数造成影响的操作个数。统计所有元素个数,就是对整个容器遍历两次,看两次modCount变量值是否相等,如果相等,统计出来的就是正确的元素个数,否则需要对整个容器进行锁定,通过同步锁定再一次统计元素个数。
Arraylist与LinkedList默认空间是多少;
ArrayList默认空间为10,扩容规则 newCapacity = oldCapacity+ oldCapacity/2+1
LinkedList底层实现为链表,无初始大小,无扩容机制。
Arraylist与LinkedList区别与各自的优势List 和 Map 区别;
ArrayList:底层由数组实现,适合用于经常查找元素的场合,查找复杂度为O(n)
LinkedList:底层由链表实现,适合用于经常增删元素的场合,增删复杂度为O(n)
Map里面的元素是Key-Value的形式存在的,key不允许重复
谈谈HashMap,哈希表解决hash冲突的方法;
HashMap:jdk1.7使用数组+链表实现;jdk1.8改良为数组+红黑树。
哈希表解决hash冲突:
- 开放地址法
- 链表法
- 二次哈希法
Java Collections和Arrays的sort方法默认的排序方法是什么;
https://blog.csdn.net/yangzhongblog/article/details/8184707
JDK1.7以前采用归并排序,JDK1.7及以后使用TimeSort
引用计数法与GC Root可达性分析法区别;
引用计数法:计算对象被引用的次数,次数等于0时回收,当出现相互引用时,会出现无法回收导致内存泄漏问题。
GC Root可达性分析:分析对象到GC Root是否有一条可达的路径,如果没有则对他进行回收
可作为GC Root的对象有:
- 虚拟机栈中引用的对象
- 方法区中的类静态属性引用的对象
- 方法区中常量引用的对象
- Native方法中引用的对象
浅拷贝和深拷贝的区别;
浅拷贝:拷贝对象的引用
深拷贝:拷贝对象的值,深拷贝之后的两个对象无引用关联
如何实现深拷贝:实现Cloneable接口,重写Object的clone()方法。重写clone方法时,如果类中成员变量都是基本类型,则调用父类的clone()方法返回即可,如果存在引用类型则需要对该引用类型成员变量调用其clone()方法。
String s="abc"和String s=new String("abc")区别;
String s = “abc”:在栈中开辟一个空间存放引用s,在常量池中寻找“abc”常量,如果有,s就直接指向这个常量,如果没有,则在常量池中创建一个,然后s指向这个常量。
String s=new String("abc"):在栈中开辟一个空间存放引用s,在堆中开辟一个新的空间,存放一个新建的对象“abc”,s指向这个堆中的对象。第一次执行这句话,虚拟机中新创建了两个“abc”一个在堆中,一个在方法区中。
toString()方法什么情况下需要重写;
object的toString()方法是返回了类名+@+对象16进制的哈希值,如果对象需要输出自己定制的格式就需要重写toString()方法。
判断对象相等时,什么情况下只需要重写 equals(),什么情况下需要重写 equals(),hashcode()?
放在HashMap、HashSet等容器中的对象需要重写hashcode方法,因为他们执行put方法时需要先根据对象的hashcode值决定对象要放入的数组位置,然后才执行equals方法判断其是否相等。
Set内存放的元素为什么不可以重复,内部是如何保证和实现的?
如何实现:set内部依靠map来实现,对set的操作实际上是对map的key进行操作,唯一性通过Hashcode+equals实现。
如何保证分布式缓存的一致性(分布式缓存一致性hash算法)?分布式session实现?
一致性hash算法:构造一个长度为232的整数环,将机器节点分布在圆环上,处于圆环上的数据被顺时针最近的机器管理。这样增删机器节点不会造成过多的数据无法命中问题。
分布式session实现:使用redis模拟session信息。Redis随机生成session_id作为cookie信息返回,之后用户携带的cookie信息就记录了用户的状态。
说一下TreeMap的实现原理?
TreeMap的实现就是红黑树的实现,放入TreeMap的类需要继承Comparable类,TreeMap通过Comparable的compareTo方法的返回值来确定对象的位置,如果该方法返回了0,则判定新对象和原来的对象相等,会用新的value覆盖原来的value。
红黑树的性质?红黑树遍历方式有哪些?如果key冲突如何解决?setColor()方法在什么时候用?什么时候会进行旋转和颜色转换?
性质:
- 节点为黑色或者红色
- 根节点为黑色
- 红色节点的子节点不能为红色
- 叶子节点(Nil节点)为黑色
- 从根节点到叶子节点路径上的黑色节点一样多
Key冲突:用新的value覆盖旧的value
setColor方法:要将刚插入树的节点设置为红色
如果插入节点的父节点为红色,则将插入节点作为当前节点并分情况进行下面的操作:
- 如果当前节点的叔节点为红色,则将父节点和叔节点同时置为黑色,祖父节点涂为红色,且将当前节点的祖父节点作为当前节点,判断条件进入步骤一或二或三。
- 如果当前节点的叔节点为黑色且当前节点为右子节点,以当前节点的父节点作为当前节点并以当前节点作为支点进行左旋,判断条件进入步骤一或二或三。
- 如果当前节点的叔节点为黑色且当前节点为左子节点,将其父节点设为黑色,祖父节点设为红色,以祖父节点为支点进行右旋,判断条件进入步骤一或二或三。
反射的作用与实现原理;
通过Class对象获得class的信息。在运行状态中,对于任何一个类,都能知道这个类的所有属性和方法;对于任何一个对象,都能调用这个对象的所有属性和方法。
作用:动态获取类的属性和方法,动态调用对象的属性和方法
实现原理:类加载时虚拟机读入class的二进制字节流,将其解析为Class对象,存入方法区。程序运行时,只要获得这个Class对象,就能知道这个类的所有属性和方法。
Java中的回调机制;
http://www.php.cn/java-article-355640.html
一个类A调用另一个类B的方法,给被调用类的方法中传入一个A对象,在被调用方法中完成自己的处理逻辑后调用传入A对象的方法,这就完成了一次回调。
设计模式原则
https://blog.csdn.net/zhengzhb/article/details/7296944
JMM里边的原子性、可见性、有序性是如何体现出来的,JMM中内存屏障是什么意思。
原子性:原子操作要么都成功要么都失败,JMM里面通过锁来保证原子性
可见性:线程对于一个变量的修改能被其他线程看到,JMM的volatile变量具有可见性——一个volatile变量的读总能看到其他线程对这个volatile变量最后的写。
有序性:happens-before规则来保证
内存屏障:用来阻止语句重排序。
线程和进程的概念、并行和并发的概念
进程是资源分配的最小单位,它拥有独立的地址空间,进程之间相互独立。
线程是cpu调度的最小单位,它与同一个进程内的其他线程共享一个地址空间。
并发是指两件及以上的事件在同一时间间隔内同时发生,但在同一时刻只有一件事在发生;而并发是指同一时刻有两件及以上的事情在发生。
创建线程的方式及实现
继承Thread、实现Runable
ArrayList和LinkList的删除一个元素的时间复杂度;
ArrayList是O(N),LinkList是O(1)
CopyOnWriteArrayList是什么;
写时复制的容器:在往容器中添加元素时,将容器复制一份出来再进行添加,这样不会影响其他线程的读过程,做到了读写分离。适用于读多写少的场景。
序列化和反序列化底层如何实现的
ObjectOutputStream 、ObjectInputStream、 readObject 、writeObject
调用ObjectOutputStream 的wirteObject(Object obj)方法时,只有实现了序列化接口的obj对象,被写入文件,否则会报错,写入文件时会将serialVersionUID写入文件,作为反序列化时判断是否是同一个类的标准。
反序列化时,调用ObjectInputStream的readObject ()方法,反序列化时系统会去检测文件中的serialVersionUID,判断它是否与当前类的serialVersionUID一致,如果一致就说明序列化类的版本与当前类版本是一样的,可以反序列化成功,否则失败。
如何调试多线程的程序;
多线程调试的时候,只有断点所在行的线程会处于suspend状态,其他线程都会处于run的状态,通过给其他线程设置睡眠时间模拟调试状态。
一个线程连着调用start两次会出现什么情况?
由于状态只有就绪、阻塞、执行,状态是无法由执行转化为执行的,所以会报不合法的状态!IllegalThreadStateException
wait方法能不能被重写?能不能被中断;
wait是final类型的,不可以被重写,不仅如此,notify和notifyall都是final类型的。
可以被中断,调用线程的interrupt方法即可。
重入锁的概念,重入锁为什么可以防止死锁
重入锁:被一个线程持有的锁可再次被这个线程获得。
考虑一个场景,子类的同步方法调用父类的同步方法:两个方法的锁对象都是子类的对象(this),这样调用的时候由于可重入就不会发生死锁。但不能防止所有的死锁,比如A线程持有锁A请求锁B,B线程持有锁B请求锁A。
volatile 实现原理
禁止指令重排:禁止volatile写与前面的任何读写重排序;禁止volatile读与后面的任何读写重排序;静止volatile写与后面的volatile读重排序。
刷新内存:在某个线程写一个volatile变量时,JMM会把该线程的所有变量刷新到主内存;在某个线程读一个volatile变量时JMM会吧该线程的所有变量置为无效,必须从主内存读取。
synchronized 实现原理
Synchronized包括同步方法和同步代码块,成员同步方法的锁对象是this即当前对象本身,静态同步方法的锁是类。
每个锁对象都有一个对象监视器,进入临界区之前,监视器会判断当前锁是否已经被其他线程占有,若当前锁对象没有被其他线程占有(位被占有或者被当前线程占有),则进入临界区,并将锁对象的状态值加1,否则,当前线程转化为同步阻塞状态,等待其他线程释放该锁对象;退出临界区之前,监视器会将当前锁对象状态值减1,并判断其是否为0,若为0,则释放当前锁并通知同步等待池。
synchronized 与 lock 的区别
Synchronized是java关键字,lock是一个类。
Lock可以实现类有许多获得锁信息的方法,获得锁的时候可设置公平/非公平锁,可中断等。
最重要的一点区别是,synchronized是悲观锁,而lock是乐观锁。
AQS同步队列
是用来构建锁或者其他同步组件的基础框架。内部使用一个int来记录队列的状态值,并通过修改和获取这个状态值来表明获得/释放锁是否成功。内部还维护着一个队列,用来存放处于同步等待的线程。
CAS无锁的概念、乐观锁和悲观锁,乐观锁的业务场景及实现方式
乐观锁和悲观锁是两种概念上的锁:
乐观锁:每次取一个数据进行操作之前假设过程中不会有其他线程修改 这个数据,而是等操作完了之后才将原值和期望原值进行对比。CAS是一个实现乐观锁的方式。适用于竞争不高的多线程环境。
悲观锁:每次取一个数据进行操作之前都假设过程中会有其他线程修改 这个数据,所以要在操作之前锁定这个数据,不让其他线程访问。 Synchronized是实现悲观锁的。适用于竞争激烈的多线程环境。
常见的原子操作类
AtomicInteger、AtomicLong、AtomicBoolean
AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray、AtomicReference、AtomicIntegerFieldUpdater
什么是ABA问题,出现ABA问题JDK是如何解决的
ABA问题是CAS操作的一个问题,CAS操作是通过比较特定内存位置的值和预期原值相比较,如果相等则说明操作过程中该内存位置的值没有被其他线程修改,就将该内存位置的值更新为计算所得新值。否则,循环这个操作。而ABA问题就是比如该内存位置原值为A,被别的线程修改了两次(A-B-A),比较发现满足“特定内存位置的值和预期原值相等”条件。但实际上该值已经被其他线程修改了。JDK通过增加版本号方式解决该问题,(A1-B2-A3,A1!=A3)
Java 8并法包下常见的并发类
并发包由3个包组成,分别是:
java.util.concurrent(前面讲的并发工具类、线程池、并发容器-concurrentHashMap、),java.util.concurrent.automic(原子操作类),java.util.concurrent.locks(各种锁)
偏向锁、轻量级锁、重量级锁、自旋锁的概念
偏向锁:从始至终最多只能被一个线程占有,偏向于第一个访问锁的线程。所对象的对象头锁标志为01.
轻量级锁:自旋锁和volatile锁都是一种轻量级锁,不阻塞线程。
重量级锁:synchronized锁
自旋锁:在竞争不激烈的多线程环境中,使用自旋锁自旋获得锁对象,可以减少线程挂起和恢复的时间。
可参考:《Java多线程编程核心技术》
JVM运行时内存区域划分
程序计数器:线程私有,记录当前栈帧执行到哪一条指令
Java虚拟机栈、native栈
方法区、堆
内存溢出OOM和堆栈溢出SOE的示例及原因、如何排查与解决
OOM:Out Of Memory
方法区:一般发生在方法区的永久代,由于使用了太多的第三方jar包, 导致加载太多类从而永久代内存溢出
堆:检查是否有内存泄露,如果没有检查堆内存是否可以调大以及检查 程序是否创建过多不合适的大对象。
栈:-Xss设置过大而总的栈内存有限导致无法创建更多线程,只发生在 多线程情况下。
SOE:StackOverflowError
栈:-Xss设置太小(栈内定义了太多变量,递归深度太深)
如何判断对象是否可以回收或存活
- 引用计数法
- 可达性分析(JVM使用):分析对象是否可达(是否在GC root引用链上),若可达则不做处理;若不可达且未被标记过则将其放入等待回收队列,稍后会有Finalizer线程检查其finalize方法是否被重写且未被调用过,若不是则直接回收,若是,则调用其finalize方法。
常见的GC回收算法及其含义
复制算法:在新生代使用,JVM将新生代分为eden+2个survivor,使用Eden和一个survivor,另一个survivor存放回收后依然存活的对象。
标记-清除算法:在老年代使用,将要回收的对象标记起来,然后统一清除。
标记-整理算法:在老年代使用,将要回收的对象标记起来,然后让仍然存活的对象往一个方向移动,最后将标记的对象回收。
常见的JVM性能监控和故障处理工具类:jps、jstat、jmap、jinfo、jconsole等
JVM性能调优
https://blog.csdn.net/snow_7/article/details/52291979
类加载器、双亲委派模型、一个类的生命周期、类是如何加载到JVM中的
双亲委派模型:当一个类收到加载类请求时,将这个请求委托给父类执行,直到委托给顶层父类,然后该类尝试执行请求,若加载类失败,则将该请求重新返还给子类,一直到原来收到加载请求的类调用findclass方法加载。
类加载的过程:加载、验证、准备、解析、初始化
加载:通过类加载器根据提供的类全限定名寻找指定的二进制字节流,将二进制字节流加载到JVM的方法区中,并转换为一个class对象实例。
链接:将二进制字节流合并到JVM的运行时状态
验证:验证数据信息是否符合规范
准备:将类中的静态对象初始化为默认值(static final 例外,在此阶段就会给其赋值为实际值)
解析:
初始化:按照父类静态变量,父类静态块,子类静态变量,子类静态块,父类成员变量,父类构造块,父类构造方法,子类成员变量,子类构造块,子类构造方法的顺序执行初始化。
强引用、软引用、弱引用、虚引用
强引用:在程序中普遍存在的类似于 A a = new A()这样的引用
软引用:如果虚拟机内存不足,就会被gc回收的引用
弱引用:无论虚拟机内存是否充足,只要发动了gc就会被回收
虚引用:相当于没有引用,只是为了在被回收时收到一个系统通知。
设计模式的的六大原则及其含义
单一职责原则:一个类只负责一个职责。
里氏替换原则:通过子类继承父类对一个程序增加功能时,子类拓展自己的方法而不要重写父类方法避免影响原来的功能。
比如:一个类A实现了加法方法method1,现在客户端需要添加减法功能,由类B继承A,并扩展减法方法method2,而不能重写method1,避免客户端调用B.method1()时影响加法功能。
依赖倒转原则:高层模块不应依赖低层模块,二者都应该依赖其抽象。
比如:类Teacher执行方法teach(Math)时需要依赖类Math,现在Teacher类要扩展方法teach(English)此时就要修改类Teacher,这是不合理的,应该将teach(Math)改为teach(Subject),再由Math和English实现接口Subject,这样Teacher拓展教授其他课程时,只要创建新类实现Subject接口即可而不需要修改Teacher类。
接口隔离原则:建立单一的接口而不要建立庞大臃肿的接口,尽量细化接口,接口中的方法应该尽量少(但不可过少,避免一个类需要实现过多接口)
比如:接口I内有方法method1,method2,method3,method4,method5。类A用到方法method1,method2,method5,类B用到方法method3,method4,method5,而两个类实现接口I时必须实现接口中的5个方法。应该将接口I拆分为接口I1(method1,method2)、I2(method3,method4),I3(method5),再使类A实现接口I1,I3,类B实现接口I2,I3.
迪米特原则:一个类只与自己的直接相关类通信,实现低耦合。
比如:当前有类A(总公司),类B(分公司),类C(总公司员工),类D(分公司员工)。类A需要获得该公司所有员工的信息,但不应该让A依赖D,而应该让A依赖B,B依赖D,这样分公司员工产生变化时不会影响总公司的类。
开闭原则:一个软件实体应该对拓展开放,对修改关闭。
常见的单例模式以及各种实现方式的优缺点,哪一种最好,手写常见的单例模式
https://blog.csdn.net/CCCCC_SSSSS/article/details/80978121
Spring中用到了哪些设计模式
代理模式:spring AOP中JDK动态代理就是利用代理模式技术实现的。
代理对象Proxy实现被代理对象Target类实现的接口I,并对接口I中的方法进行增强。
策略模式:根据不同的Context环境选择不同的代理方法(JDK/Cglib)
MyBatis中用到了哪些设计模式
工厂模式:Mybatis中的资源加载使用的就是工厂模式,根据传入的DataSource类型返回不同的资源工厂,如JDBCDataSourceFactory、HibernateDataSourceFactory。
克鲁斯卡尔算法、普林母算法、迪克拉斯算法
Kruskal算法(最小生成树):
将图中的所有边按照权重从小到大排序。
依次选择每条边,如果这条边的两个定点有其一不在最小生成树中,则将这条边加入最小生成树。
循环第二步,直到树中包含了图中所有的顶点为止。
Prime算法(最小生成树):
在图中选取任意节点为根节点。
寻找与树中相连且权重最小的边加入最小生成树,加入树的边不能形成环路,否则丢弃掉该边
循环第二步,直到树中包含了图中所有的顶点为止。
Dijkstra(最短路径):使用广度优先搜索,权重不能为负值
确定图中的起始点(V0)并将其标记,记录起点的路径长为0.其他所有节点的路径长为正无穷。
循环寻找与被标记相连的边,计算边的另一头顶点距离起点的路径长len=min(原来的len,标记点len+边权重),选择该次循环后距离起点最近的顶点对其进行标记。
直到所有的顶点都被标记
什么是一致性Hash及其原理、Hash环问题
https://blog.csdn.net/lihao21/article/details/54193868
常见的排序算法和查找算法:快排、折半查找、堆排序等
BIO、NIO、AIO的概念
BIO:阻塞IO。线程发起IO请求,阻塞线程,等待IO数据准备完成,线程被唤醒。
NIO:非阻塞IO。线程发起IO请求,若IO数据已准备完成则直接返回,否则通过轮询的方式等待IO数据准备完成再返回。
AIO:异步IO。线程发起IO请求,不管IO数据准备如何直接返回。
什么是长连接和短连接
长连接:服务端和客户端建立连接后完成一次请求保持连接,下次发起请求无需重新建立连接。
短连接:服务端和客户端建立连接后完成一次请求连接断开。
Http1.0与Http1.1的区别:
http1.0默认短连接,如需长连接要手动发送keep-alive参数,二http1.1默认长连接;
http1.1先只发送header信息,通过权限验证后才发送body信息,如果无法通过权限验证(401)则不发生body,节约了带宽;
http1.0无host域,无法设置虚拟站点共享一个ip。
Http2.0与Http1.1的区别:
http2.0支持多路复用,做到同一个连接并发处理多个请求
http2.0支持header数据压缩
http2.0支持推送。
三次握手和四次挥手、为什么挥手需要四次
第一次挥手:客户端发送停止请求
第二次挥手:服务端发送确认停止响应
第三次挥手:服务端处理完客户端请求停止前发送的请求,告诉客户端可以断开连接了。
第四次挥手:客户端收到服务端的断开连接请求,发送确认断开,在2msl(最长报文段寿命)后断开连接。而服务端在收到低四次挥手报文时立即断开连接。
如果没有第四次挥手,服务端即在第三次挥手请求发出时立即断开连接,若此次请求在传输过程丢失,客户端将永远无法收到断开请求保持连接。
从游览器中输入URL到页面加载的发生了什么?
AtomicInteger底层实现原理;
提供的常用方法:
- int addAndGet(int delta):以原子的方式将输入的数值与实例中的值相加,并放回结果。
- Boolean compareAndSet(int except, int update):如果输入的值等于预期值,则以原子方式将改之设置为输入的值。
- Int getAndIncrement():以原子方式将当前值加1,返回自增前的值。
底层都是通过是Unsafe包的native方法完成的。
synchronized与ReentraLock哪个是公平锁;
ReentraLock可设置为公平锁,synchronized是非公平锁。
CAS机制会出现什么问题;
https://blog.csdn.net/liangwenmail/article/details/80832580
- ABA问题
- 循环时间开销大
- 只能保证一个共享变量的原子操作
用过并发包下边的哪些类;
- CountDownLatch
- CyclicBarrier
一个线程连着调用start两次会出现什么情况?
线程无法从可运行状态转换为可运行状态,因此会报错
wait方法能不能被重写,wait能不能被中断;
wait是final方法,不能被重写;可以被中断
线程池的实现?四种线程池?重要参数及原理?任务拒接策略有哪几种?
线程池的好处:线程的创建和关闭都十分消耗资源,使用线程池可降低资源消耗;任务来临时,若池中存在空闲线程,可减小响应时间;提高线程的可管理性。
线程池的实现:
-
ThreadPoolExecutor
-
-
(corePoolSize,
-
-
maximumPoolSize,
-
-
keepAliveTime,
-
-
unit,
-
-
workQueue);
创建线程池以后:提交任务,若线程池内线程数量小于corePoolSize,则创建新的线程执行任务;若线程池内数量大于corePoolSize,且队列未满,则将任务放入队列;若队列已满且线程池内数量小于maximumPoolSize,则创建新的线程执行任务,否则按照拒绝策略处理该任务。
线程池中存在空闲(没有任务执行)的线程时,若空闲时间已达到keepAliveTime,且池内线程大于corePoolSize,则关闭多余的线程。
拒绝策略:
- 丢弃任务,抛出异常
- 拒绝执行,不抛异常
- 丢弃缓存队列中最老的任务,并且重新尝试提交新任务
- 重试添加当前任务,直到成功
四种线程池:
- FixedThreadPool:核心线程数=最大线程数;无界队列
- SingleThreadPool:核心线程数=最大线程数=1;无界队列
- CacheThreadPool:核心线程数=0;最大线程数=max;无容量队列
- ScheduleThreadPool:核心线程数=n;最大线程数=max;延时队列
线程状态以及API怎么操作会发生这种转换;
- 新建
- 就绪:新建线程调用start()或者运行线程时间片用完
- 阻塞:
- 普通阻塞:join、sleep、
- 等待阻塞:wait
- 同步阻塞:遇到同步块
- 运行:就绪状态的线程获得CPU时间片
- 死亡:run方法结束或异常退出
常用的避免死锁方法;
死锁产生的四个必要条件是:
- 互斥条件:对所分配的资源进行排他性控制,即在一段时间内某资源仅为一进程使用。
- 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
- 环路等待条件:发生死锁时,必然存在一个进程——资源的环形链。
避免死锁:
- 避免一个线程同时获取多个锁
- 尝试使用定时锁或者非阻塞的获取锁
Minor GC与Full GC分别在什么时候发生?
minorGC发生在新生代,当新生代剩余空间不足时,会触发minorGC,回收eden和一个survivor内的对象,回收以后仍然存活的对象会放入新生代的另一个survivor中。
FullGC发生在整个堆中,老年代剩余连续空间不足会发生FullGC,剩余空间不足有两种判别标准:
1. 若允许空间担保分配:当老年代剩余的连续空间大于历次晋升的平均大小时,不会发生FullGC,而是发生minorGC,此时若minorGC后若晋升的对象大小大于老年代剩余的连续空间,则再触发fullGC。
2. 若不允许空间担保分配:当老年代剩余的连续空间大小小于新生代中的对象总大小时,便会触发fullGC。
GC收集器有哪些?CMS收集器与G1收集器的特点。
新生代有三种GC收集器,采用复制算法:
- Serial new:一种单线程的新生代垃圾收集器,工作时会暂停其他所有线程。
- Par new:serial new的多线程版本,工作时也会暂停其他所有线程。
- Parallel scavenge:以吞吐量为目标的多线程新生代垃圾收集器,适用于不与用户交互的后台垃圾收集。
老年代也有三种GC收集器,采用标记/清除算法或者标记/整理算法:
- Serial old:serial new的老年版本——标记/整理
- Par old:Parallel scavenge的老年版本——标记/整理
- CMS:
为什么JVM调优经常会将-Xms和-Xmx参数设置成一样;
减轻伸缩堆带来的压力。因为剩余空间占到当前总空间的70%及以上时,会收缩堆大小,直到-Xms;剩余空间不足当前总空间的40%时,会扩展堆大小,直到-Xmx
Java内存模型,方法区存什么;
类的版本,字段,方法,接口和常量池,常量池中存着常量和符号引用
CMS垃圾回收过程;
CMS是一种并发的垃圾回收器,使用标记-清除算法,用在老年代,一般与parNew结合使用。回收过程分为4步:
- 初次标记
- 并发标记
- 重新标记
- 并发回收
其中,初次标记和重新标记需要stop the world,但这两个过程需要的时间较短,而并发标记和并发回收是和工作线程并发执行的,所需时间较长。
Full GC次数太多了,如何优化;
引发Full GC的原因主要有以下几种:
- 老年代剩余连续空间大小小于新生代历次minor GC后回收晋升到老年代的平均大小(无法空间担保)
- 空间担保失败,引发Full GC
- 永久代满
优化检查:
- 查看自己写的程序是否大对象频繁回收
- 调大堆大小:-Xmx和-Xms
- 适当增加新生代比例:XX:NewRatio=n
直接内存如何管理的;
直接内存不属于虚拟机运行时数据区的一部分,其大小自然不受java虚拟机堆限制,但会受到本机总内存大小限制。其大小可以通过-XX:MaxDirectMemorySize来设置,否则默认大小和堆大小一样大。回收只有等到虚拟机触发Full GC时才顺便对直接内存进行回收,或手动System.gc(),否则会报内存溢出异常。
Java线程池使用无界任务队列和有界任务队列的优劣对比;
使用无界队列,则maxPoolSize参数无效,除非资源耗尽,否则任务可以无止尽的加入到无界队列中,如果任务执行速度慢于队列添加任务的时间,那么有可能耗尽系统资源。
使用有界队列,当有界队列放满,线程池内线程数又大于maxPoolSize时,需要选择拒绝策略来执行。
CountDownLatch和CyclicBarrier的区别;
两者都是JDK封装的并发工具类,均使用计数的方法阻塞线程。CountDownLatch的计数器不可重置,而CyclicBarrier可以,故CyclicBarrier适用于更复杂的业务场景。
Java中有哪些同步方案
重量级锁synchronized,显式锁reentrantlock,并发容器concurrentHashMap、concurrentLinkedQueue, CAS,轻量级锁Volatile,AQS
如果你的项目出现了内存泄露,怎么监控这个问题呢;
使用MAT工具定位内存泄漏出现的位置,然后优化程序
https://blog.csdn.net/lc0817/article/details/67014499
标记清除和标记整理的区别和优缺点,为何标记整理会发生stop the world;
标记清除是在回收时先标记需要回收的对象,标记完成后对这些被标记的对象进行统一回收,容易出现空间碎片较多的情况;而标记整理比标记清除多了一步,即在标记完成后,将未被标记的对象移到一边,然后将边界后的所有被标记对象回收,这样能避免出现空间碎片,但是需要stop the world。因为如果与工作线程并发执行,工作线程中会不断产生新的对象,无法标记出回收边界。
线程池,如何根据CPU的核数来设计线程大小,如果是计算机密集型的呢,如果是IO密集型的呢?
CPU密集型:size = cpu核数 + 1
IO密集型:size = cpu核数 * 2
多个线程同时读写,读线程的数量远远大于写线程,你认为应该如何解决并发的问题?你会选择加什么样的锁?
方案一:读写锁reentrantWriteReadLock
方案二:WriteOnCopy,写时复制出一份,加锁修改,然后覆盖;读不加锁
线程池内的线程如果全部忙,提交一个新的任务,会发生什么?队列全部塞满了之后,还是忙,再提交会发生什么?
添加到任务队列中;如果线程池中线程数量小于maxPoolSize,则创建新线程,否则执行4种拒绝策略:
synchronized关键字锁住的是什么东西?在字节码中是怎么表示的?在内存中的对象上表现为什么?
锁对象的对象头。字节码中用monitorenter和monitorexit。
wait/notify/notifyAll方法需不需要被包含在synchronized块中?这是为什么?
https://blog.csdn.net/haluoluo211/article/details/49558155
需要,这三个方法都是object的方法,由锁对象执行,持有该锁对象的代码块才能调用这几个方法。
让你设计一个cache如何设计;
https://blog.csdn.net/xuchishao/article/details/48292197
内容转自:https://blog.csdn.net/CCCCC_SSSSS/article/details/86672145