1. ArrayList
面试题1: 说一说ArrayList的扩容规则
- ArrayList()底层使用数组存储数据,使用add()方法时,首次扩容为10,再次扩容时为上次的1.5倍
- 使用addAll()方法时。若原集合没有元素,扩容为max(10, 实际元素个数),有元素时扩容为max(1.5*原容量, 实际元素个数)
问题: vector是fail-safe还是fail-fast的
2. LinkedList
3. HashMap
4. Singleton
5. Sychronized
6. Volatile
面试题 1. 什么是JMM的可见性问题?为什么会出现可见性问题?volatile是怎么解决Java内存模型的可见性问题的?
- 可见性问题:一个线程(t1)里使用了主存中的成员变量,主线程对这个成员变量进行修改;而t1线程对于该成员变量不可见,也就是没有发现成员变量被修改了,进而使得程序的运行出现错误
- 为什么出现:t1线程里使用成员变量的代码出现了多次循环,即时编译器(JIT)会将出现多次循环解释为热点代码,并对其优化,直接将代码中的成员变量修改为它的值,而不是直接从主存中读取变量的值。当主线程对成员变量的值进行修改后,线程1已经不存在该成员变量,自然会出现对t1线程成员变量修改的不可见问题。
- volatile怎么解决:使用volatile修饰的成员变量,即时编译器不会对热点代码进行优化,而是一直从主存中读取成员变量,就解决了可见性问题!
精简回答:由于即时编译器优化或者缓存优化,导致了一个线程对共享变量的修改其他的线程看不到!使用Volatile修饰共享变量可以防止即时编译器等优化产生,让一个线程对共享变量的修改对其他线程可见!
面试题 2. 什么是JMM的有序性问题?volatile是怎么解决JMM的有序性问题的?
有序性问题:由于JIT的优化,对于执行顺序对程序运行结果不产生影响的代码,即时编译器可能会改变指令的执行顺序,即指令重排优化。在单线程的情况下,指令重排并不会对代码的运行结果产生影响,而多线程的情况则可能会由于指令的重排序使得程序执行的结果出现问题!
volatile怎么解决:使用volatile修饰的成员变量,在对其读写时会加上一个内存屏障,进而防止其指令的重排序!
精简回答:由于CPU指令重排序优化导致指令的执行顺序与编写顺序不一样!使用volatile修饰共享变量,在读写共享变量时会加上读、写屏障,阻止其他的读写操作越过屏障,从而达到阻止重排序的目的。
- volatile变量写时会在其上面加一个写屏障,可以防止上面的变量越过写屏障排到volatile变量之下
- volatile变量读时会在其下面家一个读屏障,可以防止下面的变量越过读屏障排到volatile变量之上
7. Hashtable
8. ConcurrentHashMap
9. ThreadLocal
10. ClassLoader
面试题1. 说一说JVM的类加载过程
JVM加载类的过程主要有三个阶段加载、链接和初始化,其中链接阶段又包括验证、准备和解析三个阶段。
首先,在加载阶段,类加载子系统会从磁盘中以二进制字节流的方式读取字节码文件,然后将其转化为方法区中运行时的存储结构,进而生成代表此类的Class对象。
然后,在链接阶段,类加载子系统会先检查验证加载阶段生成的Class文件是否符合JVM规范,然后进入准备阶段为静态变量分配内存并初始化为0,接着再执行解析操作,就是把常量池中的符号引用转化为直接引用。
最后,进入类的初始化阶段,类的初始化就是执行类构造器clinit()的过程,Java虚拟机会保证类的构造方法的线程安全。
面试题2: 说一说Java虚拟机的类加载器
类加载器用于实现类的加载动作,从Java虚拟机中的角度来看,只存在两种不同的类加载器:启动类加载器(Bootstrap ClassLoader)和自定义类记载器(User-Defined ClassLoader)。
从开发人员的角度来看,类加载器可以划分的更细致一些,包括启动类加载器、扩展类加载器和应用程序类加载器。当然用户也可以自定义类加载器。
- 启动类加载器存放于jre/lib目录,由底层的C++代码编写,Java程序无法直接引用。
- 扩展类加载器负责加载jre/lib/ext路径下的所有类库到内存中,开发者可以直接使用
- 用户程序类加载器负责加载用户类路径classpath上的指定类库,开发者可以直接这个类加载器。
名称 | 加载的类 | 说明 |
---|---|---|
Bootstrap ClassLoader(启动类加载器) | JAVA_HOME/jre/lib | 无法直接访问 |
Extension ClassLoader(拓展类加载器) | JAVA_HOME/jre/lib/ext | 上级为Bootstrap,显示为null |
Application ClassLoader(应用程序类加载器) | classpath | 上级为Extension |
自定义类加载器 | 自定义 | 上级为Application |
面试题3: 如何自定义类加载器?
自定义一个MyClassLoader继承自java.lang.ClassLoader,并重写findClass()方法,此方法根据类的全限定名在文件系统中查找类的字节码文件(.class),然后读取文件内容,通过defineClass()方法来把字节码代码转换成Class类的实例。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/25887408/1647095950018-04f1741b-6d37-4447-a597-bd7436ed7a01.png#clientId=u807c594b-bcd4-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=624&id=u8a52504e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=624&originWidth=647&originalType=binary&ratio=1&rotation=0&showTitle=false&size=79043&status=done&style=none&taskId=u5e926517-db27-451a-8d70-9a6f0ff897d&title=&width=647)
面试题4: 什么是双亲委派模型?
所谓双亲委派机制,指的就是:当一个类加载器收到了类加载请求的时候,它不去直接加载指定的类,而是把请求委托给父加载器去加载。只有父加载器无法加载这个类时,才使用这个加载器来负责类的加载。
- 双亲委派加载过程:
一个类加载器首先将类加载请求委托给父类加载器,只有父类加载器无法加载来,才尝试自己加载。源码中ClassLoader类的loadClass()方法负责类的加载,首先会判断类有没有加载。如果没有被加载,再判断其是否存在父加载器。如果存在父加载器就调用父加载器的loadClass()方法,若不存在父加载器就直接调用启动类加载器来加载类。启动类加载器加载失败,就使用当前类加载器来加载。如果所有的父类加载器都无法加载类,则有应用程序类在当前类路径下加载类。
- 双亲委派机制的好处:
- 通过双亲委派机制,可以避免类的重复加载,当父加载器加载一个子类时,子加载器不会在加载不会在加载这个类。
- 通过双亲委派的方式,可以保证类加载的安全性。因为启动类加载器只会加载JAVA_HOME下的jar包里的类,如java.long.Integer类,这个类是不会随意替换的,如果避免加载类路径下自定义的Integer类,防止Java核心API被篡改!
面试题5:怎么破坏双亲委派机制?
破坏双亲委派机制,必须自定义类加载器,继承ClassLoader类。要重写两个方法loadClass()和findClass(),如果没有重写loadClass()方法,就默认走双亲委派机制,重写了就可以打破双亲委派机制。重写findClass()是为了在保证在父加载器不能够满足类加载请求的时候,该方法能够完成类的加载。所以打破双亲委派机制要重写findClass()方法。
面试题6:Tomcat为什么要破坏双亲委派机制,怎么破坏的?
Tomcat是web容器,一个web容器中通常会部署多个web应用。而不同的web应用会使用同一个第三方库的不同版本。如果使用双亲委派机制,
11. JMM
12. GC
13. Finalize
14. Sort
15. Netty
面试题1: 介绍一下自己对Netty的认识
首先,Netty是一个基于NIO的client-server(客户端-服务端)框架,使用Netty可以快速的开发网络应用程序。然后,Netty极大的简化了TCP,UDP,Socket网络编程,性能及安全性都很优秀。Netty支持多种传输协议如HTTP,FTP等等