目录
- 1、线程池
- 2、并发
- 3、缓存
- 4、Spring相关
- 5、java语言特征
- 6、网络相关
- 7、jvm相关
- 8、操作系统相关
- 9、redis
- redis脑裂怎么产生的,怎么减少脑裂产生的数据损失
- 10、事务相关
- 11、数据库和缓存
- 12、队列
- 13、锁升级过程
- 14、springmvc执行过程
- 15、为什么区分度不高的字段不加索引(性别)
- 16、字段空值有什么影响
- 17、索引下推
- 18、ThreadLocal
- 19、@EnableCaching
- 20、SpringAop
- 21、springboot的jar为什么可以直接运行?
- 22、MAVEN
- 老年代中数组的访问方式
- GC 有环怎么处理
- GC 有环怎么处理
- 零拷贝
- kafka
- GC安全点和安全区域
1、线程池
线程池几种状态(状态的前3位是状态计算,后29位为线程数量计算)
private static final int RUNNING = -1 << 29; 二进制表示1110.0000共29个0
private static final int SHUTDOWN = 0 << 29; 二进制表示0000.0000共32个0
private static final int STOP = 1 << 29; 二进制表示0010.0000共29个0
private static final int TIDYING = 2 << 29; 二进制表示0100.0000共29个0
private static final int TERMINATED = 3 << 29; 二进制表示0110.0000共29个0
shutdown
//调用shutdown和shutdownNow后,不能再提交任务了。如果是shutdown,可以提交一个空任务的工作线程用于帮助处理队列中的任务
//当调用shutdown时,正在阻塞的工作线程(一般为核心线程或者设置了等待时间的非核心线程)会被中断(interruptIdleWorkers),被中断的线程在getTask方法里重新开始循环,
//1、当队列为空时,getTask返回null,被中断的线程就结束了。
//2、队列不为空,继续去队列拿任务执行,拿不到任务则返回null,被中断的线程就结束了。
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
// 在runWorker方法里,会获取每个任务(Worker)的锁
// 而interruptIdleWorkers也是去获取每个任务(Worker)的锁,获取成功了才会调用 // 这个线程的interrupt方法, 如果正在执行的线程
// 是获取不到锁的,也就不会调用这个线程的interrupt方法
// 也就是说任务只是addWork了,但是还没有开始运行,将这些还没开始运行的任务中断, // 已经提交的任务则等待其完成
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
shutdownNow
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
// 不用获取worker的锁,直接调用线程的interrupt方法中断所有线程,
// 并将队列清空
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 1、当调用shutdown时,如果队列不为空,会继续执行队列中的任务
// 2、当调用shutdownNow时,任何工作线程这里直接返回null,最终线程池退出
// 当调用shutdown时,会将运行状态设置为shutdown,
// 当调用shutdownNow时,会将运行状态设置为stop,
// 条件成立直接返回null,外部的while循环就可以退出了,那么线程池也就退出了
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 当队列为空后,非核心线程直接返回null
// 当超过最大线程数,超过的线程直接返回null,然后被回收
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 核心线程和非核心线程都会去队列拉取任务,
// 直到拉取到任务后退出当前getTask方法。
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
// 核心线程是不会走到这一步的,前面直接返回了r
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
// 1、从workers中移除当前工作线程
workers.remove(w);
} finally {
mainLock.unlock();
}
// 2、如果状态为RUNNING或者为SHUTDOWN并且队列不为空,不做任何操作
// 将STOP状态先设置为TIDYING,然后设置为TERMINATED
tryTerminate();
// 3、如果状态为RUNNING或者SHUTDOWN,那么如果核心线程数不够了再添一个线程,维持核心线程数不变
int c = ctl.get();
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false);
}
}
2、并发
CountDownLatch原理
// 初始化AQS的state值等于3
CountDownLatch ctl = new CountDownLatch(3);
主线程调用await阻塞,调用一次countDown()则state值减一,直到最后一个线程将state的值减为0的时候,唤醒阻塞的主线程。
线程同步方式
synchronized,juc工具类(CountdownLatch,CyclicBarrier,Semaphore)
3、缓存
CyclicBarrier原理
// 初始化CyclicBarrier成员变量count值,并且使用一个常量parties备份这个值,用于后面重置
CyclicBarrierctl = new CyclicBarrier(3);
每个线程调用await方法,调用时将count的值减1然后判断count的值是否等于0,,如果不等于零则使用condition.await方法阻塞。
直到最后一个线程将count减到0,然后将condition队列所有节点搬到主队列(signalAll方法),最后一个线程释放锁,等待主队列所有节点排队获取锁执行。
谷歌 guava cache
数据库建立一个索引就会维护一个索引树,所以不能创建太多索引
查找数据过程:根据索引找到数据页,然后从数据页中查找到记录
执行计划中的 select_type 可以为simple,union,subquery
4、Spring相关
spring注册主类beanDefinition
SpringApplication.prepareContext
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
.................................
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug(
"Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
BeanDefinitionLoader loader = createBeanDefinitionLoader(
getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
loader.load();
}
springboot根据ComponentScan扫描目录下的bean
在refresh方法的invokeBeanFactoryPostProcessors阶段调用ConfigurationClassPostProcessor后置处理器的方法,内部调用ConfigurationClassParser的parse方法,内部又调用ClassPathBeanDefinitionScanner.scan方法
ConfigurationClassPostProcessor的beanDefinition什么时候注册的
AnnotationConfigServletWebServerApplicationContext构造函数初始化时,new了AnnotatedBeanDefinitionReader,在AnnotatedBeanDefinitionReader的默认构造里注册了
ConfigurationClassPostProcessor的beanDefinition,AutowiredAnnotationBeanPostProcessor也是这时候注册的
@Import注解内的selector
aplicationContextAware什么时候执行的
ApplicationContextAwareProcessor后置处理器进行处理
在AbstractApplicationContest的refresh方法,创建完beanFactory后,prepareBeanFactory方法内new了一个ApplicationContextAwareProcessor后置处理器。
AbstractRefreshableApplicationContext和GenericApplicationContext两个new了DefaultListableBeanFactory
spring为什么使用三级缓存?
二级缓存用来存放代理后的对象,防止多次执行代理造成产生多个代理对象。
因为二级缓存无法解决相互依赖的aop对象。
a–>b,b–>a,c,c–>a
b去二级缓存获取代理后的a,c也去二级缓存获取代理后的a,a被代理了两次,为两个不同的代理对象,就不是单例了。
springbean的生命周期
1、实例化
2、初始化
3、如果bean实现了各种aware接口,那么调用这些接口的方法。
4、后置处理器的before处理bean
5、如果实现了InitializingBean接口,那么调用接口方法
6、如果有自定义init-method方法,那么执行
7、后置处理器的after处理bean
5、java语言特征
注解保留策略
// 注解会被编译到class文件,并且运行时jvm能够访问到注解
@Retention(RetentionPolicy.RUNTIME)
// 注解编译后会被抛弃
@Retention(RetentionPolicy.SOURCE)
// 注解会被编译到class文件,但运行时访问不到注解
@Retention(RetentionPolicy.CLASS)
为什么Java中不支持多重继承?
多继承设计有缺陷,会导致菱形继承问题。
c++处理多继承是使用虚继承,即继承类时加virtual关键字。
虽然c++支持多继承,但是并不建议使用多继承
6、网络相关
浏览器输入url后前后端发生了什么?
1、查询域名对应ip
1.1、首先查找浏览器缓存,如果不存在然后查找操作系统缓存,如果还不存在则去查找路由器缓存,如果还找不到那么去isp通信运营商的缓存
2、浏览器获取到目标ip后三次握手建立连接,封装请求数据
3、arp请求
地址解析协议,广播得到下一跳的mac地址,将目标ip和下一跳mac地址再加上请求数据传给下一跳
网络层和链路层区别:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ajC8Wgch-1625063306902)(总结1.assets/image-20210509180059586.png)]
网络层:访问目标百度的ip,220.181.38.149,将220.181.38.149与路由表所有项的掩码进行与运算,最终和第三个0.0.0.0的与运算结果为0.0.0.0,匹配上了第三个的destination,则将数据传递给第三个的网关地址,192.168.150.2(下一跳的地址,路由器)
链路层:封装网关(192.168.150.2)的mac地址(arp广播获取)和目标的ip地址和端口(百度220.181.38.149)
网关:连接基于不同通信协议的网络的设备
网络层和链路层区别:
网络层使用路由表找到要发送的目标网关。
链路层使用arp获取发送目标的mac地址进行目标寻找
怎么实现幂等
乐观锁、前端按钮置灰、请求跳转
防重放攻击
窃取到的数据原封不动再发送给接受方,常用于身份认证过程.
怎么预防?
1、随机数,如果两次随机数一致则为非法请求 。
2、时间戳,如果两次时间戳一致则为非法请求
3、自增序列,两次请求序列一致或者不是自增步长,则为非法请求
7、jvm相关
String长度限制
编译期长度为65534,超过会报错。运行时为整形最大值长度。
编译期String是要存储在常量池中的,而常量池中数据是用无符号数和表存储的,String对应的表结构用utf-8常量表来描述,而utf-8对应的常量表长度为65535,其中一个长度用于表示结束标志,所以编译器最大长度为65534,
CONSTANT_String_info{
u1 tag;
u2 string_index; //结构使用如下utf-8常量表表示
}
CONSTANT_Utf8_info{
u1 tag;
u2 length;
u1 bytes[length];
}
java类初始化过程(类变量初始化)
加载 | 1、获取class文件二进制字节流(Class.forName())。2、将二进制流代表的静态存储结构转换为方法区运行时数据结构。3、在方法区生成java.lang.Class对象 | |
---|---|---|
验证 | 验证class文件是否符合Jvm规范 | |
准备 | 将 类变量 赋值零值,final修饰的在准备阶段会赋予指定的值,public static int value = 123,准备阶段value值为0,public static final int value = 123,准备阶段value值为123 | |
解析 | 将常量池内的符号引用替换为直接引用 | |
初始化 | 将 类变量 赋初始值,只能由一个线程执行静态变量的< clinit > 指令,且只能执行一次 |
对象创建过程(成员变量初始化)
遇到new指令时,
1、先去检查类是否已被加载并被解析和初始化,如果没有,那么先执行加载过程
2、为对象分配堆内存(空闲列表、指针碰撞(分配安全cas加失败重试)、TLAB)
3、将分配的空间都初始化为零值,
4、将对象头设置一些信息(hashcode(在真正调用Object::hashCode时才计算))
5、调用< init>方法,初始化属性值
什么情况下会触发类的初始化
1、new对象
2、使用java.lang.reflect包的方法对类型进行反射调用的时候
3、访问静态变量
4、访问静态方法
5、初始化子类会导致父类先初始化
java内存模型
屏蔽不同硬件和操作系统内存访问差异。定义程序中各种变量的访问规则。
1、变量都储存在主内存中
2、工作内存和主内存交互java内存模型规定了8种操作
3、volatile关键字
4、long和double非原子性协议
5、原子性、可见性、有序性
6、先行发生原则
Java内存模型定义了程序中各个变量的访问规则,规定变量只能从主内存中产生,每个线程有自己的工作内存,主内存和工作内存之间的交互是用8种原子性操作来交互的。同时java内存模型还处理并发中的原子性,可见性,有序性。其中原子性可以使用synchronized来保证,而可见性和有序性使用volatile关键字保证。有序性还可以用先行发生原则保证
as-if-serial,within-thread,happen-before
java虚拟机栈在调用方法时,创建一个栈帧,栈帧包括操作数栈、局部变量表,
对于非静态方法,局部变量表索引0的位置存的是this。
计算的中间结果保存在操作数栈。
8、操作系统相关
进程通信方式
管道、rpc、消息队列、共享内存、socket、信号量
孤儿进程
父进程退出,子进程未退出,这些子进程称为孤儿进程。
僵尸进程
父进程未取得子进程的退出状态,这些退出状 态还在进程表中存在并占用进程表,这些已退出的占用资源子进程成为僵尸进程。
僵尸进程解决方法
kill掉父进程,僵尸进程就会变成孤儿进程并被init进程接管,init进程会释放这些占用的进程表资源的僵尸进程。
9、redis
redis内存淘汰策略
a) 针对设置了过期时间的key做处理:
1、volatile-ttl:在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的越先被删除。
2、volatile-random:就像它的名称一样,在设置了过期时间的键值对中,进行随机删除。
3、volatile-lru:会使用 LRU 算法筛选设置了过期时间的键值对删除。
4、volatile-lfu:会使用 LFU 算法筛选设置了过期时间的键值对删除。
b) 针对所有的key做处理:
5、allkeys-random:从所有键值对中随机选择并删除数据。
6、allkeys-lru:使用 LRU 算法在所有数据中进行筛选删除。
7、allkeys-lfu:使用 LFU 算法在所有数据中进行筛选删除。
c) 不处理:
8、noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error)OOM command not allowed when used memory",此时Redis只响应读操作。
数据删除策略
1、被动删除:当读/写一个已经过期的key时,会触发惰性删除策略,直接删除掉这个过期key
2、主动删除:由于惰性删除策略无法保证冷数据被及时删掉,所以Redis会定期主动淘汰一批已过期的key
3、当前已用内存超过maxmemory限定时,触发主动清理策略
4、LRU 算法(Least Recently Used,最近最少使用):淘汰很久没被访问过的数据,以最近一次访问时间作为参考。
5、LFU 算法(Least Frequently Used,最不经常使用):淘汰最近一段时间被访问次数最少的数据,以次数作为参考
持久化策略
第一种是快照(RDB),第二种是 AOF 日志。快照是一次全量备份,AOF 日志是连续的增量备份。RDB记录的是数据,AOF记录的是指令。应选择rdb和aof混合模式,这样aof只会增量记录rdb之后的日志,重启时候,先加载rdb数据,再加载aof数据
redis哨兵
客户端连接的时候连接哨兵的ip端口。哨兵中每个redis都是全量的数据。
redis集群
和哨兵的区别是数据分片存储,能存更多的数据。
redis主从同步
1、slave启动后会发送sync命令给master,master收到命令后在后台保存快照(RDB持久化)和缓存保存快照这段时间的命令,然后将保存的快照文件和缓存的命令发送给slave。slave接收到快照文件和命令后加载快照文件和缓存的执行命令。
2、master每执行一个写命令都会同步发送给slave。
redlock原理
redis集群:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0EEntGpU-1625063306904)(C:\Users\root\Desktop\面试总结\总结.assets\image-20210610113854869.png)]
1、多节点redis实现的分布式锁算法(RedLock)
主redis同步数据到从redis时,还没同步过去,master挂了,导致从redis里没有锁信息,从选举成为master以后,其他客户端可以继续从master中获取锁。
如果从redis选举成为主以后,等锁过期后再去获取锁,或者等锁过期后再去选举主
2、为什么要半数redis(master)实例获取锁?
假如共5个实例,第一个客户端在两个实例上获取了锁,第二个客户端也可也在剩下的3个实例中获取锁。导致锁被同时持有。
假如共5个实例,第一个客户端已经获取了3个实例的锁,这时候挂了一个实例,第二个客户端只能在剩下的4个里面获取两个实例的锁,没有获取半数以上的锁,获取锁失败。
当一个客户端获取了半数以上的锁,其他获取了半数以下的就必须要释放锁。
3、终极解决高可用分布式锁方案
redis持久化选择每秒同步,redis宕机后等待ttl后再重启(延时重启)
4、在过期时间内,获取了半数以上实例的锁才算获取锁成功
开启AOF
appendonly yes
appendfsync everysec
将flushall命令从aof文件中删除再重启redis,数据会恢复
redis脑裂怎么产生的,怎么减少脑裂产生的数据损失
一个master和slave不能连接了,哨兵会认为master已经挂了,再次选举一个master,这时有2个master。客户端还没来得及切换到新master,继续向老的master写数据,等老master回复连接后,变成slave,清空数据再从新master同步数据,数据丢失。
配置
Min-slaves-to-write 1
Min-slaves-max-lag 10
如果slave10秒没有回复,那么master拒绝客户端写入数据,那么最终会丢失10秒的数据。
10、事务相关
数据库事务
读提交、读未提交、可重复读、序列化
mysql默认隔离级别为可重复读
spring事务传播机制
PROPAGATION_REQUIRES_NEW | 每次创建一个新的事务并挂起当前事务 |
---|---|
PROPAGATION_REQUIRED | 支持当前事务,如果没有事务会创建一个新的事务 |
PROPAGATION_SUPPORTS | 支持当前事务,如果没有事务的话以非事务方式执行 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行,如果当前存在事务则将当前事务挂起 |
PROPAGATION_MANDATORY | 支持当前事务,如果没有事务抛出异常 |
PROPAGATION_NEVER | 以非事务方式进行,如果存在事务则抛出异常 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。 |
分布式事务
1、TCC(try,confirm.cancel)分布式事务,所谓分布式事务,回滚方式在cancel方式里实现,你confirm里insert了一条数据,在cancel里就要delete删除这条数据,如果calcel失败了就会一直重试。
2、事务补偿机制
即事务内的多个原子操作,其中一个操作失败了,尝试一定的次数,如果多次执行失败,那么将操作放入消息队列,进行失败重试
11、数据库和缓存
mysql页分裂
数据页之间双向链表连接,如果插入的数据索引不是自增的,那么会将数据页重新维护
如何保证缓存和数据库一致性
延时双删,先删除缓存,再更新数据库,等一段时间,再次删除缓存
1、时间等多长 ?
根据其他线程读取key时间来确定,在其执行读取方法的耗时之后再次删除缓存
2、如果用了mysql读写分离架构怎么办?
一样用延时双删,不过第二次删除时间为 读取方法的耗时 加上 主从同步时间
3、采用这种同步淘汰策略,吞吐量降低怎么办?
异步执行第二次删除缓存
4、删缓存失败了怎么办?
重试机制,将需要删除的key放入消息队列或者数据库,失败了重试删除。
缺点:对业务线代码造成大量的侵入
5、缓存击穿解决
1、使用互斥锁或分布式锁,
2、是设置热点数据永不过期
6、缓存雪崩解决
1、key设置随机失效时间
2、缓存集群
7、缓存穿透
布隆过滤器
缓存空值(当然要设置过期时间)
12、队列
ArrayBlockingQueue、LinkedBlockingQueue
13、锁升级过程
当对象处于偏向锁状态时,如果收到计算hashcode请求,那么就会升级为重量级锁,因为重量级锁对象ObjectMonitor对象里存有原始markword。
当对象计算过hashcode后,无法进入偏向锁状态了。
13.1、偏向升级轻量锁
当存在锁竞争时,准备撤销偏向锁,获取了偏向锁的线程如果还未执行完代码,那么升级为轻量级锁,获取偏向锁的线程持有轻量级锁,如果执行完代码了,那么将markword设置为无锁不可偏向状态。
13.2、轻量升级重量锁
当存在锁竞争时,线程2看到markword里的指针不是指向自己的栈帧,那么准备膨胀为重量级锁,线程2使用cas操作将markword设置为正在膨胀状态,然后为ObjectMonitor对象设置一些属性,其中owner属性设置为获取轻量级锁的线程1,而不是线程2,即已经获取了锁正在执行代码的线程。cas失败的等待膨胀完成(根据markword是否有指向ObjectMonitor对象的指针判断)。最终获取了轻量级锁的线程1执行释放重量级锁的过程。
13.1、重量级锁获取过程:
重量级锁对象ObjectMonitor对象里有owner、recursion、entryList、waitSet等属性,
调用monitor:enter方法时,和AQS流程大致一致,首先cas设置owner为当前线程,设置成功则加锁成功,如果是第一次获取,那么recursion加一,如果cas失败,当owner等于当前线程时,recursion加一,这就是重入。
14、springmvc执行过程
15、为什么区分度不高的字段不加索引(性别)
如果一共100条数据,一共80个男的,select * from table where sex = ‘1’, 需要回表80次,还不如直接全表扫
16、字段空值有什么影响
17、索引下推
组合索引中,第一个条件满足后继续匹配第二个条件,减少回表次数
set optimizer_switch='index_condition_pushdown=off';
18、ThreadLocal
ThreadLocal,如果外部没有对其强引用,那么gc的时候ThradLocal会被回收,那么ThreadLocalMap就会出现key为null的Entry,那么entry的value不会被回收,如果线程迟迟不结束,造成内存泄漏。
19、@EnableCaching
new CacheOperationContext时候获得Cache实现类
该注解@Import了CachingConfigurationSelector类,分别将AutoProxyRegistrar和ProxyCachingConfiguration注册到spring容器,其中ProxyCachingConfiguration配置类会将CacheInterceptor注册到spring,缓存的具体实现就在CacheInterceptor内部
20、SpringAop
1、AnnotationAwareAspectJAutoProxyCreator后置处理器的创建
spring.factories内配置的自动配置类AopAutoConfiguration,类上ConditionalOnClass注解内容EnableAspectJAutoProxy注解,EnableAspectJAutoProxy注解上方@Import导入了AspectJAutoProxyRegistrar类,为ImportBeanDefinitionRegistrar接口的实现类。在CongifurationClassParser解析@Import注解时调用ImportBeanDefinitionRegistrar接口的方法,AnnotationAwareAspectJAutoProxyCreator的beanDefinition会被注册到spring
2、在AnnotationAwareAspectJAutoProxyCreator后置处理器方法中创建代理(jdk或cglib)
1、创建CglibAopProxy或JdkDynamicAopProxy对象
AnnotationAwareAspectJAutoProxyCreator间接实现了SmartInstantiationAwareBeanPostProcessor接口,所以在bean创建完成后会调用其后置处理方法,在后置处理器方法中使用ProxyFactory(extends ProxyCreatorSupport).getProxy()来创建代理,在ProxyFactory的父类ProxyCreatorSupport默认构造函数里new DefaultAopProxyFactory();调用DefaultAopProxyFactory的createAopProxy方法,返回CglibAopProxy或JdkDynamicAopProxy对象,
AbstractAutoProxyCreator创建ProxyFactory时候,为ProxyFactory设置Advisor实现类(AspectJAroundAdvice)
springMVC方式是一样是将AnnotationAwareAspectJAutoProxyCreator的beanDefinition注册到spring
2、获取代理对象
CglibAopProxy在getProxy创建代理类的时候,设置了DynamicAdvisedInterceptor回调,调用回调的intercept方法时,new CglibMethodInvocation,调用其proceed方法。
ReflectiveMethodInvocation.proceed---->AspectJAroundAdvice.invoke,ProceedingJoinPoint作为@Around方法的参数,其实是将ReflectiveMethodInvocation包装到了ProceedingJoinPoint实现类里面,调用ProceedingJoinPoint.proceed实际上是调用ReflectiveMethodInvocation.proceed,当创建的代理类的拦截器都执行完以后就执行默认的@Around修饰的方法。
自己实现一个aop注解
1、advisor
public class MyAopAspectjAdvisor implements PointcutAdvisor {
private Pointcut pointcut = new MyAopAspectJPointcut();
private Advice advice = new MyAopAspectjAdvice();
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
@Override
public Advice getAdvice() {
return this.advice;
}
@Override
public boolean isPerInstance() {
return true;
}
public void setPointcut(Pointcut pointcut) {
this.pointcut = pointcut;
}
public void setAdvice(Advice advice) {
this.advice = advice;
}
}
2、advice
public class MyAopAspectjAdvice implements MethodInterceptor {
private static final Logger log = LoggerFactory.getLogger(MyAopAspectjAdvice.class);
public MyAopAspectjAdvice(){
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
log.info("-------------------------------------------------------------------自定义增强器");
return invocation.proceed();
}
}
3、pointcut
public class MyAopAspectJPointcut implements Pointcut {
@Override
public ClassFilter getClassFilter() {
return ClassFilter.TRUE;
}
@Override
public MethodMatcher getMethodMatcher() {
return new MyAopMethodMatcher();
}
}
4、ClassFilter
5、MethodMatcher
public class MyAopMethodMatcher implements MethodMatcher {
@Override
public boolean matches(Method method, Class<?> targetClass) {
Proxy annotation = AnnotationUtils.findAnnotation(method, Proxy.class);
return annotation != null && annotation.isProxy();
}
@Override
public boolean isRuntime() {
return false;
}
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
return false;
}
}
关于DeclareParents注解
DeclareParents和pointcut一样也会为表达式匹配的类生成代理,会为匹配的类增加一个接口实现,
真正调用这个接口的实现是注解里声明的defaultImpl实现类对应的方法。
@Aspect
@Component
public class ProxyTestConfig {
@DeclareParents(value = "com.springboot.service.*", defaultImpl = TestDeclareParentImpl.class)
private ITestDeclareParent testDeclareParent;
@Pointcut("execution(* com.springboot.service..*.*(..))")
public void around() {
}
@Around("around()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
logger.info("around start......");
return joinPoint.proceed();
}
}
public interface ITestDeclareParent {
String hello();
}
// 并不需要放入spring容器
public class TestDeclareParentImpl implements ITestDeclareParent{
@Override
public String hello(){
System.out.println("===============================================================");
return "zs";
}
}
@RunWith(SpringRunner.class)
@SpringBootTest(classes={SpringBootStarterApplication.class})
public class SpringbootTest {
@Autowired
private IProxyTestService IProxyTestService;
@Test
public void testProxy() {
((ITestDeclareParent)IProxyTestService).hello();
}
}
21、springboot的jar为什么可以直接运行?
java -jar xxx.jar
springboot项目使用spring-boot-maven-plugin打包后的结构如下
META-INF
org
WEB-INF
其中META-INF目录为jar包运行入口,目录下的MANIFEST.MF文件描述java -jar 运行的主类,
其中有这两行描述
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.DebugSpringbootApplication
会先调用JarLauncher的main方法,然后获取MANIFEST.MF文件里的Start-Class,反射实例化并调用其main方法,这正是springboot的启动类
22、MAVEN
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.0.5.RELEASE</version>
<!-- 父模块使用spring-boot-maven-plugin打包时,
子模块如果需要打包为不可执行的jar,使用如下配置,会打包成两个jar,带exec后缀的为可执行jar-->
<configuration>
<classifier>exec</classifier>
</configuration>
</plugin>
</plugins>
</build>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
<!--不会被打包-->
<scope>provided</scope>
<!--表示两个项目之间依赖不传递-->
<optional>true</optional>
</dependency>
老年代中数组的访问方式
GC 有环怎么处理
DDD领域模型,是一种开发方式,通常我们改一个功能可能会影响其他功能,DDD要求我们划分界限,使用抽象精简问题,抽象一个边界,比如对接支付接口,不关心是微信还是支付宝支付,我们只对接支付接口
priorityQueue
private void siftDownComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>)x;
int half = size >>> 1; // loop while a non-leaf
while (k < half) {
// 选出删除位置的左右子节点中的最小值
int child = (k << 1) + 1; // assume left child is least
Object c = queue[child];
int right = child + 1;
if (right < size &&
((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
c = queue[child = right];
// 如果最后一个节点小于左右节点中的最小值
// 直接将最后一个节点放在删除节点的位置
if (key.compareTo((E) c) <= 0)
break;
// 否则将左右节点中的最小值放在删除节点的位置
queue[k] = c;
// 从左右节点中最小节点的位置继续
k = child;
}
queue[k] = key;
}
代中数组的访问方式
GC 有环怎么处理
DDD领域模型,是一种开发方式,通常我们改一个功能可能会影响其他功能,DDD要求我们划分界限,使用抽象精简问题,抽象一个边界,比如对接支付接口,不关心是微信还是支付宝支付,我们只对接支付接口
priorityQueue
private void siftDownComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>)x;
int half = size >>> 1; // loop while a non-leaf
while (k < half) {
// 选出删除位置的左右子节点中的最小值
int child = (k << 1) + 1; // assume left child is least
Object c = queue[child];
int right = child + 1;
if (right < size &&
((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
c = queue[child = right];
// 如果最后一个节点小于左右节点中的最小值
// 直接将最后一个节点放在删除节点的位置
if (key.compareTo((E) c) <= 0)
break;
// 否则将左右节点中的最小值放在删除节点的位置
queue[k] = c;
// 从左右节点中最小节点的位置继续
k = child;
}
queue[k] = key;
}
零拷贝
零拷贝技术是基于PageCache
传输大文件使用异步或直接io,即不使用pageCache,小文件使用零拷贝
pageCache
使用了内核的pageCache,即高速缓存区。
pageCache有两个功能:
1、缓存最近被访问的数据,
2、预读数据,比如read方法读32kb,那么pageCache就预读后面的32kb。
零拷贝的过程
pageCache缓存每次访问的文件,当下次再次访问时直接返回,即零拷贝,不再去磁盘上再次拷贝数据。
当然这是针对小文件,大文件不应该使用零拷贝,因为大文件会占用pageCache,导致其他小文件无法使用零拷贝。
大文件应该使用异步io+直接io,不使用pageCache的叫直接io,使用的叫缓存io。
也就是说内核直接将磁盘数据拷贝到用户空间,而不再拷贝到内核缓冲区。
DMA
DMA,直接内存访问技术,直接存储器,每个io设备都有一个DMA控制器,
直接与内存进行数据交换。
在文件传输过程中,用户空间基本不会对数据进行加工,所以用户缓冲区不必存在。
传统read,write是4次上下文切换。
用户程序调用read方法后,用户态转为内核态,read方法返回数据则是内核态转为用户态。
write同理。
还有4次数据拷贝,其中2和4为cpu执行拷贝
1、磁盘拷贝到内核,
2、内核拷贝到用户空间,(read)
3、用户空间拷贝到socket缓冲区,(write)
4、socket缓冲区拷贝到网卡。
kafka
ISR(副本同步队列)
因为是异步同步数据,当leader写入数据时,宕机了,那么会造成数据丢失,没有将数据同步到副本,但是有了ISR,当恢复后会将数据同步到副本。
GC安全点和安全区域
gc安全点
用户线程执行到某段代码,这段代码不会改变引用关系
gc安全区域
用户线程在某段代码之间不会改变引用关系。当用户线程执行到安全区域代码时,标识自己已经进入,当准备退出安全区域时,会看gc是否结束,没结束则一直等待。