某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这篇关于某Java大佬在地表最强Java企业(阿里)面试总结的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-22项目:远程温湿度检测系统
- 2024-12-21《鸿蒙HarmonyOS应用开发从入门到精通(第2版)》简介
- 2024-12-21后台管理系统开发教程:新手入门全指南
- 2024-12-21后台开发教程:新手入门及实战指南
- 2024-12-21后台综合解决方案教程:新手入门指南
- 2024-12-21接口模块封装教程:新手必备指南
- 2024-12-21请求动作封装教程:新手必看指南
- 2024-12-21RBAC的权限教程:从入门到实践
- 2024-12-21登录鉴权实战:新手入门教程
- 2024-12-21动态权限实战入门指南