高频catch面试题及答案_第1页
高频catch面试题及答案_第2页
高频catch面试题及答案_第3页
高频catch面试题及答案_第4页
高频catch面试题及答案_第5页
已阅读5页,还剩14页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

高频catch面试题及答案Q:equals()和hashCode()的关系是什么?为什么重写equals()时必须重写hashCode()?A:equals()用于判断两个对象是否“逻辑相等”,而hashCode()返回对象的哈希码,主要用于哈希集合(如HashMap、HashSet)的快速查找。二者的约束关系体现在:若两个对象equals()返回true,则它们的hashCode()必须相同;若hashCode()不同,则equals()必然为false(逆否命题)。但hashCode()相同,equals()不一定为true(哈希冲突)。重写equals()时必须重写hashCode()的核心原因是保证哈希集合的正确性。例如,当自定义类作为HashMap的键时,若仅重写equals()而不重写hashCode(),可能出现两个逻辑相等的对象被计算出不同的哈希码,导致它们被存储在哈希表的不同桶中。此时调用containsKey()会返回false(因为哈希表根据hashCode()定位桶,再遍历桶内元素用equals()比较),破坏了集合的一致性。Q:String为什么是不可变的?这种设计有什么好处?A:String的不可变性由底层实现保证:JDK8及之前,String内部通过final修饰的char[]数组存储字符;JDK9改为byte[]数组(支持LATIN1和UTF16编码),同样用final修饰。此外,String类本身被final修饰,无法被继承,避免子类修改内部状态。不可变性的设计优势包括:1.线程安全:多个线程可共享同一个String实例,无需额外同步机制。2.哈希缓存:String的hashCode()采用懒加载缓存(首次计算后存储),不可变性保证了哈希值不会改变,适合作为哈希集合的键。3.字符串常量池优化:JVM通过常量池复用相同字符串,节省内存。若String可变,修改一个实例会影响所有引用它的地方,导致常量池无法安全复用。4.安全性:例如作为网络连接的URL、数据库连接的参数时,不可变性避免意外修改导致安全漏洞。Q:Java异常分为哪几类?throw和throws的区别是什么?A:Java异常体系的根是Throwable,分为Error(JVM无法处理的严重问题,如OutOfMemoryError)和Exception(程序可处理的异常)。Exception又分为RuntimeException(非检查型异常,如NullPointerException)和受检异常(CheckedException,如IOException)。throw和throws的区别:throw用于方法内部,主动抛出一个异常实例(如thrownewIllegalArgumentException()),结束当前流程。throws用于方法声明,标识该方法可能抛出的异常类型(如publicvoidread()throwsIOException),由调用者处理。注意:throw抛出的若是受检异常,必须在方法签名用throws声明或直接捕获;若为非检查型异常(如RuntimeException),可不声明。Q:HashMap的底层数据结构是什么?JDK7到JDK8有哪些主要优化?A:HashMap的底层是“数组+链表/红黑树”的复合结构。数组作为哈希表的桶(bucket),每个桶存储链表或红黑树节点。JDK7到JDK8的核心优化:1.数据结构:JDK7使用“数组+链表”,JDK8当链表长度≥8且数组容量≥64时,链表转换为红黑树(退化条件:红黑树节点数≤6时转回链表),解决链表过长时查找O(n)的性能问题。2.哈希计算:JDK7的hash()方法通过4次位运算+异或减少哈希冲突;JDK8简化为(h=key.hashCode())^(h>>>16),将高位信息与低位异或,降低碰撞概率。3.扩容机制:JDK7扩容时遍历原链表,重新计算哈希并插入新数组(头插法,多线程下可能导致链表成环);JDK8采用尾插法,扩容时根据(e.hash&oldCap)是否为0,将节点分配到原位置或原位置+oldCap的新位置,避免逆序和环的问题。4.空值处理:JDK7允许key为null(存储在0号桶),JDK8保持该特性,但ConcurrentHashMap不再允许null键值。Q:ConcurrentHashMap如何保证线程安全?JDK7和JDK8的实现有何不同?A:ConcurrentHashMap通过分段锁(JDK7)或CAS+synchronized(JDK8)实现线程安全,支持多线程并发读写。JDK7的实现:采用Segment数组(继承ReentrantLock),每个Segment对应一个子哈希表,默认16个Segment(并发度16)。写操作(put、remove)需锁定对应的Segment,不同Segment可并行操作;读操作(get)无需加锁(通过volatile保证可见性)。JDK8的优化:取消Segment分段锁,改为“数组+链表/红黑树”的结构,直接对桶的头节点加synchronized锁(锁粒度更小)。插入节点时使用CAS(Compare-And-Swap)尝试无锁插入,失败后再加锁,减少锁竞争。size()方法通过CAS统计baseCount和CounterCell数组(解决多线程计数的ABA问题),避免全量加锁。优势对比:JDK8的锁粒度从Segment(约1/16数组长度)细化到单个桶,并发性能更高;synchronized经优化(偏向锁、轻量级锁)后,比ReentrantLock更轻量;红黑树结构提升了冲突时的查找效率。Q:ArrayList和LinkedList的区别是什么?如何选择?A:核心区别体现在数据结构和操作复杂度:数据结构:ArrayList基于动态数组(Object[]elementData),支持随机访问(O(1)时间);LinkedList基于双向链表(Node<E>first/last),节点包含prev、next和item。增删操作:ArrayList尾部添加(O(1)均摊时间),但中间插入/删除需移动元素(O(n)时间);LinkedList插入/删除任意位置只需调整前后指针(O(1)时间,但需先遍历定位节点,故实际为O(n)时间)。内存占用:ArrayList有预分配空间(可能浪费);LinkedList每个节点需额外存储prev和next指针(空间开销更大)。选择建议:频繁随机访问(如遍历、按索引查询)选ArrayList;频繁中间插入/删除(如队列、栈)且数据量不大时选LinkedList;数据量极大时,ArrayList的扩容(1.5倍)可能比LinkedList的指针开销更高效。Q:volatile的作用是什么?如何保证可见性和禁止指令重排序?A:volatile是轻量级同步机制,主要有两大作用:1.保证可见性:被volatile修饰的变量,其修改对所有线程立即可见。JVM通过在写操作后插入Store屏障,强制将变量值刷新到主内存;读操作前插入Load屏障,强制从主内存读取最新值,避免线程使用本地缓存的旧值。2.禁止指令重排序:通过内存屏障限制编译器和CPU的重排序优化。JVM定义了happens-before规则,volatile变量的写操作happens-before后续对该变量的读操作,确保代码逻辑顺序与执行顺序一致。典型应用场景:状态标志(如线程终止标志位booleanisRunning=false;被volatile修饰后,修改后其他线程能立即感知);单例模式的双重检查锁定(DCL),防止指令重排序导致其他线程获取到未初始化的实例(如volatileSingletoninstance;)。Q:synchronized的底层实现是什么?JVM做了哪些优化?A:synchronized的底层通过JVM的Monitor(监视器锁)实现。Java对象头(MarkWord)中存储了锁状态信息,包括偏向线程ID、锁标志位等。JVM对synchronized的优化(锁升级过程):1.偏向锁:线程首次获取锁时,MarkWord记录该线程ID,后续无竞争时无需加锁(只需比较线程ID)。适用于单线程重复获取同一锁的场景。2.轻量级锁:当其他线程尝试获取锁(竞争出现),偏向锁升级为轻量级锁。当前线程将MarkWord复制到栈帧的锁记录(LockRecord),并尝试用CAS将对象头指向锁记录。若成功则持有锁;若失败(说明存在竞争),升级为重量级锁。3.重量级锁:依赖操作系统的互斥量(Mutex),线程竞争时进入阻塞状态(需内核态切换),性能开销大。优化意义:通过锁升级策略,减少无竞争或轻度竞争时的性能损耗,平衡了安全性和效率。Q:线程池的核心参数有哪些?拒绝策略有哪几种?A:线程池的核心参数(ThreadPoolExecutor构造参数):corePoolSize:核心线程数(即使空闲也不会被销毁,除非设置allowCoreThreadTimeOut为true)。maximumPoolSize:最大线程数(核心线程+临时线程的上限)。keepAliveTime:临时线程的空闲存活时间(超过此时间则销毁)。unit:keepAliveTime的时间单位(如TimeUnit.SECONDS)。workQueue:任务队列(存储待执行的Runnable任务),常见类型有ArrayBlockingQueue(有界)、LinkedBlockingQueue(无界/有界)、SynchronousQueue(直接提交)、PriorityBlockingQueue(优先队列)。threadFactory:线程工厂(用于创建线程,可自定义命名规则)。handler:拒绝策略(当任务队列满且线程数达到maximumPoolSize时,处理新任务的方式)。常见拒绝策略:1.AbortPolicy(默认):抛出RejectedExecutionException异常。2.CallerRunsPolicy:由调用者线程直接执行任务(减缓任务提交速度)。3.DiscardPolicy:静默丢弃新任务(无提示)。4.DiscardOldestPolicy:丢弃队列中最老的任务,尝试重新提交当前任务。Q:JVM的内存区域是如何划分的?哪些区域会发生OOM?A:JVM内存区域分为线程共享和线程私有两部分:线程共享区域:堆(Heap):存储对象实例和数组,是GC的主要区域。可通过-Xms(初始大小)和-Xmx(最大大小)设置。OOM常见场景:对象无法被回收且堆空间耗尽(java.lang.OutOfMemoryError:Javaheapspace)。方法区(MethodArea):存储类信息、常量、静态变量、即时编译后的代码等。JDK7前是“永久代”(PermGen),依赖堆内存;JDK8改为“元空间”(Metaspace),使用本地内存。OOM场景:类加载过多(如动态提供大量Class对象,java.lang.OutOfMemoryError:Metaspace)。线程私有区域:程序计数器(ProgramCounterRegister):记录当前线程执行的字节码行号,唯一无OOM的区域。虚拟机栈(VMStack):存储栈帧(局部变量表、操作数栈、动态链接、方法出口等)。OOM场景:线程请求的栈深度超过最大值(java.lang.StackOverflowError);或扩展栈时无法申请到足够内存(OOM,较少见)。本地方法栈(NativeMethodStack):为本地方法(如C语言实现的方法)服务,与虚拟机栈类似,可能抛出StackOverflowError或OOM。Q:垃圾回收的常见算法有哪些?CMS和G1收集器的区别是什么?A:垃圾回收算法的核心是标记-清除(Mark-Sweep)、标记-复制(Mark-Copy)、标记-整理(Mark-Compact)。标记-清除:标记存活对象,清除未标记对象。缺点是产生内存碎片,可能导致大对象无法分配。标记-复制:将内存分为大小相等的两块,标记存活对象后复制到另一块,清空原块。缺点是可用内存减半(适用于存活对象少的场景,如新生代)。标记-整理:标记存活对象后,将其向一端移动并整理,避免碎片(适用于存活对象多的场景,如老年代)。CMS(ConcurrentMarkSweep)和G1(Garbage-First)的区别:1.目标场景:CMS关注低延迟(老年代收集),适用于互联网高响应场景;G1面向大内存(≥4GB),将堆划分为多个Region(动态管理),兼顾吞吐量和延迟。2.算法差异:CMS基于标记-清除(可能产生碎片,需担保机制);G1基于标记-整理+复制(Region间复制,减少碎片)。3.并发阶段:CMS的并发标记和并发清除可能与用户线程竞争CPU;G1的并发阶段更细粒度(如初始标记、并发标记、最终标记、筛选回收),通过RememberedSet(RSet)记录跨Region引用,降低停顿时间。4.停顿控制:CMS无法严格控制停顿时间;G1通过“停顿时间目标”(如200ms)动态调整回收的Region数量,更适合实时性要求高的场景。Q:类加载的过程分为哪几个阶段?双亲委派模型的作用是什么?A:类加载过程分为加载、链接(验证、准备、解析)、初始化三个阶段:加载:通过类加载器将.class文件的字节码加载到内存,提供Class对象。链接:验证:检查字节码的正确性(如文件格式、元数据、字节码、符号引用验证),防止恶意修改。准备:为类静态变量分配内存并设置初始值(如int类型初始为0,引用类型为null)。解析:将符号引用(如字符串形式的类名)替换为直接引用(内存地址)。初始化:执行类构造器<clinit>()方法(静态变量赋值和静态代码块的顺序执行),是类加载的最后一步。双亲委派模型的核心是:类加载器收到加载请求时,先委托给父类加载器(递归),父类无法加载时再自己加载。作用包括:避免类重复加载:父类已加载的类,子类无需重复加载。保证核心类的安全:如java.lang.Object由启动类加载器加载,防止用户自定义同名类覆盖标准库(沙箱安全机制)。Q:Spring的依赖注入(DI)有哪几种方式?循环依赖如何解决?A:依赖注入的常见方式:构造器注入:通过构造方法传入依赖(强制依赖,避免对象不完整)。Setter注入:通过setter方法设置依赖(可选依赖,适合可变配置)。字段注入(@Autowired直接标记字段):简洁但可能隐藏依赖关系(Spring推荐构造器或setter注入)。Spring解决循环依赖的核心是“三级缓存”:1.一级缓存(singletonObjects):存储已初始化完成的单例Bean。2.二级缓存(earlySingletonObjects):存储已实例化但未初始化的早期Bean(解决AOP代理问题)。3.三级缓存(singletonFactories):存储ObjectFactory(用于提供早期Bean的工厂,支持通过SmartInstantiationAwareBeanPostProcessor提供代理对象)。流程示例(A→B→A循环依赖):创建A时,实例化A(构造器调用),将A的ObjectFactory(lambda表达式:()->getEarlyBeanReference(beanName,mbd,bean))存入三级缓存。A需要注入B,触发B的创建流程:实例化B,B的ObjectFactory存入三级缓存;B需要注入A,从三级缓存获取A的ObjectFactory,提供早期A(可能是代理),存入二级缓存。B完成属性注入和初始化,存入一级缓存。A获取B(已在一级缓存),完成属性注入和初始化,存入一级缓存,同时清除二、三级缓存中的A。注意:仅支持单例Bean的循环依赖,原型(prototype)或作用域Bean无法解决(每次创建新实例,无法缓存)。Q:SpringAOP的实现方式有哪些?切点(Pointcut)和通知(Advice)的关系是什么?A:SpringAOP基于动态代理实现,支持两种方式:JDK动态代理:通过java.lang.reflect.Proxy提供代理对象,只能代理接口(目标类必须实现接口)。CGLIB代理:通过ASM修改字节码提供子类代理,可代理无接口的类(目标类不能是final)。切点(Pointcut)定义了“在哪里织入增强”(匹配类/方法),通过AspectJ表达式(如execution(com.example.service..(..)))指定目标方法。通知(Advice)定义了“何时织入增强”(前置、后置、环绕、异常、返回通知)。二者关系:切点确定目标方法集合,通知确定在这些方法的生命周期中执行增强逻辑的时机。Q:SpringBean的生命周期包含哪些阶段?A:Bean的生命周期可分为以下阶段(以单例Bean为例):1.实例化:通过构造器或工厂方法创建Bean实例(如通过AutowiredAnnotationBeanPostProcessor处理构造器注入)。2.属性注入:通过setter或字段注入依赖(如AutowiredAn

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论