导图社区 面试题
Interview
JVM
谈谈对JVM理解
垃圾回收 GC
GC算法
标记清除 Mark-Sweep
有内存碎片
CMS Concurrent Mark Sweep减少停顿时间 初始标记 标记GC Root直接关联的对象 并发标记 进行GC Root跟踪 跟用户线程一起工作 重新标记 修正并用户运行产生的变动 并发清除 清除GC roots不可达对象和用户线程一起工作 初始标记和重新标记需要暂停。耗时的并发标记跟用户线程一起运行
标记整理 Mark-Compact
标记后整理到一边 没有内存碎片
复制 Copying
实现简单 没有内存碎片
G1
避免全区域收集 把堆分成几个独立区域 优先回收垃圾多的区域
GC标记对象死亡
可达性分析
GC Root和对象没有可达路径 为可回收对象
GC Root有哪些
方法区static引用的对象
方法区final引用的对象
栈帧中局部变量表中引用的对象
JNI(Native方法)中引用的对象
所有被同步锁(Synchronized)持有的对象
引用计数法
一个对象没有任何与之关联的引用则计数器为0 为可回收对象。 解决不了两个对象循环引用导致内存泄漏
运行时数据区
直接内存 Direct Memory
不受JVM控制
线程共享
堆Heap
对象和数组都保存在堆中。垃圾回收期的重要工作区域。
分为新生代Yong和老年代Old。 新生代分为Eden,s1,s2。新生代MinorGC使用复制算法,把eden+s1存活的对象复制到s2,年龄+1,年龄到16后的对象或者s2空间不够,移动到Old。 老年代使用标记清除法
方法区 Method Area
用来存储JVM加载的类的信息,常量,静态变量,JIT编译的代码 1.8以后永久代转移到直接内存改名MetaSpace元空间
线程独享
程序计数器PC
一块较小的空间 存当前线程执行的字节码命令的行号 用于线程切换 恢复
虚拟机栈VM Stack
每个方法在执行的时候都会创建一个栈帧Frame 用于存局部变量表 方法出口 动态链接 方法结束栈帧出栈
本地方法栈 Native Method Stack
用于执行native方法 与VM Stack类似
类加载
类加载过程
加载Loading
读取.class文件或者jar包 在内存中产生一个代表这个类的java.lang.Class的对象 放在方法区中作为各种数据的入口
连接 Linking
验证Verification
验证class文件格式
准备Preparing
初始化静态成员变量等
解析 Resolution
常量池中的符号替换为直接引用
初始化 Initialization
这步才由程序控制 执行子类静态代码块前先执行父类静态代码块
类加载器 双亲委派模型
BootStrap ClassLoader 加载JAVA_HOME/lib下重要的类rt.jar等
Extension ClassLoader 加载JAVA_HOME/lib/ext中的类
Application ClassLoader加载classpath上的类
双亲委派模型 当一个类收到了加载请求 首先不会自己加载而是递归请求父类加载器去加载,父类加载器加载不了子类才去加载。 好处是安全性 避免自己实现的Object等类影响正常运行 也可以自定义类加载器打破双亲委派模型比如Tomcat使用类加载器先加载child在加载parent隔离webapp
反射为什么慢
https://stackoverflow.com/questions/1392351/java-reflection-why-is-it-so-slow
编译器即使是JIT也没办法优化
每次需要去找类和方法 可以通过缓存优化
参数需要包装成数组 异常需要包装成InvocationTargetException
JUC
线程
线程生命周期
sleep wait区别
sleep不释放锁 sleep 导致程序暂停执行 让出cpu 但是仍然是监控状态的持有者
wait释放锁,进入等待锁定池,只有notify才能进入运行状态
sleep是Thread方法 wait是Object方法
锁
乐观锁
是乐观的思想,认为读多写少,通过CAS实现
悲观锁
是北关的思想,认为写多,通过Synchronize ReentrantLock实现
Synchronized原理
作用范围
作用于静态方法 锁的是Class实例 作用于方法锁的是this对象 作用于一个对象实例 锁住的是所有以该对象为锁的代码块
实现
对象头里有一块8字节的标识。存储锁标志位和占用该锁的ThreadId 使用monitorenter monitorexit 命令实现
Synchronized是非公平锁 。线程进入ContentionList之前会先自旋获取锁,获取不到进入ContentionList。 ContentionList会被大量线程cas访问,所以JVM移动一部分线程到EntryList作为备选。 JVM取出尾部元素作为候选者(OnDeck)。 OnDeck获取到锁之后变为Owner线程。 Owner wait()后会阻塞进入WaitSet,notify()后进入EntryList
锁升级的过程
锁可以从偏向锁,升级到轻量级锁,在升级到重量级锁的过程叫锁膨胀
偏向锁 只有一个线程一直获取锁的情况 ,去掉重入的CAS的性能消耗,一旦出现多线程竞争升级到轻量锁
轻量锁适应的场景是线程交替执行同步块的情况。如果存在统一时间访问同一锁,导致轻量锁膨胀为重量锁
重量级锁
Synchronized 通过对象监视器(monitor)实现。monitor依赖操作系统Mutex Lock。需要用户态和内核态切换,切换成本很高,效率低。所以成重量级锁
ReentrantLock和Synchronized区别
相同
1. 都可重入
2. 都是互斥锁
不同
1. Synchronized自动释放锁,ReentrantLock 需要手动调用unlock()方法
2. Synchronized是非公平的,ReentrantLock 可以实现公平锁
3. Synchronized 是语言级别的,ReentrantLock 是API级别的
4. Synchronized 是悲观锁,ReentrantLock 是乐观锁
ReentrantLock功能更强大
Condition和Object 唤醒区别
Condition 类的 awiat 方法和 Object 类的 wait 方法等效
Condition 类的 signal 方法和 Object 类的 notify 方法等效
Condition 类的 signalAll 方法和 Object 类的 notifyAll 方法等效
ReentrantLock 类可以唤醒指定条件的线程,而 object 的唤醒是随机的
volatile的作用
可见性 一个线程对volatile的修改 对另一个线程可见。对volatile的读每次都绕过cpu缓存从主存去读。写立即同步到主存。符合happen-before
防止指令重排序 1. 在volatile写操作的前面插入一个StoreStore屏障。保证volatile写操作不会和之前的写操作重排序。 2. 在volatile写操作的后面插入一个StoreLoad屏障。保证volatile写操作不会和之后的读操作重排序。 3. 在volatile读操作的后面插入一个LoadLoad屏障+LoadStore屏障。保证volatile读操作不会和之后的读操作、写操作重排序。
64位写的原子性(long)
什么是Java内存模型(JMM)
TODO
AQS理解
ReentrantLock/Semaphore/CountDownLatch 基于AQS
AQS是一个框架 具体资源的获取释放逻辑在子类实现,等待队列的维护逻辑在上层已经实现好。 AQS维护了一个volatile的state(代表资源)变量和一个FIFO线程等待队列。 独占模式(ReentrantLock)实现tryAcquire/tryRelease 共享模式(CountDownLatch,Semaphere)实现tryAcquireShared/tryReleaseShared
CAS理解
CAS(V,E,N)当内存中的旧值V跟新值E一致的时候更新N 是一种乐观锁的思想
ABA问题? 增加数据版本号 AtomicStampedReference
ThreadLocal作用
每个Thread 都有个ThradLocalMap 。ThreadLocal只是做了转发调用ThradLocalMap.get()
什么是Happen Before
Mysql
索引都有哪些?怎么优化?
从索引存储结构划分
B Tree
Hash
FULLTEXT
R Tree
从应用层次划分
普通索引
唯一索引
复合索引
主键索引
查询优化
宗旨是扫描数据行数少
提高索引的选择性 选择性高的字段放在前面
写sql注意跟索引最左匹配
使用explain 看查询计划 看索引的使用情况
MVCC原理
当前读: select in share mode(共享锁),select for update,update,insert.delete(排它锁),都是当前读 即读取的是最新版本 加锁
快照读: 不加锁 读取的是快照版本 可能是通过redo日志查找到的历史版本
每一列隐含三个字段(db_row_id 行id,db_trx_id 事务id,db_roll_pt 回滚指针) update 和delete 操作 生成undo log链表
read commited隔离级别每次select都会生成read view,所以可能存在重复读 repeatable read 第一次select生成read view 后面select读复用视图,所以可能重复读没有问题 read view 会维护一个当前活跃的事务id列表 up_limit_id 数组最小的事务号 low_limit_id 数组最大的事务号+1
http://note.youdao.com/noteshare?id=e67fab42af3711c819fd35480e89087a
mvcc执行流程:
1. 某个事务开启
2. 对某行数据快照读,获取db_trx_id, 生成read view 维护一个当前活跃事务(未提交事务)id列表
3. db_trx_id< up_limit_id 说明当前数据 最后一次提交在当前活跃事务之前 读取当前数据
4. 否则从undo log链表中通过DB_ROLL_PTR 查找数据db_trx_id< up_limit_id
多版本并发控制 维持一个数据的多个版本 是读写操作不冲突 通过三个隐式字段 undo log和 readview实现
Mysql锁
InnoDB的行锁是对索引数据页上的记录加锁实现的
不使用索引加锁会导致全表next key lock
种类
record lock行锁
gap lock间隙锁
是为了防止同一事务的两次当前读,出现幻读的情况
next-key lock 行锁+间隙锁
REPEATABLE READ或以上开启
count(1)和count(*)区别?
https://blog.csdn.net/FeiChangWuRao/article/details/89493516?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control
count(字段),根绝字段判断为不为不空,根据字段定义,考虑要不要累加返回值,既然你引擎都返回值了,那我server层 “ +1 ” count(id),根据id主键取值,累加返回值,也是server层 “ +1 ” count(1),同样会遍历,但不取值,引擎告诉不为空那我就 “+1” count(*),也不取值,而且人家还是经过优化的 根据上面的推倒,搜主键肯定比搜正常字段快, 不取值的一定比取值的快(我就是查数统计一下,你给我这一行所有的值也没啥用啊), 优化过的比没优化过的快 以下排行是按照效率,而不是时间 count(*) > count(1) > count(id) > count(字段)
group by原理? 能否使用上索引?
Redis
持久化方法?
持久化
持久化是为了快速恢复数据 持久化不保证数据的完整性
RDB(Redis Database)
通过快照完成
触发方式: save bgsave flushall save 900 1 #15分钟内1个key被改变 漏斗设计
执行过程
fork过程中会阻塞
文件结构
优点: RDB是二进制压缩文件 占用空间小 便于传输 主进程fork子进程 可以最大化Redis性能 主进程不能太大 否则fork进程阻塞 缺点: 不保证数据完整性 会丢失最后一期快照以后更改的所有数据
AOF
将所有对护具库的写入命令按照RESP的形式存储 默认不开启
原理
推荐每秒存一次
重写
fork子进程 在子进程处理
触发: auto-aof-rewrite-min-size 64mb #超过64M重写 auto-aof-rewrite-percentage 100 #当前aof文件超过上一次aof文件大小的百分比多少今次进行重写。没重写过 以启动时aof文件大小为准
回放原理
混合持久化
开启混合持久化 aof-use-rdb-preamble yes
对比
1. RDB存的是某个时刻的数据快照 AOF存的是操作命令 采用文本存储
2. RDB性能高 AOF性能低
3. RDB会丢失最后一次快照之后的数据 完整性低 AOF 配置每秒保存最多丢失2秒数据
4. RDB master不会保存过期键值对,slave 保存过期键值对,当master 向slave同步时在清空过期键值对 AOF 过期keu会追加一条del命令 在重写的时候忽略过期key的del命令
String的结构?如何扩容
SDS(Simple Dynamic String) 动态字符数组 优点: 存二进制,获取长度方便O(1)
has的结构?如何扩容
使用数组+链表 类似HashMap
单线程的redis为什么这么快?(读每秒11w 写8w)
redis 内存操作 内存不满的时候不会与磁盘swap(性能急剧下降)
多机主从 分片负载均衡
maxmemory+lru/lfu等淘汰策略
单线程没有锁
数据类型简单 有压缩处理
使用多路复用IO +事件处理机制(事件驱动)
构建了多种通信模式
持久化的会fork子进程 主进程不阻塞
高并发理论与实战
分库分表
集群高可用
没做过数据量级大的项目怎么破?????????
Netty
Netty原理
高性能 异步事件驱动的NIO框架
netty为什么快
零拷贝 Buffer使用怼外直接内存进行socket读写 避免数据拷贝
多路复用io
多路复用一个线程可以处理多个socket链接
内存池 Buffer重用机制
序列化的高性能
多路复用实现原理?
本质上就是一个线程管理多个Socket
select poll epoll区别
https://www.jianshu.com/p/397449cadc9a
select 使用数组实现 ,会扫描所有fd O(n),而且fd上线有限制(默认1024)
poll 链表实现, 扫描fdO(n), fd没有上限
epoll 红黑树实现,没有fd上限限制 epoll_ctl注册事件需要拷贝,epoll_wait不需要拷贝,通过事件回调只关心“活跃连接”O(1)
数据结构&算法
详见我算法脑图
数据结构
Map
HashMap原理
使用数组+链表/红黑树实现
1.7,1.8区别
1.8优化哈希冲突拉链法8个节点后切换成红黑树
ConcurrentHashMap原理
锁的力度
1.7,1.8区别
1.7采用分段锁将一个数组切成不同的segment 默认16个 。 put加锁只锁当前segment
2倍扩容 tab[i]移动到tab[i]和tab[n+i]中
算法
leetcoderet热题100
二分
二分很多细节边界条件
34. 在排序数组中查找元素的第一个和最后一个位置
/** * 思路: * 有序就二分 先二分法定位到相等的元素 再左右展开 * O(logn) */ class Solution { public int[] searchRange(int[] nums, int target) { if (nums == null || nums.length == 0) return new int[]{-1, -1};//控制空的边界情况 int n = nums.length, left = 0, right = n - 1, mid = left + ((right - left) >> 1); boolean find = false;//判断是否找到了相等数字 while (left <= right) { mid = left + ((right - left) >> 1);//防止right+left超出Integer最大值 使用位运算增加效率 if (nums[mid] > target) right = mid - 1; else if (nums[mid] < target) left = mid + 1; else { find = true; break;//找到了推出 } } if (!find) return new int[]{-1, -1}; int l = mid, r = mid; while (l - 1 >= 0 && nums[l] == nums[l - 1]) l--;//从中点向左展开 while (r + 1 <= n - 1 && nums[r] == nums[r + 1]) r++;//从中点向右展开 return new int[]{l, r}; } }
33. 搜索旋转排序数组
class Solution { public int search(int[] nums, int target) { if (nums == null || nums.length == 0) return -1; int n = nums.length - 1; int low = 0, high = n; while (low <= high) { int mid = low + ((high - low) >> 1); if (target == nums[mid]) return mid;//找到退出 if (nums[0] <= nums[mid]) {//左边有序 if (target >= nums[0] && target < nums[mid]) {//target在左边 high = mid - 1; } else { low = mid + 1; } } else { if (target > nums[mid] && target <= nums[n]) { low = mid + 1; } else { high = mid - 1; } } } return -1; } }
bfs
102. 二叉树的层序遍历
class Solution { //bfs public List<List<Integer>> levelOrder(TreeNode root) { List<List<Integer>> res = new ArrayList<>(); if (root == null) return res; LinkedList<TreeNode> queue = new LinkedList<TreeNode>(); queue.offer(root); while (!queue.isEmpty()) { List<Integer> l = new ArrayList<Integer>(); res.add(l); int currLevelSize = queue.size(); while (currLevelSize-- > 0) { TreeNode curr = queue.pop(); l.add(curr.val); if (curr.left != null) queue.add(curr.left); if (curr.right != null) queue.add(curr.right); } } return res; } // public List<List<Integer>> levelOrder(TreeNode root) { // List<List<Integer>> res = new ArrayList<>(); // dfs(res, root, 0); // return res; // } // // public void dfs(List<List<Integer>> res, TreeNode root, int level) { // if (root == null) return; // if (res.size() == level) res.add(new ArrayList<>()); // List<Integer> l = res.get(level); // l.add(root.val); // dfs(res, root.left, level + 1); // dfs(res, root.right, level + 1); // }
dfs
46. 全排列
class Solution { public List<List<Integer>> permute(int[] nums) { boolean[] used = new boolean[nums.length]; List<List<Integer>> res = new ArrayList<>(); Deque<Integer> deque = new LinkedList<>(); dfs(res, nums, 0, nums.length, used, deque); return res; } private void dfs(List<List<Integer>> res, int[] nums, int depth, int n, boolean[] used, Deque<Integer> deque) { if (depth == n) { res.add(new ArrayList<>(deque)); return; } for (int i = 0; i < nums.length; i++) { if (used[i]) continue; used[i] = true; deque.addLast(nums[i]); dfs(res, nums, depth + 1, n, used, deque); used[i] = false; deque.removeLast(); } } }
class Solution { public List<List<Integer>> permute(int[] nums) { List<List<Integer>> res = new ArrayList<>(); List<Integer> output = Arrays.stream(nums).boxed().collect(Collectors.toList()); backtrack(output, 0, nums.length, res); return res; } private void backtrack(List<Integer> output, int first, int n, List<List<Integer>> res) { if (first == n) res.add(new ArrayList<>(output)); for (int i = first; i < n; i++) { Collections.swap(output, first, i); backtrack(output, first + 1, n, res); Collections.swap(output, first, i); } } }
链表
链表比较繁琐画图比较容易
19. 删除链表的倒数第 N 个结点
class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { ListNode dummy = new ListNode(-1), fast = dummy, slow = dummy, pre = dummy; dummy.next = head; while (n-- > 0) { fast = fast.next; } while (fast != null) { pre = slow; slow = slow.next; fast = fast.next; } pre.next = slow.next; return dummy.next; } }
83. 删除排序链表中的重复元素
/** * 当前节点和下一个相等的时候偏移两个位置 */ class Solution { public ListNode deleteDuplicates(ListNode head) { ListNode curr = head; while (curr != null && curr.next != null) { if (curr.val == curr.next.val) curr.next = curr.next.next;//curr和next相等 不移动curr 移动next去重 else curr = curr.next;//不等curr向后移动 } return head; } }
dp
考逻辑能力
72. 编辑距离
class Solution { public int minDistance(String word1, String word2) { int l1 = word1.length(), l2 = word2.length(); int[][] dp = new int[l1 + 1][l2 + 1]; //初始化第一列 hourse -->0 编辑距离5 for (int i = 1; i <= l1; i++) dp[i][0] = dp[i - 1][0] + 1; //初始化第一行 ros -->0 编辑距离3 for (int j = 1; j <= l2; j++) dp[0][j] = dp[0][j - 1] + 1; for (int i = 1; i <= l1; i++) { for (int j = 1; j <= l2; j++) { if (word1.charAt(i - 1) == word2.charAt(j - 1)) dp[i][j] = dp[i - 1][j - 1]; else dp[i][j] = Math.min(dp[i - 1][j - 1], Math.min(dp[i - 1][j], dp[i][j - 1])) + 1; } } return dp[l1][l2]; } }
basic
设计模式
详见我设计模式脑图
系统设计题目
设计一个用户签到服务 需要统计用户自己的每月签到天数 和系统每天的签到人数
Redis BitMap 用户当天签到存userid_month:bitmap(31) 当天签到人数 day:bitmap
通用
1. 遇到的挑战 怎么解决的?
2. 从几家公司离职的原因?
个人发展原因 我是个渴望进步的人。 在这里有更强的同事,业务量更大的项目,我能进步的更快 加速度更大。
3. 优点 缺点?
优点
有责任心 对代码质量有要求(符合设计原则) 有学习能力: 新来的架构师是用go要重写项目,2周学习go可以编写复用组件以及正常开发,支撑业务所有异步打点任务
缺点
有时候会遇到难题会陷进去耽误时间。设置止损时间 超过一小时还卡主就要求助别人了
4. 你还有什么需要问我的?
面得不好:您觉得我哪块还需要加强呢?
通用: 根据面试官的描述找一个点来问? 咱们公司看重人的哪些能力呢?
5. 冰山模型
知识技能
代码 中间件等
能力
沟通能力
学习能力
逻辑能力
性格
自我驱动
Spring
实现原理?
两大核心功能IOC AOP
IOC
1.AbstractApplicationContex.refresh()先初始化xml中所有BeanDefination到DefaultListenableBeanFactory中 2.AbstractApplicationContext.finishBeanFactoryInitialization(beanFactory)中初始化所有bane
AOP
使用BeanPostProcessor为源对象 使用jdk代理或者cglib生成代理对象
如何解决循环依赖?
构造注入和多例模式不可以解决循环依赖
使用三级缓存,把创建一半的对象先放入缓存。 a<-->b循环依赖。 1.创建a放入三级缓存 发现a依赖b去创建b。 2.创建b的过程中发现依赖a 先依赖未来创建好的a,并且b放入二级缓存 3.b创建完毕从二级缓存提升到singletonObjects 一级缓存中 4.a依赖创建好的b 提升到一级缓存 解决循环依赖
网络
三次握手原理
建立TCP连接需要三次握手 客户端A,服务端B 1.A向B发送SYN=1 seq=x 2.B向A发送SYN=1 ACK=1,seq=y,ack=x+1 3.A检查 ack是否等于seq+1以及ACK是否等于1。 发送ACK=1,ack=y+1,seq=x+1。B确认ACK 和ack=y+1建立连接
四次握手原理
tcp是全双工 所以需要双向关闭需要四次 客户端A,服务端B A向B发送关闭,B也向A发送关闭 1.A向B发送FIN=1 seq=u 2.B收到后回ack=u+1 seq=v ACK=1 3.B向A发送ack=u+1 seq=w ACK=1 FIN=1 4.A确认后返回ACK=1 ack=w+1 seq=u+1
Https原理
1.请求https链接 服务端返回公钥 2.客户端验证证书机构是否合法 证书有效 生成对称秘钥 使用公钥进行加密,发送给服务端 3.服务端用私钥解密发送给客户端进行通讯
地址栏输入url发生了什么
1. 域名dns解析成服务器ip
2. 封装http数据包
3. 封装成tcp数据包
4. 三次握手建立TCP连接
5. 客户端发送请求
6. 服务器响应
7. 关闭TCP连接
tcp timewait原理
保证全双工的 TCP 连接正常终止。 四次挥手中主动发起断开连接的一方A会等待2MSL(1 msl 30s 60s等) 为了防止A发送ACK没有被B接受导致B重发FIN
保证网络中迷失的数据包正常过期。
解决:http请求都是server端发起断开 使用Keep-Alive增加TCP连接的复用
https://www.cnblogs.com/zhenbianshu/p/10637964.html
select poll epoll区别
实战问题
线上问题排查
方法论
及时止血
留下现场
定位问题
解决问题
复盘
https://mp.weixin.qq.com/s?__biz=MzIzOTU0NTQ0MA==&mid=2247497627&idx=1&sn=1e7a0019594a2320a086158416b7f732&chksm=e92aca94de5d438257607c5dd83b93ec8874555b85db595c4097ffbfd5a384aad5cc99a640ae&mpshare=1&scene=24&srcid=0717zZJbnuoDSTs7JkcGjMO5&sharer_sharetime=1594990688432&sharer_shareid=121aad91e07791e1fa372050d8b644bb#rd
https://mp.weixin.qq.com/s?__biz=MzIzOTU0NTQ0MA==&mid=2247500651&idx=1&sn=a9659f9d8ea118a0dcff29c60f8043fa&chksm=e92afe64de5d777250572bf554c9a135e6cc283572571310346f53c86a0dc10aa7da82584d78&mpshare=1&scene=24&srcid=0204suc72jXKuusHQqxpHfPo&sharer_sharetime=1612416586710&sharer_shareid=121aad91e07791e1fa372050d8b644bb#rd
负载过高排查
https://www.cnblogs.com/aspirant/p/11476884.html