JAVA面经复习(二十五)面试难度:☆☆☆☆

面试难度:☆☆☆☆

推荐指数:☆☆☆☆☆

声明:答案均为网上搜索汇总得到的参考答案,如有不妥或意见相左之处欢迎指出!

问:hashmap相关,包括底层的实现 ?

答:hashmap是一种用于存储k-v的数据结构,底层采用哈希数组,解决冲突的方式是链表或红黑树的方式,在链表长度达到8的时候,根据柏松分布判断此时检索的速度大致为O(n),因此将链表转换成对应的红黑树。初始长度为16,负载因子是0.75。在长度达到负载因子*初始长度的时候,会触发resize的机制进行扩容。

问:初始化值如果不是2的n次方会发生什么 ?

答:会自动往上取整为2的n次方,hashmap之所以要采用2^n的结构,是因为这方便于其采用的哈希函数是基于位运算的(hash&(length-1)),保持长度为2的n次方有助于减少碰撞。

问:hash是怎么求的, key是怎么求的 ?

答:调用key.hashcode()得到对应字符串的首个哈希值h1,进一步采用对应的哈希函数h2 = h1 &(length-1)进行计算得到对应的数组下标。

问:链表什么时候转换成红黑树,好处是什么?

答:当链表长度大于8的时候转换成红黑树,好处是可以降低检索元素的时间复杂度。

问:进一步,线程不安全,需要什么结构

答: concurrenthashmap和hashtable,考虑到并发的效率的话一般采用concurrenthashmap。

问:进一步,concurrenthashmap的底层实现 ?

答: concurrenthashmap在jdk1.7中采用segment+hashEntry+链表的方式。在jdk1.8中则采用Node数组+链表/红黑树的方式。

问:锁有那些 ?(七大类)

答:1、偏向锁/轻量级锁/重量级锁,这三种锁特指synchronized的状态。

2、可重入锁/非可重入锁,可重入锁是指在获取了锁的情况下,可以不再去申请锁而直接访问资源,非可重入则相反。

3、共享锁/独占锁,读写锁为例子,读锁是共享的,写锁是独占的。

4、公平锁/非公平锁,公平锁是指被阻塞的线程依次排队,直到获取锁资源。而非公平锁存在插队现象。

5、悲观锁/乐观锁,悲观锁典型的是synchronized,而乐观锁则是CAS为主,其不对资源做强制性阻塞的操作,而是采用获取锁元素,并比较是否被修改从而乐观地获取资源。

6、自旋锁/非自旋锁,即指获取不到资源后,线程是循环等待还是直接阻塞。

7、可中断锁/不可中断锁,在 Java 中,synchronized 关键字修饰的锁代表的是不可中断锁,一旦线程申请了锁,就没有回头路了,只能等到拿到锁以后才能进行其他的逻辑处理。而我们的 ReentrantLock 是一种典型的可中断锁,例如使用 lockInterruptibly 方法在获取锁的过程中,突然不想获取了,那么也可以在中断之后去做其他的事情,不需要一直傻等到获取到锁才离开。

问:自旋锁是什么 ?

答:CAS是典型的自旋锁吧,自旋锁是典型的乐观锁,其会从内存中取值A,如果值A和预期值相符,则证明当前没有人改过对应的数据,否则则认为进入

问:公平锁和不公平锁的区别 ?

答:公平锁是指被阻塞的线程依次排队,直到获取锁资源。而非公平锁存在插队现象。

问:可重入锁是什么,有什么用?

答:可重入锁是指在获取了锁的情况下,可以不再去申请锁而直接访问资源,非可重入则相反。这样做可以减少同一个资源在频繁获取锁上的消耗,从而加快了程序的运行速度。

问:读写锁是什么?

答:读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。

问:java内存的分类 ?

答:主要分为堆,虚拟机栈,本地方法区,方法栈,程序计数器。

问:虚拟机栈存了什么?(栈帧四大元素)

答: 栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟街运行时数据区中的虚拟机栈的栈元素。栈帧存储了方法的局部变量表操作数栈动态链接方法返回地址等信息。

1、局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量

2、操作数栈也成为操作栈,后入先出,操作数栈的最大深度也在编译的时候就已经确定,不会在运行期间动态变化。

3、class文件的常量池中有大量的符号引用,字节码中的方法调用指令就以常量池指向的方法的符号引用作为参数。这些符号引用一部分会在类加载阶段(解析阶段)或者第一次使用的时候就转化为直接引用,这种转化成为静态解析,另一部分在没一次运行期间转化为直接引用,这部分成为动态连接

4、正常情况下,执行引擎遇到一个方法返回的字节码指令,这时候执行引擎读取栈帧中的方法返回地址,将返回值传递给上层的方法调用者。

问:堆存了什么?

答:堆内存用来存放由 new 创建的对象和数组,是GC收集的主要区域。

问:堆怎么分区?

答:分为新生代和老年代,其比例为1:2 ,在新生代中又分成Eden区和surivior区,surivior区又可以分为from surivior和to surivior区,总比例为8:1:1。

问:为什么要分代?

答:对于不同的生存周期的对象,采用分代可以使用不同的垃圾收集算法,垃圾回收器。同时避免了每次GC回收都需要扫描整个堆,加快了垃圾回收的速度。

问:垃圾回收的算法有哪些?

答: 标记清除,标记整理,复制算法,分代回收算法。

问:可达性分析,什么叫可达 ?

答:可达性分析指的是从GCRoots出发可以通过对象引用指向找到的对象,即为可达。

问:java的引用有哪些 ?(四大引用,强软弱虚)

答:1、强引用,即正常情况下的java引用,其在任何情况下都不会被JVM进行GC回收。

2、软引用,通过SoftReference的get方法来获取对象。其在jvm内存不足的情况下会被回收。

3、弱引用,通过WeakReference的get方法来获取对象。在gc的时候就会被回收,不管内存是否充足。

4、虚引用,虚引用和没有引用是一样的,需要和队列(ReferenceQueue)联合使用。当jvm扫描到虚引用的对象时,会先将此对象放入关联的队列中,因此我们可以通过判断队列中是否存这个对象,来进行回收前的一些处理。

问:内存泄露的情况 ?

答:内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

**造成的原因:**长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是Java中内存泄漏的发生场景。具体主要有如下几大类:
1)静态集合类引起内存泄漏:

2)当集合里面的对象属性被修改后,再调用remove()方法时不起作用。JDK1.8 貌似修正了引用对象修改参数,导致hashCode变更的问题。

3)*** Listener 各种连接 Connection

4)内部类和外部模块的引用(尽量使用静态内部类)

5)单例模式(静态类持有引用,导致对象不可回收)

问 :JMM模型和操作系统里,线程和进程,线程和线程,进程和进程的通信方式?共享内存?如何共享?

答:进程间通信方式:1、通道/命名通道;2、消息队列;3、信号量;4、共享内存。

线程间通信方式:1、采用volatile修饰变量进行通信。2、object类的wait()和notify()方法。3、使用JUC工具类 CountDownLatch。4、使用 ReentrantLock 结合 Condition。5、基本LockSupport实现线程间的阻塞和唤醒。

线程和进程间通讯方式:尚无相关的资料。。。。。有待补充。

**共享内存:**即每个线程拥有一份主内存的拷贝内容,当线程对自己的工作内存修改后,需要对应地加锁去修改主内存中的内容,并通知其余线程数据的改动信息。其余线程根据改动信息,再对数据进行更新从而实现了内存共享。

问:JMM内存可见性?

答: 可见性是指一个线程对共享变量值的修改,能够及时被其他线程看到。由于JAVA中的线程操作是在自己的工作内存中执行的,因此需要及时刷入主内存中,并修改其余线程内存中的值。

保证内存可见性的方案有:1、采用Synchronized关键字或其余的加锁操作进行上锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。2、采用volatile关键字设置内存屏障,禁止指令重排序进而保证内存可见,但需要注意的是,volatile关键字并不能保证操作是原子性的,因此需要注意并发的安全性。

问:线程如何共享变量?

答: 1、采用Synchronized关键字或其余的加锁操作进行上锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。2、采用volatile关键字设置内存屏障,禁止指令重排序进而保证内存可见,但需要注意的是,volatile关键字并不能保证操作是原子性的,因此需要注意并发的安全性。

问:如何在进程里面新建一个进程 ?

答: java下的话,考虑是1、继承Thread类;2、实现Runnable接口;3、实现Callable接口;4、采用线程池去生产对应的线程。

问:TCP和UDP区别,用法 ?

答:TCP属于全双工通信,采用三次挥手四次握手建立可靠的连接,还会采用一些流量控制的方法如滑动窗口来控制流量发送。针对网络拥堵的情况,还有慢启动,快重传,佣塞避免,快恢复的算法来保证数据有效传输。

UDP则属于尽全力传输,其不保证消息传递的可靠性,也不必建立对应的连接。但其传输速度由于不需要建立连接会比TCP更快。

用法:TCP主要针对消息需要极大可靠性传输的情况,如用户消息的发送,网页消息的发送等等。而UDP则针对一些消息不需要太可靠的情况,如视频流(即使缺失一两帧也不会造成太大影响)。

问:三次握手、四次挥手 ?

答:三次握手:

第一次握手:客户端发送初始序号x和syn=1请求标志。

第二次握手:服务器发送请求标志syn,发送确认标志ACK,发送自己的序号seq=y,发送客户端的确认序号ack=x+1。

第三次握手:客户端发送ACK确认号,发送自己的序号seq=x+1,发送对方的确认号ack=y+1。

通俗理解:

第一次握手,客户端渴望建立连接,于是给服务器端发送了请求。

第二次握手,是指服务器端收到了客户端的请求,同时告诉客户端,我可以建立请求。

第三次握手,是指客户端收到了服务器端的请求,回信服务器端说:“好的,我收到你的信息了!”。

四次挥手:

第一次挥手:客户端发出释放FIN=1,自己序列号seq=u,进入FIN-WAIT-1状态。
第二次挥手:服务器收到客户端的后,发出ACK=1确认标志和客户端的确认号ack=u+1,自己的序列号seq=v,进入CLOSE-WAIT状态
第三次挥手:客户端收到服务器确认结果后,进入FIN-WAIT-2状态。此时服务器发送释放FIN=1信号,确认标志ACK=1,确认序号ack=u+1,自己序号seq=w,服务器进入LAST-ACK(最后确认态)
第四次挥手:客户端收到回复后,发送确认ACK=1,ack=w+1,自己的seq=u+1,客户端进入TIME-WAIT(时间等待)。客户端经过2个最长报文段寿命后,客户端CLOSE;服务器收到确认后,立刻进入CLOSE状态。

通俗理解:

第一次挥手:客户端说:“我信息发完了,我想退出连接了。”

第二次挥手:服务器端说:”好的我收到你的消息了,但我检查一下还有没有消息没发完。“

第三次挥手:服务器端说:”好的,我信息也发完了。“

第四次挥手:客户端说:”好的,我知道了,那我关连接啦。“ 然后客户端等了一会,确认服务器端没有因为“听不清楚”再次询问后,就走开了。

问:tcp连接的关闭,是否可以不同步

答:不可以,如果连接的关闭不同步,那么有可能出现一方已经关闭连接,而另外一方由于网络的传输不可靠,导致部分的信息没有收到,因此一直维持一个连接的状态,从而浪费了CPU资源。

问:wait_time是干嘛的

答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。

问:AOP的用法,好处坏处

答:AOP即面向切面编程,其好处在于可以将相同的业务代码进行抽象,从而在原有的业务代码上添加一些自定义的逻辑。坏处如果要说,就是改变了项目的编程思路,增大编程难度。

问:AOP怎么实现的 ?

答:动态代理或者是静态代理。通过获取代理对象的业务代码,并在该段业务代码前后加上新的处理逻辑,从而实现切面编程。

问:数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且有效的括号组合。

答:深度优先遍历做一下,但是时间复杂度较高。

问:求二叉树最底层的结点的和 ?

答:采用层次遍历或者递归都可以,只要判断对应节点的左右子树为空即可。

参考资料:

字节后端java一面

Java 中有哪几种锁?

线程间通信的几种实现方式