一面结束后的10分钟,开始了二面。

首先自我介绍,介绍自己,介绍项目

1.说说你项目吧

根据自己项目来说

2.嗯好,说说Java的垃圾回收算法吧

垃圾回收大体分为两步,首先是如何判别垃圾。分为引用计数法和可达性分析法。

引用计数法是给每个对象加一个计数器,每有一个指向它的引用就给计数器加一,当这个计数器为零时表示没有引用指向这个对象,这时这个对象就是一个可以被回收的对象。但是这个算法有个致命缺点就是无法解决A引用,B引用A的循环引用,因为A与B的引用计数器永不为零,这样就造成了内存泄漏。

可达性分析法是从一个GC Root开始,遍历可以到达的所有对象,给所有能到达的对象做标记。这时没有被标记的对象就是可被回收的垃圾对象,这个算法可以循环引用问题。我使用的HotSpot虚拟机就使用可达性分析法。

第二步是回收垃圾的过程,垃圾回收器主要工作的区域是Java堆内存,堆内存又大体分为新生代和老年代,新生代又按8:1:1的比例分为Eden区和两个Surviver区。新生代和老年代根据使用的垃圾回收器不同会有不同的算法。新生代基本都是复制清理算法,就是把存活的对象从Eden和其中的一个Surviver区复制到另一个Surviver区中,这时如果存活对象过大还会出发老年代的内存分配担保。老年代则是采用标记清理或标记整理算法,两个算法的基本思想都是标记垃圾对象,然后操作之。标记清理是直接把垃圾对象清除,但这样会产生很多的内存碎片。标记整理算法则是把未标记的对象放到整个老年代内存的一边,把标记的对象放到老年代的另一边,然后清理存放标记对象的那一部分即可。

垃圾回收又根据收集规模分为minorGC和FullGC。minorGC只清理新生代,因为新生代比较小,而且存放的大多都是朝生暮死的对象,因此MinorGC的效率很高,也进行的较为频繁。FullGC是清理整个堆内存,会导致程序卡顿,清理效果很好,但是效率不高,我们在虚拟机调优的一个方向就是减少FullGC。

3.来做个题吧。现在有个二叉树,请你找出最深最左的节点。

方法1:用一个类把节点值和它的层数封装起来,使用深度优先遍历,递归回溯的时候通过层数判断要返回的节点值。

方法2:层序遍历,在最后一层的数组中找第一个下标对应的值。

4.HashMap了解吗?说说吧

又是HashMap,参考我的博客:Map一家亲,或者字节跳动一面面经

5.Java中的同步方法了解吗?说说吧

最常用的同步方法就是使用Synchronized,但是Synchronized是一种重量级锁的实现,线程间的同步需要用互斥量使线程在阻塞和运行两个状态下运行的,状态的切换需要从用户态切换到内核态,因此Synchronized关键字效率较低。但是虚拟机对Java的重量级锁进行了优化。

在一个线程首次获取锁时,会获取到一个偏向锁,即加锁对象的MarkWord指向获取锁的线程,获取偏向锁后该线程之后执行临界区代码即不用再次获取锁,直到有其他线程来竞争锁,这时会撤销偏向锁,变为轻量级锁,轻量级锁通过CAS和自旋操作获取锁,适用于锁竞争不太激烈的情况,如果发生大量锁竞争,才会变为重量级锁。

除了Sychronized关键字,还有并发包中很多的锁对象可以提供同步的效果,比如重入锁,他通过维护一个同步队列和自旋CAS进行线程同步,效率高,并且还具备了有限时间等待,公平等待,多条件控制等Synchronized关键字不具备的功能。在编程时,使用重入锁要好于使用Synchronized关键字。

6.Java中那些地方用到了CAS

CAS操作底层是通过一条cmpxchg指令实现的,大体的实现方法是判断一个变量有没有发生改变,如果没有就进行操作,如果改变就不进行操作。但是使用CAS要注意ABA问题,就是一个变量从A变到B,再从B变到A,这样的情况普通的CAS是无法检测的,现在可以通过给变量加一个版本号来实现,每进行一次操作版本号就加一,这样ABA操作中变量的两次A值对应的版本号是不同的,就可以通过检测版本号避免ABA问题。

CAS操作主要是用在Java并发包中的同步队列中,同步队列是个抽象类,很多类都在内部实现了特定的同步类,因此只要使用到同步队列的类都涉及到了CAS操作,另外就是一些原子类,其中的提供AtomicInteger等原子类中也有很多的CAS操作。Java中的CAS主要是通过调用Unsafe类中的方法实现的,这些方法都是本地方法。

7.说说进程间的通信方式

就说了管道和共享内存两个,操作系统是我的弱点

8.说说在项目中遇到的问题,怎么解决的

说了项目中分级权限系统的设计

9.平时有没有读什么书

Java语言方面,JVM也看很多,其他的较少。

 

小结

这次面试只有一个问题没有回答上来,还不错。