一、VIPSHOP
唯品会技术架构
唯品会微服务基础中台架构设计思路:
- OSP (开放服务平台)微服务框架,提供高性能、高可扩展的远程调用机制,实现了契约化多语言服务接口,同时提供了强大的服务化治理能力,可以实现负载均衡、路由选择以及自我保护等。
- Service Center 统一的服务治理中心,对基础服务化项目提供的服务进行治理,将所有服务化项目的配置集中在一起,实现一处配置、多处运行的目标。
- Mercury 全链路跟踪监控平台,实现了全链路调用链跟踪、指标统计、监控告警等,通过 Mercury,应用管理人员 / 架构师等可以全方位把握应用整体拓扑结构、定位全网应用瓶颈。应用开发人员可以定位线上服务性能瓶颈、持续优化代码和 SQL、帮助快速解决线上问题,IT 运维 / 监控人员可以快速故障告警和进行问题定位、把握应用性能和容联评估、提供可追溯的性能数据。
- Janus 服务网关,为业务服务提供统一对外的、高性能的 HTTP 网关,针对外网支持 HTTPS、HTTP2、HTTP、自定义协议等,针对内网可以自动适配到 OSP 协议。
- Salus 服务安全管理平台,面向 OSP 和 RESTful 形式的服务,提供服务安全管理(认证、鉴权、防篡改)的手段。
基础中间件,CfgCenter 应用配置中心实现应用配置管理,Saturn 分布式任务调度平台具备高可用以及分片并发处理能力等, - Asgard 一站式存储服务平台可以实现统一管理、统一监控存储服务,VMS 消息系统具备组内广播、消息回溯、消息延时、灰度消息等。
venus
OSP
OSP-Proxy
配置中心
达人项目
Thrift
项目:
负责参与部门基础业务研发和升级:
- Talent(达人)项目:
- 负责将项目接入Asgard 服务,去除项目中原先的数据源配置和声明式事务管理,实现数据库实现读写分离 - Haitao(海淘)项目业务:
- 负责海淘实名验证业务 - 用户服务:
- 负责小程序免密免注册码注册业务 - HIVE离线分析:统一黑名单平台,整合黑名单,将分散的黑名单变成统一的配置式
- 黑名单预测
二、基础知识
Java
1.ThreadLocal
ThreadLocal实现线程本地存储的功能,同一个ThreadLocal所包含的对象,在不同的Thread中有不同的实例,获取ThreadLocal对象时其实是在Thread类中的Map类型的threadLocals变量中通过ThreadLocal变量为键值进行获取。
2.volatile的作用和原理
被volatile修饰的变量保证Java内存模型中的可见性和有序性。
- 可见性:当一个线程修改了一个被volatile修饰的变量的值,新值会立即被刷新到主内存中,其他线程可以立即得知新值。
- 有序性:禁止进行指令重排序。
volaitle底层是通过内存屏障来实现可见性和有序性。内存屏障是一个CPU的指令,他的作用有两个,一是保证特定操作的执行顺序,二是保证某些变量的内存可见性。内存屏障告诉编译器和CPU,不管什么指令都不能和这条内存屏障指令重排序,另一个作用是强制刷出各种CPU的缓存资源,因此任何CPU上的线程都能读取到这些数据的最新版本。
3.J.U.C中的锁
Java提供了两种锁机制来控制多个线程对共享资源的互斥访问。
实现 | 公平锁 | 等待可中断 | 条件 | 性能 | |
---|---|---|---|---|---|
synchronized | JVM | 非公平 | 不可中断 | / | 大致 |
ReentrantLock | JDK | 非公平/公平 | 可中断 | 可绑定多个Condition | 相同 |
除非要使用ReentrantLock的高级功能,否则优先使用synchronized,synchronized是JVM实现的一种锁机制,JVM原生的支持它,而ReentrantLock不是所有的JDK版本都支持。synchronized锁释放由JVM保证,ReentrantLock需要显式的释放。
4.atomic包里的一些问题
atomic是使用volatile和CAS来实现的
5.HashMap的扩容2n
当HashMap的容量到达threshold时,需要进行动态扩容,将容量扩大为原来的两倍,然后将存储的数据进行rehash。
6.Semaphore信号量用来做什么?
Semaphore信号量类似于操作系统的信号量,可以控制对互斥资源的访问线程数。
7.Java内存模型
CPU和内存之间增加高速缓存。
所有的变量都存储在主内存中,每个线程有自己的工作内存,工作内存存储在高速缓存中,保存了该线程使用变量的拷贝。
线程只能直接操作工作内存中的变量,不同线程之间的变量值传递需要通过主内存来完成。
内存模型的三大特性:
原子性:Java内存模型保证了read、load、use、assign、store、write、lock、unlock操作具有原子性
- 实现:原子类、synchronized
可见性:当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。
- Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值来实现可见性
- 实现:volatile、synchronize、final
有序性:在本线程内观察,所有操作都是有序的。在一个线程观察另一个线程,所有操作都是无序的,无序是因为发生了指令重排序。
- 实现:volatile、synchronized
8.Java内存空间是怎么分配的?
- 对象优先在Eden区分配
- 大对象直接进入老年代
- 长期存活的对象进入老年代
- 动态对象年龄判定
- 空间分配担保
空间分配担保
1、准备在新生代进行minorGC时,首先检查“老年代”最大连续空间区域的大小是否大于新生代所有对象的大小。
2、如果老年代能装下所有新生代对象,minorGc没有风险,进行minorGC
3、老年代无法装下,垃圾收集器进行一次预测:根据以往minorGC过后存活对象的平均数来预测这次minorGC后存活对象的平均数。
(1)以往平均数小于当前老年代最大的连续空间,就进行minorGC,
(2)大于,则进行一次fullGC,通过清楚老年代中废弃数据来扩大老年代空闲空间,以便给新生代做担保。
注意事项:
分配担保是老年代为新生代作担保;
新生代中使用“复制”算法实现垃圾回收,老年代中使用“标记-清除”或“标记-整理”算法实现垃圾回收,只有使用“复制”算法的区域才需要分配担保,因此新生代需要分配担保,而老年代不需要分配担保。
8.Full GC触发条件
- System.gc()
- 老年代空间不足
- 空间分配担保失败
8.类加载机制
- 加载:1.通过类文件加载二进制字节流 2.在方法区创建类的动态存储结构 3.在内存中创建class对象作为方法去的访问入口。
- 验证:验证class文件的字节流是否符合虚拟机要求。
- 准备:为类变量分配内存并设置初始值。
- 解析:将常量池的符号引用替换为直接引用的过程。
- 初始化:执行Java程序代码。
8.新生代和老年代可以转换吗?
对象优先分配在新生代的Eden区,通过长期存活(达到一定岁数)的对象进入老年代和动态对象年龄判定使对象从新生代进入老年代。
9.这些内存里面的垃圾怎么回收?
引用计数法和可达性分析法。回收算法包括:标记-清除、标记-整理、复制、分代收集算法。
10.怎么判断是垃圾?GCRoot可以为哪些?
可达性分析法中,从GC Root出发,不可达的是可以被回收的对象。
- Java虚拟机栈局部变量表中引用对象。
- 本地方法栈JNI中引用的对象。
- 方法区中类静态变量引用的对象。
- 方法去中常量引用的对象。
11.G1收集器
垃圾收集器都存在 Stop The World 的问题,G1对这个问题进行了优化,G1对整个新生代和老年代一起回收,把堆划分为多个大小相等的独立区域region,使得每个region可以单独进行垃圾回收,通过记录每个region垃圾回收时间以及回收所获得的空间(通过过去回收的经验获得),并维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的region。
初始标记 -> 并发标记 -> 最终标记 -> 筛选回收
特点:
- 空间整合:基于标记-整理和复制,不会产生内存空间碎片
- 可预测的停顿:也可以并发执行
8.BIO、NIO、AIO
BIO,同步阻塞IO,一个线程处理一个连接,发起和处理IO请求都是同步的
NIO,同步非阻塞IO,一个线程处理多个链接,发起IO请求是非阻塞的,处理IO请求是同步的(轮询)
AIO,异步非阻塞IO,一个有效请求一个线程,发起和处理IO请求都是异步的。
9.AQS
描述 | |
---|---|
FutureTask | 用来封装Callable的返回值 |
BlockingQueue | 当队列中没有元素时take()被阻塞,当队列满时put()被阻塞 |
ForkJoin | 大的计算任务拆分成小任务,并行计算 |
10.JUC
描述 CountDownLatch countDown()会使计数器减1,当计数器为0时,调用await()的线程会被唤醒 CyclicBarrier await()会使计数器减1,当计数器为0时,所有调用await()的方法会被唤醒 Semaphore 类似于操作系统的信号量,可以控制对互斥资源的访问线程数
11.实现线程安全的方法
- 不可变
- synchronized和ReentrantLock
- CAS、AtomicInteger
- TreadLocal
12.IO与NIO
I/O | NIO | |
---|---|---|
数据打包和传输方式 | 流 | 块 |
是否阻塞 | 阻塞 | 非阻塞 |
13.NIO
- 通道(Channel):对原I/O包中的流的模拟,可以通过它读取和写入数据,流是单向的,通道是双向的,可以同时用于读、写或者同时用于读写。
- 缓冲区:不会直接对通道进行读写数据,而是要先经过缓冲区。
- 选择器(Selector):在Socket NIO用于IO复用。
14.Class.forName()怎么执行的?
15.守护线程是什么?守护线程是怎么退出的?
守护线程是在程序运行时提供后台服务的线程,不属于程序运行中不可或缺的部分。
当程序中所有非守护线程结束时,程序也就终止,同时杀死所有的守护线程。
16.Stack与ArrayList的区别
Stack是用Vector实现的,Queue是用ArrayList实现的,所以比较Stack与ArrayList的区别就是比较这两者之间的区别。
- 一个先进先出,一个后进先出
- 一个线程不安全,一个线程安全
17.HashMap的rehash过程
HashMap中使用一个技巧,和将哈希值与旧容量进行&运算,如果位上为0则在原位置,如果为1则在下边。
18.hashcode和equals的区别
equals用来判断实体在逻辑上是否相等,当重写equals方法时要重写hashcode方法。
- 如果两个对象通过equals判定相等,则hashcode相等。
- hashcode相等,equals不一定相等。
19.equals和==的区别?我要比较内容呢?
- equals:用来比较逻辑上是否相等
- ==:用来判断两个对象地址是否相同,即是否是同一个对象。
20.Java代码编译过程
词法分析 -> 语法分析 -> 语义分析 -> 字节码生成
21.如何设计hash函数
22.常用的线程池
23.分段锁
JVM
1.运行时数据区域
程序计数器 | JVM栈 | 本地方法栈 | 堆 | 方法区 | 运行时常量池 | |
---|---|---|---|---|---|---|
功能 | 记录正在执行的虚拟机字节码指令的地址 | 栈帧用于存储局部变量表、操作数栈、常量池引用等信息 | 与JVM栈类似,为本地方法服务 | 对象分配区域,垃圾收集的主要区域 | 用于存访加载的类信息、常量、静态变量、即时编译器编译后的代码 | 方法区的一部分,存放生成的字面量和符号引用 |
线程私有 | 线程私有 | 线程私有 | 公有 | 公有 | 公有 | |
垃圾收集 | 不需要 | 不需要 | 不需要 | 需要(垃圾回收的主要区域) | 类的卸载:1.类实例被回收2.加载类的classloader被回收3.class对象没有被引用方法区在jdk1.8以前放在永久代中,jdk1.8以后放在本地内存中,而不是JVM内存 | 需要 |
2.垃圾收集算法
新生代 | 老年代 | |||
---|---|---|---|---|
垃圾收集算法 | 复制(Eden:Survivor) | 标记-清除/标记-整理 | ||
GC | Minor GC | Full GC | ||
触发条件 | Eden空间满时 | 1.调用System.gc()2.老年代空间不足3.空间分配担保失败 | ||
3.类加载过程:
- 加载:从各种渠道获取二进制字节流转化为方法区的运行时存储结构,在内存中生成一个class对象作为访问入口。
- 验证:确保字节流符合当前虚拟机的要求
- 准备:为类变量分配内存并设置初始值
- 解析:将常量池的符号引用替换为直接引用的过程
- 初始化:虚拟机执行类构造器clinit方法的过程。
()是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的
4.引用类型
描述 | |
---|---|
强引用 | 不会被回收 |
软引用 | 只有在内存不够的情况下才会被回收 |
弱引用 | 一定会被回收,只能存活到下一次垃圾回收发生之前 |
虚引用 | 不会对其生存时间造成影响,唯一目的是在这个对象被回收时收到一个系统通知 |
5.垃圾收集算法
描述 | 不足 | |
---|---|---|
标记-清除 | 标记要收集的对象,然后清除 | 标记和清除效率都不高,造成内存碎片 |
标记-整理 | 让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存 | 对标记-清除算法的补充 |
复制 | 将内存划分为相等的两块,每次只使用其中的一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后把使用过的内存空间进行一次清理 | 只使用了内存的一半 |
分代收集 | 他根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。一般将堆分为新生代和老年代。新生代使用:复制算法老年代使用:标记-清除 或者 标记-整理 算法 |
6.垃圾收集器
多线程与单线程 | 串行与并行 | 描述 | 适用场景 | |
---|---|---|---|---|
Serial收集器 | 单线程 | 串行 | 简单高效 | Client |
ParNew收集器 | 多线程 | 串行 | Serial的多线程版本 | Server |
Parallel Scavenge收集器 | 多线程 | 串行 | 动态调整以提供最合适的停顿时间或者最大吞吐量 | 注重吞吐量以及CPU资源敏感场合 |
Serial Old收集器 | 单线程 | 串行 | Serial的老年代版本 | Client |
Parallel Old收集器 | 多线程 | 串行 | Parallel Scavenge的老年代版本 | 注重吞吐量以及CPU资源敏感场合 |
CMS收集器 | 多线程 | 串行/并行 | 吞吐量低无法处理浮动垃圾标记-清除导致碎片 | |
G1收集器 | 多线程 | 串行/并行 | 空间整合:基于 标记-整理可预测的停顿 |
7.内存分配与回收
描述 | 特点 | 触发条件 | ||
---|---|---|---|---|
Minor GC | 回收新生代 | 频繁,快 | Eden区空间不够时 | |
Full GC | 回收老年代和新生代 | 少,慢 | 1. System.gc()2. 老年代不足(大对象、长期存活的对象进入老年代)3. 空间分配担保失败 |
内存分配策略
- 对象优先在Eden分配
- 大对象直接进入老年代
- 长期存活的对象进入老年代:年龄计数器
- 动态对象年龄判定
- 空间分配担保
计算机网络
1.简述TCP的三次握手、四次挥手,为什么要三次握手?为什么client会进入TIME_WAIT?
TCP的三次握手:
三次握手过程中主要对序号(seq)、确认序号(ack)、标志位(ACK、SYN)进行操作。
(1)client端发送连接请求:SYN=1(建立新连接),seq=x
(2)server端接收请求并返回确认报文:SYN=1(建立新连接),ACK=1(ack有效),ack=x+1,seq=y
(3)client接收到确认报文,再次发送确认消息:ACK=1(ack有效),seq=x+1(client上一条请求seq+1),ack=y+1
(4)server端收到确认后,连接建立
TCP的四次挥手:
(1)client端发送连接释放报文:FIN=1,seq=u
(2)server收到之后发出确认,此时TCP属于半关闭状态,server能向client发送数据反之不能:ACK=1,seq=v ack=u+1
(3)当server处理完毕后,发送连接释放报文:FIN=1,ACK=1,seq=w,ack=u+1
(4)client收到后发出确认,进入TIME-WAIT状态,等来2MSL(最大报文存活时间)后释放连接:ACK=1,seq=u+1,ack=w+1
(5)server收到client的确认后释放连接
为什么要进行三次握手?
第三次握手时为了防止失效的连接请求到达服务器,让服务器错误打开连接。
客户端发送的连接请求如果在网络中滞留,那么就会隔很长时间才能收到服务器的连接确认。客户端等待一个超时重传时间后,就会重新发起请求。但是这个滞留的连接请求最后还是会到达服务器,如果不进行第三次握手,那么服务器就会打开两个连接。如果有第三次握手,客户端会忽略服务器发送的对滞留连接请求的连接确认,不进行第三次握手,因此就不会再次打开连接。
为什么会有TIME_WAIT?
客户端接收到服务器的FIN报文后进入TIME_WAIT状态而不是CLOSED,还需要等待2MSL,理由:
确保最后一个确认报文能够到达。如果server端没收到client端发来的确认报文,那么就会重新发送连接释放请求报文。
为了让本连接持续时间内所产生的所有报文都从网络中消失,使得下一个新的连接不会出现旧的连接请求报文。
2.TCP的拥塞控制
慢开始:最初,发送方只能发送一个报文段(假设),当收到确认后,将拥塞窗口(cwnd)加倍,呈指数型增长
拥塞避免:设置一个慢开始门限ssthresh,当cwnd>=ssthresh,进入拥塞避免,每个轮次只将cwnd加1
快重传:在接收方,要求每次接收到报文段都应该对最后一个已收到的有序报文段进行确认。例如已经接收到M1和M2,此时收到M4,应该发送对M2的确认。在发送方,如果收到三个重复确认,那么可以知道下一个报文段丢失,此时执行快重传,立即重传下一个报文段。
快恢复:在这种情况下,只是丢失个别报文段,不是网络拥塞,因此执行快恢复,令ssthresh=cwnd/2,cwnd=ssthresh,此时直接进入拥塞避免。
3.浏览器输入url请求服务器的过程,分析其中哪些部分用到缓存。
输入url
浏览器查找浏览器缓存
若浏览器缓存中未找到,查找本机host文件
若本机host文件中未找到,则查找路由器、ISP缓存
若路由器、ISP缓存中未找到,则向配置的DNS服务器发起请求查找(若本地域名服务器未找到,会向根域名服务器->顶级域名服务器->主域名服务器)
获取到url对应的ip后,发起TCP三次握手
发送http请求,将响应显示在浏览器页面中
四次挥手结束
4.ARP(地址解析协议)
ARP实现由IP地址得到MAC地址。
主机A知道主机B的IP地址,但是ARP高速缓存中没有该IP地址到MAC地址的映射,此时主机A通过广播的方式发送ARP请求分组,主机B收到该请求后会发送ARP响应分组给主机A告知其MAC地址,随后主机A向其高速缓存中写入主机B的IP地址到MAC地址的映射。
5.HTTP的流量控制,具体的控制算法
流量控制是为了控制发送方发送速率,保证接收方来得及接收。
接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。
6.计算机网络体系结构
设备 | 地址 | 通信方式 | 数据单位 | 协议 | 描述 | ||
---|---|---|---|---|---|---|---|
应用层 | 报文 | HTTP、DNS、FTP、DHCP、SMTP(邮件发送)、POP3和IMAP(邮件接收) | 为特定应用程序提供数据传输服务 | ||||
传输层 | 报文段 | TCP | 为进程提供数据传输服务 | ||||
用户数据报 | UDP | ||||||
网络层 | 路由器(路由选择和分组转发)路由协议选择:RIP/OSPF(内部)BGP(外部) | IP地址 | 分组 | IP协议(分类、子网划分、无分类)NAT:将本地IP转换为全球IP | 为主机提供数据传输服务 | ||
地址解析协议(ARP):由IP地址得到MAC地址 | |||||||
网际控制报文协议(ICMP):封装在IP数据包中,但不属于高层协议,是为了更有效地转发IP数据包和提高交付成功的机会。 | |||||||
网际组管理协议(IGMP) | |||||||
数据链路层 | 交换机(自学习交换表:MAC地址到接口的映射) | 一台主机有多少个网络适配器就有多少个MAC地址 | 广播信道(星型、环形、直线型) | 帧 | 信道复用技术 | 频分复用时分复用统计时分复用波分复用码分复用 | 为同一链路的主机提供数据传输服务 |
点对点信道 | CSMA/CD | ||||||
物理层 | 集线器 | 单工通信半双工通信全双工通信 | 比特 | 在传输媒体上传输数据比特流 |
在向下的过程中,需要添加下层协议所需要的首部或者尾部,而在向上的过程中不断拆开首部和尾部。
7.路由选择协议
RIP | OSPF | BGP | |
---|---|---|---|
名称 | 开放最短路径优先 | 边界网关协议 | |
使用范围 | 内部 | 内部 | 外部 |
描述 | 基于距离向量的路由选择协议 | 洪泛法 | 每个自治系统必须配置BGP发言人,发言人之间通过TCP连接来交换路由信息 |
特点 | 实现简单开销小最大距离为15,限制了网络规模故障传播时间长 | 更新过程收敛快 | 只能寻找一条比较好的路由,而不是最佳路由 |
8.UDP和TCP比较
UDP | TCP | |
---|---|---|
连接 | 无连接 | 面向连接 |
可靠 | 尽最大能力交付 | 可靠交付 |
拥塞控制 | 无 | 有 |
面向 | 面向报文 | 面向字节流 |
通信 | 一对一、一对多、多对一和多对多 | 一对一,全双工通信 |
HTTP
1.HTTP的过程
类似于浏览器输入url请求服务器的过程?
2.HTTPS怎么建立请求
HTTPS = HTTP + SSL(Secure Sockets Layer, 安全套接字层)
HTTPS 可以防窃听(非对称密钥加密)、防伪装、防篡改(加密和认证)
客户端发送请求到服务器端
服务器端返回证书和公开密钥,公开密钥作为证书的一部分而存在
客户端验证证书和公开密钥的有效性,如果有效,则生成共享密钥并使用公开密钥加密发送到服务器端
服务器端使用私有密钥解密数据,并使用收到的共享密钥加密数据,发送到客户端
客户端使用共享密钥解密数据
SSL加密建立…
3.GET和POST比较
GET | POST | |
---|---|---|
作用 | 获取资源 | 传输实体 |
参数 | 查询字符串 | request body |
安全(不会改变服务器状态) | 安全 | 不安全 |
幂等性 | 满足 | 不满足 |
缓存 | 可缓存 | 多数情况下不可以 |
MySQL
1.mysql的索引,最左匹配原则
- 索引可以加快对数据的检索。常见的有B+Tree索引,哈希索引。
最左匹配原则:
- 当索引是联合索引,在查询条件中,mysql是从最左边开始命中的,如果出现了范围查询(>、<、between、like),就不能进一步命中了,后续退化为线性查找,列的排列顺序决定了可命中索引的列数。
2.mysql的主从复制
mysql为了保持高可用,会采用一主多从的结构,一个master节点,多个slave节点,master节点可以进行写操作,而slave节点只能进行读操作。
binlog线程:将主服务器上的数据更改写入二进制日志中
I/O线程:从主服务器上读取二进制日志,并写入从服务器的重放日志中
SQL线程:读取重放日志并重放其中的SQL语句
3.mysql的聚集索引、非聚集索引
聚集索引:以主键创建的索引,在叶子结点上存储的是表中的数据
非聚集索引:以非主键创建的索引,叶子结点上存储的是主键和索引列
使用非聚集索引查询出数据时,拿到叶子上的主键再去查到想要查找的数据。(回表)
4.mysql联合索引,要注意什么?
联合索引即索引由多个列(a,b,c,d)组成,要注意索引的命中,最左匹配原则,从左开始命中,遇到范围查询就不能进一步匹配。
5.为什么数据库要使用B+树来实现索引?
更少的查找次数(B+树相比红黑树更矮胖)
利用磁盘预读特性(一次IO能完全载入一个节点)
6.MySQL索引
描述 | 特点 | 使用场景 | |
---|---|---|---|
B+ Tree索引 | 使用B+ Tree作为底层实现 | 对树进行搜索,查找速度快分为聚簇索引和非聚簇索引 | 查找、排序、分组 |
哈希索引 | 使用哈希作为底层实现 | 无法用于排序与分组只支持精确查找,时间复杂度为O(1) | 当索引值使用的频繁时,会在B+ Tree索引之上再创建一个哈希索引 |
全文索引 | 全文索引使用倒排索引实现,记录着关键词到其所在文档的映射 | 查找文本中的关键词 | |
空间数据索引 | 从所有维度来索引数据 | 用于地理数据存储 |
索引优化:
- 独立的列:索引列不能是表达式的一部分,也不能是函数的参数。
- 多列索引:多个列为条件查询时,使用多列索引。
- 索引的顺序:让选择性最强的索引放在最前面。
- 前缀索引:对于BLOB、TEXT、VARCHAR类型的列,必须使用前缀索引,只索引开始的部分字符。
- 覆盖索引:索引包含所有需要查询的字段的值。
索引的优点:
- 大大减小了服务器需要扫描的行数。
- 帮助服务器避免排序和分组。
- 将随机I/O变为顺序I/O。
7.InnoDB和MyISAM比较
InnoDB | MyISAM | |
---|---|---|
默认 | 是 | 否 |
隔离级别 | 四个隔离级别 | |
事务 | 支持 | 不支持 |
锁 | 行级/表级 | 表级 |
外键 | 支持 | 不支持 |
备份 | 在线热备份 | |
崩溃恢复 | 概率高,恢复慢 | |
特性 | 压缩表和空间数据索引 | |
使用场景 | 读写分离的读表 |
8.切分
- 水平切分:将同一个表中的记录拆分到多个结构相同的表中。
- 切分策略:
- 哈希取模
- 范围:ID或者时间
- 映射表:使用单独的一个数据库来存储映射关系
- 切分策略:
- 垂直切分:将一个表按列切分成多个表,通常按关系紧密度或者使用频率来切分。
9.MySQL数据库是怎么插入的?
10.事务怎么回滚?里面有什么日志?
11.一百万条数据记录,如何分页显示最后一条?
设一个列从1开始自增,并设为索引,以这个列为条件进行查询。
12.数据库事务隔离级别,可重复度和可串行化实现的原理
隔离级别:读未提交、读已提交、可重复度、串行化
- 可重复度:MVCC(多版本并发控制)
- 串行化:MVCC + Next-Key Locks(Record Locks(锁定索引) + Gap Locks(锁定索引间的间隙))
数据库
1.数据库并发一致性问题
数据库并发一致性问题是由于隔离性导致的。
- 丢失修改:新的修改覆盖了老的修改。
- 读脏数据:读取其他线程rollback了的数据。
- 不可重复读:数据的值被修改。
- 幻影读:整条数据的插入和删除。
2.封锁
- 封锁粒度:表级锁 行级锁
- 封锁类型:读写锁 意向锁
- 封锁协议:三级封锁协议 两段锁协议
3.多版本并发控制
基于 | 描述 | |
---|---|---|
系统版本号 | 系统 | 没开始一个事务,系统版本号+1 |
事务版本号 | 事务 | 事务开始时的系统版本号 |
创建版本号 | 行数据 | 数据创建时的系统版本号 |
删除版本号 | 行数据 | 数据删除时的系统版本号 |
4.异常和数据库范式
描述 | |
---|---|
1NF | 属性不可分 |
2NF | 每个非主属性完全依赖于键码 |
3NF | 每个非主属性不传递函数依赖于键码 |
5.连接
关键字 | 描述 | |
---|---|---|
内链接 | INNER JOIN | 等值连接 |
自连接 | INNER JOIN | 自己连接自己 |
自然连接 | MATURAL JOIN | 所有同名列的等值连接 |
外连接 | LEFT OUTER JOIN | 保留左表没有关联的行 |
RIGHT OUTER JOIN | 保留右表没有关联的行 | |
OUTER JOIN | 保留所有没有关联的行 |
数据结构
1.B+树和B树的区别
B+树的数据都在叶子结点上,而B树的非根非叶节点也是数据节点,所以B+树的查询更稳定。
B+树有两个指针,一个指向root节点,一个指向叶子节点的最左侧,因此其范围查询效率更高,而B树则需要中序遍历B树。
同阶的情况下,B+树节点中的关键字要比B树多一个,并且B+树的中间节点不保存数据,所以磁盘也能够容纳更多结点元素,因此B+树更加矮胖,查询效率也更高。
2.红黑树
红黑树是一个自平衡二叉查找树。时间复杂度O(log n)
- 节点颜色为红或者黑
- 根结点是黑色
- 叶节点(NIL结点,空结点)为黑
- 红节点的孩子为黑(路径上不能有两个连续的红节点)
- 从根到叶子节点路径中的黑节点数相等
3.红黑树和平衡二叉树的区别
平衡二叉树和高度相关,保持平衡的代价更高(多次旋转),因此适用于插入、删除较少,查询较多的场景。
红黑树和高度无关,旋转次数较少,因此适用于插入、删除较多的场景。
框架
1.Mybatis动态代理
2.Spring IOC是什么?怎么实现的?
3.Spring IOC里面的反射机制怎么实现的?
Redis
1.redis分片,客户端请求怎么处理?
Redis的分片是指将数据分散到多个Redis实例中的方法,分片之后,每个redis拥有一部分原数据集的子集。在数据量非常大时,分片能将数据量分散到若干主机的redis实例上,进而减轻单台redis实例的压力。
- 范围分片
- 哈希分片
分片的位置:
客户端分片
代理分片
服务器分片
2.redis的zset底层实现
跳跃表来实现。
跳跃表相比于红黑树的优点:
- 存取速度快,节点不需要进行旋转
- 易于实现
- 支持无锁操作
3.redis和mysql的区别
- redis是key-value非关系型数据库,MySQL是关系型数据库
- redis基于内存,也可持久化,MySQL是基于磁盘
- redis读写比MySQL快的多
- redis一般用于热点数据的缓存,MySQL是存储
4.redis加锁
redis为单进程单线程模式,采用队列模式将并发访问变为串行访问,redis本身没有锁的概念,但可以用redis实现分布式锁。
- INCR
Redis Incr 命令将 key 中储存的数字值增一。
如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。
- SETNX
- SET:以上两种都没有设置超时时间,SET可以实现超时时间
分布式锁的核心思想是将设置锁和超时时间、删除锁分别作为一个原子操作进行。
5.redis的淘汰策略
- volatile-lru:在设置超时时间的数据中进行lru
- volatile-ttl:在设置超时时间的数据中挑选即将过期
- volatile-random:在设置超时时间的数据中随机挑选
- allkeys-lru:所有数据的lru
- allkeys-random:所有数据的随机
- noeviction:禁止驱逐数据
6.redis无法被命中怎么办?会出现什么问题?
无法被命中:无法直接通过缓存得到想要的数据
解决方案:
- 缓存尽可能聚焦在高频访问且时效性不高的业务热点上。
- 将缓存容量设置为热点数据的容量。
- 缓存预加载。
- 设置合适的缓存淘汰策略。
7.Redis和MySQL复制和分片
复制 | 分片 | |
---|---|---|
MySQL | 三个线程(binlog线程、I/O线程、SQL线程),目的是实现读写分离 | 水平切分、垂直切分 |
Redis | 使用RDB快照进行复制,发送期间使用缓冲区记录执行的写命令,在RDB快照发送完毕后,发送缓冲区中的写命令 | 水平切分 |
8.Redis是什么?Sorted List是什么?skiplist是什么?怎么实现的?怎么插入一个值?怎么进行查询?和其他数据结构进行对比?
9.Redis的hash和Java的map的区别
消息队列
JVM
1.四种引用类型
- 强引用:如用new关键字创建,不会进行回收。
- 软引用:在内存不足的情况下会进行回收。
- 弱引用:只能存活到下一次垃圾回收。
- 虚引用:不影响其生存周期,只是在回收的时候收到一个系统通知。
2.可达性分析算法的root
可达性分析算法是从GC root出发,只有通过GC root可达的对象是被引用的对象,不可达的对象属于可以回收的对象。
操作系统
1.进程和线程的区别
- 拥有资源:进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问隶属进程的资源。
- 调度:线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换,从一个进程中的线程切换到另一个进程中的线程,会引起进程切换。
- 系统开销:创建、撤销或切换进程,系统都要为之分配、回收资源或保存环境,开销远比线程大。
- 通信方面:线程间可以通过直接读取统一进程中的数据进行通信,但是进程通信需要借助IPC。
主线程是什么?
2.操作系统的内存管理
- 分页地址映射:分页是一种动态重定位技术,通过页表将逻辑地址映射为物理地址。
- 段式存储:分页有一个不可避免的问题就是用户视角的内存和实际物理内存的分离,分段则是将逻辑地址空间分成一组段,每个段长度可以不同,并且可以动态增长,由段表来维护。
- 段页式存储:段内分页。
3.分页式的页表放在哪
进程控制块(PCB)中。
4.进程的PCB里还有哪些东西?
- 进程状态
- 程序计数器
- CPU寄存器
- CPU调度信息
- 内存管理信息
- 记账信息
- I/O状态信息
5.MMU(内存管理单元)
内存管理单元(MMU)管理着地址空间和物理内存的转换,根据其内存管理方式的不同,其中包括基地址寄存器、界限地址寄存器的值以及段表和页表。
6.进程通信
- 管道(父子进程间通信)
- 命名管道FIFO(去除管道只能在父子进程间进行通信,常用于客户-服务器应用程序中)
- 信号量
- 消息队列
- 共享内存(生产者消费者的缓冲池)
- 套接字(可用于不同机器间的进程通信)
7.共享内存
采用共享内存的进程间通信需要通信进程建立共享内存区域。通常一块共享内存区域驻留在生成共享内存段的进程的地址空间。需要使用信号量用来同步对通向存储的访问。
8.Inode
9.应用程序是如何读取文件的?
LINUX —————————————————————
1.linux脚本,杀掉包含一个关键字的所有进程
ps -ef | grep 关键字 | awk '{print $2}' | xargs kill -9
2.自旋锁和互斥锁
都属于linux内核中的内核锁。
互斥锁通过对共享资源的锁定和互斥解决利用资源冲突问题,互斥锁是选择睡眠的方式来对共享工作停止访问的。
自旋锁不会引起调度者睡眠,而是一直循环。
Socket
1.linux I/O模型,说说select和epoll的区别
- Socket等待数据到达
- 复制到内核缓冲区中
- 从内核缓冲区复制到应用进程缓冲区中
描述 | 特点 | |
---|---|---|
阻塞式I/O | 应用进程被阻塞,知道数据从内核缓冲区复制到应用进程缓冲区才返回 | 阻塞期间,其他进程继续执行,CPU利用率高 |
非阻塞式I/O | 轮询I/O是否完成 | 多次执行系统调用,CPU利用率低 |
I/O复用 | select poll epoll | 单个线程具有处理多个I/O时间的能力 |
信号驱动I/O | 执行系统调用后立即返回,内核在数据到达时向应用进程发送SIGIO信号,应用进程收到后将数据从内核复制到应用进程 | CPU利用率比非阻塞I/O高 |
异步I/O | 系统调用立即返回,不会阻塞,内核完成所有操作后向应用进程发送信号 | 异步I/O通知应用进程I/O完成信号驱动I/O是通知应用进程可以开始I/O |
select | poll | epoll | |
---|---|---|---|
timeout精度 | 1ns | 1ms | 1ms |
描述符数据结构 | 数组(因此有最大限制) | 链表 | 链表 |
复制描述符 | 每次调用都需要复制 | 每次调用都需要复制 | 第一次复制、修改 |
返回结果 | 不返回准备好的描述符 | 不返回准备好的描述符 | 准备好的描述符加入到一个链表中管理 |
支持 | 几乎所有系统 | 较新的系统 | Linux系统 |
适用场景 | 实时性高,所有平台 | 实时性低,描述符适中 | 描述符多,描述符变化小 |
select和epoll的区别
- select、poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把当前进程往设备等待队列中挂一次,而epoll只要一次拷贝,而且把当前进程往等待队列上挂也只挂一次(在epoll_waitd的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。
- select、poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll是在设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进行睡眠的进程。select和poll要遍历整个fd集合,epoll只要判断一下就绪链表是否为空就行了。
2.多路复用模型
分布式
1.分布式事务
CAP定理:
- 一致性(Consistency):多个数据副本是否能保持一致的特性。
- 可用性(Availability):分布式系统在面对各种异常时可以提供正常服务的能力。
- 分区容忍性(Partition tolerance):分布式系统在遇到任何网络分区故障的时候,仍然需要能对外提供一致性和可用性的服务。
在分布式系统中,分区容忍性必不可少,因为需要总是假设网络是不可靠的。因此,CAP理论实际上是要在可用性和一致性做权衡。
BASE:
BASE是基本可用(Basically Available)、软状态(Soft State)和最终一致性(Eventually Consistent)三个短语的缩写。
BASE理论是对CAP中一致性和可用性权衡的结果,它的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
ACID要求强一致性,通常运用在传统的数据库系统上。而BASE要求最终一致性,通过牺牲强一致性来达到可用性,通常运用于大型分布式系统中。
解决方案:
(1)两阶段提交(2PC)
基于XA协议实现
存在问题:1.同步阻塞 2.单点问题 3.数据不一致 4.太过保守
(2)TCC
针对每个操作,都要注册一个与其对应的确认和补偿操作
Try/Confirm/Cancel
(3)本地消息表(异步确保)
将业务操作和本地消息表放在一个事务中。业界使用最多。
(4)Sagas事务模型
事件溯源,相当于将分布式系统中的操作都记录到数据库日志表中,要获得最新的状态,则需要重新执行一遍日志表的操作。并且可以dump某一时刻的数据表,在此基础上执行在这之后的操作。
常见设计模式(JDK中)
Spring使用到的设计模式
- 工厂设计模式:Spring使用工厂模式通过BeanFactory、
- ApplicationContext创建bean对象。
- 代理设计模式:SpringAOP功能的实现。
- 单例设计模式:Spring中的Bean默认都是单例的。
- 模板方法模式:Spring中jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类,它们就使用到了模板模式。
- 包装器设计模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
- 观察者模式:Spring事件驱动模型就是观察者模式很经典的一个应用。
- 适配器模式:SpringAOP的增强或通知(Advice)使用到了适配器模式、springMVC中也是用到了适配器模式适配Controller。
JVM
JVM调优的常见命令工具包括
1)jps命令用于查询正在运行的JVM进程,
2)jstat可以实时显示本地或远程JVM进程中类装载、内存、垃圾收集、JIT编译等数据
3)jinfo用于查询当前运行这的JVM属性和参数的值。
4)jmap用于显示当前Java堆和永久代的详细信息
5)jhat用于分析使用jmap生成的dump文件,是JDK自带的工具
6)jstack用于生成当前JVM的所有线程快照,线程快照是虚拟机每一条线程正在执行的方法,目的是定位线程出现长时间停顿的原因。
JVM常见的调优参数包括
-Xmx
指定java程序的最大堆内存, 使用java -Xmx5000M -version判断当前系统能分配的最大堆内存
-Xms
指定最小堆内存, 通常设置成跟最大堆内存一样,减少GC
-Xmn
设置年轻代大小。整个堆大小=年轻代大小 + 年老代大小。所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss
指定线程的最大栈空间, 此参数决定了java函数调用的深度, 值越大调用深度越深, 若值太小则容易出栈溢出错误(StackOverflowError)
-XX:PermSize
指定方法区(永久区)的初始值,默认是物理内存的1/64, 在Java8永久区移除, 代之的是元数据区, 由-XX:MetaspaceSize指定
-XX:MaxPermSize
指定方法区的最大值, 默认是物理内存的1/4, 在java8中由-XX:MaxMetaspaceSize指定元数据区的大小
-XX:NewRatio=n
年老代与年轻代的比值,-XX:NewRatio=2, 表示年老代与年轻代的比值为2:1
-XX:SurvivorRatio=n
Eden区与Survivor区的大小比值,-XX:SurvivorRatio=8表示Eden区与Survivor区的大小比值是8:1:1,因为Survivor区有两个(from, to)
JVM图解
算法相关
对比常见算法
#基础知识
多线程使用问题
stringBuffer和stringBuilder的区别
String 类中使用final关键字修饰字符数组来保存字符串,private final char value[], 所以String对象是不可变的。
StringBuilder与StringBuffer继承AbstractStringBuilder(char value[] 无final,因而可变).
StringBuffer线程安全(对操作接口添加了Synchronized),StringBuilder线程不安全
1.操作少量的数据:适用String
2.单线程操作字符串缓冲区下操作大量数据:适用StringBuilder
3.多线程操作字符串缓冲区下操作大量数据:适用StringBuffer
乐观锁和悲观锁
乐观锁
java 中的乐观锁基本都是通过 CAS (比较跟上一次的版本号,如果一样则更新,如果失败则要重复:读-比较-写。)操作实现的, CAS 是一种更新的原子操作, 比较当前值跟传入值是否一样,一样则更新,否则失败。
悲观锁
java中的悲观锁就是 Synchronized,AQS框架下的锁则是先尝试 cas乐观锁去获取锁,获取不到,才会转换为悲观锁,如 RetreenLock。
Hashcode 与 equals
如果两个Object是equals的,它们的Hashcode一定相等吗? 反过来如果两个Object Hashcode相同,是否一定equals呢?如果上题答对,请继续尝试描述Hashtable的实现原理
- Hashcode:获取散列码,如果对应位置可有值,equals比较值是否相同,如果不同,即发生hash冲突(拉链法),如果相同,不操作
- 如果两个对象相等,则hashcode一定也是相同的
- 两个对象相等,对两个对象分别调用equals方法都返回true
- 两个对象有相同的hashcode值,它们也不一定是相等的
- 因此,equals 方法被覆盖过,则hashCode方法也必须被覆盖
- hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等( 即使这两个对象指向相同的数据)
Java 内存泄露如何定位
- 对象是可达的,即引用计数不为0,
- 对象是无用的
容易引起内存泄漏的几大原因
静态集合类
像HashMap、Vector 等静态集合类的使用最容易引起内存泄漏,因为这些静态变量的生命周期与应用程序一致,如示例
Vector v = new Vector(10);
for (int i = 1; i<100; i++){
Object o = new Object();
v.add(o);
o = null;
}
如果该Vector 是静态的,那么它将一直存在,而其中所有的Object对象也不能被释放,因为它们也将一直被该Vector 引用着。
监听器
在java 编程中,我们都需要和监听器打交道,通常一个应用当中会用到很多监听器,我们会调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。
物理连接
一些物理连接,比如数据库连接和网络连接,除非其显式的关闭了连接,否则是不会自动被GC 回收的。Java 数据库连接一般用DataSource.getConnection()来创建,当不再使用时必须用Close()方法来释放,因为这些连接是独立于JVM的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭Connection ,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。
内部类和外部模块等的引用
内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。对于程序员而言,自己的程序很清楚,如果发现内存泄漏,自己对这些对象的引用可以很快定位并解决,但是现在的应用软件并非一个人实现,模块化的思想在现代软件中非常明显,所以程序员要小心外部模块不经意的引用,例如程序员A 负责A 模块,调用了B 模块的一个方法如:
public void registerMsg(Object b);
这种调用就要非常小心了,传入了一个对象,很可能模块B就保持了对该对象的引用,这时候就需要注意模块B 是否提供相应的操作去除引用
死锁如何定位/修复/预防
死锁是一种特定的程序状态,一般是多线程场景下两个以上的线程互相持有对方需要的锁而处于的永久阻塞状态。
定位方法:
jstack分析线程的栈信息可以定位出来; 或者使用ThreadMXBean相关的api在程序中打印出相关的死锁信息;
1, 找出运行程序的进程ID, ps -ef | grep java
2, 使用jstack pid 来分析线程的状态;
修复:
** 互斥条件、不可剥夺条件、 请求与保持条件、循环等待条件**
破坏任意一个就ok了
规避:
死锁问题一般无法在线解决,一般紧急的先重启应用保证可用;然后在开发环境采用互相代码审查,使用预防性工具比如finBugs提前发现可能发生死锁的程序,修复程序本身的问题。
1, 同一段代码尽量避免使用多个锁;
2,一定要使用多个锁,必须注意顺序;
3,尽量使用带超时时间的等待方法;
4,使用辅助工具,比如findbugs提前发现可能发生死锁的代码段,扼杀在摇篮里。
请解释什么是泛型,泛型运行时如何获取泛型类型
- <? extends T>表示该通配符所代表的类型是 T 类型的子类。
- <? super T>表示该通配符所代表的类型是 T 类型的父类
请解释auto-boxing和auto-unboxingChecked
比如
Integer i=5;int ii=i;
编译器将其变换为:
Integer i=Integer.valueOf(5);int ii=i.intValue();
exception和 Runtime exception的区别是什么?
- Exception 有 两 个 分 支 , 一 个 是 运 行 时 异 常 RuntimeException , 一 个 是CheckedException
- RuntimeException : NullPointerException 、 ClassCastException ; 一 个 是 检 查 异 常
CheckedException,如 I/O 错误导致的 IOException、 SQLException。 RuntimeException 是
那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。 如果出现 RuntimeException,那么一
定是程序员的错误 - 检查异常 CheckedException: 一般是外部错误,这种异常都发生在编译阶段, Java 编译器会强
制程序去捕获此类异常,即会出现要求你把这段可能出现异常的程序进行 try catch,该类异常一
般包括几个方面:- 试图在文件尾部读取数据
- 试图打开一个错误格式的 URL
- 试图根据给定的字符串查找 class 对象,而这个字符串表示的类并不存在
垃圾收集算法(标记-清除算法、复制算法、标记-整理算法、分代收集算法)
1.运行时数据区域
程序计数器 | JVM栈 | 本地方法栈 | 堆 | 方法区 | 运行时常量池 | |
---|---|---|---|---|---|---|
功能 | 记录正在执行的虚拟机字节码指令的地址 | 栈帧用于存储局部变量表、操作数栈、常量池引用等信息 | 与JVM栈类似,为本地方法服务 | 对象分配区域,垃圾收集的主要区域 | 用于存访加载的类信息、常量、静态变量、即时编译器编译后的代码 | 方法区的一部分,存放生成的字面量和符号引用 |
线程私有 | 线程私有 | 线程私有 | 公有 | 公有 | 公有 | |
垃圾收集 | 不需要 | 不需要 | 不需要 | 需要(垃圾回收的主要区域) | 方法区在jdk1.8以前放在永久代中,jdk1.8以后放在本地内存中,而不是JVM内存 | 需要 |
2.垃圾收集算法
新生代 | 老年代 | |||
---|---|---|---|---|
垃圾收集算法 | 复制(Eden:Survivor) | 标记-清除/标记-整理 | ||
GC | Minor GC | Full GC | ||
触发条件 | Eden空间满时 | 1.调用System.gc()2.老年代空间不足3.空间分配担保失败 | ||
内存分配与回收策略
(对象优先在Eden分配,大对象直接进入老年代,长期存活的对象将进入老年代,动态对象年龄判定,空间分配担保)
虚拟机类加载过程(加载,验证,准备,解析,初始化)
- 类加载模型(双亲类委派加载模型:Bootstrap classloader ->Extension classloader ->Application Classloader ->User classloader)
- 类加载过程:
- 加载:从各种渠道获取二进制字节流转化为方法区的运行时存储结构,在内存中生成一个class对象作为访问入口。
- 验证:确保字节流符合当前虚拟机的要求
- 准备:为类变量分配内存并设置初始值
- 解析:将常量池的符号引用替换为直接引用的过程
- 初始化:虚拟机执行类构造器clinit方法的过程。
()是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的
类的卸载
类的卸载:
1.类实例被回收
2.加载类的classloader被回收
3.class对象没有被引用
设计模式
目的在于要重用软件开发经验
你认为为什么需要设计模式
(1)重用设计,重用设计比重用代码更有意义,它会自动带来代码的重用
(2)为设计提供共同的词汇,每个模式名就是一个设计词汇,其概念是的程序员间的交流更加方便。
(3)在开发文档中采用模式词汇可以让其他人更容易理解你的想法和做法,编写开发文档也更方便。
(4)应用设计模式可以让重构系统变得容易,可以确保开发正确的代码,并降低在设计或实现中出现的错误的可能。
(5)支持变化,可以为重写其他应用程序提供很好的系统架构。
(6)正确设计模式,可以节省大量时间。
常见的设计模式
- 列举设计模式在Java API中的一些应用(Reader流接口装饰者decorator模式)
- 开闭原则
- 常用设计模式(代理、工厂、修饰。。。)
- 责任链和观察者异同
Spring
spring ioc概念、设计模式作用
- IoC (Inverse of Control:控制反转)是一-种设计思想, 就是将原本在程序中手动创建对象的控制权,交由Spring框架来管理。IoC 在其他语言中也有应用,并非Spring特有。IoC容器是Spring用来实现IoC的载体,IoC容器实际上就是个Map(key,value) ,Map 中存放的是各种对象。
- 将对象之间的相互依赖关系交给IoC容器来管理,并由IoC容器完成对象的注入。这样可以很大程度_上简化应用的开发,把应用从复杂的依赖关系中解放出来。IoC 容器就像是一个工厂一样,当我们需要创建一一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。在实际项目中一个Service类可能有几百甚至千个类作为它的底层,假如我们需要实例化这个Service,你可能要每次都要搞清这个Service所有底层类的构造函数,这可能会把人逼疯。如果利用IoC的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。
- XML —读取-–> Resource —解析-–> BeanDefinition —注册-–> BeanFactory
spring aop概念、实现(动态代理)
- AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起.来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
- Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用JDKProxy去进行代理了,这时候Spring AOP会使用Cglib,这时候Spring AOP会使用Cglib生成-一个被代理对象的子类来作为代理
- Spring AOP属于运行时增强,而AspectJ是编译时增强。Spring AOP基于代理,(Proxying),而AspectJ基于字节码操作(Bytecode Manipulation)。
spring context作用 :
我们平时常说的 spring 启动其实就是调用 AbstractApplicationContext#refresh 完成 spring context 的初始化和启动过程。spring context 初始化从开始到最后结束以及启动,这整个过程都在 refresh 这个方法中。
refresh 方法刚开始做的是一些 spring context 的准备工作,也就是 spring context 的初始化,比如:创建 BeanFactory、注册 BeanFactoryPostProcessor 等,只有等这些准备工作做好以后才去开始 spring context 的启动。spring bean生命周期
●Bean 容器找到配置文件中Spring Bean的定义。
●Bean 容器利用Java Reflection API创建- - 个Bean的实例。
●如果涉及到一-些属性值利用set()方法设置一- 些属性值。
●如果Bean实现了BeanNameAware接口,调用setBeanName()方法,传入Bean的名字。
●如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoade r对象的实例。
●与上面的类似,如果实现了其他*. Aware接口,就调用相应的方法。
●如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessBeforeInitialization()方法
●如果Bean实现了 InitializingBean接口,执行afterPropertiesSet()方法。
●如果Bean在配置文件中的定义包含init-method属性,执行指定的方法。
●如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessAfterInitialization()方法
●当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destroy()方法。
●当要销毁Bean的时候,如果Bean在配置文件中的定义包含destroy-method属性,执行指定的方法。
- spring mvc概念,如何与servlet规范集成
Spring MVC的请求流程
四大组件
- DispatcherServlet:作为前端控制器,整个流程控制的中心,控制其它组件执行,统一调度,降低组件之间的耦合性,提高每个组件的扩展性。
- HandlerMapping:通过扩展处理器映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
- HandlAdapter:通过扩展处理器适配器,支持更多类型的处理器。
- ViewResolver:通过扩展视图解析器,支持更多类型的视图解析,例如:jsp、freemarker、pdf、excel等。
三次交互
(1) 先发送请求给前端控制器DispatcherServlet,DispatcherServlet调用HandlerMapping处理映射器,找到对应的处理器生成的执行链(对象和拦截器),返还给DispatcherServlet.
(2) DispatcherServlet调用HandlerAdapter,经过适配调用适合的处理器(Controller),Controller处理完返回ModelAndView,HandlerAdapter将执行结果返还给前端控制器。
(3) 前端控制器将ModelAndView传给视图解析器ViewResolver,解析后返回具体的view给前端控制器。
(4)前端控制器根据view进行视图渲染
(5) 前端控制器响应用户。
SpringMVC 与 Servlet的关系:
- springMVC的baiDispatcherServlet继承自 FrameworkServlet继承自HttpServletBean 继承自HttpServlet(也就是servelt) 本质上是一样的东西
- Servlet在很多遗留系统中还是存在的,Servlet运行性能比springMVC高。从开发效率上面讲springMVC是完全的高于servlet的。
SpringMVC是单线程
Java 高并发
Future实现
Java Block IO/Non-Block IO/AsynchornusIO(Netty IO 框架使用)
Comparable 和 Comparator
Comparable接口位于 java.lang包下,Comparator接口位于java.util包下。
Comparable: 内部比较器,一个类如果想要使用 Collections.sort(list) 方法进行排序,则需要实现该接口
Comparator: 外部比较器用于对那些没有实现Comparable接口或者对已经实现的Comparable中的排序规则不满意进行排序.无需改变类的结构,更加灵活。(策略模式)
example:
@Data public class Person implements Comparable<Person>{ String name; Integer age; //排序的规则 public int compareTo(Person o) { //引用类型(可以排序的类型)可以直接调用CompareTo方法 //基本类型--使用 减 //return this.age - o.age;//用this对象 - 参数中的对象,是按照该属性的升序进行的排列 //return o.age - this.age; //return this.name.compareTo(o.name); //return o.name.compareTo(this.name); return this.age.compareTo(o.age); } //使用Collections.sort就可以对Person的List进行排序了
Collections.sort(list,new Comparator<Integer>(){ @Override public int compare(Integer o1, Integer o2) { return o2 - o1; } });
Java集合
hashmap的实现
Java1.8以前 hashmap===数组+链表:
HashMap 根据键的 hashCode 值存储数据,最多一个key为null,
HashMap 非线程安全,可以用 Collections 的 synchronizedMap 方法/ ConcurrentHashMap
大方向上, HashMap 里面是一个数组,然后数组中每个元素是一个单向链表。
容量:2^n(为了方便转为红黑树 “(n一1) & hash”。 (n代表数组长度) ),可以扩容原来的 2 倍。
Java1.8 : 数组+链表+红黑树
链表长度 O(N)>8 —–>红黑树O(logN)
ConcurrentHashMap
- Java1.8前: ConcurrentHashMap 是一个 Segment 数组(16段)+链表, Segment 通过继承ReentrantLock 来进行加锁
- Java1.8 : ConcurrentHashMap 是一个 Segment 数组(16段)+链表+红黑树, Segment 通过synchronized 关键字来进行加锁
如果需要对一个List中的对象频繁进行插入和删除操作,请问应该选择哪个实现类
请问 Hashtable 或者 Hashmap 初始化时的 loadfactor 参数是做什么用的?
数据结构
Heap:构建最大堆
Binary Tree:根据先序+终序遍历结果构建二叉树
算法
链表及反转
怎么判断链表上是否存在环
最长递增子串
楼梯有n个台阶,从下往上走,每跨一步只能向上1级或者2级台阶。要求用程序来求出一共有多少种走法。
一个数组长度为n,有一个数字出现次数大于n/2,找出这个数字
已知一个存放正整数的数组,除了一个元素只出现一次,其余的元素都出现了两次,找出这个只出现一次的元素
redis
redis单点问题,如何解决?
redis cluster原理
强一致性?
常用问题
ip库查找
大文件去重
基于redis设计一个分布式锁
设计实现一个基于内存的发布订阅模块