阿里java面试题

(1)JVM如何加载一个类的过程,双亲委派模型中有哪些方法?

类的生命周期:加载、(验证、准备、解析)链接、初始化、使用和卸载七个阶段

其中类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持 Java 语言的运行时绑定(也成为动态绑定或晚期绑定)。

加载阶段:通过类的全限定名取得类的二进制流,转为方法区数据结构,在Java堆中生成对应的Class对象,作为对方法区这些数据的访问入口

验证阶段:文件格式是以0xCAFEBABE开头,版本号是否合理,元数据,字节码符号引用的验证。

准备阶段:类变量(static变量)赋初值(0,NULL……),常量被赋正确的值。

解析阶段:符号引用替换为直接引用,类或接口的解析(需要判断是否为数组),字段解析(从本类找到接口->父接口->父类->祖父类 依次查找),类方法解析(与字段差不多,但是先父类后接口 ),接口方法解析(只搜父接口)

初始化:执行类构造器(static{}),static变量赋值语句,子类的<clinit>调用前保证父类的<clinit>被调用

双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。这些类加载器的父子关系不是以继承的关系实现,而都是使用组合关系来复用父加载器的代码。

双亲委派模式的方法:向上不断查看是否加载了类(findLoadedClass()),向下尝试加载类(loadClass()),自定义类加载器需要回调(findClass())。

即使两个类来源于同一个 Class 文件,只要加载它们的类加载器不同,那这两个类就必定不相等。这里的“相等”包括了代表类的 Class 对象的 equals()、isAssignableFrom()、isInstance()等方法的返回结果,也包括了使用 instanceof 关键字对对象所属关系的判定结果。

具体请查看Blog Java类装载过程与类装载器

(2)HashMap如何实现的?

在JDK1.7及以前,HashMap中维护着Entry,Entry中维护着key,value以及hash和next指针,而整个HashMap实际就是一个Entry数组

当向 HashMap 中 put 一对键值时,它会根据 key的 hashCode 值计算出一个位置, 该位置就是此对象准备往数组中存放的位置。 

如果该位置没有对象存在,就将此对象直接放进数组当中;如果该位置已经有对象存在了,则顺着此存在的对象的链开始寻找(为了判断是否是否值相同,map不允许<key,value>键值对重复), 如果此链上有对象的话,再去使用 equals方法进行比较,如果对此链上的每个对象的 equals 方法比较为 false,则将该对象放到数组当中,然后将数组中该位置以前存在的那个对象链接到此对象的后面。 

get方法类似,通过key取hash找到数组的某个位置,然后遍历这个数组上的每个Entry,直到key值equals则返回。

如果Hash碰撞严重,那么JDK1.7中的实现性能就很差,因为每次插入都要遍历完整条链去查看key值是否重复,每次get也要遍历整个链,在JDK1.8中,由于链表的查找复杂度为O(n),而红黑树的查找复杂度为O(logn),JDK1.8中采用链表/红黑树的方式实现HashMap,达到某个阀值时,链表转成了红黑树。

具体请查看 JDK7与JDK8中HashMap的实现

(3)HashMap和Concurrent HashMap区别, Concurrent HashMap 线程安全吗, ConcurrentHashMap如何保证 线程安全?

HashMap不是线程安全的,ConcurrentHashMap是线程安全的,HashMap内部维护着一个Entry数组,而ConcurrentHashMap内部有一个Segment段,它将大的HashMap切分成若干个段(小的HashMap),然后让数据在每一段上Hash,这样多个线程在不同段上的Hash操作一定是线程安全的,所以只需要同步同一个段上的线程就可以了,这样实现了锁的分离,大大增加了并发量。ConcurrentHashMap的实现中还使用了不变模式final和volatile来保障线程安全

具体请查看 ConcurrentHashMap总结

(4)HashMap和HashTable 区别,HashTable线程安全吗?

  1. HashMap允许key和value为null,HashTable不允许。

  2. HashMap是非线程安全的,HashTable是线程安全的

  3. HashMap的迭代器是Iterator是fail-fast迭代器,当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException(关于ConcurrentModificationException请查看 ConcurrentModificationException的原因以及解决措施) 而HashTable的迭代器有enumerator和Iterator两种,enumerator不会抛出上述异常。

  4. hash算法不同,HashMap能更广泛地分散到数组的不同位置

  5. 扩展数组的算法不同,HashTable:2 * 原数组长度+1,HashMap:原数组长度 * 2

(5)进程间通信有哪几种方式?

传统的进程间通信的方式有大致如下几种:

# 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
# 有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
# 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
# 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
# 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
# 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
# 套接字( socket ) : 套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。

Java如何支持进程间通信。我们把Java进程理解为JVM进程。很明显,传统的这些大部分技术是无法被我们的应用程序利用了(这些进程间通信都是靠系统调用来实现的)。但是Java也有很多方法可以进行进程间通信的。 
除了上面提到的Socket之外,当然首选的IPC可以使用Rmi,或者Corba也可以。另外Java nio的MappedByteBuffer也可以通过内存映射文件来实现进程间通信(共享内存)。

有两种类型的进程间通信(IPC):

本地过程调用(LPC):LPC用在多任务操作系统中,使得同时运行的任务能互相会话。这些任务共享内存空间使任务同步和互相发送信息。

远程过程调用(RPC):RPC类似于LPC,只是在网上工作。RPC开始是出现在Sun微系统公司和HP公司的运行UNIX操作系统的计算机中。

 

(6)JVM分为哪些区,每一个区干吗的?

虚拟机将所管理的内存分为以下几个部分:

  • 程序计数器        指向正在执行的字节码地址

  • 虚拟机栈           存放局部变量表,操作数栈,动态链接,方法出口

  • 本地方法区        Native方法服务

  • 堆                     用来存放对象实例的

  • 方法区              用于存储已经被虚拟机加载过的类信息,常量(JDK7中String常量池被移到堆中),静态变量(JDK7中被移到Java堆),及时编译期编译后的代码(类方法)等数据。

具体请查看  Java内存区域

(7)JVM如何GC,新生代,老年代,持久代,都存储哪些东西?

JVM通过可达性(可触及性)分析算法标记出哪些对象是垃圾对象,然后将垃圾对象进行回收,在新生代中采用复制算法,在老年代中采用标记清理或标记压缩算法。新生代存储了新new出的对象,老年代存储了大的对象和多次GC后仍然存在的老年对象,持久代存储了类信息,常量(JDK7中String常量池被移到堆中),静态变量(JDK7中被移到了Java堆),类方法

具体请查看 JVM 垃圾回收机制 , Java内存区域

(8)GC用的引用可达性分析算法中,哪些对象可作为GC Roots对象?

  • 栈中引用的对象

  • 方法区中静态成员或者常量引用的对象(全局对象)

  • JNI方法栈中引用对象

总体来说就是,全局中的引用(例如常量或者静态属性)与执行上下文(例如栈帧中的本地变量表)。

具体请查看  JVM 垃圾回收机制

(9)快速排序,过程,复杂度?

快排的基本思想是:

1.先从数列中取出一个数作为基准数(pivot)。

2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。

3.再对左右区间重复第二步,直到各区间只有一个数。

平均复杂度O(NlogN),最差O(N^2),最好O(NlogN)

具体请查看 排序总结(不断更新)

(10)什么是二叉平衡树,如何插入节点,删除节点,说出关键步骤。

二叉平衡树:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

AVL树的插入和删除,主要是依靠左旋和右旋来达到平衡状态。

红黑树的插入,插入节点是红色,通过一系列选择和着色使其成为红黑树。

步骤为:

  • 插入节点的父亲节点是黑色,直接插入。

  • 插入节点的父亲节点是红色:

  1. 插入节点的叔叔节点是红色的话,将父节点和叔叔节点变成黑色,父节点的父节点变成红色。然后整体上移

  2. 叔叔节点是黑色,当前节点是右孩子,通过旋转将当前节点转到左孩子

  3. 叔叔节点是黑色,当前节点是左孩子,一次旋转一次着色。

所以红黑树的插入需要最多两次旋转,删除需要最多三次旋转

具体请查看 红黑树

(11)TCP如何保证可靠传输?三次握手过程?

TCP用三次握手和滑动窗口机制来保证传输的可靠性和进行流量控制。
第一次握手:
客户端发送一个TCP的SYN标志位置1的包指明客户打算连接的服务器的端口,以及初始序号X,保存在包头的序列号(Sequence Number)字段里。
第二次握手:
服务器发回确认包(ACK)应答。即SYN标志位和ACK标志位均为1同时,将确认序号(Acknowledgement Number)设置为客户的ISN加1以.即X+1。
第三次握手.
客户端再次发送确认包(ACK) SYN标志位为0,ACK标志位为1.并且把服务器发来ACK的序号字段+1,放在确定字段中发送给对方.并且在数据段放写ISN的+1

(12)TCP和UDP区别?

TCP---传输控制协议,提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
UDP---用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快

(13)滑动窗口算法?

1. 首先是AB之间三次握手建立TCP连接。在报文的交互过程中,A将自己的缓冲区大小(窗口大小)3发送给B,B同理,这样双方就知道了对端的窗口大小。
2. A开始发送数据,A连续发送3个单位的数据,因为他知道B的缓冲区大小。在这一波数据发送完后,A就不能再发了,需等待B的确认。
3. A发送过来的数据逐渐将缓冲区填满。
4. 这时候缓冲区中的一个报文被进程读取,缓冲区有了一个空位,于是B向A发送一个ACK,这个报文中指示窗口大小为1。
A收到B发过来的ACK消息,并且知道B将窗口大小调整为1,因此他只发送了一个单位的数据并且等待B的下一个确认报文。
5. 如此反复。

(14)Linux下如何进行进程调度的?

在Linux中,进程的运行时间不可能超过分配给他们的时间片,他们采用的是抢占式多任务处理,所以进程之间的挂起和继续运行无需彼此之间的协作。

(15)操作系统什么情况下会死锁?

产生死锁的原因:一是系统提供的资源数量有限,不能满足每个进程的使用;二是多道程序运行时,进程推进顺序不合理。  

银行家算法:安全状态一定没有死锁发生
产生死锁的必要条件是:1、互斥条件;2、不可剥夺条件(不可抢占);3、部分分配;4、循环等待。

详情查看操作系统总结

(16)常用的hash算法有哪些?

  1. 直接定址法 :地址集合 和 关键字集合大小相同

  2. 数字分析法 :根据需要hash的 关键字的特点选择合适hash算法,尽量寻找每个关键字的 不同点

  3. 平方取中法:取关键字平方之后的中间极为作为哈希地址,一个数平方之后中间几位数字与数的每一位都相关,取得位数由表长决定。比如:表长为512,=2^9,可以取平方之后中间9位二进制数作为哈希地址。

  4. 折叠法:关键字位数很多,而且关键字中每一位上的数字分布大致均匀的时候,可以采用折叠法得到哈希地址,

  5. 除留取余法除P取余,可以选P为质数,或者不含有小于20的质因子的合数

  6. 随机数法:通常关键字不等的时候采用此法构造哈希函数较恰当。

实际工作中需要视不同的情况采用不同的hash函数:

  1. kao虑因素:计算哈希函数所需要的时间,硬件指令等因素。

  2. 关键字长度

  3. 哈希表大小

  4. 关键字分布情况

  5. 记录查找的频率。(huffeman树)

处理冲突的方法:

  1. 开放地址法:现行探测再散列 只要哈希表为填满,总能找到一个不冲突的地址,二次探测再散列 表长为素数时才可能保证总能找到一个不冲突的地址,随机探测再散列取决于伪随机数列

  2. 再哈希法:不易发生聚集,但是增加了计算的时间

  3. 链地址法;Chord协议中,一致性hash有应用。

(17)什么是一致性哈希?

consistent hashing 是一种 hash 算法,简单的说,在移除 / 添加一个 cache 时,它能够尽可能小的改变已存在key 映射关系,尽可能的满足单调性的要求。

通过一个环形hash空间,将服务器和需要缓存的内容都映射到环形空间内,将缓存的内容映射到下一个服务器中,当一个服务器down了以后,就映射到下一个服务器上。

但是这样会对下一个服务器造成很大的压力,没有平均到其他服务器上。这里就提出了增加虚拟节点,虚拟节点保证了映射到每个服务器的概率均衡。 

详情查看一致性Hash算法

(18)如何理解分布式锁?

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。

分布式锁可以使用数据库锁,redis(缓存),zookeeper来实现

数据库锁主要是使用唯一索引来代替锁,加锁时就往表中插入一个记录,其他线程要加锁则会唯一性约束无法成功。缺点是

1. 无法阻塞(加锁失败后,需要再发一次请求再次尝试)。

2. 如果服务器宕机,则无法解锁,造成死锁(可以从应用层上加定时任务,超过时间则强制解锁)

redis作为分布式锁:

第一种方式是缓存锁,就是使用setnx,即只有在某个key不存在情况才能set成功该key,这样就达到了多个进程并发去set同一个key,只有一个进程能set成功。(缺点和数据库锁一样,但是redis自带过期时间EX,则不需要从应用层加定时任务,虽然redis有主从复制,由于主从复制是异步的,仍然无法保证宕机后锁丢失)

第二种方式是Redlock

为了解决锁丢失,提出了Redlock算法

Redlock算法假设有N个redis节点,这些节点互相独立,一般设置为N=5,这N个节点运行在不同的机器上以保持物理层面的独立。

算法的步骤如下:

  • 1、客户端获取当前时间,以毫秒为单位。

  • 2、客户端尝试获取N个节点的锁,(每个节点获取锁的方式和前面说的缓存锁一样),N个节点以相同的key和value获取锁。客户端需要设置接口访问超时,接口超时时间需要远远小于锁超时时间,比如锁自动释放的时间是10s,那么接口超时大概设置5-50ms。这样可以在有redis节点宕机后,访问该节点时能尽快超时,而减小锁的正常使用。

  • 3、客户端计算在获得锁的时候花费了多少时间,方法是用当前时间减去在步骤一获取的时间,只有客户端获得了超过3个节点的锁,而且获取锁的时间小于锁的超时时间,客户端才获得了分布式锁。

  • 4、客户端获取的锁的时间为设置的锁超时时间减去步骤三计算出的获取锁花费时间。

  • 5、如果客户端获取锁失败了,客户端会依次删除所有的锁。

redlock由于和时间有关,比如在应用程序发生长时间的fullgc时,也会导致问题。

zookeeper的分布式锁:

zookeeper实现锁的方式是客户端一起竞争写某条数据,比如/path/lock(路径),只有第一个客户端能写入成功,其他的客户端都会写入失败。写入成功的客户端就获得了锁,写入失败的客户端,注册watch事件,等待锁的释放,从而继续竞争该锁。

优点:

  • zookeeper支持watcher机制,这样实现阻塞锁,可以watch锁数据,等到数据被删除,zookeeper会通知客户端去重新竞争锁。

  • zookeeper的数据可以支持临时节点的概念,即客户端写入的数据是临时数据,在客户端宕机后,临时数据会被删除,这样就实现了锁的异常释放。使用这样的方式,就不需要给锁增加超时自动释放的特性了。

具体关于分布式锁的详情请查看分布式锁

(19)数据库中的范式有哪些?

第一范式:确保每列的原子性.
    如果每列(或者每个属性)都是不可再分的最小数据单元(也称为最小的原子单元),则满足第一范式.
第二范式:在第一范式的基础上更进一层,目标是确保表中的每列都和主键相关.
    如果一个关系满足第一范式,并且除了主键以外的其它列,都依赖于该主键,则满足第二范式.
第三范式:在第二范式的基础上更进一层,目标是确保每列消除传递依赖.
    如果一个关系满足第二范式,并且没有传递依赖,则满足第三范式.
    为了理解第三范式,需要根据Armstrong公里之一定义传递依赖。假设A、B和C是关系R的三个属性,如果A-〉B且B-〉C,则从这些函数依赖中,可以得出A-〉C,如上所述,依赖A-〉C是传递依赖。
例如:订单表(订单编号,定购日期,顾客编号,顾客姓名,……),初看该表没有问题,满足第二范式,每列都和主键列"订单编号"相关,再细看你会发现"顾客姓名"和"顾客编号"相关,"顾客编号"和"订单编号"又相关,最后经过传递依赖,"顾客姓名"也和"订单编号"相关。为了满足第三范式,应去掉"顾客姓名"列,放入客户表中。

(20)数据库中的索引的结构?什么情况下适合建索引?

mysql索引结构是B+树和hash。(hash索引只有在等值查询时,并且重复值少时才高效,具体两者区别请查看MySQL B+树索引和哈希索引的区别)

两种情况下不建议建索引。

第一种情况是表记录比较少,例如一两千条甚至只有几百条记录的表,没必要建索引,让查询做全表扫描就好了。至于多少条记录才算多,这个个人有个人的看法,我个人的经验是以2000作为分界线,记录数不超过 2000可以考虑不建索引,超过2000条可以酌情考虑索引。

另一种不建议建索引的情况是索引的选择性较低。所谓索引的选择性(Selectivity),是指不重复的索引值(也叫基数,Cardinality)与表记录数(#T)的比值:

Index Selectivity = Cardinality / #T

显然选择性的取值范围为(0, 1],选择性越高的索引价值越大,这是由B+Tree的性质决定的。

关于mysql索引的详情请查看MySQL索引背后的数据结构及算法原理

(21)Java中的NIO,BIO,AIO分别是什么?

 

(24)用什么工具调试程序?JConsole,用过吗?

(25)JVM中某个线程挂起,如何用工具查出原因?

visualVM Dump线程信息出来。然后查看是因为死锁,还是阻塞等

(26)线程同步与阻塞的关系?同步一定阻塞吗?阻塞一定同步吗?

(27)同步和异步有什么区别?

(28)线程池用过吗?

(29)如何创建单例模式?说了双重检查,他说不是线程安全的。如何高效的创建一个线程安全的单例?

(30)concurrent包下面,都用过什么?

(31)常用的数据库有哪些?redis用过吗?

(32)了解hadoop吗?说说hadoop的组件有哪些?hdfs,hive,hbase,zookeeper。说下mapreduce编程模型。

(33)你知道的开源协议有哪些?

(34)你知道的开源软件有哪些?

(35)你最近在看的书有哪些?

(37)了解哪些设计模式?说说都用过哪些设计模式

(38)如何判断一个单链表是否有环?

给定一个单链表,只给出头指针h:

1、如何判断是否存在环?

2、如何知道环的长度?

3、如何找出环的连接点在哪里?

4、带环链表的长度是多少?

解法:

1、对于问1,使用追赶的方法,设定两个指针slow、fast,从头指针开始,每次分别前进1步、2步。如存在环,则两者相遇;如不存在环,fast遇到NULL退出。

2、对于问2,记录下问1的碰撞点p,slow、fast从该点开始,再次碰撞所走过的操作数就是环的长度s。

3、问3:有定理:碰撞点p到连接点的距离=头指针到连接点的距离,因此,分别从碰撞点、头指针开始走,相遇的那个点就是连接点。

4、问3中已经求出连接点距离头指针的长度,加上问2中求出的环的长度,二者之和就是带环单链表的长度

(39)操作系统如何进行分页调度?

(40)匿名内部类是什么?如何访问在其外面定义的变量?

猜你喜欢

转载自blog.csdn.net/hjm4702192/article/details/79613400