某Java大佬在地表最强Java企业(阿里)面试总结

2021/7/11 9:07:28

本文主要是介绍某Java大佬在地表最强Java企业(阿里)面试总结,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

> 面试题真的是博大精深,也通过这个面试题学到了很多东西,很多笔者也不是很懂,如有描述错误的地方还望大佬赐教, > 每一次面试都可能问到相同的问题,一面问到,二三面还可能会问到,笔者认为这一点是整理这篇面试题收获最大的一点。 目录: ### 一面 [1.1、HashMap和Hashtable的区别](#1_1) [1.2、实现一个保证迭代顺序的HashMap](#1_2) [1.3、 说一说排序算法,稳定性,复杂度](#1_3) [1.4、 说一说GC](#1_4) [1.5、 可以保证的实习时长](#1_5) [1.6、 职业规划](#1_6) ### 二面 [2.1、 自我介绍。](#2_1) [2.2、 JVM如何加载一个类的过程,双亲委派模型中有哪些方法?](#2_2) [2.3、 HashMap如何实现的?](#2_3) [2.4、 HashMap和Concurrent HashMap区别, Concurrent HashMap 线程安全吗, Concurrent HashMap如何保证 线程安全?](#2_4) [2.5、 HashMap和HashTable 区别,HashTable线程安全吗?](#2_5) [2.6、 进程间通信有哪几种方式?](#2_6) [2.7、 JVM分为哪些区,每一个区干吗的?](#2_7) [2.8、 JVM如何GC,新生代,老年代,持久代,都存储哪些东西?](#2_8) [ 2.9、 GC用的引用可达性分析算法中,哪些对象可作为GC Roots对象?](#2_9) [2.10、 快速排序,过程,复杂度?](#2_10) [2.11、 什么是二叉平衡树,如何插入节点,删除节点,说出关键步骤。](#2_11) [2.12、 TCP如何保证可靠传输?三次握手过程?](#2_12) [2.13、 TCP和UDP区别?](#2_13) [2.14、 滑动窗口算法?](#2_14) [2.15、 Linux下如何进行进程调度的?](#2_15) [2.16、 Linux下你常用的命令有哪些?](#2_16) [2.17、 操作系统什么情况下会死锁?](#2_17) [2.18、 常用的hash算法有哪些?](#2_18) [2.19、 什么是一致性哈希?](#2_19) [ 2.20、 如何理解分布式锁?](#2_20) [ 2.21、 数据库中的范式有哪些?](#2_21) [2.22、 数据库中的索引的结构?什么情况下适合建索引?](#2_22) [2.23、 Java中的NIO,BIO,AIO分别是什么?](#2_23) [2.24、 用什么工具调试程序?JConsole,用过吗?](#2_24) [2.25、 现在JVM中有一个线程挂起了,如何用工具查出原因?](#2_25) [2.26、 线程同步与阻塞的关系?同步一定阻塞吗?阻塞一定同步吗?](#2_26) [2.27、 同步和异步有什么区别?](#2_27) [ 2.28、 线程池用过吗?](#2_28) [2.29、 如何创建单例模式?说了双重检查,他说不是线程安全的。如何高效的创建一个线程安全的单例?](#2_29) [2.30、 concurrent包下面,都用过什么?](#2_30) [2.31、 常用的数据库有哪些?redis用过吗?](#2_31) [2.32、 了解hadoop吗?说说hadoop的组件有哪些?说下mapreduce编程模型。](#2_32) [2.33、 你知道的开源协议有哪些?](#2_33) [2.34、 你知道的开源软件有哪些?](#2_34) [2.35、 你最近在看的书有哪些?](#2_35) [2.36、 你有什么问题要问我吗?](#2_36) [2.37、 了解哪些设计模式?说说都用过哪些设计模式](#2_37) [2.38、 如何判断一个单链表是否有环?](#2_38) [2.39、 操作系统如何进行分页调度?](#2_39) [2.40、 匿名内部类是什么?如何访问在其外面定义的变量?](#2_40) ### 三面 [3.1、 自我介绍,做过什么项目。](#3_1) [3.2、java虚拟机的区域如何划分,](#3_2) [3.3、 双亲委派模型中,从顶层到底层,都是哪些类加载器,分别加载哪些类?](#3_3) [3.4、 有没有可能父类加载器和子类加载器,加载同一个类?如果加载同一个类,该使用哪一个类?](#3_4) [3.5、 HashMap的结构,get(),put()是如何实现的?](#3_5) [3.6、 ConcurrentHashMap的get(),put(),又是如何实现的?ConcurrentHashMap有哪些问题? ConcurrentHashMap的锁是读锁还是写锁?](#3_6) [3.7、 sleep()和wait()分别是哪个类的方法,有什么区别?synchronized底层如何实现的?用在代码块和方法上有什么区别?](#3_7) [3.8、 什么是线程池?如果让你设计一个动态大小的线程池,如何设计,应该有哪些方法?](#3_8) [3.9、 什么是死锁?JVM线程死锁,你该如何判断是因为什么?如果用VisualVM,dump线程信息出来,会有哪些信息?](#3_9) [3.10、 查看jvm虚拟机里面堆、线程的信息,你用过什么命令?](#3_10) [3.11、 垃圾回收算法有哪些?CMS知道吗?如何工作的?](#3_11) [3.12、 数据库中什么是事务?事务的隔离级别?事务的四个特性?什么是脏读,幻读,不可重复读?](#3_12) [3.13、 数据库索引的结构有哪些? 介绍B+树的结构。](#3_13) [3.14、 数据库中的分页查询语句怎么写?](#3_14) [3.15、 什么是一致性哈希?用来解决什么问题?](#3_15) [3.16、 Redis的存储结构,或者说如何工作的,与mysql的区别?有哪些数据类型?](#3_16) [3.17、 项目中用到redis,为什么选用redis,了解其他NoSQL数据库吗?在你的项目中是如何运用redis的?key是什么,value是什么?](#3_17) [3.18、 归并排序的过程?时间复杂度?空间复杂度?你平常用什么排序?快速排序。说说在那些场景下适用,哪些场景下不适用。](#3_18) [3.19、 Solr是如何工作的?](#3_19) # 一面 ## 1.1、HashMap和Hashtable的区别 **继承:** Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map接口。 **锁:** Hashtable 中的方法是Synchronize的,而HashMap中的方法在缺省情况下是非Synchronize的。 **方法:** HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey,因为contains方法容易让人引起误解。 Hashtable则保留了contains,containsValue和containsKey三个方法,其中contains和containsValue功能相同。 **是否可以为null:** Hashtable中,key和value都不允许出现null值。 HashMap中,null可以作为键,这样的键只有一个。 Hashtable中有类似put(null,null)的操作,编译同样可以通过,因为key和value都是Object类型,但运行时会抛出NullPointerException异常,这是JDK的规范规定的。 **Tips:** 当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。 **遍历:** Hashtable、HashMap都使用了 Iterator。但是,Hashtable还使用了Enumeration的方式 。 **计算Hash值:** HashTable直接使用对象的hashCode。 HashMap的Hash值:(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); **容量:** HashTable在不指定容量的情况下的默认容量为11,而HashMap为16, Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。 Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。 ## 1.2、实现一个保证迭代顺序的HashMap 使用HashMap的一个子类**LinkedHashMap**(顺序遍历的HashMap)进行研究 **放入方法中:** 重写了最关键的Node<K, V> newNode(int hash, K key, V value, Node next)方法,该方法首先创建一个HashMap.Node的子类LinkedHashMap.Entry,它比Node多了两个指针Entry<K, V> before, after;//用于保存前后两个节点的信息。 **Tips:** LinkedHashMap拥有两个瞬时的属性 `transient LinkedHashMap.Entry<K,V> tail;//用于保存上一个元素,即表尾;` `transient LinkedHashMap.Entry<K, V> head;//用于保存第一个元素,即表头。` **遍历:** 使用`LinkedEntryIterator`迭代器进行遍历,继承于 `abstract class LinkedHashIterator`抽象类。该迭代器拥有两个Entry的指针`next`和`current`,并结合Entry中的`before`和`after`指针,实现了LinkedHashMap中元素的**顺序遍历**。 LinkedHashIterator的`nextNode`方法使用了`LinkedHashMap.Entry<K, V>的after属性`,使得iterator的遍历按照放入顺序进行的。 **取值方法:** LinkedHashMap重写了Map接口的V get(Object key)方法,该方法分两个步骤: 1. 调用父类HashMap的getNode(hash(key), key)方法,获取value; 2. 如果accessOrder(访问后重排序)为true(默认为false),那么移动所访问的元素到表尾,并修改head和tail的值。 ## 1.3、 说一说排序算法,稳定性,复杂度 ![在这里插入图片描述](https://www.www.zyiz.net/i/ll/?i=20200823180205533.png#pic_center) 这个东西还是面试前把每个排序算法都看一看比较好 ## 1.4、 说一说GC `堆(新生代和老生代)`是Java虚拟机进行垃圾回收的主要场所,其次要场所是`方法区(永久代)`。 在堆中进行垃圾回收分为新生代和老生代;将新生代分成了`三个独立的区域`(这里的独立区域只是一个相对的概念,并不是说分成三个区域以后就不再互相联合工作了), 分别为:Eden区、From Survivor区以及To Survivor,而Eden区分配的内存较大,其他两个区较小,每次使用Eden和其中一块Survivor。 在进行垃圾回收时,将Eden和Survivor中还存活着的对象进行一次性地复制到另一块Survivor空间上,直到其两个区域中对象被回收完成, 当Survivor空间不够用时,需要依赖其他老年代的内存进行分配担保。当另外一块Survivor中没有足够的空间存放上一次新生代收集下来的存活对象时,这些对象将直接通过分配担保机制进入老生代,大对象和长期存活的对象也会直接进入老年代。 如果老生代的空间也被占满,当来自新生代的对象再次请求进入老生代时就会报OutOfMemory异常。 ```java 新生代中的垃圾回收频率高, ,保存在JVM的方法区(永久代)中的对象一般不会被回收。 其永久代进行垃圾回收的频率就较低,速度也较慢。 永久代的垃圾收集主要回收废弃常量和无用类。 ``` ```java 判断一个类是否被回收,则需同时满足的条件: 该类所有的实例和ClassLoader都已经被回收。 该类的对象没有被引用,无法通过反射访问,这里说的是可以回收而不是必然回收。 ``` 大多数情况下,对象在新生代Eden区中分配,当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC; 同理,当老年代中没有足够的内存空间来存放对象时,虚拟机会发起一次Major GC/Full GC。只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则将进行Full CG。 **虚拟机通过对象年龄计数器来判断存放在哪:** 如果对象在Eden出生并经过一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将该对象的年龄设为1。 对象每在Survivor中熬过一次Minor GC,年龄就增加1岁,当他的年龄增加到最大值15(MaxTenuringThreshold)时,就将会被晋升到老年代中。 如果在Survivor空间中所有相同年龄的对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。 **Jdk8开始废弃永久代:** This is part of the JRockit and Hotspot convergence effort. JRockit customers do not need to configure the permanent generation (since JRockit does not have a permanent generation) and are accustomed to not configuring the permanent generation. (移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。) 由于永久代内存经常不够用或发生内存泄露,爆出异常java.lang.OutOfMemoryError: PermGen 字符串存在永久代中,容易出现性能问题和内存溢出。 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。 永久代会为GC带来不必要的复杂度,而且回收效率偏低。 ## 1.5、 可以保证的实习时长 一般都是半年到一年,(太少的话,刚教会了你,你就可能走了,太多的话也不现实) 还有可能问到什么时候去上班,建议的话,就是下个月,或者两周后,今天面试明天上班,肯定准备的不充分。(问这个的话,大多都是有个项目什么的着急要人,然后面试官用你应付,) ## 1.6、 职业规划 其实这个问题不只是回答面试官,更是回答自己,为什么做,想怎么做…… 说明自己对岗位的理解和从事这份工作的原因。 说明自己愿意为这份工作付出努力。 说明自己长远的目标和规划。 **下面以产品经理为例子回答:** 互联网行业是一个高速发展的行业,同时也有大量创新和尝试的机会(阐述自己看好行业)。 而产品经理则是互联网企业的核心岗位之一,产品经理负责用户需求分析、竞品分析、产品设计和上下层需求的沟通,需要超强的逻辑思考和分析能力、用户洞察能力和沟通协作能力。(阐述自己对岗位的理解)。 而我毕业于XXX大学,在大学里曾参加XXX产品设计比赛,拿下了XXX的成绩,个人非常擅长思考和分析问题,同时能处理好和团队成员的沟通协作...(阐述自己适合这个工作)。 我认为自己非常适合这个岗位,为此,我也愿意付出努力。 在过去,我曾阅读过XXX本产品书籍,自己设计过3款产品的原型,有一款在自己的努力下成功上线,并通过持续获取用户反馈,收获了XXX万的用户。(表达自己过去的努力) 入职以后,我希望能从助理开始,系统学习产品的基本功,在一年的时间里面,成功掌握主流的产品设计方法论(表达自己愿意付出的努力)。 我知道,优秀的产品经理不仅仅需要掌握产品设计的方法,还需要XXXX。 我会努力培养自己的业务思维,站在全局业务的角度去思考和解决问题,为团队做好表率...(表达自己大致的努力方向)。 每个产品经理都有自己的目标,我也一样。 我希望在我的努力之下,在两年以后,能够独挡一面,负责好一个版块的功能; 在三到五年左右,可以负责好一个产品的规划、设计和优化; 在未来的五到八年,可以做好一个产品的全局规划、团队管理等等.... # 二面 ## 2.1、 自我介绍。 自我介绍在三到五分钟最好 一两句话概括自己名字学校什么的,主学的什么,对什么有研究,了解什么(切忌:尽量别说“精通”),然后说一下以前做过的项目(具体说一些自己做的有技术的),或者什么别的奖项,然后谈一下自己对公司的了解以及对未来的规划,等等。(百度有很多,随便搜搜) ## 2.2、 JVM如何加载一个类的过程,双亲委派模型中有哪些方法? **类加载过程:** **加载** (通过一个类的全限定名获取定义此类的二进制字节流,将这个字节流所代表的静态存储结构转化为方法区域的运行时数据结构,在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区域数据的访问入口)、 **验证** (验证阶段作用是保证Class文件的字节流包含的信息符合JVM规范,不会给JVM造成危害,如果验证失败,就会抛出一个java.lang.VerifyError异常或其子类异常。 *1.文件格式验证:* 验证字节流文件是否符合Class文件格式的规范,并且能被当前虚拟机正确的处理。 *2.元数据验证:* 是对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言的规范。 *3.字节码验证:* 主要是进行数据流和控制流的分析,保证被校验类的方法在运行时不会危害虚拟机。 *4.符号引用验证:* 符号引用验证发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在解析阶段中发生。)、 **准备** (准备阶段为(static)变量(不包括类的实例)分配内存并设置类变量的初始化,此初始化并不是赋值static int num =1,这时得num为0,并不是1)、 **解析** (解析过程是将常量池内的符号引用替换成直接引用(类或接口的解析、字段解析、方法解析、接口方法解析。))、 **初始化** (这里才是赋值阶段) 使用过程:新线程---程序计数器----jvm栈执行(对象引用)-----堆内存(直接引用)----方法区。 卸载靠GC **双亲委派模型中方法:** 双亲委派是指如果一个类收到了类加载的请求,不会自己先尝试加载,先找父类加载器去完成。当顶层启动类加载器表示无法加载这个类的时候,子类才会尝试自己去加载。当回到最开的发起者加载器还无法加载时,并不会向下找,而是抛出ClassNotFound异常。 **方法:启动(Bootstrap)类加载器,标准扩展(Extension)类加载器,应用程序类加载器(Application ),上下文(Custom)类加载器。意义是防止内存中出现多份同样的字节码 。** **1)启动类加载器(Bootstrap ClassLoader):** 负责加载JAVA_HOME\lib目录中并且能被虚拟机识别的类库到JVM内存中,如果名称不符合的类库即使放在lib目录中也不会被加载。该类加载器无法被Java程序直接引用。 **2)扩展类加载器(Extension ClassLoader):** 按《深入理解java虚拟机》这本书上所说,该加载器主要是负责加载JAVA_HOME\lib\ext目录中的类库,但是貌似在JDK的安装目录下,没看到该指定的目录。该加载器可以被开发者直接使用。 **3)应用程序类加载器(Application ClassLoader):** 该类加载器也称为系统类加载器,它负责加载用户类路径(Classpath)上所指定的类库,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。 ## 2.3、 HashMap如何实现的? [关于HashMap还是看一下这一篇比较好](https://blog.csdn.net/weixin_46285416/article/details/107555123) HashMap的底层是数组+链表,(很多人应该都知道了) JDK1.7的是`数组+链表` 首先是一个数组,然后数组的类型是链表 元素是`头插法` JDK1.8的是`数组+链表 或者 数组+红黑树` 首先是一个数组,然后数组的类型是链表 **在链表的元素大于8的时候,会变成红黑树** (当链表长度大于8并且数组长度大于64时,才会转换为红黑树。 如果链表长度大于8,但是数组长度小于64时,还是会进行扩容操作,不会转换为红黑树。因为数组的长度较小,应该尽量避开红黑树。因为红黑树需要进行左旋,右旋,变色操作来保持平衡, 所以当数组长度小于64,使用数组加链表比使用红黑树查询速度要更快、效率要更高。 ) **在红黑树的元素小于6的时候会变成链表** (这里需要注意,不是元素小于6的时候一定会变成链表,只有resize的时候才会根据UNTREEIFY_THRESHOLD 进行转换,同样也不是到8的时候就变成红黑树(不是等到扩容的时候) 链表与红黑树的转换详情) 元素进行`尾插` ## 2.4、 HashMap和Concurrent HashMap区别, Concurrent HashMap 线程安全吗, Concurrent HashMap如何保证 线程安全? [关于HashMap还是看一下这一篇比较好](https://blog.csdn.net/weixin_46285416/article/details/107555123) 使用ConcurrentHashMap(线程安全), JDK1.7的是分段数组,有`Segment锁(继承于ReentrantLock)`加速一小段保证并发 JDK1.8 是和HashMap一样了,`数组+链表(或者红黑树)` `Synchronized(锁)and CAS(compare and swap)` (JVM在1.6对Synchronize的优化很好) CAS通俗易懂,比较并替换 (CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做) (无锁化的修改值的操作,他可以大大降低锁代理的性能消耗。这个算法的基本思想就是不断地去比较当前内存中的变量值与你指定的 一个变量值是否相等,如果相等,则接受你指定的修改的值,否则拒绝你的操作。因为当前线程中的值已经不是最新的值,你的修改很可能会覆盖掉其他线程修改的结果。这一点与乐观锁,SVN的思想是比较类似的) ## 2.5、 HashMap和HashTable 区别,HashTable线程安全吗? [1.1、HashMap和Hashtable的区别](#1.1) HashTable(线程安全)就是把HashMap套上了一个Synchronized ## 2.6、 进程间通信有哪几种方式? **管道、消息队列、信号量、共享内存、套接字** **无名管道( pipe ):** 管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。 **高级管道(popen):** 将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们成为高级管道方式。 **有名管道 (named pipe) :** 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。 **消息队列( message queue ) :** 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。 **信号量( semophore ) :** 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。 **信号 ( sinal ) :** 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。 **共享内存( shared memory ) :** 共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。 **套接字( socket ) :** 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。 ## 2.7、 JVM分为哪些区,每一个区干吗的? **线程独占 : 栈 , 本地方法栈 ,程序计数器 线程共享 : 堆 , 方法区** **程序计数器PC** 线程私有的 它可以看做是当前线程所执行的字节码的行号指示器 内存区域中唯一一个没有规定任何OutOfMemoryError的区域 **Java虚拟机栈** 线程私有的 每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息 如果线程请求的栈深度大于虚拟机所允许的深度,将抛StackOverFlowError异常; 如虚拟机扩展时仍无法申请到足够的内存,就会抛出OutOfMemoryError异常 **本地方法栈** 与虚拟机栈非常相似,区别是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用Native方法服务 也会抛出StackOverFlowError和OutOfMemoryError异常 **Java堆** 线程共享的 Java堆是GC管理的主要区域 在虚拟机启动时创建 存放对象实例,几乎所有的对象实例和数组都在这里分配内存。 如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常 **方法区** 线程共享的 用于存储已被虚拟机加载的类信息、常量、静态变量、即使编译器编译后的代码等数据 当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常 **运行时常量池** 是方法区的一部分 用于存放编译器生成的各种字面量和符号引用 相对于Class文件常量池的一个重要特征是,具备动态性 运行时常量池是方法区的一部分,自然受到方法区内存的限制。当常量池无法再申请到内存时会抛出OutOfMemoryError异常 ## 2.8、 JVM如何GC,新生代,老年代,持久代,都存储哪些东西? [1.4、 说一说GC](#1.4) ## 2.9、 GC用的引用可达性分析算法中,哪些对象可作为GC Roots对象? **可达性分析算法的思想:** 从一个被称为GC Roots的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。 在java中可以作为GC Roots的对象有以下几种: `虚拟机栈中引用的对象、方法区类静态属性引用的对象、方法区常量池引用的对象、本地方法栈JNI引用的对象` 虽然这些算法可以判定一个对象是否能被回收,但是当满足上述条件时,一个对象 不一定会被回收。当一个对象不可达GC Roots时,这个对象并不会马上被回收,而是处于一个死缓的阶段,若要被真正的回收需要经历两次标记。如果对象在可达性分析中没有与GC Roots的引用链,那么此时就会被第一次标记并且进行一次筛选,筛选的条件是是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或者已经被虚拟机调用过,那么就认为是没必要的。 如果该对象有必要执行finalize()方法,那么这个对象将会放在一个称为F-Queue的队列中,虚拟机会触发一个finalize()线程去执行,此线程是低优先级的,并且虚拟机不会承诺一直等待它运行完,这还是因为如果finalize()执行缓慢或者发生了死锁,那么就会造成F-Queue队列一直等待,造成了内存回收系统的崩溃。GC对处于F-Queue中的对象进行第二次被标记,这时,该对象将被移除“即将回收”集合,等待回收。 ## 2.10、 快速排序,过程,复杂度? ```java //快速排序 void quick_sort(int s[], int l, int r) { if (l < r) { //Swap(s[l], s[(l + r) / 2]); //将中间的这个数和第一个数交换 参见注1 int i = l, j = r, x = s[l]; while (i < j) { while(i < j && s[j] >= x) // 从右向左找第一个小于x的数 j--; if(i < j) s[i++] = s[j]; while(i < j && s[i] < x) // 从左向右找第一个大于等于x的数 i++; if(i < j) s[j--] = s[i]; } s[i] = x; quick_sort(s, l, i - 1); // 递归调用 quick_sort(s, i + 1, r); } } ``` 快速排序,分治递归,对于每一段做如下处理: **从右向左第一个小于x的数放在原来左位置+1得那个地方,相反,从左向右第一个大于x的数放到原来右位置-1,一直到坐位置》=右位置,然后中间位置=原来左面的那个位置的数,在递归调用(l,i-1)和(i+1,r)** ## 2.11、 什么是二叉平衡树,如何插入节点,删除节点,说出关键步骤。 对于每一个结点来说,子树和右子树都是二叉平衡树,左右子树的高度差不能大于1,如果插入删除使得高度差大于1了,就要进行旋转操作 **插入:** 如果有当前结点就返回false,就插入,如果还是二叉平衡树就返回true,插入的过程中如果不符合条件,就左旋右旋处理 **删除:** (1)删除节点没有左子树,这种情况直接将删除节点的父节点指向删除节点的右子树。 (2)删除节点没有右子树,这种情况直接将删除节点的父节点指向删除节点的左子树。 (3)删除节点左右子树都存在,可以采用两种方式, 1:让删除节点左子树的最右侧节点代替当前节点 2:让删除节点右子树的最左侧节点代替当前节点 ## 2.12、 TCP如何保证可靠传输?三次握手过程? **TCP为了提供可靠传输:** (1)首先,采用三次握手来建立TCP连接,四次握手来释放TCP连接,从而保证建立的传输信道是可靠的。 (2)其次,TCP采用了连续ARQ协议(回退N,Go-back-N;超时自动重传)(自动重传请求(Automatic Repeat-reQuest,ARQ))来保证数据传输的正确性,使用滑动窗口协议来保证接方能够及时处理所接收到的数据,进行流量控制。 (3)最后,TCP使用慢开始、拥塞避免、快重传和快恢复来进行拥塞控制,避免网络拥塞。 在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。 **第一次握手:** 建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认; **第二次握手:** 服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态; **第三次握手:** 客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。 完成三次握手,客户端与服务器开始传送数据. ## 2.13、 TCP和UDP区别? TCP与UDP区别总结 1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接 2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。TCP通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。 3、UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。 4.每一条TCP连接只能是点到点的;UDP支持一对一、一对多、多对一和多对多的交互通信。 5、TCP对系统资源要求较多,UDP对系统资源要求较少。 为什么UDP有时比TCP更有优势? UDP以其简单、传输快的优势,在越来越多场景下取代了TCP,如实时游戏。 (1)网速的提升给UDP的稳定性提供可靠网络保障,丢包率很低,如果使用应用层重传,能够确保传输的可靠性。 (2)TCP为了实现网络通信的可靠性,使用了复杂的拥塞控制算法,建立了繁琐的握手过程,由于TCP内置的系统协议栈中,极难对其进行改进。 采用TCP,一旦发生丢包,TCP会将后续的包缓存起来,等前面的包重传并接收到后再继续发送,延时会越来越大,基于UDP对实时性要求较为严格的情况下,采用自定义重传机制,能够把丢包产生的延迟降到最低,尽量减少网络问题对游戏性造成影响。 ## 2.14、 滑动窗口算法? 大概意思:在一个数组或者其他链表中,确认左端点和右端点,这中间用和或者其他的存起来,一个一个的向右移动右端点,这个过程中可能因不符合条件要把左端点也右移,在这个过程中一直记录最大值或者最小值,(向右移动左端点就是在这个范围的和或者其他记录的数值删去左端点这个值) ## 2.15、 Linux下如何进行进程调度的? 凉凉…………(这个不会,看也看不懂的那种) **1.先来先服务调度算法**   先来先服务(FCFS)调度算法是一种最简单的调度算法,该算法既可用于作业调度,也可用于进程调度。当在作业调度中采用该算法时, 每次调度都是从后备作业队列中选择一个或多个最先进入该队列的作业,将它们调入内存,为它们分配资源、创建进程,然后放入就绪 队列。在进程调度中采用FCFS算法时,则每次调度是从就绪队列中选择一个最先进入该队列的进程,为之分配处理机,使之投入运行。 该进程一直运行到完成或发生某事件而阻塞后才放弃处理机。 **2、基于优先级调度 (Priority Scheduling)**   在优先级调度算法中,每个进程都关联一个优先级,内核将CPU分配给最高优先级的进程。具有相同优先级的进程,按照 先来先服务的原则进行调度。 Aging就是指逐渐提高系统中长时间等待的进程的 优先级。我们可以每15分钟将等待进程的优先级加1。最终 经过一段时间,即便是拥有最低优先级的进程也会变成系统中最高优先级的进程,从而被执行。   优先级调度可以抢占式或者非抢占式的。当一个进程在Ready队列中时,内核将它的优先级与正在CPU上执行的进程的优先级 进行比较。当发现这个新进程的优先级比正在执行的进程高时:对于抢占式内核,新进程会抢占CPU,之前正在执行的进程 转入Ready队列;对于非抢占式内核,新进程只会被放置在Ready队列的头部,不会抢占正在执行的进程。 **3、短进程优先(SCBF--Shortest CPU Burst First)**   最短CPU运行期优先调度算法(SCBF--Shortest CPU Burst First) 该算法从就绪队列中选出下一个“CPU执行期最短”的进程,为之分配处理机。 最短作业优先调度是优先级调度的特例。在优先级调度中我们根据进程的优先级来进行调度,在最短作业优先调度中我们 根据作业的执行时间长短来调度。 **4、轮转法 (Round-Robin Scheduling) (RR)**   前几种算法主要用于批处理系统中,不能作为分时系统中的主调度算法,在分时系统中,都采用时间片轮转法。 简单轮转法:系统将所有就绪进程按FIFO规则排队,按一定的时间间隔把处理机分配给队列中的进程。这样,就绪 队列中所有进程均可获得一个时间片的处理机而运行。多级队列方法:将系统中所有进程分成若干类,每类为一级。   RR调度算法转为分时系统设计,它与FCFS很像,但是加入了抢占。具体调度过程是:内核从Ready队列中选取第一个进程, 将CPU资源分配给它,并且设置一个定时器在一个时间片后中断该进程,调度Ready队列中的下一进程。很明显,RR调度 算法是抢占式的,并且在该算法的调度下,没有一个进程能够连续占用CPU超过一个时间片,从而达到了分时的目的。 **5、高响应比优先调度算法**   (1) 如果作业的等待时间相同,则要求服务的时间愈短,其优先权愈高,因而该算法有利于短作业.   (2) 当要求服务的时间相同时,作业的优先权决定于其等待时间,等待时间愈长,其优先权愈高,因而它实现的是先来先服务.   (3) 对于长作业,作业的优先级可以随等待时间的增加而提高,当其等待时间足够长时,其优先级便可升到很高, 从而也可获得处理机.   该算法照顾了短作业,且不会使长作业长期得不到服务 **6、抢占式调度算法** 1. 非抢占式调度算法 为每一个被控对象建立一个实时任务并将它们排列成一轮转队列,调度程序每次选择队列中的第一个任务投入运行.该任务完成后便把它挂在轮转队列的队尾等待下次调度运行. 2. 非抢占式优先调度算法. 实时任务到达时,把他们安排在就绪队列的对首,等待当前任务自我终止或运行完成后才能被调度执行. 3. 抢占式调度算法 1)基于时钟中断的抢占式优先权调度算法. 实时任务到达后,如果该任务的优先级别高于当前任务的优先级并不立即抢占当前任务的处理机,而是等到时钟中断到来时,调度程序才剥夺当前任务的执行,将处理机分配给新到的高优先权任务. 2)立即抢占的优先权调度算法. 在这种调度策略中,要求操作系统具有快速响应外部时间中断的能力.一旦出现外部中断,只要当前任务未处于临界区便立即剥夺当前任务的执行,把处理机分配给请求中断的紧迫任务,实时进程调度,实时进程抢占当前。 ## 2.16、 Linux下你常用的命令有哪些? 这个最好是多用用比较好,[直接跳过看一下个题](#2.17) 1、显示日期的指令: date 2、显示日历的指令:cal 3、简单好用的计算器:bc 怎么10/100会变成0呢?这是因为bc预设仅输出整数,如果要输出小数点下位数,那么就必须要执行 scale=number ,那个number就是小数点位数, 4、重要的几个热键[Tab],[ctrl]-c, [ctrl]-d [Tab]按键---具有『命令补全』不『档案补齐』的功能 [Ctrl]-c按键---让当前的程序『停掉』 [Ctrl]-d按键---通常代表着:『键盘输入结束(End Of File, EOF 戒 End OfInput)』的意思;另外,他也可以用来取代exit 5、man 退出用q, man -f man 6、数据同步写入磁盘: sync 输入sync,那举在内存中尚未被更新的数据,就会被写入硬盘中;所以,这个挃令在系统关机戒重新启劢乀前, 径重要喔!最好多执行几次! 7、惯用的关机指令:shutdown 此外,需要注意的是,时间参数请务必加入指令中,否则shutdown会自动跳到 run-level 1 (就是单人维护的登入情况),这样就伤脑筋了!底下提供几个时间参数的例子吧: 重启,关机: reboot, halt,poweroff 8、切换执行等级: init Linux共有七种执行等级: --run level 0 :关机 --run level 3 :纯文本模式 --run level 5 :含有图形接口模式 --run level 6 :重新启动 使用init这个指令来切换各模式: 如果你想要关机的话,除了上述的shutdown -h now以及poweroff之外,你也可以使用如下的指令来关机: 9、改变文件的所属群组:chgrp 10、改变文件拥有者:chown 他还可以顸便直接修改群组的名称 11、改变文件的权限:chmod 权限的设定方法有两种, 分别可以使用数字或者是符号来进行权限的变更。 --数字类型改变档案权限: --符号类型改变档案权限: 12、查看版本信息等 13、变换目录:cd 14、显示当前所在目录:pwd 15、建立新目录:mkdir 不建议常用-p这个选项,因为担心如果你打错字,那么目录名称就回变得乱七八糟的 16、删除『空』的目录:rmdir 17、档案与目录的显示:ls 18、复制档案或目录:cp 19、移除档案或目录:rm 20、移动档案与目录,或更名:mv 21、取得路径的文件名与目录名:basename,dirname 22、由第一行开始显示档案内容:cat 23、从最后一行开始显示:tac(可以看出 tac 是 cat 的倒着写) 24、显示的时候,顺道输出行号:nl 25、一页一页的显示档案内容:more 26、与 more 类似,但是比 more 更好的是,他可以往前翻页:less 27、只看头几行:head 28、只看尾几行:tail 29、以二进制的放置读取档案内容:od 30、修改档案时间或新建档案:touch 31、档案预设权限:umask 32、配置文件档案隐藏属性:chattr 33、显示档案隐藏属性:lsattr 34、观察文件类型:file 35、寻找【执行挡】:which 36、寻找特定档案:whereis 37、寻找特定档案:locate 38、寻找特定档案:find 39、压缩文件和读取压缩文件:gzip,zcat 40、压缩文件和读取压缩文件:bzip2,bzcat 41、压缩文件和读取压缩文件:tar ## 2.17、 操作系统什么情况下会死锁? 死锁的4个必要条件 **(1) 互斥条件:** 一个资源每次只能被一个进程使用。 **(2) 请求与保持条件:** 一个进程因请求资源而阻塞时,对已获得的资源保持不放。 **(3) 不剥夺条件:** 进程已获得的资源,在末使用完之前,不能强行被剥夺。 **(4) 循环等待条件:** 若干进程之间形成一种头尾相接的循环等待资源关系。 这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。 **竞争不可剥夺资源** 在系统中所配置的不可剥夺资源,由于它们的数量不能满足诸进程运行的需要,会使进程在运行过程中,因争夺这些资源而陷于僵局。 **竞争临时性资源** 上面所说的打印机资源属于可顺序重复使用型资源,称为永久资源。还有一种所谓的临时资源,这是指由一个进程产生,被另一个进程使用,短时间后便无用的资源,故也称为消耗性资源, ## 2.18、 常用的hash算法有哪些? ```java 加法Hash;位运算Hash;乘法Hash;除法Hash;查表Hash;混合Hash; ``` ## 2.19、 什么是一致性哈希? **一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环,整个空间按顺时针方向组织** 圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推, 0点的左侧是2的32次方-1,把这个圆环叫做Hash环 然后把服务器ip或者主机名字作为关键字Hash,每个服务器都能确定位置,把数据进行相同的Hash算出的位置,顺时针访问的第一个就是对应的服务器 一致性Hash算法对于节点的增减都只需重定位环空间中的一小部分数据,具有较好的容错性和可扩展性。 ## 2.20、 如何理解分布式锁? **分布式锁:** 甲乙两人购买时剩余量都显示一个,如果同时下单可能会出现问题,这时就需要用到分布式锁 分布式锁是实现有序调度不同的进程,解决不同进程之间相互干扰的问题的技术手段 **分布式锁的应具备的条件** 在分布式系统环境下,分布式锁在同一个时间仅能被同一个进程访问 高可用的获取/释放锁 高性能的获取/释放锁 具备锁的重入性 具备锁的失效机制,防止死锁 具备非阻塞锁的特性,即使没有获取锁也能直接返回结果 **分布式锁的实现有哪些** **mechache:** 利用mechache的add命令,改命令是原子性的操作,只有在key 不存在的情况下,才能add成功,也就意味着线程拿到了锁 **Redis:** 和Mechache的实现方法相似,利用redis的setnx命令,此命令同样是原子性的操作,只有在key不存在的情况下,add成功 **zookeeper:** 利用他的顺序临时节点,来实现分布式锁和等待队列,zookeeper的设计初衷就是为了实现分布式微服务的 **使用Redis实现分布式锁的思路** 先去redis中使用setnx(商品id,数量) 得到返回结果 这里的数量无所谓,它的作用就是告诉其他服务,我加上了锁 发现redis中有数量,说明已经可以加锁了 发现redis中没有数据,说明已经获得到了锁 解锁: 使用redis的 del商品id 锁超时, 设置exprie 生命周期,如30秒, 到了指定时间,自定解锁 三个致命问题 **非原子性操作:setnx,宕机,expire** 因为 setnx和expire不是原子性的,要么都成功要么都失败, 一旦出现了上面的情况,就会导致死锁出现 redis提供了原子性的操作 set ( key , value , expire) **误删锁** 假如我们的锁的生命事件是30秒,结果我在30s内没操作完,但是锁被释放了 jvm2拿到了锁进行操作 jvm1 操作完成使用del,结果把jvm2 的锁删除了 解决方法, 在删除之前,判断是不是自己的锁 redis提供了原子性的操作 set ( key ,threadId, expire) 超时为完成任务 增加一个守护线程,当快要超时,但是任务还没执行完成,就增加锁的时间 ## 2.21、 数据库中的范式有哪些? **第一范式**----数据库中的表(所有字段值)都是不可分割的原子数据项。 **第二范式**----数据库表中的每一列都和主键相关,而不能只和主键的某一部分相关。也就是说 一个表中只能只能包含一个,不能把多种数据保存在同一个表中。 **第三范式**----数据库表中每一列数据都和主键直接相关,不能间接相关。 ## 2.22、 数据库中的索引的结构?什么情况下适合建索引? 数据库中索引的结构是一种排序的数据结构。是通过`B树和变形的B+树`实现的。 **适合建索引:** 经常查询,使用,用在表连接的字段(经常更改的字段不适合建索引) ## 2.23、 Java中的NIO,BIO,AIO分别是什么? **BIO:** `同步并阻塞`,服务器实现模式为`一个连接一个线程`,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。 **NIO:** `同步非阻塞`,服务器实现模式为`一个请求一个线程`,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。 **AIO:** `异步非阻塞`,服务器实现模式为`一个有效请求一个线程`,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理.AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。 ## 2.24、 用什么工具调试程序?JConsole,用过吗? JConsole在JDK/bin目录下面,对资源消耗和性能进行监控,提供图表和可视化界面,占内存小(具体的一些还是自己多打开看一看就差不多了) ## 2.25、 现在JVM中有一个线程挂起了,如何用工具查出原因? 通过`Javacore`了解线程运行情况: Javacore,也可以称为“threaddump”或是“javadump”,它是 Java 提供的一种诊断特性,能够提供一份可读的当前运行的 JVM 中线程使用情况的快照。即在某个特定时刻,JVM 中有哪些线程在运行,每个线程执行到哪一个类,哪一个方法。 应用程序如果出现不可恢复的错误或是内存泄露,就会自动触发 Javacore 的生成。而为了性能问题诊断的需要,我们也会主动触发生成 Javacore。 在 AIX、Linux、Solaris 环境中,我们通常使用 kill -3 产生该进程的 Javacore。 ## 2.26、 线程同步与阻塞的关系?同步一定阻塞吗?阻塞一定同步吗? 同步是个过程,阻塞是线程的一种状态。多个线程操作共享变量时可能会出现竞争。这时需要同步来防止两个以上的线程同时进入临界区,在这个过程中,后进入临界区的线程将阻塞,等待先进入的线程走出临界区。 线程同步不一定发生阻塞!!!线程同步的时候,需要协调推进速度,互相等待和互相唤醒会发生阻塞。 同样,阻塞也不一定同步。 ## 2.27、 同步和异步有什么区别? **同步交互:** 指发送一个请 求,需要等待返回,然后 才能够发送下一个请求,有个等待过程; **异步交互:** 指发送一个请求,不需要等待返回,随时可以再发送下一个请求,即不需要等待。 **区别:** 一个需要等待,一个不需要等待,在部分情况下,我们的项目开发中都会优先选择不需要等待的异步交互方式。 ## 2.28、 线程池用过吗? **线程池做的工作** 主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。 线程池的优势 **线程复用、控制最大并发数、线程管理** (1)降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗; (2)提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行; (3)方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。 (4)提供更强大的功能,延时定时线程池。 ```java public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); } ``` **1、corePoolSize(线程池基本大小):** 当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,(除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。) **2、maximumPoolSize(线程池最大大小):** 线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于***队列,可忽略该参数。 **3、keepAliveTime(线程存活保持时间):** 当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。 **4、workQueue(任务队列):** 用于传输和保存等待执行任务的阻塞队列。 **5、threadFactory(线程工厂):** 用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。 **6、handler(线程饱和策略):** 当线程池和队列都满了,再加入线程会执行此策略。 **线程池为什么需要使用(阻塞)队列?** 1、因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换。 2、创建线程池的消耗较高。 (线程池创建线程需要获取mainlock这个全局锁,影响并发效率,阻塞队列可以很好的缓冲。) **线程池为什么要使用阻塞队列而不使用非阻塞队列?** 阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。 当队列中有任务时才唤醒对应线程从队列中取出消息进行执行。 使得在线程不至于一直占用cpu资源。 不用阻塞队列也是可以的,不过实现起来比较麻烦而已,有好用的为啥不用呢? **如何配置线程池** **CPU密集型任务:** 尽量使用较小的线程池,一般为CPU核心数+1。 **IO密集型任务:** 可以使用稍大的线程池,一般为2*CPU核心数。 **混合型任务:** 可以将任务分成IO密集型和CPU密集型任务, ## 2.29、 如何创建单例模式?说了双重检查,他说不是线程安全的。如何高效的创建一个线程安全的单例? **饿汉模式** 通过定义final型的对象,来让加载类的时候,直接创建对象,只加载一次,实现单例。 **懒汉式** 通过定义静态对象,加锁去实例化对象。 **枚举** 通过定义枚举类,来实现单例。 **Double-Check** 若有两个线程通过了第一个Check循环,进入第二个Check循环是串行化的,只能有一个线程进入,这样当这个线程创建完成后,另外的线程就无法通过第二个循环了,保证了实例的`唯一性`,随后的线程也不会通过第一个Check循环,也就不会有同步控制的环节了。但是,这种方法也伴随着一个缺点,它可能会引起空指针的异常。 高效创建线程安全的单例 **Volatile+Double-Check**  volatile关键字可以防止重排序的发生,在此不对volatile作详细介绍,通过volatile关键字,这种模式可以说是满足懒加载、多线程下单例的唯一性、安全性的。 ```java public final class SingletonObject5 { private volatile static SingletonObject5 instance ; private SingletonObject5() { } public static SingletonObject5 getInstance() { if (null == instance) { synchronized (SingletonObject5.class) { if (null == instance) instance = new SingletonObject5(); } } return SingletonObject5.instance; } } ``` ## 2.30、 concurrent包下面,都用过什么? (凉凉夜色为我思念成河……) 1.**executor接口**,使用executor接口的子接口ExecutorService用来创建线程池 2.**Lock接口下的ReentrantLock类**,实现同步,比如三个线程循环打印ABCABCABC... 3.**atomic包**,使用AtomicInteger类的incrementAndGet()方法来实现原子操作,比如a++ 4.**Callable接口**,重写call方法,实现多线程 5.**concarrenHashMap**,线程安全的HashMap ## 2.31、 常用的数据库有哪些?redis用过吗? mysql 、SQL Server、Oracle、Sybase、DB2等 Redis: 1、纯内存操作 2、核心是基于非阻塞的IO多路复用机制 3、单线程反而避免了多线程的频繁上下文切换问题 ## 2.32、 了解hadoop吗?说说hadoop的组件有哪些?说下mapreduce编程模型。 ```java common、Hadoop Distributed File System(HDFS)、MapReduce、YARN ``` **编程模型:** Mapper把复杂的任务分解为若干个小任务,分到存在所需数据结点上进行计算,这些任务可以一起,彼此没有依赖关系 Reducer把Mapper的结果汇总 eg: 一共有100个汉堡,甲吃十个,乙吃是个,丙吃十个,,,这就是Mapper 甲乙丙……放到一起就是Reducer ## 2.33、 你知道的开源协议有哪些? **• Mozilla Public License:** MPL License,允许免费重发布、免费修改,但要求修改后的代码版权归软件的发起 者。这种授权维护了商业软件的利益,它要求基于这种软件得修改无偿贡献版权给该软件。这样,围绕该软件得 所有代码得版权都集中在发起开发人得手中。但MPL是允许修改,无偿使用得。MPL软件对链接没有要求。 **• BSD开源协议:** 给于使用者很大自由的协议。可以自由的使用,修改源代码,也可以将修改后的代码作为开源或 者专有软件再发布。 **• Apache Licence 2.0 :** Apache Licence是著名的非盈利开源组织Apache采用的协议。该协议和BSD类似,同样 鼓励代码共享和尊重原作者的著作权,同样允许代码修改,再发布(作为开源或商业软件)。 **• GPL:** GPL许可证是自由软件的应用最广泛的软件许可证,人们可以修改程式的一个或几个副本或程式的任何部 分,以此形成基於这些程式的衍生作品。必须在修改过的档案中附有明显的说明:您修改了此一档案及任何修改 的日期。 您必须让您发布或出版的作品,包括本程式的全部或一部分,或内含本程式的全部或部分所衍生的作 品,允许第三方在此许可证条款下使用,并且不得因为此项授权行为而收费。 **• LGPL:** LGPL是GPL的一个为主要为类库使用设计的开源协议。和GPL要求任何使用/修改/衍生之GPL类库的的软 件必须采用GPL协议不同。LGPL允许商业软件通过类库引用(link)方式使用LGPL类库而不需要开源商业软件的代 码。这使得采用LGPL协议的开源代码可以被商业软件作为类库引用并发布和销售。 **• Public Domain:** 公共域授权。将软件授权为公共域,这些软件包没有授权协议,任何人都可以随意使用它 ## 2.34、 你知道的开源软件有哪些? • JDK • eclipse • Tomcat • Spring • Hibernate • MySQL • MyBatis • struts ## 2.35、 你最近在看的书有哪些? 这个尽量说真话,最好不要搜一点简介就说看过什么书,否则被`戳穿`很难受, **如果实在没看过可以这么说:** 最近没怎么看书,但是相对于书本,我更喜欢在B站学习,比如大学公开课和摄影栏目我就经常逛;知乎我也挺活跃的,关注……等话题,每天保持相当的阅读量,回答受赞也有几百了;我也参加了很多经验分享活动,我觉得从面对面的交流获得的知识,是阅读无法替代的。 ## 2.36、 你有什么问题要问我吗? **严禁:**~~没问题,提薪资,五年计划,某某产品被卖了等无关紧要的问题~~ 准备3-5个问题就行,太多或者太少都不好 问题 1、问工作内容 这份工作比较大的挑战是? 您希望我在短期内解决哪些问题? 对于未来加入这个团队,您对我的期望是什么? 我对这个职位工作的理解是XXX,不知道除了我的理解外,是否还有其他的工作职责? 2、问职位差距 对我此次应聘的表现有什么评价和建议? 如果可以录用,我需要学习哪方面的知识? 接下来的这段空档期,有什么值得注意或者建议学习的吗? 3、问工作潜力 请问该岗位都要经历哪些培训? 这个岗位的晋升路径是什么样子的? 咱们部门近期/未来有什么新动向/新举措? 您对这个岗位三到五年职业规划的建议是什么呢 4、问团队氛围 能带我看一下办公区吗? 您在公司的一天是如何度过的? 可以介绍下一起工作的团队是什么样的吗? **提问的原则** 1、探讨式发问 提问不是只问不答,提问后,先抛出一点自己的理解,让面试官看出你对应聘的职位是做过功课的。透过发问,更了解公司的组织文化和工作上实际会遇到的问题。 2、在其位谋其职 提问要展现专业度,切忌太跳脱或者故意装高深,引发尴尬,又给人好高骛远的感觉。记住一点:不提和所聘岗位无关的问题。 3、真诚 提问环节也是考验情商的时候!关注他人感受,掌握好分寸感。要明白提问不是辩论,你不是为了和面试官互相切磋知识、技能,而是真诚、虚心请教。 ## 2.37、 了解哪些设计模式?说说都用过哪些设计模式 设计模式的分类 总体来说是`三大类`: **创建型模式** ,五种:工厂方法模式、 抽象工厂模式、单例模式、建造者模式、原型模式 **结构性模式** ,共七种: 适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。 **行为型模式**,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介模式、解释器模式。 其实还有两类:并发型模式和线程池模式 ## 2.38、 如何判断一个单链表是否有环? **快慢指针查询**,快指针每次跳两个,慢指针每次跳一个,如果两指针相等时,就证明有环 **环的入口:** 用两个指针,一个指向快慢指针相交点(这里就是慢指针走,慢指针在走快指针的一半就相当于快指针走的路了,还会到这个点),一个指向单链表的头结点(模拟慢指针从头走,也是走快指针的一半),一起走,当两个指针相等时,就是环的入口。 **数学思维:** 设从单链表头节点到环入口节点的距离是D,环入口到相交点的距离是X,设slow和fast第一次相遇时fast走了n圈环,slow走的距离为len,那么fast走的距离是2*len,可以得出下面的两个等式: ```java len = D + X 2 * len = D + X + n * R 两个等式相减可以的到:len = n * R - X ``` 如果还不行的话,自己画个带环的单链表就明白了 ## 2.39、 操作系统如何进行分页调度? 笔者不懂,凉凉……  **1.分页的作用:** 高效率地利用内存,可以运行比物理内存空间更大的程序; **2.分页机制的原理:** 利用两级页表将内存分割成4KB/页的大小,强制内存对齐提高效率; **3.页表结构:** PDE与PTE在内存中各个位的主要作用,表项与页之间的对应关系。 ## 2.40、 匿名内部类是什么?如何访问在其外面定义的变量? **匿名内部类:** 没有名字,通常用来简化代码,只能用一次(使用的话必须要继承父类或者实现接口) **访问在其外面定义的变量:** 必须要final类型的变量(也可以不用final类型,但只能使用不能修改) # 三面 ## 3.1、 自我介绍,做过什么项目。 这。就不说了,某度一搜一堆,项目要挑自己做的最有水平的拿出来 ## 3.2、java虚拟机的区域如何划分 **程序计数器(独立内存)** 当前线程所执行的字节码的行号指示器。 **Java虚拟机栈(独立内存)** 每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。 **本地方法栈(独立内存)** 本地方法栈则为虚拟机使用到的Native方法(百度说是Java中声明的可调用的C/C++实现的方法)服务。 **Java堆(共享内存):** 存放对象实例 **方法区(共享内存):** 存储已被虚拟区加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 **运行时常量池:** 存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。 ## 3.3、 双亲委派模型中,从顶层到底层,都是哪些类加载器,分别加载哪些类? **(1)启动类加载器(Bootstrap ClassLoader)** 这个类加载器负责将存放在JAVA_HOME/lib下的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用。 **(2)扩展类加载器(Extension ClassLoader)** 这个加载器负责加载JAVA_HOME/lib/ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器 **(3)应用程序类加载器(Application ClassLoader)** 这个加载器是ClassLoader中getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(Classpath)上所指定的类库,可直接使用这个加载器,如果应用程序没有自定义自己的类加载器,一般情况下这个就是程序中默认的类加载器 ## 3.4、 有没有可能父类加载器和子类加载器,加载同一个类?如果加载同一个类,该使用哪一个类? 双亲委派,先在父类看能不能加载,如果能则由父加载,否则给子类加载 ## 3.5、 HashMap的结构,get(),put()是如何实现的? **Put:** 1、先将key和value封装到Node节点中 2、底层会调用key的hashcode()方法,通过hash函数将hash值转换为数组下标,下标位置上如果没有任何元素,就把该Node添加到该位置上(该下标处) 如果该下标处对应的位置上已经存在元素或链表(多于一个元素变成链表),那么就会拿着新节点的key与链表上的每一个人节点中的key进行equals。 1、 如果所有对比(equals)都返回false,那么这个新节点将会被添加到链表的尾部。(大于8个就会转换成红黑树) 2、 如果其中有一个对比(equals)返回true,那么这个节点上的value将会被新节点的value覆盖。 **Get:** 1、底层会调用key的hashcode()方法,通过hash函数将hash值转换为数组下标,通过数组下标快速定位到数组的指定位置上,如果这个位置上没有任何元素,那么返回null。 2、如果这个位置上有单向链表(该位置上有元素,或者有红黑树),那么会拿着我们get(key)中的key和单向链表中的每个节点的key进行equals,如果说所有的equals都返回false,那么这个get方法返回false。 3、只要其中有一个节点的key和参数key的equals对比的结果返回true,那么这个节点的value就是我们想要找的value,get方法返回这个value. ## 3.6、 ConcurrentHashMap的get(),put(),又是如何实现的?ConcurrentHashMap有哪些问题? ConcurrentHashMap的锁是读锁还是写锁? put 操作一上来就`锁定了整个segment`,这当然是为了`并发的安全`,修改数据是不能并发进行的,必须得有个判断是否超限的语句以确保容量不足时能够 rehash, 而比较难懂的是这句int index = hash & (tab.length - 1),原来segment里面才是真正的hashtable,即每个segment是一个传统意义上的hashtable, 从两者的结构就可以看出区别,这里就是找出需要的entry在table的哪一个位置,之后得到的entry就是这个链的第一个节点,如果e!=null,说明找到了,这是就要替换节点的值(onlyIfAbsent == false),否则,我们需要new一个entry,它的后继是first,而让tab[index]指向它,什么意思呢?实际上就是将这个新entry 插入到链头,剩下的就非常容易理解了。 get 方法(请注意,这里分析的方法都是针对桶的,因为`ConcurrentHashMap的最大改进就是将粒度细化到了桶上`),首先判断了当前桶的数据个数是 否为0,为0自然不可能get到什么,只有返回null,这样做避免了不必要的搜索,也用最小的代价避免出错。然后得到头节点(方法将在下面涉及)之后就 是根据hash和key逐个判断是否是指定的值,如果是并且值非空就说明找到了,直接返回; 程序非常简单,但有一个令人困惑的地方,这句return readValueUnderLock(e)到底是用来干什么的呢?研究它的代码,在锁定之后返回一个值。但这里已经有一句V v = e.value得到了节点的值,这句return readValueUnderLock(e)是否多此一举? 事实上,这里完全是为了并发考虑的,这里当v为空时,可能是一个线程正在改变节点,而之前的 get操作都未进行锁定,根据bernstein条件,读后写或写后读都会引起数据的不一致,所以这里要对这个e重新上锁再读一遍,以保证得到的是正确值, 这里不得不佩服Doug Lea思维的严密性。整个get操作只有很少的情况会锁定,相对于之前的Hashtable,并发是不可避免的啊! `ConcurrentHashmap只能保证自身数据在多线程的环境下不被破坏,而并不能保证业务逻辑的正确性。` ConcurrentHashMap的锁是`读锁` ## 3.7、 sleep()和wait()分别是哪个类的方法,有什么区别?synchronized底层如何实现的?用在代码块和方法上有什么区别? sleep方法是`Thread`类的静态方法,调用此方法会让当前线程暂停指定的时间,将执行机会(CPU)让给其他线程,但是不会释放锁,因此休眠时间结束后自动恢复(程序回到就绪状态)。 wait是`Object`类的方法,调用对象的wait方法导致线程放弃CPU的执行权,同时也放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的notify或notifyAll方法才能唤醒等待池中的线程进入等锁池(lock pool),如果线程重新获得对象的锁就可以进入就绪状态。 wait只能在同步控制方法中或者同步控制块中使用,而sleep可以在任何地方使用。 wait 可以指定时间也可以不指定,指定时间 wait(time) 在 time时间内 有别的线程 notifyAll() 是不会唤醒到它 。sleep 必须指定时间 synchronized代码块是由一对`monitorenter/monitorexit`指令实现的, Monitor对象是同步的基本实现单元。 现代的(Oracle) JDK6中, JVM对此进行了大刀阔斧地改进,提供了三种不同的Monitor实现,也就是常说的**三种不同的锁**: 偏斜锁(Biased Locking)、轻量级锁和重量级锁,大大改进了其性能。 同步方法直接在方法上加synchronized实现加锁,同步代码块则在方法内部加锁,很明显,`同步方法锁的范围比较大,而同步代码块范围要小点`, 一般同步的范围越大,性能就越差,一般需要加锁进行同步的时候,肯定是范围越小越好,这样性能更好。 ## 3.8、 什么是线程池?如果让你设计一个动态大小的线程池,如何设计,应该有哪些方法? 线程池就是创建若干个可执行的线程放入一个池(容器)中,有任务需要处理时,会提交到线程池中的任务队列,处理完之后线程并不会被销毁,而是仍然在线程池中等待下一个任务。 一个线程池包括以下四个基本组成部分: **线程管理器 (ThreadPool):** 用于创建并管理线程池,包括创建线程,销毁线程池,添加新任务; **工作线程 (PoolWorker):** 线程池中线程,在没有任务时处于等待状态,可以循环的执行任务; **任务接口 (Task):** 每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等; **任务队列 (TaskQueue):** 用于存放没有处理的任务。提供一种缓冲机制; **所包含的方法** ```java //创建线程池 private ThreadPool() //获得一个默认线程个数的线程池 public static ThreadPool getThreadPool() //执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器决定 public void execute(Runnable task) //批量执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器决定 public void execute(Runnable[] task) //销毁线程池,该方法保证在所有任务都完成的情况下才销毁所有线程,否则等待任务完成才销毁 public void destroy() //返回工作线程的个数 public int getWorkThreadNumber() //返回已完成任务的个数,这里的已完成是只出了任务队列的任务个数,可能该任务并没有实际执行完成 public int getFinishedTasknumber() //在保证线程池中所有线程正在执行,并且要执行线程的个数大于某一值时。增加线程池中线程的个数 public void addThread() //在保证线程池中有很大一部分线程处于空闲状态,并且空闲状态的线程在小于某一值时,减少线程池中线程的个数 public void reduceThread() ``` ## 3.9、 什么是死锁?JVM线程死锁,你该如何判断是因为什么?如果用VisualVM,dump线程信息出来,会有哪些信息? (不懂还是不懂,

这篇关于某Java大佬在地表最强Java企业(阿里)面试总结的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程