2024.5.13腾讯云后端开发面试

  • 腾讯
  • 一面
大约 10 分钟全民制作人ikun

2024.5.13腾讯云后端开发面试

1、线程池 七个参数 如果任务队列无界的话 则最大线程数还用得到吗

/**
 * 用给定的初始参数创建一个新的ThreadPoolExecutor。
 */
public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量
                          int maximumPoolSize,//线程池的最大线程数
                          long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间
                          TimeUnit unit,//时间单位
                          BlockingQueue<Runnable> workQueue,//任务队列,用来储存等待执行任务的队列
                          ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可
                          RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务
                           ) {
}

如果任务队列是无界的(使用 LinkedBlockingQueue 无参构造函数)的话,那么 maximumPoolSize 就不再重要了,因为:

  • 当有新任务来时,线程池首先会尝试将任务添加到无界队列中。
  • 如果队列已满,才会尝试创建新线程。
  • 但由于队列无界,所以永远不会出现队列满的情况,因此 maximumPoolSize 也就不会生效了。

2、Java内存模型

JMM(Java 内存模型)主要定义了对于一个共享变量,当另一个线程对这个共享变量执行写操作后,这个线程对这个共享变量的可见性。

JMM 定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读 / 写共享变量的副本。

2、类加载过程(双亲委派机制)

类加载过程

  1. 加载(Loading): 加载是指将类的字节码文件加载到内存中。在加载阶段,JVM会完成以下几件事情:
    • 通过类的全限定名获取类的二进制字节流。
    • 将字节流代表的静态存储结构转化为方法区的运行时数据结构。
    • 在内存中生成一个代表该类的 Class 对象,作为方法区这个类的各种数据的访问入口。
  1. 链接(Linking): 链接阶段包括三个步骤:验证、准备和解析。
    • 验证(Verification):确保加载的类符合 JVM 规范,比如检查字节码是否符合规范、类是否有父类等。
    • 准备(Preparation):为类的静态变量分配内存,并将其初始化为默认值。
    • 解析(Resolution):将符号引用转换为直接引用,即将常量池中的符号引用替换为直接指针,确保各个类之间的关联正常。
  1. 初始化(Initialization): 初始化阶段是类加载过程的最后一步,在这个阶段,JVM会执行类构造器 <clinit>方法的过程。这个方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。当类被主动使用时,JVM才会进行类的初始化,例如实例化对象、访问类的静态方法或静态字段。

双亲委派机制:一个类加载器需要加载一个类时,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最 终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无 法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。优点如下:

  • 避免重复加载
  • 安全

3、新生代(Ed、FS、TS)、老生代(垃圾回收:CMS)

Java 的对象是在堆中创建的,但堆又分为新生代(存放使用后就要被回收的对象)和老年代(存放生命周期比较长的对象),新生代又细分为 Eden、From Survivor、To Survivor。

  • 随着对象的不断创建,Eden 剩余地内存空间就会越来越少,随后就会触发 Minor GC,于是 JVM 会把 Eden 区存活的对象转入 From Survivor 空间。

  • 触发 Minor GC的时候,JVM 会对 Eden 区和 From Survivor 区中的对象进行存活判断,对于存活的对象,会转移到 To Survivor 区。

  • 下一次 Minor GC,存活的对象又会从 To 到 From,这样就总有一个 Survivor 区是空的,而另外一个是无碎片的。

大对象直接进入老年代:如果一个对象很大,一直在 Survivor 空间复制来复制去,就会很浪费性能,所以这些大对象会直接进入老年代。

长期存活的对象将进入老年代:对象在每次从一个 Survivor 区转移到另外一个 Survivor 区时,它的年龄就会增加。当对象的年龄达到一定阈值(默认为 15),则它会被转移到老年代。

javaguideopen in new window

CMS 收集器是一种 “标记-清除”算法实现的,整个过程分为四个步骤:

  • 初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;
  • 并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
  • 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
  • 并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。

4、mysql的存储引擎 (innodb)

  1. InnoDB 支持事务MyISAM 不支持;

  2. InnoDB 支持外键;

  3. InnoDB 是聚集索引,MyISAM 是非聚集索引等。

这是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一

5、mysql 的主键索引 和 唯一索引 (覆盖索引)

  • 主键索引:聚簇索引,也就是说数据行本身存储在主键索引的叶子节点上
  • 唯一索引:索引中的所有值都互不相同,允许有 NULL 值
  • 覆盖索引:查询语句的所有列都包含在索引中,这样可以直接从索引中获取数据,不需要再访问数据行。避免了"回表"操作

6、索引的数据结构 hash 平衡树 B+树 B树

  • Hash查询O(1),但是不适合范围查询
  • 平衡树查询O(logn),但是不适合范围查询,并且树高度相对B树较高
  • B树不适合范围查询,并且非叶子结点也要存储数据,占用大
  • B+树,innodb的索引结构,可以范围查询,高度3-4,磁盘IO少。

7、HTTP2 与 HTTP3区别

Javaguideopen in new window

  • 传输协议:HTTP/2.0 是基于 TCP 协议实现的,HTTP/3.0 新增了 QUIC(Quick UDP Internet Connections) 协议来实现可靠的传输,提供与 TLS/SSL 相当的安全性,具有较低的连接和传输延迟。你可以将 QUIC 看作是 UDP 的升级版本,在其基础上新增了很多功能比如加密、重传等等。HTTP/3.0 之前名为 HTTP-over-QUIC,从这个名字中我们也可以发现,HTTP/3 最大的改造就是使用了 QUIC。
  • 连接建立:HTTP/2.0 需要经过经典的 TCP 三次握手过程(由于安全的 HTTPS 连接建立还需要 TLS 握手,共需要大约 3 个 RTT)。由于 QUIC 协议的特性(TLS 1.3,TLS 1.3 除了支持 1 个 RTT 的握手,还支持 0 个 RTT 的握手)连接建立仅需 0-RTT 或者 1-RTT。这意味着 QUIC 在最佳情况下不需要任何的额外往返时间就可以建立新连接。
  • 队头阻塞:HTTP/2.0 多请求复用一个 TCP 连接,一旦发生丢包,就会阻塞住所有的 HTTP 请求。由于 QUIC 协议的特性,HTTP/3.0 在一定程度上解决了队头阻塞(Head-of-Line blocking, 简写:HOL blocking)问题,一个连接建立多个不同的数据流,这些数据流之间独立互不影响,某个数据流发生丢包了,其数据流不受影响(本质上是多路复用+轮询)。
  • 错误恢复:HTTP/3.0 具有更好的错误恢复机制,当出现丢包、延迟等网络问题时,可以更快地进行恢复和重传。而 HTTP/2.0 则需要依赖于 TCP 的错误恢复机制。
  • 安全性:HTTP/2.0 和 HTTP/3.0 在安全性上都有较高的要求,支持加密通信,但在实现上有所不同。HTTP/2.0 使用 TLS 协议进行加密,而 HTTP/3.0 基于 QUIC 协议,包含了内置的加密和身份验证机制,可以提供更强的安全性。

8、为什么要用kafka

Kafka 是一个分布式流式处理平台。流平台具有三个关键功能:

  1. 消息队列:发布和订阅消息流,这个功能类似于消息队列,这也是 Kafka 也被归类为消息队列的原因。
  2. 容错的持久方式存储记录消息流:Kafka 会把消息持久化到磁盘,有效避免了消息丢失的风险。
  3. 流式处理平台: 在消息发布的时候进行处理,Kafka 提供了一个完整的流式处理类库。

Kafka 主要有两大应用场景:

  1. 消息队列:建立实时流数据管道,以可靠地在系统或应用程序之间获取数据。
  2. 数据处理: 构建实时的流数据处理程序来转换或处理数据流。

9、JVM锁机制 怎么样判断一个对象有没有被锁住

为了判断一个对象是否被锁着,我们可以尝试获取该对象的锁。如果能够成功获取到锁,说明对象没有被锁着;反之,如果获取锁失败,则说明对象已经被锁定。

10、undolog binlog redolog是干啥的 执行顺序

  • undolog回滚日志,记录事务开始前的状态,记录相反操作,用于实现事务回滚,保证原子性。实现MVCC。
  • redolog重做日志,记录事务完成后的状态,物理日志记录,用于断电恢复
  • binlog归档日志,记录数据库所有变更,实现主从复制,数据库备份

顺序:

  1. 事务开始 -> 将修改前的数据写入undolog
  2. 执行事务操作,修改数据
  3. 事务提交 -> 将修改后的数据写入redolog和binlog

算法:买卖股票

121. 买卖股票的最佳时机open in new window

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n=prices.size();
        vector<int> v(n+100);
        for(int i=n-1;i>=0;i--) v[i]=max(prices[i],v[i+1]);
        int res=-1;
        for(int i=0;i<n;i++) res=max(res,v[i]-prices[i]);
        return res;
    }
};