当前位置: 首页 > news >正文

Java面试问题记录(三)

一、JDK源码

Hashmap

1、HashMap的数据结构(1.7、1.8的区别)

在1.8以前,hashmap是链表+数组的结构,1.8之后是链表+数组/红黑树的结构。

2、HashMap的实现原理

当一个key存入hashmap时,先计算key的哈希值,这个值就是key在数组中的位置,如果数组中该位置已经有别的key了,那么会创建一个链表,将这个key放入链表中;1.8之后,如果链表的长度超过8,那么链表会自动转换为红黑树。

3、HashMap扩容为什么是2^n-1

1)利用位运算代替取模,提高运算效率

2)保证哈希值的低位参与运算,减少哈希冲突

3)简化扩容时索引重计算逻辑

总结一下就是说让哈希值分布更均匀,减少哈希冲突。

4、HashMap是线程安全的吗

不是线程安全的。

在多线程的场景下操作hashmap可能会导致以下三种情况

1)数据不一致:当多个线程同时操作hashmap时,会导致hashmap里的值不符合预期。

2)扩容时的死循环:在 JDK 1.7 及之前的版本中,HashMap 扩容过程中使用头插法转移元素,多线程并发扩容可能会导致链表形成环形结构,当后续操作该链表时就会陷入死循环,造成 CPU 占用率飙升。

3)get操作可能返回null:即使某个键值对已经被正确 put 到 HashMap 中,在多线程环境下,其他线程的 get 操作也可能无法获取到该值,返回 null。

5、HashMap、HashTable是什么关系?

简单地说,hashmap和hashtable应该是新版和老版的区别,现在hashtable已经很少用了,可以用ConcurrentHashMap来代替。

特性HashMapHashtable
线程安全性非线程安全,多线程操作可能出现数据异常线程安全,所有方法都用 synchronized 修饰
null 键 / 值支持允许 null 作为键(仅允许一个 null 键)和多个 null 值不允许 null 键或 null 值,否则抛出 NullPointerException
继承关系继承自 AbstractMap 类继承自古老的 Dictionary 类
扩容机制初始容量 16,扩容时变为原来的 2 倍初始容量 11,扩容时变为原来的 2 倍 + 1
迭代器类型快速失败(fail-fast)迭代器枚举器(Enumeration)是安全失败(fail-safe)的
性能并发环境下性能更高(无同步开销)并发性能差(全局锁导致多线程竞争激烈)

ThreadLocal

1、讲讲你对ThreadLocal的一些理解

含义:threadloacl是线程本地变量,如果创建了这个变量,在多线程的场景下,每个线程内部都会有一个该变量的副本,当线程需要操作这个变量时,它操作的是自己本地副本,从而起到线程隔离的作用。

实现原理:在Thread类中有一个ThreadLocalMap的变量,每个线程都有属于自己的ThreadloaclMap,在这个map中维护了一个数组,key就是Threadloacl对象,属于弱引用,value是Thread的泛型值,每个线程往Threadloacl中存值实际上都是往这个map中存,ThreadLoacl本身并不存取值,只是作为一个key从该map中存取值。

2、ThreadLocal有哪些应用场景

1)保存线程上下文信息:在多层调用时,例如controller -> service -> dao,需要传递一些参数值,比如用户身份,请求ID,日志信息等,但不希望通过方法参数显式传递

2)解决线程安全问题:对于非线程安全但是需要频繁使用的对象,如SimpleDateFormat,Random,可通过ThreadLoacl创建副本,避免多线程竞争导致的异常

3)框架中的应用:在spring中存储了HttpServletRequest,在controller和service层可直接获取,无需参数传递;在spring事务管理中,TransactionSynchronizationManager 通过 ThreadLocal 绑定当前线程的事务资源;在mybatis中,使用了ThreadLoacl存储SqlSession对象,保证同一请求中数据库操作的都是同一个SqlSession。

如果错误使用ThreadLoacl,有可能会导致内存溢出,因为在ThreadLoacl内部有一个map,map的key是弱引用,只要触发GC,该值就会被回收,而map的value是强引用,当key被回收了,而value还在,那么会一直占用内存从而导致OOM,所以在使用完ThreadLoacl后,要即使调用remove方法进行清除。

3、了解过FastThreadLocal吗

FastThreadLocal 是netty提供的一个高性能线程本地存储实现,专为解决JDK中的ThreadLoacl性能瓶颈而设计的。它在多线程高并发的场景下性能远超ThreadLoacl,是netty内部优化的重要组件。

ThreadLoaclFastThreadLocal
数据结构每个线程通过 Thread.threadLocals 维护一个哈希表(ThreadLocalMap),存储 <ThreadLocal, 变量副本> 键值对。查找时需计算哈希值并处理哈希冲突,性能随 ThreadLocal 数量增加而下降。每个线程(Netty 中的 FastThreadLocalThread)持有一个数组(InternalThreadLocalMap),变量副本直接通过索引(index)访问。索引在 FastThreadLocal 初始化时分配,查找时无需哈希计算,直接通过 index 定位,时间复杂度为 O (1)。

ArrayList、LinkedList

1、是否保证线程安全

非线程安全

2、底层数据结构

1)ArrayList是基于动态数组实现的,其核心是一个可动态扩容的Object数组。

2)LinedList是基于双向链表实现的,每个元素通过节点之间的引用串联在一起。

3、插入和删除是否受元素位置的影响

ArrayList插入和删除受元素位置的影响,而LinkedList不会。

操作类型ArrayList(动态数组)LinkedList(双向链表)
尾部插入 / 删除O (1)(高效)O (1)(高效)
中间 / 头部插入 / 删除O (n)(需移动大量元素)O (n)(需遍历查找位置,但修改本身 O (1))
位置对效率的影响影响极大(越靠前效率越低)影响较小(主要成本在查找,而非修改)

4、是否支持快速随机访问

ArrayList支持,LinkedList不支持

5、内存空间占用

ArrayList底层是数组的结构,占用空间是连续的,而LinkedList底层是链表的结构,占用空间不连续。

场景ArrayListLinkedList
内存开销较低(无节点指针开销)较高(每个元素多 2 个指针)
内存分配方式连续空间,可能有预分配冗余分散空间,无冗余但碎片化
元素数量较少时可能因预分配导致少量浪费节点开销占比更高
元素数量较多时预分配冗余影响减小总指针开销累积更明显

6、如何进行扩容的,默认初始化空间是多少

ArrayList初始容量是10,每次扩容1.5倍。

LinkedList底层是链表,没有初始容量,也不存在扩容的概念。

String StringBuffer StringBuilder

1、有什么区别

1)String不可变,StringBuffer、StringBuilder 可变

2)String和StringBuffer线程安全,StringBuilder线程不安全

3)String性能最差,StringBuilder性能最好,StringBuffer居间

2、是线程安全的吗

String和StringBuffer线程安全,StringBuilder线程不安全

jdk1.8的新特性

1、lambda表达式

2、Functional Interfaces

3、Optionals

4、Stream 流

5、Parallel-Streams 并行流

二、并发编程

volatile

1、volatile 的作用和使用场景

主要有两个作用:有序性和可见性

有序性是指操作被volatile修饰的变量,不会被重排序

可见性是指当一个共享变量被线程修改后,其他线程会立即感知到

可用于状态标记变量:多线程之间传递状态,例如停止信号,可确保线程能感知到变化

双重检查锁定实现单例模式:防止指令重排序导致的半初始化对象问题

2、volatile 如何保证指令重排

内存屏障

内存屏障类型作用(限制的重排方向)对应场景
LoadLoad 屏障禁止后续 “读操作” 重排到当前 “读操作” 之前读 volatile 变量前
StoreStore 屏障禁止后续 “写操作” 重排到当前 “写操作” 之前写 volatile 变量前
LoadStore 屏障禁止后续 “写操作” 重排到当前 “读操作” 之前读 volatile 变量后
StoreLoad 屏障禁止后续 “读操作” 重排到当前 “写操作” 之前(最严格)写 volatile 变量后

3、什么情况下会发生指令重排

1)不影响单线程语义:重排后,单线程的执行结果和重排前一样

2)存在可优化的执行间隙:指令之间无依赖关系,或者依赖关系可通过重排消除

synchronized

1、一般用在什么场景

用于线程同步的场景

1)保护对象级或类级的共享资源;

2)保证复合操作的原子性;

3)实现临界区或简单的线程间通信。

2、实现原理

synchronized修饰代码块时,jvm采用了monitorenter和monitorexit两个指令来实现同步,前者指向同步代码块的开始位置,后者指向结束位置
synchronized修饰同步方法时,jvm采用了一个标记符来实现同步,标明这个方法是一个同步方法

3、锁升级过程(无锁、偏向锁、轻量级锁、重量级锁)

1)无锁:当没有线程访问同步代码块,或者对象刚创建,此时处于无锁状态,对象头中的MarkWord锁状态标志位为01,偏向锁标志位为0。

2)偏向锁:当线程访问同步资源时,会先检查对象头中的标志位,如果是无锁状态,则会通过CAS操作修改偏向锁标志位为当前线程ID,后续该线程再次进入时,有两种情况,当偏向锁标志位为当前线程ID,则无需CAS操作,直接进入;当偏向锁标志位不是当前线程ID则会触发偏向锁撤销并升级轻量级锁。

3)轻量级锁:JVM会在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头中的MarkWord复制到锁记录中,然后线程尝试使用CAS将对象头中的MarkWord替换为指向所记录的指针,如果成功,当前线程获得锁,如果失败,则通过自旋去获取锁,如果自旋超过阈值还没获取到锁,则升级到重量级锁。

4)重量级锁:当锁处于这个状态时,其他线程试图获取锁时都会被阻塞住,当持有锁的线程释放后会唤醒这些线程,被唤醒的线程就会进行新一轮的抢夺锁的过程。

4、这是JVM层面锁,还是JDK层面锁

JVM层面的。

5、这是一种悲观锁还是乐观锁

自适应的锁机制。

在竞争轻微时,以乐观锁的方式高效处理

在竞争激烈时,以悲观锁的方式避免CPU空转

lock

1、这是JVM层面锁,还是JDK层面锁

JDK层面,底层依赖AQS实现。

2、这是一种悲观锁还是乐观锁

悲观锁。

1)当线程需要访问共享资源时必须要先获得锁,如果锁已被获取,则该线程阻塞等待

2)获得锁后,线程可以安全的操作资源,其他线程无法同时访问

3)操作完成后,线程释放锁,其他线程竞争获取

3、是可重入锁吗

可以是也可以不是,Lock并不强制子类实现可重入的机制。

子类ReentrantLock 是典型的可重入锁:一个线程在持有锁的情况下,再次调用需要获取同一把锁的方法时,能够直接获取锁而不被阻塞。

ReentrantLock

1、与synchronized相比较有什么不同

对比维度synchronizedReentrantLock
实现层面JVM 层面锁:由 JVM 内置实现,通过字节码指令 monitorenter/monitorexit 控制锁的获取与释放,底层依赖对象头 Mark Word 和锁升级机制。JDK 层面锁:基于 Java 代码实现(依赖 AQS 抽象队列同步器框架),属于 API 级别的锁,逻辑完全由 JDK 代码控制。
可移植性依赖 JVM 实现,不同 JVM(如 HotSpot、J9)可能有细微差异,但主流 JVM 均完美支持。纯 Java 代码实现,不依赖 JVM 底层细节,可移植性更强(理论上可在非 JVM 环境中模拟)。
锁的本质隐式锁(自动加锁 / 解锁)。显式锁(需手动调用 lock()/unlock() 控制生命周期)。

2、ReentrantLock 与 Lock 的关系

ReentrantLock是Lock的子类。

3、锁过程中是否可中断,与之对应的synchronized可中断吗

ReentrantLock锁过程中支持中断,synchronized不支持。

ReentrantLock提供了 lockInterruptibly() 方法,允许线程在等待锁的过程中响应中断(Thread.interrupt()),主动放弃等待并抛出 InterruptedException,避免线程永久阻塞。

synchronized在等待锁的过程中不支持中断。即使对等待锁的线程调用 interrupt(),线程也不会立即响应中断,而是会继续阻塞等待锁,直到获取到锁后才会处理中断状态(抛出 InterruptedException 或标记中断状态)。

CAS

1、Unsafe 类的作用

Unsafe是Java中一个特殊的底层类,提供了一系列低级别,不安全的操作,可直接绕过Java的内存模型和安全机制直接与操作系统或底层硬件交互,其核心作用是为了给JDK内部类库提供底层支持,不建议开发者在业务代码中直接使用。

2、CAS 的理解

主要由三个部分,变量的内存地址,要修改的新值,将被修改的预期值。

主要流程:首先从变量的内存地址中读取值与预期值比较,如果相等,则修改为新值;如果不相等,则不做任何操作。

CAS是一种高效的原子操作机制,通过比较和交换的指令实现了多线程下的安全数据修改,是乐观锁实现的核心。

3、什么是ABA问题

当一个线程通过CAS操作将一个变量从A修改为B,另一个线程又将该变量从B修改为A,那么第三个线程执行CAS操作时,无法发现该变量已经被修改过两次了。

针对上述问题,可通过添加版本号去解决,每次修改时递增版本号,CAS比较时同时比较版本号和预期值。

4、CAS的实现有什么

AtomicInteger,ReentrantLock等。

AQS

1、基本原理是什么

AQS主要是处理线程同步,是很多并发锁和同步工具类的实现基础。

在它的内部维护了一个由volatile修饰的变量state,用来维护线程状态,状态的意义由子类赋予,并且还维护了一个FIFO的双向链表,属性head和tail分别表示这个链表的头部和尾部,获取锁失败后,就会通过CAS操作放到链表的末尾。

2、实现类有哪些

ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier

3、实现了AQS的锁有哪些

自旋锁、互斥锁、读锁写锁、条件产量、信号量、栅栏都是AQS的衍生物。

在这些锁里,虽然他们是基于AQS实现的,但是并没有直接继承AQS,而是在他们的内部定义了一个Sync类去继承AQS,之所以要这么做,是因为,锁面向的是用户,而同步器面向的是线程控制,那么在锁实现中聚合同步器而不是直接继承AQS就可以很好的去隔离两者之间所关注的事情。

多线程

1、线程池的种类

newCachedThreadPool(缓存线程池)

newFixedThreadPool(固定大小线程池)

newScheduledThreadPool(周期性线程池)

newSingleThreadExecutor(单线程线程池)

2、线程池的核心参数

核心线程数(corePoolSize),最大线程数(maximumPoolSize),任务队列(workQueue),拒绝策略(rejectedExecutionHandle)

3、线程的状态

新建,就绪,运行,等待,等待队列,阻塞,终止

三、JVM

1、GC 优化

1)监控:收集GC日志和堆快照

2)分析:如果是频繁GC,需要考虑是否对象创建过多,老年代空间不足,内存泄露等问题;如果是GC停顿时间过长,需要考虑是否是堆太大,垃圾回收器选择错误或者是CPU资源不足等问题

3)调优:JVM参数调优是治标,从代码层面调优是治本,尽量减少不必要的内存分配和回收压力。

4)验证:主要验证优化前后的对比

2、JVM 逃逸分析

JVM 的逃逸分析(Escape Analysis) 是一种编译器优化技术,用于分析对象的生命周期是否被限制在方法内部(未 “逃逸” 到方法外),从而对未逃逸的对象进行一系列优化,减少内存分配和垃圾回收的开销。

方法逃逸:对象的引用被作为方法返回值返回,或被存储到方法外部可访问的全局变量、静态变量中。

线程逃逸:对象的引用被传递到其他线程(如放入多线程共享的队列),可能被其他线程访问。

3、类的对象头都包括什么

1)Mark Word:动态存储对象运行时的状态信息,包含哈希码,GC分代年龄,锁状态等。

2)类型指针:该对象所属的类在方法区中的地址。

3)数组长度(仅在数组对象中):用于快速获取数组的长度。

4、new Object() 初始化都做了什么

1)类加载检查:先判断类是否被加载,如果没有,则调用类加载器加载字节码文件,并在方法区生成对应的类元数据,然后验证字节码的合法性,为静态变量分配内存并设置默认值,然后解析。执行类的静态代码块和对类的静态变量赋值。

2)内存分配

3)对象初始化:先初始化对象头,再设置实例变量默认值,最后执行构造方法

5、Java的内存模型以及GC算法

1)Java内存模型是一种抽象的模型,用来屏蔽各种硬件和操作系统的内存访问差异,java内存模型定义了线程和主内存之间的抽象关系,线程之间的共享变量存在主内存中,每一个线程都有一个私有的本地内存,本地内存存储了该线程以读写共享变量的副本

2)GC算法

判断对象是否存活的算法

引用计数器算法:给对象加一个引用计数器,当对象增加一个引用时计数器+1,引用失效时计数器-1,当计数为0的对象可被回收(存在循环引用的问题)。

可达性分析算法:通过定义GC Roots的对象作为起点进行搜索,将能够到达的对象视为可用,不可达的对象是为不可用。

垃圾收集算法

标记-清除算法:遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活的对象;清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉,与此同时,清除那些被标记过的对象的标记,以便于下次回收。

标记-整理算法:标记的过程和上一个相同,整理的过程是移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段。

复制算法:为了解决效率问题,复制收集算法出现了,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块内存用完,需要进行GC时,就将存活的对象复制到另一块上面,然后将第一块内存全部清除。

分代收集算法:

根据对象存活周期的不同,将内存划分为几块。一般是把 Java 堆分为新生代和老年代,针对各个年代的特点采用最适当的收集算法。年轻代使用复制算法,老年代使用标记-整理或者标记-清除算法。

6、JVM内存区域

方法区:储存常量,静态变量,即时编译器编译后的代码缓存等数据
程序计数器:用于记录程序执行的位置
java虚拟机栈:线程私有,java方法执行的主要区域
本地方法栈:本地方法(native)执行的区域
堆:所有对象存储位置


文章转载自:

http://7E6PhDUo.jcxqc.cn
http://eAl0qVCn.jcxqc.cn
http://roeOswvj.jcxqc.cn
http://MpPnP3Lm.jcxqc.cn
http://3sSTZuDr.jcxqc.cn
http://oWOKgtNy.jcxqc.cn
http://wPpnSkcc.jcxqc.cn
http://AeUdomQJ.jcxqc.cn
http://RpTCypQD.jcxqc.cn
http://joZNdRfJ.jcxqc.cn
http://h86ZwRtU.jcxqc.cn
http://ue73Ow79.jcxqc.cn
http://1lcVDUkS.jcxqc.cn
http://qoEP8Asv.jcxqc.cn
http://6lAcxebZ.jcxqc.cn
http://DTm2YF90.jcxqc.cn
http://QOQGmn5M.jcxqc.cn
http://ori4x5W1.jcxqc.cn
http://le3heL4a.jcxqc.cn
http://sEbXYIrU.jcxqc.cn
http://Varhoavd.jcxqc.cn
http://UQXF45gd.jcxqc.cn
http://YyXS9SoR.jcxqc.cn
http://RiIxb77Z.jcxqc.cn
http://v78bPy6C.jcxqc.cn
http://fbxJSjIb.jcxqc.cn
http://MNeoWm8c.jcxqc.cn
http://WSjT2mDM.jcxqc.cn
http://iItHvNF2.jcxqc.cn
http://3wYP9Nvk.jcxqc.cn
http://www.dtcms.com/a/382297.html

相关文章:

  • 在Excel和WPS表格中批量删除数据区域的批注
  • 商品库存扣减方案
  • smartctl Current_Pending_Sector 硬盘待处理扇区
  • 并发和高并发
  • 科技信息差(9.13)
  • 文档长期不更新导致知识过时如何解决
  • Python学习-day9 字典Dictionary
  • Ubuntu22.04更换阿里镜像源,ubuntu更换源
  • 仓颉编程语言青少年基础教程:Struct(结构)类型
  • C语言数据结构实战:从零构建一个高性能的顺序栈
  • 数据链路层总结
  • Linux线程:基于环形队列的生产消费模型
  • 【Ambari监控】高版本 DataGrip 无法使用 Phoenix 驱动
  • 1.架构师——大纲
  • 粒子群算法模型深度解析与实战应用
  • JDK 新特性
  • 数据库可视化面板下载
  • 深入解析:preload与prefetch的区别及最佳实践
  • 【层面一】C#语言基础和核心语法-01(类型系统/面向对象/异常处理)
  • Python核心技术开发指南(061)——初始化方法__init__
  • 用 Go 采集服务器资源指标:从原理到实践
  • MySQL-day2_02
  • 基于springboot+vue开发的会议预约管理系统【50906】
  • 【Ubuntu】sudo apt update出现E :仓库***没有Release文件
  • JavaWeb--day3--AjaxElement路由打包部署
  • 阿里云国际代理:怎么保障数据库在凭据变更过程中的安全与稳定?
  • 关于子空间流形的认识
  • SQL注入漏洞手动测试详细过程
  • 【Linux】gcc/g++工具篇
  • libxl写到xls