阿里云3.18最新实习后端面经解析:top 1的云难名不虚传

嗨~我是可拟雀,一个后端开发工程师,毕业于某985大学,目前供职于bat某大厂核心部门后端。每天分享最新面经答案,希望在大环境不好的当下能帮到你,让你多积累面试经验。需要内推或者面经合集请评论哦。

讲一下map,讲一下HashMap吧

答:Map是一种专门用来搜索的数据结构,它经常用来存放键值对。Map允许我们在查找的时候进行插入和删除操作,这使得它在处理这类任务时非常高效。Map的使用与其实例的子类有关,因此不同的Map实现可能会有不同的搜索效率。

HashMap是Map接口的一个实现类,它主要适用于插入、删除和定位元素的操作。HashMap基于哈希表实现,它利用哈希函数(如hashCode())将键(key)转化为数组的索引(index),然后根据这个索引来存储和查找键值对。这种机制使得HashMap在插入、删除和定位元素时具有非常高的效率。

然而,HashMap并不保证键的顺序,也就是说,遍历HashMap可能会得到不同的顺序结果,除非在特定的构造器中进行排序。如果需要有序的遍历,那么可能会选择其他类型的Map,比如TreeMap。

当不同的键通过哈希函数运算得到相同的索引时,就会发生哈希碰撞(或哈希冲突)。HashMap通过链表或红黑树(在Java 8及以后的版本中,当链表长度超过一定阈值时,链表会转化为红黑树)来解决这种冲突,这种策略被称为拉链法。

你说到了链表长度为8的时候会转换为红黑树,为什么是8呢?

答:当HashMap中的某个桶(bucket)的链表长度达到8时,会触发树化(treeifyBin)操作,将链表转换为红黑树。这是为了优化性能,因为红黑树的查找、插入和删除操作的时间复杂度都是O(log n),而链表在长度较长时的这些操作的时间复杂度为O(n)。当桶中的元素数量较少时,链表操作已经足够高效,因此没有必要转换为红黑树。

另外,链表长度超过8并不是唯一触发树化的条件。还有一个条件是桶的总数超过64。也就是说,如果HashMap中的桶的总数超过64,并且某个桶的链表长度达到8,那么链表就会转换为红黑树。这是为了避免过多的树化操作,因为红黑树的空间占用相对于链表来说要大一些。

至于为什么选择8和64这两个数字,它们是通过实验和性能测试得出的经验值。这些值在大多数情况下都能提供较好的性能和空间使用效果。当然,具体的实现可能会因Java版本的不同而有所差异,但背后的设计思想和权衡是相似的。

HashMap为什么线程不安全?

答:扩容:当HashMap中的元素数量超过其容量时,会触发扩容操作。这个操作涉及到生成新的数组、重新计算原有键值对的位置,并写入新的数组。如果在多线程环境下,多个线程同时检测到需要扩容并尝试执行resize操作,这可能导致数据不一致、死循环或死锁。例如,某些线程可能已经完成赋值,而其他线程刚开始赋值,这样就可能用已经被赋值的table作为原始数组,从而导致问题。

put和get操作的非原子性:在HashMap中,put和get操作并不是原子性的。这意味着在多线程环境下,一个线程可能正在执行put操作(包括计算哈希值、确定索引和存储键值对等步骤),而另一个线程可能同时执行get操作,这可能导致get到的值并不是最新的或者预期的值。

删除操作的问题:当在HashMap中删除某个键值对时,实际上是删除了该键值对在内部数组中的对应位置。如果有多个线程同时尝试删除不同的键值对,它们可能会相互干扰,导致数据不一致。

线程池参数有哪些?

答:线程池的参数主要包括以下几个方面:

corePoolSize:线程池中的常驻核心线程数。这些线程在创建线程池后就会立即启动,即使它们当前并没有任务执行。

maximumPoolSize:线程池能够容纳同时执行的最大线程数。当工作队列已满,并且正在执行的线程数达到这个值时,线程池就不会再创建新的线程。这个值必须大于等于1。

keepAliveTime:多余的空闲线程的存活时间。当线程池中的线程数超过corePoolSize,并且这些多余的线程在keepAliveTime这段时间内没有执行任务,那么它们就会被销毁,直到线程池中的线程数减少到corePoolSize为止。

unit:keepAliveTime的单位,比如可以是TimeUnit.SECONDS、TimeUnit.MILLISECONDS等。

workQueue:任务队列。当线程池中的线程都在忙时,新提交的任务会存放在这个队列中等待执行。

threadFactory:线程工厂,用于创建新的线程。一般情况下,我们可以使用默认的线程工厂,但如果需要自定义线程的创建方式,比如设置线程名、设置线程优先级等,就需要使用自定义的线程工厂。

handler:拒绝策略。当队列已满,并且线程池中的线程数已达到maximumPoolSize时,如果还有新的任务提交,就需要根据这个策略来处理这个新任务。常见的拒绝策略有:直接抛出异常、使用调用者的线程来运行任务、丢弃这个任务、丢弃队列中最老的任务然后尝试提交新任务等。

怎么关闭线程池?

答:在Java中,关闭线程池通常使用ExecutorService接口的shutdown()shutdownNow()方法。ExecutorService是Java并发包(java.util.concurrent)中的一个关键接口,它定义了一系列用于管理线程池的方法。

  1. shutdown() 方法:这个方法会启动线程池的关闭序列。线程池不再接受新的任务,但是会等待所有已提交的任务执行完毕。这是优雅地关闭线程池的方式,因为它确保所有任务都能得到执行。
  2. shutdownNow() 方法:这个方法尝试立即停止所有正在执行的任务,停止处理正在等待的任务,并返回等待执行的任务列表。这个方法并不保证能立即停止所有任务,因为有些任务可能正在执行关键操作,不能被立即中断。使用shutdownNow()可能会导致一些任务未能正常完成。

在调用shutdown()shutdownNow()后,通常还会调用awaitTermination()方法来等待线程池中的所有任务都执行完毕或者超时。这可以确保在程序退出前线程池被正确关闭。

一旦线程池被关闭,就不能再向其提交新任务。如果尝试在关闭后提交任务,将会抛出RejectedExecutionException异常。

如果你使用的是ScheduledThreadPoolExecutor,它提供了scheduleAtFixedRatescheduleWithFixedDelay等方法来执行定时任务。对于这类线程池,在关闭时也应该考虑正在等待执行的定时任务,并确保它们得到妥善处理。

shutdownNow()为什么有些任务无法取消?

答:`ExecutorService` 的 `shutdownNow()` 方法尝试取消当前正在执行的任务和未开始执行的任务。它会返回一个包含尚未开始执行的任务列表,并尝试通过调用每个工作线程的 `Thread.interrupt()` 方法来中断所有正在运行的任务。

然而,任务是否能够被成功取消取决于任务自身对于中断机制的支持。在Java中,线程中断是一个协作机制,而不是强制终止。这意味着当一个线程接收到中断请求时(其 `isInterrupted()` 方法返回 `true`),线程本身需要检查这个中断标志并根据该标志来决定如何响应,通常是在适当的清理后退出循环或提前结束任务。

如果一个正在运行的任务没有实现对中断的响应,或者任务中的某个阻塞操作(如 `Thread.sleep()`, `BlockingQueue.take()` 等)不响应中断(有些阻塞方法可以响应中断并抛出InterruptedException),那么即使调用了 `shutdownNow()`,该任务也可能无法立即停止执行。

TreadLocal是干嘛的?原理是什么?

答:`ThreadLocal` 是 Java 中一个类,用于在多线程环境下为每个线程提供独立的变量副本。它主要用于解决多线程中的数据同步问题,确保每个线程都能访问到独立的数据,从而避免数据竞争和不一致的问题。

`ThreadLocal` 的原理是基于线程本地存储(Thread Local Storage,简称 TLS)。每个线程都有一个独立的 TLS 存储,用于存储该线程的局部变量。当一个线程访问 `ThreadLocal` 变量时,它会在自己的 TLS 存储中查找或创建该变量的副本。这样,不同线程之间的 `ThreadLocal` 变量互不干扰,实现了线程隔离。

`ThreadLocal` 的主要方法有:

1. `set(T value)`:将指定值与当前线程的 `ThreadLocal` 变量关联。

2. `get()`:返回当前线程的 `ThreadLocal` 变量的值。如果当前线程尚未设置值,则返回 `null`。

3. `remove()`:移除当前线程的 `ThreadLocal` 变量的值。这可以防止内存泄漏,因为 `ThreadLocal` 变量在线程结束后仍然存在。

`ThreadLocal` 的使用场景包括:

1. 数据库连接:在多线程环境下,每个线程都需要独立的数据库连接,以避免资源竞争。

2. 用户会话管理:在 Web 应用中,每个用户请求都会创建一个新的线程,使用 `ThreadLocal` 可以为每个线程存储用户会话信息。

3. 日志记录:在多线程环境下,可以使用 `ThreadLocal` 存储每个线程的日志记录器,以便在日志中记录线程相关的信息。

ThreadLocal会导致什么问题?

答:虽然 `ThreadLocal` 在多线程环境下为每个线程提供独立的变量副本,从而解决了数据同步问题,但在某些情况下,它也可能导致一些问题:

1. 内存泄漏:`ThreadLocal` 变量在线程结束后仍然存在,如果没有正确地移除对应的值,可能会导致内存泄漏。在使用 `ThreadLocal` 时,需要确保在不再需要变量时调用 `remove()` 方法,以释放资源。

2. 线程池问题:在使用线程池的环境下,线程是被重用的。当一个线程从线程池中取出并执行任务时,可能会包含之前任务的 `ThreadLocal` 变量值。这可能导致数据污染和不一致。为了避免这个问题,可以在任务执行完成后调用 `remove()` 方法清除 `ThreadLocal` 变量的值。

3. 可读性和维护性:`ThreadLocal` 变量的作用域仅限于当前线程,这可能导致代码的可读性和维护性降低。在使用 `ThreadLocal` 时,需要确保变量的作用域和使用方式清晰明了,以便于理解和维护。

4. 性能问题:`ThreadLocal` 变量的访问速度可能比普通变量慢,因为它需要在线程的 TLS 存储中查找和创建变量副本。在性能敏感的场景下,需要权衡 `ThreadLocal` 带来的便利性和性能影响。

总之,虽然 `ThreadLocal` 在多线程环境下提供了线程隔离的解决方案,但在使用过程中也需要注意潜在的问题,如内存泄漏、线程池问题、可读性和维护性以及性能问题。在适当的场景下使用 `ThreadLocal`,可以提高代码的健壮性和可维护性。

synchronized 和 ReentrantLock 有什么区别?

答:synchronized和ReentrantLock在Java中都是用于控制多线程访问同步资源的机制,但它们之间存在一些重要的区别。

  1. 实现方式:synchronized是Java的关键字,是JVM级别的锁,其实现依赖于JVM。而ReentrantLock是Java提供的API,是Lock接口下的一个实现类,因此它位于API层面。
  2. 锁的公平性:synchronized只能是非公平锁,而ReentrantLock可以指定为公平锁或非公平锁。公平锁会按照线程请求的顺序来获取锁,而非公平锁则允许插队现象。
  3. 锁的灵活性:ReentrantLock提供了更多的功能,比如可以中断等待锁的线程,获取等待锁的线程列表,还可以尝试获取锁(通过tryLock方法)。相比之下,synchronized的灵活性较低。
  4. 异常处理:synchronized在发生异常时会自动释放占有的锁,因此不会出现死锁。而ReentrantLock在发生异常时不会主动释放占有的锁,必须手动调用unlock方法来释放锁,否则可能引起死锁的发生。
  5. 性能:在JDK1.6之后,synchronized的性能得到了很大优化,和ReentrantLock比较接近。然而,由于ReentrantLock提供了更多的功能和灵活性,因此在某些情况下,它可能提供更高的性能。
  6. 锁的细粒度控制:ReentrantLock可以更精确的控制锁,提供了更丰富的锁操作方法,如可重入概念(一个线程可以多次获取同一个锁)等。

ReentrantLock是怎么实现的?AQS?讲一下AQS?他是怎样获取锁的?

答:`ReentrantLock` 是 Java 中一个可重入的互斥锁,它提供了与 `synchronized` 关键字类似的功能,但具有更高的灵活性和扩展性。`ReentrantLock` 的底层实现是基于 Java 并发包中的 `AbstractQueuedSynchronizer`(简称 AQS)框架。

AQS 是 Java 并发包中的一个核心框架,它为实现基于 FIFO 队列的阻塞锁和相关同步器(如信号量、读写锁等)提供了一个框架。AQS 的主要目标是简化同步器的实现,提供一种通用的基于 FIFO 队列的阻塞锁实现。

AQS 的核心思想是将锁的获取和释放操作抽象为一个整数状态(state),通过维护一个 FIFO 队列来管理等待获取锁的线程。当一个线程尝试获取锁时,如果锁已被其他线程持有,那么该线程会被加入到等待队列的尾部,并进入阻塞状态。当锁被释放时,等待队列的头部线程会被唤醒并尝试获取锁。

AQS 提供了一些模板方法,如 `tryAcquire()`、`tryRelease()`、`tryAcquireShared()` 和 `tryReleaseShared()`,这些方法需要子类根据具体的锁实现来实现。`ReentrantLock` 就是 AQS 的一个子类,它实现了这些模板方法,以支持可重入的互斥锁功能。

`ReentrantLock` 的获取锁的过程大致如下:

1. 调用 `lock()` 方法尝试获取锁。

2. 如果锁当前未被持有,那么调用 AQS 的 `tryAcquire()` 方法尝试获取锁。在 `ReentrantLock` 的实现中,`tryAcquire()` 方法会检查当前线程是否已经持有锁,如果是,则增加锁的计数器;如果不是,则尝试获取锁。

3. 如果 `tryAcquire()` 方法返回 `true`,表示成功获取锁,执行同步代码。

4. 如果 `tryAcquire()` 方法返回 `false`,表示锁已被其他线程持有,那么当前线程会被加入到等待队列的尾部,并进入阻塞状态。

5. 当锁被释放时,等待队列的头部线程会被唤醒并尝试获取锁。

Semaphore和CountDownLatch有了解过吗?

答:是的,我了解 SemaphoreCountDownLatch。它们都是 Java 并发包中的同步工具类,用于控制多个线程之间的协作和同步。

  1. Semaphore:Semaphore 是一个计数信号量,用于控制同时访问特定资源的线程数量。它维护了一个许可集,线程可以通过调用 acquire() 方法来获取许可,如果许可已用完,线程将阻塞等待。线程可以通过调用 release() 方法来释放许可。Semaphore 可以用于限制资源的并发访问,例如限制数据库连接的数量。
  2. CountDownLatch:CountDownLatch 是一个同步辅助类,用于在多个线程之间实现协作。它维护了一个计数器,当计数器减少到 0 时,所有等待的线程将被唤醒。CountDownLatch 提供了 countDown() 方法来减少计数器,提供了 await() 方法来等待计数器减少到 0。CountDownLatch 可以用于等待多个线程完成任务,然后执行后续操作。

讲一下Java有哪些IO模型

答:Java 中涉及的IO模型主要指的是在处理输入输出操作时,Java API 提供的不同机制。以下是Java中常见的四种IO模型:

  1. 阻塞式I/O(Blocking I/O, BIO)在传统的Java IO(java.io包)中,默认采用的是阻塞式I/O模型。当调用诸如InputStream.read()或OutputStream.write()等方法时,如果数据没有准备好或者缓冲区已满,则线程会一直阻塞,直到有数据可读或空间可写。
  2. 非阻塞式I/O(Non-blocking I/O, NIO)Java NIO(java.nio包)提供了非阻塞式的通道和缓冲器进行数据传输。使用非阻塞模式时,调用read或write方法不会阻塞当前线程,而是立即返回一个状态值,例如“尚未准备好”或“已经完成”。NIO还引入了选择器(Selector),可以实现单个线程管理多个通道,并监控这些通道是否有就绪事件发生(如读就绪、写就绪等),从而实现I/O多路复用。
  3. I/O多路复用(Multiplexing I/O)这是NIO中的一个特性,通过Selector组件能够同时监听多个Channel上的事件,当任何一个Channel准备好了进行读写操作时,Selector就会通知相应的应用程序。该模型特别适用于高并发场景下,一个线程可以同时管理大量连接,而不是为每个连接分配单独的线程。
  4. 异步I/O(Asynchronous I/O, AIO)也称为Java New I/O 2 (NIO.2),自Java 7开始提供,在java.nio.channels包下增加了新的异步通道类,如AsynchronousSocketChannel和AsynchronousServerSocketChannel。异步I/O允许应用程序发起一个I/O操作后立刻返回,不等待操作结果,而是在I/O操作完成后由操作系统回调通知Java应用完成状态。异步I/O不需要使用Selector来轮询就绪的通道,而是基于事件驱动和回调机制,从而更高效地利用系统资源。

你知道select和epoll有什么区别吗?

答:select和epoll都是Linux下用于处理I/O操作的多路复用机制,但它们在多个方面存在显著的差异。

首先,从I/O模型的角度来看,select使用轮询模型。这意味着,无论文件描述符是否活跃,select都会遍历所有的文件描述符集合。这种方式在处理大量文件描述符时效率较低,因为每次都需要检查所有的文件描述符。相比之下,epoll采用基于事件驱动的模型。它只会对活跃的文件描述符进行操作,通过回调函数进行处理,从而大大提高了效率。

其次,两者的文件描述符数量限制也不同。对于select,其文件描述符数量被限制在1024左右。这在处理大量并发连接时可能会成为问题,需要使用fd_set多次扩展或者使用其他的多路复用技术。然而,epoll则没有这样的限制,它可以支持数万个文件描述符,因此非常适合处理高并发场景。

此外,两者在触发方式上也存在差异。select和epoll对可读事件和可写事件的触发方式并不相同,具体细节会根据具体的使用场景和编程需求有所不同。

零拷贝有哪些实现方式?

答:零拷贝是一种高效的数据传输技术,它可以将数据从内核空间直接传输到应用程序的内存空间中,从而减少数据传输过程中的额外拷贝。以下是一些常见的零拷贝实现方式:

  1. sendfile:这是一种在网络传输中实现零拷贝的方式。sendfile() 是一种特殊的系统调用,它允许在内核空间和用户空间之间直接传输数据,避免了数据在内核和用户空间之间的额外拷贝。这在高性能的网络传输中非常有效。
  2. mmap:mmap() 是另一种在文件传输中实现零拷贝的方式。它通过将文件映射到进程的地址空间,使得进程可以直接访问文件内容,而不需要经过中间缓冲区的拷贝。这样在文件读写时,数据可以直接在内核缓冲区和用户空间之间传输,从而实现零拷贝。
  3. Direct I/O:Direct I/O 是一种在文件传输中实现零拷贝的方式,也称为直接IO或裸IO。它允许数据在磁盘和用户空间之间直接传输,避免了数据在内核和用户空间之间的额外拷贝,从而实现零拷贝。
  4. splice:splice() 是一种在管道传输中实现零拷贝的方式。它允许将一个文件描述符的数据直接传输到另一个文件描述符,避免了数据在用户空间和内核空间之间的额外拷贝。
  5. Netty:Netty是一个高性能的网络通信框架,它通过使用堆外内存(直接内存)和组合Buffer对象来实现零拷贝。此外,Netty还采用了sendfile机制进行文件传输,从而避免了传统通过循环write方式导致的内存拷贝问题。

用过Java哪些框架?springboot是用来解决什么问题的?spring呢?

答:Java 的框架有很多种,其中一些比较流行的包括 Spring、Spring Boot、Hibernate、MyBatis、Struts、Spring MVC、Spring Security、Quartz、Apache Camel 等。这些框架各有不同的功能和用途,可以满足不同开发需求。

  • Spring 是一个开源的 Java 应用开发框架,它提供了一整套的编程模型和工具,用于简化企业级应用的开发过程。Spring 框架的核心理念是依赖注入(Dependency Injection,简称 DI)和面向切面编程(Aspect-Oriented Programming,简称 AOP),它们有助于消除耦合,提高代码的可重用性和可维护性。
  • Spring Boot 是 Spring 的一种优化和扩展,它简化了 Spring 应用的配置和部署过程,大大加速了开发周期。Spring Boot 提供了许多预设的默认配置,使得开发者能够更加快速地构建和运行应用程序。同时,Spring Boot 还支持微服务架构,易于扩展和维护。

Spring Boot 主要用来解决以下问题:

  1. 简化 Spring 应用的配置和部署过程,提高开发效率。
  2. 提供预设的默认配置,使得开发者能够更加快速地构建和运行应用程序。
  3. 支持微服务架构,易于扩展和维护。

总的来说,Spring Boot 使得开发者能够更加专注于业务逻辑的实现,而无需过多关注底层的配置和管理。

至于 Spring,它是一个开源的 Java 应用开发框架,提供了一整套的编程模型和工具,用于简化企业级应用的开发过程。Spring 框架的核心理念是依赖注入(Dependency Injection,简称 DI)和面向切面编程(Aspect-Oriented Programming,简称 AOP),它们有助于消除耦合,提高代码的可重用性和可维护性。Spring 框架广泛应用于各种企业级应用开发中,包括 Web 开发、数据访问、事务管理、安全性、消息传递等。

说一下jvm内存区域划分

答:Java 虚拟机(JVM)内存区域主要分为以下几个部分:

  1. 堆(Heap):堆是 JVM 内存中最大的一块区域,主要用于存放对象实例和数组。堆被所有线程共享,垃圾回收器(Garbage Collector)会定期回收堆中不再使用的对象,以释放内存空间。
  2. 方法区(Method Area):方法区用于存储已加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区也被所有线程共享。
  3. 栈(Stack):栈是一个线程私有的内存区域,用于存储局部变量、方法调用和基本类型数据。每个线程都有一个独立的栈,栈中的数据只能在当前线程中访问。栈的大小是有限的,当栈空间不足时,会抛出 StackOverflowError 异常。
  4. 本地方法栈(Native Method Stack):本地方法栈与栈类似,但用于存储 Java 本地方法(通过 JNI 调用的方法)的调用信息。本地方法栈也是线程私有的。
  5. 程序计数器(Program Counter Register):程序计数器是一个很小的内存区域,用于存储当前线程正在执行的字节码指令的地址。每个线程都有一个独立的程序计数器。

说一下本地方法栈和java虚拟机栈的关系

答:Java虚拟机栈是线程私有的,其生命周期与线程相同。它主要描述的是Java方法执行的线程内存模型,每次方法调用的数据都是通过栈传递的。Java虚拟机栈中存储了被调用方法的局部变量和操作数栈。局部变量是方法内部定义的变量,只有该方法内部可以访问。而操作数栈则用于存储方法执行过程中的操作数。

本地方法栈与虚拟机栈所发挥的作用非常相似,但有一些关键的区别。虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的本地(Native)方法服务。本地方法是用其他语言(如C/C++)编写的方法,在Java程序中通过JNI(Java Native Interface)来调用。本地方法栈由多个栈帧组成,每个栈帧保存了一个Native方法的执行状态,包括方法的局部变量、操作数栈、返回值等信息。

本地方法栈也是线程私有的,并且在栈深度溢出或者拓展失败时,会抛出StackOverFlowError和OutOfMemoryError异常。值得注意的是,不是所有的JVM都支持本地方法,因为Java虚拟机规范并没有明确要求本地方法栈使用的语言、具体实现方式或数据结构等。

垃圾回收算法有哪些?

答:垃圾回收(Garbage Collection,简称 GC)是 Java 虚拟机(JVM)自动管理内存的一种机制,它会定期回收不再使用的对象,以释放内存空间。以下是一些常见的垃圾回收算法:

  1. 标记-清除(Mark-Sweep):标记-清除算法分为两个阶段。首先,垃圾回收器会遍历所有可达对象,将它们标记为“存活”。然后,垃圾回收器会遍历堆中的所有对象,将未被标记为“存活”的对象清除。这种算法的缺点是会产生内存碎片,导致内存利用率降低。
  2. 复制(Copying):复制算法将堆分为两个相等的区域,每次只使用其中一个区域。当这个区域的内存空间不足时,垃圾回收器会将存活的对象复制到另一个区域,然后清除当前区域的所有对象。这种算法的优点是不会产生内存碎片,但缺点是需要额外的内存空间来存储复制的对象。
  3. 标记-整理(Mark-Compact):标记-整理算法是标记-清除算法的改进版本。在标记阶段,垃圾回收器会遍历所有可达对象,将它们标记为“存活”。然后,垃圾回收器会将存活的对象向一端移动,从而减少内存碎片。最后,垃圾回收器会清除剩余的未被标记的对象。这种算法的优点是不会产生内存碎片,但缺点是需要额外的时间来移动存活的对象。
  4. 分代收集(Generational Collection):分代收集算法将堆划分为不同的代(Generation),如新生代(Young Generation)和老年代(Old Generation)。新生代中的对象存活时间较短,因此可以频繁地进行垃圾回收。老年代中的对象存活时间较长,因此垃圾回收的频率较低。分代收集算法通常结合了复制算法和标记-清除(或标记-整理)算法,以实现高效的垃圾回收。
  5. 并行收集(Parallel Collection):并行收集算法在多核处理器上进行垃圾回收,可以显著提高垃圾回收的速度。在并行收集过程中,多个线程会同时进行垃圾回收,从而减少垃圾回收的暂停时间。
  6. 并发收集(Concurrent Collection):并发收集算法允许垃圾回收和应用程序线程同时运行,从而减少垃圾回收对应用程序性能的影响。在并发收集过程中,垃圾回收器会在应用程序线程运行的同时进行垃圾回收,从而减少垃圾回收的暂停时间。

了解哪些最新的垃圾回收器?

答:垃圾回收器是Java虚拟机(JVM)中用于自动管理内存的重要组件。随着JVM的不断发展和升级,垃圾回收器也在不断演进和创新。以下是一些较新的垃圾回收器及其特点:

  1. G1(Garbage First)回收器:G1回收器是Java 7中引入的,旨在提供一种低暂停时间且高吞吐量的垃圾回收解决方案。G1回收器采用分代收集的思想,将堆划分为多个大小相等的区域,并根据对象的存活时间和分布情况动态调整回收策略。G1回收器通过并发标记-整理算法实现,能够在减少暂停时间的同时保持较高的吞吐量。
  2. ZGC(Z Garbage Collector):ZGC是Java 11中引入的一种实验性质的垃圾回收器,它的设计目标是实现极低的暂停时间,特别是在处理大型堆时。ZGC采用了全新的内存管理和回收算法,包括着色指针、读屏障和并发压缩等关键技术。ZGC目前仍在不断优化和改进中,未来有望成为主流的垃圾回收器之一。
  3. Shenandoah GC:Shenandoah GC是另一款在Java 12中引入的实验性质的垃圾回收器,它的目标也是实现低延迟和高吞吐量的垃圾回收。Shenandoah GC采用了并发标记-清除算法,并通过多种优化手段减少了暂停时间和内存占用。Shenandoah GC还支持增量回收和并发压缩等功能,进一步提升了垃圾回收的性能和效率。

MySQL有哪些锁?

答:

  1. 共享锁(Shared Locks,S锁):允许事务读取一行数据。当一个事务为数据加上读锁后,其他事务只能对该数据加读锁,不能加写锁。直到所有读锁释放后,其他事务才能对数据加持写锁。
  2. 排他锁(Exclusive Locks,X锁):又称为写锁。当一个事务为数据加上写锁时,其他事务不能对其加上任何锁。
  3. 记录锁(Record Locks):锁定单个行记录。InnoDB存储引擎支持记录锁。
  4. 间隙锁(Gap Locks):锁定一个范围,但不包括记录本身。主要用于防止幻读(Phantom Reads)。
  5. 临键锁(Next-Key Locks):是记录锁和间隙锁的组合,它锁定一个记录以及该记录前的间隙。InnoDB存储引擎的默认锁定算法。
  6. 表锁(Table Locks):开销小,加锁快,但可能出现死锁。对整个表进行上锁,当下一个事务访问该表时,必须等待上一个事务释放了表锁才能访问。主要由MySQL Server实现,例如在DDL(数据定义语言)操作如ALTER时使用。
  7. 页锁(Page Locks):开销大于表锁但小于行锁,可能出现死锁。某些存储引擎如BDB可能使用页锁。
  8. 意向锁(Intention Locks):是一种表级别的锁,表示事务想要在行上或表上设置某种类型的锁。有两种意向锁:意向共享锁(IS)和意向排他锁(IX)。
  9. 自增锁(AUTO-INC Locks):当插入记录时,如果表中有自增字段,则可能需要自增锁来确保自增值的唯一性。

事务有哪些隔离级别

答:

  1. 读未提交(Read Uncommitted):在这个隔离级别下,一个事务可以读取另一个事务尚未提交的数据。这种隔离级别可能导致脏读、不可重复读和幻读等问题。读未提交隔离级别的并发性能较高,但数据一致性较差。
  2. 读已提交(Read Committed):在这个隔离级别下,一个事务只能读取另一个事务已经提交的数据。这种隔离级别可以避免脏读问题,但仍然可能出现不可重复读和幻读等问题。读已提交隔离级别的并发性能较高,数据一致性相对较好。
  3. 可重复读(Repeatable Read):在这个隔离级别下,一个事务在执行过程中多次读取同一数据时,始终返回相同的结果。这种隔离级别可以避免脏读和不可重复读问题,但仍然可能出现幻读等问题。可重复读隔离级别的并发性能较低,数据一致性较好。
  4. 串行化(Serializable):在这个隔离级别下,事务之间会进行串行化处理,即一个事务在执行过程中,其他事务必须等待当前事务完成后才能执行。这种隔离级别可以避免脏读、不可重复读和幻读等问题,但并发性能较差,数据一致性最好。

readview是干嘛的?

答:ReadView 是一个在数据库管理系统(DBMS)中用于实现多版本并发控制(MVCC,Multi-Version Concurrency Control)的概念。MVCC 是一种用于解决事务并发问题的技术,它允许多个事务同时访问和操作数据,而不会互相干扰。在 MVCC 中,每个事务都会看到一个特定版本的数据,这个版本是基于事务开始时的数据状态创建的。

ReadView 在 MVCC 中起到了关键作用,它表示了一个事务在执行过程中看到的数据版本。ReadView 包含了以下信息:

  1. 事务ID:当前事务的唯一标识符。
  2. 快照时间:事务开始时的时间戳,用于确定事务开始时的数据状态。
  3. 活跃事务列表:当前正在执行的事务列表,这些事务可能会对数据进行修改,因此需要在读取数据时考虑这些事务的影响。

在 MVCC 中,当一个事务需要读取数据时,它会根据 ReadView 中的信息来确定应该读取哪个版本的数据。这样,每个事务都可以在自己的事务上下文中看到一致的数据,而不会受到其他事务的影响。这种机制可以有效地解决脏读、不可重复读和幻读等并发问题,同时提高了数据库的并发性能。

除了可重复读就没别的地方使用readview了吗?

答:

  1. MVCC(多版本并发控制):MySQL的InnoDB存储引擎使用了MVCC来实现事务的并发控制,Read View就是MVCC中的核心组件之一,无论在哪个事务隔离级别下,只要涉及到并发事务间的读写冲突处理,都可能会用到Read View。
  2. Snapshot读取:在一些特定情况下,即使不是在“可重复读”的事务隔离级别下,也可能需要创建一个Read View来获取某个时间点的数据快照,以满足特定的一致性读取需求。
  3. 数据库系统设计:在其他数据库系统或者分布式数据库的设计中,为了实现类似MVCC的功能,可能会借鉴并使用Read View这种机制来管理不同事务版本的数据可见性问题。

ReadView的结构。这三个问题他都想引导我说undolog,没反应过来。

答:ReadView在数据库管理系统中,特别是在MVCC(多版本并发控制)的上下文中,扮演着非常重要的角色。它主要用于在事务执行快照读操作时生成一个“读视图”,以确定哪些数据版本对当前事务是可见的。

ReadView的结构主要包括以下几个关键部分:

  1. 活跃事务列表 (TRX_IDS): 这个列表记录了生成ReadView时刻系统中所有活跃的事务ID。每个事务在开启时都会被分配一个唯一的ID,这个ID在事务的整个生命周期内保持不变。这个列表是ReadView进行可见性判断的重要依据。
  2. 最小活跃事务ID (TRX_ID_MIN): 这个值是活跃事务列表中的最小事务ID。它用于快速判断一个数据行版本是否在当前所有活跃事务之前被修改过。
  3. 最大可能事务ID (TRX_ID_MAX): 这个值通常表示ReadView生成时刻系统尚未分配的下一个事务ID。它用于判断一个数据行版本是否在ReadView生成之后被修改过。
  4. 其他可能的属性或信息: 根据具体的实现和需要,ReadView可能还包含其他信息,比如创建时间、关联的数据库对象等。

当进行select操作时,ReadView会根据数据行版本的事务ID与上述属性之间的关系来判断该数据行版本是否对当前事务可见:

  • 如果数据行版本的事务ID小于TRX_ID_MIN,说明这个数据行版本在当前所有活跃事务之前就已经被修改,因此对当前事务是可见的。
  • 如果数据行版本的事务ID大于或等于TRX_ID_MAX,说明这个数据行版本在ReadView生成之后才被修改,因此对当前事务是不可见的。
  • 如果数据行版本的事务ID在TRX_ID_MIN和TRX_ID_MAX之间,那么还需要根据具体的事务隔离级别来进行进一步的判断。

宕机了,redolog是怎么保证持久性的?

答:当数据库宕机时,redolog(redo log)是一种用于保证数据持久性的技术。Redolog 是数据库管理系统(DBMS)在内存中维护的一个日志文件,用于记录对数据的修改操作。当数据库发生故障时,可以使用 redolog 来恢复丢失的数据。

Redolog 的工作原理如下:

  1. 当一个事务需要对数据进行修改时,DBMS 首先会将这个修改操作记录到 redolog 中。这个过程称为“写前日志”(Write-Ahead Logging,WAL)。
  2. 然后,DBMS 会将修改操作应用到实际的数据文件中。这个过程称为“脏页刷新”(Dirty Page Flush)。
  3. 当事务提交时,DBMS 会将 redolog 中的修改操作永久地保存到磁盘上的 redolog 文件中。这个过程称为“提交日志”(Commit Log)。
  4. 如果数据库发生故障,DBMS 可以使用 redolog 文件中的信息来恢复丢失的数据。恢复过程包括以下几个步骤:a. 重做(Redo):DBMS 会读取 redolog 文件中的修改操作,并将这些操作应用到数据文件上。这样可以恢复在故障发生时尚未完成的数据修改。b. 回滚(Undo):如果有未提交的事务,DBMS 会根据 redolog 文件中的信息回滚这些事务的修改操作,以确保数据的一致性。

通过这种方式,redolog 可以保证数据库在宕机后能够恢复到故障发生前的状态,从而实现了数据的持久性。此外,redolog 还可以提高数据库的并发性能,因为它允许事务在不等待数据文件写入的情况下继续执行。

binlog和redolog的区别?

答:binlog(binary log)和 redolog(redo log)都是数据库管理系统(DBMS)中用于实现数据持久性和事务处理的技术,但它们在功能和用途上有一些区别:

  1. binlog:binlog 是 MySQL 数据库中的一种日志文件,用于记录对数据的修改操作。它主要用于数据库的复制和备份。binlog 记录了所有的数据修改操作,包括 INSERT、UPDATE、DELETE 等。binlog 的内容是以二进制格式存储的,因此称为“binary log”。binlog 的主要特点如下:它是逻辑日志,记录的是 SQL 语句的执行结果,而不是物理数据页的变化。它是在主服务器(Master)上生成的,用于实现数据库的主从复制。它可以用于数据库的备份和恢复。
  2. redolog:redolog 是数据库管理系统(如 Oracle、SQL Server 等)中的一种日志文件,用于记录对数据的修改操作。它主要用于实现事务处理和数据持久性。redolog 记录了所有的数据修改操作,包括 INSERT、UPDATE、DELETE 等。redolog 的内容是以物理日志记录(Physical Log Record,PLR)的形式存储的,因此称为“redo log”。redolog 的主要特点如下:它是物理日志,记录的是物理数据页的变化,而不是 SQL 语句的执行结果。它是在数据库服务器上生成的,用于实现事务处理和数据持久性。它可以用于数据库的故障恢复。

如何实现binlog和redolog的同步?

答:在数据库管理系统(DBMS)中,binlog 和 redolog 的同步是通过主从复制(Master-Slave Replication)来实现的。主从复制是一种将主服务器(Master)上的数据更新操作同步到从服务器(Slave)的技术。在 MySQL 中,binlog 是实现主从复制的关键组件,而在其他数据库系统(如 Oracle、SQL Server 等)中,redolog 可能会扮演类似的角色。

以下是实现 binlog 和 redolog 同步的基本步骤:

  1. 配置主服务器:在主服务器上启用 binlog,并配置相关参数,如 binlog 文件的存储路径、文件名、日志格式等。
  2. 配置从服务器:在从服务器上配置主服务器的信息,如主服务器的 IP 地址、端口、用户名和密码等。
  3. 启动主从复制:在从服务器上启动主从复制进程,通常称为“I/O 线程”和“SQL 线程”。I/O 线程负责从主服务器获取 binlog 数据,而 SQL 线程负责将获取到的 binlog 数据应用到从服务器上。
  4. 同步 binlog:当主服务器上有数据更新操作时,binlog 会记录这些操作。I/O 线程会将这些操作从主服务器获取到从服务器。
  5. 应用 redolog:在从服务器上,SQL 线程会将获取到的 binlog 数据应用到从服务器的数据文件上。这个过程可能涉及到 redolog 的使用,具体取决于从服务器所使用的数据库系统。
  6. 监控和管理:在主从复制过程中,可以使用一些监控和管理工具来监控复制状态、延迟等信息,并在需要时进行故障切换和其他操作。

讲一下zookeeper的底层原理

答:Zookeeper的底层原理主要涉及数据状态管理、集群扩展性、可靠性以及分布式算法的应用。

Zookeeper的数据状态不是保存在内存中,而是使用磁盘来保存日志。这种设计有助于确保数据的持久性和可恢复性。

Zookeeper的集群扩展性是通过设计不同的节点角色来实现的。其中,Observer节点主要是为了放大查询能力而设计的,它并不参与写操作。这种设计可以使得查询操作在多个节点上并行进行,从而提高系统的查询能力。

在可靠性方面,Zookeeper具有快速恢复leader的能力。当一个leader节点挂掉,且没有新的leader节点确定时,Zookeeper集群会暂停对外提供服务,以防止错误的数据被提供给客户端。这种设计保证了系统的高可用性。此外,Zookeeper底层使用了Paxos算法和Zab广播算法,这两种算法都是分布式系统中常用的算法,它们保证了在分布式环境下数据的正确性和可靠性。

Zookeeper的底层原理还包括其独特的数据结构。Zookeeper采用树形结构,每个子目录项如NameService都被称作为znode(目录节点)。与文件系统类似,用户可以自由地增加、删除znode,以及在一个znode下增加、删除子znode。但与文件系统不同的是,znode是可以存储数据的,并且会传递节点的数据变化。

除了ZAB,还知道哪些分布式一致性协议?

答:

  1. Paxos:由Leslie Lamport在1990年提出的一种分布式一致性算法,用于解决分布式系统中的一致性问题,包括选举、数据复制等。
  2. Raft:由Diego Ongaro和John Ousterhout在2013年提出的一种更易理解和实现的分布式一致性算法,与Paxos类似,但提供了更清晰的领导者选举机制和日志复制算法。
  3. Viewstamped Replication:一种由Barbara Liskov和Brian Oki于1998年提出的一致性算法,用于实现分布式系统中的状态机复制。
  4. Multi-Paxos:基于Paxos算法的扩展,用于在分布式系统中执行多个命令序列。
  5. EPaxos:一种高效的基于Paxos的分布式一致性算法,用于支持分布式数据库系统等高性能应用。

这些算法在不同的分布式系统中发挥着重要作用,用于确保数据的一致性、可靠性和可用性。

说一下你对最终一致性和强一致性的理解

答:最终一致性和强一致性是分布式系统中两种不同的一致性模型。它们在数据一致性、性能和可用性等方面有不同的权衡。

  1. 最终一致性:最终一致性是一种弱一致性模型,它允许在一段时间内系统中的数据存在不一致的状态,但最终所有节点上的数据都会达到一致的状态。最终一致性的优点是它可以提供较高的性能和可用性,因为它允许在不同节点上进行并发读写操作,而不需要等待所有节点达成一致。最终一致性的缺点是它可能导致数据的不一致,因此在某些场景下可能不适用。
  2. 强一致性:强一致性是一种强一致性模型,它要求在任何时刻,所有节点上的数据都必须保持一致。强一致性的优点是它可以确保数据的一致性,避免数据不一致的问题。强一致性的缺点是它可能导致性能和可用性的降低,因为在进行数据更新操作时,所有节点都需要等待其他节点达成一致,从而增加了系统的延迟。

手撕 lc287 寻找重复数

#阿里云##校招##实习##后端##面经#
大厂校招实习最新面经解析 文章被收录于专栏

专注于最新各大厂最新面筋解析

全部评论
需要的话友友可以看看我首页内推码,米哈游大量内推实习和正式
1
送花
回复
分享
发布于 03-20 08:42 上海
???
1
送花
回复
分享
发布于 03-21 11:01 黑龙江
滴滴
校招火热招聘中
官网直投

相关推荐

国产大飞机 总体工程师 年包22-24 硕士海归
点赞 评论 收藏
转发
16 85 评论
分享
牛客网
牛客企业服务