这位同学是学习群的同学,之前已经写了一篇阿里一面的面经:#
专科生阿里大数据一面面经「已过」「附详细答案」
如今又收到了作业帮的大数据offer,
他是甘肃职业学校的专科生,而且是罕见的警犬专业
,但他学习非常刻苦认真,值得我们学习!
以下答案由我重新整理:
一面
1、先是两分钟左右的自我介绍,你有什么优势?
2、数据库索引结构有哪些?
(1)生成索引,建立二叉查找树 生成索引
(2)建立B-Tree 生成索引
(3)建立B+-Tree 生成索引
(4)建立Hash,基于InnoDB和MyISAM的Mysql不显示支持哈希(5) 位图数据结构,BitMap,少量主流数据库使用,如Oracle,mysql不支持;
3、紧接着又问了如何定位并优化慢查询sql?
(1)根据慢日志定位慢查询SQL
(2)使用explain等工具分析SQL
(3)修改SQL或者尽量让SQL走索引以优化查询效率
4、索引是建立的越多越好吗?
(1)数据量小的表不需要建立索引,建立会增加额外的索引开销;(2)数据变更需要维护索引,因此更多的索引意味着更多的维护成本;(3)更多的索引也意味着需要更多的空间
5、说一下你知道的垃圾收集算法和垃圾收集器
垃圾收集算法(垃圾回收的方法论):
1.复制算法:此种算法是将空间分成两部分,每次使用其中的一部分。在垃圾回收时,将正在使用的内存中存活的对象复制到未使用的内存中,然后清除正在使用的内存。这种算法不会产生碎片,但会造成空间的利用率低。
2.记清除法:此种算法是将垃圾收集分为两个阶段,标记阶段和清除阶段。标记阶段是将所有需要回收的对象进行标记,然后标记结束后,对标记的对象进行回收。这种算法会产生大量碎片,效率低下。
3.标记整理法:此种算法是将所有需要回收的对象进行标记后,将所有存活的对象移到另外一端,其他不需要保留的全部清理。此种算法解决了标记清除法所产生的碎片问题。
4.分代法:根据对象生命周期的长短进行分块,在根据每块区间不同的特点,使用不同的算法进行垃圾回收,从而提高垃圾回收效率。比如:Java虚拟机中的堆采用了这种方法分成了新生代和老年代,然后对于不同的代采用不同的垃圾回收算法。新生代采用了复制算法,老年代采用了标记清除法。
5.分区法:将空间分成连续多个不等的小区间,每个区间单独使用,独立回收。优点是一个可以控制多个区间的回收。
6.引用计数法:此种算法是对对象设置一个引用计数器,每增加一个变量对它的引用,计数器就加1,反之,减1。只有当计数器的值为0时,该对象才会被回收。该算法简单,也有缺点,对对象操作频繁,增加了系统消耗;也无法处理循环引用的情况。
1.新生代:Serial、ParNew、parallel Scaveng
2.老年代:Serail old、ParNew old、CMS
========================================
a.Serial:它是一个单线程的垃圾回收器,单线程的意义是:只会使用一个CPU或者一条垃圾收集线程去完成垃圾收集工作。而在它进行垃圾收集工作的时候,其他线程必须暂停,直到垃圾收集结束。
b.ParNew:它是serail的多线程版本,基本操作和Serial一样。该收集器一般和CMS搭配工作。
c.parallel Scavenge:此收集器的目的是达到一个可控制的吞吐量。
吞吐量=运行用户代码的时间/(运行用户代码的时间+垃圾回收的时间)
d.GC自适应策略:JVM会根据当前系统运行情况收集性能监控情况,动态调整这些参数以提供最适合的停顿时间或者吞吐量。
1.Serial Old:是Serail 的老版本,也是单线程收集器,该收集器主要是给Client模式下的虚拟机使用的。
Note:分代收集算法:新生代采用复制算法,并暂停所有用户线程。 老年代采用标记整理法,并暂停所有用户线程。
2.Parallel Old:是Parallel的老版本,使用多线程和标记整理算法进行垃圾回收。
3.CMS:是一种以获取最短回收停顿时间为目标的收集器。该收集器是基于"标记清除"算法实现的。
1.并发和并行:使用多线程来缩短暂停时间,G1收集器通过并行的方式让Java继续执行。
G1收集器将整个Java堆划分为多个大小相等的独立区域(Region)。G1收集器之所以可以有计划地避免在整个Java堆中进行全区域的垃圾收据,是因为G1收集器跟踪各个Region里面的垃圾堆积的价值大小(回收获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。即Grabage-First。
在G1收集器中,Region之间的对象引用以及其他收集器中的新生代与老年代之间的对象引用,虚拟机都是通过Remembered Set来避免全堆扫描的。G1中每个Region都有一个与之对应的Remembered Set。在新建对象时,JVM会将相关的引用信息记录到被引用对象所属的Region的Remembered Set中。当进行回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对堆进行扫描也不会有遗漏。
4、可预测的停顿:建议可预测的停顿时间模型,让使用者明确在y一个长度为M毫秒的时间片段内,消耗在垃圾收集器上的时间不得超过N毫秒,达到了实时的垃圾收集器。
1、初始标记(只是标记一下GC Roots能直接关联到的对象,并修改可以得Region中创建新对象,这阶段需要停顿线程,但耗时很短)
2、并发标记(从GC Roots开始对堆中对象进行可达性分析,找出存活对象)
3、最终标记(修正在并发标记期间因月洪湖程序继续运行而导致标记产生变动的那一部分标记记录)
4、筛选回收(首先对各个Regin的回收价值和成本进行排序,根据用户所期待的GC停顿时间指定回收计划,回收一部分Region)
Java 堆主要分为2个区域-年轻代与老年代,年轻代包括Eden 区和 Survivor 区,Survivor 区又分From区和 To区。如下图:
对象会优先在新生代 Eden 区中进行分配,当 Eden 区空间不足时,虚拟机会使用复制算法发起一次 Minor GC(Young GC),清除掉垃圾对象。之后,Eden 区中绝大部分对象会被回收,而那些无需回收的存活对象,将会进到 Survivor 的 From 区(From 区内存不足时,直接进入 Old 区)。
Survivor 区相当于是 Eden 区和 Old 区的一个缓冲区。如果没有Survivor 区域,Old区将很快被填满,就会触发Major GC(因为Major GC一般伴随着Minor GC,也可以看做触发了Full GC)。Survivor 的存在意义就是减少被送到老年代的对象,进而减少 Major GC 的发生。
Survivor 又分为2个区,一个是 From 区,一个是 To 区。每次执行 Minor GC,会将 Eden 区和 From 存活的对象放到 Survivor 的 To 区(To 区内存不足时,直接进入 Old 区)。
为什么要将Survivor区分成From和To两个区?
为了解决内存碎片化的问题。Minor GC 执行后,Eden 区会清空,存活的对象放到了 Survivor 区,而之前 Survivor 区中的对象,可能也有一些是需要被清除的。这时候JVM要使用标记清除算法去清除垃圾对象,而标记清除算法最大的问题就是内存碎片,由于在Eden区中有很多对象是“朝生夕死”的,所以必然会让内存产生严重的碎片化。Survivor 有2个区域,每次 Minor GC时,会将之前 Eden 区和 From 区中的存活对象复制到 To 区域。第二次 Minor GC 时,再将 Eden 区和 To 区中的存活对象再复制到 From 区域,以此反复。这样一来,总有一个Survivor区域是空闲的。这样就解决了内存碎片的问题。
Old区据着2/3的堆内存空间,当对象从新生代中存活下来,就会被拷贝到这里。Major GC 会清理Old区中的对象,每次Major GC 都会触发“Stop-The-World”。内存越大,执行的时间也就越长。由于老年代中对象存活率很高,采用复制算法效率很低,所以老年代垃圾收集采用的是标记整理算法。
对象优先分配在Eden区,如果Eden区没有足够的空间进行分配时,虚拟机执行一次MinorGC。
大对象直接进入老年代(需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄(Age Count)计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,直到达到阀值(默认15次),对象进入老年区。
动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。
7、你看过flink源码?
(因为简历中有写看过部分源码)直接问我Flink作业的执行过程了
https://www.cnblogs.com/ooffff/p/9486451.html
8、然后就问了 java中强引用,软引用,弱引用,虚引用有什么用?
最普遍 的引用:Object obj = new Object();
通过将对象 设置为null 来弱化引用,使其被回收,或者等待生命周期超时。
当内存空间不足时,会回收它 ,内存足够时,会允许它存在。
跟踪对象被垃圾收集器回收 的活动,起到一个哨兵的作用
不能单独使用,必须和 引用队列ReferenceQueue联合使用;
9、Http和https的区别是什么,https的数据传输过程?
1. Https需要到CA 申请证书,Http不需要
3. 连接方式不同,Https默认使用的是 443端口,Http使用 80端口
4. Htttps = http + SSL(加密+认证+完整性保护),较Http安全;
(2). 服务器选择一套浏览器支持的加密算法,以证书的形式回发给浏览器
(3). 浏览器验证 证书的合法性,并结合证书 公钥 加密信息发给服务器
(4). 服务器使用 私钥解密信息,验证哈希,加密响应消息回发给 浏览器
(5). 浏览器解密响应信息,并对消息进行验证,之后进行加密交互数据
Http报文层面:GET请求将请求信息放在URL,POST放在报文体中
数据库层面:GET 符合幂等性和安全性,POST不符合
其它层面:GET可以被缓存,被存储,而POST不行。( GET可以将请求缓存在浏览器的本地,书签等,而POST不行 ,非幂等。)
二面
1. 第一次握手,建立连接时,客户端发送SYN包 ( syn=j )到服务器,并进入SYN_SEND状态,等待服务器确认。
2. 第二次握手:服务器收到SYN 包,必须确认客户的SYN (ack=j+1),同时自己也发送一个SYN包 (syn=k),即 SYN+ACK包,此时服务器进入 SYN_RECV 状态;
3. 第三次握手:客户端收到服务器的SYN+ACK 包,向服务器发送确认包 ACK(ack=k+1), 此包发送完毕,客户端和服务器进入ESTAB -LISTEN 状态,完成三次握手。
1. SYN队列满后,通过tcp_syncookies 参数回发SYN Cookie
2. 若为正常连接则Client会回发 SYN Cookie, 直接建立连接
1. 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入 FIN_WAIT_1 状态;
2. 第二次挥手:Server收到 FIN后,发送一个ACK给 Client,确认序号 为收到 序号 +1 (与SYN相同,一个FIN占用一个程序号),Server进入 CLOSE_WAIT 状态;
3. 第三次挥手:Server发送一个FIN后,用来关闭Server到Client的数据传送,Server 进入 LAST_ACK 状态;
4. 第四次挥手:Client收到 FIN 后,Client进入 TIME_WAIT 状态,接着发送一个ACK 给Server,确认序号为 收到序号+1,Server进入CLOSED 状态,完成四次挥手。
(2)服务器出现大量CLOSE_WAIT状态的原因
对方关闭socket连接,我方忙于读或写,没有及时关闭连接
因为全双工,发送方和接收都需要FIN报文和ACK 报文
(有些模糊只想起说了https的数据传输过程 答到一般面试官就说跳过了)
public V put(K key,V value){
return putVal(hash(key),key,value,false,true);
}
static final int hash(Object key){
int h;
return(key==null)?0:(h=key.hashCode())^(h>>>16);
}
final V putVal(int hash,K key,V value,boolean onlyIfAbsent,
boolean evict){
Node[]tab;
Node p;
int n,i;
if((tab=table)==null||(n=tab.length)==0)
n=(tab=resize()).length;
if((p=tab[i=(n-1)&hash])==null)
tab[i]=newNode(hash,key,value,null);
else{
Node e;
K k;
if(p.hash==hash&&
((k=p.key)==key||(key!=null&&key.equals(k))))
e=p;
else if(p instanceof TreeNode)
e=((TreeNode)p).putTreeVal(this,tab,hash,key,value);
else{
for(int binCount=0;;++binCount){
if((e=p.next)==null){
p.next=newNode(hash,key,value,null);
if(binCount>=TREEIFY_THRESHOLD-1)
treeifyBin(tab,hash);
break;
}
if(e.hash==hash&&
((k=e.key)==key||(key!=null&&key.equals(k))))
break;
p=e;
}
}
if(e!=null){
V oldValue=e.value;
if(!onlyIfAbsent||oldValue==null)
e.value=value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if(++size>threshold)
resize();
afterNodeInsertion(evict);
return null