一、静态库和动态库如何制作及使用,区别是什么
静态库在程序的链接阶段被复制到了程序中;动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。
二、说一说ConcurrentHashMap的实现原理
在JDK8中,ConcurrentHashMap的底层数据结构与HashMap一样,也是采用“数组+链表+红黑树”的形式。同时,它又采用锁定头节点的方式降低了锁粒度,以较低的性能代价实现了线程安全。底层数据结构的逻辑可以参考HashMap的实现,下面我重点介绍它的线程安全的实现机制。
1. 初始化数组或头节点时,ConcurrentHashMap并没有加锁,而是CAS的方式进行原子替换(原子操作,基于Unsafe类的原子操作API)。
2. 插入数据时会进行加锁处理,但锁定的不是整个数组,而是槽中的头节点。所以,ConcurrentHashMap中锁的粒度是槽,而不是整个数组,并发的性能很好。
3. 扩容时会进行加锁处理,锁定的仍然是头节点。并且,支持多个线程同时对数组扩容,提高并发能力。每个线程需先以CAS操作抢任务,争抢一段连续槽位的数据转移权。抢到任务后,该线程会锁定槽内的头节点,然后将链表或树中的数据迁移到新的数组里。
4. 查找数据时并不会加锁,所以性能很好。另外,在扩容的过程中,依然可以支持查找操作。如果某个槽还未进行迁移,则直接可以从旧数组里找到数据。如果某个槽已经迁移完毕,但是整个扩容还没结束,则扩容线程会创建一个转发节点存入旧数组,届时查找线程根据转发节点的提示,从新数组中找到目标数据。
加分回答
ConcurrentHashMap实现线程安全的难点在于多线程并发扩容,即当一个线程在插入数据时,若发现数组正在扩容,那么它就会立即参与扩容操作,完成扩容后再插入数据到新数组。在扩容的时候,多个线程共同分担数据迁移任务,每个线程负责的迁移数量是 (数组长度 >>> 3) / CPU核心数。
也就是说,为线程分配的迁移任务,是充分考虑了硬件的处理能力的。多个线程依据硬件的处理能力,平均分摊一部分槽的迁移工作。另外,如果计算出来的迁移数量小于16,则强制将其改为16,这是考虑到目前服务器领域主流的CPU运行速度,每次处理的任务过少,对于CPU的算力也是一种浪费。
三、JVM包含哪几部分
JVM由三部分组成:类加载子系统、执行引擎、运行时数据区。
1. 类加载子系统,可以根据指定的全限定名来载入类或接口。
2. 执行引擎,负责执行那些包含在被载入类的方法中的指令。
3. 当程序运行时,JVM需要内存来存储许多内容,例如:字节码、对象、参数、返回值、局部变量、运算的中间结果,等等,JVM会把这些东西都存储到运行时数据区中,以便于管理。而运行时数据区又可以分为方法区、堆、虚拟机栈、本地方法栈、程序计数器。
加分回答
运行时数据区是开发者重点要关注的部分,因为程序的运行与它密不可分,很多错误的排查也需要基于对运行时数据区的理解。在运行时数据区所包含的几块内存空间中,方法区和堆是线程之间共享的内存区域,而虚拟机栈、本地方法栈、程序计数器则是线程私有的区域,就是说每个线程都有自己的这个区域
四、说说你对Spring Boot的理解
Spring Boot本身并不提供Spring的核心功能,而是作为Spring框架的脚手架框架,以达到快速构建项目,预置第三方配置,开箱即用的目的。
Spring Boot 核心功能:
1. 自动配置:针对很多Spring应用程序常见的应用功能,Spring Boot能自动提供相关配置。
2. 起步依赖:Spring Boot通过起步依赖为项目的依赖管理提供帮助。起步依赖其实就是特殊的Maven依赖和Gradle依赖,利用了传递依赖解析,把常用库聚合在一起,组成了几个为特定功能而定制的依赖。
3. 端点监控:Spring Boot 可以对正在运行的项目提供监控。
Spring Boot 优点如下:
· 可以快速构建项目;
· 可以对主流开发框架的无配置集成;
· 项目可独立运行,无需外部依赖Servlet容器;
· 提供运行时的应用监控;
· 可以极大地提高开发、部署效率;
· 可以与云计算天然集成。
加分回答
其实从本质上来说,Spring Boot就是Spring,它帮你完成了一些Spring Bean配置。Spring Boot使用“习惯优于配置”的理念让你的项目快速地运行起来,使用Spring Boot能很快的创建一个能独立运行、准生产级别、基于Spring框架的项目。
五、谈谈MySQL的事务隔离级别
隔离性描述的是一个事务所做的修改何时对其它事务可见。
MySQL数据库有四种隔离级别。
未提交读:事务可以读取到其它事务未提交的数据。在这个隔离级别下会导致脏读。
提交读:也叫不可重复读,事务所做的修改只有在提交过后才能对其它事务可见。在这个隔离级别下会出现幻读。幻读是指一个事务中连续执行相同的查询,得到的结果集可能不一致(其他事务对数据进行了操作),对比两次的结果集,数据就好像是凭空出现亦或凭空消失。
可重复读:一个事务中连续执行相同的查询,查询的结果集总是一致的。在这个隔离级别下事务读取到的是当前事务开启前的数据版本。MySQL通过MVCC版本控制机制和一致性锁定读(自动加上Next-Key Locking,锁定查询的记录和一定的范围)解决了幻读问题。
串行化:InnoDB存储引擎自动为每个select语句加上共享锁。使MySQL读写操作之间串行执行,从而避免了脏读和幻读的,但是数据库也就无法并发读写了。
六、说一说ArrayList的实现原理
数组实现、默认容量为10、每次扩容1.5倍
标准回答
ArrayList是基于数组实现的,它的内部封装了一个Object[]数组。
通过默认构造器创建容器时,该数组先被初始化为空数组,之后在首次添加数据时再将其初始化成长度为10的数组。我们也可以使用有参构造器来创建容器,并通过参数来显式指定数组的容量,届时该数组被初始化为指定容量的数组。
如果向ArrayList中添加数据会造成超出数组长度限制,则会触发自动扩容,然后再添加数据。扩容就是数组拷贝,将旧数组中的数据拷贝到新数组里,而新数组的长度为原来长度的1.5倍。
ArrayList支持缩容,但不会自动缩容,即便是ArrayList中只剩下少量数据时也不会主动缩容。如果我们希望缩减ArrayList的容量,则需要自己调用它的trimToSize()方法,届时数组将按照元素的实际个数进行缩减。
加分回答
Set、List、Queue都是Collection的子接口,它们都继承了父接口的iterator()方法,从而具备了迭代的能力。但是,相比于另外两个接口,List还单独提供了listIterator()方法,增强了迭代能力。iterator()方法返回Iterator迭代器,listIterator()方法返回ListIterator迭代器,并且ListIterator是Iterator的子接口。ListIterator在Iterator的基础上,增加了向前遍历的支持,增加了在迭代过程中修改数据的支持。
七、Serializable接口为什么需要定义serialVersionUID常量
兼容性、自定义
serialVersionUID是序列化版本,为一个类定义序列化版本,是出于兼容性的考虑。如果某个类随着项目进行了升级,那么对于升级之前序列化的数据,在升级之后反序列化时就很可能出现不兼容的情况。如果事先定义了序列化的版本,则在反序列化的时候,只要版本不变就可以将其认定为同一个class文件。Java采用以下的形式来定义序列化版本:
private static final long serialVersionUID = ...;
需要注意的是,如果没有显示定义serialVersionUID,则JVM会根据类的信息自动计算出它的值,如果升级前后类的内容发生了变化,该值的计算结果通常就不同,这会导致反序列化的失败。所以,最好在打算序列化的类中显示地定义serialVersionUID,这样即便在序列化后它对应的类被修改了,由于版本号是一致的,所以该对象依然可以被正确的反序列化。
加分回答
如果类的修改会导致反序列化失败,则应该为此类分配新的serialVersionUID,那么对类的哪些内容进行修改会导致反序列化失败呢?
1. 如果修改类时只是修改了方法,则反序列化不受影响。
2. 如果修改类时只是修改了静态变量,则反序列化不受影响。
3. 如果修改类时改变了实例变量,则可能导致反序列化失败
八、说一说你对Spring IoC的理解
IoC是控制反转的意思,是一种面向对象编程的设计思想。在不采用这种思想的情况下,我们需要自己维护对象与对象之间的依赖关系,很容易造成对象之间的耦合度过高。尤其是在一个大型的项目中,对象与对象之间的关系是十分复杂的,这十分不利于代码的维护。IoC则可以解决这种问题,它可以帮我们维护对象与对象之间的依赖关系,并且降低对象之间的耦合度。
说到IoC就不得不说DI,DI是依赖注入的意思,它是IoC实现的实现方式。由于IoC这个词汇比较抽象而DI比较直观,所以很多时候我们就用DI来代替它,在很多时候我们简单地将IoC和DI划等号,这是一种习惯。实现依赖注入的关键是IoC容器,它的本质就是一个工厂。
加分回答
在以Spring为代表的轻量级Java EE开发风行之前,实际开发中是使用更多的是EJB为代表的开发模式。在EJB开发模式中,开发人员需要编写EJB组件,这种组件需要满足EJB规范才能在EJB容器中运行,从而完成获取事务,生命周期管理等基本服务。
Spring提供的服务和EJB并没有什么区别,只是在具体怎样获取服务的方式上两者的设计有很大不同:Spring IoC提供了一个基本的JavaBean容器,通过IoC模式管理依赖关系,并通过依赖注入和AOP切面增强了为JavaBean服务于事务管理、生命周期管理等基本功能。
而对于EJB,一个简单的EJB组件需要编写远程/本地接口、Home接口和Bean的实体类,而且EJB运行不能脱离EJB容器,查找其他EJB组件也需要通过诸如JNDI的方式,这就造成了对EJB容器和技术规范的依赖。也就是说Spring把EJB组件还原成了POJO对象或者JavaBean对象,以此降低了用用开发对于传统J2EE技术规范的依赖。
在应用开发中开发人员设计组件时往往需要引用和调用其他组件的服务,这种依赖关系如果固化在组件设计中,会造成依赖关系的僵化和维护难度的增加,这个时候使用IoC把资源获取的方向反转,让IoC容器主动管理这些依赖关系,将这些依赖关系注入到组件中,这就会让这些依赖关系的适配和管理更加灵活。
九、说一说synchronized的实现原理
synchronized的底层是采用Java对象头来存储锁信息的,并且还支持锁升级。
十、说一说NIO的实现原理
NIO是基于IO多路复用模型的实现,它包含三个核心组件,分别是Buffer、Channel、Selector。
1. NIO是面向缓冲区的,在NIO中所有的数据都是通过缓冲区处理的。Buffer就是缓冲区对象,无论读取还是写入,数据都是先进入Buffer的。Buffer的本质是一个数组,通常它是一个字节数组,也可以是其他类型的数组。Buffer是一个接口,它的实现类有ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。
2. Channel是一个通道,可以通过它读取和写入数据。与流不同的是,流是单向的,而Channel是双向的。数据可以通过Channel读到Buffer里,也可以通过Channel写入到Buffer里。为了支持不同的设备,Channel接口有好几种子类,如FileChannel用于访问磁盘文件、SocketChannel和ServerSocketChannel用于TCP协议的网络通信、DatagramChannel用于UDP协议的网络通信。
3. Selector是多路复用器,可以通过它监听网络IO的状态。它可以不断轮询注册的Channel,如果某Channel上有连接、读取、写入事件发生,则这个Channel就处于就绪状态,就会被Selector轮询出来。所有被轮询出来的Channel集合,我们可以通过SelectionKey获取到,然后进行后续的IO操作。
加分回答
Buffer对象包含三个重要的属性,分别是capacity、position、limit。其中,capacity代表Buffer的容量,就是说Buffer中最多只能写入capacity个数据。position代表访问的位置,它的初始值为0,每读取/写入一个数据,它就会向后移动一个位置。limit代表访问限制,就是本次操作最多能读取/写入多少个数据。这三个属性的关系是,position<=limit<=capacity,Buffer通过不断调整position和limit的值,使得自身可以不断复用。
十一、说一说zset类型的底层数据结构
zset底层的存储结构包括ziplist或skiplist,在同时满足有序集合保存的元素数量小于128个和有序集合保存的所有元素的长度小于64字节的时候使用ziplist,其他时候使用skiplist。
当ziplist作为zset的底层存储结构时候,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个元素保存元素的分值。
当skiplist作为zset的底层存储结构的时候,使用skiplist按序保存元素及分值,使用dict来保存元素和分值的映射关系。
十二、常用的 Linux 命令有:
| 命令 | 说明 |
| ---- | --------------------------------------------------------- |
| cd | 切换当前目录 |
| ls | 查看当前文件与目录 |
| grep | 通常与管道命令一起使用,用于对一些命令的输出进行筛选加工 |
| cp | 复制文件或文件夹 |
| mv | 移动文件或文件夹 |
| rm | 删除文件或文件夹 |
| ps | 查看进程情况 |
| kill | 向进程发送信号 |
| tar | 对文件进行打包 |
| cat | 查看文件内容 |
| top | 查看操作系统的信息,如进程、CPU占用率、内存信息等(实时) |
| free | 查看内存使用情况 |
十三、HTTPS协议中间人攻击是什么?
客户端和服务器之间的桥梁、双向获取并且篡改信息
中间人攻击是指攻击者通过与客户端和客户端的目标服务器同时建立连接,作为客户端和服务器的桥梁,处理双方的数据,整个会话期间的内容几乎是完全被攻击者控制的。攻击者可以拦截双方的会话并且插入新的数据内容。