java开发面试题(提高篇)
java开发面试题
- 一、API扩展
- (1) synchronized关键字底层锁升级是怎样实现的
- (2)volatile关键字是什么
- (3)IO和NIO的区别
- (4) HashMap的put操作流程是怎么样的
- (5)说说你对ThreadLocal的理解
- 二、JVM技术
- (1)JVM的构成
- (2)JVM堆的构成和分配内存的过程
- (3) 内存中方法和变量存在位置
- (4)什么是内存泄露以及导致的原因
- (5)四大引用类型的GC回收特点是什么
- 三、redis
- (1)redis是什么
- (2)基础数据类型有哪些
- (3)什么是缓存穿透及解决方案
- (4)什么是缓存雪崩及解决方案
一、API扩展
(1) synchronized关键字底层锁升级是怎样实现的
java中,锁是一种用于线程同步的机制,为了提高性能,就那些锁升级操作。
1.偏向锁:当一个线程获取锁时,会在对象头部记录该线程的ID,并标记为偏向锁,此后再次执行相同线程请求锁的操作,就可以直接获得锁
2.轻量级锁(自旋锁):多个线程竞争同一个锁时,偏向锁就会升级为轻量级锁。轻量级锁使用CAS(比较-交换) 操作来实现
将对象头部的标记字段修改为指向线程私有的锁记录。如果CAS操作成功,表示当前线程获取了锁。
3.重量级锁:当多个线程竞争同一个锁并且轻量级锁获取失败,会升级为重量级锁,依赖于操作系统的底层同步机制。会导致线程阻塞和切换,且性能开销较大,重量级锁会让未获取到锁的线程进入阻塞状态。一旦锁被释放,操作系统会唤醒其中一个等待的线程,以此来解决竞争问题
(2)volatile关键字是什么
- 可见性:保证了变量的可见性,当修改此变量的值时,其他线程可以立即看到修改后的值,不会使用缓存中的旧值。
- 禁止指令重排序:指令重排序是编译器和处理器为了提高执行效率而进行的操作,可能会导致多线程程序出现问题。
- 不保证原子性:如果一个操作涉及到多个步骤,且需要保证原子性的话,还需要使用其他的同步手段:synchronized关键字等
(3)IO和NIO的区别
1.阻塞与非阻塞:
IO是阻塞的,也称为同步模型。如果没有数据可读或无法立即写入,线程会被阻塞;相反,NIO是非阻塞(异步)
2.缓冲:
IO使用字节流和字符流,通常用缓冲流提供缓冲功能,NIO的Channel和Buffer提供内置缓冲功能。
3.数据处理方式:
IO是以流为基础的,每次读取一个或多个字节或字符。NIO是以缓冲区(Buffer)为基础的,可以一次读取或写入过个数据。
4.选择器:
NIO中的selector提供多路复用的功能,一个线程处理多个Channel的IO操作。这种机制允许同时监听多个输入流,只有在有数据可读或可写时才会被唤醒。
(4) HashMap的put操作流程是怎么样的
1.计算键的哈希值:通过hashCode()方法计算,键对象转换为整数计算出来哈希值,将键映射到哈希表的索引位置
2.映射哈希值到数组索引位置,进行与操作(hash& (length -1))
3.检查数组位置是否已经存在元素:为空表示没有冲突,直接将值放入
4.冲突处理:如果该位置已经存在元素,需要进行冲突处理。HashMap才用拉链法来解决冲突,找到相同的健泽更新对应的值。
5.动态扩容:插入键值对后,判断是否需要扩容,当前元素数量大于等于负载因子乘以容量,则需要扩容。
(5)说说你对ThreadLocal的理解
ThreadLocal是一个Java中的线程局部变量。主要是用来为每个线程提供一个独立的变量副本,每个线程都可以独立地操作自己的副本互不干扰。
- 线程隔离:都有独立的副本,线程之间的变量不会相互冲突,可以避免数据竞争和线程安全问题。
- 解决共享资源问题:多个线程需要访问同一个变量时,可以使用ThreadLocal来解决,无需使用锁或者同步机制。
- 垃圾回收:使用ThreadLocalMap来存储每个线程的变量副本,键是ThreadLocal实例本身,值是该线程的变量副本。线程结束后,线程本地变量会自动被回收,不会造成内存泄漏。
二、JVM技术
(1)JVM的构成
1.类加载子系统:负责将类读到内存,校验类的合法性、对类进行初始化
2.运行时数据区:方法区、堆区、栈区、计数器,负责存储类信息、对象信息、执行逻辑
3.执行引擎:负责从指定地址对应的内存中读取数据然后解释执行以及垃圾回收
4.本地库接口:负责实现java语言与其他编程语言的协同
(2)JVM堆的构成和分配内存的过程
主要用于创建JAVA对象,由年轻代和老年代构成,年轻代又分Eden 伊甸园区和两个幸存者区
1.编译器通过逃逸分析确定对象是栈还是堆上分配
2.如果是堆上分配,首先检测是否可在TLAB(线程本地分配缓冲区)上直接分配
3.如果TLAB上无法直接分配则在Eden加锁区进行分配
4.如果Eden区无法存储对象,则执行Yong GC
5.如果Yong GC之后 还不足以存储对象,则直接分配在老年代
(3) 内存中方法和变量存在位置
方法区:实例方法和静态方法,静态变量(都是属于类属性)
堆:实例变量(属于对象属性)
栈:局部变量(属于方法内部变量),调用方法时会获取到栈中进行执行
(4)什么是内存泄露以及导致的原因
程序运行时,动态分配的内存空间,在使用完毕后未得到释放,结果一直占用着内存单元,这个现象称为内存泄露
原因:
- 大量使用静态变量
- IO或连接资源没及时关闭
- 内部类的使用方式存在问题
- ThreadLocal应用不当,没有remove操作
- 缓存应用不当(尽量不要使用强引用–>对象类型定义的变量)
(5)四大引用类型的GC回收特点是什么
按照内存中的生命力强弱分出了四大引用类型,按强到弱分别是强引用、软引用(SoftReference)、弱引用(WeakReference)、虚引用
- 强引用
即使内存溢出也不会销毁
Object o1=new Object();//这里的o1就是强引用
- 软引用
内存不足时会被销毁
SoftReference<Object> sr=new SoftReference<Object>(new Object());//sr为软引用
sr.get();//获取引用的对象
- 弱引用
在GC触发时被销毁
WeakReference<Object> sr=new WeakReference<Object>(new Object());//sr为弱引用
- 虚引用
相当于没有被引用,主要是用来记录被销毁的对象,当这个虚引用的对象被销毁时,此引用会存储到引用队列
ReferenceQueue<Object> referenceQueue=new ReferenceQueue<Object>();
PhantomReference<Object> sr=
new PhantomReference<Object>(new Object(),referenceQueue);//sr为虚引用
三、redis
(1)redis是什么
redis是一个使用C语言编写的高性能内存数据库,用键值对结构存储数据,一般用来做缓存、消息队列、分布式锁、同事支持事务、持久化等
(2)基础数据类型有哪些
- String 字符串
- list 列表
- hash 字典
- set 集合
- zset 有序列表
(3)什么是缓存穿透及解决方案
当访问一个缓存和数据库都不存在的key时,请求会直接打在数据库上,这时缓存就像被穿透了一样,起不到作用,加入有一些恶意的请求故意查询不存在的key,请求量很大,会对数据库造成很大的压力。
解决方案:
- 接口校验。 在最外层先做一层校验,用户鉴权,数据合法性校验等
- 缓存空值。当访问缓存和数据库都没有查询到值时,可以将空值写进缓存,但是设置较短的过期时间
- 布隆过滤器。存储可能访问的所有key,不存在的key直接被过滤,可把所有可能存在的key放在一个大的Bitmap中,查询时通过该bitmap过滤
(4)什么是缓存雪崩及解决方案
缓存服务器重启或者大量缓存集中在某一个时间段失效,造成瞬时数据库请求量大,导致系统崩溃
解决方案:
- 打散过期时间:不同的key设置不同的过期时间
- 做二级缓存:一级失效时,二级做兜底
- 加互斥锁:缓存失效后,通过加锁或者队列来控制写缓存的线程数量,减少压力
- 热点数据不过期。