1.有没有有顺序的Map实现类,如果有,他们是怎么保证有序的?
TreeMap和LinkedHashmap都是有序的。(TreeMap默认是key升序(字典排序)),LinkedHashmap默认是数据插入顺序)
TreeMap是基于比较器Comparator来实现有序的(内部结构为红黑树)。
LinkedHashmap是基于链表来实现数据插入有序的。
2.HashMap什么时候扩容,每次扩容多少,为什么?
扩容时机:容量达到当前最大容器 * 负载因子(初始默认容量16,负载因子0.75)
扩容大小:每次扩容一倍
为什么扩容一倍:1.容量为2的n次方时,可以减少hash碰撞 2.计算位置时通过位运算,而不是取模可以使效率提高,源码如下:
e.hash & (newCap - 1)
3.JAVA8的ConcurrentHashMap为什么放弃了分段锁,有什么问题吗?JAVA8怎么处理的?
问题:
1.加入多个分段锁浪费内存空间
2.生产环境中, map 在放入时竞争同一个锁的概率非常小,分段锁反而会造成更新等操作的长时间等待。
3.为了提高 GC 的效率
java8处理:
采用CAS+volatile 来代替锁保证原子性,不过其中部分代码段还是加上了Synchronized(说明Synchroniezd的效率已经高于Lock锁了)。
4.IO模型有哪些,讲讲你理解的nio ,他和bio,aio的区别是啥,谈谈reactor模型。
JAVA BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程并处理,如果这个连接不做任何事情会造成不必要的开销,当然可以通过线程池机制改善。
JAVA NIO:同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求时才启动一个线程进行处理。
JAVA AIO(NIO2):异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
reactor模型自行百度。
5.反射的原理,反射创建类实例的三种方式是什么
反射机制指的是程序在运行时能够动态获取自身的信息。在java中,只要给定类的名字, 那么就可以通过反射机制来获得类的所有信息。
反射机制获取class的方法:
1使用 Class.forName :Class1=Class.forName("user")
2.使用类的 .class 方法:Class2=User.class //每个类型都有class属性
3.使用类对象的 getClass() 方法:User user=new User(); Class3=user.getClass() //每个Java对象都有getClass方法。
反射创建对象的两种方式:
1.调用默认构造函数(无参):
Class<?> forName=Class.forName("com.test.entity.user");
Object newInstance=forName.newInstance();
2.调用有带参数的构造函数的类,先获取到其构造对象,再通过该构造方法类获取实例:
Class<?> forName=Class.forName("com.test.entity.user");
Constructor<?> constructor=forName.getConstructor(String.class,String.class); ///获取构造函数类的对象
User user=(User)constructor.newInstance("test","test");
6.反射中,Class.forName和ClassLoader区别
在java中Class.forName()和ClassLoader都可以对类进行加载。ClassLoader就是遵循双亲委派模型最终调用启动类加载器的类加载器,实现的功能是“通过一个类的全限定名来获取描述此类的二进制字节流”,获取到二进制流后放到JVM中。Class.forName()方法实际上也是调用的CLassLoader来实现的。不过Class.forName加载类时将类进了初始化,而ClassLoader的loadClass并没有对类进行初始化,只是把类加载到了虚拟机中。
7.动态代理与cglib实现的区别
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
8.java8的常用新特性
lambda表达式 stream流(推荐使用,在对集合的数据处理时,使用特别方便)
9.jvm相关
内存结构 : 堆 元空间(方法区) java虚拟机栈 本地方法栈 程序计数器
内存模型 : 主内存 ,工作内存(本地内存) 等概念。
怎么判断对象(引用)是否存活: 引用计数法(淘汰---不能解决循环依赖) , 根搜索算法(可达性分析算法)
常用垃圾回收算法 :标记清除(老年代,产生大量内存碎片,效率不高),标记整理(老年代),复制算法(新生带)
一次完整的GC流程分析
java类加载机制--双亲委派模型(如何打破双亲委派模型--- 自定义类加载器重写loadClass方法;使用线程上下文类加载器)
垃圾收集器: 掌握cms g1即可。(常问cms和g1的阶段以及stw发生在那些阶段, g1的筛选回收为什么回收的时间可以让用户 设定。)
另外可以了解下: 逃逸分析,本地线程分配缓冲,空间分配担保机制,动态年龄判断等概念。
10.多线程方面
怎么创建线程---继承Thread 实现Runnable 或者Callable ,线程池
线程池的流程,核心参数(几乎必问)---为什么使用newCachedThreadPool这些线程池创建线程会发生内存溢出?
怎么关闭线程池:
shutdown()
调用后,不可以再 submit 新的 task,已经 submit 的将继续执行 shutdownNow()
调用后,试图停止当前正在执行的 task,并返回尚未执行的 task 的 list
ThreadLocal的原理分析(几乎必问)-- 注意不使用时需要remove,不然会造成内存泄漏
Synchronized的底层实现原理以及锁的升级 参考这篇文章(真的不错 ):
https://blog.csdn.net/tongdanping/article/details/79647337
volatile的作用:保证线程见的可见性,禁止重排序(实现原理--直接读写主内存)
CAS的实现原理,如何解决ABA问题
线程死锁的解决方式 : 长时间获取不到锁,放弃操作并且释放自己占有的锁;一次性申请所需的锁资源,按一定顺序申请锁资 源。
原子类的实现原理 --CAS
countdowlatch和cyclicbarrier的内部原理和用法,以及相互之间的差别:countdownlatch表示线程到达一个点把一个变量减一,子线程并不会阻塞,继续运行,主要就是countDown和await两个方法,调用await方法的线程被阻塞直到变量减到0,cyclibarrier表示线程到达一个点后,阻塞当前线程直到这组线程的其他线程也到达这个点。
countdowlatch 不能重复使用,一次可以唤醒多个线程 ; cyclibarrier可以重复使用,一次只能唤醒一个线程。
11.spring框架相关问题
一个接口被多个类实现,怎么注入:@Resource @Qualifier+@Autowired
springmvc的处理流程
AOP概念,实现原理: 动态代理(@Aspect注解实现,了解其中的通知类型--前置通知,环绕通知,后置通知,异常通知, 最终通知)
spring事务的传播机(七种),数据库隔离级别(4种)
spring事务@transactional失效的原因:
1.数据库引擎是否支持事务(Mysql的MyIsam引擎就不支持事务)
2.注解所在的类是否注入spring容器中
3.注解所在方法是否为public修饰
4.所用数据源是否加载了事务管理器
5.是否发生了方法的自调用(同一个类中的A方法调用B方法)
6.当方法发生异常时,使用try catch捕获了异常,并且catch中没有抛出异常或者手动回滚。
spring bean的生命周期。
12.数据库相关
mysql和orcale的分页查询 : mysql:limit orcale: rownum
索引的分类:普通索引,唯一索引,主键索引,全文索引,组合索引
索引的实现原理:B+树
怎么对数据库进行优化:表结构优化,sql语句优化
怎么对sql语句进行分析: explain(执行计划)
数据库乐观锁,悲观锁实现:乐观锁:版本号 悲观锁:共享锁(LOCK IN SHARE MODE) 排他锁(FOR UPDATE)
数据库中的脏读,重复读,幻读对应的解决隔离级别
mysql中in 和exists 区别
1.IN查询在内部表和外部表上都可以使用到索引。
2.Exists查询仅在内部表上可以使用到索引。
3.当子查询结果集很大,而外部表较小的时候,Exists的Block Nested Loop(Block 嵌套循环)的作用开始显现,并弥补外部表无法用到索引的缺陷,查询效率会优于IN。
4.当子查询结果集较小,而外部表很大的时候,Exists的Block嵌套循环优化效果不明显,IN 的外表索引优势占主要作用,此时IN的查询效率会优于Exists。
5.表的规模不是看内部表和外部表,而是外部表和子查询结果集。
13.redis相关
redis中的数据结构 :string list hash set zset
redis中键的淘汰策略:
noeviction: 不删除策略, 达到最大内存限制时, 如果需要更多内存, 直接返回错误信息。 大多数写命令都会导致占用更多的内存(有极少数会例外, 如 DEL )。
allkeys-lru: 所有key通用; 优先删除最近最少使用(less recently used ,LRU) 的 key。
volatile-lru: 只限于设置了 expire 的部分; 优先删除最近最少使用(less recently used ,LRU) 的 key。
allkeys-random: 所有key通用; 随机删除一部分 key。
volatile-random: 只限于设置了 expire 的部分; 随机删除一部分 key。
volatile-ttl: 只限于设置了 expire 的部分; 优先删除剩余时间(time to live,TTL) 短的key。
redis持久化机制:
aof:实时对数据进行持久化,效率不高,AOF文件比RDB文件大,且恢复速度慢。
rdb:m时间n个key发生了改变才对数据进行一次记录,采用单独的子进程进行持久化。只有一个文件dump.rdb,方便持久 化。
redis内存设置: maxmemory
redis实现分布式锁:先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。
redis缓存穿透解决方案:
1.查询数据库没有找到值的时候,直接返回null,然后将null或者特定的字符写入redis缓存中。缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。
2.网关判定查询条件的规则 ,不符合规则的查询条件直接返回null。
3.布隆过滤器 :布隆过滤器是一种非常高效的数据结构,把所有数据库的value对应的key 存储到布隆过滤器里,几乎不消耗什么空间,而且查询也是相当的快!但是请注意,它只能判断 key 是否存在(判断一定不存在,可能存在)。
Redis 的并发竞争问题如何解决,了解 Redis 事务的 CAS 操作吗
Redis为单进程单线程模式,采用队列模式将并发访问变为串行访问。Redis本身没有锁的概念,Redis对于多个客户端连接并不存在竞争,但是在Jedis客户端对Redis进行并发访问时会发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题,这些问题均是由于客户端连接混乱造成。对此有2种解决方法:
1.客户端角度,为保证每个客户端间正常有序与Redis进行通信,对连接进行池化,同时对客户端读写Redis操作采用内部锁synchronized。
2.服务器角度,利用setnx实现锁。
MULTI,EXEC,DISCARD,WATCH 四个命令是 Redis 事务的四个基础命令。其中:
MULTI,告诉 Redis 服务器开启一个事务。注意,只是开启,而不是执行
EXEC,告诉 Redis 开始执行事务
DISCARD,告诉 Redis 取消事务
WATCH,监视某一个键值对,它的作用是在事务执行之前如果监视的键值被修改,事务会被取消。 可以利用watch实现cas乐观锁
14.跨域问题的解决方案
jsonp解决跨域(不推荐,只支持get)
设置响应头允许跨域:response.setHeader("Access-Control-Allow-Origin", "*");
使用HttpClient进行转发实现跨域
SpringBoot设置corsFilter实现跨域:
搭建nginx api接口网关实现跨域
15.应用集群时,怎么防止job重复执行
使用分布式任务调度中心(xxl-job)
总结:
因为我19年才毕业,面试问的问题都比较简单,总的来说如下方面几乎必问:
数据结构(hashmap concurrenthashmap) 多线程(线程创建方式, 线程池原理,ThradLocal原理,volatile,CAS,锁)
数据库(分页,索引,优化) jvm(内存结构,判断对象是否存活,垃圾回收算法,gc流程,垃圾收集器(cms,g1))
框架 (aop ,mvc流程,事务) redis(数据结构,持久化机制 )