1. List和Set的区别
List有序,可重复,允许null,可以指定下标获取元素。
set无需,不重复
2.ArrayList和LinkedList区别?
- 底层数据结构不同。ArrayList-数组,LinkedList-链表
- 适用场景不同。Arraylist-随机查找,LinkedList-增删
- LinkedList实现Deque,能当队列使用。
3. 重载和重写的区别
- 重载:同一个类,方法名一样,参数类型,个数,顺序不同。返回类型和修饰符可以不同,发生在编译阶段。
- 重写:父子类,方法名,参数列表必须相同。返回类型<=父类,抛出异常<=父类,修饰符>=父类。
private不能重写。
4. hashCode和equals的关系
- 都是Object方法。
- 每个对象都有自己的哈希值,类似指纹。
hashCode不同,对象不同。
hashCode相同,对象可能相同。
对象相同,hashCode相同。 - 集合比较对象是否相等,会用到hashCode比较,相同,再用equals比较两个对象的引用(内存地址)是否相等。
5 String,StringBuffer,StringBuilder
- String 常量,不变。
- StringBuffer,线程安全
- StringBuilder,线程不安全
6. 泛型中extends,super的区别
- <? extends T>表示包含T在内的任何的T的子类。
- <? super T>表示包含T在内的任何T的父类。
7. Jdk1.7-Jdk1.8 HashMap发生了什么变化?
- jdk1.7=数组+链表,jdk1.8=数组+链表+红黑树
8 深拷贝和浅拷贝
- 浅拷贝:只会拷贝基本数据类型,以及引用对象的地址,指向同一个对象。
- 深拷贝:即会拷贝基本数据类型的值,也会对引用对象进行复制,指向不同的对象。
9 HashMap的put方法
- 根据key通过hash算法与运算得出数组下标
- 如果数组下标的元素为空,则将key,value封装成entry(1.7)Node(1.8),并放入该位置
- 如果数组下标的元素不为空。分成三种情况:
a. jdk1.7,判断是否需要扩容,不需要则头插法添加到链表中。
b. jdk1.8,判断当前位置是链表还是红黑树- 如果是红黑树Node,先判断红黑树是否存在该key,存在则更新,不存在则添加到红黑树中。
- 如果是链表结构,判断链表是否存在该key,存在则更新,不存在则尾插法存入链表。
- 插入后,在判断是否需要扩容。
10. HashMap扩容机制
- 先生成数组。
- 重新根据key通过hash算法和运算生成数组下标。把旧数据迁移到新数组上。
- 迁移完后,把新数组赋值给HashMap对象的table属性。
11 CopyOnWriteArrayList的底层原理?
- 线程安全。读操作-原数组,写操作-复制一个新数组上操作
- 写操作会加锁。
- 写操作完成后,原数组地址指向新数组。
- 适合读多写少的场景。
12 什么是字节码?有什么作用?
- javac编译后class文件。
- 一次编译到处运行,只要有jre环境,就可以运行。具有跨平台的作用。
13 Java的异常
- 所有异常的顶级父类Throwable
- Throwable有Exception和Error 2个子类
- Error子类:StackOverFlowException和OutOfMemoryError。比较严重的错误。
- Exception子类:NullPoinerException,illegalAccessException。
14 类加载器有哪些?
- BootStrapClassLoader-lib下的jar包和class文件
- ExtClassLoader-lib/ext下的jar包和class文件
- AppClassLoader -自定义加载器,加载classpath下的jar包和class文件
15 双亲委派机制?
加载一个类时,先从AppClassLoader查询该类是否已经加载过,如果没有,则找父级ExtClassLoader,也是判断是否加载过,如果没有再找父级BootStrapClassLoader,如果没有加载过,则判断是否可以加载,如果不能,再找子级ExtClassLoader,再找AppClassLoader。
16 JVM内存共享区
堆和方法区
17 JVM线程隔离数据区
虚拟机栈,本地方法栈,程序计数器
18 项目如何排查JVM问题
宕机
- JVM参数内存溢出时自动生成dump文件,再用MAT软件分析。
- 应用/中间件日志
- 服务器的日志,var/message文件
线上 - JMap查看各个区域的内存使用情况。
- JStack查看各个线程阻塞,死锁情况。
- JStat查看GC的情况。是否频繁GC,年轻代还是老年代。
- 查看占用CPU最多的线程。Top命令。
- 阿里的分析软件。arthas
19 一个对象从加载到GC,经历了哪些过程?
- 把字节码加载到方法区/元空间。
- 根据类信息在堆中创建对象。
- 对象分配在堆中的Eden区,一次Minor GC后进入Survivor区。后续Minor GC中,如果对象一直存在,将在survivor区来回拷贝,每移动一次,年龄+1
- 当年龄超过15后,移动到老年代
- Full GC后,被标记为垃圾对象,将被GC线程回收。
20 HashMap什么时候需要扩容
HashMap是Java中常用的存储键值对的数据结构,其内部实现了哈希表。在使用HashMap时,如果键值对的数量增加到一定程度,就 需要扩容以提高HashMap的性能。
具体来说,当HashMap中的元素数量超过负载因子(load factor)与当前容量之积时,就需要进行扩容操作。负载因子是一个浮点数,通常设置为0.75,在默认情况下,当HashMap中元素数量超过容量的0.75倍时,就会触发扩容操作。
在进行扩容操作时,HashMap会新建一个更大的数组,并将原有的元素重新计算散列值后放入新的数组中,以保证元素的散列位置正确。同时,容量也会随之扩大,以满足更多元素的存储需求。
扩容操作会涉及到元素的复制和重新计算散列值等操作,因此较其它操作而言耗时和资源消耗都会比较大。因此,在设计HashMap时,需要根据业务需求合理地选择负载因子大小,以减少扩容的次数,从而提高HashMap的性能。
21 JVM(Java虚拟机)包括以下几个区域:
程序计数器(Program Counter Register):它是一块较小的内存空间,用于保存当前线程正在执行的字节码指令的地址。当线程执行Java方法时,程序计数器被用来记录正在执行的虚拟机字节码指令的地址,它也会在线程切换后恢复到正确的执行位置。
Java虚拟机栈(JVM Stack):每个线程都有自己的 JVM 栈,用于存储方法调用和局部变量等信息。每个方法在执行的同时都会创建一个栈帧(Stack Frame),用于存储方法的参数、局部变量和返回值等信息。当方法执行完毕后,对应的栈帧会被销毁。如果 JVM 栈空间不足或者无法分配新的栈帧,就会抛出 StackOverflowError 或 OutOfMemoryError 异常。
堆(Heap):堆是 Java 虚拟机中最大的一块内存区域。所有的 Java 对象都在堆中分配,并且堆是由垃圾回收器进行自动管理的。在堆中分配时,可能会发生垃圾回收,这样会使得应用程序暂停或停止,因为在这个过程中,所有的线程都必须等待垃圾回收器完成工作。
方法区(Method Area):方法区是用于存储类的信息、常量池、静态变量、即时编译器编译后的代码等内容的内存区域。它也被称为“永久代(PermGen)”,但在 Java 8 中已经被 MetaSpace 取代,且静态变量存储在堆中。
运行时常量池(Runtime Constant Pool):运行时常量池是方法区的一部分,用于存储编译器生成的各种字面量和符号引用。例如,字符串常量池在运行时就是运行时常量池的一部分。在类加载的时候将会把所有的常量池中的符号和字符串转化为直接引用,并且这个过程可以称之为解析。但jdk8中,运行时常量池存储在堆中。元空间(Meta Space):元数据描述了该类的结构信息,例如类名、访问修饰符、方法签名等等
22 创建一个对象需要哪些步骤?
1.将Java类加载到方法区,加载到方法区的时候实际上就是创建了一个Klass,Klass中保存了这个Java类的所有信息,例如:变量、方法、父类、接口、构造方法、属性等。
2.而在完成对象的初始化时,JVM会在堆分配的空间中,创建一个Oop,这个Oop便是我们这个对象实例在内存中的对等体,主要存储这个对象实例的成员变量,其中这个Oop中存在一个指针,指向Klass,通过这个指针,JVM可以在运行期间,获取这个对象的所有类元信息。
3.把对象引用指向该内存空间
在 JDK 8 中,创建一个对象需要以下几个步骤:
- 类加载:当我们第一次使用一个类时,JVM 就会进行类的加载。在加载过程中,JVM 查找并读取相应的类文件,并将它们转换成 JVM 内部格式。
- 分配内存空间:一旦类被加载完成,JVM 就会为新对象分配内存空间。如果 Java 堆中没有足够的可用空间,则会触发垃圾回收器来释放一些无用的内存空间。如果定义成员变量时,赋予新对象,那么在分配空间时进行初始化。
- 初始化零值:在分配内存空间后,JVM 会将该内存空间中的所有二进制位都初始化为零值。对于引用类型来说,这意味着它们初始化为 null。
- 设置对象头信息:JVM 在对象内存空间中设置了一些额外的信息,称为对象头信息。其中包括锁信息、GC 相关信息等。
- 执行构造函数:最后,JVM 调用类的构造函数来完成对象的初始化。构造函数会根据用户提供的参数来设置对象的状态,例如属性值和其他相关状态。