Java基础面试题大总结(3)

简介: Java基础面试题大总结(3)

41、怎么预防死锁?


预防死锁的方式就是打破四个必要条件中的任意一个即可。


1)打破互斥条件:在系统里取消互斥。若资源不被一个进程独占使用,那么死锁是肯定不会发生的。但一般来说在所列的四个条件中,“互斥”条件是无法破坏的。因此,在死锁预防里主要是破坏其他几个必要条件,而不去涉及破坏“互斥”条件。。


2)打破请求和保持条件:1)采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不然就等待。 2)每个进程提出新的资源申请前,必须先释放它先前所占有的资源。


3)打破不可剥夺条件:当进程占有某些资源后又进一步申请其他资源而无法满足,则该进程必须释放它原来占有的资源。


4)打破环路等待条件:实现资源有序分配策略,将系统的所有资源统一编号,所有进程只能采用按序号递增的形式申请资源。


42、为什么要使用线程池?直接new个线程不是很舒服?


如果我们在方法中直接new一个线程来处理,当这个方法被调用频繁时就会创建很多线程,不仅会消耗系统资源,还会降低系统的稳定性,一不小心把系统搞崩了,就可以直接去财务那结帐了。


如果我们合理的使用线程池,则可以避免把系统搞崩的窘境。总得来说,使用线程池可以带来以下几个好处:


降低资源消耗。通过重复利用已创建的线程,降低线程创建和销毁造成的消耗。

提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

增加线程的可管理型。线程是稀缺资源,使用线程池可以进行统一分配,调优和监控。


43、线程池的核心属性有哪些?


threadFactory(线程工厂):用于创建工作线程的工厂。


corePoolSize(核心线程数):当线程池运行的线程少于 corePoolSize 时,将创建一个新线程来处理请求,即使其他工作线程处于空闲状态。


workQueue(队列):用于保留任务并移交给工作线程的阻塞队列。


maximumPoolSize(最大线程数):线程池允许开启的最大线程数。


handler(拒绝策略):往线程池添加任务时,将在下面两种情况触发拒绝策略:1)线程池运行状态不是 RUNNING;2)线程池已经达到最大线程数,并且阻塞队列已满时。


keepAliveTime(保持存活时间):如果线程池当前线程数超过 corePoolSize,则多余的线程空闲时间超过 keepAliveTime 时会被终止。


44、说下线程池的运作流程。


image.png


45、线程池有哪些拒绝策略?


AbortPolicy:中止策略。默认的拒绝策略,直接抛出 RejectedExecutionException。调用者可以捕获这个异常,然后根据需求编写自己的处理代码。


DiscardPolicy:抛弃策略。什么都不做,直接抛弃被拒绝的任务。


DiscardOldestPolicy:抛弃最老策略。抛弃阻塞队列中最老的任务,相当于就是队列中下一个将要被执行的任务,然后重新提交被拒绝的任务。如果阻塞队列是一个优先队列,那么“抛弃最旧的”策略将导致抛弃优先级最高的任务,因此最好不要将该策略和优先级队列放在一起使用。


CallerRunsPolicy:调用者运行策略。在调用者线程中执行该任务。该策略实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将任务回退到调用者(调用线程池执行任务的主线程),由于执行任务需要一定时间,因此主线程至少在一段时间内不能提交任务,从而使得线程池有时间来处理完正在执行的任务。


46、List、Set、Map三者的区别?


List(对付顺序的好帮手): List 接口存储一组不唯一(可以有多个元素引用相同的对象)、有序的对象。


Set(注重独一无二的性质):不允许重复的集合,不会有多个元素引用相同的对象。


Map(用Key来搜索的专业户): 使用键值对存储。Map 会维护与 Key 有关联的值。两个 Key可以引用相同的对象,但 Key 不能重复,典型的 Key 是String类型,但也可以是任何对象。


47、ArrayList 和 LinkedList 的区别。


ArrayList 底层基于动态数组实现,LinkedList 底层基于链表实现。


对于按 index 索引数据(get/set方法):ArrayList 通过 index 直接定位到数组对应位置的节点,而 LinkedList需要从头结点或尾节点开始遍历,直到寻找到目标节点,因此在效率上 ArrayList 优于 LinkedList。


对于随机插入和删除:ArrayList 需要移动目标节点后面的节点(使用System.arraycopy 方法移动节点),而 LinkedList 只需修改目标节点前后节点的 next 或 prev 属性即可,因此在效率上 LinkedList 优于 ArrayList。


对于顺序插入和删除:由于 ArrayList 不需要移动节点,因此在效率上比 LinkedList 更好。这也是为什么在实际使用中 ArrayList 更多,因为大部分情况下我们的使用都是顺序插入。


48、ArrayList 和 Vector 的区别。


Vector 和 ArrayList 几乎一致,唯一的区别是 Vector 在方法上使用了 synchronized 来保证线程安全,因此在性能上 ArrayList 具有更好的表现。


有类似关系的还有:StringBuilder 和 StringBuffer、HashMap 和 Hashtable。


49、介绍下 HashMap 的底层数据结构


我们现在用的都是 JDK 1.8,底层是由“数组+链表+红黑树”组成,如下图,而在 JDK 1.8 之前是由“数组+链表”组成。


50、为什么要改成“数组+链表+红黑树”?


主要是为了提升在 hash 冲突严重时(链表过长)的查找性能,使用链表的查找性能是 O(n),而使用红黑树是 O(logn)。


51、那在什么时候用链表?什么时候用红黑树?


对于插入,默认情况下是使用链表节点。当同一个索引位置的节点在新增后超过8个(阈值8):如果此时数组长度大于等于 64,则会触发链表节点转红黑树节点(treeifyBin);而如果数组长度小于64,则不会触发链表转红黑树,而是会进行扩容,因为此时的数据量还比较小。


对于移除,当同一个索引位置的节点在移除后达到 6 个,并且该索引位置的节点为红黑树节点,会触发红黑树节点转链表节点(untreeify)。


52、HashMap 的默认初始容量是多少?HashMap 的容量有什么限制吗?


默认初始容量是16。HashMap 的容量必须是2的N次方,HashMap 会根据我们传入的容量计算一个大于等于该容量的最小的2的N次方,例如传 9,容量为16。


53、HashMap 的插入流程是怎么样的?


微信图片_20220520132725.png


54、HashMap 的扩容(resize)流程是怎么样的?


微信图片_20220520132744.png


55、除了 HashMap,还用过哪些 Map,在使用时怎么选择?


微信图片_20220520132806.png


56、HashMap 和Hashtable 的区别?


HashMap 允许 key 和 value 为 null,Hashtable 不允许。


HashMap 的默认初始容量为 16,Hashtable 为 11。


HashMap 的扩容为原来的 2 倍,Hashtable 的扩容为原来的 2 倍加 1。


HashMap 是非线程安全的,Hashtable是线程安全的。


HashMap 的 hash 值重新计算过,Hashtable 直接使用 hashCode。


HashMap 去掉了 Hashtable 中的 contains 方法。


HashMap 继承自 AbstractMap 类,Hashtable 继承自 Dictionary 类。


57、Java 内存结构(运行时数据区)


程序计数器:线程私有。一块较小的内存空间,可以看作当前线程所执行的字节码的行号指示器。如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值则为空。


Java虚拟机栈:线程私有。它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。


本地方法栈:线程私有。本地方法栈与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。


Java堆:线程共享。对大多数应用来说,Java堆是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。


方法区:与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息(构造方法、接口定义)、常量、静态变量、即时编译器编译后的代码(字节码)等数据。方法区是JVM规范中定义的一个概念,具体放在哪里,不同的实现可以放在不同的地方。


运行时常量池:运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。


String str = new String(“hello”);

上面的语句中变量 str 放在栈上,用 new 创建出来的字符串对象放在堆上,而"hello"这个字面量是放在堆中。


58、什么是双亲委派模型?


如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。


59、Java虚拟机中有哪些类加载器?


启动类加载器(Bootstrap ClassLoader):


这个类加载器负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。


扩展类加载器(Extension ClassLoader):


这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。


应用程序类加载器(Application ClassLoader):


这个类加载器由sun.misc.Launcher$AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。


自定义类加载器:


用户自定义的类加载器。


60、类加载的过程


类加载的过程包括:加载、验证、准备、解析、初始化,其中验证、准备、解析统称为连接。


加载:通过一个类的全限定名来获取定义此类的二进制字节流,在内存中生成一个代表这个类的java.lang.Class对象。


验证:确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。


准备:为静态变量分配内存并设置静态变量初始值,这里所说的初始值“通常情况”下是数据类型的零值。


解析:将常量池内的符号引用替换为直接引用。


初始化:到了初始化阶段,才真正开始执行类中定义的 Java 初始化程序代码。主要是静态变量赋值动作和静态语句块(static{})中的语句。


61、介绍下垃圾收集机制(在什么时候,对什么,做了什么)?


在什么时候?

在触发GC的时候,具体如下,这里只说常见的 Young GC 和 Full GC。


触发Young GC:当新生代中的 Eden 区没有足够空间进行分配时会触发Young GC。


触发Full GC:


当准备要触发一次Young GC时,如果发现统计数据说之前Young GC的平均晋升大小比目前老年代剩余的空间大,则不会触发Young GC而是转为触发Full GC。(通常情况)

如果有永久代的话,在永久代需要分配空间但已经没有足够空间时,也要触发一次Full GC。

System.gc()默认也是触发Full GC。

heap dump带GC默认也是触发Full GC。

CMS GC时出现Concurrent Mode Failure会导致一次Full GC的产生。

对什么?


对那些JVM认为已经“死掉”的对象。即从GC Root开始搜索,搜索不到的,并且经过一次筛选标记没有复活的对象。


做了什么?


对这些JVM认为已经“死掉”的对象进行垃圾收集,新生代使用复制算法,老年代使用标记-清除和标记-整理算法。


相关文章
|
16天前
|
Java 数据库连接 数据库
spring--为web(1),富士康java面试题整理
spring--为web(1),富士康java面试题整理
|
1天前
|
缓存 安全 Java
【Java面试——并发基础、并发关键字】
随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略: 先进行操作,如果没有其它线程争用共享数据,那操作就成功了,否则采取补偿措施(不断地重试,直到成功为止)。这种乐观的并发策略的许多实现都不需要将线程阻塞,因此这种同步操作称为非阻塞同步。 乐观锁需要操作和冲突检测这两个步骤具备原子性,这里就不能再使用互斥同步来保证了,只能靠硬件来完成。硬件支持的原子性操作最典型的是: 比较并交换(Compare-and-Swap,CAS)。CAS 指令需要有 3 个操作数,分别是内存地址 V、旧的预期值 A 和新值 B。当执行操作时,只有当 V 的值等于 A,才将 V 的值更新为 B。
|
9天前
|
SQL 存储 Java
致远互联java实习生面试
致远互联java实习生面试
29 0
|
9天前
|
Java C++
java面试基础 -- 深克隆 & 浅克隆
java面试基础 -- 深克隆 & 浅克隆
11 1
|
9天前
|
Java
java面试基础 -- 普通类 & 抽象类 & 接口
java面试基础 -- 普通类 & 抽象类 & 接口
14 0
|
9天前
|
存储 安全 Java
java面试基础 -- ArrayList 和 LinkedList有什么区别, ArrayList和Vector呢?
java面试基础 -- ArrayList 和 LinkedList有什么区别, ArrayList和Vector呢?
17 0
|
9天前
|
Java
java面试基础 -- 方法重载 & 方法重写
java面试基础 -- 方法重载 & 方法重写
9 0
|
11天前
|
消息中间件 存储 Java
Java分布式技术面试总结(全面,实时更新)
Java分布式技术面试总结(全面,实时更新)
|
11天前
|
监控 Java Nacos
Java微服务框架面试总结(全面,实时更新)
Java微服务框架面试总结(全面,实时更新)
|
11天前
|
缓存 NoSQL Redis
Java技术栈Redis面试总结(全面,实时更新)
Java技术栈Redis面试总结(全面,实时更新)