一个月面了几家大厂 总结了5W字的后端面经(持续更新)

点赞再看,养成习惯,微信搜索【三太子敖丙】关注这个互联网苟且偷生的工具人。前端

本文 GitHub github.com/JavaFamily 已收录,有一线大厂面试完整考点、资料以及个人系列文章。java

前言

前段时间敖丙不是在复习嘛,不少小伙伴也想要个人复习路线,以及我本身笔记里面的一些知识点,好了,丙丙花了一个月的时间,整整一个月啊,给你们整理出来了。mysql

一上来我就放个大招好吧,个人复习脑图,能够说是全得不行,为了防止被盗图,我加了水印哈。nginx

这期看下去你会发现很硬核,并且我会持续更新,啥也不说了,看在我熬夜一个月满脸痘痘的份上,你能够点赞了哈哈。git

噗此不知道为啥掘金这么小了,别的都还挺大的

注:若是图被压缩了,能够去公众号【三太子敖丙】回复【复习】获取原图github

Spring

Spring框架的七大模块

Spring Core:框架的最基础部分,提供 IoC 容器,对 bean 进行管理。web

Spring Context:继承BeanFactory,提供上下文信息,扩展出JNDI、EJB、电子邮件、国际化等功能。面试

Spring DAO:提供了JDBC的抽象层,还提供了声明性事务管理方法。redis

Spring ORM:提供了JPA、JDO、Hibernate、MyBatis 等ORM映射层.算法

Spring AOP:集成了全部AOP功能

Spring Web:提供了基础的 Web 开发的上下文信息,现有的Web框架,如JSF、Tapestry、Structs等,提供了集成

Spring Web MVC:提供了 Web 应用的 Model-View-Controller 全功能实现。

Bean定义5种做用域

singleton(单例) prototype(原型) request session global session

spring ioc初始化流程?

resource定位 即寻找用户定义的bean资源,由 ResourceLoader经过统一的接口Resource接口来完成 beanDefinition载入 BeanDefinitionReader读取、解析Resource定位的资源 成BeanDefinition 载入到ioc中(经过HashMap进行维护BD) BeanDefinition注册 即向IOC容器注册这些BeanDefinition, 经过BeanDefinitionRegistery实现

BeanDefinition加载流程?

定义BeanDefinitionReader解析xml的document BeanDefinitionDocumentReader解析document成beanDefinition

DI依赖注入流程? (实例化,处理Bean之间的依赖关系)

过程在Ioc初始化后,依赖注入的过程是用户第一次向IoC容器索要Bean时触发

  • 若是设置lazy-init=true,会在第一次getBean的时候才初始化bean, lazy-init=false,会容器启动的时候直接初始化(singleton bean);

  • 调用BeanFactory.getBean()生成bean的;

  • 生成bean过程运用装饰器模式产生的bean都是beanWrapper(bean的加强);

    依赖注入怎么处理bean之间的依赖关系?

    其实就是经过在beanDefinition载入时,若是bean有依赖关系,经过占位符来代替,在调用getbean时候,若是遇到占位符,从ioc里获取bean注入到本实例来

Bean的生命周期?

  • 实例化Bean: Ioc容器经过获取BeanDefinition对象中的信息进行实例化,实例化对象被包装在BeanWrapper对象中
  • 设置对象属性(DI):经过BeanWrapper提供的设置属性的接口完成属性依赖注入;
  • 注入Aware接口(BeanFactoryAware, 能够用这个方式来获取其它 Bean,ApplicationContextAware):Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给bean
  • BeanPostProcessor:自定义的处理(分前置处理和后置处理)
  • InitializingBean和init-method:执行咱们本身定义的初始化方法
  • 使用
  • destroy:bean的销毁

IOC:控制反转:将对象的建立权,由Spring管理. DI(依赖注入):在Spring建立对象的过程当中,把对象依赖的属性注入到类中。

Spring的IOC注入方式

构造器注入 setter方法注入 注解注入 接口注入

怎么检测是否存在循环依赖?

Bean在建立的时候能够给该Bean打标,若是递归调用回来发现正在建立中的话,即说明了循环依赖了。

Spring如解决Bean循环依赖问题?

Spring中循环依赖场景有:

  • 构造器的循环依赖
  • 属性的循环依赖
  • singletonObjects:第一级缓存,里面放置的是实例化好的单例对象; earlySingletonObjects:第二级缓存,里面存放的是提早曝光的单例对象; singletonFactories:第三级缓存,里面存放的是要被实例化的对象的对象工厂
  • 建立bean的时候Spring首先从一级缓存singletonObjects中获取。若是获取不到,而且对象正在建立中,就再从二级缓存earlySingletonObjects中获取,若是仍是获取不到就从三级缓存singletonFactories中取(Bean调用构造函数进行实例化后,即便属性还未填充,就能够经过三级缓存向外提早暴露依赖的引用值(提早曝光),根据对象引用能定位到堆中的对象,其原理是基于Java的引用传递),取到后从三级缓存移动到了二级缓存彻底初始化以后将本身放入到一级缓存中供其余使用,
  • 由于加入singletonFactories三级缓存的前提是执行了构造器,因此构造器的循环依赖无法解决。
  • 构造器循环依赖解决办法:在构造函数中使用@Lazy注解延迟加载。在注入依赖时,先注入代理对象,当首次使用时再建立对象说明:一种互斥的关系而非层次递进的关系,故称为三个Map而非三级缓存的原因 完成注入;

Spring 中使用了哪些设计模式?

  • 工厂模式: spring中的BeanFactory就是简单工厂模式的体现,根据传入惟一的标识来得到bean对象;
  • 单例模式: 提供了全局的访问点BeanFactory;
  • 代理模式: AOP功能的原理就使用代理模式(一、JDK动态代理。二、CGLib字节码生成技术代理。)
  • 装饰器模式: 依赖注入就须要使用BeanWrapper;
  • 观察者模式: spring中Observer模式经常使用的地方是listener的实现。如ApplicationListener。
  • 策略模式: Bean的实例化的时候决定采用何种方式初始化bean实例(反射或者CGLIB动态字节码生成)

AOP 核心概念

一、切面(aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象

二、横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点。

三、链接点(joinpoint):被拦截到的点,由于 Spring 只支持方法类型的链接点,因此在Spring 中链接点指的就是被拦截到的方法,实际上链接点还能够是字段或者构造器。

四、切入点(pointcut):对链接点进行拦截的定义

五、通知(advice):所谓通知指的就是指拦截到链接点以后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类。

六、目标对象:代理的目标对象

七、织入(weave):将切面应用到目标对象并致使代理对象建立的过程

八、引入(introduction):在不修改代码的前提下,引入能够在运行期为类动态地添加方法或字段。

解释一下AOP

传统oop开发代码逻辑自上而下的,这个过程当中会产生一些横切性问题,这些问题与咱们主业务逻辑关系不大,会散落在代码的各个地方,形成难以维护,aop思想就是把业务逻辑与横切的问题进行分离,达到解耦的目的,提升代码重用性和开发效率;

AOP 主要应用场景有:

  • 记录日志
  • 监控性能
  • 权限控制
  • 事务管理

AOP源码分析

  • @EnableAspectJAutoProxy给容器(beanFactory)中注册一个AnnotationAwareAspectJAutoProxyCreator对象;

  • AnnotationAwareAspectJAutoProxyCreator对目标对象进行代理对象的建立,对象内部,是封装JDK和CGlib两个技术,实现动态代理对象建立的(建立代理对象过程当中,会先建立一个代理工厂,获取到全部的加强器(通知方法),将这些加强器和目标类注入代理工厂,再用代理工厂建立对象);

  • 代理对象执行目标方法,获得目标方法的拦截器链,利用拦截器的链式机制,依次进入每个拦截器进行执行

    AOP应用场景

    • 日志记录
    • 事务管理
    • 线程池关闭等

AOP使用哪一种动态代理?

  • 当bean的是实现中存在接口或者是Proxy的子类,---jdk动态代理;不存在接口,spring会采用CGLIB来生成代理对象;
  • JDK 动态代理主要涉及到 java.lang.reflect 包中的两个类:Proxy 和 InvocationHandler。
  • Proxy 利用 InvocationHandler(定义横切逻辑) 接口动态建立 目标类的代理对象。

jdk动态代理

  • 经过bind方法创建代理与真实对象关系,经过Proxy.newProxyInstance(target)生成代理对象
  • 代理对象经过反射invoke方法实现调用真实对象的方法

动态代理与静态代理区别

  • 静态代理,程序运行前代理类的.class文件就存在了;
  • 动态代理:在程序运行时利用反射动态建立代理对象<复用性,易用性,更加集中都调用invoke>

CGLIB与JDK动态代理区别

  • Jdk必须提供接口才能使用;
  • C不须要,只要一个非抽象类就能实现动态代理

SpringMVC

springMVC流程:

(1):用户请求发送给DispatcherServlet,DispatcherServlet调用HandlerMapping处理器映射器;

(2):HandlerMapping根据xml或注解找到对应的处理器,生成处理器对象返回给DispatcherServlet;

(3):DispatcherServlet会调用相应的HandlerAdapter;

(4):HandlerAdapter通过适配调用具体的处理器去处理请求,生成ModelAndView返回给DispatcherServlet

(5):DispatcherServlet将ModelAndView传给ViewReslover解析生成View返回给DispatcherServlet;

(6):DispatcherServlet根据View进行渲染视图;

->DispatcherServlet->HandlerMapping->Handler ->DispatcherServlet->HandlerAdapter处理handler->ModelAndView ->DispatcherServlet->ModelAndView->ViewReslover->View ->DispatcherServlet->返回给客户

Mybatis

Mybatis原理

  • sqlsessionFactoryBuilder生成sqlsessionFactory(单例)
  • 工厂模式生成sqlsession执行sql以及控制事务
  • Mybatis经过动态代理使Mapper(sql映射器)接口能运行起来即为接口生成代理对象将sql查询到结果映射成pojo

sqlSessionFactory构建过程

  • 解析并读取配置中的xml建立Configuration对象 (单例)
  • 使用Configruation类去建立sqlSessionFactory(builder模式)

Mybatis一级缓存与二级缓存

默认状况下一级缓存是开启的,并且是不能关闭的。

  • 一级缓存是指 SqlSession 级别的缓存 原理:使用的数据结构是一个 map,若是两次中间出现 commit 操做 (修改、添加、删除),本 sqlsession 中的一级缓存区域所有清空
  • 二级缓存是指能够跨 SqlSession 的缓存。是 mapper 级别的缓存; 原理: 是经过 CacheExecutor 实现的。CacheExecutor实际上是 Executor 的代理对象

Zookeeper+eureka+springcloud

SpringBoot启动流程

  • new springApplication对象,利用spi机制加载applicationContextInitializer, applicationLister接口实例(META-INF/spring.factories);

  • 调run方法准备Environment,加载应用上下文(applicationContext),发布事件 不少经过lister实现

  • 建立spring容器, refreshContext() ,实现starter自动化配置,spring.factories文件加载, bean实例化

    SpringBoot自动配置的原理

    • @EnableAutoConfiguration找到META-INF/spring.factories(须要建立的bean在里面)配置文件
    • 读取每一个starter中的spring.factories文件

Spring Boot 的核心注解

核心注解是@SpringBootApplication 由如下三种组成

  • @SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
  • @EnableAutoConfiguration:打开自动配置的功能。
  • @ComponentScan:Spring组件扫描。

SpringBoot经常使用starter都有哪些

spring-boot-starter-web - Web 和 RESTful 应用程序; spring-boot-starter-test - 单元测试和集成测试; spring-boot-starter-jdbc - 传统的 JDBC; spring-boot-starter-security - 使用 SpringSecurity 进行身份验证和受权; spring-boot-starter-data-jpa - 带有 Hibernate 的 Spring Data JPA; spring-boot-starter-data-rest - 使用 Spring Data REST 公布简单的 REST 服务

Spring Boot 的核心配置文件

(1):Application.yml 通常用来定义单个应用级别的,若是搭配 spring-cloud-config 使用

(2).Bootstrap.yml(先加载) 系统级别的一些参数配置,这些参数通常是不变的

Zuul与Gateway区别

(1):zuul则是netflix公司的项目集成在spring-cloud中使用而已, Gateway是spring-cloud的 一个子项目;

(2):zuul不提供异步支持流控等均由hystrix支持, gateway提供了异步支持,提供了抽象负载均衡,提供了抽象流控; 理论上gateway则更适合于提升系统吞吐量(但不必定能有更好的性能),最终性能还须要经过严密的压测来决定

(3):二者底层实现都是servlet,可是gateway多嵌套了一层webflux框架

(4): zuul可用至其余微服务框架中,内部没有实现限流、负载均衡;gateway只能用在springcloud中;

Zuul原理分析

(1):请求给zuulservlet处理(HttpServlet子类) zuulservlet中有一个zuulRunner对象,该对象中初始化了RequestContext(存储请求的数据),RequestContext被全部的zuulfilter共享;

(2): zuulRunner中有 FilterProcessor(zuulfilter的管理器),其从filterloader 中获取zuulfilter;

(3):有了这些filter以后, zuulservelet执行的Pre-> route-> post 类型的过滤器,若是在执行这些过滤器有错误的时候则会执行error类型的过滤器,执行完后把结果返回给客户端.

Gateway原理分析

(1):请求到达DispatcherHandler, DispatchHandler在IOC容器初始化时会在容器中实例化HandlerMapping接口

(2):用handlerMapping根据请求URL匹配到对应的Route,而后有对应的filter作对应的请求转发最终response返回去

Zookeeper 工做原理(待查)

Zookeeper 的核心是原子广播,这个机制保证了各个 server 之间的同步。实现这个机制的协议叫作 Zab 协议。Zab 协议有两种模式,它们分别是恢复模式和广播模式。

zoo与eur区别

  • zookeeper保证cp(一致性)
  • eureka保证ap(可用性)
  • zoo在选举期间注册服务瘫痪,期间不可用
  • eur各个节点平等关系,只要有一台就可保证服务可用,而查询到的数据可能不是最新的,能够很好应对网络故障致使部分节点失联状况
  • zoo有leader和follower角色,eur各个节点平等
  • zoo采用半数存活原则(避免脑裂),eur采用自我保护机制来解决分区问题
  • eur本质是个工程,zoo只是一个进程 ZooKeeper基于CP,不保证高可用,若是zookeeper正在选主,或者Zookeeper集群中半数以上机器不可用,那么将没法得到数据。 Eureka基于AP,能保证高可用,即便全部机器都挂了,也能拿到本地缓存的数据。做为注册中心,其实配置是不常常变更的,只有发版(发布新的版本)和机器出故障时会变。对于不常常变更的配置来讲,CP是不合适的,而AP在遇到问题时能够用牺牲一致性来保证可用性,既返回旧数据,缓存数据。 因此理论上Eureka是更适合作注册中心。而现实环境中大部分项目可能会使用ZooKeeper,那是由于集群不够大,而且基本不会遇到用作注册中心的机器一半以上都挂了的状况。因此实际上也没什么大问题。

Hystrix原理(待查)

经过维护一个本身的线程池,当线程池达到阈值的时候,就启动服务降级,返回fallback默认值

为何须要hystrix熔断

防止雪崩,及时释放资源,防止系统发生更多的额级联故障,须要对故障和延迟进行隔离,防止单个依赖关系的失败影响整个应用程序;

微服务优缺点

  • 每一个服务高内聚,松耦合,面向接口编程;
  • 服务间通讯成本,数据一致性,多服务运维难度增长,http传输效率不如rpc

eureka自我保护机制

  • eur不移除长时间没收到心跳而应该过时的服务
  • 仍然接受新服务注册和查询请求,可是不会同步到其它节点(高可用)
  • 当网络稳定后,当前实例新注册信息会同步到其它节点(最终一致性)

MQ对比

ActiveMQ:Apache出品,最先使用的消息队列产品,时间比较长了,最近版本更新比较缓慢。 RabbitMQ:erlang语言开发,支持不少的协议,很是重量级,更适合于企业级的开发。性能较好,可是不利于作二次开发和维护。 RocketMQ:阿里开源的消息中间件,纯Java开发,具备高吞吐量、高可用性、适合大规模分布式系统应用的特色,分布式事务。 ZeroMQ:号称最快的消息队列系统,尤为针对大吞吐量的需求场景,采用 C 语言实现。 消息队列的选型须要根据具体应用需求而定,ZeroMQ 小而美,RabbitMQ 大而稳,Kakfa 和 RocketMQ 快而强劲

JAVA基础

AVL树与红黑树(R-B树)的区别与联系

  • AVL是严格的平衡树,所以在增长或者删除节点的时候,根据不一样状况,旋转的次数比红黑树要多;
  • 红黑树是用非严格的平衡来换取增删节点时候旋转次数的下降开销;
  • 因此简单说,查询多选择AVL树,查询更新次数差很少选红黑树
  • AVL树顺序插入和删除时有20%左右的性能优点,红黑树随机操做15%左右优点,现实应用固然通常都是随机状况,因此红黑树获得了更普遍的应用 索引为B+树 Hashmap为红黑树

为啥redis zset使用跳跃链表而不用红黑树实现

  • skiplist的复杂度和红黑树同样,并且实现起来更简单。
  • 在并发环境下红黑树在插入和删除时须要rebalance,性能不如跳表。

JAVA基本数据类型

(1个字节是8个bit) 整数型:byte(1字节)、short(2字节)、int(4字节)、long(8字节) 浮点型:float(4字节)、double(8字节) 布尔型:boolean(1字节) 字符型:char(2字节)

IO与NIO

包括 类File,outputStream,inputStream,writer,readerseralizable(5类1接口)

NIO三大核心内容 selector(选择器,用于监听channel),channel(通道),buffer(缓冲区)

NIO与IO区别,IO面向流,NIO面向缓冲区;io阻塞,nio非阻塞

异常类

throwable为父类,子为error跟exception,exception分runtime(空指针,越界等)跟checkexception(sql,io,找不到类等异常)

LVS(4层与7层)原理

  • 由前端虚拟负载均衡器和后端真实服务器群组成;
  • 请求发送给虚拟服务器后其根据包转发策略以及负载均衡调度算法转发给真实服务器
  • 所谓四层(lvs,f5)就是基于IP+端口的负载均衡;七层(nginx)就是基于URL等应用层信息的负载均衡

StringBuilder与StringBuffer

  • StringBuilder 更快;
  • StringBuffer是线程安全的

interrupt/isInterrupted/interrupt区别

  • interrupt() 调用该方法的线程的状态为将被置为"中断"状态(set操做)
  • isinterrupted() 是做用于调用该方法的线程对象所对应的线程的中断信号是true仍是false(get操做)。例如咱们能够在A线程中去调用B线程对象的isInterrupted方法,查看的是A
  • interrupted()是静态方法:内部实现是调用的当前线程的isInterrupted(),而且会重置当前线程的中断状态(getandset)

sleep与wait区别

sleep属于线程类,wait属于object类;sleep不释放锁

CountDownLatch和CyclicBarrier区别

  • con用于主线程等待其余子线程任务都执行完毕后再执行,cyc用于一组线程相互等待你们都达到某个状态后,再同时执行;
  • CountDownLatch是不可重用的,CyclicBarrier可重用

终止线程方法

  • 使用退出标志,说线程正常退出;
  • 经过判断this.interrupted() throw new InterruptedException()来中止 使用String常量池做为锁对象会致使两个线程持有相同的锁,另外一个线程不执行,改用其余如new Object()

ThreadLocal的原理和应用

原理:

线程中建立副本,访问本身内部的副本变量,内部实现是其内部类名叫ThreadLocalMap的成员变量threadLocals,key为自己,value为实际存值的变量副本

应用:

  • 用来解决数据库链接,存放connection对象,不一样线程存放各自session;
  • 解决simpleDateFormat线程安全问题;
  • 会出现内存泄漏,显式remove..不要与线程池配合,由于worker每每是不会退出的;

threadLocal 内存泄漏问题

若是是强引用,设置tl=null,可是key的引用依然指向ThreadLocal对象,因此会有内存泄漏,而使用弱引用则不会; 可是仍是会有内存泄漏存在,ThreadLocal被回收,key的值变成null,致使整个value再也没法被访问到; 解决办法:在使用结束时,调用ThreadLocal.remove来释放其value的引用;

若是咱们要获取父线程的ThreadLocal值呢

ThreadLocal是不具有继承性的,因此是没法获取到的,可是咱们能够用InteritableThreadLocal来实现这个功能。InteritableThreadLocal继承来ThreadLocal,重写了createdMap方法,已经对应的get和set方法,不是在利用了threadLocals,而是interitableThreadLocals变量。

这个变量会在线程初始化的时候(调用init方法),会判断父线程的interitableThreadLocals变量是否为空,若是不为空,则把放入子线程中,可是其实这玩意没啥鸟用,当父线程建立完子线程后,若是改变父线程内容是同步不到子线程的。。。一样,若是在子线程建立完后,再去赋值,也是没啥鸟用的

线程状态

线程池有5种状态:running,showdown,stop,Tidying,TERMINATED。

  • running:线程池处于运行状态,能够接受任务,执行任务,建立线程默认就是这个状态了

  • showdown:调用showdown()函数,不会接受新任务,可是会慢慢处理完堆积的任务。

  • stop:调用showdownnow()函数,不会接受新任务,不处理已有的任务,会中断现有的任务。

  • Tidying:当线程池状态为showdown或者stop,任务数量为0,就会变为tidying。这个时候会调用钩子函数terminated()。

  • TERMINATED:terminated()执行完成。

在线程池中,用了一个原子类来记录线程池的信息,用了int的高3位表示状态,后面的29位表示线程池中线程的个数。

Java中的线程池是如何实现的?

  • 线程中线程被抽象为静态内部类Worker,是基于AQS实现的存放在HashSet中;
  • 要被执行的线程存放在BlockingQueue中;
  • 基本思想就是从workQueue中取出要执行的任务,放在worker中处理;

若是线程池中的一个线程运行时出现了异常,会发生什么

若是提交任务的时候使用了submit,则返回的feature里会存有异常信息,可是若是数execute则会打印出异常栈。可是不会给其余线程形成影响。以后线程池会删除该线程,会新增长一个worker。

线程池原理

  • 提交一个任务,线程池里存活的核心线程数小于corePoolSize时,线程池会建立一个核心线程去处理提交的任务
  • 若是线程池核心线程数已满,即线程数已经等于corePoolSize,一个新提交的任务,会被放进任务队列workQueue排队等待执行。
  • 当线程池里面存活的线程数已经等于corePoolSize了,而且任务队列workQueue也满,判断线程数是否达到maximumPoolSize,即最大线程数是否已满,若是没到达,建立非核心线程执行提交的任务。
  • 若是当前的线程数达到了maximumPoolSize,还有新的任务过来的话,直接采用拒绝策略处理。

拒绝策略

  • AbortPolicy直接抛出异常阻止线程运行;

  • CallerRunsPolicy若是被丢弃的线程任务未关闭,则执行该线程;

  • DiscardOldestPolicy移除队列最先线程尝试提交当前任务

  • DiscardPolicy丢弃当前任务,不作处理

newFixedThreadPool (固定数目线程的线程池)

  • 阻塞队列为无界队列LinkedBlockingQueue
  • 适用于处理CPU密集型的任务,适用执行长期的任务

newCachedThreadPool(可缓存线程的线程池)

  • 阻塞队列是SynchronousQueue
  • 适用于并发执行大量短时间的小任务

newSingleThreadExecutor(单线程的线程池)

  • 阻塞队列是LinkedBlockingQueue
  • 适用于串行执行任务的场景,一个任务一个任务地执行

newScheduledThreadPool(定时及周期执行的线程池)

  • 阻塞队列是DelayedWorkQueue
  • 周期性执行任务的场景,须要限制线程数量的场景

java锁相关

synchronized实现原理

contentionList(请求锁线程队列) entryList(有资格的候选者队列) waitSet(wait方法后阻塞队列) onDeck(竞争候选者) ower(竞争到锁线程) !ower(执行成功释放锁后状态); Synchronized 是非公平锁。

Synchronized 在线程进入 ContentionList 时,等待的线程会先尝试自旋获取锁,若是获取不到就进入 ContentionList,这明显对于已经进入队列的线程是不公平的,还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占 OnDeck 线程的锁资源。

底层是由一对monitorenter和monitorexit指令实现的(监视器锁)

每一个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的全部权,过程:

  • 若是monitor的进入数为0,则该线程进入monitor,而后将进入数设置为1,该线程即为monitor的全部者。
  • 若是线程已经占有该monitor,只是从新进入,则进入monitor的进入数加1.
  • 若是其余线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再从新尝试获取monitor的全部权。

ReentrantLock 是如何实现可重入性的 ?

内部自定义了同步器 Sync,加锁的时候经过CAS 算法 ,将线程对象放到一个双向链表 中,每次获取锁的时候 ,看下当前维 护的那个线程ID和当前请求的线程ID是否同样,同样就可重入了;

ReentrantLock如何避免死锁?

  • 响应中断lockInterruptibly()
  • 可轮询锁tryLock()
  • 定时锁tryLock(long time)

tryLock 和 lock 和 lockInterruptibly 的区别

(1):tryLock 能得到锁就返回 true,不能就当即返回 false,

(2):tryLock(long timeout,TimeUnit unit),能够增长时间限制,若是超过该时间段还没得到锁,返回 false

(3):lock 能得到锁就返回 true,不能的话一直等待得到锁

(4):lock 和 lockInterruptibly,若是两个线程分别执行这两个方法,但此时中断这两个线程, lock 不会抛出异常,而 lockInterruptibly 会抛出异常。

CountDownLatch和CyclicBarrier的区别是什么

CountDownLatch是等待其余线程执行到某一个点的时候,在继续执行逻辑(子线程不会被阻塞,会继续执行),只能被使用一次。最多见的就是join形式,主线程等待子线程执行完任务,在用主线程去获取结果的方式(固然不必定),内部是用计数器相减实现的(没错,又特么是AQS),AQS的state承担了计数器的做用,初始化的时候,使用CAS赋值,主线程调用await()则被加入共享线程等待队列里面,子线程调用countDown的时候,使用自旋的方式,减1,知道为0,就触发唤醒。

CyclicBarrier回环屏障,主要是等待一组线程到底同一个状态的时候,放闸。CyclicBarrier还能够传递一个Runnable对象,能够到放闸的时候,执行这个任务。CyclicBarrier是可循环的,当调用await的时候若是count变成0了则会重置状态,如何重置呢,CyclicBarrier新增了一个字段parties,用来保存初始值,当count变为0的时候,就从新赋值。还有一个不一样点,CyclicBarrier不是基于AQS的,而是基于RentrantLock实现的。存放的等待队列是用了条件变量的方式。

synchronized与ReentrantLock区别

  • 都是可重入锁; R是显示获取和释放锁,s是隐式;
  • R更灵活能够知道有没有成功获取锁,能够定义读写锁,是api级别,s是JVM级别;
  • R能够定义公平锁;Lock是接口,s是java中的关键字

什么是信号量Semaphore

信号量是一种固定资源的限制的一种并发工具包,基于AQS实现的,在构造的时候会设置一个值,表明着资源数量。信号量主要是应用因而用于多个共享资源的互斥使用,和用于并发线程数的控制(druid的数据库链接数,就是用这个实现的),信号量也分公平和非公平的状况,基本方式和reentrantLock差很少,在请求资源调用task时,会用自旋的方式减1,若是成功,则获取成功了,若是失败,致使资源数变为了0,就会加入队列里面去等待。调用release的时候会加一,补充资源,并唤醒等待队列。

Semaphore 应用

  • acquire() release() 可用于对象池,资源池的构建,好比静态全局对象池,数据库链接池;
  • 可建立计数为1的S,做为互斥锁(二元信号量)

可重入锁概念

(1):可重入锁是指同一个线程能够屡次获取同一把锁,不会由于以前已经获取过还没释放而阻塞;

(2):reentrantLock和synchronized都是可重入锁

(3):可重入锁的一个优势是可必定程度避免死锁

ReentrantLock原理(CAS+AQS)

CAS+AQS队列来实现

(1):先经过CAS尝试获取锁, 若是此时已经有线程占据了锁,那就加入AQS队列而且被挂起;

(2): 当锁被释放以后, 排在队首的线程会被唤醒CAS再次尝试获取锁,

(3):若是是非公平锁, 同时还有另外一个线程进来尝试获取可能会让这个线程抢到锁;

(4):若是是公平锁, 会排到队尾,由队首的线程获取到锁。

AQS 原理

Node内部类构成的一个双向链表结构的同步队列,经过控制(volatile的int类型)state状态来判断锁的状态,对于非可重入锁状态不是0则去阻塞;

对于可重入锁若是是0则执行,非0则判断当前线程是不是获取到这个锁的线程,是的话把state状态+1,好比重入5次,那么state=5。 而在释放锁的时候,一样须要释放5次直到state=0其余线程才有资格得到锁

AQS两种资源共享方式

  • Exclusive:独占,只有一个线程能执行,如ReentrantLock

  • Share:共享,多个线程能够同时执行,如Semaphore、CountDownLatch、ReadWriteLock,CyclicBarrier

CAS原理

内存值V,旧的预期值A,要修改的新值B,当A=V时,将内存值修改成B,不然什么都不作;

CAS的缺点:

(1):ABA问题; (2):若是CAS失败,自旋会给CPU带来压力; (3):只能保证对一个变量的原子性操做,i++这种是不能保证的

CAS在java中的应用:

(1):Atomic系列

公平锁与分公平锁

(1):公平锁指在分配锁前检查是否有线程在排队等待获取该锁,优先分配排队时间最长的线程,非公平直接尝试获取锁 (2):公平锁需多维护一个锁线程队列,效率低;默认非公平

独占锁与共享锁

(1):ReentrantLock为独占锁(悲观加锁策略) (2):ReentrantReadWriteLock中读锁为共享锁 (3): JDK1.8 邮戳锁(StampedLock), 不可重入锁 读的过程当中也容许获取写锁后写入!这样一来,咱们读的数据就可能不一致,因此,须要一点额外的代码来判断读的过程当中是否有写入,这种读锁是一种乐观锁, 乐观锁的并发效率更高,但一旦有小几率的写入致使读取的数据不一致,须要能检测出来,再读一遍就行

4种锁状态

  • 无锁

  • 偏向锁 会偏向第一个访问锁的线程,当一个线程访问同步代码块得到锁时,会在对象头和栈帧记录里存储锁偏向的线程ID,当这个线程再次进入同步代码块时,就不须要CAS操做来加锁了,只要测试一下对象头里是否存储着指向当前线程的偏向锁 若是偏向锁未启动,new出的对象是普通对象(即无锁,有稍微竞争会成轻量级锁),若是启动,new出的对象是匿名偏向(偏向锁) 对象头主要包括两部分数据:Mark Word(标记字段, 存储对象自身的运行时数据)、class Pointer(类型指针, 是对象指向它的类元数据的指针)

  • 轻量级锁(自旋锁) (1):在把线程进行阻塞操做以前先让线程自旋等待一段时间,可能在等待期间其余线程已经 解锁,这时就无需再让线程执行阻塞操做,避免了用户态到内核态的切换。(自适应自旋时间为一个线程上下文切换的时间)

  • (2):在用自旋锁时有可能形成死锁,当递归调用时有可能形成死锁

  • (3):自旋锁底层是经过指向线程栈中Lock Record的指针来实现的

  • 重量级锁

轻量级锁与偏向锁的区别

(1):轻量级锁是经过CAS来避免进入开销较大的互斥操做

(2):偏向锁是在无竞争场景下彻底消除同步,连CAS也不执行

自旋锁升级到重量级锁条件

(1):某线程自旋次数超过10次;

(2):等待的自旋线程超过了系统core数的一半;

读写锁了解嘛,知道读写锁的实现方式嘛

经常使用的读写锁ReentrantReanWritelock,这个其实和reentrantLock类似,也是基于AQS的,可是这个是基于共享资源的,不是互斥,关键在于state的处理,读写锁把高16为记为读状态,低16位记为写状态,就分开了,读读状况其实就是读锁重入,读写/写读/写写都是互斥的,只要判断低16位就行了。

zookeeper实现分布式锁

(1):利用节点名称惟一性来实现,加锁时全部客户端一块儿建立节点,只有一个建立成功者得到锁,解锁时删除节点。

(2):利用临时顺序节点实现,加锁时全部客户端都建立临时顺序节点,建立节点序列号最小的得到锁,不然监视比本身序列号次小的节点进行等待

(3):方案2比1好处是当zookeeper宕机后,临时顺序节点会自动删除释放锁,不会形成锁等待;

(4):方案1会产生惊群效应(当有不少进程在等待锁的时候,在释放锁的时候会有不少进程就过来争夺锁)。

(5):因为须要频繁建立和删除节点,性能上不如redis锁

volatile变量

(1):变量可见性

(2):防止指令重排序

(3):保障变量单次读,写操做的原子性,但不能保证i++这种操做的原子性,由于本质是读,写两次操做

volatile如何保证线程间可见和避免指令重排

volatile可见性是有指令原子性保证的,在jmm中定义了8类原子性指令,好比write,store,read,load。而volatile就要求write-store,load-read成为一个原子性操做,这样子能够确保在读取的时候都是从主内存读入,写入的时候会同步到主内存中(准确来讲也是内存屏障),指令重排则是由内存屏障来保证的,由两个内存屏障:

  • 一个是编译器屏障:阻止编译器重排,保证编译程序时在优化屏障以前的指令不会在优化屏障以后执行。
  • 第二个是cpu屏障:sfence保证写入,lfence保证读取,lock相似于锁的方式。java多执行了一个“load addl $0x0, (%esp)”操做,这个操做至关于一个lock指令,就是增长一个彻底的内存屏障指令。

JVM

jre、jdk、jvm的关系:

jdk是最小的开发环境,由jre++java工具组成。

jre是java运行的最小环境,由jvm+核心类库组成。

jvm是虚拟机,是java字节码运行的容器,若是只有jvm是没法运行java的,由于缺乏了核心类库。

JVM内存模型

(1):堆<对象,静态变量,共享

(2):方法区<存放类信息,常量池,共享>(java8移除了永久代(PermGen),替换为元空间(Metaspace))

(3):虚拟机栈<线程执行方法的时候内部存局部变量会存堆中对象的地址等等数据>

(4):本地方法栈<存放各类native方法的局部变量表之类的信息>

(5):程序计数器<记录当前线程执行到哪一条字节码指令位置>

对象4种引用

(1):强(内存泄露主因)

(2):软(只有软引用的话,空间不足将被回收),适合缓存用

(3):弱(只,GC会回收)

(4):虚引用(用于跟踪GC状态)用于管理堆外内存

对象的构成:

一个对象分为3个区域:对象头、实例数据、对齐填充

对象头:主要是包括两部分,1.存储自身的运行时数据好比hash码,分代年龄,锁标记等(可是不是绝对哦,锁状态若是是偏向锁,轻量级锁,是没有hash码的。。。是不固定的)2.指向类的元数据指针。还有可能存在第三部分,那就是数组类型,会多一块记录数组的长度(由于数组的长度是jvm判断不出来的,jvm只有元数据信息)

实例数据:会根据虚拟机分配策略来定,分配策略中,会把相同大小的类型放在一块儿,并按照定义顺序排列(父类的变量也会在哦)

对齐填充:这个意义不是很大,主要在虚拟机规范中对象必须是8字节的整数,因此当对象不知足这个状况时,就会用占位符填充

若是判断一个对象是否存活:

通常判断对象是否存活有两种算法,一种是引用计数,另一种是可达性分析。在java中主要是第二种

java是根据什么来执行可达性分析的:

根据GC ROOTS。GC ROOTS能够的对象有:虚拟机栈中的引用对象,方法区的类变量的引用,方法区中的常量引用,本地方法栈中的对象引用。

JVM 类加载顺序

(1):加载 获取类的二进制字节流,将其静态存储结构转化为方法区的运行时数据结构

(2):校验 文件格式验证,元数据验证,字节码验证,符号引用验证

(3):准备 在方法区中对类的static变量分配内存并设置类变量数据类型默认的初始值,不包括实例变量,实例变量将会在对象实例化的时候随着对象一块儿分配在Java堆中

(4):解析 将常量池内的符号引用替换为直接引用的过程

(5):初始化 为类的静态变量赋予正确的初始值(Java代码中被显式地赋予的值)

JVM三种类加载器

(1):启动类加载器(home) 加载jvm核心类库,如java.lang.*等

(2):扩展类加载器(ext), 父加载器为启动类加载器,从jre/lib/ext下加载类库

(3):应用程序类加载器(用户classpath路径) 父加载器为扩展类加载器,从环境变量中加载类

双亲委派机制

(1):类加载器收到类加载的请求

(2):把这个请求委托给父加载器去完成,一直向上委托,直到启动类加载器

(3):启动器加载器检查能不能加载,能就加载(结束);不然,抛出异常,通知子加载器进行加载

(4):保障类的惟一性和安全性以及保证JDK核心类的优先加载

双亲委派模型有啥做用:

保证java基础类在不一样的环境仍是同一个Class对象,避免出现了自定义类覆盖基础类的状况,致使出现安全问题。还能够避免类的重复加载。

如何打破双亲委派模型?

(1):自定义类加载器,继承ClassLoader类重写loadClass方法;

(2):SPI

tomcat是如何打破双亲委派模型:

tomcat有着特殊性,它须要容纳多个应用,须要作到应用级别的隔离,并且须要减小重复性加载,因此划分为:/common 容器和应用共享的类信息,/server容器自己的类信息,/share应用通用的类信息,/WEB-INF/lib应用级别的类信息。总体能够分为:boostrapClassLoader->ExtensionClassLoader->ApplicationClassLoader->CommonClassLoader->CatalinaClassLoader(容器自己的加载器)/ShareClassLoader(共享的)->WebAppClassLoader。虽然第一眼是知足双亲委派模型的,可是不是的,由于双亲委派模型是要先提交给父类装载,而tomcat是优先判断是不是本身负责的文件位置,进行加载的。

SPI: (Service Provider interface)

(1):服务提供接口(服务发现机制):

(2):经过加载ClassPath下META_INF/services,自动加载文件里所定义的类

(3):经过ServiceLoader.load/Service.providers方法经过反射拿到实现类的实例

SPI应用?

(1):应用于JDBC获取数据库驱动链接过程就是应用这一机制

(2):apache最先提供的common-logging只有接口.没有实现..发现日志的提供商经过SPI来具体找到日志提供商实现类

双亲委派机制缺陷?

(1):双亲委派核心是越基础的类由越上层的加载器进行加载, 基础的类老是做为被调用代码调用的API,没法实现基础类调用用户的代码….

(2):JNDI服务它的代码由启动类加载器去加载,可是他须要调独立厂商实现的应用程序,如何解决? 线程上下文件类加载器(Thread Context ClassLoader), JNDI服务使用这个线程上下文类加载器去加载所须要的SPI代码,也就是父类加载器请求子类加载器去完成类加载动做Java中全部涉及SPI的加载动做基本上都采用这种方式,例如JNDI,JDBC

致使fullGC的缘由

(1):老年代空间不足

(2):永久代(方法区)空间不足

(3):显式调用system.gc()

堆外内存的优缺点

Ehcache中的一些版本,各类 NIO 框架,Dubbo,Memcache 等中会用到,NIO包下ByteBuffer来建立堆外内存 堆外内存,其实就是不受JVM控制的内存。

相比于堆内内存有几个优点:

减小了垃圾回收的工做,由于垃圾回收会暂停其余的工做。 加快了复制的速度。由于堆内在 flush 到远程时,会先复制到直接内存(非堆内存),而后在发送;而堆外内存至关于省略掉了复制这项工做。 能够扩展至更大的内存空间。好比超过 1TB 甚至比主存还大的空间。

缺点总结以下:

堆外内存难以控制,若是内存泄漏,那么很难排查,经过-XX:MaxDirectMemerySize来指定,当达到阈值的时候,调用system.gc来进行一次full gc 堆外内存相对来讲,不适合存储很复杂的对象。通常简单的对象或者扁平化的比较适合 jstat查看内存回收概况,实时查看各个分区的分配回收状况, jmap查看内存栈,查看内存中对象占用大小, jstack查看线程栈,死锁,性能瓶颈

JVM七种垃圾收集器

(1): Serial 收集器 复制算法,单线程,新生代)

(2): ParNew 收集器(复制算法,多线程,新生代)

(3): Parallel Scavenge 收集器(多线程,复制算法,新生代,高吞吐量)

(4):Serial Old 收集器(标记-整理算法,老年代)

(5):Parallel Old 收集器(标记-整理算法,老年代,注重吞吐量的场景下,jdk8默认采用 Parallel Scavenge + Parallel Old 的组合)

(6):CMS 收集器(标记-清除算法,老年代,垃圾回收线程几乎能作到与用户线程同时工做,吞吐量低,内存碎片)以牺牲吞吐量为代价来得到最短回收停顿时间-XX:+UseConcMarkSweepGC jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代) jdk1.9 默认垃圾收集器G1

使用场景:

(1):应用程序对停顿比较敏感

(2):在JVM中,有相对较多存活时间较长的对象(老年代比较大)会更适合使用CMS

cms垃圾回收过程:

(1):初始标识<找到gcroot(stw)>

GC Roots有如下几种:

1:系统类加载器加载的对象

2:处于激活状态的线程

3:JNI栈中的对象

4:正在被用于同步的各类锁对象

5:JVM自身持有的对象,好比系统类加载器等。

(2):并发标记(三色标记算法) 三色标记算法处理并发标记出现对象引用变化状况: 黑:本身+子对象标记完成 灰:本身完成,子对象未完成 白:未标记; 并发标记 黑->灰->白 从新标记 灰->白引用消失,黑引用指向->白,致使白漏标 cms处理办法是incremental update方案 (增量更新)把黑色变成灰色 多线程下并发标记依旧会产生漏标问题,因此cms必须remark一遍(jdk1.9之后不用cms了)

G1 处理方案:

SATB(snapshot at the begining)把白放入栈中,标记过程是和应用程序并发运行的(不须要Stop-The-World) 这种方式会形成某些是垃圾的对象也被当作是存活的,因此G1会使得占用的内存被实际须要的内存大。不过下一次就回收了 ZGC 处理方案: 颜色指针(color pointers) 2*42方=4T

(3):从新标记(stw)

(4)并发清理

备注:从新标记是防止标记成垃圾以后,对象被引用

(5):G1 收集器(新生代 + 老年代,在多 CPU 和大内存的场景下有很好的性能) G1在java9 即是默认的垃圾收集器,是cms 的替代者 逻辑分代,用分区(region)的思想(默认分2048份) 仍是有stw 为解决CMS算法产生空间碎片HotSpot提供垃圾收集器,经过-XX:+UseG1GC来启用

G1中提供了三种模式垃圾回收模式

(1):young gc(eden region被耗尽没法申请内存时,就会触发)

(2):mixed gc(当老年代大小占整个堆大小百分比达到该阈值时,会触发)

(3):full gc(对象内存分配速度过快,mixed gc来不及回收,致使老年代被填满,就会触发)

(8):ZGC和shenandoah (oracle产收费) no stw

arthas 监控工具

(1):dashboard命令查看整体jvm运行状况

(2):jvm显示jvm详细信息

(3):thread 显示jvm里面全部线程信息(相似于jstack) 查看死锁线程命令thread -b

(4):sc * 显示全部类(search class)

(5):trace 跟踪方法

定位频繁full GC,堆内存满 oom

第一步:jps获取进程号 第二步:jmap -histo pid | head -20 得知有个对象在不断建立 备注:jmap若是线上服务器堆内存特别大,,会卡死需堆转存(通常会说在测试环境压测,导出转存) -XX:+HeapDumpOnOutOfMemoryError或jmap -dumpLformat=b,file=xxx pid 转出文件进行分析 (arthas没有实现jmap命令)heapdump --live /xxx/xx.hprof导出文件

G1垃圾回收器(重点)

回收过程 (1):young gc(年轻代回收)--当年轻代的Eden区用尽时--stw 第一阶段,扫描根。 根是指static变量指向的对象,正在执行的方法调用链条上的局部变量等 第二阶段,更新RS(Remembered Sets)。 处理dirty card queue中的card,更新RS。此阶段完成后,RS能够准确的反映老年代对所在的内存分段中对象的引用 第三阶段,处理RS。 识别被老年代对象指向的Eden中的对象,这些被指向的Eden中的对象被认为是存活的对象。 第四阶段,复制对象。 此阶段,对象树被遍历,Eden区内存段中存活的对象会被复制到Survivor区中空的内存分段 第五阶段,处理引用。 处理Soft,Weak,Phantom,Final,JNI Weak 等引用。

(2):concrruent marking(老年代并发标记) 当堆内存使用达到必定值(默认45%)时,不须要Stop-The-World,在并发标记前先进行一次young gc

(3):混合回收(mixed gc) 并发标记过程结束之后,紧跟着就会开始混合回收过程。混合回收的意思是年轻代和老年代会同时被回收

(4):Full GC? Full GC是指上述方式不能正常工做,G1会中止应用程序的执行,使用单线程的内存回收算法进行垃圾回收,性能会很是差,应用程序停顿时间会很长。要避免Full GC的发生,一旦发生须要进行调整。

何时发生Full GC呢?

好比堆内存过小,当G1在复制存活对象的时候没有空的内存分段可用,则会回退到full gc,这种状况能够经过增大内存解决

尽管G1堆内存仍然是分代的,可是同一个代的内存再也不采用连续的内存结构

年轻代分为Eden和Survivor两个区,老年代分为Old和Humongous两个区

新分配的对象会被分配到Eden区的内存分段上

Humongous区用于保存大对象,若是一个对象占用的空间超过内存分段Region的一半;

若是对象的大小超过一个甚至几个分段的大小,则对象会分配在物理连续的多个Humongous分段上。

Humongous对象由于占用内存较大而且连续会被优先回收

为了在回收单个内存分段的时候没必要对整个堆内存的对象进行扫描(单个内存分段中的对象可能被其余内存分段中的对象引用)引入了RS数据结构。RS使得G1能够在年轻代回收的时候没必要去扫描老年代的对象,从而提升了性能。每个内存分段都对应一个RS,RS保存了来自其余分段内的对象对于此分段的引用

JVM会对应用程序的每个引用赋值语句object.field=object进行记录和处理,把引用关系更新到RS中。可是这个RS的更新并非实时的。G1维护了一个Dirty Card Queue

那为何不在引用赋值语句处直接更新RS呢?

这是为了性能的须要,使用队列性能会好不少。

线程本地分配缓冲区(TLAB: Thread Local Allocation Buffer)?

栈上分配->tlab->堆上分配 因为堆内存是应用程序共享的,应用程序的多个线程在分配内存的时候须要加锁以进行同步。为了不加锁,提升性能每个应用程序的线程会被分配一个TLAB。TLAB中的内存来自于G1年轻代中的内存分段。当对象不是Humongous对象,TLAB也能装的下的时候,对象会被优先分配于建立此对象的线程的TLAB中。这样分配会很快,由于TLAB隶属于线程,因此不须要加锁

PLAB: Promotion Thread Local Allocation Buffer

G1会在年轻代回收过程当中把Eden区中的对象复制(“提高”)到Survivor区中,Survivor区中的对象复制到Old区中。G1的回收过程是多线程执行的,为了不多个线程往同一个内存分段进行复制,那么复制的过程也须要加锁。为了不加锁,G1的每一个线程都关联了一个PLAB,这样就不须要进行加锁了

OOM问题定位方法

(1):jmap -heap 10765如上图,能够查看新生代,老生代堆内存的分配大小以及使用状况;

(2):jstat 查看GC收集状况

(3):jmap -dump:live,format=b,file=到本地

(4):经过MAT工具打开分析

DUBBO

dubbo流程

(1):生产者(Provider)启动,向注册中心(Register)注册

(2):消费者(Consumer)订阅,然后注册中心通知消费者

(3):消费者从生产者进行消费

(4):监控中心(Monitor)统计生产者和消费者

Dubbo推荐使用什么序列化框架,还有哪些?

推荐使用Hessian序列化,还有Duddo、FastJson、Java自带序列化

Dubbo默认使用的是什么通讯框架,还有哪些?

默认使用 Netty 框架,也是推荐的选择,另外内容还集成有Mina、Grizzly。

Dubbo有哪几种负载均衡策略,默认是哪一种?

(1):随机调用<默认>

(2):权重轮询

(3):最少活跃数

(4):一致性Hash

RPC流程

(1)消费者调用须要消费的服务,

(2):客户端存根将方法、入参等信息序列化发送给服务端存根

(3):服务端存根反序列化操做根据解码结果调用本地的服务进行相关处理

(4):本地服务执行具体业务逻辑并将处理结果返回给服务端存根

(5):服务端存根序列化

(6):客户端存根反序列化

(7):服务消费方获得最终结果

RPC框架的实现目标PC框架的实现目标是把调用、编码/解码的过程给封装起来,让用户感受上像调用本地服务同样的调用远程服务

服务暴露、服务引用、服务调用(TODO)

Redis

redis单线程为何执行速度这么快?

(1):纯内存操做,避免大量访问数据库,减小直接读取磁盘数据,redis将数据储存在内存里面,读写数据的时候都不会受到硬盘 I/O 速度的限制,因此速度快

(2):单线程操做,避免了没必要要的上下文切换和竞争条件,也不存在多进程或者多线程致使的切换而消耗CPU,不用去考虑各类锁的问题,不存在加锁释放锁操做,没有由于可能出现死锁而致使的性能消耗

(3):采用了非阻塞I/O多路复用机制

Redis数据结构底层实现

String:

(1)Simple dynamic string(SDS)的数据结构

struct sdshdr{
 //记录buf数组中已使用字节的数量
 //等于 SDS 保存字符串的长度
 int len;
 //记录 buf 数组中未使用字节的数量
 int free
 //字节数组,用于保存字符串
 char buf[];
}
复制代码

它的优势: (1)不会出现字符串变动形成的内存溢出问题

(2)获取字符串长度时间复杂度为1

(3)空间预分配, 惰性空间释放free字段,会默认留够必定的空间防止屡次重分配内存

应用场景: String 缓存结构体用户信息,计数

Hash:

数组+链表的基础上,进行了一些rehash优化; 1.Reids的Hash采用链地址法来处理冲突,而后它没有使用红黑树优化。

2.哈希表节点采用单链表结构。

3.rehash优化 (采用分而治之的思想,将庞大的迁移工做量划分到每一次CURD中,避免了服务繁忙)

应用场景: 保存结构体信息可部分获取不用序列化全部字段

List:

应用场景: (1):好比twitter的关注列表,粉丝列表等均可以用Redis的list结构来实现

(2):list的实现为一个双向链表,便可以支持反向查找和遍历

Set:

内部实现是一个 value为null的HashMap,实际就是经过计算hash的方式来快速排重的,这也是set能提供判断一个成员 是否在集合内的缘由。 应用场景: 去重的场景,交集(sinter)、并集(sunion)、差集(sdiff),实现如共同关注、共同喜爱、二度好友等功能

Zset:

内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是全部的成员,排序依据是HashMap里存的score,使用跳跃表的结构能够得到比较高的查找效率,而且在实现上比较简单。 跳表:每一个节点中维持多个指向其余节点的指针,从而达到快速访问节点的目的 应用场景: 实现延时队列

redis事务

(1):Multi开启事务

(2):Exec执行事务块内命令

(3):Discard 取消事务

(4):Watch 监视一个或多个key,若是事务执行前key被改动,事务将打断

redis事务的实现特征

(1):全部命令都将会被串行化的顺序执行,事务执行期间,Redis不会再为其它客户端的请求提供任何服务,从而保证了事物中的全部命令被原子的执行。

(2):Redis事务中若是有某一条命令执行失败,其后的命令仍然会被继续执行

(3):在事务开启以前,若是客户端与服务器之间出现通信故障并致使网络断开,其后全部待执行的语句都将不会被服务器执行。然而若是网络中断事件是发生在客户端执行EXEC命令以后,那么该事务中的全部命令都会被服务器执行

(4):当使用Append-Only模式时,Redis会经过调用系统函数write将该事务内的全部写操做在本次调用中所有写入磁盘。

然而若是在写入的过程当中出现系统崩溃,如电源故障致使的宕机,那么此时也许只有部分数据被写入到磁盘,而另一部分数据却已经丢失。

Redis服务器会在从新启动时执行一系列必要的一致性检测,一旦发现相似问题,就会当即退出并给出相应的错误提示。此时,咱们就要充分利用Redis工具包中提供的redis-check-aof工具,该工具能够帮助咱们定位到数据不一致的错误,并将已经写入的部分数据进行回滚。修复以后咱们就能够再次从新启动Redis服务器了

Redis的同步机制?

(1):全量拷贝, 1.slave第一次启动时,链接Master,发送PSYNC命令,

2.master会执行bgsave命令来生成rdb文件,期间的全部写命令将被写入缓冲区。

  
  
  
  
3. master bgsave执行完毕,向slave发送rdb文件 复制代码
  • slave收到rdb文件,丢弃全部旧数据,开始载入rdb文件

  • rdb文件同步结束以后,slave执行从master缓冲区发送过来的因此写命令。

  • 复制代码复制代码此后 master 每执行一个写命令,就向slave发送相同的写命令。 复制代码

    (2):增量拷贝 若是出现网络闪断或者命令丢失等异常状况,从节点以前保存了自身已复制的偏移量和主节点的运行ID

    1. 主节点根据偏移量把复制积压缓冲区里的数据发送给从节点,保证主从复制进入正常状态。

      redis集群模式性能优化

      (1) Master最好不要作任何持久化工做,如RDB内存快照和AOF日志文件

      (2) 若是数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次

      (3) 为了主从复制的速度和链接的稳定性,Master和Slave最好在同一个局域网内

      (4) 尽可能避免在压力很大的主库上增长从库

      (5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…这样的结构方便解决单点故障问题,实现Slave对Master的替换。若是Master挂了,能够马上启用Slave1作Master,其余不变。

      Redis集群方案

      (1):官方cluster方案

      (2):twemproxy

      代理方案twemproxy是一个单点,很容易对其形成很大的压力,因此一般会结合keepalived来实twemproy的高可用

      (3):codis 基于客户端来进行分片

    集群不可用场景

    (1):master挂掉,且当前master没有slave

    (2):集群超过半数以上master挂掉,不管是否有slave集群进入fail状态

    redis 最适合的场景

    (1):会话缓存session cache

    (2):排行榜/计数器ZRANGE

    (3):发布/订阅

    缓存淘汰策略

    (1):先进先出算法(FIFO)

    (2):最近使用最少Least Frequently Used(LFU)

    (3):最长时间未被使用的Least Recently Used(LRU)

    当存在热点数据时,LRU的效率很好,但偶发性的、周期性的批量操做会致使LRU命中率急剧降低,缓存污染状况比较严重

    redis过时key删除策略

    (1):惰性删除,cpu友好,可是浪费cpu资源

    (2):定时删除(不经常使用)

    (3):按期删除,cpu友好,节省空间

    缓存雪崩以及处理办法

    同一时刻大量缓存失效;

    处理方法:

    (1):缓存数据增长过时标记

    (2):设置不一样的缓存失效时间

    (3):双层缓存策略C1为短时间,C2为长期

    (4):定时更新策略

    缓存击穿缘由以及处理办法

    频繁请求查询系统中不存在的数据致使;

    处理方法:

    (1):cache null策略,查询反馈结果为null仍然缓存这个null结果,设置不超过5分钟过时时间

    (2):布隆过滤器,全部可能存在的数据映射到足够大的bitmap中 google布隆过滤器:基于内存,重启失效不支持大数据量,没法在分布式场景 redis布隆过滤器:可扩展性,不存在重启失效问题,须要网络io,性能低于google

    redis阻塞缘由

    (1):数据结构使用不合理bigkey

    (2):CPU饱和

    (3):持久化阻塞,rdb fork子线程,aof每秒刷盘等

    hot key出现形成集群访问量倾斜解决办法

    (1):使用本地缓存

    (2): 利用分片算法的特性,对key进行打散处理(给hot key加上前缀或者后缀,把一个hotkey 的数量变成 redis 实例个数N的倍数M,从而由访问一个 redis key 变成访问 N * M 个redis key)

    Redis分布式锁

    2.6版本之后lua脚本保证setnx跟setex进行原子性(setnx以后,未setex,服务挂了,锁不释放) a获取锁,超过过时时间,自动释放锁,b获取到锁执行,a代码执行完remove锁,a和b是同样的key,致使a释放了b的锁。 解决办法:remove以前判断value(高并发下value可能被修改,应该用lua来保证原子性)

    Redis如何作持久化

    bgsave作镜像全量持久化,aof作增量持久化。由于bgsave会耗费较长时间,不够实时,在停机的时候会致使大量丢失数据 ,因此须要aof来配合使用。在redis实例重启时,会使用bgsave持久化文件从新构建内存,再使用aof重放近期的操做指令来 实 现完整恢复重启以前的状态。

    对方追问那若是忽然机器掉电会怎样?

    取决于aof日志sync属性的配置,若是不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。可是在高性能的要求下每次都sync是不现实的,通常都使用定时sync,好比1s1次,这个时候最多就会丢失1s的数据.

    redis锁续租问题?

    (1):基于redis的redission分布式可重入锁RLock,以及配合java集合中lock;

    (2):Redission 内部提供了一个监控锁的看门狗,不断延长锁的有效期,默认检查锁的超时时间是30秒

    (3):此方案的问题:若是你对某个redis master实例,写入了myLock这种锁key的value,此时会异步复制给对应的master ,slave实例。可是这个过程当中一旦发生redis master宕机,主备切换,redis slave变为了redis master。

    接着就会致使,客户端2来尝试加锁的时候,在新的redis master上完成了加锁,而客户端1也觉得本身成功加了锁。 此时就会致使多个客户端对一个分布式锁完成了加锁 解决办法:只须要将新的redis实例,在一个TTL时间内,对客户端不可用便可,在这个时间内,全部客户端锁将被失效或者自动释放.

    bgsave的原理是什么?

    fork和cow。fork是指redis经过建立子进程来进行bgsave操做,cow指的是copy on write,子进程建立后,父子进程共享数据段,父进程继续提供读写服务,写进的页面数据会逐渐和子进程分离开来。

    RDB与AOF区别

    (1):R文件格式紧凑,方便数据恢复,保存rdb文件时父进程会fork出子进程由其完成具体持久化工做,最大化redis性能,恢复大数据集速度更快,只有手动提交save命令或关闭命令时才触发备份操做;

    (2):A记录对服务器的每次写操做(默认1s写入一次),保存数据更完整,在redis重启是会重放这些命令来恢复数据,操做效率高,故障丢失数据更少,可是文件体积更大;

    1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,若是将它们所有找出来?

    使用keys指令能够扫出指定模式的key列表。 若是这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题? redis的单线程的。keys指令会致使线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候能够使用scan指令,scan指令能够无阻塞的提取出指定模式的key列表,可是会有必定的重复几率,在客户端作一次去重就能够了 ,可是总体所花费的时间会比直接用keys指令长。

    如何使用Redis作异步队列?

    通常使用list结构做为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。

    可不能够不用sleep呢?

    list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。

    能不能生产一次消费屡次呢?

    使用pub/sub主题订阅者模式,能够实现1:N的消息队列。

    pub/sub有什么缺点?

    在消费者下线的状况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。

    redis如何实现延时队列?

    使用sortedset,想要执行时间的时间戳做为score,消息内容做为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒以前的数据轮询进行处理。

    为啥redis zset使用跳跃链表而不用红黑树实现?

    (1):skiplist的复杂度和红黑树同样,并且实现起来更简单。

    (2):在并发环境下红黑树在插入和删除时须要rebalance,性能不如跳表。

    MYSQL

    数据库三范式

    一: 确保每列的原子性

    二:非主键列不存在对主键的部分依赖 (要求每一个表只描述一件事情)

    三: 知足第二范式,而且表中的列不存在对非主键列的传递依赖

    数据库主从复制原理

    (1):主库db的更新事件(update、insert、delete)被写到binlog

    (2):主库建立一个binlog dump thread线程,把binlog的内容发送到从库

    (3):从库建立一个I/O线程,读取主库传过来的binlog内容并写入到relay log.

    (4):从库还会建立一个SQL线程,从relay log里面读取内容写入到slave的db.

    复制方式分类

    (1):异步复制(默认) 主库写入binlog日志后便可成功返回客户端,无须等待binlog日志传递给从库的过程,可是一旦主库宕机,就有可能出现丢失数据的状况。

    (2)半同步复制:( 5.5版本以后) (安装半同步复制插件)确保从库接收完成主库传递过来的binlog内容已经写入到本身的relay log(传送log)后才会通知主库上面的等待线程。若是等待超时,则关闭半同步复制,并自动转换为异步复制模式,直到至少有一台从库通知主库已经接收到binlog信息为止

    存储引擎

    (1):Myiasm是mysql默认的存储引擎,不支持数据库事务,行级锁,外键;插入更新需锁表,效率低,查询速度快,Myisam使用的是非汇集索引

    (2):innodb 支持事务,底层为B+树实现,适合处理多重并发更新操做,普通select都是快照读,快照读不加锁。InnoDb使用的是汇集索引

    汇集索引

    (1):汇集索引就是以主键建立的索引

    (2):每一个表只能有一个聚簇索引,由于一个表中的记录只能以一种物理顺序存放,实际的数据页只能按照一颗 B+ 树进行排序

    (3):表记录的排列顺序和与索引的排列顺序一致

    (4):汇集索引存储记录是物理上连续存在

    (5):聚簇索引主键的插入速度要比非聚簇索引主键的插入速度慢不少

    (6):聚簇索引适合排序,非聚簇索引不适合用在排序的场合,由于聚簇索引叶节点自己就是索引和数据按相同顺序放置在一块儿,索引序便是数据序,数据序便是索引序,因此很快。非聚簇索引叶节点是保留了一个指向数据的指针,索引自己固然是排序的,可是数据并未排序,数据查询的时候须要消耗额外更多的I/O,因此较慢

    (7):更新汇集索引列的代价很高,由于会强制innodb将每一个被更新的行移动到新的位置

    非汇集索引

    (1):除了主键之外的索引

    (2):汇集索引的叶节点就是数据节点,而非聚簇索引的叶节点仍然是索引节点,并保留一个连接指向对应数据块

    (3):聚簇索引适合排序,非聚簇索引不适合用在排序的场合

    (4):汇集索引存储记录是物理上连续存在,非汇集索引是逻辑上的连续。

    使用汇集索引为何查询速度会变快?

    使用聚簇索引找到包含第一个值的行后,即可以确保包含后续索引值的行在物理相邻

    创建汇集索引有什么须要注意的地方吗?

    在聚簇索引中不要包含常常修改的列,由于码值修改后,数据行必须移动到新的位置,索引此时会重排,会形成很大的资源浪费

    InnoDB 表对主键生成策略是什么样的?

    优先使用用户自定义主键做为主键,若是用户没有定义主键,则选取一个Unique键做为主键,若是表中连Unique键都没有定义的话,则InnoDB会为表默认添加一个名为row_id隐藏列做为主键。

    非汇集索引最多能够有多少个?

    每一个表你最多能够创建249个非聚簇索引。非聚簇索引须要大量的硬盘空间和内存

    BTree 与 Hash 索引有什么区别?

    (1):BTree索引可能须要屡次运用折半查找来找到对应的数据块 (2):HASH索引是经过HASH函数,计算出HASH值,在表中找出对应的数据 (3):大量不一样数据等值精确查询,HASH索引效率一般比B+TREE高 (4):HASH索引不支持模糊查询、范围查询和联合索引中的最左匹配规则,而这些Btree索引都支持

    数据库索引优缺点

    (1):须要查询,排序,分组和联合操做的字段适合创建索引

    (2):索引多,数据更新表越慢,尽可能使用字段值不重复比例大的字段做为索引,联合索引比多个独立索引效率高

    (3):对数据进行频繁查询进创建索引,若是要频繁更改数据不建议使用索引

    (4):当对表中的数据进行增长、删除和修改的时候,索引也要动态的维护,下降了数据的维护速度。

    索引的底层实现是B+树,为什么不采用红黑树,B树?

    (1):B+Tree非叶子节点只存储键值信息,下降B+Tree的高度,全部叶子节点之间都有一个链指针,数据记录都存放在叶子节点中

    (2): 红黑树这种结构,h明显要深的多,效率明显比B-Tree差不少

    (3):B+树也存在劣势,因为键会重复出现,所以会占用更多的空间。可是与带来的性能优点相比,空间劣势每每能够接受,所以B+树的在数据库中的使用比B树更加普遍

    索引失效条件

    (1):条件是or,若是还想让or条件生效,给or每一个字段加个索引

    (2):like开头%

    (3):若是列类型是字符串,那必定要在条件中将数据使用引号引用起来,不然不会使用索引

    (4):where中索引列使用了函数或有运算

    数据库事务特色

    ACID 原子性,一致性,隔离性,永久性

    数据库事务说是如何实现的?

    (1):经过预写日志方式实现的,redo和undo机制是数据库实现事务的基础

    (2):redo日志用来在断电/数据库崩溃等情况发生时重演一次刷数据的过程,把redo日志里的数据刷到数据库里,保证了事务 的持久性(Durability)

    (3):undo日志是在事务执行失败的时候撤销对数据库的操做,保证了事务的原子性

    数据库事务隔离级别

    (1):读未提交read-uncommitted-- 脏,不可重复读--幻读 A读取了B未提交的事务,B回滚,A 出现脏读;

    (2):不可重复读read-committed-- 不可重复读--幻读 A只能读B已提交的事务,可是A还没结束,B又更新数据隐式提交,而后A又读了一次出现不可重复读;

    (3):可重复读repeatable-read<默认>-- 幻读 事务开启,不容许其余事务的UPDATE修改操做 A读取B已提交的事务,然而B在该表插入新的行,以后A在读取的时候多出一行,出现幻读;

    (4):串行化serializable--

    七种事务传播行为

    (1)Propagation.REQUIRED<默认> 若是当前存在事务,则加入该事务,若是当前不存在事务,则建立一个新的事务。

    (2)Propagation.SUPPORTS 若是当前存在事务,则加入该事务;若是当前不存在事务,则以非事务的方式继续运行。

    (3)Propagation.MANDATORY 若是当前存在事务,则加入该事务;若是当前不存在事务,则抛出异常。

    (4)Propagation.REQUIRES_NEW 从新建立一个新的事务,若是当前存在事务,延缓当前的事务。

    (5)Propagation.NOT_SUPPORTED 以非事务的方式运行,若是当前存在事务,暂停当前的事务。

    (6)Propagation.NEVER 以非事务的方式运行,若是当前存在事务,则抛出异常。

    (7)Propagation.NESTED 若是没有,就新建一个事务;若是有,就在当前事务中嵌套其余事务。

    产生死锁的四个必要条件

    (1):互斥: 资源x的任意一个时刻只能被一个线程持有 (2):占有且等待:线程1占有资源x的同时等待资源y,并不释放x (3):不可抢占:资源x一旦被线程1占有,其余线程不能抢占x (4):循环等待:线程1持有x,等待y,线程2持有y,等待x 当所有知足时才会死锁

    用于掘金字数限制2W字,原文你们去个人Git或者公众号看吧,我掐准了2W字发的

    总结

    内容过于硬核了,致使不少排版细节,我没办法作得像其余期同样精致了,你们见谅。

    涉及的内容和东西太多了,可能不少都是点到为止,也有不少不全的,也有不少错误的点,已经快3W字了,我校验实在困难,我会放在GitHub上面,你们能够跟我一块儿更新这个文章,造福后人吧。

    搞很差下次我须要看的时候,我都得看着这个复习了。

    我是敖丙,一个在互联网苟且偷生的工具人。

    你知道的越多,你不知道的越多人才们的 【三连】 就是丙丙创做的最大动力,咱们下期见!

    注:若是本篇博客有任何错误和建议,欢迎人才们留言,你快说句话啊


    文章持续更新,能够微信搜索「 三太子敖丙 」第一时间阅读,回复【资料】【面试】【简历】有我准备的一线大厂面试资料和简历模板,本文 GitHub github.com/JavaFamily 已经收录,有大厂面试完整考点,欢迎Star。

    相关文章
    相关标签/搜索