JVM篇
JVM组成
什么是程序计数器
线程私有的,内部保存的字节码行号。用于记录正在执行的字节码指令的地址。
堆
线程共享的区域:主要用来存储对象、数组等,当堆中没有内存空间可分配给实例,也无法扩展时,则会抛出OOM异常。
堆由年轻代和老年代组成。年度又分为Eden区和两个大小一致的Survivor区,老年代主要保存生命周期长的对象。
1.7对中有一个方法区/永久代,存储的是类信息、静态变量、常量、编译后的代码。1.8移除了1.7中的方法区/永久代,把数据存储到了本地内存的元空间中,防止内存溢出。
虚拟机栈
每个线程运行时需要的内存被称为虚拟机栈。
每个栈由多个栈帧组成,对应着每次方法调用时所占用的内存。
每个线程只能由一个活动的栈帧,对应着当前正在执行的那个方法。
垃圾回收是否涉及栈内存?
垃圾回收主要是针对堆,当栈栈弹栈后,内存就会被释放。
栈内存分配越大越好嘛?
不一定,默认的栈内存通常为1024k。
栈帧过大会导致线程数变少。机器总内存512M,目前能活动的线程数则为512个,若给栈内存改为2048K,那么活动栈帧就会减半。
方法内的局部变量释放线程安全?
如果方法内局 ...
并发编程篇
线程的基础知识
线程和进程的区别
进程的正在运行程序的实例,一个进程包含了多个线程,每个线程执行不同的任务。
不同的进程使用不同的内存空间,当前进程下的线程可以共享内存空间。
线程更加轻量,线程上下文切换成本一般会比进程的上下文切换低。
并行与并发的区别
如果是单核CPU,只有并发,没有并行。
并发是在单位时间内交替运行多个线程。
并行是在单位时间内同时运行多个线程。
创建线程的方式有哪些
继承Thread类
重写run方法,调用start方法启动线程。
实现Runnable接口
与第一种一样。
实现Callable接口
实现Callable接口,需要传入一个泛型。
实现call方法,call方法返回类型就是传入的泛型。
创建Callable实现类的对象a。
创建FutureTask对象b,将对象a传入。
创建Thread对象c,将对象b传入。
调用c对象的start()方法启动线程。
可以使用b.get()获取线程执行结果。
线程池创建
使用Executors.newFiexdThreadPool()创建一个线程池,然后调用submit ...
集合篇
Collection
单列集合
List
有序可重复
Vector
数组结构,线程安全
ArrayList
数据结构,非线程安全
ArrayList底层的实现原理是什么?
底层是用动态数组实现的。
初始容量为0,当第一次添加数据的时候才会初始化容量为10。
进行扩容的时候是原来的1.5倍,每次扩容都需要拷贝数组。
添加数据时候:
确保size + 1后能存储下下一个数据。
计算数组容量,如果size + 1大于当前的数组容量,调用grow方法扩容。
确保新增的数据有地方存放后,则将新元素加到对应的位置上。
放回true。
ArrayList list = new ArrayList(10)中的list扩容了几次?
答:没有扩容,只是指定了一个容量为10的Object数组。
如何实现数组与List之间的转换
List转数组
使用List.toArray(T[] a): T[]。
数组转List
Arrays.asList(T ... a): List<T>。
使用Arrays.asList转List后,修改数组内容,List受影响吗?
受影响。他 ...
消息中间件篇
消息中间件使用场景
异步发送(验证码,短信,邮件)
MySQL和Redis,ES之间的数据同步
分布式事务(最终一致性)
作为发布/订阅系统实现一个微服务系统间的观察者模式。
连接流计算任务和数据。
用于将消息广播给大量的接收者,数据同步。
流量控制(削峰填谷)
错峰与流控。
问题:如何避免过多的请求压垮系统?
设计思路:使用消息队列隔离网关和后端服务,以达到流量控制和保护后端服务的目的。
代价:
增加系统调用的链路,导致总体的响应时间变成。
同步调用变成了异步调用,增加系统的复杂度。
成本问题,MQ的高性能和高可用。
常见的限流算法:
固定窗口算法
滑动窗口算法
漏桶算法
令牌桶算法:有一个程序,在单位时间内只发放固定的令牌到令牌桶中,规定服务在处理请求之前必须先从令牌桶中先获取一个令牌,如果令牌桶中没有令牌,则拒绝请求。这样就可以保证单位时间内,能处理的请求不超过发放令牌的数量。
服务解耦
微服务的通信模式:
调用链模式:A -> B -> C
聚合器模式:有点像DDD中和聚合。
基于事件的异步模式:有点像DDD中的事件。
分布式事务
产生的原 ...
微服务篇
Spring Cloud
Spring Cloud组件有哪些?
注册中心:Eureka/Nacos。
负载均衡:Ribbon。
服务熔断:Hystix/Sentinel。
远程调用:Feign。
服务网关:Zuul/Gateway。
服务注册和发现
eureka
服务注册:服务提供者需要把自己的信息注册到eureka,由eureka来保存这些信息。例如服务名称、IP、端口等。
服务发现:消费者向eureka拉起服务列表的信息,如果服务提供者有集群,则消费者利用负载均衡算法选择一个服务发起调用。
服务监控:服务提供者每隔30秒向eureka发送心跳,报告监控状态,若eureka90秒还没收到心跳,从eureka剔除该服务。
nacos
nacos与eureka大体相同。
不同点
nacos支持服务端主动监测提供者状态:临时实例采用心跳检查,非临时实例采用主动检测模式。
临时实例心跳不正常会被剔除,非临时实例不正常不会被剔除。
nacos支持服务列表变更的消息推送模式,服务列表更新及时。
nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式,而eureka只 ...
MyBatis篇
执行流程
读取MyBatis配置文件:mybaits-config.xml加载运行环境和配置文件。
创建会话工厂SqlSessionFactory。
会话工厂创建SqlSession对象。(包含了执行SQL语句的所有方法)。
操作数据库的接口,Executor执行器,同时负责查询缓存的维护。
Executor接口的执行方法中有一个MapperStatement类型的参数,封装了映射信息。
输入参数的映射。
输出结果的映射。
延迟加载
需要用的时候才加载数据,不需要用到就不加载。
MyBatis支持一对一、一对多关联的延迟加载。
是否支持延迟加载?
支持,但是默认关闭。
全局:全局配置文件中的lazyLoadingEnable=true。
局部:fetchType=lazy。
原理
使用CGLIB创建目标代理对象。
当调用getXXX()方法的时候,进入拦截器invoke方法,判断xxx属性是否为空,如果为则执行sql,从数据库中获取数据
获取到数据后,调用setXXX()为xxx属性赋值,接着完成getXXX()方法的调用。
一二级缓存
本地缓存:PerpetualCa ...
Spring篇
Spring
Bean线程安全问题
Spring中的Bean默认是单例的。可以使用@Scope注解设置将属性设置成prototype变成多例。
Spring中的Bean不是线程安全的。
Spring Bean并没有可改变状态(例如Service类和Mapper类),所以在某种程度上说Spring的单例Bean是线程安全的。如果在bean中定义了可修改的成员变量,是要考虑线程安全问题的,可以使用多例或者加锁来解决。
AOP
AOP被称为面向切面编程,用于将那些与业务无关,但却会影响多个对象的公共代码和逻辑抽取并封装成为一个可重用的模块,这个模块被命名为”切面“(Aspect),减少系统中重复的代码,降低了模块之间的耦合度,同时提高系统的可维护性。
常见场景
记录操作日志。
缓存处理。
Spring中内置事务处理。
事务原理
编程式事务:需要使用TransactionTemplate来实现。
声明式事务:AOP。
事务失效
异常捕获处理:方法内部给异常捕获了,会导致声明式事务感受不到异常。解决:在catch中再抛一个异常出去。
抛出检查异常:使用throws抛出异常。 ...
MySQL篇
优化
定位慢查询
聚和查询
多表查询
表数据量过大查询
深度分页查询
表现:页面加载过慢、接口测压响应时间过长(超过1s)
如何定位
开源工具
调试工具:Arthas
运维工具:Prometheus、Skywalking
MySQL自带慢日志
它记录了所有执行时间超过指定参数(默认10秒)所有SQL语句的日志,默认是关闭的,要开启需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息。慢日志文件存储在/var/lib/mysql/localhost-slow.log。
show_query_log=1
long_query_time=2# 一般配置在2秒左右
注:在调试阶段开启就可以了,生产阶段开启会损耗一点性能。
如何分析,解决
深度分页查询:
SQL执行计划
聚和查询:新增一张临时表。
多表查询:尝试优化SQL的结构。
表数据量过大查询:添加索引,添加了还是慢怎么办?
可以使用EXPLAIN或者DESC命令获取MySQL如何执行SELECT语句的信息。在SELECT前面添加EXPLAIN或者DESC。
返回的不是查询结果,而是执行SQL的一些信 ...
Redis篇
Redis有哪些使用场景?
一、缓存穿透
例如查询根据文章,通常情况下,应该是先查redis,若redis中有,则直接返回。若redis中没有,那么就需要去数据库中查,从数据库中查到数据后,将数据保存到redis中,然后再返回给客户端。
这时候就会出一个一个问题,如果查询一个不存在的数据,数据库中查不到数据也不会直接写入到缓存,就会导致每次请求都需要查询数据库,就是这缓存穿透。
解决方案
缓存空数据。查询放回的数据为空,任给这个空结果进行缓存。
优点:简单
缺点:消耗内存。可能会导致不一致的问题。
布隆过滤器。在查询redis之前,先查布隆过滤器。在缓存预热的时候,需要给数据添加到布隆过滤器中。
优点:内存占用少,没有多余的key。
缺点:实现起来复杂,存在误判。
缓存穿透是指查询一个不存在的数据,如果从数据库中找不到这个数据则不会写入缓存,这样就导致这个不存在的数据每次请求都需要到数据库中查询,可能会导致数据库挂掉。这种情况大概率是受到了攻击。
通常使用布隆过滤器来解决。
什么是布隆过滤器呢?
布隆过滤器主要是检索一个数据是否存在一个集合中。当时是使用redissio ...
AcWing 901. 滑雪
题目
原题链接
题目描述
给定一个 RRR 行 CCC 列的矩阵 GGG,表示一个矩形网格滑雪场。
矩阵中第 iii 行第 jjj 列的点表示滑雪场的第 iii 行第 jjj 列区域的高度。
一个人从滑雪场中的某个区域内出发,每次可以向上下左右任意一个方向滑动一个单位距离。
当然,一个人能够滑动到某相邻区域的前提是该区域的高度低于自己目前所在区域的高度。
下面给出一个矩阵作为例子:
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
在给定矩阵中,一条可行的滑行轨迹为 24−17−2−124 - 17 - 2 - 124−17−2−1。
在给定矩阵中,最长的滑行轨迹为 25−24−23−…−3−2−125 - 24 - 23 - … - 3 - 2 - 125−24−23−…−3−2−1,沿途共经过 252525 个区域。
现在给定你一个二维矩阵表示滑雪场各区域的高度,请你找出在该滑雪场中能够完成的最长滑雪轨迹,并输出其长度(可经过最大区域数)。
输入格式
第一行包含两个整数 RRR ...
