-
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动态代理。
- MVCC,binlog,redolog,undolog都是什么,起什么作用?
(1)undolog 也就是我们常说的回滚日志文件 主要用于事务中执行失败,进行回滚,以及MVCC中对于数据历史版本的查看。由引擎层的InnoDB引擎实现,是逻辑日志,记录数据修改被修改前的值,比如"把id='B' 修改为id = 'B2' ,那么undo日志就会用来存放id ='B'的记录”。当一条数据需要更新前,会先把修改前的记录存储在undolog中,如果这个修改出现异常,则会使用undo日志来实现回滚操作,保证事务的一致性。当事务提交之后,undo log并不能立马被删除,而是会被放到待清理链表中,待判断没有事物用到该版本的信息时才可以清理相应undolog。它保存了事务发生之前的数据的一个版本,用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读。 (2)redoLog 是重做日志文件是记录数据修改之后的值,用于持久化到磁盘中。redo log包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(redo log file),该部分日志是持久的。由引擎层的InnoDB引擎实现,是物理日志,记录的是物理数据页修改的信息,比如“某个数据页上内容发生了哪些改动”。当一条数据需要更新时,InnoDB会先将数据更新,然后记录redoLog 在内存中,然后找个时间将redoLog的操作执行到磁盘上的文件上。不管是否提交成功我都记录,你要是回滚了,那我连回滚的修改也记录。它确保了事务的持久性。 (3)MVCC多版本并发控制是MySQL中基于乐观锁理论实现隔离级别的方式,用于读已提交和可重复读取隔离级别的实现。在MySQL中,会在表中每一条数据后面添加两个字段:最近修改该行数据的事务ID,指向该行(undolog表中)回滚段的指针。Read View判断行的可见性,创建一个新事务时,copy一份当前系统中的活跃事务列表。意思是,当前不应该被本事务看到的其他事务id列表。 (4)binlog由Mysql的Server层实现,是逻辑日志,记录的是sql语句的原始逻辑,比如"把id='B' 修改为id = ‘B2’。binlog会写入指定大小的物理文件中,是追加写入的,当前文件写满则会创建新的文件写入。 产生:事务提交的时候,一次性将事务中的sql语句,按照一定的格式记录到binlog中。用于复制和恢复在主从复制中,从库利用主库上的binlog进行重播(执行日志中记录的修改逻辑),实现主从同步。业务数据不一致或者错了,用binlog恢复
- Kafka是如何实现高吞吐率的?
1)顺序读写:kafka的消息是不断追加到文件中的,这个特性使kafka可以(2)充分利用磁盘的顺序读写性能 (3)零拷贝:跳过“用户缓冲区”的拷贝,建立一个磁盘空间和内存的直接映射,数据不再复制到“用户态缓冲区” (4)文件分段:kafka的队列topic被分为了多个区partition,每个partition又分为多个段segment,所以一个队列中的消息实际上是保存在N多个片段文件中 (5)批量发送:Kafka允许进行批量发送消息,先将消息缓存在内存中,然后一次请求批量发送出去 (6)数据压缩:Kafka还支持对消息集合进行压缩,Producer可以通过GZIP或Snappy格式对消息集合进行压缩
- Http请求的完全过程
(1)浏览器根据域名解析IP地址(DNS),并查DNS缓存 (2)浏览器与WEB服务器建立一个TCP连接 (3)浏览器给WEB服务器发送一个HTTP请求(GET/POST):一个HTTP请求报文由请求行(request line)、请求头部(headers)、空行(blank line)和请求数据(request body)4个部分组成。 (4)服务端响应HTTP响应报文,报文由状态行(status line)、相应头部(headers)、空行(blank line)和响应数据(response body)4个部分组成。 (5)浏览器解析渲染
- Spring的@Transactional如何实现的?
(1)配置文件开启注解驱动,在相关的类和方法上通过注解@Transactional标识。 (2)spring 在启动的时候会去解析生成相关的bean,这时候会查看拥有相关注解的类和方法,并且为这些类和方法生成代理,并根据@Transaction的相关参数进行相关配置注入,这样就在代理中为我们把相关的事务处理掉了(开启正常提交事务,异常回滚事务)。 (3)真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的
- 为什么要使用线程池?
(1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 (2)提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。 (3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
- rpc框架实现原理?
主要有以下几个步骤: (1)建立通信: 首先要解决通讯的问题:即A机器想要调用B机器,首先得建立起通信连接。主要是通过在客户端和服务器之间建立TCP连接,远程过程调用的所有相关的数据都在这个连接里面进行传输交换。 通常这个连接可以是按需连接(需要调用的时候就先建立连接,调用结束后就立马断掉),也可以是长连接(客户端和服务器建立起连接之后保持长期持有,不管此时有无数据包的发送,可以配合心跳检测机制定期检测建立的连接是否存活有效),多个远程过程调用共享同一个连接。 (2)服务寻址: 解决寻址的问题:即A机器上的应用A要调用B机器上的应用B,那么此时对于A来说如何告知底层的RPC框架所要调用的服务具体在哪里呢? 通常情况下我们需要提供B机器(主机名或IP地址)以及特定的端口,然后指定调用的方法或者函数的名称以及入参出参等信息,这样才能完成服务的一个调用。比如基于Web服务协议栈的RPC,就需要提供一个endpoint URI,或者是从UDDI服务上进行查找。如果是RMI调用的话,还需要一个RMI Registry来注册服务的地址。 (3)网络传输: ①序列化 当A机器上的应用发起一个RPC调用时,调用方法和其入参等信息需要通过底层的网络协议如TCP传输到B机器,由于网络协议是基于二进制的,所有我们传输的参数数据都需要先进行序列化(Serialize)或者编组(marshal)成二进制的形式才能在网络中进行传输。然后通过寻址操作和网络传输将序列化或者编组之后的二进制数据发送给B机器。 ②反序列化 当B机器接收到A机器的应用发来的请求之后,又需要对接收到的参数等信息进行反序列化操作(序列化的逆操作),即将二进制信息恢复为内存中的表达方式,然后再找到对应的方法(寻址的一部分)进行本地调用(一般是通过生成代理Proxy去调用, 通常会有JDK动态代理、CGLIB动态代理、Javassist生成字节码技术等),之后得到调用的返回值。 (4)服务调用: B机器进行本地调用(通过代理Proxy)之后得到了返回值,此时还需要再把返回值发送回A机器,同样也需要经过序列化操作,然后再经过网络传输将二进制数据发送回A机器,而当A机器接收到这些返回值之后,则再次进行反序列化操作,恢复为内存中的表达方式,最后再交给A机器上的应用进行相关处理(一般是业务逻辑处理操作)。
- redis热key怎么解决?
(1)利用二级缓存: 比如利用ehcache,或者一个HashMap都可以。在你发现热key以后,把热key加载到系统的JVM中。针对这种热key请求,会直接从jvm中取,而不会走到redis层。假设此时有十万个针对同一个key的请求过来,如果没有本地缓存,这十万个请求就直接怼到同一台redis上了。 现在假设,你的应用层有50台机器,OK,你也有jvm缓存了。这十万个请求平均分散开来,每个机器有2000个请求,会从JVM中取到value值,然后返回数据。避免了十万个请求怼到同一台redis上的情形。 (2)备份热key: 这个方案也很简单。不要让key走到同一台redis上不就行了。我们把这个key,在多个redis上都存一份不就好了。接下来,有热key请求进来的时候,我们就在有备份的redis上随机选取一台,进行访问取值,返回数据。
- 不同年代GC收集器有哪些?
(1)serial收集器:单线程,工作时必须暂停其他工作线程。多用于client机器上,使用复制算法 (2)ParNew收集器:serial收集器的多线程版本,server模式下虚拟机首选的新生代收集器。复制算法 (3)Parallel Scavenge收集器:复制算法,可控制吞吐量的收集器。吞吐量即有效运行时间。 (4)Serial Old收集器:serial的老年代版本,使用整理算法。 (5)Parallel Old收集器:第三种收集器的老年代版本,多线程,标记整理 (6)CMS收集器:目标是最短回收停顿时间。标记清除算法实现,分四个阶段: •初始标记:GC Roots直连的对象做标记 •并发标记:多线程方式GC Roots Tracing •重新标记:修正第二阶段标记的记录 •并发清除。 缺点:标记清除算法的缺点,产生碎片。CPU资源敏感。
- ES脑裂问题分析及优化
(1)脑裂问题可能的成因 •网络问题:集群间的网络延迟导致一些节点访问不到master,认为master挂掉了从而选举出新的master,并对master上的分片和副本标红,分配新的主分片 •节点负载:主节点的角色既为master又为data,访问量较大时可能会导致ES停止响应造成大面积延迟,此时其他节点得不到主节点的响应认为主节点挂掉了,会重新选取主节点。 •内存回收:data节点上的ES进程占用的内存较大,引发JVM的大规模内存回收,造成ES进程失去响应。 (2)脑裂问题解决方案: •减少误判:discovery.zen.ping_timeout节点状态的响应时间,默认为3s,可以适当调大,如果master在该响应时间的范围内没有做出响应应答,判断该节点已经挂掉了。调大参数(如6s,discovery.zen.ping_timeout:6),可适当减少误判。 •选举触发 discovery.zen.minimum_master_nodes:1 该参数是用于控制选举行为发生的最小集群主节点数量。 当备选主节点的个数大于等于该参数的值,且备选主节点中有该参数个节点认为主节点挂了,进行选举。官方建议为(n/2)+1,n为主节点个数(即有资格成为主节点的节点个数) 增大该参数,当该值为2时,我们可以设置master的数量为3,这样,挂掉一台,其他两台都认为主节点挂掉了,才进行主节点选举。 •角色分离:即master节点与data节点分离,限制角色
- 讲一讲类加载的过程
一般来说,我们把 Java 的类加载过程分为三个主要步骤:加载,连接,初始化,具体行为在 Java 虚拟机规范里有非常详细的定义。 (1)首先是加载过程(Loading),它是 Java 将字节码数据从不同的数据源读取到 JVM 中,并映射为 JVM 认可的数据结构(Class 对象),这里的数据源可能是各种各样的形态,比如 jar 文件,class 文件,甚至是网络数据源等;如果输入数据不是 ClassFile 的结构,则会抛出 ClassFormatError。加载阶段是用户参与的阶段,我们可以自定义类加载器,去实现自己的类加载过程。 (2)第二阶段是连接(Linking),这是核心的步骤,简单说是把原始的类定义信息平滑地转入 JVM 运行的过程中。这里可进一步细分成三个步骤:1,验证(Verification),这是虚拟机安全的重要保障,JVM 需要核验字节信息是符合 Java 虚拟机规范的,否则就被认为是 VerifyError,这样就防止了恶意信息或者不合规信息危害 JVM 的运行,验证阶段有可能触发更多 class 的加载。2,准备(Pereparation),创建类或者接口中的静态变量,并初始化静态变量的初始值。但这里的“初始化”和下面的显示初始化阶段是有区别的,侧重点在于分配所需要的内存空间,不会去执行更进一步的 JVM 指令。3,解析(Resolution),在这一步会将常量池中的符号引用(symbolic reference)替换为直接引用。在 Java 虚拟机规范中,详细介绍了类,接口,方法和字段等各方面的解析。 (3)最后是初始化阶段(initialization),这一步真正去执行类初始化的代码逻辑,包括静态字段赋值的动作,以及执行类定义中的静态初始化块内的逻辑,编译器在编译阶段就会把这部分逻辑整理好,父类型的初始化逻辑优先于当前类型的逻辑。再来谈谈双亲委派模型,简单说就是当加载器(Class-Loader)试图加载某个类型的时候,除非父类加载器找不到相应类型,否则尽量将这个任务代理给当前加载器的父加载器去做。使用委派模型的目的是避免重复加载 Java 类型。
- 不可重复读和幻读区别
(1)"不可重复读" 是指在一个事务内,多次读同一数据。在这个事务还没有结束时,bai另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。 (2)幻觉读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。
- 单例对象会被jvm的gc时回收吗
(1)jvm卸载类的判定条件如下: ①该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。 ②加载该类的ClassLoader已经被回收。 ③该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。 (2)只有上面三个条件都满足,jvm才会在垃圾收集的时候卸载类。显然,单例的类不满足条件一,因此单例类也不会被卸载。也就是说,只要单例类中的静态引用指向jvm堆中的单例对象,那么单例类和单例对象都不会被垃圾收集,依据根搜索算法,对象是否会被垃圾收集与未被使用时间长短无关,仅仅在于这个对象是不是不可回收的。
- Get和Post区别
(1)Get是不安全的,因为在传输过程,数据被放在请求的URL中;Post的所有操作对用户来说都是不可见的。 (2)Get传送的数据量较小,这主要是因为受URL长度限制;Post传送的数据量较大,一般被默认为不受限制。 (3)Get限制Form表单的数据集的值必须为ASCII字符;而Post支持整个ISO10646字符集。 (4)Get执行效率却比Post方法好。Get是form提交的默认方法。 GET产生一个TCP数据包;POST产生两个TCP数据包。(非必然,客户端可灵活决定)
- 死锁的4个必要条件
(1)互斥条件:一个资源每次只能被一个线程使用; (2)请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放; (3)不剥夺条件:进程已经获得的资源,在未使用完之前,不能强行剥夺; (4)循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系
- Redis 的数据结构及使用场景
1)String字符串:字符串类型是 Redis 最基础的数据结构,首先键都是字符串类型,而且 其他几种数据结构都是在字符串类型基础上构建的,我们常使用的 set key value 命令就是字符串。常用在缓存、计数、共享Session、限速等。 (2)Hash哈希:在Redis中,哈希类型是指键值本身又是一个键值对 结构,添加命令:hset key field value。哈希可以用来存放用户信息,比如实现购物车。 (3)List列表(双向链表):列表(list)类型是用来存储多个有序的字符串。可以做简单的消息队列的功能。 (4)Set集合:集合(set)类型也是用来保存多个的字符串元素,但和列表类型不一 样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过 索引下标获取元素。利用 Set 的交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。 (5)Sorted Set有序集合(跳表实现):Sorted Set 多了一个权重参数 Score,集合中的元素能够按 Score 进行排列。可以做排行榜应用,取 TOP N 操作
- ZAB协议
ZAB协议包括两种基本的模式:崩溃恢复和消息广播。当整个 Zookeeper 集群刚刚启动或者Leader服务器宕机、重启或者网络故障导致不存在过半的服务器与 Leader 服务器保持正常通信时,所有服务器进入崩溃恢复模式,首先选举产生新的 Leader 服务器,然后集群中 Follower 服务器开始与新的 Leader 服务器进行数据同步。 当集群中超过半数机器与该 Leader 服务器完成数据同步之后,退出恢复模式进入消息广播模式,Leader 服务器开始接收客户端的事务请求生成事物提案来进行事务请求处理。
- volatile作用
(1)volatile在多处理器开发中保证了共享变量的“ 可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。(共享内存,私有内存) (2)volatile关键字通过“内存屏障”来防止指令被重排序。
- 什么是值传递和引用传递
(1)值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量. (2)引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身 。
- Java内存模型
Java虚拟机规范中将Java运行时数据分为六种: (1)程序计数器:是一个数据结构,用于保存当前正常执行的程序的内存地址。Java虚拟机的多线程就是通过线程轮流切换并分配处理器时间来实现的,为了线程切换后能恢复到正确的位置,每条线程都需要一个独立的程序计数器,互不影响,该区域为“线程私有”。 (2)Java虚拟机栈:线程私有的,与线程生命周期相同,用于存储局部变量表,操作栈,方法返回值。局部变量表放着基本数据类型,还有对象的引用。 (3)本地方法栈:跟虚拟机栈很像,不过它是为虚拟机使用到的Native方法服务。 (4)Java堆:所有线程共享的一块内存区域,对象实例几乎都在这分配内存。 (5)方法区:各个线程共享的区域,储存虚拟机加载的类信息,常量,静态变量,编译后的代码。 (6)运行时常量池:代表运行时每个class文件中的常量表。包括几种常量:编译时的数字常量、方法或者域的引用。
- string.stringbuilder.stringbuffer的区别,为什么string不可变
(1)区别 ①String是字符串常量,而StringBuffer和StringBuilder是字符串变量。由String创建的字符内容是不可改变的,而由StringBuffer和StringBuidler创建的字符内容是可以改变的。 ②StringBuffer是线程安全的,而StringBuilder是非线程安全的。StringBuilder是从JDK 5开始,为StringBuffer类补充的一个单线程的等价类。我们在使用时应优先考虑使用StringBuilder,因为它支持StringBuffer的所有操作,但是因为它不执行同步,不会有线程安全带来额外的系统消耗,所以速度更快。 (2)String为什么不可变: 虽然String、StringBuffer和StringBuilder都是final类,它们生成的对象都是不可变的,而且它们内部也都是靠char数组实现的,但是不同之处在于,String类中定义的char数组是final的,而StringBuffer和StringBuilder都是继承自AbstractStringBuilder类,它们的内部实现都是靠这个父类完成的,而这个父类中定义的char数组只是一个普通是私有变量,可以用append追加。因为AbstractStringBuilder实现了Appendable接口
- 为什么在重写equals方法的时候要重写hashcode的方法?
(1)我们知道判断的时候先根据hashcode进行的判断,相同的情况下再根据equals()方法进行判断。如果只重写了equals方法,而不重写hashcode的方法,造成hashcode的值不同,而equals()方法判断出来的结果为true。 (2)在Java中的一些容器中,不允许有两个完全相同的对象,插入的时候,如果判断相同则会进行覆盖。这时候如果只重写了equals()的方法,而不重写hashcode的方法,Object中hashcode是根据对象的存储地址转换而形成的一个哈希值。这时候就有可能因为没有重写hashcode方法,造成相同的对象散列到不同的位置而造成对象的不能覆盖的问题。
- 反射的作用是什么?
(1)反射的主要作用是用来扩展系统和动态调用程序集。 (2)所谓扩展系统就是先把系统写好,系统里面定义接口,后面开发的人去写接口的代码。 (3)动态调用程序集就是利用反射去调用编译好的dll,当然此时的dll没有被引用到你所建的工程里面。
- 同步与异步区别?
(1)同步,可以理解为在执行完一个函数或方法之后,一直等待系统返回值或消息,这时程序是出于阻塞的,只有接收到返回的值或消息后才往下执行其他的命令。 (2)异步,执行完函数或方法后,不必阻塞性地等待返回值或消息,只需要向系统委托一个异步过程,那么当系统接收到返回值或消息时,系统会自动触发委托的异步过程,从而完成一个完整的流程。
- Java中overload override的区别
(1)Overload是重载的意思,Override是覆盖的意思,也就是重写。 (2)重写Override表示子类中的方法可以与父类中的某个方法的名称和参数完全相同,通过子类创建的实例对象调用这个方法时,将调用子类中定义的方法,这相当于把父类中的方法给覆盖了,这也是多态性的一种表现。 (3)重载overload的特点就是与返回值无关,只看参数列表,所以重载的方法可以改变返回值类型。所以,如果两个方法的参数列表完全一样,是不能通过让它们的返回值类型不同来实现重载的。 (4)override是覆盖一个方法并且对其重写,以求达到不同的作用。对我们来说最熟悉的覆盖就是对接口方法的实现,在接口中一般只是对方法进行了声明,而我们在实现时,就需要实现接口声明的所有方法。除了这个典型的用法以外,我们在继承中也可能会在子类覆盖父类中的方法。 (5)overload对我们来说可能比较熟悉,可以翻译为重载,它是指我们可以定义一些名称相同的方法,通过定义不同类型的输入参数来区分这些方法,然后再调用时,JVM就会根据不同的参数样式,来选择合适的方法执行。 (6)方法的重写和重载是Java多态性的不同表现。重写是父类与子类之间多态性的一种表现,而重载是一个类中多态性的一种表现。
- 线程的创建方式
(1)继承Thread ①定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。 ②创建Thread子类的实例,即创建了线程对象。 ③调用线程对象的start()方法来启动该线程。 (2)实现Runnable接口 ①定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。 ②创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。 ③调用线程对象的start()方法来启动该线程。 (3)实现Callable接口 ①创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。 ②创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。 ③使用FutureTask对象作为Thread对象的target创建并启动新线程。 ④调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
- LinkList和ArrayList的区别
(1)ArrayList的底层实现就是数组,且ArrayList实现了RandomAccess,表示它能快速随机访问存储的元素,通过下标 index 访问,只是我们需要用 get() 方法的形式,数组支持随机访问,查询速度快,增删元素慢; (2)LinkedList的底层实现是链表,LinkedList没有实现RandomAccess 接口,链表支持顺序访问,查询速度慢,增删元素快;
- 垃圾回收算法
(1)标记—清除算法 标记—清除算法是最基础的收集算法,它分为“标记”和“清除”两个阶段:首先标记出所需回收的对象,在标记完成后统一回收掉所有被标记的对象,它的标记过程其实就是前面的可达性分析算法中判定垃圾对象的标记过程。 (2)复制算法 复制算法是针对标记—清除算法的缺点,在其基础上进行改进而得到的,它将可用内存按容量分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就将还存活着的对象复制到另外一块内存上面,然后再把已使用过的内存空间一次清理掉。 (3)标记—整理算法 复制算法比较适合于新生代,在老年代中,对象存活率比较高,如果执行较多的复制操作,效率将会变低,所以老年代一般会选用其他算法,如标记—整理算法。该算法标记的过程与标记—清除算法中的标记过程一样,但对标记后出的垃圾对象的处理情况有所不同,它不是直接对可回收对象进行清理,而是让所有的对象都向一端移动,然后直接清理掉端边界以外的内存
- Hashtable 和 HashMap的区别?
( 1)主要区别在于 HashMap允许将 null作为一个 entry的 key或者 value,而 Hashtable不允许。由于非线程安全,多线程情况下,效率上可能高于 Hashtable。 Hashtable和 HashMap采用的 hash/rehash 算法都大概一样,所以单线程性能不会有很大的差异。 ( 2) Hashtable的方法是 Synchronize的,而 HashMap不是,在多个线程访问 Hashtable时,不需要自己为它的方法实现同步,而 HashMap 就必须为之提供外同步 (Collections.synchronizedMap)。 ( 3) HashMap是 Hashtable的轻量级实现(非线程安全的实现),他们都完成了 Map接口, Hashtable继承自 Dictionary类,而 HashMap是 Java1.2引进的 Map interface的一个实现。
- servlet的线程安全问题?
答:如果代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。那么我们都知道servlet是多线程的,同时一个servlet实现类只会有一个实例对象,也就是它是Singleton的,所以多个线程是可能会访问同一个servlet实例对象的。同一个实例对象被多个线程访问,如果没有做同步处理,那么servlet就是非线程安全的,如果做了同步处理,就是线程安全。所以,servlet是否线程安全是由它的实现来决定的,如果它内部的属性或方***被多个线程改变,它就是线程不安全的,反之,就是线程安全的。
- 为什么要用volatile关键字?
答:在高并发时会出现并发模式异常,volatile可以防止指令重排,创建对象操作并不是一个原子操作,分为三个步骤 (1)构建对象:根据Person类元信息确定对象的大小,向JVM堆中申请一块内存区域并构建对象的默认信息(加载Person对象成员变量信息并赋默认值如 int类型为0,引用类型为null)。 (2)初始化对象:然后执行对象内部生成的init方法,初始化成员变量值,同时执行搜集到的{}代码块逻辑,最后执行对象构造方法。 (3)引用对象:对象实例化完毕后,再把栈中的Person对象引用地址指向Person对象在堆内存中的地址......
- java并发锁机制。
(1)偏向锁:JDK1.6提出来的一种锁优化的机制。其核心的思想是,如果程序没有竞争,则取消之前已经取得锁的线程同步操作。也就是说,若某一锁被线程获取后,便进入偏向模式,当线程再次请求这个锁时,就无需再进行相关的同步操作了,从而节约了操作时间,如果在此之间有其他的线程进行了锁请求,则锁退出偏向模式 (2)轻量级锁:如果偏向锁失败,Java虚拟机就会让线程申请轻量级锁,轻量级锁在虚拟机内部,使用一个成为BasicObjectLock的对象实现的,这个对象内部由一个BasicLock对象和一个持有该锁的Java对象指针组成。BasicObjectLock对象放置在Java栈帧中。在BasicLock对象内部还维护着displaced_header字段,用于备份对象头部的Mark Word。 (3)重量级锁:当轻量级锁失败,虚拟机就会使用重量级锁。重量级锁在操作过程中,线程可能会被操作系统层面挂起,如果是这样,线程间的切换和调用成本就会大大提高。 (4)自旋锁:它可以使线程在没有取得锁的时候,不被挂起,而转去执行一个空循环,若在若干个空循环后,线程如果可以获得锁,则继续执行。若线程依然不能获得锁,才会被挂起。使用自旋锁后,线程被挂起的几率相对减少,线程执行的连贯性相对加强。因此,对于那些锁竞争不是很激烈,锁占用时间很短的并发线程,具有一定的积极意义,但对于锁竞争激烈,单线程锁占用很长时间的并发程序,自旋锁在自旋等待后,往往毅然无法获得对应的锁,不仅仅白白浪费了CPU时间,最终还是免不了被挂起的操作 ,反而浪费了系统的资源。
- 了解zookeeper的leader选取算法吗,讲一下它的流程。
(1)自增选举轮次。Zookeeper规定所有有效的投票都必须在同一轮次中,在开始新一轮投票时,会首先对logicalclock进行自增操作。 (2)初始化选票。在开始进行新一轮投票之前,每个服务器都会初始化自身的选票,并且在初始化阶段,每台服务器都会将自己推举为Leader。 (3)发送初始化选票。完成选票的初始化后,服务器就会发起第一次投票。Zookeeper会将刚刚初始化好的选票放入sendqueue中,由发送器WorkerSender负责发送出去。 (4)接收外部投票。每台服务器会不断地从recvqueue队列中获取外部选票。如果服务器发现无法获取到任何外部投票,那么就会立即确认自己是否和集群中其他服务器保持着有效的连接,如果没有连接,则马上建立连接,如果已经建立了连接,则再次发送自己当前的内部投票。 (5)判断选举轮次。在发送完初始化选票之后,接着开始处理外部投票。在处理外部投票时,会根据选举轮次来进行不同的处理。 ·外部投票的选举轮次大于内部投票。若服务器自身的选举轮次落后于该外部投票对应服务器的选举轮次,那么就会立即更新自己的选举轮次(logicalclock),并且清空所有已经收到的投票,然后使用初始化的投票来进行PK以确定是否变更内部投票。最终再将内部投票发送出去。 ·外部投票的选举轮次小于内部投票。若服务器接收的外选票的选举轮次落后于自身的选举轮次,那么Zookeeper就会直接忽略该外部投票,不做任何处理,并返回步骤4。 ·外部投票的选举轮次等于内部投票。此时可以开始进行选票PK。 (6)选票PK。在进行选票PK时,符合任意一个条件就需要变更投票。 · 若外部投票中推举的Leader服务器的选举轮次大于内部投票,那么需要变更投票。 · 若选举轮次一致,那么就对比两者的ZXID,若外部投票的ZXID大,那么需要变更投票。 · 若两者的ZXID一致,那么就对比两者的SID,若外部投票的SID大,那么就需要变更投票。 (7)变更投票。经过PK后,若确定了外部投票优于内部投票,那么就变更投票,即使用外部投票的选票信息来覆盖内部投票,变更完成后,再次将这个变更后的内部投票发送出去。 (8)选票归档。无论是否变更了投票,都会将刚刚收到的那份外部投票放入选票集合recvset中进行归档。recvset用于记录当前服务器在本轮次的Leader选举中收到的所有外部投票。 (9)统计投票。完成选票归档后,就可以开始统计投票,统计投票是为了统计集群中是否已经有过半的服务器认可了当前的内部投票,如果确定已经有过半服务器认可了该投票,则终止投票。否则返回步骤(4)。 (10)更新服务器状态。若已经确定可以终止投票,那么就开始更新服务器状态,服务器首选判断当前被过半服务器认可的投票所对应的Leader服务器是否是自己,若是自己,则将自己的服务器状态更新为LEADING,若不是,则根据具体情况来确定自己是FOLLOWING或是OBSERVING。
- 双亲委派机制及其使用原因?
(1)当某个特定的类加载器它在接到需要加载类的请求时,这个类会首先查看自己已加载完的类中是否包含这个类,如果有就返回,没有的话就会把加载的任务交给父类加载器加载,以此递归,父类加载器如果可以完成类加载任务,就返回它,当父类加载器无法完成这个加载任务时,才会不得已自己去加载。这种机制就叫做双亲委派机制。 (2)原因: 双亲委派机制能够保证多加载器加载某个类时,最终都是由一个加载器加载,确保最终加载结果相同。
- treemap和HashMap的区别?
(1)HashMap是通过hashcode()对其内容进行快速查找的;HashMap中的元素是没有顺序的;TreeMap中所有的元素都是有某一固定顺序的,如果需要得到一个有序的结果,就应该使用TreeMap; (2)HashMap继承AbstractMap类;覆盖了hashcode() 和equals() 方法,以确保两个相等的映射返回相同的哈希值;TreeMap继承SortedMap类;他保持键的有序顺序; (3)HashMap:基于hash表实现的;使用HashMap要求添加的键类明确定义了hashcode() 和equals();为了优化HashMap的空间使用,可以调优初始容量和负载因子;TreeMap:基于红黑树实现的;TreeMap就没有调优选项,因为红黑树总是处于平衡的状态;
- java内存泄露解决
堆的dump文件,通过jmx的mbean生产当前 的heap信息, 用eclipse自带的静态分析工具Mat(windDBG)打开 分析内存泄露:那些被怀疑为内存泄露,哪些占用空间大,对象调用关系
- 分布式锁的实现原理和有多少种实现方式?
目前主流的分布式锁的实现方式有三种: ①借助数据库来实现,新建一张锁表; 操作前向表添加一条锁记录(锁id建立唯一索引),成功添加者获得锁权限,处理完后删除锁记录来释放锁。 ②基于缓存实现,如memcache 和 redis; memcache的add操作具有原子性,可以保证同一个key add操作只有一个成功,来获取锁权限,利用缓存的失效时间来解决死锁问题。相对于第一种方案,这种方案性能更好,而且操作更方便。 ③通过zookeeper实现; 客户端会在zookeeper生成一个临时的目录节点,存储在一个序列中,每次节点序号最小的节点对应的客户端获得锁,处理完成后删除最小节点,而且可重复获取锁(通过判断序号是否和最小的节点相同)。这种方式可以实现阻塞分布式锁,和锁的重复获取问题。
- HashMap和Hashtable的区别
主要的区别有:线程安全性,同步,以及速度。 (1)HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。 HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。 (2)另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。 由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。 HashMap不能保证随着时间的推移Map中的元素次序是不变的。
- HashMap中是否任何对象都可以做为key,用户自定义对象做为key有没有什么要求?
用自定义类作为key,必须重写equals()和hashCode()方法。 自定义类中的equals() 和 hashCode()都继承自Object类。 Object类的hashCode()方法返回这个对象存储的内存地址的编号。 而equals()比较的是内存地址是否相等。
- 对sql进行优化的原则有哪些?
(1)减少返回不必要的数据 (2)减少物理和逻辑读次数 (3)减少计算次数
- String和StringBuffer的区别
1)运行速度:StringBuilder >StringBuffer >String String是字符串常量,不可变,每次改变只是创建一个新的对象,然后GC回收掉老的那个,所以执行速度最慢,另外两个是字符串对象,可变。 (2)线程安全: StringBuilder是线程不安全的,StringBuffer是线程安全的,看是否带synchronized关键字。多线程则采用StringBuffer,单线程则要建议用速度较快的StringBuilder。 (3)String:适用于少量的字符串操作的情况,String是final类,无法被继承。StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况。 StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况。
- 如果A和B对象循环引用,是否可以被GC?
答:这个循环引用是否被回收,就看这个循环引用是否挂在根上,A引用B,B引用A,A和B并没有挂在某个内存元和根上,当他们的生命周期结束的时候。这两个对象都有可能被回收。
- Error、Exception和RuntimeException的区别,作用又是什么?
Error是Throwable 的子类,用于指示合理的应用程序不应该试图捕获的严重问题。大多数这样的错误都是异常条件。虽然 ThreadDeath 错误是一个“正规”的条件,但它也是 Error 的子类,因为大多数应用程序都不应该试图捕获它。在执行该方法期间,无需在其 throws 子句中声明可能抛出但是未能捕获的 Error的任何子类,因为这些错误可能是再也不会发生的异常条件。 Exception类及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件。 RuntimeException是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。可能在执行方法期间抛出但未被捕获的RuntimeException 的任何子类都无需在 throws 子句中进行声明。它是Exception的子类。
- reader和inputstream区别
(1)InputStream是表示字节输入流的所有类的超类;Reader是用于读取字符流的抽象类 (2)InputStream提供的是字节流的读取,而非文本读取,这是和Reader类的根本区别。 即用Reader读取出来的是char数组或者String ,使用InputStream读取出来的是byte数组。
- hashCode的作用;
hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable。 Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。
- Java中的内存溢出是如何造成的?
(1)内存中加载的数据量过于庞大,如一次从数据库取出过多数据; (2)集合类中有对对象的引用,使用完后未清空,使得JVM不能回收; (3)代码中存在死循环或循环产生过多重复的对象实体; (4)使用的第三方软件中的BUG; (5)启动参数内存值设定的过小;
- springMVC的工作原理图
(1)客户端发出一个http请求给web服务器,web服务器对http请求进行解析,如果匹配 DispatcherServlet的请求映射路径(在web.xml中指定), web容器将请求转交给DispatcherServlet; (2)DipatcherServlet接收到这个请求之后将根据请求的信息(包括URL、Http方法、请求报文头和请 求参数Cookie等) 以及HandlerMapping的配置找到处理请求的处理器(Handler); (3)DispatcherServlet根据HandlerMapping找到对应的Handler,将处理权交给Handler(Handler将 具体的处理进行封装), 再由具体的HandlerAdapter对Handler进行具体的调用。 (4)Handler对数据处理完成以后将返回一个ModelAndView()对象给DispatcherServlet; (5)Handler返回的ModelAndView()只是一个逻辑视图并不是一个正式的视图,DispatcherSevlet通过 ViewResolver将逻辑视图转化为真正的视图View; (6)Dispatcher通过model解析出ModelAndView()中的参数进行解析最终展现出完整的view并返回给客户端;
- RPC框架和普通http有什么区别和优势?
基于Tcp封装还是http封装的? (1)1、RPC是一种API,HTTP是一种无状态的网络协议。RPC可以基于HTTP协议实现,也可以直接在TCP协议上实现。 (2)RPC主要是用在大型网站里面,因为大型网站里面系统繁多,业务线复杂,而且效率优势非常重要的一块,这个时候RPC的优势就比较明显了。 (3)HTTP主要是用在中小型企业里面,业务线没那么繁多的情况下。 (4)HTTP开发方便简单、直接。开发一个完善的RPC框架难度比较大。 (5)HTTP发明的初衷是为了传送超文本的资源,协议设计的比较复杂,参数传递的方式效率也不高。开源的RPC框架针对远程调用协议上的效率会比HTTP快很多。 (6)HTTP需要事先通知,修改Nginx/HAProxy配置。RPC能做到自动通知,不影响上游。 (7)HTTP大部分是通过Json来实现的,字节大小和序列化耗时都比Thrift要更消耗性能。RPC,可以基于Thrift实现高效的二进制传输。
- GC的基本原理?什么时候需要GC?为什么需要GC?
GC (Garbage Collection)的基本原理:将内存中不再被使用的对象进行回收,GC中用于回收的方法称为收集器,由于GC需要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、旧生代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停 (1)对新生代的对象的收集称为minor GC; (2)对旧生代的对象的收集称为Full GC; (3)程序中主动调用System.gc()强制执行的GC为Full GC。 不同的对象引用类型, GC会采用不同的方法进行回收,JVM对象的引用分为了四种类型: (1)强引用:默认情况下,对象采用的均为强引用(这个对象的实例没有其他对象引用,GC时才会被回收) (2)软引用:软引用是Java中提供的一种比较适合于缓存场景的应用(只有在内存不够用的情况下才会被GC) (3)弱引用:在GC时一定会被GC回收 (4)虚引用:由于虚引用只是用来得知对象是否被GC
- 怎样避免死锁?
(1)破坏“不可剥夺”条件:一个进程不能获得所需要的全部资源时便处于等待状态,等待期间他占有的资源将被隐式的释放重新加入到 系统的资源列表中,可以被其他的进程使用,而等待的进程只有重新获得自己原有的资源以及新申请的资源才可以重新启动,执行。 (2)破坏”请求与保持条件”:第一种方法静态分配即每个进程在开始执行时就申请他所需要的全部资源。第二种是动态分配即每个进程在申请所需要的资源时他本身不占用系统资源。 (3)破坏“循环等待”条件:采用资源有序分配其基本思想是将系统中的所有资源顺序编号,将紧缺的,稀少的采用较大的编号,在申请资源时必须按照编号的顺序进行,一个进程只有获得较小编号的进程才能申请较大编号的进程
- BEAN的生命周期
(1)应用启动的时候检查加载需要被Spring管理的bean. (2)根据实现的接口,依次设置beanName,BeanFactory,ApplicationContext应用上下文。 (3)根据实现的接口,依次调用加载前,设置值,自定义初始化方法,加载完成后。 (4)bean已经可以用了,存活直到上下文也被销毁。 (5)销毁的时候调用destroy方法和自定义的销毁方法。