## 一、JAVA

1、四种引用讲讲,分别使用在哪些地方?

java根据其生命周期的长短又将引用类型分为强引用、软引用、弱引用、虚引用; 强引用:new一个对象就是强引用,例如 Object obj = new Object();当JVM的内存空间不足时,宁愿抛出OutOfMemoryError使得程序异常终止也不愿意回收具有强引用的存活着的对象; 软引用:软引用的生命周期比强引用短一些。软引用是通过SoftReference类实现的。当JVM认为内存空间不足时,就会去试图回收软引用指向的对象;软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收! 这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。 弱引用:弱引用是通过WeakReference类实现的,它的生命周期比软引用还要短。在GC的时候,不管内存空间足不足都会回收这个对象,也同样适用于内存敏感的缓存。如果一个对象是偶尔的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么应该用 Weak Reference 来记住此对象。或者想引用一个对象,但是这个对象有自己的生命周期,你不想介入这个对象的生命周期,这时候就应该用弱引用,这个引用不会在对象的垃圾回收判断中产生任何附加的影响。 虚引用:虚引用是通过PhantomReference类实现的。任何时候可能被GC回收,就像没有引用一样;无法通过虚引用访问对象的任何属性或者函数。虚引用仅仅只是提供了一种确保对象被finalize以后来做某些事情的机制。

二、设计模式

1、访问者模式是什么东西,解决什么问题?

访问者模式是一种分离对象数据结构与行为的方法,通过这种分离,可以为一个已存在的类增加新的操作而无须为它们进行修改,在spring中就有对这个设计模式的实现案例,在java中我们会通过注解@Value通过占位符对类中的属性赋值,而且是解析的Properties 文件中的值映射到类的成员变量上,只需要修改Properties文件。在spring中每个对象都会被解析成BeanDefinition ,然后访问者模式中,会用Spring 的 BeanDefinitionVisitor 用来访问 BeanDefinition,访问的具体调用就是BeanDefinitionVisitor.visitBeanDefinition(bd);封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。于要更新的表有状态字段,并且刚好要更新状态字段的这种特殊情况,并非所有场景都适用。

三、ORM

1、MyBatis只需要声明接口即可调用SQL是怎么做到的?JDK动态代理和CGLib代理区别?

mybatis是通过对dao接口进行代理,在启动mybatis的时候,会去加载在mybtais配置文件中配置的mapper文件,mapper文件中的每个sql语句都封装成一个statement,然后通过mapper文件中的namespace去得到到接口然后给他生成代理的工厂类放入到map中,注入mapper接口类时从该map中拿出对接口代理的对象,在代理对象中的invoke方法中除了object的方法,其他的所有方法都直接去找到相应的statment,然后去执行sql语句; Jdk动态代理的类必须要实现接口,并且不能是final修饰的类,方法不能是非public的方法,在生成代理类时快,只生成一个代理文件,生成的代理类回去实现目标类实现的接口,运行时通过反射调用目标类的方法,调用时慢,cglib是使用asm框架来生成代理类,目标类无须实现接口,生成的代理类会继承目标类,不能是final修饰的类,方法不能是非public,由于生成了几个类文件,所以生成时慢,之所以有几个类文件,因为cglib在生成代理类的同时,会为目标类的每个方法都生成一个相应的index,通过index直接定位到方法,直接调用,所以调用时快,在spring中会判断如果有接口就使用jdk代理,如果没有接口就使用cglib代理。

2、MyBatis 与Hibernate 有哪些不同?

1、Mybatis 和 hibernate 不同,它不完全是一个 ORM 框架,因为 MyBatis 需要程序员自己编写 Sql 语句。

2、Mybatis 直接编写原生态 sql, 可以严格控制 sql 执行性能, 灵活度高, 非常适合对关系数据模型要求不高的软件开发, 因为这类软件需求变化频繁, 一但需求变化要求迅速输出成果。但是灵活的前提是 mybatis 无法做到数据库无关性, 如果需要实现支持多种数据库的软件,则需要自定义多套 sql 映射文件,工作量大。

3、Hibernate 对象/关系映射能力强, 数据库无关性好, 对于关系模型要求高的软件, 如果用 hibernate 开发可以节省很多代码, 提高效率。

3、Mybaits的优点

1、基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。 2、与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接; 3、很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC 支持的数据库MyBatis都支持)。 4、能够与Spring很好的集成; 5、提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。

4、什么是Mybatis?

1、Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建 statement等繁杂的过程。程序员直接编写原生态sql,可以严格控制sql执行性能,灵活度高。 2、MyBatis可以使用XML或注解来配置和映射原生信息,将POJO映射成数据库中的记录,避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。 3、通过xml文件或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。(从执行sql到返回result的过程)。

四、Spring

1、Bean是如何创建的?

在spring中,万物都是bean对象,每一个对象都可以封装成BeanDefinition,然后去生成bean对象。 所以首先第一步,spring要找到哪些bean需要实例化,第一种是xml的方式,如果需要实例化bean就在xml中配置bean标签,找到所有需要创建的bean,第二种注解方式,扫描所有添加了spring注解的bean,把所有的bean封装成一个BeanDefinition放入一个list. 第二步,循环list,通过BeanDefinition中的类全名称,通过反射进行实例化,属性注入,如果还有一个初始化的动作,也可以在属性注入后做,比如:init-method方法,比如实现了InitializingBean这个接口,然后在初始化的时候自动调用afterPropertiesSet该方法,我们可以在这个里面对bean做其他的操作,如果bean需要被代理,则通过后置通知,去生成代理的bean,如果bean实现了接口就使用jdk代理,如果没有实现就使用cglib,如果配置的优先级,则优先使用cglib. 第三步,完成后就将bean放入到spring的一级容器中。

2、对spring ioc的理解和使用?

以前没有spring的时候,我们需要得到一个对象,都是自己主动去new一个对象,然后通过set方法给对象注入属性,但是这种动作其实是一个重复的动作,所以spring提供ioc的容器解决方案,在容器启动的时候就把许多需要实例化和属性注入的bean都提前做好并放入到一个map中存储起来。这就是控制反转,原来的控制全在用户,现在的控制权完全交给了容器,在bean实例化后,通过反射对属性进行依赖注入 有两种使用方式,一种是xml的方式,一种是注解的方式。 xml的加载方式,首先在spring的xml中通过bean标签配置我们需要注入的bean,当扫描到所有的bean后,首先把bean包装成BeanDefinition,放入到list中,然后循环这个list去创建bean,创建bean的步骤为实例化->属性注入->初始化->aop,然后最终放入到spring的一级缓存中保存起来。 注解方式是通过在类上面加上spring的注解去扫描,比如controller,service等,后续步骤和xml基本一样。

五、JVM

1、一个对象是多少字节?在堆区都存了什么东西?对象头里面都有啥?

对象在内存中的存储的布局可以分为三块区域:对象头(Header),实例数据(Instance Data)和对齐填充(Padding);对象头由 Markword + 类指针kclass(该指针指向该类型在方法区的元类型) 组成;普通对象头在32位系统上占用8bytes,64位系统上占用16bytes。64位机器上,数组对象的对象头占用24个字节,启用压缩之后占用16个字节。 MarkWord用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。在64位的虚拟机(未开启压缩指针)为64bit。 kclass存储的是该对象所属的类在方法区的地址,所以是一个指针,默认Jvm对指针进行了压缩,用4个字节存储,如果不压缩就是8个字节。 关于Compressed Oops的知识,大家可以自行查阅相关资料来加深理解。 实例数据部分就是成员变量的值,其中包含父类的成员变量和本类的成员变量。也就是说,除去静态变量和常量值放在方法区,非静态变量的值是随着对象存储在堆中的。 用于确保对象的总长度为8字节的整数倍。HotSpot要求对象的总长度必须是8字节的整数倍。由于对象头一定是8字节的整数倍,但实例数据部分的长度是任意的。因此需要对齐补充字段确保整个对象的总长度为8的整数倍。

2、CMS和G1讲讲流程

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。采用的是"标记-清除算法",整个过程分为4步; (1)初始标记,标记GC Roots能直接关联到的对象 Stop The World-- ->速度很快 (2)并发标记,就是从GC Roots开始找到它能引用的所有其它对象的过程 (3)重新标记,Stop The World 为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间要短。 (4)并发清除,在整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,因此,从总体上看,CMS收集器的内存回收过程是与用户线程一起并发执行的。 G1收集器的工作过程也可以分为四步: 初始标记、并发标记、最终标记跟CMS的前三步基本一致; 筛选回收,对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间制定回收计划

六、多线程

1、讲讲synchronized锁升级流程

其实在 JDK 1.6之前,synchronized 还是一个重量级锁,是一个效率比较低下的锁,但是在JDK 1.6后,Jvm为了提高锁的获取与释放效率对(synchronized )进行了优化,引入了 偏向锁 和 轻量级锁 ,从此以后锁的状态就有了四种(无锁、偏向锁、轻量级锁、重量级锁),并且四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级。 jdk1.6之前的synchronized实现方式是 “阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态切换需要耗费处理器时间,如果同步代码块中内容过于简单,这种切换的时间可能比用户代码执行的时间还长”,这也是在JDK6以前 synchronized效率低下的原因,JDK6中为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”。 当一个线程去抢占锁的时候,锁会升级成偏向锁,此时共享资源加锁和解锁不需要额外的消耗,和执行非同步方法相比仅存在纳秒级的差距; 当多个线程去抢占锁的时候,此时一个线程会抢占到锁(线程会在栈中开辟一段空间来存储对象头中的MarkWord数据,而对象头会指向线程)然后执行同步代码块,而其他线程此时并不会直接进入到阻塞状态,而是通过自旋的方式去不断尝试抢占锁,自旋竞争,会消耗cpu,但是比起后面的重量级锁性能还是快很多,毕竟不会阻塞线程; 线程自旋抢占锁的次数并不是无限的,因为自旋也会消耗CPU的性能,当自旋到一定次数后还没拿到锁的话,线程就会讲锁的状态修改为重量级锁,并且阻塞。

七、分布式微服务

1、kafka 分布式(不是单机)的情况下,如何保证消息的顺 序消费?

Kafka 分布式的单位是 partition,同一个 partition 用一个 write ahead log 组织,所以可 以保证 FIFO 的顺序。不同 partition 之间不能保证顺序。但是绝大多数用户都可以通过 message key 来定义, 因为同一个 key 的 message 可以保证只发送到同一个 partition。 Kafka 中发送 1 条消息的时候, 可以指定(topic, partition, key) 3 个参数。 partiton 和 key 是可选的。如果你指定了 partition,那就是所有消息发往同 1 个 partition,就是有序的。并且在消费端,Kafka 保证,1 个 partition 只 能 被 1 个 consumer 消费。或者你指定 key( 比如 order id),具有同 1 个 key 的 所有消息, 会发往同 1 个 partition。

2、SpringBoot启动过程

SpringBoot启动时通过执行main方法中的SpringApplication.run方法去启动的,在run方法中调用了SpringApplication的构造方法,在该构造方法中加载了META-INFA\spring.factories文件配置的ApplicationContextInitializer的实现类和ApplicationListenerr的实现类,ApplicationContextInitializer 这个类当springboot上下文Context初始化完成后会调用。ApplicationListener当springboot启动时事件change后都会触发。 SpringApplication实例构造完之后会调用它的run方法,在run方法中作了以下几步重要操作:

  1. 获取事件监听器SpringApplicationRunListener类型,并且执行starting()方法
  2. 准备环境,并且把环境跟spring上下文绑定好,并且执行environmentPrepared()方法
  3. 创建上下文,根据项目类型创建上下文
  4. 执行spring的启动流程扫描并且初始化单实列bean 同时通过@SpringBootApplication注解将ClassPath路径下所有的META-INF\spring.factories文件中的EnableAutoConfiguration实例注入到IOC容器中

八、数据库

1、请你说下对InnoDB索引数据结构的理解?

InnoDB索弓|的数据结构用的是B+树。为什么要用B+? InnoDB索引肯定会有1个聚集索引,聚集索引默认是主键,、然后是非空的唯一索引, 最后是隐藏列rowid。 聚集索引的存储方式为叶子节点有完整的数据,而非叶子节点,只存有索引值。 那么每页存的数据也就更多,内存跟磁盘交互的单位为页。每页的数据越多,那么就能减少跟磁盘的交互次 数,整体上提升速度。 同时,因为真实数据都在叶子节点,所以sq|语句的查询路径都是一样长。 查询稳定。 为什么不用二叉查找树?因为二叉树会有斜树的情况出现,会退化成链表,不够平衡。 为什么不用红黑树?同样的,路数比较少,深度会随着数据量的提升而提升。速度会越来越慢。同时也不够平 衡。 为什么不用B tree?B树跟B+最大的一个区别,B树每个节点都有真实数据,那么每页存的数据就越少,查询数 据跟磁盘的交互也就越多,同时,索弓|树的高度也就越高,查询的链路也会越长,整体查询会慢。还有每个节 点都有真实数据。查询数据就不稳定,有些在索引第-层就能查到,有些要查到 索引最后一层。

2、MylSAM和InnoDB引擎的区别

主要区别: MylSAM是非事务安全 型的,而InnoDB是 事务安全型的。 MylSAM锁的粒度是表级 ,而InnoDB支持行级锁定。 MylSAM支持全文类型索引,而InnoDB不支持全文索弓|。 MylSAM相对简单,所以在效率上要优于InnoDB ,小型应用可以考虑使用MyI5AM。 MylSAM表是保存成文件的形式,在跨平台的数据转移中使用My|5AM存储会省去不少的嘛烦。 0 InnoDB表比MyI5AM表更安全,可以在保证数据不会丢失的情况下,切换非事务表到事务表( alter table tablenametype=innodb )。 ●应用场景: MylSAM管理非事务表。它提供高連存储和检索,以及全文搜索能力。如果应用中需要执行大量的SELECT查询,那么MylSAM是更好的选择。 InnoDB用于事务处理应用程序,具有众多特性,包括ACID事务支持。如果应用中需要执行大量的INSERT或UPDATE操作,则应该使用InnoDB ,这样可以提高多用户并发操作的性能。

3、数据库索引的优缺点以及什么时候数据库索引失效

●索引的特点 (可以加快数据库的检索連度 (降低数据库插入、修改删除等维护的連度 (只能创建在表上,不能创建到视图上 ( 既可以直接创建又可以间接创建 (可以在优化隐藏中使用索弓| (使用查询处理器执行5QL语句 ,在一个表上,- -次只能使用一个索引 ●索引的优点 (创建唯一-性索引,保证数据库表中每一-行数据的唯- -性 ( 大大加快数据的检索連度,这是创建索引的最主要的原因 (加連数据库表之 间的连接,特别是在实现数据的参考完整性方面特别有意义 (在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间 (通过使用索引,可以在查询中使用优化隐藏器, 提高系统的性能 ●索引的缺点 (创建索引和维护索引|要耗费时间,这种时间随着数据量的增而增加) (索引需要占用物理空间,除了数据表占用数据空间之外,每-个索引还要占-定的物理空间,如果建立聚簇索引,那么需要的空间就会更大) (当对表中的数据进行增加口、 删除和修改的时候,索引也需要维护,降低数据维护的連度) ●索引分类 (直接创建索弓|和间接创建索引) (昔通索弓|和唯一-性索引) (单个索引和复合索弓|) ( 聚簇索弓|和非聚簇索引) ●索引失效 (如果条件中有or ,即使其中有条件带索引也不会使用(这就是问什么尽量少使用or的原因) (对于多列索引,不是使用的第一 部分,则不会使用索引 (like查询是以 %开头) (如果列类型是字符串 , 那一定要在条件中使用引号弓起来,否则不会使用索弓|) (如果mysq|估计使用全表扫秒比使用索弓|快,则不适用索引)。

九、业务技术方案

1、限流策略有哪些,滑动窗口算法和令牌桶区别,使用场景

限流算法常用的几种实现方式有如下四种:计数器、滑动窗口、漏桶和令牌桶; ● 计数器: ○ 思想:在固定时间窗口内对请求进行计数,与阀值进行比较判断是否需要限流,一旦到了时间临界点,将计数器清零。 ○ 问题:计数器算法存在“时间临界点”缺陷。比如每一分钟限制100个请求,可以在00:00:00-00:00:58秒里面都没有请求,在00:00:59瞬间发送100个请求,这个对于计数器算法来是允许的,然后在00:01:00再次发送100个请求,意味着在短短1s内发送了200个请求,如果量更大呢,系统可能会承受不住瞬间流量,导致系统崩溃 ● 滑动窗口: ○ 思想:滑动窗口算法将一个大的时间窗口分成多个小窗口,每次大窗口向后滑动一个小窗口,并保证大的窗口内流量不会超出最大值,这种实现比固定窗口的流量曲线更加平滑。 ○ 问题:没有根本解决固定窗口算法的临界突发流量问题 ● 漏桶: ○ 思想:漏桶算法是首先想象有一个木桶,桶的容量是固定的。当有请求到来时先放到木桶中,处理请求的worker以固定的速度从木桶中取出请求进行相应。如果木桶已经满了,直接返回请求频率超限的错误码或者页面 ○ 适用场景:漏桶算法是流量最均匀的限流实现方式,一般用于流量“整形”。例如保护数据库的限流,先把对数据库的访问加入到木桶中,worker再以db能够承受的qps从木桶中取出请求,去访问数据库。 ○ 问题:木桶流入请求的速率是不固定的,但是流出的速率是恒定的。这样的话能保护系统资源不被打满,但是面对突发流量时会有大量请求失败,不适合电商抢购和微博出现热点事件等场景的限流。 ● 令牌桶: ○ 思想:令牌桶是反向的"漏桶",它是以恒定的速度往木桶里加入令牌,木桶满了则不再加入令牌。服务收到请求时尝试从木桶中取出一个令牌,如果能够得到令牌则继续执行后续的业务逻辑。如果没有得到令牌,直接返回访问频率超限的错误码或页面等,不继续执行后续的业务逻辑。 ○ 适用场景:适合电商抢购或者微博出现热点事件这种场景,因为在限流的同时可以应对一定的突发流量。如果采用漏桶那样的均匀速度处理请求的算法,在发生热点时间的时候,会造成大量的用户无法访问,对用户体验的损害比较大。