先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
正文
-
2.2.2 CopyOnWrite
-
2.2.3 锁
-
2.2.4 AQS
-
2.2.5 CAS
-
2.2.5 ReentrantLock 互斥锁
-
2.3 线程池
-
- 2.3.1 ThreadPoolExecutor
-
2.3.2 线程池拒绝策略
-
2.3.3 ScheduledThreadPoolExecutor
-
3. java 基础
-
- 3.1 spring 启动的流程(12步)
-
3.2 BeanFactory 创建流程
-
3.3 Bean 创建流程
-
3.4 Bean 生命周期(11步)
-
3.5 **Autowired 和Resource 注解的区别**
-
3.6 循环依赖
-
3.7 事务的传播行为(7中类型)
-
3.8 spring mvc 请求处理流程(10步)
-
3.9 过滤器拦截器
-
3.10 springboot 启动流程(9步)
-
3.11. 说说 hashMap 的原理
-
3.12. synchronized 和 ReentrantLock 的区别
-
4. jvm
-
- 4.1 jvm 内存模型
-
4.2 jvm 堆
-
- 4.2.1 堆内存分布
-
4.2.2 对象分配过程
-
4.3 方法区
-
4.4 类加载的执行过程
-
4.5 双亲委派模型
-
4.6 判断对象已死算法
-
4.7 引用类型
-
4.8 垃圾回收算法
-
- 4.8.1 分代收集理论
-
4.8.2 标记清除算法
-
4.8.3 标记复制算法
-
4.8.4 标记整理算法
-
4.9 垃圾收集器
-
- 4.9.1 CMS 收集器
-
4.9.2 G1 收集器
-
4.9.3 ZGC 收集器
-
5. 设计模式
-
- 5.1 单例模式
-
5.2 工厂模式
-
5.3 构建者模式
-
5.4 代理模式
-
5.5 适配器模式
-
6. 数据结构和算法
-
- 6.1 数据结构
-
- 6.1.1 线性表
-
- 6.1.1.1数组
-
6.1.1.2链表
-
6.1.1.3 栈
-
6.1.1.4 队列
-
6.1.2 散列表
-
6.1.3 树
-
- 6.1.3.1 二叉树
-
6.1.3.2 满二叉树
-
6.1.3.3 完全二叉树
-
6.1.3.4 平衡二叉树
-
6.1.3.5 二叉查找树
-
6.1.3.6 红黑树
-
6.1.3.7 B 树
-
6.1.3.8 B+ 树
-
6.2 算法
-
- 6.2.1 排序
-
- 6.2.1.1 冒泡
-
6.2.1.2 快排
-
6.2.1.3 堆排序
-
7. 消息队列
-
- 7.1 消息队列的使用场景
-
7.2 RocketMQ 的角色
-
7.3 RocketMQ 的执行流程
-
7.4 RocketMQ 消息过滤
-
7.5 零拷贝
-
7.6 同步复制异步复制
-
7.7 刷盘机制
-
7.8 延时消息
-
7.9 事务消息
-
7.10 顺序消息
-
8. mysql
-
8.1 mysql 体系架构
-
- 8.2 mysql 运行机制
-
8.3 mysql 存储引擎InnoDB
-
- 8.3.1 内存结构
-
8.4 mysql 索引
-
- 8.4.1 普通索引
-
8.4.2 唯一索引
-
8.4.3 主键索引
-
8.4.4 复合索引
-
8.4.5 聚集索引
-
8.4.6 索引原理
-
8.4.7 like 查询
-
8.4.8 explain 有哪些字段
-
8.4.9 慢查询优化
-
8.5 mysql 锁
-
- 8.5.1 锁分类
-
8.5.2 悲观锁
-
8.5.3 乐观锁
-
8.5.4 死锁
-
8.6 mysql 事务
-
- 8.6.1 事务特性
-
8.6.2 事务隔离级别
-
8.6.3 MVCC
-
8.7 mysql 分库分表
-
- 8.7.1 主键策略
-
8.7.2 分片策略
-
8.8 mysql 主从同步
-
- 8.8.1 适用场景
-
8.8.2 主从同步作用
-
8.8.3 主从同步实现原理
-
9. redis
-
- 9.1 跳跃表
-
9.2 字典
-
9.3 压缩列表
-
9.4 快速列表
-
9.5 缓存过期和淘汰策略
-
- 9.5.1 过期删除策略
-
- 9.5.1.1 定时删除
-
9.5.1.2 惰性删除
-
9.5.1.3 主动删除(定期删除)
-
9.5.2 淘汰策略
-
- 9.5.2.1 LRU
-
9.5.2.2 随机
-
9.5.2.3 volatile-ttl
-
9.6 缓存穿透、缓存雪崩、缓存击穿
-
- 9.6.1 缓存穿透
-
9.6.2 缓存雪崩
-
9.6.3 缓存击穿
-
9.7 单线程的Redis 为什么这么快
-
9.8 redis 分布式锁实现
-
10. 中间件
-
- 10.1 zookeeper
-
-
- 10.1.1 zookeeper 数据结构
-
-
10.1.2 监听器
-
10.1.3 zookeeper 应用场景
-
10.1.4 ZAB 协议
-
10.2 dubbo
-
11. 分布式
-
- 11.1 分布式下读写一致性
-
11.2 单调一致性
-
11.3 CAP 理论
-
11.4 BASE 理论
-
11.5 2PC 和 3PC
-
11.6 paxos 一致性算法
-
11.7 Raft 一致性算法
-
12. mybatis
-
- 12.1 mybatis 初始化
-
12.2 sql 执行过程
-
12.3 mybatis 插件
这是本人整理的数万字的面试笔记,基本上涵盖了 Java 领域的所有技术栈,本人也是凭借这份面试笔记斩获了近 10 个 offer,面试成功率高达80%。当然这份笔记是我根据自身的经验和技术栈整理的,自己觉得很重要的或者记不清的就会记录记录下来,面试被问到的时候也有回答的思路。现在共享给大家,希望对准备面试的小伙伴有帮助。
1. 微服务
1.1 主流注册中心对比
zookeeper:zookeeper 作为注册中心主要是因为它具有节点变更通知功能。只要客户端监听相关服务节点,服务节点有所变更就能及时的通知到监听客户端,非常方便。zookpeeper 是cp模式的。
eureka:是 netflix 开源的 基于RestFulAPI 风格开发的服务注册和发现组件。现在不会再更新。
consul:是使用go 语言开发的支持多数据中心分布式高可用的服务发布和注册的服务软件。
nacos:是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
1.2 eureka 心跳检测和自我保护机制
心跳检测:eureka 每隔30 秒就会向注册中心续约心跳一次,也就是报活。如果没有续约,租约在90s 后到期,然后服务就会失效,每隔30s的续约操作,就是心跳检测。
自我保护:eureka 服务端如果在15 分钟内,超过 85% 的客户端节点都没有正常心跳,那么Eureka 就认为客户端和注册中心出现了网络故障。而微服务本身是可正常运行的,此时不应该移除这个微服务,所以引入了自我保护机制。在自我保护状态下,不会移除任何服务,能接受新服务的注册和查询请求,但是不会和集群中其他节点同步。
1.3 Ribbon 负载均衡策略
1、轮寻策略
2、随机策略
3、重试策略:一定时间内循环重试。继承随机策略。
4、最小连接数策略:遍历serverList,选出可用且连接数最小的server.
5、可用过滤策略:扩展了轮寻策略,会先通过默认的轮询选取一个 server,再判断 server 是否超时可用。
6、区域权衡策略:扩展了轮询策略。除了过滤连接超时和连接数过多的server,还会过滤掉不符合要求的 zone 区域里面所有的节点。然后在剩下的节点中轮询获取。
1.4 Hystrix 舱壁模式
使用舱壁避免了单个工作负载(或服务)消耗掉所有资源,从而导致其他服务出现故障的场景。这种模式主要是通过防止由一个服务引起的级联故障来增加系统的弹性。通过应用舱壁模式,可以保护有限的资源不被耗尽。为了避免问题服务请求过多导致正常服务⽆法访问,Hystrix 不是采⽤增加线程数,⽽是单独的为每⼀个控制⽅法创建⼀个线程池的⽅式,这种模式叫做“舱壁模式"。
1.5 Hystrix 工作模式
1、当调⽤出现问题时,开启⼀个时间窗(10s)
2、在这个时间窗内,统计调⽤次数是否达到最⼩请求数?
-
如果没有达到,则重置统计信息,回到第1步;
-
如果达到了,则统计失败的请求数占所有请求数的百分⽐,是否达到阈值?
-
如果达到,则跳闸(不再请求对应服务)
-
如果没有达到,则重置统计信息,回到第1步
3、如果跳闸,则会开启⼀个活动窗⼝(默认5s),每隔5s,Hystrix 会让⼀个请求通过,到达那个问题服务,看是否调⽤成功,如果成功,重置断路器回到第1步,如果失败,回到第3步。
1.6 Fein
fegin 是NetFlix 开发的一个轻量级 RestFulf 的 HTTP 服务客户端。用来进行远程调用。
1.7 gateway
gateway 核心逻辑就是路由转发加执行过滤器链。
客户端向gateway 发送请求。然后在gateway handler Mapping 中找到与请求相匹配的路由,然后handler 通过制定了过滤器链来将请求发送到我们实际服务执行业务逻辑,然后返回。在过滤器中可以进行参数加解密,参数校验、权限校验、日志输出、协议转换等等。
2. 并发编程
2.1 多线程
2.1.1 创建一个阻塞队列
核心思想就是当队列为空时,调用出队列的方法,会进行wait() 等待。直到有数据入队列时,调用notify() 方法。同样的在队列满是,入队列会阻塞,直到有出队列才唤醒。
public class MyBlockingQueue {
private int[] data=new int[10];
private int putIndex;
private int getIndex;
private int size;
public synchronized void put(int val){
if(size==data.length){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
put(val);
}else {
data[putIndex]=val;
putIndex++;
if(putIndex==data.length){
putIndex=0;
}
size++;
}
}
public synchronized int get(){
if(size==0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
return get();
}else {
int val = data[getIndex];
getIndex++;
if(getIndex==data.length){
getIndex=0;
}
size–;
return val;
}
}
}
2.1.2 为什么wait() 方法要释放锁。
因为当一个线程进入一个 synchronized 修饰的同步方法后,会锁住当前对象,调用 wait() 方法进入阻塞状态。如果不释放当前对象的锁的话,其他线程永远获取不到当前对象的锁,也就没有办法唤醒当前线程了,这样就形成了死锁。所以在调用wait() 方法会释放锁,等待其他线程获取当前对象锁,调用notify() 唤醒此线程。然后此线程重新获得锁,执行剩下的操作,执行结束后,会再次的释放锁。
2.1.3 volatile 关键字的作用
1、保证64位写入的原子性。
2、内存可见性
3、禁止指令重新排序。
2.1.4 synchronized
synchronized 实现原理是获取不到锁先自旋,自旋依然获取不到锁,再阻塞。
2.2 JUC
2.2.1 阻塞队列
阻塞队列有基于数组实现的ArrayBlockQueue 和基于 链表实现的 LinkBlockQueue。以及还有优先级的 PriorityBlockQuere,按照元素的优先级出队列,实现了Comparable 接口。以及延时队列。根据延时时间大小出队列的,实际上是未来时间减去当前时间放入,DelayQueue 中的元素。如果getDelay() 小于等于0,说明该元素到期,可以出队列了。同步队列没有容量,先调用put(),线程就会阻塞,只要等另一个线程调用了task(),两个线程才会唤醒。
2.2.2 CopyOnWrite
是指在写的时候,不会直接操作源数据,而是先copy 一份数据进行修改,然后通过悲观锁或者乐观锁的方式写回。这样的好处是,读不用加锁。
CopyOnwriteArrayList 读操作的时候不会加锁,只有写的时候才会加同步锁,所以是线程安全的 ArrayList。
CopyOnwriteArraySet 就是用array 实现的一个线程安全的 Set .保证所有元素不重复,封装的是CopyOnwriteArrayList 。利用CopyOnwriteArrayList 的addAllAbsent() 方法。
2.2.3 锁
为了实现一把具有阻塞或唤醒功能的锁,需要几个核心要素:
-
需要一个state变量,标记该锁的状态。state变量至少有两个值:0、1。对state变量的操作,使用CAS保证线程安全。
-
需要记录当前是哪个线程持有锁。
-
需要底层支持对一个线程进行阻塞或唤醒操作。
-
需要有一个队列维护所有阻塞的线程。这个队列也必须是线程安全的无锁队列,也需要使用CAS。
2.2.4 AQS
AbstractQueuedSynchronizer 的核心就是一个双向链表形成的阻塞队列以及CAS.
2.2.5 CAS
CAS 名为:compare and swap。是比较内存中的值是否和预期值一致,如果一致就进行更新。这个过程是原子的。cas 有三个操作数,内存值,预期值和新值。只有当内存值和预期值相等时,才会将内存值更新为新值。否则什么都不做。底层是通过unsafe 类保证原子性的,unsafe类中都是native 方法,可以直接操作内存。
缺点:存在ABA 问题。解决方案就是加版本号,或者加时间戳。
2.2.5 ReentrantLock 互斥锁
Condition 本身也是一个接口,其功能和wait/notify类似,Condition 也必须和Lock一起使用。因此,在Lock的接口中,有一个与Condition相关的接口。
await() 是获取锁线程阻塞方法。signal() 是唤醒线程的方法。
2.3 线程池
线程池是一个典型的生产者消费者模型。线程池的核心是使用阻塞队列。我们常用的就是 ThreadPoolExecutor。
2.3.1 ThreadPoolExecutor
ThreadPoolExecutor 主要是 包含一个阻塞队列和一组线程集合 Workers。每个线程是一个worker 对象,worker 继承了AQS 。
ThreadPoolExecutor 包含7 个参数。
corePoolSize:在线程池中始终维护的线程个数。核心线程数
maxPoolSize:在corePooSize已满、队列也满的情况下,扩充线程至此值。最大线程数
keepAliveTime/TimeUnit:maxPoolSize 中的空闲线程,销毁所需要的时间,总线程数收缩回corePoolSize。存活时间和单位
blockingQueue:线程池所用的队列类型。阻塞队列
threadFactory:线程创建工厂
RejectedExecutionHandler:corePoolSize已满,队列已满,maxPoolSize 已满,最后的拒绝策略。
提交过程的核心流程:
1、先判读当前线程数是否小于 corePoolSize 。如果小于就新建线程执行
2、如果大于,就判断阻塞队列是否已经满了,如果没有满,就加入阻塞队列中。
3、如果满了,就判断当前线程数是否小于 maxPoolSize 。如果小于,就直接创建线程执行。如果大于就根据拒绝策略,拒绝任务。
线程池的关闭:
在调用shutdown() 或者shutdownNow() 之后,线程池并不会立即关闭,会等待所有任务执行完成之后,才会关闭线程池。
shutdown() 不会清空任务队列,并且只会中断空闲线程。
shutdownNow() 会清空任务队列,并且中断所有的线程。
2.3.2 线程池拒绝策略
线程池有四种拒绝策略
1、丢弃任务,并抛出异常
2、丢弃任务,单不抛出异常
3、丢弃队列最前面的任务,然后重新提交当前任务。
4、线程池什么都不做,由当前线程自己处理。
2.3.3 ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor实现了按时间调度来执行任务。有延迟任务和周期性任务。底层实现一个延迟队列来实现的。
3. java 基础
3.1 spring 启动的流程(12步)
1、prepareRefresh()。刷新前预处理。主要是设置启动时间,以及初始化配置文件中的占位符,以及校验配置信息是否正确。
2、obtainFreshBeanFactory()。获取 Beanfactory。
3、prepareBeanFactory(beanFactory)。Beanfactory 的准备工作,对BeanFactory 的一些属性进行配置。比如 context 的类加载器
4、postProcessBeanFactory()。BeanFactory 准备工作完成后,进行的后置处理工作。是一个钩子函数。
5、invokeBeanFactoryPostProcessors(beanFactory)。实例化并调⽤实现了BeanFactoryPostProcessor接⼝的Bean
6、registerBeanPostProcessors(beanFactory)。注册BeanPostProcessor(Bean的后置处理器),在创建Bean的前后执行
7、initMessageSource()。初始化 MessageSource 组件,并将信息加入allpication.singletonObjects 中。
8、initApplicationEventMulticaster()。初始化事件派发器,并加入到singletonObjects 中
9、onRefresh()。子类重写这个方法,在容器刷新时可以自定义逻辑。
10、registerListeners()。注册应用监听器。 就是 注册 ApplicationListener 接口监听器 bean
11、finishBeanFactoryInitialization()。初始化所有没有设置延时加载的Bean
12、finishRefresh()。完成context 的刷新,发布事件。
3.2 BeanFactory 创建流程
1、判断是否已经存在BeanFactory,如果存在,就销毁Bean 和BeanFactory。
2、创建 BeanFactory
3、为当前BeanFactory 设置序列化id
4、将bean 对象加载到 BeanFactory 对象的 beanDefinitionMap 中。
5、返回 BeanFactory
3.3 Bean 创建流程
1、初始化所有剩下的⾮懒加载的单例bean
2、初始化创建⾮懒加载⽅式的单例Bean实例(未设置属性)
3、填充属性
4、初始化⽅法调⽤(⽐如调⽤afterPropertiesSet⽅法、init-method⽅法)
5、调⽤BeanPostProcessor(后置处理器)对实例bean进⾏后置处
3.4 Bean 生命周期(11步)
1、根据配置情况调用 Bean的构造方法或者工厂方法实例化Bean
2、利用依赖注入完成Bean的所以属性值的配置注入
3、如果Bean 实现了BeanNameAware 接口,则spring 调用Bean的setBeanName() 传入当前Bean的id
4、如果Bean实现了BeanFactoryAware 接口,调用setBeanFactory() 方法传入当前工厂实例的引用
5、如果Bean 实现了ApplicationContextAware 接口,通过调用setApplicationContext 传入当前applicationContext 实例的引用。
6、如果BeanPostProcessor 和Bean 关联,则Spring 将调用改接口的预初始化方法。postProcessBeforeInitialization() 是前置处理的方法,Spring的AOP就是利用它实现的。
7、如果Bean 实现了InitializingBean 接口,需要实现afterPropertiesSet 方法
8、如果在配置文件中通过init-method 属性指定了初始化方法。则的调用该方法。
9、如果BeanPostProcessor 和Bean 关联,则Spring 将调用该接口的初始化方法postProcessAfterInitialization().此时Bean可以被应用系统使用。
10、如果在Bean标签中指定了Bean的作用范围的scope=“singleton” 则将改Bean 方法singletonObjects的缓存池中
11、如果Bean 实现了DisposableBean 接口,则spring会调用destory() 方法 将Spring中的Bean销毁。
3.5 Autowired 和Resource 注解的区别
1、autowired 是spring 提供的注解,@Resource是javaee 提供的注解。
2、Autowired 采用的是按类型注入。当一个类有多个Bean的时候需要配合@Qualifier 来指定唯一的Bean。而@Resource 默认安装byName 自动注入。
3、@Resource 可以执行 name 和type .可以通过type 注入,也可以通过name 来注入。
3.6 循环依赖
利用三级缓存来解决循环依赖的。
1、当创建对象A的Bean 的时候,会先将A对象Bean 放入三级缓存,然后填充属性;
2、发现依赖对象B ,但是对象B 还没有创建,所以就创建对象B
3、对象B在创建过程中,发现依赖对象A,就先从一级缓存中获取,没有获取到就从二级缓存中找,没有找到就从三级缓存中找,找到了还没有创建完成的对象A .然后将对象A 进行一些处理移入二级缓存中。
4、这样对象B就可以继续完成Bean 创建的其他步骤知道完全创建好。
5、B 对象的Bean 创建好之后,放入了一级缓存SingLetonObject中,对象A可以继续完成创建。最终创建好的A对象也会进入一级缓存中。
3.7 事务的传播行为(7中类型)
事务往往在service 层控制,如果在 service 层方法A调用另个 service 的方法 B 。A和B 本身都本身都添加了事务控制,那么在A调用B 的时候就需要进行事务的一些协商,这就是事务的传播行为。
有如下7中类型:
A调用B,站在B的角度来定义传播行为。
1、如果当前没有事务,就新建事务;如果存在一个事物,就加入到当前事务中。这是最常见的一种类型。
2、支持当前事务,如果没有事务,就以非事务执行。
3、支持当前事务,如果没有事务,就抛出异常。
4、新建事务,如果当前存在事务,就将当前事务挂起。
5、以非事务运行,如果存在当前事务,就将当前事务挂起。
6、以非事务运行,如果存在当前事务,就抛出异常。
7、如果当前存在事务,就在嵌套事务内执行。如果当前没有事务,就创建一个事物执行。
3.8 spring mvc 请求处理流程(10步)
1、DispatcherServlet 接收到客户端发送的请求。
2、DispatcherServlet 收到请求调用HandlerMapping 处理器映射器。
3、HandleMapping 根据请求URL 找到对应的handler 以及处理器 拦截器,返回给DispatcherServlet
4、DispatcherServlet 根据handler 调用HanderAdapter 处理器适配器。
5、HandlerAdapter 根据handler 执行处理器,也就是我们controller层写的业务逻辑,并返回一个ModeAndView
6、HandlerAdapter 返回ModeAndView 给DispatcherServlet
7、DispatcherServlet 调用 ViewResolver 视图解析器来 来解析ModeAndView
8、ViewResolve 解析ModeAndView 并返回真正的view 给DispatcherServlet
9、DispatcherServlet 将得到的视图进行渲染,填充到request域中
10、返回给客户端响应结果。
3.9 过滤器拦截器
-
过滤器(Filter):对Request请求起到过滤的作⽤,作⽤在Servlet之前,如果配置为/*可以对所有的资源访问(servlet、js/css静态资源等)进⾏过滤处理
-
拦截器(Interceptor):是SpringMVC、Struts 等表现层框架⾃⼰的,不会拦截 jsp/html/css/image 的访问等,只会拦截访问的控制器⽅法(Handler)。拦截器会作用三个地方:在 handler 执行之前执行一次,用来校验参数合理性,handler 之后、视图解析器之前执行一次,视图解析之后执行一次。
3.10 springboot 启动流程(9步)
1、加载并启动监听器
2、创建项目运行环境,并加载配置
3、创建 spring 容器。
4、运行spring 容器的前置处理。主要是容器刷新之前的准备工作,设置容器环境,并且将启动类注入容器,为后面开启自动化配置服务。
5、刷新是spring 容器。通过refresh() 方法对整个IOC 容器初始化。包含bean 资源点定位解析注册等。
6、运行spring 容器后置处理器。扩展接口,如果有自定义需求,可以重写该方法。
7、发布结束执行的事件。
8、执行自定义执行器。
9、返回容器。
3.11. 说说 hashMap 的原理
在jdk 1.7 版本,hashmap 的数据结构是 数组加链表
在jdk 1.8 版本,hashmap 的数据结构是数组加链表叫红黑树。当数组长度大于64 且链表长度大于8时,链表就会转换成红黑树。
3.12. synchronized 和 ReentrantLock 的区别
1、synchronized 是 JVM 隐式实现的,而 ReentrantLock 是 Java 语言提供的 API;
2、ReentrantLock 可设置为公平锁,而 synchronized 却不行;
3、ReentrantLock 只能修饰代码块,而 synchronized 可以用于修饰方法、修饰代码块等;
4、ReentrantLock 需要手动加锁和释放锁,如果忘记释放锁,则会造成资源被永久占用,而 synchronized 无需手动释放锁;
5、ReentrantLock 可以知道是否成功获得了锁,而 synchronized 却不行。
4. jvm
4.1 jvm 内存模型
jvm 内存分为:程序计数器、虚拟机栈、本地方法栈、堆、方法区。
程序计数器:线程私有。是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。
虚拟机栈:线程私有。用于存储栈帧。每个方法在执行时都会创建一个栈帧(Stack Frame)**,**用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直到执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
本地方法栈:线程私有。用于 虚拟机 Native 方法
堆:线程共享、保存对象实例,所有的对象实例都在堆上存储。
方法区:线程共享。存储被虚拟机加载的类信息、常量、静态变量等。
4.2 jvm 堆
4.2.1 堆内存分布
jvm 将堆内存分为 年轻代和年老代。年轻代和年老代的比例为1:2 。年老代用来存储存活时间比较长或者大对象。年轻代用来存储错过时间比较短的对象。年轻代中又分为Eden 区和两个Survivor 区,比例为8:1:1。
4.2.2 对象分配过程
1、首先会将对象放入年轻代的 Eden 区,如果Eden 区能发下就放入。
2、如果 Eden 区放不下了,就会触发 YangGC。会对Eden 区和使用的 Survivor 区进行垃圾回收,存活的对象保存到另一个空闲的Survivor 区。然后将新对象放入 Eden 区。
3、当Survivor 区有对象经历 15 次 yangGC 后还存活,就迁移到年老代。15次是默认的,可以调整。
4、如果新增对象大小超过Eden 区 一半时,会直接加入年老区。如果年老区 能放下就放入。
5、如果年老区放不下,就会触发OldGC。
6、如果OldGC 后,还是放不下对象,就会触发FullGC
7、如果FullGC 还放不下,就会报OOM 异常。
4.3 方法区
方法区保存的内容:类型信息、域信息、方法信息、运行时常量池。元空间用来存储类的元信息,存储位置为本地内存。静态变量和常量池存储在堆中。
4.4 类加载的执行过程
类的生命周期:包含7个阶段,有加载、验证、准备、解析、初始化、使用和卸载。
加载:分为预加载和运行时加载、预加载是在JVM启动的时候就加载的class 文件。加载的都是lib/rt.jar 下的 .class 文件。运行时加载是指我们一般的class 文件,只有在用到的时候会先去内存中查一下有没有,没有才会进行加载到内存中。主要获取 .class 文件的二进制流。类信息、静态变量、字节码、常量这些信息会存入 方法区中。
连接分为验证、准备和解析
验证:保证 .class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全。
准备:为类的变量分配内存并设置其初始值。这里的初始值是 默认初始值。,比如"public static int value = 123",value在准备阶段过后是0而不是123,给value赋值为123的动作将在初始化阶段才进行;比如"public static final int value = 123;"就不一样了,在准备阶段,虚拟机就会给value赋值为123。
解析:是虚拟机将常量池内的符号引用替换为直接引用的过程。符号引用是一种定义,可以是任何字面上的含义,而直接引用就是直接指向目标的指针、相对偏移量。
初始化:类的初始化阶段是类加载的最后一个过程。初始化阶段就是执行类构造器方法的过程。为类变量进行初始化赋值。这里就是我们代码中定义的值了。
4.5 双亲委派模型
如果一个类加载器收到类加载的请求,它首先不会自己尝试加载这个类,而是把这个请求委派给自己的父类加载器完成,每个类加载器都是如此,只有当父类加载器在自己范围内找不到指定类时,又会委派自己的子类进行加载。这就是双亲委派模型。这样做的好处是:让类加载尽量都交给父类加载,这样就可以避免不同的子类都需要进行类加载,提升了效率,也更加安全。
4.6 判断对象已死算法
1、引用计数算法。当该对象被应用了,计数器就+1,引用失效了,就-1。当计数器值为0 时表示没有被使用,可以回收了。不能解决循环引用的问题。
2、可达性分析算法。从一系类的 GC root 的对象作为起点,向下搜索到对象的路劲。如果对象没有和 GC root 对象不存在路劲。就会被标记需要清除。
GC root 对象有哪些?
1、栈帧中局部变量表的reference 引用所引用的对象。
2、方法区中 static 静态引用的对象。
3、方法区中final 常量引用的对象。
4、所有被同步锁持有的对象。
5、java 虚拟机内部的引用,如基本数据类型的对象、异常对象、系统类加载器等
finalize() 方法
如果对象进行可达性分析,发现对象没有和 GCroot 相连接的引用链,会被第一次标记,随后进行一次筛选,判断是否有必要执行 finalize() 方法。
如果没有重载finalize() 方法,或者虚拟机已经执行过了该对象的 finalize() 方法,则判定为没有必要,直接等待回收。如果判定有必要执行 finalize() 方法。就会放入F-Queue 队列中,随后由低优先级的线程执行 finalize() 方法。在方法中,可以将当前对象和GCroot 对象建立引用链,摆脱被回收的命运。否则执行完 finalize() 依旧会被回收。
4.7 引用类型
java虚拟机有4中引用类型:
强引用:进行垃圾回收时,垃圾回收器不会对强引用的对象进行回收。
软引用:只要内存足够,垃圾回收器就就不会回收,只要当内存不足时才会对软引用的对象进行回收。
弱引用:在垃圾回收器执行垃圾回收时,不管内存够不够,都会对弱引用的对象进行回收。
虚引用: 它是最弱的一种引用关系。如果一个对象仅持有虚引用,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。
4.8 垃圾回收算法
4.8.1 分代收集理论
基于两个假说:
1、大部分对象都是朝生熄灭的
2、存活时间越久的对象越难以消亡。
4.8.2 标记清除算法
算法分为“标记”和“清除”两个阶段: 首先标记出所有需要回 收的对象, 在标记完成后,统一回收掉所有被标记的对象, 也可以反过来, 标记存活的对象, 统一回收所有未被标记的对象
缺点:内存空间碎片化严重。
4.8.3 标记复制算法
当这一块的内存用完了, 就将还存活着 的对象复制到另外一块上面, 然后再把已使用过的内存空间一次清理掉。
缺点:每次有一半的内存空闲,利用率不高。如果99%的对象都是存活的(老年代),那么老年代是无法使用这种算法的。
4.8.4 标记整理算法
的“标记-整 理”(Mark-Compact) 算法, 其中的标记过程仍然与“标记-清除”算法一样, 但后续步骤不是直接对可回收对象进行清理, 而是让所有存活的对象都向内存空间一端移动, 然后直接清理掉边界以外的内存 。
4.9 垃圾收集器
4.9.1 CMS 收集器
CMS 收集器分为4个步骤:
1、初始标记: 这个阶段会stop-the-world 。主要是来标记 GC roots 能够直接关联到的对象。标记之后恢复暂停的所有应用。操作速度是比较快的。
2、并发标记:这个阶段是GC roots 直接关联到的对象开始遍历标记整个对象图的过程,耗时较长,但不用暂停用户线程。可以并发执行。
3、重新标记:为了修正并发标记阶段用户线程运行导致标记发生变动,从而进行重新标记,也会stop-the-world 。耗时比初始标记会长一点。
4、并发清理:清理哪些被标记为死亡的对象,并释放内存空间。采用标记清除算法,不用移动存活对象,可以并发执行。
4.9.2 G1 收集器
G1 收集器特点:
1、G1 把堆内存分为多个独立的 region 区域。
2、G1 沿用了分代思想。保留了年轻代、年老代。但他们不再物理隔离。都通过Region 存储。
3、G1 采用标记整理算法,局部采用标记复制算法。不会产生内存碎片化。
4、G1 能充分利用多CPU 、多核硬件环境,尽量缩短STW
过程:
初始标记:和CMS 初始标记阶段一样,标记 GC roots 能够直接关联到的对象。会STW
并发标记:和CMS 并发标记阶段一样。GC roots 直接关联的对象整个对象树进行标记。
最终标记:修正并发标记阶段,因程序运行产生变化的那部分对象。
筛选回收:根据时间来进行价值最大化收集。
4.9.3 ZGC 收集器
ZGC 是java 11 版本中提供的高效垃圾回收算法,有以下特点:
1、使用了着色指针技术,利用指针额外信息位,在指针上对对象进行着色标记。
2、使用了读屏障,使得进行垃圾回收大部分时间都不需要STW,因此大部分时间都是并发处理的。
3、基于Region ,没有进行分代,也灭有固定Region 的大小,Region 是可以动态创建和销毁的。对大对象可以更好的管理。
4、压缩整理。在回收后会对 Region 对象进行移动合并,解决碎片化问题。
过程:
1、初始标记:在开始的时候会有短暂的 STW ,用户标记 GC Roots。
2、并发标记:这个阶段通过对象指针着色来标记,结合读屏障解决单个对象的并发问题。在最后会有一个短暂的STW 来解决边缘情况。
3、清理阶段:这个阶段会对不再使用的对象进行回收。
4、并发重定位:就是对存活的对象进行移动,来解决碎片化的问题。也是利用读屏障和用户线程并发处理的。
5. 设计模式
5.1 单例模式
保证一个类仅有一个实例,并提供一个访问它的方法。包含一个静态变量,一个私有的构造方法,一个 public 的静态方法。
饿汉式、懒汉式、双重校验锁。
public class SingLeton{
private volalite static SingLeton sing;
private SingLeton(){
}
public static SingLeton getSingLeton(){
if(sing==null){
synchronized(SingLeton.class){
if(sing==null){
sing=new SingLeton();
}
}
}
return sing;
}
}
静态内部类。
public class SingLeton{
private static class SingLetonInner{
public static final SingLeton sing=new SingLeton();
}
private SingLeton(){}
public static SingLeton getInstance(){
return SingLetonInner.sing;
}
}
5.2 工厂模式
工厂模式就是通过工厂来创建想要的对象,而不是自己去创建对象。这样降低了代码间的耦合度。比如一个服饰工厂可以生产衣服,裤子,鞋子,袜子,帽子等等。我们想要用衣服,不用自己来造了,而是和工厂说你想要什么,那工厂就给你生产什么。这就是工厂模式。主要是将创建对象的实例交给工厂类来完成,并进行管理。
工厂分为简单工厂模式、工厂方法模式和抽象工厂模式。
简单工厂模式:是工厂模式最简单的一种,实例有工厂直接创建。也就是上面的例子中,你想要衣服,那这个服饰工厂就给你做一件衣服给你。
工厂方法模式 :就是工厂本身不进行实体对象的创建,而是通过对应的下游工厂来创建然后在返回,有点总工厂子工厂的意思,总工厂管理着所有的子工厂,每个子工厂只生产指定的商品,当通知总部想要什么东西时,总部就通知对应的子工厂生产,拿到产品后再返回客户。
抽象工厂:就是一个抽象工厂类,里面只是声明可以实现哪些对象,但是具体的实现就交给具体的工厂完成。抽象工厂就好比包皮公司,它告诉你他可生产服饰,也就可以生产食品。那比如你想要衣服,它就给你一个服饰工厂的联系方式,你通过这个服饰工厂来获取到衣服。想要辣条,那他就给你推一个食品公司的联系方式,让这个食品公司给你做。
工厂方法模式和抽象工厂模式的区别:在于工厂方法模式主要是生产某一类商品。而抽象工厂,我不关心你怎么实现,只要你说你能做这个商品,我就可以为你代言。
5.3 构建者模式
将构建一个复杂的对象可以进行拆分成构建一个个简单的对象。然后组装成复杂的对象,就是构建者模式。比如mybatis 中为了构建Configuration 这个复杂对象,就先构建了Dadasource、MapperStment、等很多的对象组成,在解析SqlMapConfig配置文件的时候,就会先将各种配置文件封装到对应的对象中,最后组装成 Configuration 对象
5.4 代理模式
代理模式就是我们想要执行的方法通过代理对象来完成,就好比我们要抢票回家,我们不想自己盯着抢,所以就找代理帮我们抢从而达到目的。代理模式分为静态代理和动态代理。
静态代理:就是具体的代理类,在编译阶段就知道这个代理能做什么事情。就好比抢票的代理一样,它只能做抢票这个代理,而不能代你抢钱哈哈。
动态代理:动态代理就不同了,它在编译阶段也没有具体的实现。而是在程序运行期间根据JVM的反射机制动态生成的。比较出名的就JDK 动态代理 和cglib 动态代理。
JDK 动态代理:主要实现InvocationHandle 接口并实现其 invoke 方法。
Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
// 前置增强
…
// 调用原有业务逻辑
result = method.invoke(obj,args);
//后置增强
…
return result;
}
})
cglib 动态代理:需要引入其依赖,使用方法和JDK动态代理差不多。实现MethodInterceptor 接口并实现 intercept 方法。
public Object getCglibProxy(Object obj) {
return Enhancer.create(obj.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object result = null;
//前置增强
…
result = method.invoke(obj,objects);
//后置增强
…
return result;
}
});
}
JDK 动态代理是java 语言自带的功能,提供了稳定的支持。是通过拦截器和反射实现的,jdk 动态代理只能代理继承接口的类。
cglib 动态代理是第三方提供的工具,基于ASM 实现的,性能比较高,cglib 动态代理无需通过接口实现,它是通过实现子类的方式来完成调用的。
5.5 适配器模式
6. 数据结构和算法
6.1 数据结构
常见的数据结构如下:
6.1.1 线性表
6.1.1.1数组
有限个相同类型的变量组成的集合。物理地址连续(连续的内存空间存储)。
删除和插入元素的时候,后续的元素需要进行位移。
扩容数组的时候,需要先创建一个新数据,然后将原数组的值填充到新数组中。
优点:具有高效的随机访问能力。只需要给出下标,就可以常量时间找到对应元素。
缺点:插入和删除,会导致其他元素发生位移。影响效率。
6.1.1.2链表
链表是物理上不连续的节点组成。每个节点包含两部分。存储数据元素以及指向下个节点的指针。
常见的链表有:单链表,双链表,循环链表。
优点:插入,删除、更新的效率高。
缺点:查询的效率低,不能随机访问。在查询节点的时候,只能从头结点开始一个个的往后找。
6.1.1.3 栈
栈是一种线性结构,栈中的元素只能先入后出。最早进入的元素为栈底,最后进入的元素叫栈顶。
栈可以用数组实现也可以用链表实现。
6.1.1.4 队列
队列也是一种线性结构,只能先入先出。队列的出口端叫做队头,队列的入口端加做队尾。
队列可以用数组实现也可以用链表实现。
6.1.2 散列表
散列表也是 hash 表。这种数据结构提供了key 和value 的映射关系。只要给出 key ,就可以高效的查询出它所匹配的 value 。
6.1.3 树
树又分为二叉树和多叉树。
6.1.3.1 二叉树
二叉树每个节点最多只有两个子节点。
6.1.3.2 满二叉树
一个二叉树所有的非叶子节点都存在左右子节点。且所有的叶子节点都在同一层,就是满二叉树。
6.1.3.3 完全二叉树
如果二叉树的深度为k。k-1 层为满二叉树。第k 层,所有节点都连续集中在最左边,则是一颗完全二叉树。
6.1.3.4 平衡二叉树
左子树和右子树的深度差的绝对值不超过1。并且左子树和右子树也为平衡二叉树。
6.1.3.5 二叉查找树
在二叉树的基础上。如果有左节点,左节点的的值都小于根节点。如果有右节点,右节点的值都大于根节点。二叉查找树又叫二叉排序树。
6.1.3.6 红黑树
一种自平衡的二叉查找树。有以下特征:
1、每个节点不是红色节点就是黑色节点。
2、根节点是黑色。
3、每个叶子节点都是黑色的空节点。
4、如果一个节点是红色的。那么它的子节点必须为黑色。
5、每个节点到各个叶子节点的包含相同数量的黑节点。
6、新加入的节点为红色,然后校验是否满足红黑树的规则,。如果不满足则需要进行颜色翻转、左旋、右旋等操作。
6.1.3.7 B 树
最后总结
搞定算法,面试字节再不怕,有需要文章中分享的这些二叉树、链表、字符串、栈和队列等等各大面试高频知识点及解析
最后再分享一份终极手撕架构的大礼包(学习笔记):分布式+微服务+开源框架+性能优化
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
6.1.1.1数组
有限个相同类型的变量组成的集合。物理地址连续(连续的内存空间存储)。
删除和插入元素的时候,后续的元素需要进行位移。
扩容数组的时候,需要先创建一个新数据,然后将原数组的值填充到新数组中。
优点:具有高效的随机访问能力。只需要给出下标,就可以常量时间找到对应元素。
缺点:插入和删除,会导致其他元素发生位移。影响效率。
6.1.1.2链表
链表是物理上不连续的节点组成。每个节点包含两部分。存储数据元素以及指向下个节点的指针。
常见的链表有:单链表,双链表,循环链表。
优点:插入,删除、更新的效率高。
缺点:查询的效率低,不能随机访问。在查询节点的时候,只能从头结点开始一个个的往后找。
6.1.1.3 栈
栈是一种线性结构,栈中的元素只能先入后出。最早进入的元素为栈底,最后进入的元素叫栈顶。
栈可以用数组实现也可以用链表实现。
6.1.1.4 队列
队列也是一种线性结构,只能先入先出。队列的出口端叫做队头,队列的入口端加做队尾。
队列可以用数组实现也可以用链表实现。
6.1.2 散列表
散列表也是 hash 表。这种数据结构提供了key 和value 的映射关系。只要给出 key ,就可以高效的查询出它所匹配的 value 。
6.1.3 树
树又分为二叉树和多叉树。
6.1.3.1 二叉树
二叉树每个节点最多只有两个子节点。
6.1.3.2 满二叉树
一个二叉树所有的非叶子节点都存在左右子节点。且所有的叶子节点都在同一层,就是满二叉树。
6.1.3.3 完全二叉树
如果二叉树的深度为k。k-1 层为满二叉树。第k 层,所有节点都连续集中在最左边,则是一颗完全二叉树。
6.1.3.4 平衡二叉树
左子树和右子树的深度差的绝对值不超过1。并且左子树和右子树也为平衡二叉树。
6.1.3.5 二叉查找树
在二叉树的基础上。如果有左节点,左节点的的值都小于根节点。如果有右节点,右节点的值都大于根节点。二叉查找树又叫二叉排序树。
6.1.3.6 红黑树
一种自平衡的二叉查找树。有以下特征:
1、每个节点不是红色节点就是黑色节点。
2、根节点是黑色。
3、每个叶子节点都是黑色的空节点。
4、如果一个节点是红色的。那么它的子节点必须为黑色。
5、每个节点到各个叶子节点的包含相同数量的黑节点。
6、新加入的节点为红色,然后校验是否满足红黑树的规则,。如果不满足则需要进行颜色翻转、左旋、右旋等操作。
6.1.3.7 B 树
最后总结
搞定算法,面试字节再不怕,有需要文章中分享的这些二叉树、链表、字符串、栈和队列等等各大面试高频知识点及解析
最后再分享一份终极手撕架构的大礼包(学习笔记):分布式+微服务+开源框架+性能优化
[外链图片转存中…(img-MJwQx2gI-1713135473852)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-2BN1Fehs-1713135473853)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!