题目整理【一】

Spring AOP底层原理

aop底层是采用动态代理机制实现的:接口+实现类

  • 如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象。

  • 没有实现接口的对象,就无法使用JDK Proxy去进行代理了,这时候Spring AOP会使用Cglib生成一个被代理对象的子类来作为代理。

就是由代理创建出一个和impl实现类平级的一个对象,但是这个对象不是一个真正的对象,只是一个代理对象,但它可以实现和impl相同的功能,这个就是aop的横向机制原理,这样就不需要修改源代码。

HashMap的底层数据结构是怎样的?

JDK1.8之前

  • JDK1.8之前HashMap底层是数组和链表结合在一起使用也就是链表散列

  • HashMap通过key的hashCode经过扰动函数处理过后得到hash值,然后通过(n-1)&hash判断当前元素存放的位置(这里的n指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的hash值以及key是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。

  • 所谓扰动函数指的就是HashMap的hash方法。使用hash方法也就是扰动函数是为了防止一些实现比较差hashCode()方法,换句话说使用扰动函数之后可以减少碰撞。

JDK1.8之后

当链表长度大于阈值(默认为8)时,会首先调用treeifyBin()方法。这个方法会根据HashMap数组来决定是否转换为红黑树。只有当数组长度大于或者等于64的情况下,才会执行转换红黑树操作,以减少搜索时间。否则,就是只是执行resize()方法对数组扩容。

HashMap的扩容机制是怎样的?

一般情况下,当元素数量超过阈值时便会触发扩容。每次扩容的容量都是之前容量的2倍。

HashMap的容量是有上线的,必须小于1<<30,即1073741824。如果容量超出了这个数,则不再增长,且阈值会被设置为Integer.MAX_VALUE。

JDK7中的扩容机制

  • 空参数的构造函数:以默认容量、默认负载因子、默认阈值初始化数组。内部数组是空数组。

  • 有参数构造函数:根据参数确定容量、负载因子、阈值等。

  • 第一次put时会初始化数组,其容量变为不小于指定容量的2的幂数,然后根据负载因子确定阈值。

  • 如果不是第一次扩容,则新容量=旧容量*2,新阈值=新容量*负载因子。

JDK8的扩容机制

  • 空参数的构造函数:实例化的HasMap默认内部数组是null,即没有实例化。第一次调用put方法时,则会开始第一次初始化扩容,长度16.

  • 有参构造函数:用于指定容量。会根据指定的正整数找到不小于指定容量的2的幂数,将这个数设置赋值给阈值(threshold)。第一次调用put方法时,会将阈值赋值给容量,然后让阈值=容量*负载因子。

  • 如果不是第一次扩容,则容量变为原来的2倍,阈值也变为原来的2倍。(容量和阈值都变为原来的2倍时,负载因子还是不变)。

此外还有几个细节需要注意:

  • 首次put时,先会触发扩容(算是初始化),然后存入数据,然后判断是否需要扩容;

  • 不是首次put,则不再初始化,直接存入数据,然后判断是否需要扩容;

ConcurrentHashMap的存储结构是怎样的?

  • Java7中的ConcurrentHashMap使用的分段锁,也就是每一个Segment上同时只有一个线程可以操作,每一个Segment都是一个类似HashMap数组的结构,它可以扩容,它的冲突会转化为链表。但是Segment的个数一但初始化就不能改变,默认Segment的个数是16个。

  • Java8中的ConcurrentHashMap使用的Synchronized锁加CAS的机制。结构也由Java7中的Segment数组+HashEntry数组+链表进化成了Node数组+链表/红黑树,Node是类似于一个HashEntry的结构。它的冲突再达到一定大小时会转换成红黑树,在冲突小于一定数量时又退回链表。

线程池大小如何设置?

  • CPU密集型任务(N+1):这种任务消耗的主要是CPU资源,可以将线程数设置为N(CPU核心数)+1,比CPU核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其他原因导致的任务暂停而带来的影响。一旦任务暂停,CPU就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用CPU的空闲时间。

  • I/O密集型任务(2N):这种任务应用起来,系统会用大部分的时间处理I/O交互,而线程在处理I/O的时间段内不会占用CPU来处理,这时就可以将CPU交出给其他线程使用。因此I/O密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是2N。

如何判断是CPU密集任务还是IO密集任务?

CPU密集型简单理解就是利用CPU计算能力的任务比如你在内存中对大量数据进行排序。但凡涉及到网络读取,文件读取这类都是IO密集型,这类任务的特点是CPU计算耗费时间相比于等待IO操作完成的时间来说很少,大部分时间都花在了等待IO操作完成上。

IO密集=Ncpu*2是怎么计算出来的?

I/O密集型任务应用起来,系统会用大部分的时间来处理I/O交互而线程在处理I/O的时间段内不会占用CPU来处理,这时就可以将CPU交出给其他线程使用。因此在I/O密集型任务的应用中,我们可以多配置一些线程。例如:数据库交互,文件上传下载,网络传输等。IO密集型,即该任务需要大量的IO,即大量的阻塞,故需要多配置线程数。

G1收集器有哪些特点?

  • G1的全称是Garbage-First,意为垃圾优先,哪一块的垃圾最多就优先清理它。

  • G1 GC最主要的设计目标:将STW停顿的时间和分布,变成可预期且可配置的。

  • 被视为JDK1.7中HotSpot虚拟机的一个重要进化特征。它具备以下特点:

  • 并行与并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。

  • 分代收集:虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。

  • 空间整合:与CMS的"标记-清理"算法不同,G1从整体来看是基于"标记-清理"算法实现的收集器;从局部上来看是基于"标记-复制"算法实现的。

  • 可预测的停顿:这是G1相对于CMS的另一个大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内。

G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的Region(这也就是它的名字Garbage-First的由来)。

你有哪些手段来排查OOM的问题?

  • 增加两个参数-XX:HeapDumpOnOutOfMemoryError  -XX:HeapDumpPath=/tmp/heapdump.hprof,当OOM发生时自动dump堆内存信息到指定目录

  • 同时jstat查看监控JVM的内存和GC情况,先观察问题大概出在什么区域。

  • 使用MAT工具载入到dump文件,分析大对象的占用情况,比如HashMap做缓存未清理,时间长了就会内存溢出,可以把改为弱引用。

请你谈谈Mysql事务隔离级别,Mysql默认隔离级别是什么?

为了达到事务的四大特性,数据库定义了4种不同的事务隔离级别:

  • READ-UNCOMMITTED(读取未提交):最低的隔离级别,允许脏读,也就是可能读取到其他会话中

    未提交事务修改的数据,可能会导致脏读、幻读或不可重复读。

  • READ-COMMITTED(读取已提交):只能读取到已经提交的数据。Oracle等多数据库默认都是该级别(不重复读),可以防止脏读,但是幻读或不可重复读仍有可能发生。

  • REPEATABLE-READ(可重复读) :对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。

  • SERIALIZABLE(可串行化):最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。

  • Mysql默认采用的REPEATABLE_READ隔离级别。

可重复读解决了哪些问题?

  • 可重复读的核心就是一致性读(consistent read);保证多次读取同一个数据时,其值都和事务开始时候的内容一致,禁止读取到别的事务未提交的数据,会造成幻读.

  • 而事务更新数据的时候,只能用当前读。如果当前的记录的行锁被其他事务占用的话,就需要进入锁等待。

  • 查询只承认在事务启动前就已经提交完成的数据。

  • 可重复读解决的是重复读的问题,可重复读在快照读的情况下是不会有幻读,但当前读的时候会有幻读。

对SQL慢查询会考虑哪些优化?

  • 分析语句,是否加载了不必要的字段/数据。

  • 分析SQL执行计划(explain extended) ,思考可能的优化点,是否命中索引等。

  • 查看SQL涉及的表结构和索引信息。

  • 如果SQL很复杂,优化SQL结构。

  • 按照可能的优化点执行表结构变更、增加索引、SQL改写等操作。

  • 查看优化后的执行时间和执行计划。

  • 如果表数据量太大,考虑分表。

  • 利用缓存,减少查询次数。

谈一谈缓存穿透、缓存击穿、和缓存雪崩,以及解决办法?

缓存穿透

  • 问题:大量并发查询不存在的KEY,在缓存和数据库中都不存在,同时给缓存和数据库带来压力。

  • 原因:一般而言,缓存穿透有两种可能性:业务数据被误删,导致缓存和数据库中都没有数据。恶意进行ddos攻击。

  • 分析:为什么会多次透传呢?不存在一直为空,需要注意让缓存能够区分KEY不存在和查询到一个空值。

  • 解决办法:缓存空值的KEY,这样第一次不存在也会被加载会记录,下次拿到有这个KEY。Bloom过滤或RoaingBitmap判断KEY是否存在,如果布隆过滤器中没有查到这个数据,就不去数据库中查。在处理请求前增加恶意请求检查,如果监测到是恶意攻击,则拒绝进行服务。完全以缓存为准,使用延迟异步加载的策略(异步线程负责维护缓存的数据,定期或根据条件触发更新),这样就不会触发更新。

缓存击穿

  • 问题 :某个KEY失效的时候,正好有大量并发请求访问这个KEY。

  • 分析:跟穿透其实很像,属于比较偶然的。

  • 解决办法:KEY的更新操作添加全局互斥锁。完全以缓存为准,使用延迟异步加载的策略(异步线程负责维护缓存的数据,定期或根据条件触发更新),这样就不会触发更新。

缓存雪崩

  • 问题:当某一时刻发生大规模的缓存失效的情况,导致大量的请求无法获取数据,从而将流量压力传导到数据库上,导致数据库压力过大甚至宕机。

  • 原因:一般而言,缓存雪崩有两种可能性:大量的数据同一个时间失效;比如业务关系强相关的数据要求要求同时失效Redis宕机。

  • 分析:一般来说,由于更新策略、数据热点、缓存服务宕机等原因,可能会导致缓存数据同一个时间点大规模不可用,或者都更新。所以,需要我们的更新策略要在时间上合适,数据要均匀分享,缓存服务器要多台高可用。

  • 解决办法:更新策略在时间上做到比较平均。如果数据需要同一时间失效,可以给这批数据加上一些随机值,使得这批数据不要在同一个时间过期,降低数据库的压力。使用的热数据库尽量分散到不同的机器上。多台机器做主从复制或者多副本,实现高可用。做好主从的部署,当主节点挂掉后,能快速的使用从节点顶上。实现熔断限流机制,对系统进行负载能力控制。对于非核心功能的业务,拒绝其请求,只允许核心功能业务访问数据库获取数据。服务降级:提供默认返回值,或简单的提示信息。

LRU是什么?如何实现?

最近最少使用策略LRU(Least Recently Used)是一种缓存淘汰算法,

  • 使用双向链表实现的队列,队列的最大容量为缓存的大小。在使用过程中,把最近使用的页面移动到队列头,最近没有使用的页面将被放在队列尾的位置。

  • 使用一个哈希表,把页号作为键,把缓存在队列中的节点的地址作为值,只需要把这个页对应的节点移动到队列的前面,如果需要的页面在内存中,此时需要把这个页面加载到内存中,简单的说,就是将一个新节点添加到队列前面,并在哈希表中更新相应的节点地址,如果队列是满的,那么就从队列尾移除一个节点,并将新节点添加到队列的前面。

什么是堆内存?参数如何设置?

堆内存是指由程序代码自由分配的内存,与栈内存作区分。

在Java中,堆内存主要用于分配对象的存储空间,只要拿到对象引用,所有线程都可以访问堆内存。-

  • -Xmx,指定最大内存。如-Xmx4g.这只是限制了Heap部分的最大值为4g。这个内存不包括栈内存,也不包括堆外使用的内存。

  • -Xms,指定堆内存空间的初始大小,如-Xms4g。而且指定的内存大小,并不是操作系统实际分配的初始值,而是GC先规划好,用到才分配。专用服务器上需要保持-Xms和-Xmx一致,否则应用刚启动可能就有好几个FullGC。当两者配置不一致时,堆内存扩容可能会导致性能抖动。

  • -Xmn,等价于-XX:NewSize,使用G1垃圾收集器不应该设置该选项,在其它的某些业务场景下可以设置,官方建议为-Xmx的1/2~1/4。

  • -XX:MaxPermSize=size,这是JDK1.7之前使用的。Java8默认允许的Meta空间无限大,此参数无效。

  • -XX:MaxMetaspaceSize=size,Java8默认不限制Meta空间,一般不允许设置该选项。

  • -XX:MaxDirectMemorySize=size,系统可以使用的最大堆外内存,这个参数跟-Dsun.nio.MaxDirectMemorySize效果相同。

  • -Xss,设置每个线程的字节数。例如-Xss1m指定线程栈为1MB,与-XX:ThreadStatckSize=1m等价

栈和队列,举个使用场景例子?

  • 栈(后进先出)可以用于字符匹配,数据反转等场景。

  • 队列(先进先出)可以用于任务队列,共享打印机等场景。

MySQL为什么InnoDB是默认引擎?

聚焦索引(聚簇索引)是指数据库表行中数据的物理顺序与键值的逻辑(索引)顺序相同。一个表只能有一个聚簇索引,因为一个表的物理顺序只有一种情况,所以,对应的聚簇索引只能有一个,聚簇索引的叶子节点就是数据节点,既存储索引值,又在叶子节点存储行数据。

Innodb创建表后生成的文件有:

frm:创建表的语句

idb:表里面的数据+索引文件

MySQL索引底层结构为什么使用B+树?

  • 哈希虽然能够提供O(1)的单数据行操作性能,但是对于范围查询和排序却无法很好的支持,最终导致全部扫描;B树能够在非叶子节点中存储数据,但是这也导致在查询连续数据时可能会带来更多的随机I/O,而B+树的所有叶子节点可以通过指针相互连接,能够减少顺序遍历时产生的额外随机I/O。

  • 第一,B树一个节点里存的是数据,而B+树存储是所有(地址),所以B树里一个节点存不了很多个数据,但是B+树一个节点能存很多索引,B+树叶子节点存所有的数据。

  • 第二,B+树的叶子节点是数据阶段用了一个链表串联起来,便于范围查找。

B+树的叶子节点链表是单向还是双向?

双向链表

MVCC是什么?它的底层原理是什么?

MVCC,多版本并发控制,它是通过读取历史版本的数据,来降低并发事务冲突,从而提高并发性能的一种机制。

  • 事务版本号

  • 表的隐藏列

  • undo log

  • read view

详细参考:https://blog.csdn.net/qq_33512765/article/details/127886617

undo log具体怎么回归事务?

举个例子:

  • 对于insert类型的sql,会在undo log中记录下方才你insert进来的数据ID,当你想roll back时,根据ID完成精准的删除。

  • 对于delete类型的sql,会在undo log中记录方才你删除的数据,当你回滚时会将删除前的数据insert进度。

  • 对于update类型的sql,会在undo log中记录下修改前的数据,回滚时只需要反向update即可。

  • 对于select类型的sql,别费心了,select不需要回滚。

如何查询慢SQL产生的原因

  • 分析SQL执行计划(explain extended),思考可能的优化点,是否命中索引等。

  • 没有索引或着没有用到索引(这是查询慢最常见的问题,是程序设计的缺陷)。

  • 内存不足。

  • 网络速度慢。

  • 是否查询出的数据量过大(可以采用多次查询,其它的方法降低数据量)。

  • 是否返回了不必要的行和列。

  • 锁或者死锁。

  • I/O吞吐量小,形成了瓶颈效应。

  • sp_lock,sp_who活动的用户查看,原因是读写竞争资源。

索引失效的情况有哪些?

  • like以%开头索引无效,当like以%结尾,索引有效。

  • or语句前后没有同时使用索引,当且仅当or语句查询条件的前后列均为索引时,索引生效。

  • 组合索引,使用的不是第一列索引时候,索引失效,即最左匹配规则。

  • 数据类型出现隐式转换,如varchar不加单引号的时候可能会自动转换为int类型,这个时候索引失效。

  • 在索引列上使用IS NULL或者IS NOT NULL时候,索引失效,因为索引是不索引空值的。

  • 在索引字段上使用,NOT,<>、!=时候是不会使用索引的,对于这样的处理只会进行全表扫描。

  • 对索引字段进行计算操作,函数操作时不会使用索引。

  • 当全表扫描速度比索引速度快的时候不会使用索引。

一个Redis实例最多能存放多少的keys?List、Set、Sorted Set他们最多能存多少元素?

理论上Redis可以处理多达2的32次方的keys,并且在实际中进行了测试,每个实例至少存放了2亿5千万的keys。我们正在测试一些较大的值。任何list、set、和sorted set都可以放2的32次方个元素。换句话说,Redis的存储极限是系统中的可用内存值。

Redis数据结构 压缩列表和跳跃表的区别

  • 压缩列表(ziplist)本质上就是一个字节数组,是Redis为了节约内存而设计的一种线性数据结构,可以包含多个元素,每个元素可以是一个字节数组或一个整数。

  • 跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。跳跃表支持平均O(logN)最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点。

为什么数据量小的时候用压缩列表?

为了省内存。

Redis主从同步是怎么实现的?

全量同步

master服务器会开启一个后台进程用于将redis中的数据生成一个rdb文件,与此同时,服务器会缓存所有接收到来自客户端的写命令(包含增、删、改),当后台保存进程处理完毕后,会将该rdb文件传递给slave服务器,而slave服务器会将rdb文件保存在磁盘并通过读取该文件将数据加载到内存,在此之后master服务器会将在此期间缓存的命令通过redis传输协议发送给slave服务器,然后slave服务器将这些命令依次作用于自己本地的数据集上最终达到数据的一致性。

增量同步

从redis 2.8版本以前,并不支持部分同步,当主从服务器之间的连接断掉之后,master服务器和slave服务器之间都是进行全量数据同步。

从redis 2.8开始,即使主从连接中途断掉,也不需要进行全量同步,因为从这个版本开始融入了部分同步的概念。部分同步的实现依赖于在master服务器内存中给每个slave服务器维护了一份同步日志和同步标识,每个slave服务器在跟master服务器进行同步时都会携带自己的同步标识和上次同步的最后位置。当主从连接断掉之后,slave服务器隔断时间(默认1s)主动尝试和master服务器进行连接,如果从服务器携带的偏移量标识还在master服务器上的同步备份日志中,那么就从slave发送的偏移量开始继续上次的同步操作,如果slave发送的偏移量已经不再master的同步备份日志中(可能由于主从之间断掉的时间比较长或者在断掉的短暂时间内master服务器接收到大量的写操作),则必须进行一次全量更新。在部分同步过程中,master会将本地记录的同步备份日志中记录的指令依次发送给slave服务器从而达到数据一致。

Redis主从同步策略

主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave在任何时候都可以起全量同步。redis策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。

Redis持久化 RDB和AOF优缺点

RDB

RDB持久化方式,是将Redis某一时刻的数据持久化到磁盘中,是一种快照方式的持久化方法。

RDB优点:

  • RDB是一种非常紧凑(有压缩)的文件,它保存了某个时间点的数据,非常适用于数据的备份。

  • RDB作为一个非常紧凑(有压缩)的文件,可以很方便传送到另一个远端数据中心,非常适用于灾难恢复。

  • RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能。

  • 与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些。

RDB缺点:

  • Redis意外宕机时,会丢失部分数据。

  • 当Redis数据量比较大时,fork的过程是非常耗时的,fork子进程时是会阻塞的,在这期间Redis是不能响应客户端的请求的。

AOF

AOF方式是将执行过的写指令记录下来,在数据恢复时按照从前到后的顺序再将指令都执行一遍。

AOF优点:

  • 使用AOF会让你的Redis更加持久化。

  • AOF文件是一个只进行追加的日志文件,不需要在写入时读取文件。

  • Redis可以在AOF文件体积变得过大时,自动地在后台对AOF进行重写。

  • AOF文件可读性高,分析容易。

AOF缺点:

  • 对于相同的数据来说,AOF文件大小通常要大于RDB文件。

  • 根据所使用的fsync策略,AOF的速度可能会慢于RDB。

谈谈自己对于Spring AOP的了解?

AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性。

Spring Bean容器的生命周期是什么样的?

  • Bean容器找到配置文件中Spring Bean的定义。

  • Bean容器利用Java Reflection API创建一个Bean的实例。

  • 如果涉及到一些属性值 利用set()方法设置一些属性值。

  • 如果Bean实现了BeanNameAware接口,调用setBeanName()方法,传入Bean的名字。

  • 如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。

  • 如果Bean实现了BeanFactoryAware接口,调用setBeanFactory()方法,传入BeanFactory对象的实例。

  • 与上面的类似,如果实现了其他*Aware接口,就调用相应的方法。

  • 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessBeforeInitialization()方法。

  • 当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destory()方法。

  • 当要销毁Bean的时候,如果Bean在配置文件中定义包含destroy-method属性,执行指定的方法。

RabbitMQ如何保证消息不丢失?

生产者:

方案1:开启RabbitMQ事务(同步、性能差)

方案2:开启confirm模式(异步、性能较好)

MQ:(1)exchange持久化(2)queue持久化(3)消息持久化

消费者:关闭自动ACK

备注:以上题目来源网络,如若有问题请联系修改或删除

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值