31.请你说说innodb和myisam的区别?
得分点 事务、锁、读写性能、存储结构
标准回答
InnoDB是具有事务、回滚和崩溃修复能力的事务安全型引擎,它可以实现行级锁来保证高性能的大量数据中的并发操作;MyISAM是具有默认支持全文索引、压缩功能及较高查询性能的非事务性引擎。
具体来说,可以在以下角度上形成对比:
| 事务 | |
功能 | InnoDB | MyISAM |
---|---|---|
事务 | 支持事务 | 不支持 |
数据锁 | 支持行级锁 | 支持表级锁 |
读写性能 | 增删改性能更优 | 查询性能更优 |
全文索引 | 不支持(但可通过插件等方式支持) | MyISAM默认支持 |
外键 | 支持 | 不支持 |
存储结构 | 在磁盘存储为一个文件 | 在磁盘上存储成三个文件(表定义、数据、索引) |
存储空间 | 需要更多的内存和存储 | 支持支持三种不同的存储格式:静态表(默认)、动态表、压缩表 |
加分回答 InnoDB中行级锁是怎么实现的?
InnoDB行级锁是通过给索引上的索引项加锁来实现的。只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁。
当表中锁定其中的某几行时,不同的事务可以使用不同的索引锁定不同的行。另外,不论使用主键索引、唯一索引还是普通索引,InnoDB都会使用行锁来对数据加锁。
32.String、StringBuffer、Stringbuilder有什么区别
得分点 字符串是否可变,StringBuffer、StringBuilder线程安全问题
标准回答 Java中提供了String,StringBuffer两个类来封装字符串,并且提供了一系列方法来操作字符串对象。
- String是一个不可变类,也就是说,一个String对象创建之后,直到这个对象销毁为止,对象中的字符序列都不能被改变。 由 char[] 数组构成,使用了 final 修饰,对 String 进行改变时每次都会新生成一个 String 对象,然后把指针指向新的引用对象。
- StringBuffer可变并且线程安全
- StringBuiler可变但线程不安全。
- 使用环境:操作少量字符数据用 String;单线程操作大量数据用 StringBuilder;多线程操作大量数据用 StringBuffer。
- StringBuffer、StringBuild提供了append()、insert()、reverse()、setCharAt()、setLength()、等方法来改变这个字符串对象的字符序列。
33.请你说说HashMap底层原理
得分点 数据结构、put()流程、扩容机制
标准回答
- 数据结构 :在JDK8中,HashMap底层是采用“数组+链表+红黑树”来实现的。
- 自动扩容机制:是为了保证HashMap初始时不必占据太大的内存,而在使用期间又可以实时保证有足够大的空间。HashMap的默认容量为16,默认的负载因子为0.75,当HashMap中元素个数超过容量乘以负载因子的个数时,就创建一个大小为前一次两倍的新数组,再将原来数组中的数据复制到新数组中。当数组长度到达64且链表长度大于8时,链表转为红黑树。
- put()流程 put()方法的执行过程中,主要包含四个步骤:
-
- 判断数组,若发现数组为空,则进行首次扩容。
-
- 判断头节点,若发现头节点为空,则新建链表节点,存入数组。
-
- 判断头节点,若发现头节点非空,则将元素插入槽内。
-
- 插入元素后,判断元素的个数,若发现超过阈值则再次扩容。
-
扩容机制 向HashMap中添加数据时,有三个条件会触发它的扩容行为:
- 如果数组为空,则进行首次扩容。
- 将元素接入链表后,如果链表长度达到8,并且数组长度小于64,则扩容。
- 添加后,如果数组中元素超过阈值,即比例超出限制(默认为0.75),则扩容。 并且,每次扩容时都是将容量翻倍,即创建一个2倍大的新数组,然后再将旧数组中的数组迁移到新数组里。
加分回答
HashMap是非线程安全的,在多线程环境下,多个线程同时触发HashMap的改变时,有可能会发生冲突。所以,在多线程环境下不建议使用HashMap,可以考虑使用Collections将HashMap转为线程安全的HashMap,更为推荐的方式则是使用ConcurrentHashMap。
34.说说你了解的JVM内存模型
得分点 类加载子系统、执行引擎、运行时数据区
标准回答 JVM由三部分组成:类加载子系统、执行引擎、运行时数据区
- 类加载子系统,可以根据指定的全限定名来载入类或接口。
- 执行引擎,负责执行那些包含在被载入类的方法中的指令。
- 当程序运行时,JVM需要内存来存储许多内容,例如:字节码、对象、参数、返回值、局部变量、运算的中间结果,等等,JVM会把这些东西都存储到运行时数据区中,以便于管理。
- 而运行时数据区又可以分为方法区、堆、虚拟机栈、本地方法栈、程序计数器。
35.说说JVM的垃圾回收机制
得分点 新生代收集、老年代收集、混合收集、整堆收集
标准回答
垃圾回收可以分为如下几类:
- 新生代收集:目标为新生代的垃圾收集。
- 老年代收集:目标为老年代的垃圾收集,目前只有CMS收集器会有这种行为。
- 混合收集:目标为整个新生代及部分老年代的垃圾收集,目前只有G1收集器会有这种行为。
- 整堆收集:目标为整个堆和方法区的垃圾收集。
36.类加载机智
(1)加载 :把字节码通过二进制的方式转化到方法区中的运行数据区
(2)连接:
验证:验证字节码文件的正确性。
准备:正式为类变量在方法区中分配内存,并设置初始值,final类型的变量在编译时已经赋值了
解析:将常量池中的符号引用(如类的全限定名)解析为直接引用(类在实际内存中的地址)
(3)初始化 :执行类构造器(不是常规的构造方法),为静态变量赋初值并初始化静态代码块。
37.请你说一下抽象类和接口的区别
得分点 接口与抽象类的方法,接口与抽象类的常量与变量,单继承多实现
标准答案
接口和抽象类相同点有:
- 接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其它类实现和继承
- 接口和抽象类都可以有抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法
接口和抽象类不同点:
- 接口里只能包含抽象方法和默认方法,不能为普通方法提供方法实现;抽象类则可以包含普通方法。
- 接口里只能定义静态常量( public static final),不能定义普通成员变量;抽象类里既可以定义普通成员变量,也可以定义静态常量
- 接口里不包含构造器;抽象类可以包含构造器,但抽象类的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作
- 接口里不能包含初始化块,抽象类则可以包含初始化块
- 一个类最多只能有一个父类,包括抽象类;但一个类可以直接实现多个接口
38.请你说说==与equals()的区别
得分点 和equals()比较基本变量用法,和equals()对比引用变量的用法
标准回答
== 比较基本类型,比较的是值,==比较引用类型,比较的是内存地址。
equlas是Object类的方法,本质上与==一样,但是有些类重写了equals方法,比如String的equals被重写后,比较的是字符值,另外重写了equlas后,也必须重写hashcode()方法。
39.说说synchronize的用法及原理
得分点 作用于三个位置、对象头、锁升级
标准回答 用法 synchronized可以作用在三个不同的位置,对应三种不同的使用方式,这三种方式的区别是锁对象不同。
不同的锁对象,意味着不同的锁粒度
- 作用在静态方法上,则锁是当前类的Class对象。
- 作用在普通方法上,则锁是当前的实例(this)。
- 作用在代码块上,则需要在关键字后面的小括号里,显式指定一个对象作为锁对象。
原理 synchronized的底层是采用Java对象头来存储锁信息的,并且还支持锁升级。 Java对象头包含三部分,分别是Mark Word、Class Metadata Address、Array length。其中,Mark Word用来存储对象的hashCode及锁信息,Class Metadata Address用来存储对象类型的指针,而Array length则用来存储数组对象的长度。
Java 6为了减少获取锁和释放锁带来的性能消耗,引入了偏向锁和轻量级锁。所以,在Java 6中,锁一共被分为4种状态,级别由低到高依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。随着线程竞争情况的升级,锁的状态会从无锁状态逐步升级到重量级锁状态。锁可以升级却不能降级,这种只能升不能降的策略,是为了提高效率。
40.JVM有哪些垃圾回收算法?
(1)标记清除算法: 标记不需要回收的对象,然后清除没有标记的对象,会造成许多内存碎片。
(2)复制算法: 将内存分为两块,只使用一块,进行垃圾回收时,先将存活的对象复制到另一块区域,然后清空之前的区域。用在新生代
(3)标记整理算法: 与标记清除算法类似,但是在标记之后,将存活对象向一端移动,然后清除边界外的垃圾对象。用在老年代