Java高阶难点面试题(含详细实战答案)_第1页
Java高阶难点面试题(含详细实战答案)_第2页
Java高阶难点面试题(含详细实战答案)_第3页
Java高阶难点面试题(含详细实战答案)_第4页
Java高阶难点面试题(含详细实战答案)_第5页
已阅读5页,还剩3页未读, 继续免费阅读

下载本文档

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

文档简介

Java高阶难点面试题(含详细实战答案)本次面试题筛选企业高频难点,避开基础简答,聚焦底层原理、坑点辨析、实战问题、源码机制、并发高阶、JVM调优,答案贴合一线面试口述逻辑,通俗易懂且专业度足够。一、Java基础高阶难点1、==和equals()的本质区别?为什么重写equals必须重写hashCode?详细答案:第一,核心逻辑不同。==是运算符,对于基本数据类型比较的是数值本身;对于引用类型比较的是对象的内存地址。equals()是Object类的方法,原生默认实现和==一致,也是比较对象地址,但是String、Integer等类重写了该方法,改为比较对象的内容值。第二,重写equals必须重写hashCode的原因:Java规范规定,两个equals相等的对象,hashCode必须相等;但hashCode相等的对象,equals不一定相等。如果只重写equals不重写hashCode,会出现逻辑相等的两个对象,hashCode值不同的情况。在HashMap、HashSet等哈希集合中,会导致两个本该相等的对象被存储为两个不同元素,出现重复数据、查询不到数据的严重bug。补充:如果仅仅重写hashCode不重写equals,会出现hash碰撞增多,但是不会出现数据错乱,只是性能变差。2、String、StringBuilder、StringBuffer底层区别?为什么字符串拼接优先用Builder?详细答案:String底层是final修饰的char数组(JDK9后改为byte数组),属于不可变类。每次字符串拼接、替换都会生成新的String对象,原对象会被丢弃,频繁操作会产生大量无效对象,造成内存浪费、GC频繁。StringBuffer和StringBuilder底层是可变字符数组,默认初始化容量16,扩容时会自动扩容,不会每次创建新对象。两者核心区别是线程安全:StringBuffer所有方法加了synchronized锁,线程安全但性能低;StringBuilder无锁,线程不安全,性能高。日常开发中,绝大多数场景都是单线程操作,所以优先使用StringBuilder。只有多线程并发操作字符串的场景,才使用StringBuffer。额外坑点:循环内绝对不要使用+拼接字符串,编译器的优化只针对单行固定拼接,循环中+会持续创建新对象,性能极差。3、final、finally、finalize三者完全区别?final修饰变量的细节坑点详细答案:三者无任何关联,只是命名相似:1、final是修饰符,可以修饰类、方法、变量。final修饰的类不能被继承;修饰的方法不能被重写;修饰的变量是常量,只能赋值一次。核心坑点:final修饰引用类型变量,只是引用地址不可变,对象内部的属性依然可以修改。比如finalListlist,依然可以add、remove元素,只是不能再newList赋值给list。2、finally是异常处理关键字,搭配try-catch使用,作用是无论代码正常执行还是抛出异常,最终代码都会执行(除了极端情况:JVM退出、线程死亡)。常用于释放资源、关闭流、关闭连接。坑点:finally中如果有return语句,会覆盖try中的return结果,业务中绝对禁止在finally写return。3、finalize()是Object的废弃方法,JDK9开始废弃。原本作用是GC回收对象前触发该方法,用于手动回收资源。但该方法执行时机不确定、可能导致对象复活、严重影响GC效率,生产中完全不使用。二、集合框架高阶难点(面试高频)1、HashMap底层原理?JDK7和JDK8核心差异?为什么线程不安全?详细答案:HashMap基于哈希表实现,底层是数组+链表+红黑树,用于存储键值对,无序、key唯一、允许null键null值。JDK7与JDK8核心区别:1、数据结构:JDK7只有数组+链表;JDK8新增红黑树,链表长度大于8、数组长度大于64时,链表转为红黑树,查询时间复杂度从O(n)优化为O(logn)。2、哈希算法:JDK7做了四次扰动运算;JDK8简化扰动算法,只做一次,效率更高。3、插入方式:JDK7链表头插法;JDK8链表尾插法。4、扩容机制:JDK7扩容后会重新计算所有元素哈希位置;JDK8优化为高位低位拆分,无需全部重算。线程不安全原因:多线程并发put数据时,会出现数据覆盖;JDK7头插法会导致链表闭环,出现死循环CPU100%;JDK8虽然解决了闭环问题,但依然存在数据覆盖、数据丢失问题,并发场景下绝对不能用HashMap。并发替代方案:ConcurrentHashMap。2、ConcurrentHashMap底层原理?JDK7和JDK8的优化点详细答案:ConcurrentHashMap是线程安全的哈希集合,性能远高于Hashtable,Hashtable是全局锁,并发效率极低。JDK7实现:采用分段锁机制,将整个集合分为16个Segment分段,每个分段独立加锁。多线程操作不同分段互不影响,大幅提升并发量,但分段锁粒度依然较粗。JDK8实现(主流):放弃分段锁,采用CAS+synchronized锁数组节点。锁粒度细化到单个数组位置,并发性能大幅提升。核心逻辑:无冲突时通过CAS自旋写入数据;出现哈希冲突时,对当前数组头节点加synchronized锁,保证线程安全。同时保留数组+链表+红黑树结构,保证查询效率。补充:synchronized在JDK1.6后做了大量优化,偏向锁、轻量级锁、重量级锁升级,性能已经非常优秀,所以JDK8放弃分段锁改用节点锁。3、ArrayList为什么线程不安全?多线程场景如何解决?详细答案:ArrayList底层是动态扩容数组,add方法没有加锁,线程不安全核心场景:多线程同时执行add操作。第一,数组越界异常:多个线程同时判断容量足够,同时执行添加,超出数组长度,报ArrayIndexOutOfBoundsException。第二,数据覆盖:多个线程同时在同一个位置赋值,后写入的数据会覆盖先写入的数据,导致数据丢失。解决方案:1、Vector:全局锁,线程安全但性能极差,不推荐。2、Collections.synchronizedList:普通同步包装类,方法级锁,性能一般。3、CopyOnWriteArrayList:写时复制,读无锁、写加锁,读多写少场景首选,性能最优。缺点是写操作会复制新数组,占用内存高,不适合写多场景。三、Java并发编程难点(核心面试重灾区)1、synchronized和Lock的底层区别?什么时候用Lock?详细答案:1、锁类型:synchronized是JVM底层原生锁,隐式锁,自动加锁自动释放;Lock是JDK代码层面实现的显式锁,需要手动lock、unlock。2、锁机制:synchronized经过锁升级,偏向锁->轻量级锁->重量级锁,不可中断;Lock可以实现可中断锁、超时锁、公平锁、读写锁。3、功能粒度:synchronized只能独占锁;Lock有丰富扩展,比如ReentrantLock可重入、可公平,ReadWriteLock读写分离,大幅提升读多场景并发。使用场景选择:简单同步场景、代码块同步,优先用synchronized,简洁无手动释放风险。需要公平锁、锁超时、可中断、读写分离、多条件唤醒的复杂并发场景,必须使用Lock(ReentrantLock)。2、volatile关键字的作用?不能保证原子性的原因?适用场景详细答案:volatile是轻量级同步关键字,有两大核心作用:保证可见性、禁止指令重排,不保证原子性。1、保证可见性:一个线程修改volatile变量,会强制刷新主内存,其他线程会从主内存读取最新值,消除线程工作内存缓存差异。2、禁止指令重排:编译器和CPU不会对volatile修饰的变量做指令重排序,解决并发中的指令乱序问题,典型应用是DCL单例模式。为什么不保证原子性?原子性是指操作不可拆分。比如i++,是读取、自增、写入三步操作。volatile只能保证每次读取的是最新值,但多线程同时自增时,依然会出现覆盖,无法保证三步操作整体原子性。适用场景:状态标记量、开关变量、DCL单例,只做赋值读取,不做复合运算的场景。3、线程池核心参数?为什么不推荐使用Executors创建线程池?详细答案:线程池核心七大参数:核心线程数、最大线程数、空闲超时时间、时间单位、阻塞队列、线程工厂、拒绝策略。执行流程:任务提交后,优先创建核心线程执行→核心线程满了放入阻塞队列→队列满了创建非核心线程→线程数达到最大值触发拒绝策略。禁止使用Executors的原因(阿里规范):1、newFixedThreadPool、newSingleThreadExecutor:阻塞队列是无界队列,任务无限堆积,会导致OOM内存溢出。2、newCachedThreadPool:最大线程数无限大,高并发下会创建大量线程,导致CPU耗尽、系统卡死。生产规范:必须通过ThreadPoolExecutor手动创建线程池,自定义队列大小、最大线程数、拒绝策略,规避风险。四、JVM高阶难点(中高级必问)1、JVM内存区域划分?哪些是线程私有,哪些是共享?详细答案:JVM运行时数据区分为五大区域:线程私有(随线程创建销毁):1、程序计数器:当前线程执行的行号指示器,唯一一个无OOM的区域。2、虚拟机栈:存储局部变量、方法栈帧,方法调用和执行都在这里,栈溢出、栈OOM都出自这里。3、本地方法栈:服务于native本地方法,底层C/C++方法执行区域。线程共享(全局唯一):1、堆内存:所有对象、数组的存储区域,GC主要回收区域,频繁发生GC、OOM。2、方法区(元空间):存储类信息、常量、静态变量、运行时常量池。JDK8彻底移除永久代,改为元空间,直接使用本地内存,不容易OOM。2、GC垃圾回收机制?MinorGC、MajorGC、FullGC区别详细答案:GC核心是回收堆中无引用的无效对象,释放内存。堆分为新生代、老年代。1、MinorGC:新生代垃圾回收,频率高、速度快。新生代对象存活率低,采用复制算法。触发时机:新生代Eden区满了自动触发。2、MajorGC:老年代垃圾回收,频率低、速度慢。一般伴随MinorGC,采用标记整理、标记清除算法。3、FullGC:整堆回收,回收新生代+老年代+元空间,STW时间最长,严重影响系统性能。FullGC常见触发场景:老年代空间不足、方法区空间不足、显式调用System.gc()、MinorGC后晋升失败、并发GC模式下的失败降级。生产调优核心:尽量减少FullGC,避免STW导致接口超时、服务卡顿。3、对象创建与分配流程?对象晋升老年代的条件详细答案:对象创建流程:类加载检查→分配内存(指针碰撞/空闲列表)→初始化零值→设置对象头→执行init方法。对象内存分配优先在Eden区,满足以下条件会晋升老年代:1、年龄达标:对象在新生代熬过15次MinorGC(默认阈值15),晋升老年代。2、动态年龄判定:相同年龄对象总大小超过Survivor区一半,大于等于该年龄的对象全部晋升。3、大对象直接晋升:超过阈值的大对象,直接分配到老年代,避免新生代复制开销。4、MinorGC后存活对象过多,Survivor放不下,直接移入老年代。五、Spring&业务高阶难点1、SpringBean的生命周期?循环依赖如何解决?详细答案:Bean完整生命周期:实例化(创建对象)→populateBean(属性填充、依赖注入)→初始化前置处理→初始化方法(InitializingBean、init-method)→初始化后置处理→放入单例池→销毁。循环依赖问题:两个Bean互相依赖对方,导致无法完成创建。Spring只能解决单例、setter/字段注入的循环依赖,无法解决构造器循环依赖。解决核心:三级缓存一级缓存:完整初始化完成的单例Bean;二级缓存:已实例化但未填充属性的Bean;三级缓存:Bean工厂对象。原理:Bean实例化后提前放入三级缓存,依赖注入时提前暴露半成品对象,打破循环依赖闭环。2、SpringAOP的底层原理?动态代理两种方式区别详细答案:AOP核心是动态代理,在不修改原有代码的前提下,对方法进行增强,用于日志、事务、权限、监控等通用功能。Spring动态代理两种实现:1、JDK动态代理:原生JDK实现,必须基于接口,只能代理接口方法,性能较高。2、CGLIB动态代理:字节码增强,无需接口,通过继承目标类生成子类代理,可代理普通类。Spring选择规则:目标类有接口用JDK代理,无接口用CGLIB。SpringBoot2.x之后默认统一使用CGLIB代理。六、高频坑点面试题1、Integer缓存机制坑点?详细答案:Integer有缓存区间,默认缓存-128~127的整数。该区间内的Integer对象,通过valueOf创建会复用缓存对象,地址相同;超出区间会新建对象,地址不同。坑点1:newInteger(10)永远是新对象,不会走缓存;Integer.valueOf(10)、自动装箱走缓存。坑点2:

温馨提示

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

评论

0/150

提交评论