面试刷题网站:
大家好,我是小林。
去哪儿 26 届校招开发岗也开奖了!
我整理了一波薪资数据,给大家参考参考:
| 岗位 | 薪资构成 | 年薪 | 工作地点 |
|---|---|---|---|
| 测试开发 | 18k x 16 | 28.8w | 北京 |
| 测试开发 | 20k x 16 | 32w | 北京 |
| 测试开发 | 21k x 16 | 33.6w | 北京 |
| 测试开发 | 22k x 16 | 35.2w | 北京 |
| 前端开发 | 22k x 16 | 35.2w | 北京 |
| 后端开发 | 20k x 16 | 32w | 北京 |
| 后端开发 | 22k x 16 | 35.2w | 北京 |
| 后端开发 | 23k x 16 | 36.8w | 北京 |
整体年薪范围在 28w~36w,估摸着眼下白菜档就是 18k-20k,SP offer 大概在 21k-23k 这个区间。
去哪儿跟老对手携程一样,也有居家办公福利,每周三、周五能选在家干活。
而且作为旅行公司,福利还带点专属特色, 每年有 1000 元旅行经费补贴,还会跟着工作年限涨,一年涨 1k,封顶 5k,这波属实是给爱出去玩的同学发福利了。
当然也有人吐槽,居家办公等于没了固定上下班时间,赶上项目紧张的时候,组长也可能喊你回公司。
但这都是特殊情况,大部分时候在家办公还是香的。不用挤地铁挤公交,省下的通勤时间能多睡半小时;家里有猫有狗的同学,累了还能撸两把宠物回血,这幸福感不比在公司强?
再说说去哪儿今年秋招的面试流程,强度可不低:
笔试:AI 面 + 测评 + 算法题三合一,还要求双机位防作弊
技术面三轮 + HR 面,全部面完才会进入 offer 开奖环节
笔试:AI 面 + 测评 + 算法题三合一,还要求双机位防作弊
技术面三轮 + HR 面,全部面完才会进入 offer 开奖环节
也有同学在秋招拿到了 3 offer,一个是工业『互联网』公司,一个是去哪儿,还有个京东,可惜『互联网』中大厂 offer 都开了白菜,不过总归也是挺进大厂的,这下他的名字真的可以改名了
。

这次就给大家扒一扒今年去哪儿 Java 后端开发的一面面经。
整场面试 40 分钟,跟大厂的面试强度差不了多少,而且面试官专挑知识点原理往深了问,Redis、MySQL、Spring、JVM 这几块被狠狠拷打了一顿,最后手撕算法也是标配,一点没落下。
另外项目这块也是必问项,面试官会让你挑项目里的技术难点和亮点来讲。这其实是所有面试的通用套路了,建议大家提前把项目梳理好,最好写个草稿,别到时候临场发挥支支吾吾,白白浪费机会。

去哪儿(Java 后端一面)1. Redis中的数据结构你知道那些?
Redis 提供了丰富的数据类型,常见的有五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合)。


随着 Redis 版本的更新,后面又支持了四种数据类型:BitMap(2.2 版新增)、HyperLogLog(2.8 版新增)、GEO(3.2 版新增)、Stream(5.0 版新增)。Redis 五种数据类型的应用场景:
String 类型的应用场景:缓存对象、常规计数、分布式锁、共享 session 信息等。
List 类型的应用场景:消息队列(但是有两个问题:1. 生产者需要自行实现全局唯一 ID;2. 不能以消费组形式消费数据)等。
Hash 类型:缓存对象、购物车🛒等。
Set 类型:聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等。
Zset 类型:排序场景,比如排行榜、电话和姓名排序等。
String 类型的应用场景:缓存对象、常规计数、分布式锁、共享 session 信息等。
List 类型的应用场景:消息队列(但是有两个问题:1. 生产者需要自行实现全局唯一 ID;2. 不能以消费组形式消费数据)等。
Hash 类型:缓存对象、购物车🛒等。
Set 类型:聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等。
Zset 类型:排序场景,比如排行榜、电话和姓名排序等。
Redis 后续版本又支持四种数据类型,它们的应用场景如下:
BitMap(2.2 版新增):二值状态统计的场景,比如签到、判断用户登陆状态、连续签到用户总数等;
HyperLogLog(2.8 版新增):海量数据基数统计的场景,比如百万级网页 UV 计数等;
GEO(3.2 版新增):存储地理位置信息的场景,比如滴滴叫车;
Stream(5.0 版新增):消息队列,相比于基于 List 类型实现的消息队列,有这两个特有的特性:自动生成全局唯一消息ID,支持以消费组形式消费数据。
BitMap(2.2 版新增):二值状态统计的场景,比如签到、判断用户登陆状态、连续签到用户总数等;
HyperLogLog(2.8 版新增):海量数据基数统计的场景,比如百万级网页 UV 计数等;
GEO(3.2 版新增):存储地理位置信息的场景,比如滴滴叫车;
Stream(5.0 版新增):消息队列,相比于基于 List 类型实现的消息队列,有这两个特有的特性:自动生成全局唯一消息ID,支持以消费组形式消费数据。
Redis 选择「惰性删除+定期删除」这两种策略配和使用,以求在合理使用 CPU 时间和避免内存浪费之间取得平衡。
Redis 的惰性删除策略由 db.c 文件中的 expireIfNeeded 函数实现,代码如下:
intexpireIfNeeded(redisDb *db, robj *key){
// 判断 key 是否过期
if(!keyIsExpired(db,key)) return0;
....
/* 删除过期键 */
....
// 如果 server.lazyfree_lazy_expire 为 1 表示异步删除,反之同步删除;
returnserver.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
dbSyncDelete(db,key);
}
Redis 在访问或者修改 key 之前,都会调用 expireIfNeeded 函数对其进行检查,检查 key 是否过期:
如果过期,则删除该 key,至于选择异步删除,还是选择同步删除,根据 lazyfree_lazy_expire 参数配置决定(Redis 4.0版本开始提供参数),然后返回 null 客户端;
如果没有过期,不做任何处理,然后返回正常的键值对给客户端;
如果过期,则删除该 key,至于选择异步删除,还是选择同步删除,根据 lazyfree_lazy_expire 参数配置决定(Redis 4.0版本开始提供参数),然后返回 null 客户端;
如果没有过期,不做任何处理,然后返回正常的键值对给客户端;
惰性删除的流程图如下:

Redis 的定期删除是每隔一段时间「随机」从数据库中取出一定数量的 key 进行检查,并删除其中的过期key。
1、这个间隔检查的时间是多长呢?
在 Redis 中,默认每秒进行 10 次过期检查一次数据库,此配置可通过 Redis 的配置文件 redis.conf 进行配置,配置键为 hz 它的默认值是 hz 10。特别强调下,每次检查数据库并不是遍历过期字典中的所有 key,而是从数据库中随机抽取一定数量的 key 进行过期检查。
2、随机抽查的数量是多少呢?
我查了下源码,定期删除的实现在 expire.c 文件下的 activeExpireCycle 函数中,其中随机抽查的数量由 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 定义的,它是写死在代码中的,数值是 20。也就是说,数据库每轮抽查时,会随机选择 20 个 key 判断是否过期。接下来,详细说说 Redis 的定期删除的流程:
从过期字典中随机抽取 20 个 key;
检查这 20 个 key 是否过期,并删除已过期的 key;
如果本轮检查的已过期 key 的数量,超过 5 个(20/4),也就是「已过期 key 的数量」占比「随机抽取 key 的数量」大于 25%,则继续重复步骤 1;如果已过期的 key 比例小于 25%,则停止继续删除过期 key,然后等待下一轮再检查。
从过期字典中随机抽取 20 个 key;
检查这 20 个 key 是否过期,并删除已过期的 key;
如果本轮检查的已过期 key 的数量,超过 5 个(20/4),也就是「已过期 key 的数量」占比「随机抽取 key 的数量」大于 25%,则继续重复步骤 1;如果已过期的 key 比例小于 25%,则停止继续删除过期 key,然后等待下一轮再检查。
可以看到,定期删除是一个循环的流程。那 Redis 为了保证定期删除不会出现循环过度,导致线程卡死现象,为此增加了定期删除循环流程的时间上限,默认不会超过 25ms。针对定期删除的流程,我写了个伪代码:
do{
//已过期的数量
expired = 0;
//随机抽取的数量
num = 20;
while(num--) {
//1. 从过期字典中随机抽取 1 个 key
//2. 判断该 key 是否过期,如果已过期则进行删除,同时对 expired++
}
// 超过时间限制则退出
if(timelimit_exit) return;
/* 如果本轮检查的已过期 key 的数量,超过 25%,则继续随机抽查,否则退出本轮检查 */
} while(expired > 204);
定期删除的流程如下:

3. Redis热点key,怎么优化?
通常以其接收到的Key被请求频率来判定,例如:
QPS集中在特定的Key:Redis实例的总QPS(每秒查询率)为10,000,而其中一个Key的每秒访问量达到了7,000。
带宽使用率集中在特定的Key:对一个拥有上千个成员且总大小为1 MB的HASH Key每秒发送大量的HGETALL操作请求。
CPU使用时间占比集中在特定的Key:对一个拥有数万个成员的Key(ZSET类型)每秒发送大量的ZRANGE操作请求。
QPS集中在特定的Key:Redis实例的总QPS(每秒查询率)为10,000,而其中一个Key的每秒访问量达到了7,000。
带宽使用率集中在特定的Key:对一个拥有上千个成员且总大小为1 MB的HASH Key每秒发送大量的HGETALL操作请求。
CPU使用时间占比集中在特定的Key:对一个拥有数万个成员的Key(ZSET类型)每秒发送大量的ZRANGE操作请求。
解决热 key 的方式:
在Redis集群架构中对热Key进行复制。在Redis集群架构中,由于热Key的迁移粒度问题,无法将请求分散至其他数据分片,导致单个数据分片的压力无法下降。此时,可以将对应热Key进行复制并迁移至其他数据分片,例如将热Key foo复制出3个内容完全一样的Key并名为foo2、foo3、foo4,将这三个Key迁移到其他数据分片来解决单个数据分片的热Key压力。
使用读写分离架构。如果热Key的产生来自于读请求,您可以将实例改造成读写分离架构来降低每个数据分片的读请求压力,甚至可以不断地增加从节点。但是读写分离架构在增加业务代码复杂度的同时,也会增加Redis集群架构复杂度。不仅要为多个从节点提供转发层(如Proxy,LVS等)来实现『负载均衡』,还要考虑从节点数量显著增加后带来故障率增加的问题。Redis集群架构变更会为监控、运维、故障处理带来了更大的挑战。
在Redis集群架构中对热Key进行复制。在Redis集群架构中,由于热Key的迁移粒度问题,无法将请求分散至其他数据分片,导致单个数据分片的压力无法下降。此时,可以将对应热Key进行复制并迁移至其他数据分片,例如将热Key foo复制出3个内容完全一样的Key并名为foo2、foo3、foo4,将这三个Key迁移到其他数据分片来解决单个数据分片的热Key压力。
使用读写分离架构。如果热Key的产生来自于读请求,您可以将实例改造成读写分离架构来降低每个数据分片的读请求压力,甚至可以不断地增加从节点。但是读写分离架构在增加业务代码复杂度的同时,也会增加Redis集群架构复杂度。不仅要为多个从节点提供转发层(如Proxy,LVS等)来实现『负载均衡』,还要考虑从节点数量显著增加后带来故障率增加的问题。Redis集群架构变更会为监控、运维、故障处理带来了更大的挑战。
笼统地说 MongoDB 比 MySQL 快是不严谨的,但如果是在特定场景下,MongoDB 确实能展现出更高的读写性能。
我将从它们的架构、数据模型和核心机制三个方面来对比,解释 MongoDB 在高并发读写和特定查询上快的原因:
| 特征 | MongoDB (NoSQL 文档型) | MySQL (关系型) | 为什么 MongoDB 快? |
|---|---|---|---|
| 数据模型 | Schema-less(无固定结构)的 BSON 文档(类似 JSON)。 | Schema-based(固定结构)的表结构。 | 写入快:MongoDB 写入时不需要严格的 Schema 校验,减少了额外的处理时间。 |
| 数据存储 | 文档嵌入(Embedded Documents):将相关数据存储在一个文档中。 | 范式化:数据分散在多个表中,通过外键关联。 | 读取快:进行一次查询就能获取所有关联数据,避免了耗时的 JOIN操作。 |
| 事务支持 | 弱事务性(原子性在单个文档级别)。 | 强事务性(ACID)。 | 并发高:由于没有复杂的分布式事务开销,可以处理更高的并发写入请求。 |
MongoDB 并不是在所有情况下都比 MySQL 快。它快的原因,是牺牲了传统关系型数据库的强一致性和复杂事务能力,换来了更高的读写并发和更灵活的数据模型,使其在面向文档和高并发写入的场景下性能更出色。
5. 如何解决慢sql?
分析查询语句:使用EXPLAIN命令分析SQL执行计划,找出慢查询的原因,比如是否使用了全表扫描,是否存在索引未被利用的情况等,并根据相应情况对索引进行适当修改。
创建或优化索引:根据查询条件创建合适的索引,特别是经常用于WHERE子句的字段、Orderby 排序的字段、Join 连表查询的字典、 group by的字段,并且如果查询中经常涉及多个字段,考虑创建联合索引,使用联合索引要符合最左匹配原则,不然会索引失效
避免索引失效:比如不要用左模糊匹配、函数计算、表达式计算等等。
查询优化:避免使用SELECT *,只查询真正需要的列;使用覆盖索引,即索引包含所有查询的字段;联表查询最好要以小表驱动大表,并且被驱动表的字段要有索引,当然最好通过冗余字段的设计,避免联表查询。
分页优化:针对 limit n,y 深分页的查询优化,可以把Limit查询转换成某个位置的查询:select * from tb_sku where id>20000 limit 10,该方案适用于主键自增的表。
读写分离:搭建主从架构, 利用数据库的读写分离,Web『服务器』在写数据的时候,访问主数据库(master),主数据库通过主从复制将数据更新同步到从数据库(slave),这样当Web『服务器』读数据的时候,就可以通过从数据库获得数据。这一方案使得在大量读操作的Web应用可以轻松地读取数据,而主数据库也只会承受少量的写入操作,还可以实现数据热备份,可谓是一举两得。
优化数据库表:如果单表的数据超过了千万级别,考虑是否需要将大表拆分为小表,减轻单个表的查询压力。也可以将字段多的表分解成多个表,有些字段使用频率高,有些低,数据量大时,会由于使用频率低的存在而变慢,可以考虑分开。
使用缓存技术:引入缓存层,如Redis,存储热点数据和频繁查询的结果,但是要考虑缓存一致性的问题,对于读请求会选择旁路缓存策略,对于写请求会选择先更新 db,再删除缓存的策略。
分析查询语句:使用EXPLAIN命令分析SQL执行计划,找出慢查询的原因,比如是否使用了全表扫描,是否存在索引未被利用的情况等,并根据相应情况对索引进行适当修改。
创建或优化索引:根据查询条件创建合适的索引,特别是经常用于WHERE子句的字段、Orderby 排序的字段、Join 连表查询的字典、 group by的字段,并且如果查询中经常涉及多个字段,考虑创建联合索引,使用联合索引要符合最左匹配原则,不然会索引失效
避免索引失效:比如不要用左模糊匹配、函数计算、表达式计算等等。
查询优化:避免使用SELECT *,只查询真正需要的列;使用覆盖索引,即索引包含所有查询的字段;联表查询最好要以小表驱动大表,并且被驱动表的字段要有索引,当然最好通过冗余字段的设计,避免联表查询。
分页优化:针对 limit n,y 深分页的查询优化,可以把Limit查询转换成某个位置的查询:select * from tb_sku where id>20000 limit 10,该方案适用于主键自增的表。
读写分离:搭建主从架构, 利用数据库的读写分离,Web『服务器』在写数据的时候,访问主数据库(master),主数据库通过主从复制将数据更新同步到从数据库(slave),这样当Web『服务器』读数据的时候,就可以通过从数据库获得数据。这一方案使得在大量读操作的Web应用可以轻松地读取数据,而主数据库也只会承受少量的写入操作,还可以实现数据热备份,可谓是一举两得。
优化数据库表:如果单表的数据超过了千万级别,考虑是否需要将大表拆分为小表,减轻单个表的查询压力。也可以将字段多的表分解成多个表,有些字段使用频率高,有些低,数据量大时,会由于使用频率低的存在而变慢,可以考虑分开。
使用缓存技术:引入缓存层,如Redis,存储热点数据和频繁查询的结果,但是要考虑缓存一致性的问题,对于读请求会选择旁路缓存策略,对于写请求会选择先更新 db,再删除缓存的策略。
线程在正常执行或者异常中断时会被销毁,如果频繁的创建很多线程,不仅会消耗系统资源,还会降低系统的稳定性,一不小心把系统搞崩了。
使用线程池可以带来以下几个好处:
线程池内部的线程数是可控的,可以灵活的设置参数;
线程池内会保留部分线程,当提交新的任务可以直接运行;
方便内部线程资源的管理,调优和监控;
线程池内部的线程数是可控的,可以灵活的设置参数;
线程池内会保留部分线程,当提交新的任务可以直接运行;
方便内部线程资源的管理,调优和监控;
所以,线程池是为了减少频繁的创建线程和销毁线程带来的性能损耗,线程池的工作原理如下图:

当用户提交了一个任务,接下来这个任务将如何执行都是由这个阶段决定的。首先,所有任务的调度都是由execute方法完成的,这部分完成的工作是:检查现在线程池的运行状态、运行线程数、运行策略,决定接下来执行的流程,是直接申请线程执行,或是缓冲到队列中执行,亦或是直接拒绝该任务。其执行过程如下:
首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。
如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。
如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。
如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。
如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。
首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。
如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。
如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。
如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。
如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。
SpringBoot 自动装配核心就一句话:通过约定好的规则,自动把指定的 Bean 加载到 Spring 容器中,不用我们手动写 XML 或 @Bean 配置。具体拆解成 3 个核心步骤,特别好理解:
1、 入口:@SpringBootApplication
1、 入口:@SpringBootApplication
一切的开始都是启动类上的 @SpringBootApplication注解。这其实是一个复合注解,里面最重要的一个就是 @EnableAutoConfiguration(开启自动配置)。
2、 机制:Java SPI 机制 (加载)
2、 机制:Java SPI 机制 (加载)
@EnableAutoConfiguration内部使用了一个 @Import注解,导入了一个关键的“选择器”类(AutoConfigurationImportSelector)。
这个选择器干了什么事呢?它利用了 Java 的 SPI (Service Provider Interface)思想。
它会去扫描所有 jar 包下的 META-INF/spring.factories文件(在 Spring Boot 2.7+ / 3.x 版本中,也会扫描 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports)。
这些文件里写死了一长串的配置类全限定名,比如 RedisAutoConfiguration、DataSourceAutoConfiguration等。
Spring Boot 会把这成百上千个配置类的名字全部读取出来。
它会去扫描所有 jar 包下的 META-INF/spring.factories文件(在 Spring Boot 2.7+ / 3.x 版本中,也会扫描 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports)。
这些文件里写死了一长串的配置类全限定名,比如 RedisAutoConfiguration、DataSourceAutoConfiguration等。
Spring Boot 会把这成百上千个配置类的名字全部读取出来。
3、 过滤:@Conditional(按需生效)
3、 过滤:@Conditional(按需生效)
既然读取了这么多配置类,难道全部都要加载并创建 Bean 吗?肯定不是,那样启动太慢了,而且会报错。
这时候,Spring 的 条件注解(@Conditional)就起到了关键的“过滤”作用。每一个自动配置类上,都加了各种条件判断,比如:
@ConditionalOnClass:判断 classpath 下有没有某个核心类。举例:只有当你引入了 spring-boot-starter-data-redis依赖,classpath 下有了 Redis 的核心类,RedisAutoConfiguration才会生效。
@ConditionalOnMissingBean:判断容器里是不是已经有了这个 Bean。举例:如果你自己手动配置了一个 DataSource,Spring Boot 就不会再帮你自动配置默认的数据源了,这就是“约定大于配置”的体现,用户配置优先。
@ConditionalOnClass:判断 classpath 下有没有某个核心类。举例:只有当你引入了 spring-boot-starter-data-redis依赖,classpath 下有了 Redis 的核心类,RedisAutoConfiguration才会生效。
@ConditionalOnMissingBean:判断容器里是不是已经有了这个 Bean。举例:如果你自己手动配置了一个 DataSource,Spring Boot 就不会再帮你自动配置默认的数据源了,这就是“约定大于配置”的体现,用户配置优先。
所以,自动装配的本质就是:Spring Boot 在启动时,通过 SPI 机制扫描 classpath 下所有 jar 包的配置文件,加载所有的自动配置类,然后通过 @Conditional系列注解进行筛选,只有满足条件的配置类才会真正生效,最终将 Bean 注册到容器中。
8. Spring IOC 原理是什么?
IOC:Inversion Of Control,即控制反转,是一种设计思想。在传统的 Java SE 程序设计中,我们直接在对象内部通过 new 的方式来创建对象,是程序主动创建依赖对象;

而在Spring程序设计中,IOC 是有专门的容器去控制对象。

所谓控制就是对象的创建、初始化、销毁。
创建对象:原来是 new 一个,现在是由 Spring 容器创建。
初始化对象:原来是对象自己通过构造器或者 setter 方法给依赖的对象赋值,现在是由 Spring 容器自动注入。
销毁对象:原来是直接给对象赋值 null 或做一些销毁操作,现在是 Spring 容器管理生命周期负责销毁对象。
创建对象:原来是 new 一个,现在是由 Spring 容器创建。
初始化对象:原来是对象自己通过构造器或者 setter 方法给依赖的对象赋值,现在是由 Spring 容器自动注入。
销毁对象:原来是直接给对象赋值 null 或做一些销毁操作,现在是 Spring 容器管理生命周期负责销毁对象。
总结:IOC 解决了繁琐的对象生命周期的操作,解耦了我们的代码。所谓反转:其实是反转的控制权,前面提到是由 Spring 来控制对象的生命周期,那么对象的控制就完全脱离了我们的控制,控制权交给了 Spring 。这个反转是指:我们由对象的控制者变成了 IOC 的被动控制者。
9. Spring Bean生命周期说一下
Bean 的生命周期可以简单分成 4 个阶段,流程很清晰:

创建阶段:Spring 先找到需要管理的 Bean,实例化对象,然后给属性赋值(依赖注入)。
初始化阶段:
先调用各种 Aware 接口(比如 BeanNameAware、ApplicationContextAware),让 Bean 知道自己的 ID、上下文等信息。
接着执行 BeanPostProcessor 的前置处理方法。
然后调用 InitializingBean 的 afterPropertiesSet 方法,或者自定义的 init-method 初始化方法。
最后执行 BeanPostProcessor 的后置处理方法,此时 Bean 才算初始化完成。
使用阶段:Bean 在容器中待命,随时被程序调用。
销毁阶段:容器关闭时,先调用 DisposableBean 的 destroy 方法,或者自定义的 destroy-method 销毁方法,完成资源清理。
创建阶段:Spring 先找到需要管理的 Bean,实例化对象,然后给属性赋值(依赖注入)。
初始化阶段:
先调用各种 Aware 接口(比如 BeanNameAware、ApplicationContextAware),让 Bean 知道自己的 ID、上下文等信息。
接着执行 BeanPostProcessor 的前置处理方法。
然后调用 InitializingBean 的 afterPropertiesSet 方法,或者自定义的 init-method 初始化方法。
最后执行 BeanPostProcessor 的后置处理方法,此时 Bean 才算初始化完成。
先调用各种 Aware 接口(比如 BeanNameAware、ApplicationContextAware),让 Bean 知道自己的 ID、上下文等信息。
接着执行 BeanPostProcessor 的前置处理方法。
然后调用 InitializingBean 的 afterPropertiesSet 方法,或者自定义的 init-method 初始化方法。
最后执行 BeanPostProcessor 的后置处理方法,此时 Bean 才算初始化完成。
使用阶段:Bean 在容器中待命,随时被程序调用。
销毁阶段:容器关闭时,先调用 DisposableBean 的 destroy 方法,或者自定义的 destroy-method 销毁方法,完成资源清理。
G1 的特点:
G1最大的特点是引入分区的思路,弱化了分代的概念。
合理利用垃圾收集各个周期的资源,解决了其他收集器、甚至 CMS 的众多缺陷
G1最大的特点是引入分区的思路,弱化了分代的概念。
合理利用垃圾收集各个周期的资源,解决了其他收集器、甚至 CMS 的众多缺陷
G1 相比较 CMS 的改进:
算法: G1 基于标记--整理算法, 不会产生空间碎片,在分配大对象时,不会因无法得到连续的空间,而提前触发一次 FULL GC 。
停顿时间可控: G1可以通过设置预期停顿时间(Pause Time)来控制垃圾收集时间避免应用雪崩现象。
并行与并发:G1 能更充分的利用 CPU 多核环境下的硬件优势,来缩短 stop the world 的停顿时间。
算法: G1 基于标记--整理算法, 不会产生空间碎片,在分配大对象时,不会因无法得到连续的空间,而提前触发一次 FULL GC 。
停顿时间可控: G1可以通过设置预期停顿时间(Pause Time)来控制垃圾收集时间避免应用雪崩现象。
并行与并发:G1 能更充分的利用 CPU 多核环境下的硬件优势,来缩短 stop the world 的停顿时间。
每个项目是怎么来的?背景是什么?
项目中挑几个技术有价值的介绍一下
每个项目是怎么来的?背景是什么?
项目中挑几个技术有价值的介绍一下
合并区间
合并区间




