栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

备战秋招2022/3/28

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

备战秋招2022/3/28

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

1、详细说说类加载器

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

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

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

这个类加载器是在类sum.misc.Launcher$ExtClassLoader中以Java代码的形式实现的。它负责加载libext目录中,或者被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。

从上面我们可以看出:synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 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 方法");
    }
}

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

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

总结:

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

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

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

答案参考javaguide

衍生问题:

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

3、MyISAM和InnoDB的区别

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

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

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

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

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

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

答案参考知乎:

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

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

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

基于内存实现

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

高效的数据结构

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

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

SDS简单动态字符串

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

为了保持高效,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功能可以实现冷热数据分离,使热数据仍在内存中、冷数据保存到磁盘。这样就可以避免因为内存不足而造成访问速度下降的问题。

答案整理参考博客地址:

https://blog.csdn.net/hollis_chuang/article/details/118865058https://www.cnblogs.com/ysocean/p/9102811.html#_label0https://www.codenong.com/cs106843764/

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

不是的

举例说明:

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

三次握手过程

三次握手(Three-way Handshake)指客户端和服务器建立一个TCP连接时,双方总共需要发送3个报文段。目的:

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

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

    第一次握手:客户端给服务端发一个 连接请求报文段,头部指明SYN=1,以及初始化序列号 ISN(seq=x)。此报文段不能携带数据,但要消耗掉一个序号。随后客户端进入 SYN_SENT (同步发送)状态。第二次握手:服务端收到客户端的 连接请求报文段之后,向客户端发送连接确认报文段,头部指明SYN=1,ACK=1,确认号(ack)为x+1,并且也选择一个初始化序列号y。随后服务器进入 SYN_RCVD (同步接收)的状态。第三次握手:客户端收到服务端的 连接确认报文段之后,会向服务端回送一个确认报文段,头部指明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=1,ACK=1,序号seq=w,确认号ack=u+1,随后服务端进入LAST_ACK(最后确认)状态,等待客户端的确认报文段。第四次挥手:客户端收到连接释放报文段之后,同样向服务端发出确认报文段,头部指明ACK=1,seq=u+1,ack=w+1,此时客户端进入 TIME_WAIT 状态。服务端收到客户端的确认报文段之后,进入 CLOSED 状态。客户端必须经过 2*MSL 后才进入 CLOSED 状态。此时TCP连接已经完全释放。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/786011.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号