2019阿里巴巴Java研发实习生面经(一面+二面)

本人本科就读于某双非一本,软件工程专业大数据方向,于19年3月经学院老师内推阿里菜鸟的Java研发实习生(包含大数据开发)。经历测评、一面、二面以及笔试,自认为表现还不错但终于还是挂掉了,在这里记录一下,权当攒下一些经验。

一、面试流程

内推投递简历后,审核通过,阿里会发来邮件通知做测评,这种测评一般不涉及专业知识,涵盖对阅读理解、代数、逻辑、几何等基本知识和能力的检验,做过其他公司测评的应该也不陌生。测评时长大概在一个小时,注意网络要稳定。测评一般情况下都能通过。
接下来就到了多轮面试的环节,其中技术面试大概有三到四次。如果是远程电面的话,时间不固定,也不会提前通知,大约在测评完成后的一周内,电话会打来,如果当时不方便也可以另约。建议选择自己手头没有事情的时候,本人的两次电话面试都约在了晚上。

二、一面

一面重基础,而且是很深的底层理论,这方面临时突击是没有用的,建议找一些相关书籍深入学习。这里我凭借记忆大概记录一下面试的问题,尽可能完全涵盖。

·计算机网络部分

1.TCP和IP分别工作在哪一层?
TCP传输层,IP网络层。这里附上一张图供深入理解。
详细请参考here在这里插入图片描述

2.TCP建立连接的过程(三次握手)
设主机B运行一个服务器进程,它先发出一个被动打开命令,告诉它的TCP要准备接收客户进程的连续请求,然后服务进程就处于听的状态。不断检测是否有客户进程发起连续请求,如有,作出响应。设客户进程运行在主机A中,他先向自己的TCP发出主动打开的命令,表明要向某个IP地址的某个端口建立运输连接,过程如下:
1)主机A的TCP向主机B的TCP发出连接请求报文段,其首部中的同步比特SYN应置1,同时选择一个序号x,表明在后面传送数据时的第一个数据字节的序号是x。
2)主机B的TCP收到连接请求报文段后,如同意,则发挥确认。在确认报文段中应将SYN置为1,确认号应为x+1,同时也为自己选择一个序号y
3)主机A的TCP收到此报文段后,还要向B给出确认,其确认号为y+1

4)主机A的TCP通知上层应用进程,连接已经建立,当主机B的TCP收到主机A的确认后,也通知上层应用进程,连接建立。
这里可以向面试官补充:
TCP释放连接的过程(四次分手):
1)数据传输结束后,主机A的应用进程先向其TCP发出释放连接请求,不在发送数据。TCP通知对方要释放从A到B的连接,将发往主机B的TCP报文段首部的终止比特FIN置为1,序号u等于已传送数据的最后一个字节的序号加1。
2)主机B的TCP收到释放连接通知后发出确认,其序号为u+1,同时通知应用进程,这样A到B的连接就释放了,连接处于半关闭状态。主机B不在接受主机A发来的数据;但主机B还向A发送数据,主机A若正确接收数据仍需要发送确认。
3)在主机B向主机A的数据发送结束后,其应用进程就通知TCP释放连接。主机B发出的连接释放报文段必须将终止比特置为1,并使其序号w等于前面已经传送过的数据的最后一个字节的序号加 1,还必须重复上次已发送过的ACK=u+1。
4)主机A对主机B的连接释放报文段发出确认,将ACK置为1,ACK=w+1, seq=u+1。这样才把从B到A的反方向连接释放掉,主机A的TCP再向其应用进程报告,整个连接已经全部释放。
另外,注意的问题:
三次握手建立连接时,发送方再次发送确认的必要性
主要是为了防止已失效的连接请求报文段突然又传到了B,因而产生错误。假定出现一种异常情况,即A发出的第一个连接请求报文段并没有丢失,而是在某些网络结点长时间滞留了,一直延迟到连接释放以后的某个时间才到达B,本来这是一个早已失效的报文段。但B收到此失效的连接请求报文段后,就误认为是A又发出一次新的连接请求,于是就向A发出确认报文段,同意建立连接。假定不采用三次握手,那么只要B发出确认,新的连接就建立了,这样一直等待A发来数据,B的许多资源就这样白白浪费了。
四次挥手释放连接时,等待2MSL的意义
第一,为了保证A发送的最有一个ACK报文段能够到达B。这个ACK报文段有可能丢失,因而使处在LAST-ACK状态的B收不到对已发送的FIN和ACK报文段的确认。B会超时重传这个FIN和ACK报文段,而A就能在2MSL时间内收到这个重传的ACK+FIN报文段。接着A重传一次确认。
第二,就是防止上面提到的已失效的连接请求报文段出现在本连接中,A在发送完最有一个ACK报文段后,再经过2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。

3.域名解析的过程
1)找缓存
2)找本机hosts文件
3)找DNS服务器
可参考here

·Java部分

1.堆和栈的区别(JVM内存划分)
堆:存放实例化的对象,是内存中最大的一块,也是垃圾回收器管理的主要区域。线程共享。
栈:分为Java虚拟机栈和本地方法栈,存放成员变量表,方法的调用到执行即使入栈到出栈的过程。线程隔离。

2.Hashmap和Hashtable的区别(线程安全的集合类)
Hashmap线程不安全,Hashtable线程安全(为所有public方法加syncronized)
Hashmap允许key为null,Hashtable不允许空,执行hashcode()时会抛NullPointException
确切来说,Hashtable虽然线程安全,但效率低下,基本不再使用。
关于Hashmap的相关知识点,可以参考我另一篇博客。

3.RuntimeException和CheckedException
前者无需显式声明抛出(当然也可以),常为运行时的异常;
后者需要try-catch/throw,常为编译时的异常。
(可能会让举例)

4.Java中的序列化和反序列化
序列化:把堆内存中的 Java 对象数据,通过某种方式把对象存储到磁盘文件中或者传递给其他网络节点(在网络上传输)。这个过程称为序列化。通俗来说就是将数据结构或对象转换成二进制串的过程;
反序列化:把磁盘文件中的对象数据或者把网络节点上的对象数据,恢复成Java对象模型的过程。也就是将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程。
序列化和反序列化的目的:
1)在分布式系统中,此时需要把对象在网络上传输,就得把对象数据转换为二进制形式,需要共享的数据的 JavaBean 对象,都需要做序列化。
2)服务器钝化:如果服务器发现某些对象好久没活动了,那么服务器就会把这些内存中的对象持久化在本地磁盘文件中(Java对象转换为二进制文件);如果服务器发现某些对象需要活动时,先去内存中寻找,找不到再去磁盘文件中反序列化对象数据,恢复成 Java 对象。这样能节省服务器内存。
序列化的过程:
1)需要做序列化的对象的类,必须实现序列化接口:Java.lang.Serializable 接口(这是一个标志接口,没有任何抽象方法),Java 中大多数类都实现了该接口,比如:String,Integer
2)底层会判断,如果当前对象是 Serializable 的实例,才允许做序列化,Java对象 instanceof Serializable 来判断。
3)在 Java 中使用对象流来完成序列化和反序列化。
PS:
ObjectOutputStream通过writeObject()方法做序列化操作
ObjectInputStream通过readObject() 方法做反序列化操作

5.多态和重载的区别
两者的区别在于编译器何时去寻找所要调用的具体方法:
对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”;
(
重载,是指在一个类中的同名不同参数的函数调用。C++中继承的时候存在隐藏,Java中不存在。
重载、覆盖,是继承中的概念。
)
而对于多态,只有等到方法调用的那一刻,编译器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”。
可参考here
6.Java五大包
lang(默认导入,最基础的包);
util(操作数组、集合等的工具类);
io(涉及io编程);
sql(涉及数据库编程);
net(涉及网络编程)

7.静态方法和非静态方法的区别
1)静态方法是类所有,可直接调用;非静态方法需要实例化后调用。
2)静态方法内部只能出现static,且不能用this关键字。
3)静态方法比非静态方法效率高,但不能自动销毁。
4)静态方法、变量共享内存空间;非静态方法及变量有独立的内存空间。

8.多线程和锁机制相关问题
这里有我的一篇博客→多线程和锁机制

9.框架部分
这方面和本人所学有所偏差,只回答了一些MVC的底层原理,面试官也就没再追问。可以深入学习一下SSH、SSM或者SpringBoot等常用的相关框架知识。

·数据结构部分

1.堆排序
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。
堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列。
有篇博客总结得很不错:here

2.二叉平衡树(或与红黑树的区别)
二叉平衡树:
任何节点的两个子树高度最大差别为1;
红黑树:
1)每个节点都是红色或黑色;
2)根节点永远是黑色;
3)所有叶节点永远都是空节点,且是黑色;
4)每个红色节点的两个子节点都是黑色,从每个叶子到根的路径上不会有连续两个红色节点
红黑树并不追求“完全平衡”,它只要求部分地达到平衡,降低了对旋转的要求,从而提高性能。

ps: 最后面试官可能会问到关于开源工具的相关问题,这里只回答自己实际使用过或最擅长的即可,问题相对简单。
如:Git的常用指令?
git clone 复制
git push 提交
git pull 同步

三、二面

二面重思想。一面我提及了过多的大数据、分布式术语,所以二面的面试官可能是同部门的大数据工程师,这一轮面试也着重于大数据相关的理论和技术。当然Java部分也会更加深入。

·Java部分

1.线程池中的corePoolSize和maximumPoolSize
corePoolSize:核心池的大小。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
具体请参考
Java并发编程:线程池的使用:
https://www.cnblogs.com/dolphin0520/p/3932921.html
线程池corePoolSize和maximumPoolSize关系:
https://blog.csdn.net/sunct/article/details/85056417

2.servlet运行原理及生命周期
在这里插入图片描述
Servlet生命周期定义了一个Servlet如何被加载、初始化,以及它怎样接收请求、响应请求,提供服务。Servlet的生命周期也分别对应着这几个方法:
1)init()方法→初始化
在Servlet的生命周期中,仅执行一次init()方法,它是在服务器装入Servlet时执行的,可以配置服务器,以在启动服务器或客户机首次访问Servlet时装入Servlet。无论有多少客户机访问Servlet,都不会重复执行init();
2)service()方法→请求处理
它是Servlet的核心,每当一个客户请求一个HttpServlet对象,该对象的Service()方法就要调用,而且传递给这个方法一个“请求”(ServletRequest)对象和一个“响应”(ServletResponse)对象作为参数。在HttpServlet中已存在Service()方法。默认的服务功能是调用与HTTP请求的方法相应的do功能。
3)destroy()方法→卸载/销毁servlet
仅执行一次,在服务器端停止且卸载Servlet时执行该方法,有点类似于C++的delete方法。一个Servlet在运行service()方法时可能会产生其他的线程,因此需要确认在调用destroy()方法时,这些线程已经终止或完成。
参考:https://www.cnblogs.com/fifiyong/p/6390805.html

3.sleep()和wait()的区别
1)sleep()方法是属于Thread类中的;而wait()方法则属于Object类中。
2)sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁;
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。

4.syncronized和volatile的区别
1)volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
2)volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
3)volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性。
4)volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
5)volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
若想更好理解,请参考:https://blog.csdn.net/it_manman/article/details/79497807

另外还有设计模式,可参考:https://blog.csdn.net/qq_37495786/article/details/88740070

大数据部分
首先问了一下关于我简历上提到的一个实时计算的项目,涉及了kafka、flume、spark streaming等底层各种琐碎知识以及相应场景下可能会出现的问题以及解决方案,这里不再赘述,只总结一下大数据方面的核心问题。

1.Spark的运行原理和shuffle
使用spark-submit提交一个Spark作业之后,这个作业就会启动一个对应的Driver进程。根据你使用的部署模式(deploy-mode)不同,Driver进程可能在本地启动,也可能在集群中某个工作节点上启动。而Driver进程要做的第一件事情,就是向集群管理器(可以是Spark Standalone集群,也可以是其他的资源管理集群,美团•大众点评使用的是YARN作为资源管理集群)申请运行Spark作业需要使用的资源,这里的资源指的就是Executor进程。YARN集群管理器会根据我们为Spark作业设置的资源参数,在各个工作节点上,启动一定数量的Executor进程,每个Executor进程都占有一定数量的内存和CPU core。
在申请到了作业执行所需的资源之后,Driver进程就会开始调度和执行我们编写的作业代码了。Driver进程会将我们编写的Spark作业代码分拆为多个stage,每个stage执行一部分代码片段,并为每个stage创建一批Task,然后将这些Task分配到各个Executor进程中执行。Task是最小的计算单元,负责执行一模一样的计算逻辑(也就是我们自己编写的某个代码片段),只是每个Task处理的数据不同而已。一个stage的所有Task都执行完毕之后,会在各个节点本地的磁盘文件中写入计算中间结果,然后Driver就会调度运行下一个stage。下一个stage的Task的输入数据就是上一个stage输出的中间结果。如此循环往复,直到将我们自己编写的代码逻辑全部执行完,并且计算完所有的数据,得到我们想要的结果为止。

Spark是根据shuffle类算子来进行stage的划分。如果我们的代码中执行了某个shuffle类算子(比如reduceByKey、join等),那么就会在该算子处,划分出一个stage界限来。可以大致理解为,shuffle算子执行之前的代码会被划分为一个stage,shuffle算子执行以及之后的代码会被划分为下一个stage。因此一个stage刚开始执行的时候,它的每个Task可能都会从上一个stage的Task所在的节点,去通过网络传输拉取需要自己处理的所有key,然后对拉取到的所有相同的key使用我们自己编写的算子函数执行聚合操作(比如reduceByKey()算子接收的函数)。这个过程就是shuffle。
当我们在代码中执行了cache/persist等持久化操作时,根据我们选择的持久化级别的不同,每个Task计算出来的数据也会保存到Executor进程的内存或者所在节点的磁盘文件中。
因此Executor的内存主要分为三块:第一块是让Task执行我们自己编写的代码时使用,默认是占Executor总内存的20%;第二块是让Task通过shuffle过程拉取了上一个stage的Task的输出后,进行聚合等操作时使用,默认也是占Executor总内存的20%;第三块是让RDD持久化时使用,默认占Executor总内存的60%。
Task的执行速度是跟每个Executor进程的CPU core数量有直接关系的。一个CPU core同一时间只能执行一个线程。而每个Executor进程上分配到的多个Task,都是以每个Task一条线程的方式,多线程并发运行的。如果CPU core数量比较充足,而且分配到的Task数量比较合理,那么通常来说,可以比较快速和高效地执行完这些Task线程。
以上是运行原理的描述。

shuffle 是划分 DAG 中 stage 的标识,同时也是影响 Spark 执行速度的关键步骤.
RDD 的 Transformation 函数中,又分为窄依赖(narrow dependency)和宽依赖(wide dependency)的操作.窄依赖跟宽依赖的区别是是否发生 shuffle(洗牌) 操作.宽依赖会发生 shuffle 操作. 窄依赖是子 RDD的各个分片(partition)不依赖于其他分片,能够独立计算得到结果,宽依赖指子 RDD 的各个分片会依赖于父RDD 的多个分片,所以会造成父 RDD 的各个分片在集群中重新分片,即shuffle
https://blog.csdn.net/databatman/article/details/53023818

持续更新中
其他相关问题,可参考我另外的博客 点击前往

  • 4
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值