今天挑选一篇热门面经【0526美团日常实习一面面经】,给大家做讲解分析~
感谢这位同学的分享,预祝Offer多多~~~ 原贴链接
本篇面经几乎就是考察了Java后端Top6重点专项,关于这些专项的占比我之前总结过,大家可以关注下,按照优先级去准备。链接:大厂后端专项考察占比分布
另外最近
赠送
大家的题库命中率也极高
自产《大厂后端Top100面试题讲解》对本篇面经题目覆盖率:
9.5/10 = 95%
自产《大厂后端Top200面试题讲解》对本篇面经题目覆盖率:
10/10 = 100%
讲解开始~~~~~
解析
属于高频考题:
自产《大厂后端Top100面试题讲解|第73,74,75题》
所属专项:Redis|应用
专项考察占比:
【Redis】面试中考察操作系统的比率:大厂:12%| 腾讯:7%| 阿里:13%
【Redis-应用】面试考察Redis时,问“应用”相关问题的比率:34%
参考回答
1. 什么是缓存雪崩、击穿、穿透?如何解决?
缓存雪崩
缓存雪崩是指缓存由于某些原因(如大量数据同时过期或Redis 故障宕机)整体崩溃,导致大量请求到达后端数据库,从而导致数据库崩溃,整个系统崩溃,发生灾难。
解决方案:
- 使用分布式缓存部署,避免单点故障。
- 设置不同的过期时间,避免缓存集中失效。
- 缓存不过期,使用缓存预热和自动刷新机制。
- 应用程序限流,避免过多请求同时到达数据库。
缓存击穿
缓存击穿是指一个热点数据失效后,大量请求同时涌入后端存储,导致后端存储负载增大、响应时间变慢,甚至瘫痪。
解决方案:
- 使用互斥锁或者分布式锁对并发请求进行控制,避免对同一资源的并发读写竞争。
- 热点数据预加载,提前将热点数据加入缓存,在其失效时快速刷新缓存。
缓存穿透
缓存穿透是指查询一个数据库一定不存在的数据。正常的使用缓存流程大致是,数据查询先进行缓存查询,如果key不存在或者key已经过期,再对数据库进行查询,并把查询到的对象,放进缓存。如果数据库查询对象为空,则不放进缓存。持续查询不存在的数据会导致所有请求都落到后端存储上,导致系统瘫痪。
解决方案:
- 使用布隆过滤器或者黑白名单等方式过滤掉无效请求。
- 在应用程序中加入缓存预热等机制,预先加载常用数据到缓存中。
- 对于查询结果为空的数据,也可以将其key对应的value设置为一个默认值(如null),并设置一个较短的缓存过期时间,以减少对数据库的无效查询。
2. 缓存读写策略(一致性如何保证?)
先删除缓存还是先更新数据库?
先删除缓存,再更新数据库:
- 问题:在删除缓存后、更新数据库前的这段时间里,如果有读请求,会因为缓存已删除而读取数据库中的旧数据,然后这些数据又会被写入缓存,导致数据不一致。
先更新数据库,再删除缓存:
- 问题:在更新数据库后、删除缓存前的这段时间里,如果有读请求,会读取到缓存中的旧数据。此外,如果删除缓存操作失败,也会导致数据不一致。
解决方案
为了解决上述问题,我们可以采用以下策略:
延迟双删策略:
- 操作步骤:首先删除缓存,然后更新数据库,稍微延迟几百毫秒(这个延迟时间需要根据具体的业务场景来定),再次删除缓存。
- 优点:这种方法能够较大程度地保证数据库和缓存的一致性。
- 注意事项:延迟时间的设置是关键,太长会影响性能,太短则可能达不到预期效果。
结合消息队列和重试机制:
- 当更新数据库成功后,可以将删除缓存的操作放入消息队列中。
- 如果删除缓存失败,可以利用消息队列的重试机制来重新尝试删除操作。
- 优点:这种方法能够确保即使初次删除缓存失败,也能通过重试机制最终达到数据一致性。
- 注意事项:需要合理配置消息队列和重试策略,以避免对系统造成过大的压力。
3. redis 如何实现分布式锁?
- 使用SetNX实现基本的锁机制
Redis的
SETNX
命令(SET if Not eXists)是实现分布式锁的基础。当一个客户端尝试获取锁时,它会使用SETNX
来设置一个键。如果键不存在,SETNX
会设置该键并返回1,表示成功获取了锁。如果键已经存在,SETNX
不会做任何事情并返回0,表示获取锁失败。设置过期时间防止死锁
但是,仅仅使用
SETNX
是不够的。如果持有锁的客户端在处理过程中崩溃或网络断开,那么锁可能永远不会被释放,导致其他客户端永远无法获取锁,这就是所谓的死锁。为了防止这种情况,我们需要为锁设置一个过期时间。在Redis中,这可以通过EXPIRE
命令来实现。使用Lua脚本确保解锁操作的原子性
解锁操作需要谨慎处理。为了确保解锁操作的原子性,我们通常使用Lua脚本来执行。Lua脚本在Redis中是原子执行的,这意味着在脚本执行期间,其他命令不会被插入执行。这确保了即使在并发环境中,解锁操作也是安全的。
看门狗机制自动续期
为了确保锁在客户端处理过程中不会因过期而被意外释放,我们可以实现一个看门狗机制来自动续期锁的过期时间。这可以通过在客户端创建一个定时任务来实现,该任务在锁过期前不断地重新设置锁的过期时间。
- 使用Redisson库简化分布式锁的实现
手动实现上述所有功能可能相当复杂且容易出错。幸运的是,有一些库如Redisson已经为我们封装了这些复杂的逻辑。使用Redisson,我们可以非常简单地实现分布式锁,而无需关心底层的细节。Redisson还提供了很多高级功能,如锁的重试机制、公平锁等。
推荐学习资料
《小林Coding》|图解Redis:什么是缓存雪崩、击穿、穿透?
《小林Coding》|图解Redis:数据库和缓存如何保证一致性?
《博客》ITPUB:Redis 分布式锁的正确实现原理演化历程与 Redisson 实战总结
《博客》Dunwu Blog:分布式锁基本原理: 含非redis实现方式
解析
属于高频考题:
自产《大厂后端Top100面试题讲解|第66,67题》
所属专项:Redis|数据类型
专项考察占比:
【Redis】面试中考察操作系统的比率:大厂:12%| 腾讯:7%| 阿里:13%
【Redis-数据类型】面试考察Redis时,问“数据类型”相关问题的比率:20%
参考口述回答
1.跳表的插入过程(原理)
Redis中跳表的插入过程:
一、查找插入位置
- 从跳表的最高层开始,从左到右依次检查每个节点的下一个节点。
- 在查找的过程中,记录每一层中最后一个小于或等于待插入元素的节点,这些节点被称为“前驱”。
- 当找到插入位置或者到达跳表的底层时停止查找。
二、创建新节点并设置值
- 根据待插入元素创建一个新的节点。
- 将新节点的值设置为待插入元素的值。
三、更新前驱节点的指针
- 在每一层中,将新节点的指针指向前驱节点的下一个节点。
- 将前驱节点的指针指向新节点。
四、决定是否提升新节点
- 通过一个随机算法决定是否将新节点提升到更高层,以便加快查找的速度。
- 如果新节点被提升到更高层,需要对新节点进行连接操作。具体来说,就是在每一层中找到前驱节点的上一层节点,然后将新节点的指针指向前驱节点的上一层节点的下一个节点,接着将前驱节点的上一层节点的指针指向新节点。
这个过程确保了跳表在插入新元素后仍然保持有序,并且通过随机化的层高设计,使得跳表在查询、插入和删除操作时都能保持高效的性能。
总的来说,Redis中跳表的插入过程是一个结合了有序链表和二分查找思想的优化算法,它通过增加多级索引和随机化的层高设计来提高操作效率。
2.ZSET与跳表(跳表应用)
ZSET,即有序集合,是Redis提供的一种数据结构。它类似于Set,但其成员是唯一的,且每个成员都关联着一个分数。这个分数用于对集合中的元素进行排序。ZSET在实现上采用了跳表(SkipList)和哈希表(HashTable)两种数据结构,其中跳表用于维护元素的排序,而哈希表则提供快速的元素查找。
跳表(SkipList)是一种随机化的数据结构,实质上就是一种可以进行二分查找的有序链表。跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。它不仅提高了搜索性能,同时也提高了插入和删除操作的性能。
3.布隆过滤器的原理?
布隆过滤器由一个数组和多个哈希函数组成。首先,初始化一个长度为m的数组,起始位都初始化为0。使用k个不同的哈希函数来生成k个不同的哈希值。
当有一个元素要加入集合时,通过k个哈希函数计算出对应的k个哈希值,将位数设置为1。这样,当一个元素是否在集合中时,同样通过k个哈希函数计算出对应的k个哈希值,判断其位是否都为1,如果一位为0,则该元素肯定不在集合中;如果都为1,则可能在集合中。
由于布隆过滤器会确认阳性,因此可以为假阳性(即可以为不存在的阳性),但如果存在假阳性(即可以为不存在的阳性),则可以根据情况调整数组长度m和哈希函数数k来控制误判率。
推荐学习资料
《小林Coding》|图解Redis:Redis 数据结构-跳表
《小林Coding》|图解Redis:Redis 常见数据类型底层实现
解析
属于高频考题:
自产《大厂后端Top100面试题讲解|第44题》
自产《大厂后端Top200面试题讲解|第106题》
所属专项:Java|SSM框架
专项考察占比:
【Java】面试中考察Java的比率:大厂:17%| 腾讯:6%| 阿里:32%
【Java-SSM】面试考察Java时,问JavaSSM框架问题的比率:18%
参考口述回答
1. Spring IOC?
Spring的IOC,也就是控制反转,是Spring框架的核心特性之一。要理解IOC,我们首先需要了解两个概念:控制反转和依赖注入。
控制反转,即Inversion of Control,简称IoC。它的主要思想是将原本由代码直接操控的对象的调用权交给第三方,比如一个容器来控制,以解耦代码。在传统的程序设计中,我们直接在对象内部通过
new
关键字来创建依赖的对象,这种方式会导致代码之间高度耦合,不利于测试和维护。而IoC的思想是,将这些对象创建和绑定的控制权交由Spring容器来管理,从而降低系统的耦合性。具体来说,在Spring中实现IoC主要有两种方式:基于XML的配置和基于注解的配置。在XML配置中,我们可以在Spring的配置文件中,通过bean元素来定义需要Spring管理的对象,并通过property元素来注入依赖。而在注解配置中,我们可以使用如@Autowired、@Resource等注解来自动注入依赖。
依赖注入,即Dependency Injection,简称DI,是实现IoC的一种方式。它指的是通过外部的配置或者设定来将依赖关系注入到对象中。在Spring中,这通常是通过配置文件或者注解来实现的。这样,对象之间的依赖关系就由Spring容器来管理,而不是由对象自身来管理。
Spring IoC容器负责管理Bean的生命周期、依赖注入等。我们只需要配置好相关Bean,就可以轻松实现对象之间的解耦和依赖管理。总的来说,IoC的思想和Spring的IoC容器极大地提高了代码的灵活性和可维护性,降低了系统的复杂度。
另外,Spring IoC还提供了很多高级特性,如Bean的作用域控制、生命周期回调、事件传播等,这些特性可以帮助我们更好地管理和控制Bean的行为。
总的来说,我对Spring IoC的理解是,它是一种强大的解耦和管理对象依赖关系的工具,通过把对象的创建和依赖关系的控制权交给Spring容器来管理,从而提高了代码的灵活性和可维护性。
2. Spring事务?
在Spring框架中,事务管理主要通过@Transactional注解和TransactionManager实现。
- 使用@Transactional注解:在Spring中,通过@Transactional注解来标识需要进行事务管理的方法。可以在类级别或方法级别上添加@Transactional注解,指定事务的传播行为、级别、超时时间等参数。在applicationContext.xml配置文件中开启事务注解支持,可以实现@Transactional生效。
- TransactionManager:Spring中定义了PlatformTransactionManager接口,用于管理事务。具体的事务管理器如DataSourceTransactionManager、HibernateTransactionManager等,根据不同的数据访问技术选择相应的事务管理器。事务管理器负责开启、提交、回滚事务,并且遵循所有的事务的一致性。
Spring提供了多种事务管理的方式,包括编程式事务管理和声明式事务管理。声明式事务管理是通过@Transactional注解实现的,使得事务配置更加简洁清晰;而编程式事务管理是通过编写Java代码控制事务的开启、提交和回滚,更加灵活但繁琐。
推荐学习资料
《JavaGuide》| 常用框架: #Spring IOC
《JavaGuide》| Java 常用框架:Spring 事务详解
解析
属于高频考题:
自产《大厂后端Top100面试题讲解|第79,82,85题》
所属专项:计算机网络|TCP&UDP
专项考察占比:
【计算机网络】面试中考察计算机网络的比率:大厂:11%| 腾讯:17%| 阿里:7%
【计算机网络-TCP&UDP】面试考察计网时问TCP&UDP问题的比率:42%
参考回答
TCP和UDP的区别?(简单版回答)
TCP和UDP的主要区别体现在它们的连接方式和数据传输的可靠性上。
首先,TCP是一个面向连接的协议,它在数据传输之前需要先建立连接,确保数据的可靠传输,像打电话一样需要事先拨号建立通话。而UDP则是无连接的,它不需要在传输数据之前建立连接,就像发短信一样直接发送,速度可能更快,但数据可靠性可能不如TCP。
其次,TCP提供可靠的数据传输服务,通过数据校验、重传等机制确保数据完整性和顺序性。而UDP不保证数据包的可靠传输,数据包可能会丢失或乱序。
总的来说,TCP注重数据的可靠性和顺序性,适用于文件传输等需要稳定可靠传输的场景;UDP则更注重传输速度,适用于实时性要求较高的场景,如在线视频等。在选择使用TCP还是UDP时,需要根据具体的应用需求和场景来做决策。
讲一下TCP三次握手?(简单版回答)
TCP三次握手是建立TCP连接的一个过程。首先,客户端向服务器发送一个SYN包,询问服务器是否愿意建立连接,这可以看作是客户端的打招呼。接着,服务器回应一个SYN+ACK包,表示愿意建立连接,并对客户端的初始序列号进行确认,这相当于服务器的回应和打招呼。最后,客户端再发送一个ACK包,对服务器的SYN包进行确认,这就像是客户端对服务器的回应说‘好的,我也准备好了’。这样,三次握手完成后,TCP连接就建立了。这个过程确保了双方都能准确地知道对方已准备好进行数据传输,是TCP协议中保证数据可靠传输的重要机制之一
讲一下TCP四次挥手?(简单版回答)
首先,当客户端决定关闭连接时,它会向服务器发送一个FIN报文,这是第一次挥手,表示客户端没有数据要发送了。
接着,服务器收到FIN报文后,会发送一个ACK报文给客户端,确认关闭请求,这是第二次挥手。此时,连接进入半关闭状态,服务器还可以继续发送数据。
然后,当服务器也准备关闭连接时,它会发送一个FIN报文给客户端,告知自己也没有数据要发送了,这是第三次挥手。
最后,客户端收到服务器的FIN报文后,会发送一个ACK报文进行确认,这是第四次挥手。此后,客户端会等待一段时间后关闭连接,而服务器收到ACK后则立即关闭连接。
推荐学习资料
《小林 Coding》|图解网络: UDP 和 TCP 有什么区别呢?分别的应用场景是?
《小林 Coding》|图解网络: TCP 建立连接|三次握手
《小林 Coding》|图解网络: TCP 四次挥手过程是怎样的?
解析
属于高频考题之一:
《大厂后端Top100面试题讲解|第13题》
所属专项:MySQL|锁
专项考察占比:
【MySQL】面试中考察MySQL的比率:大厂:18%| 腾讯:13%| 阿里:17%
【MySQL-锁】面试考察MySQL时,问“锁”相关问题的比率:8%
参考回答
表级锁是锁定整个表,从而阻止其他用户并发访问。在MySQL中,表级锁主要包括以下几种:
- 表锁:最简单的表级锁,直接对整个表进行加锁。这种锁的优点是开销小、加锁快、无死锁,但缺点是锁定粒度大,发生锁冲突的概率最高,并发度最低。
- 元数据锁(MDL):当对一个表结构进行修改时(例如ALTER TABLE),MySQL会自动为该表加上元数据锁,以防止其他事务在该表上进行DDL或DML操作。这种锁主要用于保证表结构的完整性。
- 意向锁:在InnoDB存储引擎中,当事务准备在某个数据行上加共享锁或排他锁之前,会先在表上获取相应的意向锁。意向锁是兼容的,主要用于在行锁和表锁之间建立一种层级关系。
- AUTO-INC锁:当插入操作中涉及到自增字段时,MySQL会使用AUTO-INC锁来确保自增值的唯一性。这种锁在插入完成后会立即释放。
推荐学习资料
《掘金专栏》|全解MySQL数据库:# (八)MySQL锁机制
《小林Coding》|图解MySQL:# MySQL 有哪些锁?
解析
属于高频考题:
自产《大厂后端Top100面试题讲解|第22题》
所属专项:Java|Java集合框架
专项考察占比:
【Java】面试中考察Java的比率:大厂:17%| 腾讯:6%| 阿里:32%
【Java-集合框架】面试考察Java时,问Java集合框架问题的比率:9%
参考回答
一、数据结构:
- HashMap内部使用了一个数组加链表和红黑树的组合。数组用于存储键值对的桶(bucket),而链表和红黑树则用于解决哈希冲突。
- 当发生哈希冲突时,HashMap会在数组的同一个桶中形成链表。为了提升性能,在Java 8中,当链表长度超过一定阈值(默认为8)时,链表会转化为红黑树。
二、哈希计算与桶定位:
- HashMap使用哈希函数来计算键的哈希值,并根据这个哈希值确定键值对应该存放在数组的哪个桶中。
- 这个哈希函数被设计为尽量减少冲突,并确保键值对能够均匀分布到各个桶中。
三、冲突解决与数据结构转换:
- 当插入新的键值对时,如果计算出的桶已经有元素存在(即发生哈希冲突),则新的键值对会被添加到该桶对应的链表中。
- 如果链表长度过长(超过8),并且HashMap的数组长度大于或等于64,则链表会转换为红黑树,以提高搜索效率。
四、扩容机制:
- HashMap内部有一个扩容机制,当元素数量达到数组长度与加载因子(默认为0.75)的乘积时,就会触发扩容操作。
- 扩容时,HashMap的容量会翻倍,并且所有的元素都需要重新散列到新的数组中。这个过程是为了保持哈希表的均匀分布和减少冲突。
五、线程安全:
- HashMap并不是线程安全的。如果在多线程环境下使用HashMap,可能会导致数据不一致的问题。如果需要线程安全的哈希表,可以考虑使用ConcurrentHashMap。
推荐学习资料
解析
属于高频考题:
自产《大厂后端Top100面试题讲解|第29,35题》
所属专项:Java|Java并发
专项考察占比:
【Java】面试中考察Java的比率:大厂:17%| 腾讯:6%| 阿里:32%
【Java-并发】面试考察Java时,问Java并发问题的比率:37%
参考回答
1. ThreadLocal原理
ThreadLocal介绍
ThreadLocal是Java中的一个类,它提供了线程局部(thread-local)变量。这些变量与普通的可共享变量不同,因为每一个访问这个变量的线程都有它自己的独立初始化的变量副本。通过ThreadLocal,我们可以避免多线程环境下的数据不一致问题,因为每个线程都操作自己的数据,不存在线程安全问题。ThreadLocal适用于每个线程需要自己独立的数据副本,并且该数据可以在多个方法中使用,但在线程之间应该是隔离的场景。
ThreadLocal底层实现原理
ThreadLocal的底层实现原理主要依赖于Thread类中的threadLocals属性,这是一个ThreadLocalMap类型的变量。ThreadLocal本身并不存储数据,而是作为ThreadLocalMap的管理者。当调用ThreadLocal的set()方法时,会将ThreadLocal的引用作为key,用户传入的值作为value存入ThreadLocalMap中。每个线程内部都维护着自己的ThreadLocalMap,这样每个线程的读写操作都是基于线程本身的一个私有的变量副本,线程之间的数据是相互隔离的。
具体来说,ThreadLocalMap是通过一个Entry数组实现的,每个ThreadLocal实例在数组中都有对应的索引位置,这个位置是通过ThreadLocal的hashCode()方法和数组长度取模得到的。当线程调用ThreadLocal的get()或set()方法时,它会根据自己线程内的ThreadLocalMap来存取数据。
2. 线程池参数?
corePoolSize(核心线程数) :
- 线程池中始终保持的线程数量,即使这些线程处于空闲状态,也不会被销毁。例如,可以设置为5,表示线程池将始终保持5个活跃线程。
maximumPoolSize(最大线程数) :
- 线程池中允许同时运行的最大线程数量。当工作队列已满,且线程数未达到这个最大值时,线程池会创建新的线程来处理任务。例如,设置为10,表示线程池最多可以有10个线程同时运行。
keepAliveTime(线程空闲时间) :
- 当线程池中的线程数量超过corePoolSize时,这是多余的空闲线程在被终止之前等待新任务的最长时间。例如,设置为60秒,表示空闲线程在等待60秒后若仍无任务处理则会被销毁。
unit(时间单位) :
- keepAliveTime的时间单位,可以是毫秒、秒等。例如,使用
TimeUnit.SECONDS
表示以秒为单位。workQueue(工作队列) :
- 用于存储待执行的任务的阻塞队列。常见的类型有
LinkedBlockingQueue
、ArrayBlockingQueue
等。例如,可以创建一个容量为10的LinkedBlockingQueue
作为任务队列。threadFactory(线程工厂) :
- 用于创建新线程的工厂类,可以定制线程的名称、优先级等属性。通常情况下,使用默认的线程工厂即可。
handler(拒绝策略) :
- 当线程池中的线程数达到maximumPoolSize,且工作队列已满时,用于处理无法执行的新任务的策略。Java线程池提供了四种内置的拒绝策略:
AbortPolicy
(默认,抛出异常)、CallerRunsPolicy
(调用者执行)、DiscardPolicy
(丢弃任务)、DiscardOldestPolicy
(丢弃最旧任务)。
推荐学习资料
《JavaGuide》| 并发面试题:# ThreadLocal 面试题
《JavaGuide》| 详解:# ThreadLocal 详解
《JavaGuide》| 并发面试题:###线程池常见参数有哪些?如何解释?
解析
属于高频考题:
自产《大厂后端Top100面试题讲解|第36,39,41,42题》
所属专项:Java| JVM
专项考察占比:
【Java】面试中考察Java的比率:大厂:17%| 腾讯:6%| 阿里:32%
【Java-JVM】面试考察Java时,问JVM问题的比率:24%
参考回答
1. Java内存区域?
首先是程序计数器,这是一块较小的内存空间。它可以看作是当前线程所执行的字节码的行号指示器。每个线程都有一个独立的程序计数器,用于记录下一条要执行的指令的地址。
接下来是虚拟机 栈,这也是线程私有的内存区域。每个线程在创建时都会创建一个虚拟机栈,它描述的是Java方法执行的内存模型。栈中的每个栈帧都存储着局部变量、操作数栈、动态链接和方法出口等信息。当方法被调用时,一个新的栈帧会被压入栈中,当方法执行完毕后,对应的栈帧会被弹出。
与虚拟机栈相似的是本地方法栈,它为虚拟机使用到的Native方法提供内存空间。它也是线程私有的,与虚拟机栈不同的是,虚拟机栈为执行Java方法服务,而本地方法栈为执行Native方法服务。
再来看Java堆区,这是JVM内存中最大的一块区域,用于存储Java对象实例。堆区是所有线程共享的,它还可以被细分为年轻代和老年代,其中年轻代又可以分为Eden区和Survivor区。堆区的大小是在虚拟机启动时就已经设定好的,但可以通过JVM参数来进行调整。
最后是方法区,这也是一个线程共享的内存区域。它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区中有一个非常重要的部分叫做运行时常量池,它存放着编译期生成的各种字面量和符号引用。
2. 对象的创建过程,类加载过程,类加载器?
当一个
Java对象被创建
时,会经历以下几个关键步骤:
类加载过程
:首先,JVM会检查该类是否已经加载。如果没有加载,则会进行类加载过程包括加载、连接和初始化三个阶段。在加载阶段,会通过类加载器将类的字节码文件加载到内存中。在连接阶段,会进行验证、准备和解析等操作。最后,在初始化阶段,会执行类的静态代码块、静态变量的初始化等。- 分配内存:当类加载时,需要为分配内存空间分配不同的内存,不同内存大小的因素不同。在不同的JVM实现中,可能采用不同的内存分配,例如指针、空闲时间等。
- 初始化实例对象:初始化实例对象,然后根据该实例对象的初始化方法(即方法调用)初始化实例对象(包括初始化和用户指定的初始值)。
- 对象引用:一旦对象初始化完成,就会返回一个对象引用,即指向该对象的引用变量。通过这个引用变量,可以操作该对象的属性和方法。
总的来说,Java 实例的创建过程包括类、程序和对象的分配、以及实例的构造和执行。这些过程都是由 JVM 负责管理和执行的,并且有助于 Java 开发者更好地理解 Java 的编程思想和实现方式。
类加载器
是Java虚拟机(JVM)的一部分,它的职责是将类的字节码加载到内存中,并生成对应的Class对象。Java中的类加载器主要分为三种类型:
- 启动类加载器(Bootstrap Class Loader) :这是JVM的一部分,负责加载Java的核心类库,如rt.jar中的类。它是由C++实现的,因此在Java代码中无法直接获取。
- 扩展类加载器(Extension Class Loader) :它用于加载Java的扩展类库,这些类库位于JRE的lib/ext目录下。扩展类加载器是由启动类加载器加载的Java类。
- 应用程序类加载器(Application Class Loader) :也称为系统类加载器,是加载应用程序类的默认类加载器。它负责加载应用程序classpath下的类,包括用户自定义的类和第三方库。
3. 垃圾回收算法?
首先是标记-清除算法,这是最基本的垃圾回收算法。它的原理很简单,分为两个阶段:标记阶段和清除阶段。在标记阶段,垃圾回收器会从根对象开始,递归地访问这些对象的引用,将访问到的对象都标记为‘存活’。然后在清除阶段,垃圾回收器会遍历整个堆内存,将没有被标记的对象全部清除。这种算法的优点是实现简单,但缺点是会造成内存碎片化,导致后续分配大对象时可能找不到连续的内存空间。
接下来是复制算法,这种算法将内存划分为两个等大小的区域。在任意时间点,只有其中一个区域被使用,另一个区域是空闲的。当当前使用的区域内存耗尽时,垃圾回收器会将所有存活的对象复制到另一个空闲区域中,然后清空当前区域。这种算法的优点是垃圾回收速度快,但缺点是内存使用率只有50%。复制算法通常用于新生代的垃圾回收,特别是针对大量短命对象进行回收时效率很高。
还有一种算法是标记-整理算法,它标记存活对象的处理方式与标记-清除算法相同,但在清除阶段,它会将所有存活的对象都移动到一端,然后清理掉边界以外的内存。这种算法的优点是不会造成内存碎片化,但缺点是移动存活对象的成本较高。这种算法通常用于老年代的垃圾回收,因为老年代中存活的对象较多,不适合使用复制算法。
除了以上几种基本算法外,还有一些更先进的垃圾回收算法,如分代收集算法和区域化垃圾回收算法等。这些算法根据对象的存活周期和内存布局特点进行优化,以提高垃圾回收的效率和性能。
推荐学习资料
《JavaGuide》| JVM面试题: # Java内存区域详解(重点)
《JavaGuide》| JVM面试题:类的生命周期
《JavaGuide》| JVM面试题: 垃圾收集算法
解析
高频考题:属于高频考题:
自产《大厂后端Top100面试题讲解|第50和51题》
所属专项:操作系统|进程管理
专项考察占比:
【操作系统】面试中考察操作系统的比率:大厂:12%| 腾讯:18%| 阿里:8%
【操作系统-进程管理】面试考察操作系统时,问“进程管理”相关问题的比率:32%
参考回答
1. 进程,线程的区别?(简单版)
首先,我们来谈谈进程。进程是操作系统中进行资源分配和调度的基本单位,它拥有自己的独立内存空间和系统资源。每个进程都有独立的堆和栈,不与其他进程共享。进程间通信需要通过特定的机制,如管道、消息队列、信号量等。由于进程拥有独立的内存空间,因此其稳定性和安全性相对较高,但同时上下文切换的开销也较大,因为需要保存和恢复整个进程的状态。
接下来是线程。线程是进程内的一个执行单元,也是CPU调度和分派的基本单位。与进程不同,线程共享进程的内存空间,包括堆和全局变量。线程之间通信更加高效,因为它们可以直接读写共享内存。线程的上下文切换开销较小,因为只需要保存和恢复线程的上下文,而不是整个进程的状态。然而,由于多个线程共享内存空间,因此存在数据竞争和线程安全的问题,需要通过同步和互斥机制来解决。
2. 进程间通信的方式
首先,管道(Pipe)是一种常见的进程间通信方式,它允许一个进程的输出作为另一个进程的输入。管道是半双工的,数据只能单向流动,且通常用于具有亲缘关系的进程之间,如父子进程。此外,还有命名管道(FIFO),它与管道类似,但允许无亲缘关系的进程间进行通信。
其次,消息队列(Message Queue)是另一种重要的IPC机制。进程可以将消息发送到消息队列,其他进程则可以从队列中检索消息。这种方式克服了管道的一些限制,如只能承载无格式的字节流以及缓冲区大小受限等。消息队列允许进程之间发送和接收具有特定格式的消息,且可以异步地进行通信。
共享内存(Shared Memory)也是一种高效的进程间通信方式。多个进程可以访问同一块内存区域,从而直接读写共享的数据。这种方式速度非常快,因为数据不需要在不同进程之间复制。然而,它也需要更复杂的同步机制来防止数据冲突和不一致性。
信号量(Semaphore)则是一种用于控制多个进程对共享资源的访问的同步机制。它可以被视为一个计数器,用于实现进程间的互斥和同步操作。信号量常用于保护对共享内存或其他资源的访问,以防止发生竞态条件。
套接字(Socket)通信则是一种更为通用的进程间通信方式,它不仅适用于同一台计算机上的进程间通信,还适用于网络中的不同计算机上的进程间通信。套接字提供了一种标准的接口来发送和接收数据,支持多种协议(如TCP、UDP等),并具有跨平台和可靠性高的特点。
最后,信号(Signal)也是一种进程间通信的方式,但它主要用于通知接收进程某个事件已经发生。信号是一种软件中断,可以由操作系统或其他进程发送。接收进程在收到信号后,可以根据信号的类型执行相应的操作。
推荐学习资料:
《博客》| 腾讯云开发者社区: #一文读懂什么是进程、线程、协程
《小林Coding》|图解系统:# 进程、线程基础知识
《小林Coding》|图解系统:# 进程间有哪些通信方式?
解析
属于高频考题:
自产《大厂后端Top100面试题讲解|第61题》
所属专项:操作系统|Linux命令
专项考察占比:
【操作系统】面试中考察操作系统的比率:大厂:12%| 腾讯:18%| 阿里:8%
【操作系统-Linux命令】面试考察操作系统时,问“网络系统”相关问题的比率:7%
参考回答
查看系统资源
- top命令:这是一个实时显示系统中各个进程的资源占用状况的监视器。它可以展示CPU使用率、内存使用率、进程状态等信息。通过top命令,用户可以快速识别哪些进程占用了过多的系统资源。
- ps命令:用于显示当前系统中活动进程的信息。通过执行“ps -ef”或“ps aux”,用户可以查看所有进程的详细信息,包括进程ID、CPU和内存使用率等。此外,结合grep命令,可以方便地查找特定进程。
- free命令:用于显示系统的内存使用情况。执行“free -m”可以以MB为单位显示内存使用情况,包括总内存、已用内存、空闲内存等信息。
- df命令:用于显示磁盘分区上的可用和已使用的磁盘空间。执行“df -h”可以以人类可读的格式(如GB、MB)显示磁盘空间使用情况。
- sar命令:这是一个系统活动报告工具,可以用于收集、报告和保存系统活动信息。通过sar命令,用户可以查看CPU使用率、内存使用率、I/O等历史数据。
- vmstat命令:用于显示关于系统虚拟内存、进程、CPU活动等的统计信息。通过vmstat命令,用户可以实时监控系统的资源使用情况。
查看日志
- tail命令:常用于查看实时变化的日志文件。例如,“tail -f /var/log/syslog”可以实时查看系统日志的最新内容。此外,“tail -n [行数] 文件名”可用于查看文件的最后几行。
- cat命令:用于显示文件内容。结合grep命令,可以搜索关键字附近的日志内容。例如,“cat /var/log/syslog | grep '关键字'”可以搜索包含特定关键字的日志行。
- less命令:这是一个强大的文本查看器,允许用户向前和向后翻页查看文件内容。对于大型日志文件,less命令提供了方便的导航和搜索功能。
- head命令:与tail命令相反,head命令用于查看文件的开头部分。例如,“head -n [行数] 文件名”可以查看文件的前几行。
- more命令:以全屏幕的方式按页显示文件内容,适合查看长文件。
查看网络状态
- ifconfig:此命令用于显示和配置网络接口的详细信息,包括IP地址、MAC地址、网络掩码等。例如,ifconfig 可以列出所有活动网卡的信息。
- ip:ip 命令是一个多功能的网络工具,用于显示或操作路由、网络设备、策略路由和隧道。例如,ip addr 可以显示网络接口的详细信息,类似于 ifconfig;ip route 可以显示路由表信息。
- netstat: 此命令用于显示网络连接、路由表、接口统计等网络相关信息。例如,netstat -an 可以显示所有活动的网络连接和监听的端口。
- ss:ss 命令是一个比 netstat 更强大的工具,用于查看系统的socket统计信息。它可以提供更详细的信息,并且比 netstat 更快。例如,ss -tuln 可以列出所有TCP和UDP的监听端口。
- ping:此命令用于测试网络的连通性,通过发送ICMP回显请求数据包到目标主机,并等待接收回显回复数据包来判断网络是否连通。例如,ping www.baidu.com 可以测试到Baidu的网络连通性。
推荐学习资料
本文也是 《热门面经讲解》 专栏系列文章之一,大家可以点跳转链接,订阅&关注师兄,我会持续更新~
#面经##美团##实习##日常实习##后端#