备战秋招2022/3/28

过分高估自己了,每日5题,完成不了,时间不够,因为对每一个知识点深入查缺补漏,然后记住需要的时间很多

1、详细说说类加载器

启动类加载器(Bootstrap Class Loader):

启动类加载器负责加载存放在<JAVA HOME>\lib目录,或者被Xbootclasspath参数所指定的路径中存放的,而且是java虚拟机能够识别的(按照文件名识别,如rt.jar、tools.jar,名字不符合的类库即使放在lib目录也不会被加载)类库加载到虚拟机的内存中。启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器的时候,如果需要把加载请求委派给启动类加载器取处理,那直接使用null代替即可

扩展类加载器(Extension Class Loader):

这个类加载器是在类sum.misc.Launcher$ExtClassLoader中以Java代码的形式实现的。它负责加载<JAVA_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库

应用程序类加载器(Application Class Loader):

这个类加载器由sum.miscLauncher$AppClassLoader来实现。由于应用程序类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以有些场合也称它位“系统类加载器”。它负责加载用户类路径(ClassPath)上所有的类库,开发者同样可以直接在代码中使用这个类加载器。如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中的默认类加载器。

2、Synchronize关键字的底层原理

synchronized 关键字底层原理属于 JVM 层面。

第一种情况:synchronized 同步语句块的情况

public class SynchronizedDemo {
    public void method() {
        synchronized (this) {
            System.out.println("synchronized 代码块");
        }
    }
}

通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息:首先切换到类的对应目录执行 javac SynchronizedDemo.java 命令生成编译后的 .class 文件,然后执行javap -c -s -v -l SynchronizedDemo.class

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cEwVBLML-1648456692109)(每日5题.assets/1648259104332.png)]

从上面我们可以看出:synchronized 同步语句块的实现使用的是 monitorentermonitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。

当执行 monitorenter 指令时,线程试图获取锁也就是获取 对象监视器 monitor 的持有权。

在 Java 虚拟机(HotSpot)中,Monitor 是基于 C++实现的,由ObjectMonitor实现的。每个对象中都内置了一个 ObjectMonitor对象。

另外,wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

在执行monitorenter时,会尝试获取对象的锁,如果锁的计数器为 0 则表示锁可以被获取,获取后将锁计数器设为 1 也就是加 1。

在这里插入图片描述

如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。

第二种情况:synchronized 修饰方法的的情况

public class SynchronizedDemo2 {
    public synchronized void method() {
        System.out.println("synchronized 方法");
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h4Sv7bp6-1648456692110)(每日5题.assets/1648259554667.png)]

synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。

如果是实例方法,JVM 会尝试获取实例对象的锁。如果是静态方法,JVM 会尝试获取当前 class 的锁。

总结:

synchronized 同步语句块的实现使用的是 monitorentermonitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。

synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。

不过两者的本质都是对对象监视器 monitor 的获取。

答案参考javaguide

衍生问题:

  • synchronized的原理(从ObjectMonitor角度分析)

3、MyISAM和InnoDB的区别

  1. InnoDB 支持事务,MyISAM 不支持事务。这是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一;

  2. InnoDB 支持外键,而 MyISAM 不支持。对一个包含外键的 InnoDB 表转为 MYISAM 会失败;

  3. InnoDB 是聚簇索引,MyISAM 是非聚簇索引。聚簇索引的文件存放在主键索引的叶子节点上,因此 InnoDB 必须要有主键,通过主键索引效率很高。但是非聚簇需要两次查询,先查询到主键,然后再通过主键查询到数据。而 MyISAM 是非聚集索引,值得注意的是数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。

    InnoDB的非聚簇索引data域存储相应记录 主键的值 ,而MyISAM索引记录的是 地址

  4. InnoDB 不保存表的具体行数,执行 select count(*) from table 时需要全表扫描。而MyISAM 用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快;

  5. InnoDB 最小的锁粒度是行锁,MyISAM 最小的锁粒度是表锁。一个更新语句会锁住整张表,导致其他查询和更新都会被阻塞,因此并发访问受限。这也是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一;

答案参考知乎:

https://www.zhihu.com/question/20596402

4、为什么Redis比较快,单线程就一定快吗?

第一个问题为什么redis这么快

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cNH3EsJI-1648456692111)(每日5题.assets/1648435367203.png)]

  • 基于内存实现

我们都知道内存读写是比磁盘读写快很多的。Redis是基于内存存储实现的数据库,相对于数据存在磁盘的数据库,就省去磁盘磁盘I/O的消耗。MySQL等磁盘数据库,需要建立索引来加快查询效率,而Redis数据存放在内存,直接操作内存,所以就很快。

  • 高效的数据结构

我们知道,MySQL索引为了提高效率,选择了B+树的数据结构。其实合理的数据结构,就是可以让你的应用/程序更快。

redis设计到的数据结构如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TvBJpsoD-1648456692111)(每日5题.assets/1648435534353.png)]

SDS简单动态字符串

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cGjlztup-1648456692111)(每日5题.assets/1648435753798.png)]

1、常数复杂度获取字符串长度

由于 len 属性的存在,我们获取 SDS 字符串的长度只需要读取 len 属性,时间复杂度为 O(1)。而对于 C 语言,获取字符串的长度通常是经过遍历计数来实现的,时间复杂度为 O(n)

2、减少修改字符串的内存重新分配次数

在C语言中,修改一个字符串,需要重新分配内存,修改越频繁,内存分配就越频繁,而分配内存是会消耗性能的。而在Redis中,SDS提供了两种优化策略:空间预分配和惰性空间释放。

  • 空间预分配:对字符串进行空间扩展的时候,扩展的内存比实际需要的多,这样可以减少连续执行字符串增长操作所需的内存重分配次数。
  • 惰性空间释放:当SDS缩短时,不是回收多余的内存空间,而是用free记录下多余的空间。后续再有修改操作,直接使用free中的空间,减少内存分配。

哈希

Redis 作为一个K-V的内存数据库,它使用用一张全局的哈希来保存所有的键值对。这张哈希表,有多个哈希桶组成,哈希桶中的entry元素保存了*key*value指针,其中*key指向了实际的键,*value指向了实际的值。

在这里插入图片描述

哈希表查找速率很快的,有点类似于Java中的HashMap,它让我们在O(1) 的时间复杂度快速找到键值对。首先通过key计算哈希值,找到对应的哈希桶位置,然后定位到entry,在entry找到对应的数据。

Redis为了解决哈希冲突,采用了链式哈希。链式哈希是指同一个哈希桶中,多个元素用一个链表来保存,它们之间依次用指针连接。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aT6sl6i8-1648456692112)(每日5题.assets/1648436232281.png)]

为了保持高效,Redis 会对哈希表做rehash操作,也就是增加哈希桶,减少冲突。为了rehash更高效,Redis还默认使用了两个全局哈希表,一个用于当前使用,称为主哈希表,一个用于扩容,称为备用哈希表。

跳跃表

跳跃表是Redis特有的数据结构,它其实就是在链表的基础上,增加多级索引,以提高查找效率。跳跃表的简单原理图如下

在这里插入图片描述

  • 每一层都有一条有序的链表,最底层的链表包含了所有的元素。
  • 跳跃表支持平均 O(logN),最坏 O(N)复杂度的节点查找,还可以通过顺序性操作批量处理节点。

压缩列表ziplist

压缩列表ziplist是列表键和字典键的的底层实现之一。它是由一系列特殊编码的内存块构成的列表, 一个ziplist可以包含多个entry, 每个entry可以保存一个长度受限的字符数组或者整数,如下:

在这里插入图片描述

由于内存是连续分配的,所以遍历速度很快

各个字段详细解释见redis笔记

  • 合理的数据编码

Redis支持多种数据基本类型,每种基本类型对应不同的数据结构,每种数据结构对应不一样的编码。为了提高性能,Redis设计者总结出,数据结构最适合的编码搭配。

Redis是使用对象(redisObject)来表示数据库中的键值,当我们在 Redis 中创建一个键值对时,至少创建两个对象,一个对象是用做键值对的键对象,另一个是键值对的值对象。

typedef struct redisObject{
     //类型
     unsigned type:4;
     //编码
     unsigned encoding:4;
     //指向底层数据结构的指针
     void *ptr;
     //引用计数
     int refcount;
     //记录最后一次被程序访问的时间
     unsigned lru:22;
}robj

redisObject中,type 对应的是对象类型,包含String对象、List对象、Hash对象、Set对象、zset对象。encoding 对应的是编码。

  • String:如果存储数字的话,

    int 编码:保存的是可以用 long 类型表示的整数值。

    raw 编码:保存长度大于44字节的字符串(redis3.2版本之前是39字节,之后是44字节)。

    embstr 编码:保存长度小于44字节的字符串(redis3.2版本之前是39字节,之后是44字节)。

  • List:

    当同时满足下面两个条件时,使用ziplist(压缩列表)编码:

    1、列表保存元素个数小于512个

    2、每个元素长度小于64字节

    不能满足这两个条件的时候使用 linkedlist 编码。

  • Hash:

    当同时满足下面两个条件时,使用ziplist(压缩列表)编码:

    1、列表保存元素个数小于512个

    2、每个元素长度小于64字节

    不能满足这两个条件的时候使用 hashtable 编码

  • Set:

    当集合同时满足以下两个条件时,使用 intset 编码:

    1、集合对象中所有元素都是整数

    2、集合对象所有元素数量不超过512

    不能满足这两个条件的就使用 hashtable 编码。

  • Zset:

    当有序集合对象同时满足以下两个条件时,对象使用 ziplist 编码:

    1、保存的元素数量小于128;

    2、保存的所有元素长度都小于64字节。

    不能满足上面两个条件的使用 skiplist 编码。

  • 合理的线程模型

单线程模型:避免了上下文切换

Redis是单线程的,其实是指Redis的网络IO和键值对读写是由一个线程来完成的。但Redis的其他功能,比如持久化、异步删除、集群数据同步等等,实际是由额外的线程执行的。

Redis的单线程模型,避免了CPU不必要的上下文切换和竞争锁的消耗。也正因为是单线程,如果某个命令执行过长(如hgetall命令),会造成阻塞。Redis是面向快速执行场景的内存数据库,所以要慎用如lrange和smembers、hgetall等命令。

I/O 多路复用

多路I/O复用技术可以让单个线程高效的处理多个连接请求,而Redis使用用epoll作为I/O多路复用技术的实现。并且Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间。

  • 虚拟内存机制

Redis的VM(虚拟内存)机制就是暂时把不经常访问的数据(冷数据)从内存交换到磁盘中,从而腾出宝贵的内存空间用于其它需要访问的数据(热数据)。通过VM功能可以实现冷热数据分离,使热数据仍在内存中、冷数据保存到磁盘。这样就可以避免因为内存不足而造成访问速度下降的问题。

答案整理参考博客地址:

第二个问题单线程就一定快吗?

不是的

举例说明:

  • 当你往 Redis 中写入大量数据后,就可能发现操作有时候会突然变慢了,可能是由于哈希表的冲突问题和 rehash 可能带来的操作阻塞。(当然redis为了解决这个问题提出了相应的解决方案)
  • 我们在进行AOF日志和RDB日志文件的时候就可能由于数据量过大导致fork子进程的时候导致主线程阻塞,从而让其他操作变慢

5、三次握手四次挥手过程

三次握手过程
在这里插入图片描述
三次握手(Three-way Handshake)指客户端和服务器建立一个TCP连接时,双方总共需要发送3个报文段。目的:

  • 确认双方的接收能力和发送能力是否正常
  • 同时指定双方的初始化序列号(ISN)为后面的可靠性传送做准备

刚开始服务端处于监听状态,进行三次握手由客户端主动发起:

  1. 第一次握手:客户端给服务端发一个 连接请求报文段,头部指明SYN=1,以及初始化序列号 ISNseq=x)。此报文段不能携带数据,但要消耗掉一个序号。随后客户端进入 SYN_SENT (同步发送)状态。
  2. 第二次握手:服务端收到客户端的 连接请求报文段之后,向客户端发送连接确认报文段,头部指明SYN=1ACK=1,确认号(ack)为x+1,并且也选择一个初始化序列号y。随后服务器进入 SYN_RCVD (同步接收)的状态。
  3. 第三次握手:客户端收到服务端的 连接确认报文段之后,会向服务端回送一个确认报文段,头部指明ACK=1,确认号ack=y+1,序号seq=x+1,该报文段可以携带数据,不携带数据则不消耗序号。随后客户端进入 ESTABLISHED (连接已建立)状态。待服务器收到客户端发送的 ACK 报文段也会进入 ESTABLISHED 状态,完成三次握手。

四次挥手的过程
在这里插入图片描述
由于客户端或服务端均可主动发起挥手动作,因此这里称主动方和被动方。四次挥手(Four-way handshake)指主动方和和被动方断开 TCP连接需要发送四个包报文段。挥手前,双方都处于ESTABLISHED 状态,假如是客户端先发起关闭请求,对应过程如下:

  • 第一次挥手:客户端向服务端发送一个连接释放报文段,头部指明FIN=1,序号seq=u。并停止发送数据,主动关闭TCP连接。随后客户端进入 FIN_WAIT1 (终止等待1)状态,等待服务端的确认。
  • 第二次挥手:服务端收到客户端发来的连接释放报文段后,回送 确认报文段,头部指明ACK=1,确认号ack=u+1,序号seq=v,随后服务端进入 CLOSE_WAIT(关闭等待) 状态。客户端收到服务端的确认报文段后,进入FIN_WAIT2(终止等待2)状态,等待服务端发出的连接释放报文段
  • 第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,向客户端发送连接释放报文段,头部指明FIN=1ACK=1,序号seq=w,确认号ack=u+1,随后服务端进入LAST_ACK(最后确认)状态,等待客户端的确认报文段
  • 第四次挥手:客户端收到连接释放报文段之后,同样向服务端发出确认报文段,头部指明ACK=1seq=u+1ack=w+1,此时客户端进入 TIME_WAIT 状态。服务端收到客户端的确认报文段之后,进入 CLOSED 状态。客户端必须经过 2*MSL 后才进入 CLOSED 状态。此时TCP连接已经完全释放。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值