Java基础面试题
1. HashMap 和 HashTable 以及 CurrentHashMap 的区别
首先说下最大的区别,HashMap是线程非安全的,效率比较高;HashTable和CurrentHashMap是线程安全的,效率比HashMap差一点,但CurrentHashMap比HashTable更加高效一些,因为CurrentHashMap采用了更加高效的分段锁机制。
https://blog.csdn.net/majingjing66/article/details/74451916
2. synchronized 和 volatile 、ReentrantLock 、CAS 的 区别。
synchronized提供了同步锁的概念,被synchronized修饰的代码段可以防止被多个线程同时执行,必须一个线程把synchronized修饰的代码段都执行完毕了,其他的线程才能开始执行这段代码。 因为synchronized保证了在同一时刻,只能有一个线程执行同步代码块,所以执行同步代码块的时候相当于是单线程操作了,那么线程的可见性、原子性、有序性(线程之间的执行顺序)它都能保证了。
volatile关键字的作用就是保证了可见性和有序性(不保证原子性),如果一个共享变量被volatile关键字修饰,那么如果一个线程修改了这个共享变量后,其他线程是立马可知的。为什么是这样的呢?比如,线程A修改了自己的共享变量副本,这时如果该共享变量没有被volatile修饰,那么本次修改不一定会马上将修改结果刷新到主存中,如果此时B去主存中读取共享变量的值,那么这个值就是没有被A修改之前的值。如果该共享变量被volatile修饰了,那么本次修改结果会强制立刻刷新到主存中,如果此时B去主存中读取共享变量的值,那么这个值就是被A修改之后的值了。
volatile和synchronized的作用和区别是什么?
- (1)、volatile只能作用于变量,使用范围较小。synchronized可以用在方法、类、同步代码块等,使用范围比较广。 (要说明的是,java里不能直接使用synchronized声明一个变量,而是使用synchronized去修饰一个代码块或一个方法或类。)
- (2)、volatile只能保证可见性和有序性,不能保证原子性。而可见性、有序性、原子性synchronized都可以保证。
- (3)、volatile不会造成线程阻塞。synchronized可能会造成线程阻塞。
ReenTrantLock可重入锁和synchronized的区别
1、可重入性:
从名字上理解,ReenTrantLock的字面意思就是再进入的锁,其实synchronized关键字所使用的锁也是可重入的,两者关于这个的区别不大。两者都是同一个线程没进入一次,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。
2、锁的实现:
Synchronized是依赖于JVM实现的,而ReenTrantLock是JDK实现的,有什么区别,说白了就类似于操作系统来控制实现和用户自己敲代码实现的区别。前者的实现是比较难见到的,后者有直接的源码可供阅读。
3、性能的区别:
在Synchronized优化以前,synchronized的性能是比ReenTrantLock差很多的,但是自从Synchronized引入了偏向锁,轻量级锁(自旋锁)后,两者的性能就差不多了,在两种方法都可用的情况下,官方甚至建议使用synchronized,其实synchronized的优化我感觉就借鉴了ReenTrantLock中的CAS技术。都是试图在用户态就把加锁问题解决,避免进入内核态的线程阻塞。
4、功能区别:
便利性:很明显Synchronized的使用比较方便简洁,并且由编译器去保证锁的加锁和释放,而ReenTrantLock需要手工声明来加锁和释放锁,为了避免忘记手工释放锁造成死锁,所以最好在finally中声明释放锁。
5、锁的细粒度和灵活度:很明显ReenTrantLock优于Synchronized
ReenTrantLock独有的能力:
- 1、ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。
- 2、ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
- 3、ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。
ReenTrantLock实现的原理:
在网上看到相关的源码分析,本来这块应该是本文的核心,但是感觉比较复杂就不一一详解了,简单来说,ReenTrantLock的实现是一种自旋锁,通过循环调用CAS操作来实现加锁。它的性能比较好也是因为避免了使线程进入内核态的阻塞状态。想尽办法避免线程进入内核的阻塞状态是我们去分析和理解锁设计的关键钥匙。
https://blog.csdn.net/songzi1228/article/details/99975018/
3. JVM 类加载机制、垃圾回收算法对比、Java 虚拟机结构等。
JVM 类加载机制
在代码编译后,就会生成JVM(Java虚拟机)能够识别的二进制字节流文件(*.class)。而JVM把Class文件中的类描述数据从文件加载到内存,并对数据进行校验、转换解析、初始化,使这些数据最终成为可以被JVM直接使用的Java类型,这个说来简单但实际复杂的过程叫做JVM的类加载机制。
https://blog.csdn.net/zhangliangzi/article/details/51319033
https://www.cnblogs.com/wxd0108/p/6681618.html
垃圾回收算法对比
1、标记-清除算法
最基础的算法,GC会判断堆中对象是否不可达,如果满足清理条件(查看该对象是否有必要执行finalize()方法,有无必要的标准是该对象有没有被调用过finalize方法或该对象有没有覆盖finalize()方法,因为finalize()只能被调用一次),则给这个对象进行标记,将对象放在F-queue队列。此时除非对象在finalize()方法中重新获得了引用,否则它就会被清除掉。
以下几种算法不过都是对标记清除算法的改进。
2、复制算法
将内存分为大小相等的两块,当对象不可达后并不是及时清理,而是等待正在使用的内存满了之后,将该内存内还存活的对象整体复制到另一块内存中,复制结束后再清理掉原内存块中的所有内容。这种方法的优点是快速,但牺牲了一半的内存。方法的改进版(事实上也是虚拟机的做法)是只在新生代空间使用复制算法,并且由于新生代对象生命周期往往很短,因此又将新生代区域分为Eden和Survivor空间。其中Eden分配的空间又比Survivor大出很多,从而节省内存空间。如果存活对象过多,使得Survivor区也满,那么就会转移Survivor区对象到老年代。
3、标记-整理算法
标记过程与1一样,将1中的清除过程换成了整理,即将内存中存活的对象归拢到一边,使得内存更“紧凑”一些,整理之后将边界之外的对象清理掉。这种算法是为了防止2算法中出现存活率100%的极端情况,那么复制就没有止境了。
4、分代算法
新生代采用2算法,老年代采用1或3算法。这是由他们的特点决定的,新生代注定了其中很多对象生命周期转瞬即逝,因此复制算法移动的存货对象并不是很多。而老年代存活率较高,只能采用1、3来执行,提高效率。
Java 虚拟机结构
https://www.cnblogs.com/wangyu19900123/p/11641580.html
4. Java 的四大引用
一.强引用(StrongReference)
只要该引用还一直指向对象,就不会被gc回收,即使发生OOM,也不会被回收
二.软引用(SoftReference)
它的性质属于可有可无的那种,如果内存空间足够,就不会被gc回收,如果内存空间不足啦,就会回收这些对象的内存,一般用于缓存。
三.弱引用(WeakReference)
它的性质也属于可有可无的那种,弱引用的对象拥有更短的生命周期,只要垃圾回收器扫描到它,不管内存空间是否充足,都会回收它的内存
四.虚引用(PhantomReference)
必须与ReferenceQueue联合使用,对象在垃圾回收之前就会被加入到引用对列中,可以用来跟踪对象被垃圾回收器回收的活动。
https://www.cnblogs.com/fomin/p/10254547.html
5、final 、finally、finalize 区别。
final用于声明属性,方法和类,分别表示属性不可交变,方法不可覆盖,类不可继承。
finally是异常处理语句结构的一部分,表示总是执行。
finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,供垃圾收集时的其他资源回收,例如关闭文件等。
https://www.cnblogs.com/smart-hwt/p/8257330.html
6、接口和抽象类的区别
接口是对动作的抽象,抽象类是对根源的抽象。
抽象类表示的是,这个对象是什么。接口表示的是,这个对象能做什么。
https://www.cnblogs.com/yongjiapei/p/5494894.html
7、java 中==和 equals 和 hashCode 的区别
https://blog.csdn.net/waitingbb123/article/details/85329076
8、String、StringBuffer、StringBuilder 区别
都是字符串类,String 类中使用字符数组保存字符串,因有 final 修饰符,String 对象是不可变的,每次对 String 操作都会生成新的 String 对象,这样效率低,且浪费内存空间。但线程安全。StringBuilder 和 StringBuffer 也是使用字符数组保存字符,但这两种对象都是可变的,即 对字符串进行 append 操作,不会产生新的对象。它们的区别是:StringBuffer 对方法加了同步锁,是线程安全的,StringBuilder 非线程安全。
9、进程和线程的区别
进程:具有一定独立功能的程序,是系统进行资源分配和调度运行的基本单位。
线程:进程的一个实体,是 CPU 调度的苯单位,也是进程中执行运算的最小单位,即执行处理机调度的基本单位,如果把进程理解为逻辑上操作系统所完成的任务,线程则表示完成该任务的许多可能的子任务之一。
关系:一个进程可有多个线程,至少一个;一个线程只能属于一个进程。同一进程的所有线程共享该进程的所有资源。不同进程的线程间要利用消息通信方式实现同步。
区别:进程有独立的地址空间,而多个线程共享内存;进程具有一个独立功能的程序,线程不能独立运行,必须依存于应用程序中;
10、说说你对 Java 反射的理解
在运行状态中,对任意一个类,都能知道这个类的所有属性和方法,对任意一个对象,都能调用它的任意一个方法和属性。这种能动态获取信息及动态调用对象方法的功能称为 java 语言的反射机制。
反射的作用:开发过程中,经常会遇到某个类的某个成员变量、方法或属性是私有的,或只对系统应用开放,这里就可以利用 java 的反射机制通过反射来获取所需的私有成员或是方法。
-
获取类的 Class 对象实例 Class clz = Class.forName(“com.zhenai.api.Apple”);
-
根 据 Class 对 象 实 例 获 取 Constructor 对 象 Constructor appConstructor = clz.getConstructor();
-
使 用 Constructor 对 象 的 newInstance 方 法 获 取 反 射 类 对 象 Object appleObj = appConstructor.newInstance();
-
获取方法的 Method 对象 Method setPriceMethod = clz.getMethod(“setPrice”, int.class);
-
利用 invoke 方法调用方法 setPriceMethod.invoke(appleObj, 14);
-
通过 getFields()可以获取 Class 类的属性,但无法获取私有属性,而 getDeclaredFields()可以获取到包括私有属性在内的所有属性。带有 Declared 修饰的方法可以反射到私有的方法,没有 Declared 修饰的只能用来反射公有的方法,其他如 Annotation\Field\Constructor 也是如此。
ass 类的属性,但无法获取私有属性,而 getDeclaredFields()可以获取到包括私有属性在内的所有属性。带有 Declared 修饰的方法可以反射到私有的方法,没有 Declared 修饰的只能用来反射公有的方法,其他如 Annotation\Field\Constructor 也是如此。