Java面试题

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的区别

  • List:有序,按对象进入的顺序保存对象,可重复,允许多个null元素对象,可以使用iterator取出所有元素,在逐一遍历,也可以按照get(int index)获取指定下标的元素
  • Set:无序,不可重复,最多允许有一个null元素对象,取元素时只能用iterator接口取得所有对象,在逐一遍历各个元素.

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.可达性算法 这种方式是在内存中,从根对象向下一直找引用,找到的对象就不是垃圾,没找到的对象就是垃圾

你可能感兴趣的:(java,开发语言)