慧策(掌上先机)一面 2023.6.12
自我介绍
聊天 + 八股 以聊天的形式问的,体验很好
对学校情况的一些了解
1.currentHashMap底层实现(简历第二行写的并发,看简历上有currentHashMap问的)
ConcurrentHashMap 1.7
- 数据结构:
- 提供了一个segment数组,在初始化ConcurrentHashMap 的时候可以指定数组的长度,默认是16,一旦初始化之后中间不可扩容
- 在每个segment中都可以挂一个HashEntry数组,数组里面可以存储具体的元素,HashEntry数组是可以扩容的
- 在HashEntry存储的数组中存储的元素,如果发生冲突,则可以挂单向链表
存储流程
- 先去计算key的hash值,然后确定segment数组下标
- 再通过hash值确定hashEntry数组中的下标存储数据
- 在进行操作数据的之前,会先判断当前segment对应下标位置是否有线程进行操作,为了线程安全使用的是ReentrantLock进行加锁,如果获取锁是被会使用cas自旋锁进行尝试
- 并发度:Segment 数组大小即并发度,决定了同一时刻最多能有多少个线程并发访问。Segment 数组不能扩容,意味着并发度在 ConcurrentHashMap 创建时就固定了
- 索引计算
- 扩容:每个小数组的扩容相对独立,小数组在超过扩容因子时会触发扩容,每次扩容翻倍
- Segment[0] 原型:首次创建其它小数组时,会以此原型为依据,数组长度,扩容因子都会以原型为准
小数组容量为 容量/并发度
ConcurrentHashMap 1.8
- 在JDK1.8中,放弃了Segment臃肿的设计,数据结构跟HashMap的数据结构是一样的:数组+红黑树+链表采用 CAS + Synchronized来保证并发安全进行实现
- 并发度:Node 数组有多大,并发度就有多大,与 1.7 不同,Node 数组可以扩容
- 扩容条件:Node 数组满 3/4 时就会扩容
- 扩容单位:以链表为单位从后向前迁移链表,迁移完成的将旧数组头节点替换为 ForwardingNode
- 扩容时并发 get
- 扩容时并发 put
- 与 1.7 相比是懒惰初始化
- capacity 代表预估的元素个数,capacity / factory 来计算出初始数组大小,需要贴近
- loadFactor 只在计算初始数组大小时被使用,之后扩容固定为 3/4
- 超过树化阈值时的扩容问题,如果容量已经是 64,直接树化,否则在原来容量基础上做 3 轮扩容
2.map的数据结构了解吗
3.hashmap的实现
讲了put方法
- 判断键值对数组table是否为空或为null,否则执行resize()进行扩容(初始化)
- 根据键值key计算hash值得到数组索引
- 判断table[i]==null,条件成立,直接新建节点添加
- 如果table[i]==null ,不成立
4.1 判断table[i]的首个元素是否和key一样,如果相同直接覆盖value
4.2 判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对
4.3 遍历table[i],链表的尾部插入数据,然后判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操 作,遍历过程中若发现key已经存在直接覆盖value
- 插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold(数组长度*0.75),如果超过,进行扩容。
扩容讲错了
- 添加元素或初始化的时候需要调用resize方法进行扩容,第一次添加数据初始化数组长度为16,以后每次每次扩容都是达到了扩容阈值(数组长度 * 0.75)
- 每次扩容的时候,都是扩容之前容量的2倍;
- 扩容之后,会新创建一个数组,需要把老数组中的数据挪动到新的数组中
- 没有hash冲突的节点,则直接使用 e.hash & (newCap - 1) 计算新数组的索引位置
- 如果是红黑树,走红黑树的添加
- 如果是链表,则需要遍历链表,可能需要拆分链表,判断(e.hash & oldCap)是否为0,该元素的位置要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位置上
4.hashmap为什么线程不安全
具体来说,当多个线程同时对HashMap进行写操作时,可能会导致数据覆盖或者数据丢失的问题。例如,当两个线程同时向HashMap中添加一个键值对时,它们可能会同时计算出相同的哈希值,从而导致数据覆盖的问题。又或者,当一个线程正在遍历HashMap的同时,另一个线程对HashMap进行修改,可能会导致ConcurrentModificationException异常的抛出。
5.currentHashMap如何实现线程安全的(其实我感觉一开始讲了,可能讲的不细)
6.有哪几种垃圾回收器?和他们的特点?1.8默认的垃圾回收器(ps + po)
记不清,讲的不好
7.然后讲的太细了, 标记清除、标记复制和标记整理。标记清除是哪个垃圾修堆器使用的一个方法
不清楚
8.类加载机制
9.jvm内存结构
10.哪些线程共享,哪些不共享
线程私有的:
- 程序计数器
- 虚拟机栈
- 本地方法栈
线程共享的:
- 堆
- 方法区
- 直接内存 (非运行时数据区的一部分)
11.gc发生在哪里
12.方法区会被gc吗
会(答错了)
13.栈帧的结构
局部变量表、操作数栈、动态链接、方法返回地址
14.程序进栈出栈的过程(不会,他说这个问题太难了)(栈的大小多少合适,这种问题)
- 当一个函数被调用时,会为该函数创建一个栈帧(Stack Frame),栈帧包含了该函数的参数、局部变量、返回地址等信息。
- 创建好栈帧后,将其压入调用栈(Call Stack)中,成为当前栈帧,同时将程序计数器(Program Counter)指向该函数的第一条指令,开始执行该函数。
- 在函数执行过程中,如果遇到其他函数的调用,会重复上述过程,为该函数创建一个新的栈帧,将其压入调用栈中,成为当前栈帧。
- 如果函数执行过程中遇到了返回语句,会将当前栈帧弹出调用栈,同时将程序计数器指向返回地址,继续执行调用该函数的函数。
- 当调用栈为空时,程序执行结束。
15.java内存的布局
在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域:对象头、实例数据和对齐填充。
Hotspot 虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据(哈希码、GC 分代年龄、锁状态标志等等),
hashcode:25位的对象标识Hash码
age:对象分代年龄占4位
biased_lock:偏向锁标识,占1位 ,0表示没有开始偏向锁,1表示开启了偏向锁
thread:持有偏向锁的线程ID,占23位
epoch:偏向时间戳,占2位
ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录的指针,占30位
ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针,
占30位
我们可以通过lock的标识,来判断是哪一种锁的等级
后三位是001表示无锁
后三位是101表示偏向锁
后两位是00表示轻量级锁
后两位是10表示重量级锁
另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
实例数据部分是对象真正存储的有效信息,也是在程序中所定义的各种类型的字段内容。
对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。 因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说就是对象的大小必须是 8 字节的整数倍。而对象头部分正好是 8 字节的倍数(1 倍或 2 倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
16.jvm是如何判断他是垃圾的
引用计数法 可达性分析算法(面试官补充了python的计数算法)
17.对象被判定为不可达后对象就死了吗?
对象被判定为不可达后,并不代表对象就死了。
在Java中,对象的生命周期由垃圾回收器来管理。当一个对象不再被任何强引用、软引用、弱引用和虚引用所引用时,就会被判定为不可达(Unreachable)状态。在下一次垃圾回收时,垃圾回收器会对不可达对象进行标记、清理和回收操作。
具体来说,当一个对象被判定为不可达时,垃圾回收器会首先对该对象进行标记操作,将其标记为待清理状态。接着,垃圾回收器会对待清理对象进行清理操作,释放其占用的内存空间。最后,垃圾回收器会对被清理对象进行回收操作,将其从内存中彻底删除。
需要注意的是,垃圾回收器的标记、清理和回收操作并不是一次性完成的,而是分阶段进行的。在标记阶段,垃圾回收器会标记所有可达对象,而不会对不可达对象进行标记。在清理阶段,垃圾回收器会对待清理对象进行清理操作,而不会立即回收被清理对象的内存空间。在回收阶段,垃圾回收器会将被清理对象的内存空间回收,以便下次分配内存时使用。
因此,对象被判定为不可达后,并不代表对象就死了,而是需要经过垃圾回收器的标记、清理和回收操作,才能真正从内存中删除。
18.对象的几个阶段 给个开头创建阶段、应用阶段
没听说过
创建阶段、应用阶段、不可见阶段、不可达阶段,还有搜集阶段、终结阶段还有重新分配
在Java中,对象的生命周期可以分为多个阶段,包括创建阶段、应用阶段、不可见阶段、不可达阶段、搜集阶段、终结阶段和重新分配阶段。下面是各个阶段的具体说明:
1. 创建阶段:对象的创建阶段是指从类加载到对象分配内存空间、初始化零值、设置对象头、执行构造函数等一系列步骤,直到对象被创建成功。
2. 应用阶段:对象的应用阶段是指对象被程序所使用的过程。在应用阶段,对象可能会被多次引用和修改,直到程序不再需要该对象时,对象进入下一个阶段。
3. 不可见阶段:对象的不可见阶段是指对象不再被程序所引用和访问,但是仍然可以通过其他途径访问到该对象,例如通过弱引用或虚引用。
4. 不可达阶段:对象的不可达阶段是指对象不再被任何强引用、软引用、弱引用和虚引用所引用时,被判定为不可达状态。在下一次垃圾回收时,垃圾回收器会对不可达对象进行标记、清理和回收操作。
5. 搜集阶段:对象的搜集阶段是指垃圾回收器对内存中的不可达对象进行标记、清理和回收操作的过程。搜集阶段是垃圾回收的核心过程,可以分为标记、清理和压缩等多个阶段。
6. 终结阶段:对象的终结阶段是指对象在被垃圾回收器回收前,执行finalize()方法的过程。在finalize()方法中,可以进行一些资源释放和清理操作。
7. 重新分配阶段:对象的重新分配阶段是指在垃圾回收器回收内存后,将空闲内存重新分配给新的对象的过程。在重新分配阶段,JVM会对内存进行整理和压缩,以提高内存的利用效率。
需要注意的是,对象的生命周期是由垃圾回收器来管理的,程序员无法直接控制对象的各个阶段。在实际应用中,应该尽量避免对象的过早创建和过晚销毁,以减少内存占用和垃圾回收的开销,提高程序的性能和效率。
19.finalize方法
20.你有一个对象,我想把它显示的设为不可不可见阶段应该怎么做?
不会
将该对象的所有引用都设置为null
21.事务级别?默认是那种?
22.什么是幻读?如何解决幻读?
23.MySQL的锁
24.间隙锁和临建锁是解决什么问题的?
25.MySQL的数据结构,是怎么存储数据的
算法 实现加法 忘了,没写出来
反问
多久出结果
学习建议 建议好好算法
实习生干些什么
业务介绍
另:6.13 oc
6.14 offer
有没有去过的佬讲讲公司怎么样 看网上说不好
部门平台集成
#我的实习求职记录##晒一晒我的offer#
