大厂面试系列一些问题的答案
知识点总结
-
InnoDB的行锁
(1)共享锁(S):用法lock in share mode,又称读锁,允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。 (2)排他锁(X):用法for update,又称写锁,允许获取排他锁的事务更新数据,阻止其他事务取得相同的数据集共享读锁和排他写锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。在没有索引的情况下,InnoDB只能使用表锁
-
Spring的事务传播级别
(1)REQUIRED(默认):支持使用当前事务,如果当前事务不存在,创建一个新事务。 (2)SUPPORTS:支持使用当前事务,如果当前事务不存在,则不使用事务。 (3)MANDATORY:强制,支持使用当前事务,如果当前事务不存在,则抛出Exception。 (4)REQUIRES_NEW:创建一个新事务,如果当前事务存在,把当前事务挂起。 (5)NOT_SUPPORTED:无事务执行,如果当前事务存在,把当前事务挂起。 (6)NEVER:无事务执行,如果当前有事务则抛出Exception。 (7)NESTED:嵌套事务,如果当前事务存在,那么在嵌套的事务中执行。如果当前事务不存在,则表现跟REQUIRED一样。
-
Redis与Mysql双写一致性方案
先更新数据库,再删缓存。数据库的读操作的速度远快于写操作的,所以脏数据很难出现。可以对异步延时删除策略,保证读请求完成以后,再进行删除操作
- 索引B+树的叶子节点都可以存哪些东西?
可能存储的是整行数据,也有可能是主键的值。B+树的叶子节点存储了整行数据的是主键索引,也被称之为聚簇索引。而索引B+ Tree的叶子节点存储了主键的值的是非主键索引,也被称之为非聚簇索引
- 分代回收
(1)HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。 (2)因为年轻代中的对象基本都是朝生夕死的,所以在年轻代的垃圾回收算法使用的是复制算法,复制算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片。 (3)在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。
- 线程之间如何通信?
(1)利用最基本的synchronized (2)利用synchronized、notify、wait (3)while轮询的方式 (4)利用Lock和Condition (5)利用volatile (6)利用AtomicInteger (7)利用CyclicBarrier (8)利用PipedInputStream (9)利用BlockingQueue
- redis并发竞争key的解决方案
(1)分布式锁+时间戳 (2)利用消息队列
- 如何避免(预防)死锁?
破坏“请求和保持”条件:让进程在申请资源时,一次性申请所有需要用到的资源,不要一次一次来申请,当申请的资源有一些没空,那就让线程等待。不过这个方法比较浪费资源,进程可能经常处于饥饿状态。还有一种方法是,要求进程在申请资源前,要释放自己拥有的资源。 破坏“不可抢占”条件:允许进程进行抢占,方法一:如果去抢资源,被拒绝,就释放自己的资源。方法二:操作系统允许抢,只要你优先级大,可以抢到。 破坏“循环等待”条件:将系统中的所有资源统一编号,进程可在任何时刻提出资源申请,但所有申请必须按照资源的编号顺序提出(指定获取锁的顺序,顺序加锁)
- 高并发系统的设计与实现
在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流。 (1)缓存:缓存比较好理解,在大型高并发系统中,如果没有缓存数据库将分分钟被爆,系统也会瞬间瘫痪。使用缓存不单单能够提升系统访问速度、提高并发访问量,也是保护数据库、保护系统的有效方式。大型网站一般主要是“读”,缓存的使用很容易被想到。在大型“写”系统中,缓存也常常扮演者非常重要的角色。比如累积一些数据批量写入,内存里面的缓存队列(生产消费),以及HBase写数据的机制等等也都是通过缓存提升系统的吞吐量或者实现系统的保护措施。甚至消息中间件,你也可以认为是一种分布式的数据缓存。 (2)降级:服务降级是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。降级往往会指定不同的级别,面临不同的异常等级执行不同的处理。根据服务方式:可以拒接服务,可以延迟服务,也有时候可以随机服务。根据服务范围:可以砍掉某个功能,也可以砍掉某些模块。总之服务降级需要根据不同的业务需求采用不同的降级策略。主要的目的就是服务虽然有损但是总比没有好。 (3)限流:限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说系统的吞吐量是可以被测算的,为了保证系统的稳定运行,一旦达到的需要限制的阈值,就需要限制流量并采取一些措施以完成限制流量的目的。比如:延迟处理,拒绝处理,或者部分拒绝处理等等。
- 高并发系统的限流如何实现?
常见的限流算法有计数器、漏桶和令牌桶算法。漏桶算法在分布式环境中消息中间件或者Redis都是可选的方案。发放令牌的频率增加可以提升整体数据处理的速度,而通过每次获取令牌的个数增加或者放慢令牌的发放速度和降低整体数据处理速度。而漏桶不行,因为它的流出速率是固定的,程序处理速度也是固定的。
-
JVM有哪些回收算法
1)引用计数法 (2)复制算法 (3)标记-清除算法 (4)标记-整理算法 (5)分代收集算法
-
垃圾收集器有哪些?
(1)Serial收集器 (2)ParNew 收集器 (3)Parallel Scavenge 收集器 (4)Serial Old收集器 (5)Parallel Old收集器 (6)CMS收集器 (7)G1收集器 (8)ZGC
- 常见的分布式事务方案有哪些?
(1)两阶段提交方案 (2)eBay 事件队列方案 (3)TCC 补偿模式 (4)缓存数据最终一致性
- 运行时数据区域(内存模型)
(1)程序计数器:程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。是线程私有”的内存。 (2)Java虚拟机栈:与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧 ,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。 (3)本地方法栈:本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。 (4)Java堆:对于大多数应用来说,Java堆是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。 (5)方法区(1.8叫元数据):方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- CAS操作ABA问题
如果在这段期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。Java并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性
- 为什么选择B+树作为索引结构?
(1)Hash索引:Hash索引底层是哈希表,哈希表是一种以key-value存储数据的结构,所以多个数据在存储关系上是完全没有任何顺序关系的,所以,对于区间查询是无法直接通过索引查询的,就需要全表扫描。所以,哈希索引只适用于等值查询的场景。而B+ 树是一种多路平衡查询树,所以他的节点是天然有序的(左子节点小于父节点、父节点小于右子节点),所以对于范围查询的时候不需要做全表扫描 (2)二叉查找树:解决了排序的基本问题,但是由于无法保证平衡,可能退化为链表。 (3)平衡二叉树:通过旋转解决了平衡的问题,但是旋转操作效率太低。 (4)红黑树:通过舍弃严格的平衡和引入红黑节点,解决了 AVL旋转效率过低的问题,但是在磁盘等场景下,树仍然太高,IO次数太多。 (5)B+树:在B树的基础上,将非叶节点改造为不存储数据纯索引节点,进一步降低了树的高度;此外将叶节点使用指针连接成链表,范围查询更加高效。
- 脏读和幻读是什么?
(1)脏读是指当一个事务正在访问数据,并且对数据进行了修改。而这种修改还没有提交到数据库中,这时,另外一个事务也访问了这个数据,然后使用了这个数据。 (2)幻读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到了表中的全部数据行。同时,第二个事务也修改了这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好像发生了幻觉一样。
- 如何解决Redis缓存雪崩,缓存穿透问题?
缓存雪崩: (1)使用 Redis 高可用架构:使用 Redis 集群来保证 Redis 服务不会挂掉 (2)缓存时间不一致,给缓存的失效时间,加上一个随机值,避免集体失效 (3)限流降级策略:有一定的备案,比如个性推荐服务不可用了,换成热点数据推荐服务 缓存穿透: (1)在接口做校验 (2)存null值(缓存击穿加锁) (3)布隆过滤器拦截: 将所有可能的查询key 先映射到布隆过滤器中,查询时先判断key是否存在布隆过滤器中,存在才继续向下执行,如果不存在,则直接返回。 布隆过滤器将值进行多次哈希bit存储,布隆过滤器说某个元素在,可能会被误判。布隆过滤器说某个元素不在,那么一定不在。
- Redis的持久化机制
redis为了保证效率,数据缓存在了内存中,但是会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件中,以保证数据的持久化。 Redis的持久化策略有两种: (1)RDB:快照形式是直接把内存中的数据保存到一个dump的文件中,定时保存,保存策略。 当Redis需要做持久化时,Redis会fork一个子进程,子进程将数据写到磁盘上一个临时RDB文件中。当子进程完成写临时文件后,将原来的RDB替换掉。 (2)AOF:把所有的对Redis的服务器进行修改的命令都存到一个文件里,命令的集合。 使用AOF做持久化,每一个写命令都通过write函数追加到appendonly.aof中。aof的默认策略是每秒钟fsync一次,在这种配置下,就算发生故障停机,也最多丢失一秒钟的数据。 缺点是对于相同的数据集来说,AOF的文件体积通常要大于RDB文件的体积。根据所使用的fsync策略,AOF的速度可能会慢于RDB。 Redis默认是快照RDB的持久化方式。
- 三次握手
(1)第一次握手:建立连接时,客户端发送syn包(syn=x)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。 (2)第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态; (3)第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
- Kafka消息是采用Pull模式,还是Push模式?
Kafka最初考虑的问题是,customer应该从brokes拉取消息还是brokers将消息推送到consumer,也就是pull还push。在这方面,Kafka遵循了一种大部分消息系统共同的传统的设计:producer将消息推送到broker,consumer从broker拉取消息。push模式下,当broker推送的速率远大于consumer消费的速率时,consumer恐怕就要崩溃了。最终Kafka还是选取了传统的pull模式。Pull模式的另外一个好处是consumer可以自主决定是否批量的从broker拉取数据。Pull有个缺点是,如果broker没有可供消费的消息,将导致consumer不断在循环中轮询,直到新消息到达。为了避免这点,Kafka有个参数可以让consumer阻塞知道新消息到达。
- HashMap相关
(1)在jdk1.8之后,HashMap除了数组+链表之外,引用了红黑树。需要说明对于引用了红黑树的 HashMap 如何put一个元素,以及链表是在何时转化为红黑树的。比如,首先需要知道这个元素落在哪一个数组里,获取hashcode后并不是对数组长度取余来确定的,而是高低位异或求与来得到的。这个地方首先得知道异或求与是做什么样的运算的。 (2)之后说一下在HashMap中的实现,比如hashcode无符号右移16位后和原hashcode做异或运算,这相当于把hashcode的高16位拿过来和hashcode的低16位做异或运算,因为无符号右移后前面说的16位都补零,这就是前面说的 "高低位异或“,进而是“求与”,和谁求与呢,和数组长度减1 求与。说到这里起码能够证明你是看过源码的,接下来说说你的思考,比如我们知道对于hashmap 初始化容量决定了数组大小,一般我们对于数组这个初始容量的设置是有规律的,它应该是 2^n 。这个初始容量的设置影响了HashMap的效率,那又涉及到影响HashMap效率的主要因素,比如初始容量和负载因子。
- 手写一个基于懒汉式的双重检测的单例。
(1)单例有三个比较关键的点,一是私有构造方法,避免外部new出对象;二是保证唯一性;三是提供一个全局访问点。 (2)另外,懒汉式双重检测的实现方式 有三点需要注意的地方,一是全局访问点必须是静态的,外界使用可以通过类直接调用,二是在进入锁之后还需要校验,三是保存单例对象的私有变量一定要用volatile修饰,这个地方可以多说一些,比如volatile防止指令重排序,保证内存可见性(JVM层面和CPU层面可以分别说)。
- RPC原理
(1)为什么会出现RPC? RPC(Remote Procedure Call Protocol)——远程过程调用协议。 一般来说,自己写程序然后本地调用,这种程序的特点是服务的消费方和提供方。当我们进入公司时,面对的很可能就是成千上万的服务提供方,这时候就需要使用RPC来进行远程服务调用。RPC将原来的本地调用转变为调用远端的服务器上的方法,给系统的处理能力和吞吐量带来了近似于无限制提升的可能。 (2)RPC的组成 ①客户端:服务的调用方 ②客户端存根:存放服务端的地址消息,再将客户端的请求参数打包成网络消息,③然后通过网络远程发送给服务方。 ④服务端:真正的服务提供者。 ⑤服务端存根:接收客户端发送过来的消息,将消息解包,并调用本地的方法。
- Redis缓存回收机制
(1)数据过期: ①定时删除策略:Redis启动一个定时器监控所有的key,一旦有过期的话就进行删除(遍历所有key,非常耗费CPU) ②惰性删除策略:获取key的时候判断是否过期, 过期则进行删除 Redis采用的方式:①(随机抓取一部分key进行检测)+② (2)内存淘汰: ①noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。(Redis 默认策略) ②allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 Key。(LRU推荐使用) ③allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 Key。 ④volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 Key。这种情况一般是把 Redis 既当缓存,又做持久化存储的时候才用。 ⑤volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 Key。 ⑥volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 Key 优先移除。不推荐。如果没有对应的键,则回退到noeviction策略。
- Redis主从同步
(1)主从复制作用 ①数据冗余 ②故障恢复(服务冗余) ③负载均衡 ④读写分离(主节点写操作、从节点读操作) (2)主从复制过程 ①连接建立阶段 步骤1:保存主节点信息 步骤2:建立socket连接 步骤3:发送ping命令 步骤4:身份验证 步骤5:发送从节点端口信息 ②数据同步阶段 从节点向主节点发送psync命令 根据主从节点当前状态的不同,可以分为全量复制和部分复制 ③命令传播阶段 主从节点进入命令传播阶段;在这个阶段主节点将自己执行的写命令发送给从节点,从节点接收命令并执行,从而保证主从节点数据的一致性。 (3)介绍全量复制和部分复制 ①全量复制:用于初次复制或其他无法进行部分复制的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作。 ②部分复制:用于网络中断等情况后的复制,只将中断期间主节点执行的写命令发送给从节点,与全量复制相比更加高效。需要注意的是,如果网络中断时间过长,导致主节点没有能够完整地保存中断期间执行的写命令,则无法进行部分复制,仍使用全量复制。 (4)主从复制缺点:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制。
- 为什么会有哨兵机制?
在主从复制的基础上,哨兵实现了自动化的故障恢复。
- 哨兵机制作用?
(1)监控(Monitoring):哨兵会不断地检查主节点和从节点是否运作正常。 (2)自动故障转移(Automatic failover):当主节点不能正常工作时,哨兵会开始自动故障转移操作,它会将失效主节点的其中一个从节点升级为新的主节点,并让其他从节点改为复制新的主节点。 (3)配置提供者(Configuration provider):客户端在初始化时,通过连接哨兵来获得当前Redis服务的主节点地址。 (4)通知(Notification):哨兵可以将故障转移的结果发送给客户端。
- 哨兵机制节点组成?
它由两部分组成,哨兵节点和数据节点: (1)哨兵节点:哨兵系统由一个或多个哨兵节点组成,哨兵节点是特殊的redis节点,不存储数据。 (2)数据节点:主节点和从节点都是数据节点。
- 哨兵机制原理?
(1)定时任务:每个哨兵节点维护了3个定时任务。定时任务的功能分别如下:通过向主从节点发送info命令获取最新的主从结构;通过发布订阅功能获取其他哨兵节点的信息;通过向其他节点发送ping命令进行心跳检测,判断是否下线。 (2)主观下线:在心跳检测的定时任务中,如果其他节点超过一定时间没有回复,哨兵节点就会将其进行主观下线。顾名思义,主观下线的意思是一个哨兵节点“主观地”判断下线;与主观下线相对应的是客观下线。 (3)客观下线:哨兵节点在对主节点进行主观下线后,会通过sentinel is-master-down-by-addr命令询问其他哨兵节点该主节点的状态;如果判断主节点下线的哨兵数量达到一定数值,则对该主节点进行客观下线。 (4)选举领导者哨兵节点:当主节点被判断客观下线以后,各个哨兵节点会进行协商,选举出一个领导者哨兵节点,并由该领导者节点对其进行故障转移操作。监视该主节点的所有哨兵都有可能被选为领导者,选举使用的算法是Raft算法;Raft算法的基本思路是先到先得:即在一轮选举中,哨兵A向B发送成为领导者的申请,如果B没有同意过其他哨兵,则会同意A成为领导者。选举的具体过程这里不做详细描述,一般来说,哨兵选择的过程很快,谁先完成客观下线,一般就能成为领导者。 (5)故障转移:选举出的领导者哨兵,开始进行故障转移操作,该操作大体可以分为3个步骤: ①在从节点中选择新的主节点:选择的原则是,首先过滤掉不健康的从节点;然后选择优先级最高的从节点(由slave-priority指定);如果优先级无法区分,则选择复制偏移量最大的从节点;如果仍无法区分,则选择runid最小的从节点。 ②更新主从状态:通过slaveof no one命令,让选出来的从节点成为主节点;并通过slaveof命令让其他节点成为其从节点。 ③将已经下线的主节点(即6379)设置为新的主节点的从节点,当6379重新上线后,它会成为新的主节点的从节点。
- 哨兵机制缺点
写操作无法负载均衡;存储能力受到单机的限制。(Redis集群解决了该情况)
- Zookeeper锁是如何实现的?
一般使用Curator进行使用Zookeeper锁,例如有两个客户端A和客户端B,首先A先在锁节点下创建例如01子节点的锁,然后再获取节点信息,发现自己的01节点排名第一,那么就获得锁。 客户端B也需要获取锁,现在锁节点下创建例如02的子节点,然后再获取锁节点信息,发现锁节点信息为[01,02],并不排第一,因此获取不到锁,客户端B会在他的顺序节点的上一个顺序节点加一个监听器。 当客户端A使用完锁,删除01节点,客户端B获取到01删除的监听,然后发现自己的02节点排名第一,那么就获取到锁。
- JVM内存模型
(1)程序计数器:线程私有,用来程序跳转,流程控制 (2)方法区(1.8叫元数据区):线程共享,用于存储类信息、常量、静态变量等信息 (3)Java虚拟机栈:线程私有,用于方法调用Java 虚拟机栈会出现两种错误:StackOverFlowError 和 OutOfMemoryError (4)堆:线程私有,主要的内存区域,存储对象实例,垃圾回收主要针对这一块。 (5)本地方法栈:线程共享,本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。
- G1和CMS垃圾回收器
G1和CMS垃圾回收器 (1)CMS收集器:是一种以获取最短回收停顿时间为目标的收集器。 过程: ①初始标记:标记GC Roots能直接关联到的对象,需要在safepoint位置暂停所有执行线程。---->STW ②并发标记:进行GC Roots Tracing,遍历完从root可达的所有对象。该阶段与工作线程并发执行。 ③重新标记:修正并发标记期间因用户程序继续运作而导致标记产生表动的那一部分对象的标记记录。需要在safepoint位置暂停所有执行线程。--->STW ④并发清除: 开启用户线程,同时 GC 线程开始对为标记的区域做清扫。 优点:并发收集、低停顿。 缺点: ①CMS收集器对CPU资源非常敏感。 ②CMS收集器无法处理浮动垃圾(Floating Garbage)。 ③CMS收集器是基于标记-清除算法,该算法缺点都有:标记和清除效率低/产生大量不连续的内存碎片。 ④停顿时间是不可预期的。 (2)G1收集器:重新定义了堆空间,打破了原有的分代模型,将堆划分为一个个区域。这么做的目的是在进行收集时不必在全堆范围内进行,这是它最显著的特点。 过程: ①初始标记:标记GC Roots 可以直接关联的对象,该阶段需要线程停顿但是耗时短。---->STW ②并发标记:寻找存活的对象,可以与其他程序并发执行,耗时较长。 ③最终标记:并发标记期间用户程序会导致标记记录产生变动(好比一个阿姨一边清理垃圾,另一个人一边扔垃圾)虚拟机会将这段时间的变化记录在Remembered Set Logs 中。最终标记阶段会向Remembered Set合并并发标记阶段的变化。这个阶段需要线程停顿,也可以并发执行---->STW ④筛选回收:对每个Region的回收成本进行排序,按照用户自定义的回收时间来制定回收计划 优点: ①空间整合:G1使用Region独立区域概念,G1利用的是标记复制法,不会产生垃圾碎片 ②分代收集:G1可以自己管理新生代和老年代 ③并行于并发:G1可以通过机器的多核来并发处理 STW停顿,减少停顿时间,并且可不停顿java线程执行GC动作,可通过并发方式让GC和java程序同时执行。 ④可预测停顿:G1除了追求停顿时间,还建立了可预测停顿时间模型,能让制定的M毫秒时间片段内,消耗在垃圾回收器上的时间不超过N毫秒 缺点: G1 需要记忆集 (具体来说是卡表)来记录新生代和老年代之间的引用关系,这种数据结构在 G1 中需要占用大量的内存,可能达到整个堆内存容量的 20% 甚至更多。而且 G1 中维护记忆集的成本较高,带来了更高的执行负载,影响效率。
- wait/await和sleep区别
(1)两者最主要的区别在于:sleep 方法没有释放锁,而 wait 方法释放了锁 。 (2)两者都可以暂停线程的执行。 (3)wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。 (4)wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用 wait(long timeout)超时后线程会自动苏醒。
- Redis的LRU过期策略的具体实现 Redis的LRU具体实现
用栈的形式会导致执行select *的时候大量非热点数据占领头部数据,所以需要改进。 Redis每次按key获取一个值的时候,都会更新value中的lru字段为当前秒级别的时间戳。Redis初始的实现算法很简单,随机从dict中取出五个key,淘汰一个lru字段值最小的。 在3.0的时候,又改进了一版算法,首先第一次随机选取的key都会放入一个pool中(pool的大小为16),pool中的key是按lru大小顺序排列的。接下来每次随机选取的keylru值必须小于pool中最小的lru才会继续放入,直到将pool放满。放满之后,每次如果有新的key需要放入,需要将pool中lru最大的一个key取出。淘汰的时候,直接从pool中选取一个lru最小的值然后将其淘汰。
- 哪些对象可以作为GC Roots?
(1)虚拟机栈(栈帧中的本地变量表)中引用的对象。 (2)方法区中类静态属性引用的对象。 (3)方法区中常量引用的对象。 (4)本地方法栈中JNI(即一般说的Native方法)引用的对象。
- ConcurrentHashMap的数据结构
在JDK1.7版本中,ConcurrentHashMap维护了一个Segment数组,Segment这个类继承了重入锁ReentrantLock,并且该类里面维护了一个 HashEntry<K,V>[] table数组,在写操作put,remove,扩容的时候,会对Segment加锁,所以仅仅影响这个Segment,不同的Segment还是可以并发的,所以解决了线程的安全问题,同时又采用了分段锁也提升了并发的效率。在JDK1.8版本中,ConcurrentHashMap摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap。
- tcp和udp的优点与缺点
(1)TCP的优点: 可靠,稳定 TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源。 (2)TCP的缺点: 慢,效率低,占用系统资源高,易被攻击 TCP在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞控制机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的CPU、内存等硬件资源。 而且,因为TCP有确认机制、三次握手机制,这些也导致TCP容易被人利用,实现DOS、DDOS、CC等攻击。 (3)UDP的优点: 快,比TCP稍安全 UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制,UDP是一个无状态的传输协议,所以它在传递数据时非常快。没有TCP的这些机制,UDP较TCP被攻击者利用的漏洞就要少一些。但UDP也是无法避免攻击的,比如:UDP Flood攻击…… (4)UDP的缺点: 不可靠,不稳定 因为UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包。 基于上面的优缺点,那么: 什么时候应该使用TCP: 当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议。 在日常生活中,常见使用TCP协议的应用如下: 浏览器,用的HTTP FlashFXP,用的FTP Outlook,用的POP、SMTP Putty,用的Telnet、SSH QQ文件传输。什么时候应该使用UDP: 当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快,这时就可以使用UDP。 比如,日常生活中,常见使用UDP协议的应用如下: QQ语音 QQ视频 TFTP。
- HashMap原理
(1)hashMap 是非线程安全的, hashMap 1.7的底层实现为数组(table[])+链表(LinkList-->Entry),hashmap 1.8底层为数组+链表/红黑树(当链表长度到达阈值TREEIFY_THRESHOLD(默认为8)时,会转化为红黑树)
- HashMap的put和resize的过程
(1)put过程: ①查看数组是否需要初始化 ②根据key计算hashcode ③根据hashcode计算出桶位置 ④遍历链表,查看key值与链表节点的key值是否相等,如果相等的话,那么进行覆盖旧值,并返回旧值。1.8的话需要先查看链表长度是否达到阈值,如果达到阈值,先进行红黑树转化然后再进行检查扩容。 ⑤新增的时候需要检查是否需要扩容,需要扩容的话进行两倍扩容,扩容完成后进行插入新值。 (2)resize过程: resize扩容需要从四个方面来进行回答: ①什么时候触发resize? 当容量超过当前容量(默认容量16)乘以负载因子(默认0.75)就会进行扩容,扩容大小为当前大小的两倍(扩展问题,为啥是两倍:通过限制length是一个2的幂数,h & (length-1)和h % length结果是一致的)。 ②resize是如何hash的:h & (length-1) ③resize是如何进行链表操作的:使用头插法进行数据插入,每次新put的值放在头部 ④并发操作下,链表是如何成环的:HashMap的环:若当前线程此时获得ertry节点,但是被线程中断无法继续执行,此时线程二进入transfer函数,并把函数顺利执行,此时新表中的某个位置有了节点,之后线程一获得执行权继续执行,因为并发transfer,所以两者都是扩容的同一个链表,当线程一执行到e.next = new table[i] 的时候,由于线程二之前数据迁移的原因导致此时new table[i] 上就有ertry存在,所以线程一执行的时候,会将next节点,设置为自己,导致自己互相使用next引用对方,因此产生链表,导致死循环。
- 线程池有哪些类型
①FixedThreadPool:创建可重用固定线程数的线程池。 ②SingleThreadPool:创建只有一个线程的线程池。 ③CachedThreadPool:一个可根据需要创建新线程的线程池,如果现有线程没有可用的,则创建一个新线程并添加到池中,如果有被使用完但是还没销毁的线程,就复用该线程。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。 ④ScheduledThreadPool:创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
- ConcurrentHashMap分段锁原理
(1)ConcurrentHashMap采用了分段锁技术,其中Segement继承了RecentLock,当ConcurrentHashMap进行get、put操作时,均是同步的。各个Segement之间的get、put操作可以进行并发,即当一个线程访问ConcurrentHashMap的Segement时,不会影响对其他Segement的访问。
- B-树和B+树区别
1)B-树和B树是一个概念,是多路搜索树(相比于二叉搜索树,IO次数更少)。B-树的特性: ①关键字集合分布在整颗树中; ②任何一个关键字出现且只出现在一个结点中; ③搜索有可能在非叶子结点结束; ④其搜索性能等价于在关键字全集内做一次二分查找; ⑤其最底搜索性能为O(logN) (2)B+树是B-树的变体,也是一种多路搜索树 B+的特性: ①所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的; ②不可能在非叶子结点命中; ③非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层; ④更适合文件索引系统; (3)B+树的优势: ①单一节点存储更多的元素,使得查询的IO次数更少。 ②所有查询都要查找到叶子节点,查询性能稳定。 ③所有叶子节点形成有序链表,便于范围查询。
- Mysql数据库索引原理
(1)MyISAM索引实现:MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址。 (2)Innodb索引实现: ①第一个重大区别是InnoDB的数据文件本身就是索引文件。MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。 ②第二个与MyISAM索引的不同是InnoDB的辅助索引data域存储相应记录主键的值而不是地址。换句话说,InnoDB的所有辅助索引都引用主键作为data域。
- 组合索引怎么使用?最左匹配的原理。
1)组合索引怎么使用? 例如组合索引(a,b,c),组合索引的生效原则是: 从前往后依次使用生效,如果中间某个索引没有使用,那么断点前面(范围值也算断点,orderby不算断点,用到索引)的索引部分起作用,断点后面的索引没有起作用; (2)最左匹配的原理:以最左边的为起点任何连续的索引都能匹配上
- Spring生命周期
Bean 的生命周期概括起来就是 4 个阶段: (1)实例化(Instantiation) (2)属性赋值(Populate) (3)初始化(Initialization) (4)销毁(Destruction)
- Spring几种scope区别?
(1)singleton:Spring的IOC容器中只有一个实例bean,该值为scope的默认值 (2)prototype:每次getBean时都会创建一个新的实例 (3)request:每次请求都会创建一个实体bean (4)session:每次session请求时都会创建一个实体bean (5)globalsession:每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。
- Spring AOP实现有哪几种实现
接口代理和类代理会有什么区别? (1)Spring AOP有两种实现,均为动态代理: ①JDK动态代理:基于反射进行动态代理,核心类是InvocationHandker类和Proxy类,被代理的类必须实现接口 ②CGLIB动态代理:被代理类无需实现接口,主要实现MethodInterceptor接口即可实现代理 (2)Spring AOP如果代理的类存在接口,优先使用JDK动态代理,否则使用CGLIB动态代理。
欢迎搜索关注本人与朋友共同开发的微信面经小程序【大厂面试助手】和公众号【微瞰技术】