(内部)阿里、腾讯、字节Java 大厂面试题搜集

1. 题目范围

Java后端研发涉及到的范围包括:

  • 计算机基础:
  • 网络基础:
  • 云服务基础:
  • 开发框架:
  • 中间件:
  • 领域建模设计

2. 适用人群

  • 需要面试的初/中/高级 java 程序员
  • 想要查漏补缺的人
  • 想要不断完善和扩充自己 java 技术栈的人
  • java 面试官

3. 各大公司对候选者的筛选偏好

百度:简单聊过,未真正面试,不发表评论;

阿里:既要候选人的知识广度,又要深度,特别注重候选人在以往项目中的思考和解决方案,要求工作年限和能力有一定匹配度;

腾讯:北京Java岗位,整体聊下来比较轻松,对知识面有一定要求,但不强烈,更多看交流气氛;

字节:直接哐哐哐的算法,答不上来没机会到下一轮;

美团:对细节关注比较多,会问到比较深的知识点;LZ面试得到的反馈要求岗位匹配度,也就是说需要有一定的项目经历匹配;

快手:面试常用知识点,但是要求能够手写出来实现,有些知识点要求有专业度;

滴滴:整体偏向于基础知识问答;

小米:基础知识问答+首先简单算法程序题;

京东:老东家了,有些记不清了,欢迎评论补充;

4. 具体面试题搜集

4.1 开放类题目

        在过往的项目经历中,印象最深的项目是什么?你在其中做了什么?

4.2 数据库题目

2)常用的MySQL架构是怎样的,各有什么优劣

常见的数据库的高可用方案包括:双机主备,一主一从,一主多从,同步多主机,中间件。

双机主备:优点是一个机器宕机后可以自动切换到另一个机器,操作简单;缺点是只有一个机器在提供服务,读写压力大;

一主一从:优点是从库支持读,读写分离,并且一个机器宕机后可切换到另一个机器;缺点是并发度还是不够;

一主多从:优点是多个从库分担了主库的读请求,提升了并发度;缺点是只有一台主机,写的能力受限;

同步多主机:通过代理层负载,读写的能力都有明显提升,有较高的可用性保障;缺点是:如果集群中一个节点变慢,其他节点也会变慢;

数据库中间件Mycat:数据库分片存储,解决高并发度读写,并且高可用性;缺点是维护成本比较高;

3)什么是事务?常见分布式事务解决方案

事务是逻辑上的一组数据库操作,要么都执行,要么都不执行,遵循ACID原则;

分布式事务的几种解决方案:

  • 2PC:分为prepare和commit/rollback两阶段,存在同步阻塞、事务管理器单点、极端情况下数据不一致等缺点
  • 3PC:分为CanCommit, preCommit和doCommit, 利用超时机制解决2PC的同步阻塞问题,避免资源呗永久锁定,但无法应对极端情况下宕机数据不一致问题;
  • TCC:采用了补偿机制,核心思想是每个操作都注册一个对应的确认和补偿(撤销)操作。TCC降低了数据库锁的操作粒度,提升性能,但对业务侵入性较大;
  • 本地消息表:核心思想是将分布式事务分拆成本地事务执行,具体实现如事务消息等;
  • Saga:核心思想是将长事务分拆成多个本地短事务,由saga事务协调器协调,如果正常结束就正常完成,如果某个步骤失败,则根据相反顺序调用补偿;

4)事务的隔离级别有哪些?默认的数据库隔离级别是什么?为什么选它?

数据库四大隔离级别:Read Uncommit, Read Commit, Repeatable Read, Serializable。 读未提交有脏读、不可重复、幻读问题。读已提交有不可重复读、幻读问题,读已提交有幻读问题。MySQL默认的事务隔离级别是Repeatable Read。 

主从模式下MySQL主从同步是通过binlog实现的,MySQL binlog有三种格式:statement,row和mixed。 statement格式下,记录的是SQL原文。如果采用RC,

事务1:set autocommit = false; update user set name = 'xxx' where id = 123;

事务2:set auto commit = false; update user set id = 123 where id = 456; commit;

事务1:commit ;

在binlog记录中,记录为update user set id = 123 where id = 456; 和update user set name = 'xxx' where id = 123;这样就导致了从库数据的不一致性;

RR隔离级别下,不仅会对更新记录加行锁,还会增加gap lock,这样保障事务2阻塞,需要等等事务1执行完成才能继续,维护主从一致性;

5)RR隔离级别实现原理,是如何解决不可重复度的

RR的隔离级别是通过MVCC实现的,而MVCC是通过read View和undo log实现。 read view的可见性原则如下:

  • 每一个数据行会有两个隐藏的列:tx_id和roll_pointer;
  • read View有几个重要属性:m_ids, 系统中未提交的事务id list; min_limit_id, 生成read view时系统中最小的未提交事务ID; max_limit_id,read view生成时,系统应该给下一个事务分配的id. creator_trx_id: 创建当前read view的事务ID。
    • 如果trx_id<min_limit_id,表示生成该版本的事务在read view之前,已提交,该版本可以被事务访问;
    • 如果trx_id>max_limit_id,表示生成该版本的事务在read view之后,该版本不能被事务访问;
    • 如果min_limit_id<=trx_id<=max_limit_id,分为三种情况:如果m_ids包含trx_id并且trx_id=creator_trx_id,表示是自己未提交的版本,所以能被控制;如果m_ids包含trx_id并且rx_id != creator_trx_id,表示不是自己的事务版本并且也未提交,所以不可见;如果m_ids不包含trx_id,表示已提交,可被访问;

6)项目中怎么设计分库分表?分布式ID如何生成

分库分表有水平切分和垂直切分两种。 水平切分的做法可以通过ldc或者用户单号hash。 垂直切分则是将列分散到不同的数据库内。

分布式ID生成方式:时间戳+随机数方式,雪花算法,号段方法,uuid, 数据库自增、Redis 自增等。

4.3 开发框架题目

1)   为什么线程崩溃不会导致JVM进程崩溃

线程崩溃进程一定会崩溃么?一般来说,如果线程非法访问内存引起的崩溃,进程一定会崩溃,原因是各个线程是共享进程地址空间的,所以某个线程非法访问,会影响到别的线程,操作系统认为很可能导致一系列严重问题,所以会处理成进程崩溃。常见的线程非法访问内存的几种情况:

  • 访问没有权限的地址空间
  • 访问不存在的内存空间
  • 只读内存写入数据等

线程崩溃后进程是如何崩溃的呢?答案是信号机制。比如我们常在Linux系统中执行的kill -9 命令,9就是信号。背后大致运行如下:

  • CPU执行正常的进程指令,调用kill向进程发送信号
  • 进程收到OS的信号后,CPU暂停当前应用程序运行,将控制权转交给OS
  • OS根据情况执行对应的信号处理逻辑,一般信号处理完成后会让进程退出。
  • 如果进程没有注册自己的信号处理函数,OS会采用默认信号处理;同理,如果注册了新号处理函数,则调用注册函数。

为什么JVM线程崩溃不会导致JVM进程崩溃?首先明白Java中线程异常通常由非法访问内存产生,常见的有Exception(NPE)和error(Stackoverflow)。从前面知识可以知道,只要执行拦截处理信号函数,就能让进程不崩溃。

主线程异常会导致JVM退出么?做一个实验

public static void main(String[] args) {
        Thread t = new Thread(()->{
            while (true){
                System.out.println("test");
            }
        });
        t.start();
        String s = null;
        if(s.equals("3")){
            System.out.println("haha");
        }
    }

实际结果是不停的打印test。说明主线程异常后并不会导致JVM崩溃。原因是什么呢?实际上Java中并没有主线程概念,线程都是平等,普通线程异常不会导致JVM崩溃。

2)Java线程模型

从源码看Java线程模型:

  // 是一个native方法
  @HotSpotIntrinsicCandidate
  public static native Thread currentThread();

// 启动start方法
  public synchronized void start() {
        ...
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
            }
        }
    }

// start0也是一个native方案
  private native void start0();

继续追踪start0方法,从GitHub - AdoptOpenJDK/openjdk-jdk8u: JDK8u mirror from mercurial

// jvm.cpp
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  native_thread = new JavaThread(&thread_entry, sz);

// thread.cpp
JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz)
{
  os::create_thread(this, thr_type, stack_sz);
}

// os_linux.cpp
bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {
  int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);
}

(代码来自芋道源码) 

可以看到jvm调用线程创建最终会调用底层pthread_create创建一个native thread内核线程,由此推断出Java线程和内核线程是一一对应关系,这种模型也称之为NPTL。

 内核线程在Linux系统中是通过一个task_struct结构来表示的,定义了进程所需的虚拟地址,文件描述符,寄存器、信号等资源。早期内核线程是用进程来实现的,所以创建一个线程就要fork一个进程,copy task_struct结构,开销很大。后期线程实现通过除线程栈和寄存器外都共享虚拟地址,提升线程执行效率。Linux中还引入了线程组的概念,同一组线程主pid一样,线程间的通信通过POSIX Thread标准。

NPTL线程模型的优缺点:

  • 优点:相比于内核进程模式,有非常高的性能提升,单台服务器生成10万+线程2秒内完成;
  • 缺点:线程或者进程的阻塞,都需要系统调度,包括上下文切换等,开销大;基于抢占式模式,线程同步需要应用自行保障顺序;内存开销仍然较大,单个线程Linux默认栈大小是1M。

所以有了协程(Coroutine)概念的出现。协程可以分为两个角度来看:一个是routine执行单元,一个是cooperative协作单元。协程与线程的不同之处在:协程遇到IO阻塞事件,会自动让出调度权,转而执行其他协程,而协程的挂起和唤醒完全是由用户态决定的,不会到内核态开销。所以协程业非常适合IO密集的应用。

3)Spring中为何不推荐直接使用异步线程注解@Async

@Async使用默认的线程池,而默认的线程池是SimpleAsyncTaskExecutor,具体的代码实现:

public class AsyncSupportConfigurer {
    ...
	/**
     * 异步配置默认使用SimpleAsyncTaskExecutor
	 * The provided task executor is used to:
	 * <ol>
	 * <li>Handle {@link Callable} controller method return values.
	 * <li>Perform blocking writes when streaming to the response
	 * through a reactive (e.g. Reactor, RxJava) controller method return value.
	 * </ol>
	 * <p>By default only a {@link SimpleAsyncTaskExecutor} is used. However when
	 * using the above two use cases, it's recommended to configure an executor
	 * backed by a thread pool such as {@link ThreadPoolTaskExecutor}.
	 * @param taskExecutor the task executor instance to use by default
	 */
	public AsyncSupportConfigurer setTaskExecutor(AsyncTaskExecutor taskExecutor) {
		this.taskExecutor = taskExecutor;
		return this;
	}
    ...
}

public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator implements AsyncListenableTaskExecutor, Serializable {
    ...
    // 执行方法
    protected void doExecute(Runnable task) {
        Thread thread = this.threadFactory != null ? this.threadFactory.newThread(task) : this.createThread(task);
        thread.start();
    }
    ...
}

所以在只用@Async注解的时候, 一般要通过接口AsyncSupportConfigurer自定义线程池。

4)Java中new一个对象的过程

在《深入理解Java虚拟机》中介绍:当虚拟机遇到new 指令后,会检查静态常量池中是否定位到一个类的引用符号,并检查这个类引用符号是否被加载、解析和初始化过,如果没有则执行类的加载过程。

类加载是把class文件变成一个二进制流加载到内存中,classloader加载class文件会把类中的常量值、方法和类信息记在到元数据内,最终生成一个class对象。

加载完成后是连接,为类中的静态变量分配存储空间,设立初始值(还未初始化),如果有静态代码块,也会执行静态代码块内容。

最后是初始化,具体包括给对象分配存储空间,并设置默认值,如果有引用对象会在栈内存中申请一个空间来指向实际对象;执行初始化代码实例,先从父类再到子类;对象实例化完成后,如果栈中存在引用对象指针,还需要把指针对应的对象也实例化出来。

(图来自知乎 健美猪)

5)Java中守护线程和非守护线程

守护线程和非守护线程都是线程,守护线程有一个特征:当主线程销毁时会和主线程一起销毁,比如GC线程,而非守护线程则不会随着主线程销毁。

 

4.4 中间件题目

4.4.1 消息中间件

消息中间件选择
ActiveMqRabbitMqRocketMqKafka
吞吐量万+万+10万+10万+
开发语言javaerlangjavascala
时效msmsmsms
可用性主从架构,高主从架构,高分布式,非常高分布式,非常高
功能成熟性成熟度高,有较多公开文档,使用公司也较多性能好,延时低功能比较完备,扩展性好只支持MQ基本功能,适合大数据量吞吐场景
缺点社区维护性较差,不适合大吞吐量场景erlang语言不利于二次开发,吞吐量较弱,学校维护成本高Java和c++语言支持,c++支持度较弱单机队列超过64个后,load会明显飘高;不支持消息重试,消息有序性维度度较低

 了解Kafka的详情可参见:KafKa消息中间件_西木风落的博客-CSDN博客_kafka消息中间件

RocketMq的源码解读可参见:RocketMq源码解读(一)_西木风落的博客-CSDN博客 

1)怎么解决百万消息积压问题:

首先对积压问题定位:如果是瞬时流量飙高,后续流量回落,并且消费者在正常消费,这种情况持续监控即可;如果是流量持续加大,原来的消费者数量不够,可以选择增大消费者数量;如果消费速度过慢,可以将消费的消息直接写入新的topic下更大的message queue下,新建立消费链路。

4.4.2 Nginx 

参见:Nginx安装配置_西木风落的博客-CSDN博客

4.4.3 mybais

参见:Mybaits 常用问题详解_西木风落的博客-CSDN博客

4.5 微服务题目

1)什么是微服务

(Martin Fowler)目前而言对微服务并没有一个统一的、标准的定义,但通常而言,微服务架构是一种架构模式或者风格,提倡讲单一应用划分为一组小的服务,每个服务运行在自己的进程。

简单来说,微服务就是独立的单一职责应用服务。

2)微服务通讯方式

同步:大部分采用rpc远程调用,spring cloud 采用的是rest接口JSON调用。

异步:通常采用消息队列方式;

3)什么是熔断,什么是降级

熔断是指当某服务不可用或者相应超时,为防止整个系统雪崩,暂时停止对该系统的服务调用;降级是指当系统负载过高时,为防止系统崩溃,内部暂时放弃对一些非重要接口和数据的请求,而直接返回一个预先准备好的数据,提高RT响应,保证系统的稳定性和可用性。

4)为什么要采用微服务设计

优点:

  • 架构上:服务单一职责,各系统服务高内聚,可维护性强,链路抗风险性强;
  • 资源成本上:微服务应用可按需分配资源,有助提升资源利用率;
  • 人力成本上:团队按需分配,降本提效;

缺点:系统复杂性增加,管理和运维成本随着复杂度也会提升,系统依赖性强;

5)eureka、zookeeper、nacos都可以作为注册中心,区别

  • Nacos与Eureka的区别:
    • Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式;
    • 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
    • Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
    • Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
  • zk和eureka的区别:
    • zk是CP原则,当主节点挂掉时,zk会重新选主,选主节点如果有服务注册或者注销,都不能及时同步到集群,可能导致服务不可用;
    • eureka是AP原则,一个节点挂掉后其他节点仍能保持正常工作;

6)分布式锁实现

分布式锁是控制不同进程对共享资源访问一种锁实现形式。常见的视线方式有:数据库锁、Redis锁和zk锁。数据库锁分为悲观(select for update)和乐观锁(版本控制) 。Redis锁一般通过setnx+expire或者扩展命令set ex px nx xx(管道)。zk采用临时有序节点机制。从性能角度Redis>zk>=db。实现角度(从低到高)db>redis>zk;可靠性(从高到底)zk>redis>db

4.6 计算机网络题目

1)http 协议详解

http协议是一个无状态协议,本身不会对发送请求和响应通信状态持久化,目的是为了保持http协议的简单性和高效率性。http请求体有:请求行、header和请求体组成。

  • 请求行:包含method, url和version。e.g. POST   https://event.csdn.net/logstores HTTP/1.1
  • 请求头:可以含带较多信息,比如请求格式 content-type:text/plain;charset=UTF-8

响应报文包含状态行,响应头和响应体组成。

Http存在通信明文,内容可能被窃听,以及内容篡改等风险,所以后续推出了https。

2)https协议

https在TCP协议之上增加了一层SSL/TLS,用于加密http的通信内容。

https通信模型

3)http3协议

HTTP2协议虽然大幅提升了HTTP/1.1的性能,但基于TCP实现的HTTP2遗留下3个问题:

  • 有序字节流引出的队头阻塞Head-of-line blocking),使得HTTP2的多路复用能力大打折扣;
  • TCP与TLS叠加了握手时延,建链时长还有1倍的下降空间;
  • 基于TCP四元组确定一个连接,这种诞生于有线网络的设计,并不适合移动状态下的无线网络,这意味着IP地址的频繁变动会导致TCP连接、TLS会话反复握手,成本高昂

HTTP3协议解决了这些问题:

  • HTTP3基于UDP协议重新定义了连接,在QUIC层实现了无序、并发字节流的传输,解决了队头阻塞问题(包括基于QPACK解决了动态表的队头阻塞);
  • HTTP3重新定义了TLS协议加密QUIC头部的方式,既提高了网络攻击成本,又降低了建立连接的速度(仅需1个RTT就可以同时完成建链与密钥协商);
  • HTTP3 将Packet、QUIC Frame、HTTP3 Frame分离,实现了连接迁移功能,降低了5G环境下高速移动设备的连接维护成本。

4)高并发场景下httpclient优化

httpclient是一个线程安全的类,反复创建存在的开销:对象创建开销和TCP连接创建开销。 所以优化可以从:单列client和缓存保活。具体的client工具类可见:HttpClient高并发下性能优化-http连接池_a363722188的博客-CSDN博客_httpclient 连接池监控

4.7 云原生题目

4.8 设计类题目

1)为什么要设计接口幂等性

什么叫幂等?是指可以使用相同参数重复执行,并能获得相同结果(来自百科)。接口为什么要幂等?个人任务最主要是接口逻辑一致性要求。比如:在电商商品退款时,同一个订单不论执行多少次退款,退款的逻辑都会保持一致(只退款一次);常见实现接口幂等性通过token机制,逻辑实现一锁二判3更新。

2)feed流实现

feed流即持续更新并呈现给用户内容的信息流,特点是将用户主动订阅的若干消息源组合在一起形成内容聚合器,帮助用户持续地获取最新的订阅源内容。比如微博,微信朋友圈。feed流有两种实现模式:push和pull. 

  • push:新的feed发布后,将其插入到发布者所有粉丝的feed流中。这种方式的优点是快速响应用户拉取最新消息,缺点是大v发布信息后,push给所有粉丝会产生较大的流量尖峰;
  • pull:优点是大v发布信息后不会产生流量尖峰,缺点是粉丝收到信息响应较慢。

feed流系统中存储的数据有3部分:作者发布的feed列表,作者和粉丝的关注关系,粉丝用户的feed流信息。轻量化的解决方案作者发布的feed列表存储在NoSQL中,作者和粉丝的关系存储即可是关系型数据库,也可以是KV键值数据库,对于粉丝用户的信息一般保存在内存中。

Feed流优化:作者拥有小客群课直接使用push模式,大v可参用:在线粉丝数量可控,push模式,离线的用户粉丝pull模式。如果在线人数非常多,则仍以pull模式为主。

其次,feed流通常会很大,不可能一次性将所有内容拉取到本地,所以要支持分页查询,通常用lastId+limit式分页器。

最后异步缓存历史feed流信息。默认可缓存用户一月或者一周的信息,当用户浏览到月最后几天时,开始异步组装缓存历史信息,这样减少用户等待时间,提高用户满意度。

4.9 算法类题目

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值