1.java中有哪几种方式来创建线程
1.继承Thread类,重写run方法(继承的单一性,无返回值,无异常抛出)
2.实现Runnable接口,重写run方法(实现可以采用多实现,但是也无返回值,无异常抛出)
3.实现Callable接口,配合FutureTask类(这种方式存在返回值,有异常抛出)
4.线程池的方式创建
定义七大核心参数 :1.核心线程数 2.最大线程数3.线程工厂 4.线程达到核心线程数的等待时间 5.时间单位 6.阻塞队列 7.拒绝策略
2.spingboot的自动配置原理
1.可以自动检测工程中spring.factories文件中的自动配置类加载出来,并且可以通过添加依赖的方式更换配置,在其中加载做了去重和过滤处理,去重采用将list转换为set再转为list方式,过滤采用一次加载配置文件的方式判断在工程中是否存在.
3.为什么不建议采用Executors来创建线程池
主要是因为在底层创建的时候采用的是无界的阻塞队列,在你的线程数比较少的时候,可能会造成耗尽内存.
4.线程池有哪几种状态
新建 停止(不会处理队列中任务) shutdown(会处理队列中任务)tidying terminated
5.sychronized和ReentrantLock有什么区别
sychronized | ReentrantLock |
java中的关键字 | jdk中的类 |
自动加锁和释放锁 | 手动加锁和释放锁 |
JVM层面的锁 | Api层面的锁 |
非公平锁 | 公平锁或非公平锁 |
锁的是对象,锁信息保存在对象头中 | int类型或者state状态标识锁的状态 |
锁底层有锁升级过程 | 锁底层没有锁升级过程 |
6.ThreadLocal有哪些应用场景,它底层是如何实现的?
ThreadLocal是java底层提供的线程本地存储机制,可以利用该机制将数据缓存在某个线程内部,该线程可以在任意时刻丶任意方法中获取缓存的数据
ThreadLocal底层是通过ThreadLocalMap来实现的,每个Thread对象(注意不是ThreadLocal对象)中都存在一个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的值
7.List和Set的区别
8.Arraylist和LinkedList区别
1.首先,底层的数据结构是不同的,ArrayList是基于数组实现的,LinkedList是基于双向链表实现的
2.由于底层数据结构不同,他们所适用的场景也不同,ArrayList更适合随机查找,LinkedList更适合删除和添加
3.另外ArrayList和LinkedList都实现了List接口,但是LinkedList还额外实现了Deque接口,所以LinkedList还可以当做队列来使用
9.谈谈ConcurrentHashMap的扩容机制
1.7版本
1. 1.7版本的ConcurrentHashMap是基于Segment分段实现的
2. 每个Segment相对一个小型的HashMap
3. 每个Segment内部会进行扩容,和HashMap的扩容逻辑类似
4. 先生成新的数组,然后转移元素到新数组中
5. 扩容的判断也是每个Segment内部单独判断的,判断是否超过阈值
1.8版本
1. 1.8版本的ConcurrentHashMap不再基于Segment实现
2. 当某个线程进行cpu时,如果发现ConcurrentHashMap正在扩容那么该线程一起进行扩容
3. 如果某个线程put时,发现没有正在进行扩容,则将key-value添加到ConcurrentHashmap中,然后判断是否超过阈值,超过了则进行扩容
4. ConcurrentHashMap是支持多个线程同时扩容的
5. 扩容之前也先生成一个新的数组
6. 在转移元素时,先将原数组分组,将分组分给不同的线程来进行元素的转移,每个线程负责一组或多组元素转移工作
10.jdk1.7到jdk1.8HashMap发生了什么变化
1. 1.7底层是数组+链表,1.8中底层是数组+链表+红黑树,加红黑树的目的是提高HashMap插入和查询整体效率
2. 1.7中链表插入使用的是头插法,1.8中链表插入使用的尾插法,因为1.8中插入key和value时需要判断链表元素个数,所以需要遍历链表统计表元素个数,所以正好就直接使用尾插法
3. 1.7中哈希算法比较复杂,存在各种右移与异或运算,1.8中进行了简化,因为复杂的哈希算法目的性就是提高散列性,来提供Hashmap的整体效率,而1.8中新增了红黑树,所以适当地简化了哈希算法,节省cpu资源
11.说一下hashmap的put方法
1.根据key通过哈希算法与运算得出数组下标
2.如果数组下标位置为空,则将key和value封装为Entry对象(JDK1.7中是Entry对象,JDK1.8中是Node对象)并放入该位置
3.如果数组下标位置不为空,则要分情况讨论
3.1.a 如果是JDK1.7,则先按断是否需要扩容,如果扩容就进行扩容,如果不用扩容就生成Entry对象,并使用头插法放入当前位置的链表中
3.1.b 如果是JDK1.8 ,则先回判断当前位置的Node类型,看是红黑树Node还是链表Node
3.1.b1 如果是红黑树Node,则将key和value封装为一个红黑树节点并添加到红黑树中去,在这个过程中会判断红黑树中是否存在当前key,如果存在则更新value
3.1.b2 如果此位置上的Node对象是Node节点,则将Node和value封装为一个链表Node并通过尾插法插入到链表的最后位置去,因为是尾插法,所以需要遍历链表,在遍历链表的过程中会判断是否存在当前key,如果存在则更新value,当遍历完成链表后,将新链表Node插入到链表后,会看当前链表的节点个数,如果大于8,则将该链表转换为红黑树
3.1.b3 将key和value封装为Node插入到链表或红黑树中后,再判断是否需要扩容,如果不需要就结束put方法
12.HashMap的扩容机制原理
1.7版本
1.先生成数组
2.遍历老数组中每个位置上的链表上的每个元素
3.取每个元素的key,并基于新数组长度,计算出每个元素在新数组中的下标
4.将所有的元素添加到数组中去
5.所有元素转移完了之后,将新数组赋值给HashMap对象的table属性
1.8版本
1.先生成数组
2..遍历老数组中每个位置上的链表或红黑树
3.如果是链表,则直接将链表中的每个元素从新计算下标,并添加到新数组中去
4.如果是红黑树,则先遍历红黑树,先计算出红黑树中每个元素对应在新数组的对应的下标位置
统计每个下标位置的元素个数
如果该位置下的元素个数超过了8,则生成一个新的红黑树,并将根节点的添加到新数组的对应位置
如果该位置下的元素没有超过8,那么则生成一个链表,并将链表的头节点添加到新数组的对应位置
5.所有的元素转移完了之后,将新数组赋值给HashMap对象的table属性
13.Java中有那些类加载器
启动类加载器 BootStapClassLoader是ExtClassLoader的父类加载器,默认负责加载%JAVA_HOME%lib下的jar包和class文件
扩展类加载器 ExtClassLoader是AppClassLoader的父类加载器,负责加载%JAVA_HOME%lib/ext文件夹下的jar包和class类
自定义加载器是自定义加载器的父类,负责加载classpath下的类文件
14.说一说双亲委派模型
JVM中存在三个默认的类加载器 向上加载 向下委派
15.JVM中有哪些线程共享区
方法区 类的信息
堆 类所产生的各个对象
虚拟机栈
本地方法栈
程序计数器
16.你们项目中如何排查JVM问题
对于还在正常运行的系统
可以使用jmap来查看JVM中各个区域的使用情况
可以通过jstack来查看线程的运行情况,比如那些线程阻塞,是否出现了死锁
可以通过jstact命令查看垃圾回收的情况,特别是fullgc,如果发生fullgc比较频繁,那么就得调优了
通过各个命令的结果,或者jvisualvm等工具来进行分析
首先,初步猜测频繁发送fullgc的原因,如果频繁发生fullgc但是又一直没有出现内存溢出,那么表示fullgc实际上是回收了很多对象,所以这些对象最好能在younggc过程中就直接回收掉,避免这些对象进入老年代,对于这种情况,就要考虑这些存活时间不长的对象是不是比较大,导致年轻代放不下,直接进入到了老年代,尝试加大年轻代的大小,如果改完之后,full减少,则证明修改有效
同时,还可以找到占用cpu最多的线程,定位到具体的方法,优化这个方法的执行,看是否能避免某些对象的创建,从而节省内存
对于已经发生了OOM的系统
一般生产系统中都会设置当系统发生了OOM时,生成当时的dump文件
我们可以利用jsisualvm等工具来分析dump文件
根据dump文件找到异常的实例对象,和异常的线程(占用cpu高),定位到具体的代码
然后再进行详细的分析和调试
17.一个对象从加载到JVM,再到被GC清除,都经历了什么过程?
1.首先把字节码文件内容加载到方法区
2.然后再根据类信息在堆区创建对象
3.对象首先会分配在堆中的Eden区,经过一次Minor GC后,对象如果存活,就会进入Suvivor区,在后续的每次Minor GC中,如果对象一直存活,就会在Suvivor区来回拷贝,每移动一次,年龄加一
4.当年龄超过15后,对象依然存活,对象就会进入老年代
5.如果经过Full GC,被标记为垃圾对象,那么就会被GC线程清理掉
18.怎么确定一个对象到底是不是垃圾
1.引用计数算法 这种方式是给堆内存中的每个对象记录一个引用个数.引用个数为0的就认为是垃圾.这是早期JDK中使用的方式.引用计数无法解决循环引用的问题
2.可达性算法 这种方式是在内存中,从根对象向下一直找引用,找到的对象就不是垃圾,没找到的对象就是垃圾