自我介绍

在实习过程中的收获是什么

对于 IO 通信/IO 多路复用模型 有了解吗

IO 多路复用的话,一开始 TCP 的连接是一对一的通信。但一对一的通信效率并不高,所以就对他做了一些改进,比如多进程或多线程:就相当于 一个 TCP 请求对应一个进程,虽然连接数上来了,但是并发量达不到很大,而且服务器的负载也很高。从整体来看,服务器的运行瓶颈主要在于 IO,CPU 的运行效率比 IO 快的多。所以后面就诞生了 IO 多路复用:用一个单线程同时去处理多个 IO 的请求,而且单线程相比于多线程的一个好处就是不存在线程安全的问题,减少了线程的创建和销毁开销。通俗的讲 就好比我们去点餐,这里面分为两步,① 用户思考要吃什么,也就是等待数据 ② 想好了,开始点餐,就是读取数据。服务员在用户点单的过程中是阻塞的,其他用户要等我们点完单后才能叫服务员去点单。解决方案有两种:一、增加服务员(多线程),二、不排队,那个用户想好吃什么了,服务员在去那里点单。那么问题来了,用户进程如何知道内核中的数据是否就绪呢?也就是说 IO 多路复用是如何实现的?在这里面有一个定义 FD 文件描述符 他关联了 Linux 中的一个文件。最原始的实现就是 select。用户进程调用 select,指定监听 FD 集合,里面包含了多个 socket。当一个或多个 socket 数据就绪,则会返回 readable 给用户态。用户态就去找到对应的 socket,反复调用 recvfrom ,将数据从内核态复拷贝到用户态,最后处理数据。
select(数组) 和 poll(链表) 都是这么做的,缺点是只会通知用户进程有 FD 就绪,但不能具体定位到那个 FD,所以就要进行遍历来确定。epoll(红黑树) 就是对上面的一种优化,当 FD 就绪的同时,会将已就绪的 FD 写入用户空间。就省略了遍历的步骤,效率提升更大。

epoll_wait 的边缘触发和水平触发 了解吗

水平触发:有 FD 就绪时,重复通知直到数据处理完成,默认方式
边缘触发:有 FD 就绪时,通知一次,直到下次有新的数据来,才会再次通知。

当一段数据到达计算机的网卡,接下来计算机会做哪些事情

首先会经过数据链路层,去过滤掉不匹配的 MAC 地址,之后传输到网络层检查 IP 地址看看是不是本机,然后再到传输层根据 IP 的协议看看是 TCP 还是 UDP 交给相应的协议处理,最后在根据端口号去交给对应的进程处理数据。这里还设计到用户态到内核态的数据拷贝,首先使用 DMA 将数据从网卡拷贝到内核态,在 cpu 拷贝将内核态的数据拷贝到用户态。

cpu 的软中断和硬中断了解吗

软中断就是不会抢占 cpu,硬中断就是立即抢占 cpu

从用户态到内核态有什么方式减少拷贝 –> 引申出一个自己的思考:为什么使用 0 拷贝技术后,还有两次数据拷贝,既然可以使用 DMA 将磁盘文件拷贝到内核态,再从内核态拷贝到网卡。那为什么不直接将磁盘文件使用 DMA 拷贝到网卡呢?

减少拷贝的话可以使用零拷贝技术,在传统的情况下从网卡拷贝到内核态,再从内核态拷贝到用户态里面存在两次拷贝:DMA 拷贝和 cpu 拷贝。再这里可以使用一个共享缓冲区,就是用户态和内核态的数据是共享的,减少了一次内核态到用户态的数据拷贝。

JVM 都有哪些区域? 在问的时候一定要分清楚:内存结构和内存模型

内存结构是:程序计数器、虚拟机栈、本地方法栈、堆、方法区。程序计数器、虚拟机栈、和本地方法栈都属于线程私有的部分,堆和方法区是线程共享的

哪些区域会 OOM 呢

有 OOM 问题的就是栈、堆和方法区,栈的话它里面每调用一个方法就有一个栈帧,如果这个方法是一个循环调用的话,很容易出现 OOM 问题。堆的话所有的对象都是在堆里面分配内存的,如果 new 的对象太多也会造成 OOM 的问题。还有一个就是方法区,他是一个概念上的模型,具体的实现在 jdk1.7 前使用的是堆内存,1.8 用的是本地内存,它就会存储一些类的 Class 信息、或静态常量,如果存的信息过多,占用的堆内存也会有 OOM 问题。

一个对象在上面场景下会从新生代转移到老年代

首先对象从新生代到老年代会有一个垃圾收集的一个过程,新生代又分为一个伊甸园和两个幸存区。新 new 的对象会在伊甸园分配内存,当伊甸园的内存满了后,会触发 minioGC,将对象全部放到幸存区,每次 minioGC 幸存区中还存获得对象他的年龄会+1,当对象的年龄达到一定的次数比如说 15 次后,这个对象就会从新生代转移到老年代。还有一种情况就是创建的大对象也会直接放到老年代中。还有一种就是 MinorGC 后存活的对象过多并且幸存区无法容纳所有的对象,那么多余的对象直接进入老年代。

内存担保机制

内存担保机制就是,确保老年代有足够的空间容纳从新生代晋升的对象,如果老年代的内存不足,并开启了内存担保机制的话,就不会先进行 FullGC 而是 MinorGC,如果 minorGC 后确实内存确实不够了,那就触发 OOM。

写一个单例的 demo

单例:① 构造方法私有,提供一个类方法获取对象。
② 线程安全的实现:使用静态内部类,在类加载时就会初始化。

有 5000 万行记录的一张表,每行数据占 1k,主键是 long 类型的 id,求 B+树的高度

求树高度:以 MySQL 的 InnoDB 为例,索引按页(16KB)查询,行数据大小 1KB
非叶子节点保存主键(8 字节)+子节点指针(6 字节)=14 字节。每页(16KB)可以存储 16 KB/14 字节=1170 个键值对—>非叶子节点的最大分支为 1170
叶子节点按页保存实际数据行(1KB)。 每页(16KB)可以存储 16 KB/1 KB=16 行数据。5000 万行数据需要 50000000 行/16 行=3125000 页树高由非叶子节点层数和叶子节点层数共同决定的:1 层 =>1170 叶子节点、2 层 =>1170*1170 叶子节点、3 层 => 1170*1170 ***** 1170 个叶子节点。实际叶子节点个数3125000<<< 117011701170 由此推出树高为 3 层

表中共有三个字段:id(主键)、number(普通索引)。执行 delete from 表 where number=10,该语句会加哪些锁?

数据库锁:在默认的隔离级别下,number 会加上临间锁,对所有满足的记录 和 他们之间的间隙加锁,主键也会加上行级排他锁,因为使用普通索引操作行数据最终会有回表操作,要根据主键来删除记录。为了防止其他线程插入数据,所以要对主键加上排他锁。

磁盘文件上有 20 亿个整型数据,如何对他做排序

方案一:使用位图,若每个元素都唯一,可以创建一个最大数的 bitmap,每个数在位图的下标上标记为 1,这样遍历完即可
方案二:多路归并,归并排序也就叫 2 路归并,这其实就升级为了 k。

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Demo {
// 1. 静态私有实例
private static Demo instance;
// 2. 私有构造函数,防止外部直接创建实例
private Demo() {
// 初始化操作
}
// 3. 公共静态方法,提供全局访问点
public static Demo getInstance() {
if (instance == null) { // 第一次调用时创建实例
instance = new Demo();
}
return instance;
}
}

1
2
3
4
5
6
7
8
class Demo{
private static class Single{
private static final Demo INSTANCE =new Demo();
}
public static Demo getInstance(){
return Single.INSTANCE;
}
}