最近接到了某个知名外企公司的面试电话,说实话去的路上心里很没底,整个第一轮面试持续了40分钟,问了许多技术问题,总结下来就是潜入深出,特别是对于Java底层实现和多线程实现上面特别深究,最终我也止步于第二轮面试,第二轮面试的问题实在是有点懵逼,下面就整理总结出我这次第一轮面试的问题和回答吧~
1.5说实话我不知道,1.6~1.8默认使用了Parallel Scavenge(年轻代收集器),Parallel Old(老年代回收器)
Parallel是一个吞吐量回收器,不存在内存碎片核心思想是标记-整理方式,会将年轻代内存区域划分成三块,每块大小比例为8:1:1,一块是备用专们用来整理复制时使用,但是对于CPU的利用没有最大化,也会存在STOP-THE-WORLD情况
JDK1.9开始默认使用G1回收器,全量回收器,当今JAVA最好的回收器,充分利用CPU并且不存在内存碎片,原理是将内存切分成多个相同大小的区域,标记-整理时也是按区域进行,所以当回收时会采用分区回收最大程度的降低了STOP-THE-WORLD情况
年轻代和老年代都是存储在堆中的,年轻代主要存放小对象和可被快速回收的对象
老年代主要存储大对象或者经过多次回收还是未被回收的年轻代,默认15次每存活一次+1
永久代主要是存放常量,方法区,JDK1.8以后取消了老年代概念使用了元空间,不在使用虚拟内存直接使用了本地内存
Object obj = new Object();这种就属于强引用,只要obj不释放那么对象就不会被释放回收
SoftReference soft = new SoftReference(obj);这种属于软引用,soft对对象obj的软引用,只要obj未被回收那么soft.get()就能拿到对象值,如果obj已经被标记需要回收,那么get方法就会返回null,如果内存足够那么使用软引用可以减少获取新对象的开销,从缓存中获取即可
WeakReference wr = new WeakReference(obj);这种属于弱引用,使用wr.isEnQueued()方法判断obj对象是否被标记回收了
虚引用用的场景较少,主要用于检测对象是否还存在内存中
Person p = new Person(); //用户类,属性包含name,age
HashMap
m.put(p);
p.setName("xxx"); //修改了name属性值
m.remove(p);
此时由于对象p变更了name的值,导致hash值已经发生改变在调用remove时已经无法删除,若没有发现在循环中调用的话就会内存溢出
内部将Map划分成了多个区域,并未使用Sync关键字做锁,因为这样的锁过于重,每一个区域有自己的一个LockKey,当某一个区域被多个线程访问时会进行等待拿取锁,但是访问其他区域时不受影响,解决了性能问题也有了线程安全性
Vloatile是用来多线程对变量共享的关键字,在多线程环境下每个线程都拥有自己的工作内存,所有变量的读取会先从主内存中复制到工作内存,所以在未使用此关键字时各个线程对变量的修改都局限于工作内存中,其他线程是不可见的,但是用了这个关键字后变量的读取会强制使用主内存中的值,变量的值修改会强制将工作内存的值刷新到主内存中,所以各个线程就可见了。
但是Vloatile关键字有使用局限性,不能用在自增场景下,因为Vloatile不是线程安全的,它能保证的是避免线程的重排序
ThreadLocal使用场景是本地变量副本,例如一些数据库连接获取方法
ThreadLocal内部有一个ThreadLocalMap,这个Map的key软引用了ThreadLocal对象,如果ThreadLocal对象已经被标记回收,那么这个Map的key就会为null,但是值还存在所以GC时不会被回收,就有可能出现内存溢出问题,但是如果使用强引用的话也会出现内存溢出问题,因为ThreadLocalMap生命周期和ThreadLocal一样长,那么也极易造成内存溢出问题,所以ThreadLocalMap在设计时考虑到了这个问题,当调用ThreadLocalMap.remove时内部会检查所有key=null值的数据然后将value设置null等待下次回收,所以为了最大限度避免内存溢出问题需要在每次使用完后调用一次ThreadLocalMap.remove方法即可
一共有四种隔离级别:未提交读,已提交读,可重复读,串行化
默认隔离级别是可重复读,但是解决不了幻读
如果需要解决幻读问题有2种方案:
1.隔离级别提高到串行化,但是效率极低
2.使用MVCC版本控制的乐观锁
Mysql的MVCC主要是为RR(可重复读)隔离级别设计的
A/B客户端所见的数据相互隔离,各自的更新互不可见,主要流程:
1.使用排他锁对数据进行更新
2.把修改前的数据存放于undo.log中
3.如果更新操作成功不做任何操作,如果失败则从undo.log中回滚数据
其实并非真正意义上的MVCC设计,因为真正的MVCC设计难以在生产环境实现
binlog提交分了三个阶段:
指的是开启binlog后,redo和binlog文件的两阶段提交,redo是新数据的备份文件,undo是修改前老数据的备份
B-TREE索引优势:
B-TREE的限制:
Hash索引的优势:
Hash索引的限制:
1.使用@Header注解
2.实现RequestInceptor
断路器再打开一段时间之后会进行自我修复,此时会进入半打开状态,再次访问被短路的服务询问是否恢复正常
1.保证消息一定被消费最简单的做法就是使用一张消息消费记录表,由于所有MQ在消息被消费成功后都应该能拿到消费者的回执,如果没拿到这个回执,那么可能是网络问题未被消费,也可能是消费成功了但是回执回传失败,无论哪种情况我们都可以使用消费事件表进行跟踪,只要某个消息ID的状态未被更新我们就再次PUSH
2.分布式MQ系统中保证消息的顺序性最常用的做法就是通过计算消息的RequestID的Hash值,如果是同一种Hash值消息全部放到同一个机器的消息队列,这样就保证了顺序性
3.消息去重需要在消费端进行,一般MQ不会拥有这种功能,实现方式也比较简单:
Redis分布式开发我们一般使用Jedis,调用Jedis.set(lockkey,value,xx,xx,xx)方法即可,xx是因为具体英文描述忘记了,这五个参数说明:
但是在早期的Jedis中,还没有这五个参数的方法,只有Jedis.senx和Jedis.expire方法,一个是加锁一个是设置失效时间,但是使用不当非常容易死锁
分布式事务最常用的设计是使用MQ来进行,将一个大事务拆分成多个小事务,例如A系统扣钱,B系统加钱,那么使用Spring框架开发时会把发送B系统的MQ放到A系统扣钱的事务中,如果A系统扣钱失败那么B系统停止MQ推送,如果A扣钱成功,但是B发送MQ失败那么抛出异常回滚A系统操作
内部实现通过数组+链表+红黑树方式,数组存放key经过hasgcode计算之后的下标数据,然后K-V通过链表形式存储,当链表长度大于8时转换为红黑树存储