转载自 做到我这样,你也能拿到京东Offer
最近,春招已经基本接近尾声了,我找了几位拿到名企Offer的粉丝,请他们总结了面试经验,近期会分批的推送给大家。希望给那些正在准备秋招的同学提供些帮助。这是一篇7000字的长文,作者是一名211小硕,拿到了京东的研发岗Offer,总结了自己参加京东校招并拿到offer的面试题及答案,还有学习方法,Hollis只做了简单的格式修改。对于一个校招生来说,能把JVM、网络知识、JDK源码等了解的这么全面,他不进京东,谁进京东。做到他这样,你,也可以。
一面
面试时间较长,回答速度也较快,所有问题都进行了完整的回答。形式为电话面试,都是基础,难度一般,不要紧张,回答知识点即可。
内容主要包括jvm相关,网络知识(TCP/IP,DNS),JDK源码(HashMap, ArrayList, HashTable等)
JVM部分
这部分主要考的是知识点的串联能力,面试官提出一个问题时,要把该问题相关的知识点都罗列出来(在说之前可以询问面试官是否需要详细讲述该知识点)。
参考书籍:深入理解Java虚拟机-周志明 神书!神书!神书!建议多刷几遍,书中的所有知识点可以通过JAVA运行时区域和JAVA的内存模型与线程两个大模块罗列完全。
常考内容有:GC,JAVA线程实现方式,volatile底层原理,线程安全,锁与CAS等
1. 讲下JAVA的运行时区域
回答:运行时数据区整体分为两类 线程私有和线程共享。
线程私有的包括:
-
程序计数器
-
若正在执行的是java方法,则计数器记录的是正在执行的字节码指令的地址
-
若正在执行的是native方法,则计数器为空
-
该区域是唯一一个不会导致outofmemoryError的区域
-
-
虚拟机栈
-
描述的是Java方法执行的内存模型:每个方法都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息
-
局部变量表存放了编译期可知的基本数据类型,对象引用,和returnAddress类型(指向一条字节码指令地址),局部变量表的内存空间在编译器确定,在运行期不变
-
可导致两种异常:线程请求的栈深度大于虚拟机允许的深度-StackOverflowError;虚拟机无法申请到足够的内存-OutOfMemoryError
-
-
本地方法栈
-
和虚拟机栈类似,但它是为Native方法服务的
-
线程共享的包括:
-
堆
-
java堆是被所有线程共享的内存区域,在虚拟机启动时创建,用来分配对象实例和数组
-
堆是垃圾回收器主要管理的区域,堆可分为新生代和老年代
-
从内存分配角度看,堆可划分出多个线程私有的分配缓冲区(TLAB)
-
大小可通过 -Xmx 和 -Xms 控制
-
-
方法区
-
用来存放虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等信息
-
GC会回收该区域的常量池和进行类型的卸载 *运行时常量池
-
Class文件的常量池用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放在运行时常量池中
-
还把翻译出来的直接引用也放在运行时常量池中,运行时产生的常量也放在里面
-
2. 简单说下垃圾回收机制
大致思路: 要进行垃圾回收,首先要判断一个对象是否活着,这就引出了两种方法…
引用计数法和可达性分析法
gc roots 类型
引用类型
两次标记过程
垃圾回收算法
内存分配策略
触发垃圾回收
垃圾回收器
也会回收方法区
回答:要进行垃圾回收,首先要判断对象是否存活,引出了两个方法:
-
引用计数法
-
思想:给对象设置引用计数器,没引用该对象一次,计数器就+1,引用失效时,计数器就-1,当任意时候引用计数器的值都为0时,则该对象可被回收
-
Java不适用原因:无法解决对象互相循环引用的问题
-
-
可达性分析法
-
虚拟机栈(栈帧中的局部变量表)中引用的对象
-
方法区中类静态属性引用的对象
-
方法区中常量引用的对象
-
本地方法栈中JNI(Native方法)引用的对象
-
以GC Roots为起点,从这些起点开始向下搜索,经过的路径称为引用链。若一个对象到GC Roots之间没有任何引用链,则该对象是不可达的。
-
那么可作为GC Roots的对象有
-
-
在可达性分析过程中,对象引用类型会对对象的生命周期产生影响,JAVA中有这几种类型的引用:
-
强引用:只要该引用还有效,GC就不会回收
-
软引用:内存空间足够时不进行回收,在内存溢出发生前进行回收、用SoftReference类实现
-
弱引用:弱引用关联的对象只能存活到下一次Gc收集、用WeakReference类实现
-
虚引用:无法通过虚引用获得对象实例,也不会对对象的生存时间产生影响、唯一目的:当该对象被Gc收集时,收到一个系统通知。用PhantomReference类实现
-
一个对象真正不可用,要经历两次标记过程:
-
首先进行可达性分析,筛选出与GC Roots没用引用链的对象,进行第一次标记
-
第一次标记后,再进行一次筛选,筛选条件是是否有必要执行finalize()方法。若对象有没有重写finalize()方法,或者finalize()是否已被jvm调用过,则没必要执行,GC会回收该对象
-
若有必要执行,则该对象会被放入F-Queue中,由jvm开启一个低优先级的线程去执行它(但不一定等待finalize执行完毕)。
-
Finalize()是对象最后一次自救的机会,若对象在finalize()中重新加入到引用链中,则它会被移出要回收的对象的集合。其他对象则会被第二次标记,进行回收
JAVA中的垃圾回收算法有:
-
标记-清除(Mark-Sweep)
-
两个阶段:标记, 清除
-
缺点:两个阶段的效率都不高;容易产生大量的内存碎片
-
-
复制(Copying)
-
把内存分成大小相同的两块,当一块的内存用完了,就把可用对象复制到另一块上,将使用过的一块一次性清理掉
-
缺点:浪费了一半内存
-
-
标记-整理(Mark-Compact)
-
标记后,让所有存活的对象移到一端,然后直接清理掉端边界以外的内存
-
-
分代收集
-
把堆分为新生代和老年代
-
新生代使用复制算法
-
将新生代内存分为一块大的Eden区和两块小的Survivor;每次使用Eden和一个Survivor,回收时将Eden和Survivor存活的对象复制到另一个Survivor(HotSpot的比例Eden:Survivor = 8:1)
-
老年代使用标记-清理或者标记-整理
-
触发GC又涉及到了内存分配规则: (对象主要分配在Eden,若启动了本地线程分配缓冲,将优先在TLAB上分配)
-
对象优先在Eden分配
-
当Eden区没有足够的空间时就会发起一次Minor GC
-
-
大对象直接进入老年代
-
典型的大对象是很长的字符串和数组
-
-
长期存活的对象进入老年代
-
每个对象有年龄计数器,每经过一次GC,计数器值加一,当到达一定程度时(默认15),就会进入老年代
-
年龄的阈值可通过参数 -XX:MaxTenuringThreshold设置
-
-
对象年龄的判定
-
Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于等于该年龄的对象就可直接进入老年代,无须等到MaxTenuringThreshold要求的年龄
-
-
空间分配担保
-
发生Minor GC前,jvm会检查老年代最大可用的连续空间是否大于新生代所有对象总空间,若大于,则Minor GC是安全的
-
若不大于,jvm会查看HandlePromotionFailure是否允许担保失败,若不允许,则改为一次Full GC
-
若允许担保失败,则检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,若大于,则尝试进行Minor GC;若小于,则要改为Full GC
-
垃圾收集器:
-
Serial(串行收集器)
-
特性:单线程,stop the world,采用复制算法
-
应用场景:jvm在Client模式下默认的新生代收集器
-
优点:简单高效
-
-
ParNew
-
特点:是Serial的多线程版本,采用复制算法
-
应用场景:在Server模式下常用的新生代收集器,可与CMS配合工作
-
-
Parallel Scavenge
-
特点:并行的多线程收集器,采用复制算法,吞吐量优先,有自适应调节策略
-
应用场景:需要吞吐量大的时候
-
-
SerialOld
-
特点:Serial的老年代版本,单线程,使用标记-整理算法
-
-
Parallel Old
-
Parallel Scavenge的老年代版本,多线程,标记-整理算法
-
-
CMS
-
对CPU资源敏感
-
无法处理浮动垃圾(并发清除 时,用户线程仍在运行,此时产生的垃圾为浮动垃圾)
-
产生大量的空间碎片
-
初始标记:stop the world 标记GC Roots能直接关联到的对象
-
并发标记:进行GC Roots Tracing
-
重新标记:stop the world;修正并发标记期间因用户程序继续运作而导致标记产生变动的 那一部分对象的标记记录
-
并发清除:清除对象
-
特点:以最短回收停顿时间为目标,使用标记-清除算法
-
过程:
-
优点:并发收集,低停顿
-
缺点:
-
-
G1
-
初始标记:stop the world 标记GC Roots能直接关联到的对象
-
并发标记:可达性分析
-
最终标记:修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录
-
筛选回收:筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划
-
并行与并发
-
分代收集
-
空间整合:从整体看是基于“标记-整理”的,从局部(两个region之间)看是基于“复制”的。
-
可预测的停顿:使用者可明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。
-
特点:面向服务端应用,将整个堆划分为大小相同的region。
-
执行过程:
-
GC自适应调节策略 Parallel Scavenge收集器有一个参数-XX:+UseAdaptiveSizePolicy。当这个参数打开之后,就不需要手工指定新生代的大小、Eden与Survivor区的比例、晋升老年代对象年龄等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics)。
-
-
(垃圾回收器部分重点讲CMS和G1)
最后提一下也会回收方法区:
-
永久代中主要回收两部分内容:废弃常量和无用的类
-
废弃常量回收和对象的回收类似
-
无用的类需满足3个条件
-
该类的所有实例对象已被回收
-
加载该类的ClassLoader已被回收
-
该类的Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
-
上面的知识点在你多刷几遍书,脑中形成相应的知识网后能很全面的说出来。
网络部分
网络知识在面试中非常重要,尤其是TCP,DNS,HTTP等知识点。
该部分我的参考书籍是:图解HTTP,图解TCP/IP(对于开发来说,这两本书在网络方面的讲解应该够用了),以及相关博客。
回答该类问题时,依然要从面试官提到的问题进行扩散,把相关的问题自己抛出来进行讲述(在抛出讲述时可以询问面试官是否需要进行详细的讲解)
1. 讲一下TCP三次握手
对于该问题,可以直接关联TCP四次分手进行回答。若是当场面试,可以在纸上画出客户端和服务端的TCP状态序列
然后自己可以抛出相关的问题进行回答,如:
为什么不采用两次握手,SYN半连接攻击,TIMEWAIT数量太多怎么办,为什么连接的时候是3次握手,关闭的时候是4次分手,为什么TIMEWAIT状态需要经过2MSL(最大报文段生存时间)才能回到CLOSE状态等等问题(这些问题在网上都有讲解,这里就不赘述了)。
2. TCP和UDP的区别(很常见的问题):
-
TCP面向连接(如打电话要先拨号建立连接) UDP是无连接的,即发送数据之前不需要建立连接
-
TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
-
TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
-
UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
-
每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
-
TCP首部开销20字节;UDP的首部开销小,只有8个字节
-
TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
3. 讲下ARP原理
-
根据目标Ip地址借助ARP请求和ARP响应来确定目标的MAC地址
-
原理:通过广播发送ARP请求,Ip地址一致的主机接收该请求,然后将自己的MAC地址加入的ARP响应包返回给源主机。要进行MAC地址缓存来避免占用网络流量
-
每个主机都会在自己的ARP缓冲区建立一个ARP列表,表示Ip地址和MAC地址的对应关系
-
当源主机要发送数据时,首先检查ARP列表中是否有对应Ip地址的目标主机的MAC地址,若有,则直接发送,若无,则向本网段的所有主机发送ARP数据包,该数据包包括的内容有:源Ip地址,源MAC地址,目标主机Ip地址
-
当本网络的所有主机收到该ARP数据包时,首先检查书包中的Ip地址是否是自己的Ip地址,若不是则丢弃,若是,则首先从数据包中取出源主机的IP和MAC地址写入自己的ARP列表中,若已存在,则覆盖;然后将自己的MAC地址写入ARP响应包中,告诉源主机自己的MAC地址
-
源主机收到ARP响应包后,将目标主机的IP和MAC地址写入ARP列表中。若源主机一直没有收到ARP响应,则ARP查询失败
-
(广播发送ARP请求,单播发送ARP响应) 此时,自己可以加上ARP攻击及免费ARP相关知识点(可自行搜索)
-
JDK源码部分
这部分主要在于自己平时的积累,可以跟着相关的博客看源码。常考的内容有String,集合框架,foreach(Iterator及fail-fast机制)等内容。
1. HashMap了解吗,说一下
这里就不详细说了,大致思路是jdk7和jdk8的实现原理及区别(重点有实现的数据结构,存储单元从Entry到Node的转变,加载因子,什么时候扩容,jdk1.8扩容的具体实现方式等等),HashMap和HashTable的区别,HahsMap和HashSet的关系。要结合源码说。
ArrayList了解吗,说一下
重点是底层实现方式,扩容机制,以及LinkedList的底层实现方式;它们之间的区别。要结合源码说
二面
面试时间不长,个别问题回答的有些底虚。主要考察的是知识面的广度和对技术的热爱程度,以及对做过项目的熟悉程度。还是那句话,坦诚地回答问题,不会就是不会,如果假装会但是被接下来地问题问倒的话,那就没戏了。
实习时项目的相关问题
每个人的项目都不同,只列举几个问题
项目是怎样预防sql注入的
回答:用的是mybatis,sql语句中用#{},#{}表示一个占位符号,通过#{}可以实现preparedStatement向占位符中设置值,jdbc有个预编译的过程可以有效预防sql注入,尽量不用${},它是个拼接符,用来拼接sql字符串。
项目中你做到的redis缓存相关的切面配置(简历中有写)
-
切面分析:@Aspect
-
切面: 查询前先查询redis,若查询不到,则查数据库,得到数据后存到redis中
-
目标方法:查询数据库
-
-
前置:查询之前先查redis
-
后置:从数据库中查到的内容放到redis中
-
切面中的通知定位环绕通知:@Around
-
然后又问了aop的实现原理(jdk动态代理和cglib字节码增强,在回答时要说出底层源码)
喜欢什么技术
答:分布式,实习时虽然自己做的是企业后台,并没有涉及到很多分布式的内容,但是会经常从同事那里了解一些分布式的技术感觉很有趣
都了解到了哪些分布式的技术
答:进行反向代理和负载均衡的nginx,及实现高可用的keepalived+nginx;内存数据库redis及它的基本数据类型和持久化方式;用于做注册中心的zookeeper和服务治理的dubbo;防止用户重复登录的单点登录;分布式的文件存储系统fastdfs;页面静态化处理的freemarker;以及用于搜索的solr(这部分由于有些技术自己只是会用,不了解底层,所以说的吞吞吐吐,很没底气,其实没必要,大大方方说出来后再说明自己了解的程度即可)
感觉自己哪方面有欠缺
答:spring源码不太了解,另外linux方面有些薄弱,正在补充相关知识。(可以加上自己目前正在看哪些书)
jdk9有哪些认识
答:不太清楚,还没了解(内心状态:卧槽,jdk9什么时候出来的) (这个问题可以很好的了解到求职者对技术的热爱程度,平时可以多关注技术的发展方向,版本迭代。这方面可以通过关注一些优质的公众号:如Hollis)
为什么想来互联网公司
答:可能是第一份实习工作的影响吧, 面试官:还有呢? 我:我也说不清楚,就是想去互联网公司(说完之后就想宰了自己)
HR面
hr面重要的也是真诚,还有就是表达好自己想去该公司及对该公司的了解
目前有几个offer
答:1个,但是已经拒了
为什么拒了
答:因为它不是互联网公司
你为什么想在互联网公司发展
答:工资高是一方面,另外在互联网公司中一般可以更快接触到新的技术
为什么想来京东
答:因为京东是我关注很久的公司,从京东第一次做秋招视频直播就开始关注了。首先京东是一家互联网公司,其次京东近些年的发展有目共睹,而且京东正在向技术公司转型,相信自己可以学到很多东西。
你有什么缺点
答:压力大时喜欢通过吃东西来排解压力,所以又要减肥什么的很麻烦
还有呢
答:喜欢熬夜
还有呢
答:emmmm,哎呀,想不起来了 (其实,关于优点和缺点的问题应该好好总结一下,不然问的当时容易懵逼) 还有几个问题不太记得了,大概就是实习时自己意见和同事不一样时是怎么解决的。对于这种问题,最好举个实际的例子。
总结
其实网上的大多数面经已经说的很清楚,一定要形成自己的知识树,如果仅仅想通过看面经来通过面试,会很难。自己平常一定要积累知识,把知识点分类进行记录,在面试过程中主动说出面试官所提问题的关联问题的解决方案能够加分,体现自己的知识串联能力。关于要积累哪些知识,Hollis在他的java成神之路已经写的很详细。