- 为什么要用volatile关键字?
答:在高并发时会出现并发模式异常,volatile可以防止指令重排,创建对象操作并不是一个原子操作,分为三个步骤 (1)构建对象:根据Person类元信息确定对象的大小,向JVM堆中申请一块内存区域并构建对象的默认信息(加载Person对象成员变量信息并赋默认值如 int类型为0,引用类型为null)。(2)初始化对象:然后执行对象内部生成的init方法,初始化成员变量值,同时执行搜集到的{}代码块逻辑,最后执行对象构造方法。(3)引用对象:对象实例化完毕后,再把栈中的Person对象引用地址指向Person对象在堆内存中的地址......
- java并发锁机制。
(1)偏向锁:JDK1.6提出来的一种锁优化的机制。其核心的思想是,如果程序没有竞争,则取消之前已经取得锁的线程同步操作。也就是说,若某一锁被线程获取后,便进入偏向模式,当线程再次请求这个锁时,就无需再进行相关的同步操作了,从而节约了操作时间,如果在此之间有其他的线程进行了锁请求,则锁退出偏向模式 (2)轻量级锁:如果偏向锁失败,Java虚拟机就会让线程申请轻量级锁,轻量级锁在虚拟机内部,使用一个成为BasicObjectLock的对象实现的,这个对象内部由一个BasicLock对象和一个持有该锁的Java对象指针组成。BasicObjectLock对象放置在Java栈帧中。在BasicLock对象内部还维护着displaced_header字段,用于备份对象头部的Mark Word。(3)重量级锁:当轻量级锁失败,虚拟机就会使用重量级锁。重量级锁在操作过程中,线程可能会被操作系统层面挂起,如果是这样,线程间的切换和调用成本就会大大提高。(4)自旋锁:它可以使线程在没有取得锁的时候,不被挂起,而转去执行一个空循环,若在若干个空循环后,线程如果可以获得锁,则继续执行。若线程依然不能获得锁,才会被挂起。使用自旋锁后,线程被挂起的几率相对减少,线程执行的连贯性相对加强。因此,对于那些锁竞争不是很激烈,锁占用时间很短的并发线程,具有一定的积极意义,但对于锁竞争激烈,单线程锁占用很长时间的并发程序,自旋锁在自旋等待后,往往毅然无法获得对应的锁,不仅仅白白浪费了CPU时间,最终还是免不了被挂起的操作 ,反而浪费了系统的资源。
- 了解zookeeper的leader选取算法吗,讲一下它的流程。
(1)自增选举轮次。Zookeeper规定所有有效的投票都必须在同一轮次中,在开始新一轮投票时,会首先对logicalclock进行自增操作。(2)初始化选票。在开始进行新一轮投票之前,每个服务器都会初始化自身的选票,并且在初始化阶段,每台服务器都会将自己推举为Leader。(3)发送初始化选票。完成选票的初始化后,服务器就会发起第一次投票。Zookeeper会将刚刚初始化好的选票放入sendqueue中,由发送器WorkerSender负责发送出去。(4)接收外部投票。每台服务器会不断地从recvqueue队列中获取外部选票。如果服务器发现无法获取到任何外部投票,那么就会立即确认自己是否和集群中其他服务器保持着有效的连接,如果没有连接,则马上建立连接,如果已经建立了连接,则再次发送自己当前的内部投票。(5)判断选举轮次。在发送完初始化选票之后,接着开始处理外部投票。在处理外部投票时,会根据选举轮次来进行不同的处理。·外部投票的选举轮次大于内部投票。若服务器自身的选举轮次落后于该外部投票对应服务器的选举轮次,那么就会立即更新自己的选举轮次(logicalclock),并且清空所有已经收到的投票,然后使用初始化的投票来进行PK以确定是否变更内部投票。最终再将内部投票发送出去。·外部投票的选举轮次小于内部投票。若服务器接收的外选票的选举轮次落后于自身的选举轮次,那么Zookeeper就会直接忽略该外部投票,不做任何处理,并返回步骤4。·外部投票的选举轮次等于内部投票。此时可以开始进行选票PK。(6)选票PK。在进行选票PK时,符合任意一个条件就需要变更投票。· 若外部投票中推举的Leader服务器的选举轮次大于内部投票,那么需要变更投票。· 若选举轮次一致,那么就对比两者的ZXID,若外部投票的ZXID大,那么需要变更投票。· 若两者的ZXID一致,那么就对比两者的SID,若外部投票的SID大,那么就需要变更投票。(7)变更投票。经过PK后,若确定了外部投票优于内部投票,那么就变更投票,即使用外部投票的选票信息来覆盖内部投票,变更完成后,再次将这个变更后的内部投票发送出去。(8)选票归档。无论是否变更了投票,都会将刚刚收到的那份外部投票放入选票集合recvset中进行归档。recvset用于记录当前服务器在本轮次的Leader选举中收到的所有外部投票。(9)统计投票。完成选票归档后,就可以开始统计投票,统计投票是为了统计集群中是否已经有过半的服务器认可了当前的内部投票,如果确定已经有过半服务器认可了该投票,则终止投票。否则返回步骤(4)。(10)更新服务器状态。若已经确定可以终止投票,那么就开始更新服务器状态,服务器首选判断当前被过半服务器认可的投票所对应的Leader服务器是否是自己,若是自己,则将自己的服务器状态更新为LEADING,若不是,则根据具体情况来确定自己是FOLLOWING或是OBSERVING。
- 双亲委派机制及其使用原因?
(1)当某个特定的类加载器它在接到需要加载类的请求时,这个类会首先查看自己已加载完的类中是否包含这个类,如果有就返回,没有的话就会把加载的任务交给父类加载器加载,以此递归,父类加载器如果可以完成类加载任务,就返回它,当父类加载器无法完成这个加载任务时,才会不得已自己去加载。这种机制就叫做双亲委派机制。(2)原因:双亲委派机制能够保证多加载器加载某个类时,最终都是由一个加载器加载,确保最终加载结果相同。
- treemap和HashMap的区别?
(1)HashMap是通过hashcode()对其内容进行快速查找的;HashMap中的元素是没有顺序的;TreeMap中所有的元素都是有某一固定顺序的,如果需要得到一个有序的结果,就应该使用TreeMap;(2)HashMap继承AbstractMap类;覆盖了hashcode() 和equals() 方法,以确保两个相等的映射返回相同的哈希值;TreeMap继承SortedMap类;他保持键的有序顺序;(3)HashMap:基于hash表实现的;使用HashMap要求添加的键类明确定义了hashcode() 和equals();为了优化HashMap的空间使用,可以调优初始容量和负载因子;TreeMap:基于红黑树实现的;TreeMap就没有调优选项,因为红黑树总是处于平衡的状态;
- java内存泄露解决
堆的dump文件,通过jmx的mbean生产当前 的heap信息, 用eclipse自带的静态分析工具Mat(windDBG)打开 分析内存泄露:那些被怀疑为内存泄露,哪些占用空间大,对象调用关系
- 分布式锁的实现原理和有多少种实现方式?
目前主流的分布式锁的实现方式有三种:①借助数据库来实现,新建一张锁表;操作前向表添加一条锁记录(锁id建立唯一索引),成功添加者获得锁权限,处理完后删除锁记录来释放锁。②基于缓存实现,如memcache 和 redis;memcache的add操作具有原子性,可以保证同一个key add操作只有一个成功,来获取锁权限,利用缓存的失效时间来解决死锁问题。相对于第一种方案,这种方案性能更好,而且操作更方便。③通过zookeeper实现;客户端会在zookeeper生成一个临时的目录节点,存储在一个序列中,每次节点序号最小的节点对应的客户端获得锁,处理完成后删除最小节点,而且可重复获取锁(通过判断序号是否和最小的节点相同)。这种方式可以实现阻塞分布式锁,和锁的重复获取问题。
- HashMap和Hashtable的区别
主要的区别有:线程安全性,同步,以及速度。(1)HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。(2)另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。HashMap不能保证随着时间的推移Map中的元素次序是不变的。
- HashMap中是否任何对象都可以做为key,用户自定义对象做为key有没有什么要求?
用自定义类作为key,必须重写equals()和hashCode()方法。自定义类中的equals() 和 hashCode()都继承自Object类。Object类的hashCode()方法返回这个对象存储的内存地址的编号。而equals()比较的是内存地址是否相等。
- 对sql进行优化的原则有哪些?
(1)减少返回不必要的数据 (2)减少物理和逻辑读次数 (3)减少计算次数
- String和StringBuffer的区别
1)运行速度:StringBuilder >StringBuffer >String String是字符串常量,不可变,每次改变只是创建一个新的对象,然后GC回收掉老的那个,所以执行速度最慢,另外两个是字符串对象,可变。(2)线程安全:StringBuilder是线程不安全的,StringBuffer是线程安全的,看是否带synchronized关键字。多线程则采用StringBuffer,单线程则要建议用速度较快的StringBuilder。(3)String:适用于少量的字符串操作的情况,String是final类,无法被继承。StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况。StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况。
- 如果A和B对象循环引用,是否可以被GC?
答:这个循环引用是否被回收,就看这个循环引用是否挂在根上,A引用B,B引用A,A和B并没有挂在某个内存元和根上,当他们的生命周期结束的时候。这两个对象都有可能被回收。
- Error、Exception和RuntimeException的区别,作用又是什么?
Error是Throwable 的子类,用于指示合理的应用程序不应该试图捕获的严重问题。大多数这样的错误都是异常条件。虽然 ThreadDeath 错误是一个“正规”的条件,但它也是 Error 的子类,因为大多数应用程序都不应该试图捕获它。在执行该方法期间,无需在其 throws 子句中声明可能抛出但是未能捕获的 Error的任何子类,因为这些错误可能是再也不会发生的异常条件。Exception类及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件。RuntimeException是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。可能在执行方法期间抛出但未被捕获的RuntimeException 的任何子类都无需在 throws 子句中进行声明。它是Exception的子类。
- reader和inputstream区别
(1)InputStream是表示字节输入流的所有类的超类;Reader是用于读取字符流的抽象类 (2)InputStream提供的是字节流的读取,而非文本读取,这是和Reader类的根本区别。即用Reader读取出来的是char数组或者String ,使用InputStream读取出来的是byte数组。
- hashCode的作用;
hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable。Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。
- Java中的内存溢出是如何造成的?
(1)内存中加载的数据量过于庞大,如一次从数据库取出过多数据;(2)集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;(3)代码中存在死循环或循环产生过多重复的对象实体;(4)使用的第三方软件中的BUG;(5)启动参数内存值设定的过小;
- springMVC的工作原理图
(1)客户端发出一个http请求给web服务器,web服务器对http请求进行解析,如果匹配 DispatcherServlet的请求映射路径(在web.xml中指定), web容器将请求转交给DispatcherServlet;(2)DipatcherServlet接收到这个请求之后将根据请求的信息(包括URL、Http方法、请求报文头和请 求参数Cookie等) 以及HandlerMapping的配置找到处理请求的处理器(Handler);(3)DispatcherServlet根据HandlerMapping找到对应的Handler,将处理权交给Handler(Handler将 具体的处理进行封装), 再由具体的HandlerAdapter对Handler进行具体的调用。(4)Handler对数据处理完成以后将返回一个ModelAndView()对象给DispatcherServlet;(5)Handler返回的ModelAndView()只是一个逻辑视图并不是一个正式的视图,DispatcherSevlet通过 ViewResolver将逻辑视图转化为真正的视图View;(6)Dispatcher通过model解析出ModelAndView()中的参数进行解析最终展现出完整的view并返回给客户端;
- RPC框架和普通http有什么区别和优势?
基于Tcp封装还是http封装的?(1)1、RPC是一种API,HTTP是一种无状态的网络协议。RPC可以基于HTTP协议实现,也可以直接在TCP协议上实现。(2)RPC主要是用在大型网站里面,因为大型网站里面系统繁多,业务线复杂,而且效率优势非常重要的一块,这个时候RPC的优势就比较明显了。(3)HTTP主要是用在中小型企业里面,业务线没那么繁多的情况下。(4)HTTP开发方便简单、直接。开发一个完善的RPC框架难度比较大。(5)HTTP发明的初衷是为了传送超文本的资源,协议设计的比较复杂,参数传递的方式效率也不高。开源的RPC框架针对远程调用协议上的效率会比HTTP快很多。(6)HTTP需要事先通知,修改Nginx/HAProxy配置。RPC能做到自动通知,不影响上游。(7)HTTP大部分是通过Json来实现的,字节大小和序列化耗时都比Thrift要更消耗性能。RPC,可以基于Thrift实现高效的二进制传输。
- GC的基本原理?什么时候需要GC?为什么需要GC?
GC (Garbage Collection)的基本原理:将内存中不再被使用的对象进行回收,GC中用于回收的方法称为收集器,由于GC需要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、旧生代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停 (1)对新生代的对象的收集称为minor GC;(2)对旧生代的对象的收集称为Full GC;(3)程序中主动调用System.gc()强制执行的GC为Full GC。不同的对象引用类型, GC会采用不同的方法进行回收,JVM对象的引用分为了四种类型:(1)强引用:默认情况下,对象采用的均为强引用(这个对象的实例没有其他对象引用,GC时才会被回收) (2)软引用:软引用是Java中提供的一种比较适合于缓存场景的应用(只有在内存不够用的情况下才会被GC) (3)弱引用:在GC时一定会被GC回收 (4)虚引用:由于虚引用只是用来得知对象是否被GC
- 怎样避免死锁?
(1)破坏“不可剥夺”条件:一个进程不能获得所需要的全部资源时便处于等待状态,等待期间他占有的资源将被隐式的释放重新加入到 系统的资源列表中,可以被其他的进程使用,而等待的进程只有重新获得自己原有的资源以及新申请的资源才可以重新启动,执行。(2)破坏”请求与保持条件”:第一种方法静态分配即每个进程在开始执行时就申请他所需要的全部资源。第二种是动态分配即每个进程在申请所需要的资源时他本身不占用系统资源。(3)破坏“循环等待”条件:采用资源有序分配其基本思想是将系统中的所有资源顺序编号,将紧缺的,稀少的采用较大的编号,在申请资源时必须按照编号的顺序进行,一个进程只有获得较小编号的进程才能申请较大编号的进程
- BEAN的生命周期
(1)应用启动的时候检查加载需要被Spring管理的bean. (2)根据实现的接口,依次设置beanName,BeanFactory,ApplicationContext应用上下文。(3)根据实现的接口,依次调用加载前,设置值,自定义初始化方法,加载完成后。(4)bean已经可以用了,存活直到上下文也被销毁。(5)销毁的时候调用destroy方法和自定义的销毁方法。