Java中高级面试总结-更新中

java中高级面试:
算法的时间复杂度是指执行一个算法所需要耗费的所有时间,用T(n)表示。一个算法中的所有消耗的时间是算法中所有的语句执行之间之和。
算法必须具备:有穷性,确定性,可行性三个特性。
List和Set的区别: 源码快捷方式 查看该类的所有方法【Alt + 7】
List继承自Collection,是有序的,其实现类ArrayList,是一个动态数组,擅长随机访问,使用数组存储,查询效率会高一些,因为是连续的地址,ArrayList要移动数据,所以插入和删除的时候效率较低。ArrayList的使用会比Vector快,他是非同步的,多线程安全的【多线程访问同一个代码块,不会产生不确定的结果】如果涉及到多线程,使用Vector会较好一些,Vector类中有很多方法是被Synchornize进行修饰,这样就导致了在效率上低于 ArrayList;LinkList基于链表的书数据机构,所以在开辟内存空间上不需要连续的地址,对于添加或者删除新的节点,效率会高一些,LinkList适用于要头尾操作或者指定位置的产插入或者删除的场景,继而,因为LinkList需要移动指针,所以查询效率会更底一些。
当然LinkList和ArrayList的使用场景还是可以这样理解,在需要对数据频繁的删除或者添加时候,用采用LinkList,相反对数据访问的场景较多,选择ArrayList较好。
TreeSet:是二叉树(红黑树的数据结构)实现的,其数据是排好序的,不允许加入空值。
HashSet:是哈希表实现的,HashSet中的数据是无序的,可以放入空值,但是只能放入一个空值,两者中的值都不能重复,就如同数据库主键约束一样。HashSet要求放入的对象必须实现HashCode方法,放入的对象,是以hashcode码作为标志的。hashset是基于hash算法实现的,查询效率要明显高于treeset,而如果需要排序的时候,才使用treeSet。
HashMap:基于Hash表实现,使用HashMap要求添加的键类明确定义了hashCode和equals方法。
TreeMap:基于红黑树实现的,没有调优选项,因为该树总是处于平衡状态的。
HashMap和HashTable:HashMap去掉了HashTable中的contains方法,但是加上了containsValue()和containsKey()方法,适用于插入,删除和定位元素。HashTable是同步的,线程安全的,HashMap是非线程安全的,效率上要比HashTable高,HashMap允许空键值,HashTable不允许。HashTable适用于按自然顺序或者自定义顺序遍历key。

HashTable和ConcurrentHashMap的区别是什么?
答:他们都适用于多线程环境,但是当HashTable当大小增加到一定时候,性能会急剧下降,因为迭代时需要被锁定很长的时间,但是ConcurrentHashMap引入了分割,不乱数据变的多大,仅仅需要锁定map中的某个部分,而其他线程不需要等到迭代完成才能访问map,它锁住的仅仅是map的部分,而HashTable是锁住整个map。

sleep和wait方法的区别?
答:首先sleep()是Thread类的静态方法,wait()是object的方法,这在本质上还是有区别的,sleep不能改变对象的机锁,所以当一个Synchronized块中调用Sleep()方法,线程虽然休眠了,但是对象的机锁并木有被释放,其他线程无法访问这个对象,即使睡着了也同样持有该对象锁。在sleep()结束休眠后,该线程并不一定会立即执行,因为其他线程可能正在执行而且没有被调度为放弃执行,除非线程有更高的优先级,对于CPU资源来说,不管哪个方式暂停的线程,都表示暂时不需要CPU时间,OS会将执行时间分配给别的线程,区别是使用wait()方法后,释放了该对象的机锁【暂时失去】,需要使用notify/notifyall来唤醒等待池中的线程才能重新获取CPU执行时间,wait()方法必须放在Synchornized 代码块中,否则会抛出异常。

多进程模型:
讨论两种模型,
多进程单线程模型 和 单进程多线程模型 。
多进程模型有更强的容错性,比起多线程的一个好出是一个进程奔溃了不会影响其他的进程。数据能到达到容错隔离,对于多线程架构的车程序一般可以做到一定程度的自动恢复能力,(master守护进程监控着所有的worker进程,发现进程挂了会将其重启)。
目前nginx主流的是多进程模型,,但是也同样支持多线程模型。几乎所有的web服务器也是多线程的,至少有一个守护进程配合一个worker线程,例如:apache,httpd等等以d结尾的进程,包括init.d本身就是0级总线程。Redis也可归类为多进程单线程模型,平时工作基本是单个进程。
多线程模型:创建速度快,方便高效的数据共享,共享数据:多线程可以共享同一个虚拟地址空间,多进程见的数据共享需要用到共享内存,信号量等IPC技术。常见的应用场景:键盘的输入,立刻响应。

JVM的内存模型?
答:JVM的内存结构主要有三块,堆内存,方法区和栈。

java字节码 (byte code):是java虚拟机(JVM)可执行的一种虚拟指令格式,也就是class文件,然后能在JVM中执行,机器码(machine code)只能有电脑CPU来执行,java在运行的时候把字节码转换为机器码。
程序计数器:可以看作是当前线程执行 字节码行号 的指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、
线程恢复等基础功能都需要依赖这个计数器来完成。
java虚拟机中的 还可理解为:java虚拟机栈或者说是描述成java虚拟机中局部变量表部分,存放了编译期间可知的各种基本数据类型(boolean,int, byte,short ,long,float,double,char)和对象的引用(reference)和returnAddress,
是java虚拟机中所有线程共享的一部分内存,在虚拟机启动的时候创建,所有的对像实例以及数组都要在堆上进行分配,而且堆也是垃圾回收的主要区域,(Darbage Collect Heap)GC堆,“垃圾堆”。
方法区 可以存储一些运行期的常量池(编译期间产生的字变量和)还有对象类型数据,包括对象类型,父类,实现的接口,方法等。
数组中简单的值类型数组类型,每个数组是一个引用,引用到栈上的空间,引用类型,类类型的数组,每个数组成员任是一个引用,引用到堆上的空间,因为类的实例分配在堆上。

String和StringBuffer,StringBuilder的区别?
答:其实讨论String到底是可变还是不可变,本质上是值对象中的value[]字符数组可不可变,而不是对象的引用可不可变。关于StringBuilder和StringBuffer的效率问题,两者可谓是前世今生,StringBuilder的前世是StringBuffer,自1.5引入,StringBuilder的效率比StringBuffer稍微高,如果不考虑线程安全,StringBuilder应该是首选。JVM运行程序主要耗费在对象的创建和回收对象上。

HTTP的三次握手和四次挥手
HTTP的三次握手:
第一次:客户端向服务器发送SYN报文(SYN一般是用来进行同步的)此时客户端进入了 SYN_SEND状态,服务器进入SYN_RECE等待确认状态。
第二次:服务器接收到SYN报文后同时向客户端发送接收确认报文,并带上ACK报文,(用于应答的)。
第三次:客户端接收到ACK报文并检查ACK报文是否正确,如果正确的话客户端再次发送ACK,服务器收到后确认验证通过,表示连接已经成功建立,可以发送数据包了。

断开的四次挥手:
由于TCP是全双工的,因此每个方向都必须单独进行关闭才可,这个原则是当一方完成数据发送任务后就能发送一个FIN的标志来结束这个发送通道,收到FIN后只意味着这一个方向上没有数据流动(没有需要的数据要发送了),一个TCP可以在收到一个FIN后仍然能发送数据。
第一次挥手:客户端向服务器发送FIN,用来告诉服务器你发的东西我收到了,我要关闭通道了。
第二次挥手:服务器从客户度收到FIN,并发回一个ACK报文(应答用的),确认收到客户端的消息。
第三次挥手:服务器关闭与客户端的连接,发送一个FIN给客户端。
第四次关闭连接:客户端收到后发回ACK报文确认。

Spring AOP
要理解AOP我们可以对照着OOP(面向对象的思想来说) 面向对象是在万物皆是对象 利用面向对象的特性 继承 多态 抽象 封装 来构建对象,是一种自上而下的对象层次结构,但是细粒度到每个对象事务的内部,OOP就显得有些力不从心,考虑到系统的高聚集 低耦合,例如系统日志功能 权限判断 事务管理(日志)它几乎是水平散列分布在每个对象中,却与核心功能毫无关系。这样会导致了大量的代码重复,不利于各个模块的重用。
AOP思想:将通用的逻辑从代码业务逻辑中分离出来。是一种编程范式,也是一种编程思想,跟语言无关。
基于注解的方式:首先在一个类上@Aspect 代表的是一个切面类,@Pointcut 要切入的点是什么 一般用在方法上,还有@Advice 表示需要在方法的哪个时机进行切入。
例如:@pointcut("within(com.immoc.service.PointcutService)")或者@PointCut("within(com.immoc..*)")扫描这个包下面的所有子包及包下的所有方法。
AOP中 通配符
* 用于匹配一些任意数量的字符
+ 用于指定类及其子类
.. 用于匹配人意数量的子包或者参数


基本类型
==
equals
字符串变量
对象在内存中的首地址
字符串内容
非字符串变量
对象在字符串中的首地址
对象在字符串中的首地址
基本类型【原生类型】
不可用
包装类
地址
内容
== 和equals区别?
答:注意要点:当“==”运算符的两个操作数都是包装器类型的引用,则是比较执行的是否是同于个对象,而如果两者比较中有一个操作数是表达式(含运算符)则比较的是数值(会自动触发拆箱的过程),
装箱:即调用包装类型valueOf(),拆箱即XXXValue()。XXX可对应到不同的包装类型。
注意:Integer,Short,Byte,Charater,Long这几个类的valueOf()实现是类似的,Double,Float的valueOf方法的实现是类似的。

Session和Cookie的区别?
答:首先我先不比较Session和Cookie的区别,先说一些关于Session的主要知识点,在服务器的集群中Session的安全和同步是最大的问题,一般是使用客户端cookie加密方式,用的较少,不推荐,另一种是Session的复制---参与集群的节点上的session状态需要同步到其他所有的节点上,只要是session的状态一经改变,session数据都要被复制到其余节点上,如果服务器集群数量过大,在session复制的过程中会消耗大量的带宽,是服务器的效率明显降低;另一种方式是session共享,将所有的session信息一台服务器进行统一的管理,

session是存放在服务器端的,cookie是存放在客户端的,Session作为两个两个设备之间的状态保持者,至少需要一方需要保持另一方的会话状态,我们可以把用户访问页面产生的session放到cookie里面,就是以cookie为中转站。你访问web服务器A,产生了session然后把它放到cookie里面,当你的请求被分配到B服务器时,服务器B先判断服务器有没有这个session,如果没有,再去看看客户端的cookie里面有没有这个session,如果也没有,说明session真的不存,如果cookie里面有,就把cookie里面的sessoin同步到服务器B,这样就可以实现session的同步了。
说明:这种方法实现起来简单,方便,也不会加大数据库的负担,但是如果客户端把cookie禁掉了的话,那么session就无从同步了,这样会给网站带来损失;cookie的安全性不高,虽然它已经加了密,但是还是可以伪造的。
 
由于Http是无状态的协议,但是多数需要保持应用状态的程序,需要保证客户端和服务端的交互状态一致,对于浏览器发起的请求,仅基于http协议,比如:客户端对服务器请求同一个URL,请求1次和一万次服务器是无法记住用户身份的,是无法识别出是否为同一个用户的请求,为了保持这种交互的状态,就需要采取一系列的措施:如:
Cookie,隐藏form表单域,Session,URL,Https。那就先说说Tomcat中session的用法。
分布式Session中的处理方式:
第一:粘性Session,原理是把一个用户锁定在都一台服务器上,当用户第一次请求时,负载均衡器将用户的请求转发到了A服务器上,以后每次用户请求都会打在A服务器上,这就是Session的粘性原理,其现方式为在nginx的配置文件中这样配置:
upstearm mycluster{
  ip_hash; ## 粘性Session
 server 192.168.0.1:8080 weight:1;
 server 192.168.0.2:9090 weight:1;
}
第二种是Session的复制:
参与集群的节点上的session状态需要同步到其他所有的节点上,只要是session的状态一经改变,session数据都要被复制到其余节点上,如果服务器集群数量过大,在session复制的过程中会消耗大量的带宽,是服务器的效率明显降低;另一种方式是session共享,将所有的session信息一台服务器进行统一的管理,
第三种是Sessio共享机制:
使用分布式的Redis和Memcache实现,两者必须是集群方案。也可细分为两种Session共享方式:
① 粘性Session共享: 链接地址 https://my.oschina.net/u/1774673/blog/871912【实在没时间去深入理解了】

死锁产生发生:
死锁检测算法:设置一张进程等待表【进程号等待资源号】和一张资源等待表【资源号和进程号】
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。当线程进入对象的synchronized代码块时,便占有了资源,直到它退出该代码块或者调用wait方法,才释放资源,在此期间,其他线程将不能进入该代码块。当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。

死锁的产生的一些特定条件:

1、 互斥条件:进程对于所分配到的资源具有排它性,即一个资源只能被一个进程占用,直到被该进程释放 。

2、 请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。

3、 不剥夺条件:任何一个资源在没被该进程释放之前,任何其他进程都无法对他剥夺占用。

4、 循环等待条件:当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。

如何避免:

1、加锁顺序:
当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生。如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生。当然这种方式需要你事先知道所有可能会用到的锁,然而总有些时候是无法预知的。

2、加锁时限:
加上一个超时时间,若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试。但是如果有非常多的线程同一时间去竞争同一批资源,就算有超时和回退机制,还是可能会导致这些线程重复地尝试但却始终得不到锁。

3、死锁检测:
死锁检测即每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph等等)将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。死锁检测是一个更好的死锁预防机制,它主要是针对那些不可能实现按序加锁并且锁超时也不可行的场景。

谈谈你对MysqlInnoDB的认识?
InnoDB是mysql一个重要的存储引擎,跟其他的存储引擎相比较,其特点是:
  1. 具有较好的事务支持,事务的原子性,支持四个事务隔离级别,多版本读。
  2. 行级锁定:通过索引实现,全表扫描仍然会表锁,注意间隙锁的影响。
  3. 读写阻塞与事务隔离级别相关。
  4. 整个表和主键以Cluster方式存储,组成一颗平衡树。
  5. 具有能缓存索引,也能缓存数据的性能
      6. 所有的Secondary Index都会保存主键信息。
使用场景:
  1. 需要事务支持
  2. 行级锁对高并发有很好的适应能力,但是要确保查询是通过索引完成的。
  3. 数据更新较为频繁。
  4. 数据一致性要求较高。
  5. 硬件设备内存较大,可以使用InnoDB较高的缓存能力来提高内存利用率,尽量减少磁盘的IO。

数据库事务的隔离级别?
  1. 读未提交:一个事务可以读取另一个未提交事务的数据。【read uncommited】。
  2. 读提交:一个事务只有能到另一个事务提交完之后才可读取该数据【read commited】。
  3. 重复读:一个事务在开启之时,不允许修改操作【Reattable read】。
  4. 序列化:事务串行执行,可避免 脏读,患读,不可重复读,是数据库事务的最高隔离级别,但是会影响数据库性能,代价过大【Serializable】。
      
可重复读:(MySql的默认隔离级别)
表示同一个事务中多次读取同样的记录的结果是一致的。但在理论上,可重复读还是无法解决幻读问题,幻读(当某个事务在读取某个范围内的记录时另一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时产生幻读)

在可重复读中,该sql第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复 读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会 发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。需要Serializable隔离级别 ,读用读锁,写用写锁,读锁和写锁互斥,这么做可以有效的避免幻读、不可重复读、脏读等问题,但会极大的降低数据库的并发能力。

RPC 言外之意就是远程调用,也就是说两台服务器A和B,一个应用部署在A服务器上,想要调用B服务器上提供的方法,由于不在同一个内存空间内,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据,在RPC中所有的函数都有自己的一个ID,这个ID在所有的进程中是唯一的,客户端在做远程调用时必须带上这个ID,服务端和客户端都要维持这一个 函数  《---》ID的对应表名称,两者的表不一定完全相同,但是对应的方法名成和ID一定是相同的,当客户端需要调用远程的服务器上的函数时候,带上ID发起请求,服务器接到请求后也会查询相关表来确定相关的函数,确保调用的正确性。

#######################五月份更新##############################################

Linux进程间通信的几种方式:
  1.  管道(pipe)以及有名管道:管道可用于有亲缘关系进程间通信,有名管道克服了管道没有名字的限制,因此具有管道的所有功能之外,它还允许无亲缘关系进程间通信。
  2. 信号(Signal)。
  3. 报文(Message)队列(消息队列):消息队列是消息的链接表,包括Posix消息队列和System V消息队列。
  4. 共享内存:使得多个进程间可访问同一块内存空间,是最快的IPC形式,往往是跟信号量结合使用,来达到进程间的同步以及互斥。
  5. 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
  6. 套接字(Socket):可用于不同机器之间的进程间通信。

线程间的通信:
通信是指线程间通过何种机制来交换信息,在命令式编程中,线程之间的通信有两种:共享内存和消息传递。
同步:同步是指程序用于控制不同的线程之间操作发生相对顺序的机制。java的并发采用的是共享内存的模型(在共享内存模型里,线程之间通过写-读内存中的公共状态来隐式通信,但是在消息传递模型里面,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信),java线程之间的通信总是隐式进行的。Java中典型的消息传递方式就是wait()和notify().
java 内存模型的抽象:
java中所有的 实例域,静态域和数组元素存储在堆内存中,堆内存在线程之间是共享的。局部变量,方法参数和异常处理参数不会再线程间共享,没有内存可见性问题。java线程之间的通信有java内存模型来控制,简称JMM,JMM决定一个线程对共享变量的写入适合对另一个线程可见,从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系,线程之间的共享变量存储在主内存中(Main memory),每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。JMM是一个抽象的事务,本身并不真实存在,涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。

一个本地变量如果是原始类型,那么他完全会存储到栈区。
一个本地变量也可能是一个对象的引用,这种情况下,本地引用会存储在栈中,但是对象本身会存储在堆区。
对于一个对象的成员方法,这些方法中包含的本地变量,仍需要存储在栈区,即使所属对象在堆区。
对于一个对象的成员变量,不管他是原始类型还是包装类型,都会存储在堆区。

static变量和类本身信息都会存储在堆区。
硬件内存架构
不管是什么内存模型,最终还是要运行在计算机的硬件上的,所以我们很有必要了解一下计算机的硬件模型,现在的计算机中一般都是有多个CPU,同时CPU会有多个核心,java在执行多线程的时,这些线程会在核心里面并行运行。在计算机的硬件内存模型中,绝大多数的数据会存储在计算机的主存中,CPU上会有一组寄存器,部分堆和栈中的数据会存储在CPU的寄存器中,CPU操作寄存器的速度要远远的高于操作主存,在CPU寄存器和主存之间还存在一层CPU Cache,CPU 的缓存分为一级缓存,二级缓存,CPU操作缓存的速度要稍微慢于寄存器,主存要比寄存器和缓存大很多。

Java的垃圾回收
很多回收器都是通过分代技术来进行实现的,所以我们很有必要了解一下回收器的各个生命周期所做的事情。
年轻代:该划分为三个区域,一个是原始区域(Eden)和Survivor(存活区),存活区根据功能又可划分为From和To两个区域,所有的新生对象会分配在Eden原始区,如果经过一次垃圾回收后,仍然存活的对象会存储在其中一个Survivor区,复制到另一个Survivor 区,当这个Survivor区也满的时候,从第一个区复制过来的并且仍然存活的对象会复制到年久代。绝大多数的垃圾回收是发生在年轻代。需要注意的是:两个Survivor并没有先后关系,一个Survivor可能同时来自Eden区和从前一个Survivor复制过来的对象, 而复制到年久区只有从第一个Survivor区中复制过来的对象。在年轻代发生的垃圾回收叫做Minor GC,
年久代:如果经过多次垃圾回收周期后仍然存活的对象会存放在年久代,或者刚开始分配的较大内存也可直接存储在年久代。
持久代:静态信息、存储类,方法和他们的描述信息,基本不会发生垃圾回收。
什么时候GC开始工作?
答案是:从root开始搜索,而且经过一次标记、清理后,仍然没有复活的对象。

GC算法:
Serial收集器(复制算法)
     新生代单线程收集器,标记清理都是单线程,优点是高效。
Serial Old收集器(标记-清理算法)
    老年代单线程收集器,Serial收集器的老年代版本。
ParNew收集器(停止-复制算法)
    新生代收集器,可理解为Serial收集器的多线程版本,在多核环境下有着更优良的性能表现。
Parallel Scavenge收集器(停止-复制算法)
   并行收集器,追求高吞吐量,高效利用CPU,吞吐量一般能到达99%。
Parallel Old收集器(停止-复制算法)
   Paralle Scavenge收集器的老年代版本,并行收集器,吞吐量优先。
CMS(Concurrent Mark Sweep)收集器(标记-清理算法)
   高并发,低停顿,追求最短GC回收停顿时间,CPU占用较高,响应时间快,停顿时间短,多核CPU追求高响应时间的选择。
GC的执行机制:
    对象进行了分代处理,因此垃圾回收区域、时间也不一样,GC有两种类型的:Scavenge GC和Full GC。
Scavenge GC :当对象在申请Eden空间失败的时候,就会触发Scavenge GC对Eden区域进行垃圾回收,清理掉未存活的对象,并且把存活的对象移动到Survivor区,然后整理Survivor区域,这种方式是对Eden区域进行清理,是不会影响到年久代的。因为GC回收绝大多数都发生在Eden区域,频率较高,对回收的算法需要高效,速度快。
Full GC:
对整个堆进行垃圾回收,不管是年轻代还是持久代,速度相对要慢很多,因此尽量减少使用Full GC的次数,可能导致Full GC的情况:
  • 年久代被写满;
  • 持久代被写满
  • System.gc()被显示调用
  • 上一次GC之后Heap的各个域分配策略动态变化

猜你喜欢

转载自blog.csdn.net/m_jack/article/details/80325270