面试准备:Java常见面试题汇总(一)

面试准备:Java常见面试题汇总(一)

面试准备:Java常见面试题汇总(二)

面试准备:Java常见面试题汇总(三)

文章目录

1.面向对象的特点/特性有哪些?*

面向对象四大特性包括了:抽象、封装、继承、多态。(也可以说是三大特性,除开抽象)

  • 抽象
    抽象是一种思维方式,抽象出客观事物的数据和行为,以封装的方式聚合成类。
  • 封装:
    封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。
  • 继承
    基于现有的类的数据和功能进行拓展,继承包含了实现继承、接口继承。实现继承可以直接使用父类的属性和方法,接口继承要求子类提供实现。
  • 多态
    多态是一个对象具有多种的状态。具体表现为父类的引用指向子类的实例,通过父类提供的相同的外部接口,提供子类的不同实现。

补充: Java的多态是编译时多态还是运行时多态?

都有。ref
编译时多态:方法重载都是编译时多态。根据实际参数的数据类型、个数和次序,Java在编译时能够确定执行重载方法中的哪一个。
运行时多态:方法覆盖表现出两种多态性,当对象引用本类实例时,为编译时多态,否则为运行时多态。

2.接口和抽象类的相同点和不同点?

相同点:

  1. 都不能被实例化。
  2. 都可以包含抽象方法。

不同点:

  1. 接口里只能包含抽象方法,静态方法和默认方法,抽象类可以包含普通方法。
  2. 接口不能包含构造器、初始化块,而抽象类可以包含构造器和初始化块。
  3. 一个类最多只能有一个直接父类,包括抽象类,但一个类可以直接实现多个接口。
  4. 接口中声明的变量默认都是public static final的,接口中的成员函数默认是public abstract的。抽象类则没有要求。

接口类:default方法属于实例,static方法属于类。
抽象类:可以在不提供实现的情况下继承接口。
抽象类:抽象类是否可继承实体类,但前提是实体类必须有明确的构造函数。

3.重载和重写有什么区别?

  • overload(重载)
    参数类型、个数、顺序至少有一个不相同。
  • override(重写)
    存在于父类和子类关系中。
    子类方法不能缩小父类方法的访问权限。
    子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。

4.基本数据类型有哪些?为什么需要基本数据类型?

参考:Java类型和类

5.为什么需要包装类?什么是自动拆装箱?

  • 因为Java是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型。
  • 包装类就属于引用类型,自动装箱和拆箱就是基本类型和引用类型之间的转换。

参考:Java类型和类

6.Java类的实例化顺序?

1. 父类静态变量和静态初始化块 ,按在代码中出现的顺序依次执行
2. 子类静态变量和静态初始化块 ,按在代码中出现的顺序依次执行
3. 父类成员变量和普通初始化块 ,按在代码中出现的顺序依次执行
4. 父类构造方法
5. 子类成员变量和实例初始化块 ,按在代码中出现的顺序依次执行
6. 子类构造方法

结论:对象初始化的顺序,先静态方法,再构造方法,每个又是先基类后子类。
参考:java对象实例化顺序

7.什么是值传递和引用传递?

值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量.
引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身 。

8.String和StringBuilder、StringBuffer的区别?

相同点:

  1. 三者都是final关键字限定的(public final class),不能被继承。
  2. 都使用了char数组来存储,实现CharSequence接口。

1、String 字符串常量(final修饰,不可被继承),当创建之后即不能更改。(可以通过StringBuffer和StringBuilder创建String对象(常用的两个字符串操作类))另外String的char[]数组是final修饰的private final char value[];,保证了String不可变。

从 JDK 1.5 开始,带有字符串变量的连接操作(+),JVM 内部采用的是 StringBuilder 来实现的,而之前这个操作是采用 StringBuffer 实现的。


StingBuffer和StringBuilder都继承自AbstractStringBuilder,他们的父类AbstractStringBuilder包含了char[] value,不是 final修饰的。

2、StringBuffer 字符串变量(线程安全),其也是final类别的,不允许被继承,其中的绝大多数方法都进行了同步处理,包括常用的Append方法也做了同步处理(synchronized修饰方法)。其自jdk1.0起就已经出现。其toString方法会进行对象缓存,以减少元素复制开销。

public synchronized String toString() {
    if (toStringCache == null) {
        toStringCache = Arrays.copyOfRange(value, 0, count);
    }
     return new String(toStringCache, true);
}

3、StringBuilder 字符串变量(非线程安全)其自jdk1.5起开始出现,也是final类别的。与StringBuffer一样都继承和实现了同样的接口和类,方法除了没使用synch修饰以外基本一致,不同之处在于最后toString的时候,会直接返回一个新对象

	@Override
    public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }

其他知识点,参考:String是最基本的数据类型吗?为什么String类型是不可变的?

9.Java集合框架的基础接口有哪些?集合框架的优点是什么?

Collection:
为集合层级的根接口。一个集合代表一组对象,这些对象即为它的元素。Java平台不提供这个接口任何直接的实现。

Set:
是一个不能包含重复元素的集合。这个接口对数学集合抽象进行建模,被用来代表集合,就如一副牌。

List:
是一个有序集合,可以包含重复元素。你可以通过它的索引来访问任何元素。List更像长度动态变换的数组。

Map:
是一个将key映射到value的对象.一个Map不能包含重复的key:每个key最多只能映射一个value。

一些其它的接口有Queue、Dequeue、SortedSet、SortedMap和ListIterator。

集合框架的部分优点如下:

  • 它经过了严格的测试,具有复用性和可操作性,并且某些集合框架还是线程安全的。

10.HashMap 与HashTable有什么区别?

  • 线程是否安全
    HashMap 是非线程安全的,HashTable 是线程安全的,因为 HashTable 内部的方法基本都经过synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);

  • 效率
    因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它;

  • 对 Null key 和 Null value 的支持
    HashMap 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个;HashTable 不允许有 null 键和 null 值,否则会抛出 NullPointerException。

  • 初始容量大小和每次扩充容量大小的不同
    ① 创建时如果不指定容量初始值,Hashtable 默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1。HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。
    ② 创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为 2 的幂次方大小(HashMap 中的tableSizeFor()方法保证)。也就是说 HashMap 总是使用 2 的幂作为哈希表的大小。

    负载因子默认都是0.75

  • 底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。

更多HashMap知识,参考:面试准备:Java8 HashMap详解

11.ArrayList 和 LinkedList 有什么区别?

参考:Java架构直通车——ArrayList和LinkedList底层原理解析

12.简单介绍Java异常框架?Error与Exception有什么区别?

Java对异常进行了分类,不同类型的异常分别用不同的Java类表示,所有异常的根类为java.lang.Throwable,Throwable下面又派生了两个子类:Error和Exception。

Error 表示应用程序本身无法克服和恢复的一种严重问题,程序只有死的份了,例如,说内存溢出和线程死锁等系统问题,比如说StackOverflowError和OutOfMemoryError。

Exception表示程序还能够克服和恢复的问题,其中又分为

  • unchecked异常
    派生自RuntimeException的异常类。使用throw语句可以随时抛出这种异常对象(也可以不必抛出)。例如,数组脚本越界(ArrayIndexOutOfBoundsException),空指针异常(NullPointerException)、类转换异常(ClassCastException)。RuntimeException是软件本身缺陷所导致的问题,也就是软件开发人员考虑不周所导致的问题,软件使用者无法克服和恢复这种问题,但在这种问题下还可以让软件系统继续运行或者让软件死掉
  • checked异常
    直接派生自Exception的异常类,必须被捕获或再次声明抛出。例如输入输出异常(IOException)或者数据库异常(SQLException)等等。

在这里插入图片描述

13.throw 和 throws关键字有什么区别?

throw是语句抛出一个异常,一般是在代码块的内部,当程序出现某种逻辑错误时由程序员主动抛出某种特定类型的异常

throws是方法可能抛出异常的声明。(用在声明方法时,表示该方法可能要抛出异常)

14.列举几个常见的运行时异常?*

常用的RuntimeException如:

java.lang.ClassNotFoundException 类不存在异常

java.lang.NullPointerException 空指针异常

java.lang.IndexOutOfBoundsException 数据下标越界

java.lang.IllegalArgumentException 参数异常

java.io.FileNotFoundException 文件不存在异常

运行时异常和非运行时异常
Exception Class
编译时和运行时

15.final, finally, finalize有什么区别?

  • final:
    Java并发编程实战——你真的了解final吗?
    注意:final重排序规则的作用是:在对象引用为任意线程可见之前,对象的final域已经被正确初始化过了,而普通域不具有这个保障。
    • final与static区别:浅析final关键字
    • static final
      很多时候会容易把static和final关键字混淆,static作用于成员变量用来表示只保存一份副本,而final的作用是用来保证变量不可变。
      运行这段代码就会发现,每次打印的两个j值都是一样的,而i的值却是不同的。从这里就可以知道final和static变量的区别了。
public class Test {
    public static void main(String[] args)  {
        MyClass myClass1 = new MyClass();
        MyClass myClass2 = new MyClass();
        System.out.println(myClass1.i);
        System.out.println(myClass1.j);
        System.out.println(myClass2.i);
        System.out.println(myClass2.j); 
    }
} 
class MyClass {
    public final double i = Math.random();
    public static double j = Math.random();
}
  • finally:
    其作用就是保证在try块中的代码执行完成之后,不管try块中是否抛出异常,必然会执行finally中的语句。

  • finalize:
    方法名。 finalize是在对象回收前做一些清扫工作,当对象不可达的时候,要经过至少两次标记的过程才真正宣告这个对象的死亡。

finalize()方法可以被子类对象所覆盖,然后作为一个终结者,当GC被调用的时候完成最后的清理工作(例如释放系统资源之类)。这就是终止。默认的finalize()方法什么也不做,当被调用时直接返回。

  • 对象在进行可达性分析后被发现不可达,它将会被第一次标记并进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法:当对象没有覆盖finalize()方法或者finalize()方法已经被JVM调用过,那么就没必要执行finalize()方法;如果被判定为有必要执行finalize()方法,那么此对象将会放置在一个叫做F-Quenen的队列之中,并在稍后由一个虚拟机自动建立的、低优先级的Finalize线程去触发这个方法。
  • 稍后GC将对F-Queue中的对象进行第二次小规模的标记,虚拟机会触发一个Finalize()线程去执行,此线程是低优先级的。然后该对象就会等待回收。
  • 如果对象要在finalize()中成功拯救自己——只要重新与引用链上的任何一个对象建立关系即可,譬如把自己(this关键字)赋值给某个类变量或者对象的成员变量,那么在第二次标记时它将被移出“即将回收”集合;finalize()方法是对象逃脱死亡的最后一次机会,如果对象这时候还没有成功逃脱,那他就会真的被回收了。

16.描述Java内存模型?

参考JVM虚拟机八问

17.Java中垃圾收集的方法有哪些?

参考JVM虚拟机八问

18.如何判断一个对象是否存活?(或者GC对象的判定方法)

参考JVM虚拟机八问

19. Minor GC、Major GC和Full GC之间的区别?什么是Stop The World?

参考JVM虚拟机八问

19.Java GC是在什么时候,对什么东西,做了什么事情?

参考JVM虚拟机八问

20.分别写出堆内存溢出与栈内存溢出的程序*

# 栈内存溢出
public void f() {
        f();
    }
# 堆内存溢出
public void testd() {
        Listlist = new ArrayList<>(); 
        int i = 0; 
        while (true) {
            list.add(new String(i + ""));
            i++;
        }
    }

21.Java 8 内存模型进行了哪些改进?

在 jdk1.8 中对内存模型中方法区的实现永久代(Perm Gen space)进行了移除,取而代之的是元空间(Metaspace)。

原因是在方法区中实现垃圾回收的条件比较苛刻,因此存在着内存溢出的风险。在 jdk1.8 之后,当方法区内存使用较多时,元空间会使用物理内存,减少了风险。

22.简述java内存分配与回收策率以及Minor GC和Major GC

(1)对象优先在堆的Eden区分配。

(2)大对象直接进入老年代.

(3)长期存活的对象将直接进入老年代.

当Eden区没有足够的空间进行分配时,虚拟机会执行一次Minor GC.Minor Gc通常发生在新生代的Eden区,在这个区的对象生存期短,往往发生Gc的频率较高,回收速度比较快;Full Gc/Major GC 发生在老年代,一般情况下,触发老年代GC的时候不会触发Minor GC,但是通过配置,可以在Full GC之前进行一次Minor GC这样可以加快老年代的回收速度。

23.描述Java类加载机制?

类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定)。

  • 加载:
    加载时类加载的第一个过程,在这个阶段,将完成一下三件事情:
    1. 通过一个类的全限定名获取该类的二进制流(也就是字节码文件)。
    2. 将该二进制流中的静态存储结构转化为方法区运行时数据结构
    3. 在内存中生成该类的Class对象,用来封装类在方法区内的数据结构,并且作为该类的数据访问入口。

加载阶段获取类的二进制字节流的动作是可控性最强的阶段,这一步我们可以去完成还可以自定义类加载器去控制字节流的获取方式(重写一个类加载器的 loadClass() 方法)。
类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它。

  • 验证:确保被加载的类的正确性

对文件格式、元数据、字节码、符号引用做验证。

  • 准备:为类的静态变量分配内存,并将其初始化为默认值
//在准备阶段value初始值为0 。在初始化阶段才会变为123 。
public static int value=123;
  • 解析:把类中的符号引用转换为直接引用
    符号引用就是一组符号来描述目标,可以是任何字面量。
    直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。比如,class.NAMETYPE这种形式的字符串转为具体的方法在内存中的地址。

  • 初始化:对类变量进行赋值及执行静态代码块。

类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下:

  1. 创建类的实例,也就是new的方式。
  2. 初始化某个类的子类,则其父类也会被初始化。
  3. 调用类的静态方法或者访问静态变量
  4. 反射(如 Class.forName(“com.shengsiyuan.Test”))
  5. Java虚拟机启动时被标明为启动类的类( JavaTest),直接使用 java.exe命令来运行某个主类。

24.什么是双亲委派模型?什么情况下会对双亲委派模型的破坏?

双亲委派模型,简单说就是类加载器试图加载某个类型的时候,除非父加载器找不到相应类型,否则尽量将这个任务代理给当前加载器的父加载器去做。使用委派模型的目的是避免重复加载Java类型确保其全局唯一性和安全性

假如我们自己编写一个类java.util.Object,它的实现可能有一定的危险性或者隐藏的bug。而我们知道Java自带的核心类里面也有java.util.Object,如果JVM启动的时候先行加载的是我们自己编写的java.util.Object,那么就有可能出现安全问题!
所以,Sun(后被Oracle收购)采用了双亲委派模型方式来保证最基本的、也是最核心的功能不会被破坏。

  1. 当一个类加载器接收到类加载任务时,先查缓存里有没有,如果没有,将任务委托给它的父加载器去执行。
  2. 父加载器也做同样的事情,一层一层往上委托,直到最顶层的启动类加载器为止。
  3. 如果启动类加载器没有找到所需加载的类,便将此加载任务退回给下一级类加载器去执行,而下一级的类加载器也做同样的事情。
  4. 如果最底层类加载器仍然没有找到所需要的class文件,则抛出异常

如果你自己写的一个类与核心类库中的类重名,会发现这个类可以被正常编译,但永远无法被加载运行。因为你写的这个类不会被应用类加载器加载,而是被委托到顶层,被启动类加载器在核心类库中找到了。如果没有双亲委托机制来确保类的全局唯一性,谁都可以编写一个java.lang.Object类放在classpath下,那应用程序就乱套了。


由于SPI(Service Provider Interface),由第三方来负责实现的类是无法放在Java核心类里面的,只能由第三方实现类的类加载器做加载。
参考:
Java架构直通车——以Tomcat为例谈双亲委派模型的破坏
Java架构直通车——以JDBC为例谈双亲委派模型的破坏

25.类加载器有哪些?

实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。
在这里插入图片描述
主要有一下四种类加载器:

  1. 启动类加载器(Bootstrap ClassLoader)用来加载java核心类库,无法被java程序直接引用。

核心类库,将加载‘\lib’目录下的类库。

  1. 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。

Java扩展库,默认加载JAVA_HOME/jre/lib/ext/目录下的所有jar包,当然也可以加载由java.ext.dirs系统属性指定的jar包,用来加载java的扩展库。

  1. 系统类加载器(system class loader,也称为Application class Loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。

加载用户类路径(CLASSPATH)下的类库,即来自在命令java中的classpath或者java.class.path系统属性或者CLASSPATH操作系统属性所指定的JAR类包和类路径。一般我们编写的java类都是由这个类加载器加载,这个类加载器是CLassLoader中的getSystemClassLoader()方法的返回值,所以也称为系统类加载器。

  1. 用户自定义类加载器,通过继承 java.lang.ClassLoader类的方式实现。

启动类加载器属于虚拟机的一部分,它是用C++写的,看不到源码;其他类加载器是用Java写的,比如扩展类加载器、应用类加载器。
除了启动类加载器(BootstrapClassLoader),每个类加载器都有一个父加载器。应用类加载器,它的父加载器是扩展类加载器。扩展类加载器的parent是null,所以它没有父加载器?有,它的父加载器就是BootstrapClassLoader。任何parent为null的加载器,其父加载器为BootstrapClassLoader

26.JVM,JDK和JRE有什么区别与联系?

  • JVM:
    Java虚拟机能够识别.class类文件的字节码并解释成机器码交给本地系统去执行。
    通过源程序到字节码,再到对应平台的机器语言,所以Java能够进行一次编译,处处运行。

字节码记录的是十六进制的字节(指令),但是这个文件属于二进制文件。

  • JRE:
    JRE是java runtime environment(java运行环境)的缩写。光有JVM还不能让class文件执行,因为在解释class的时候JVM会调用解释所需要的基本类库。

  • JDK:
    JDK是java development kit(java开发工具包)的缩写,它包含了JRE,并且集成了javac编译器和一些好用的小工具。总的来说JDK是用于java程序的开发,而jre则是只能运行class而没有编译的功能。

小工具:
- javac.exe(javac命令用于编译Java源文件)
- java.exe(ava命令用于运行Java程序,它会启动Java虚拟机,Java虚拟机加载相关的类,然后调用主程序main()方法)
- jar.exe(jar命令能够把Java应用打包成一个文件,jar命令可以打包任意文件,但通常情况下我们只把编译后的.class文件打包成JAR包)

eclipse、idea等其他IDE有自己的编译器而不是用JDK bin目录中自带的,所以在安装时你会发现他们只要求你选jre路径就ok了。

如果你只是为了运行一下 Java 程序的话,那么你只需要安装 JRE 就可以了。如果你需要进行一些 Java 编程方面的工作,那么你就需要安装 JDK 了。但是,这不是绝对的。有时,即使您不打算在计算机上进行任何 Java 开发,仍然需要安装 JDK。例如,如果要使用 JSP 部署 Web 应用程序,那么从技术上讲,您只是在应用程序服务器中运行 Java 程序。那你为什么需要 JDK 呢?因为应用程序服务器会将 JSP 转换为 Java servlet,并且需要使用 JDK 来编译 servlet。

27.Java 反射机制的作用?优缺点?

java反射机制的作用:

(1)在运行时判断任意一个对象所属的类

(2)在运行时构造任意一个类的对象

(3)在运行时判断任意一个类所具有的成员变量和方法

(4)在运行时调用任意一个对象的方法

反射就是动态加载对象,并对对象进行剖析。在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法,这种动态获取信息以及动态调用对象方法的功能成为Java反射机制。

优点:
可以动态的创建对象和编译,最大限度发挥了java的灵活性。

缺点:
对性能有影响。使用反射基本上一种解释操作,告诉JVM我们要做什么并且满足我们的要求,这类操作总是慢于直接执行java代码。

28.反射创建实例的方法*

  1. 通过全限定类名获取Class
Class<?> clz=Class.forName("String.class");
# 或者
String s="";
Class<? extends String> clz = s.getClass();
# 或者
Class<String> clz = String.class;
  1. 生成对象
# 通过构造器生成对象
Constructor<?> constructor = clz.getConstructor();
Object o = constructor.newInstance();
# 或者
# 直接生成对象
Object o = clz.newInstance();

29.哪些项目中用到了Java反射机制?*

jdbc中有一行代码:Class.forName(‘com.MySQL.jdbc.Driver.class’).newInstance();

Spring IOC依赖注入时也用到了。

30.线程同步的方法有哪些?*

synchronized 方法
synchronized 代码块
volatile
ReentrantLock
CountDownLatch
CyclicBarrier
ThreadLocal

31.解释一下锁的一些基本概念:可重入锁、可中断锁、公平锁、读写锁、乐观锁

  1. 可重入锁

同一个对象内,同一个线程在外层方法获取锁的时候,再进入该对象的内层方法会自动获取锁,而不会死锁。
这是因为该对象的对象头记录了获取到锁的线程ID,当线程再次执行内层方法需要锁的方法的时候,会比较对象头里线程ID与当前线程的ID,如果相同,就说明不需要再次获取锁了。像synchronized和ReentrantLock都是可重入锁。

所以通过对象头这一概念,同一个对象里两个加了synchronized的方法,是不能同时执行的。

  1. 可中断锁

可中断、可被抢占的锁,也就是他可以被其他线程中断,或者自己中断。

在Java中,synchronized就不是可中断锁,而Lock是可中断锁。如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。

  1. 公平锁

公平锁:即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。

非公平锁:即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。

在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。

而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。

  1. 读写锁

读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。
读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的(排他的)。 每次只能有一个写线程,但是可以有多个线程并发地读数据。

  • 互斥原则:
    读-读能共存,
    读-写不能共存,
    写-写不能共存。

ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。

可以通过readLock()获取读锁,通过writeLock()获取写锁。

  1. 乐观锁

CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

CAS最后会被翻译成一个CPU的特殊指令,这个指令是原子性的,不可被打断的。这样就确保了当比较预期值和内存值的时候,不会出现线程的切换。

32.synchronized什么情况下会释放锁?

1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
2)线程执行发生异常,此时JVM会让线程自动释放锁。
3).调用wait方法,在等待的时候立即释放锁,方便其他的线程使用锁.

33. synchronized和ReentrantLock有什么区别?

Lock和synchronized有以下几点不同:

  1. ReetrantLock是一个接口,而synchronized是Java中的关键字,synchronized 是 JVM 实现的,而 ReentrantLock 是 JDK 实现的;

  2. synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

  3. Lock是可中断锁,可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断。

  4. Lock可以是公平锁或者非公平锁,而synchronized只能是非公平锁。

  5. 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

  6. Lock可以设置等待锁的时间,而synchronized不行。

性能已不是选择标准,判断的依据是是否需要用ReentrantLock的高级特性,比如公平锁、超时、中断、判断是否上锁等等特性。

34.线程池的作用有哪些?

线程池的作用: 在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。
第四:通过缓冲队列有限流的作用。
常用线程池:ExecutorService 是主要的实现类,其中常用的有

Executors.newSingleThreadPool(),
 
newFixedThreadPool(),
 
newCachedTheadPool(),
 
newScheduledThreadPool()

35. 列举几个设计模式*

设计模式是经常被问到的题型,需要了解几种常用的概念、应用场景、java的实现方式、以及在jdk及一些开源项目(如:spring,tomcat)中的应用

36.Java元注解有哪些,都有什么作用?*

注解的注解,称为元注解。

  1. @Retention

定义注解的保留策略,由RetentionPolicy定义,包括以下3种策略:
1、RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
2、RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
3、RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
这3个生命周期分别对应于:Java源文件(.java文件) —> .class文件 —> 内存中的字节码。

首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。
1.一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解,比如@Deprecated使用RUNTIME注解
2.如果要在编译时进行一些预处理操作,比如生成一些辅助代码(比如Android中的Butter knife),就用 CLASS注解;
3.如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,使用SOURCE 注解。

  1. @Target

定义注解的使用对象,注解可以用在类上、方法上、属性上等,由ElementType枚举类定义。

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    /** 用于描述类、接口(包括注解类型) 或enum声明 */
    TYPE,
 
    /** Field declaration (includes enum constants) */
    /** 用于描述域 */
    FIELD,
 
    /** Method declaration */
    /** 用于描述方法 */
    METHOD,
 
    /** Formal parameter declaration */
    /** 用于描述参数 */
    PARAMETER,
 
    /** Constructor declaration */
    /** 用于描述构造器 */
    CONSTRUCTOR,
 
    /** Local variable declaration */
    /** 用于描述局部变量 */
    LOCAL_VARIABLE,
 
    /** Annotation type declaration */
    /** 用于描述注解,比如注解A描述注解B */
    ANNOTATION_TYPE,
 
    /** Package declaration */
    /** 用于描述包 */
    PACKAGE,
 
    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
     /** 用来标注类型参数 */
    TYPE_PARAMETER,
 
    /**
     * Use of a type
     *
     * @since 1.8
     */
     /** 能标注任何类型名称 */
    TYPE_USE
}
  1. @Inherited
  • 类继承关系中@Inherited的作用
    类继承关系中,子类会继承父类使用的注解中被@Inherited修饰的注解

  • 接口继承关系中@Inherited的作用
    接口继承关系中,子接口不会继承父接口中的任何注解,不管父接口中使用的注解有没有被@Inherited修饰

  • 类实现接口关系中@Inherited的作用
    类实现接口时不会继承任何接口中定义的注解

  1. @Documented

javadoc工具会使用该注解生成文档。

37.jdk自带了哪些注解,有什么作用*

@Overried
告诉编译器要检查该方法是重写父类的方法

@Deprecated
标记某个类或方法等已经废弃,不推荐使用,保留仅为兼容以前的程序

@SuppressWarnings
抑制编译器警告

@FunctionalInterface
用来指定该接口是函数式接口

@SafeVarargs
处理可变长参数中的泛型,此注解告诉编译器:在可变长参数中的泛型是类型安全的。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}

@SafeVarargs注解,只能用于标记构造函数和方法,由于保留策略声明为RUNTIME,所以此注解可以在运行时生效。
使用的时候要注意:@SafeVarargs注解,对于非static或非final声明的方法,不适用,会编译不通过。
对于非static或者非final声明的方法一般使用@SuppressWarnings(“unchecked”)

public class SafeVarargsAnnotation<S>{

    private S[] args;

    //构造函数可以使用@SafeVarargs标记
    @SafeVarargs
    public SafeVarargsAnnotation(S... args){
        this.args = args;
    }

    //此处不能使用@SafeVarargs,因为此方法未声明为static或final方法,如果要抑制unchecked警告,可以使用@SuppressWarnings注解
    @SuppressWarnings("unchecked")
    //@SafeVarargs
    public void loopPrintArgs(S... args){
        for (S arg : args) {
            System.out.println(arg);
        }
    }

    //final方法可以使用@SafeVarargs标记
    @SafeVarargs
    public final void printSelfArgs(S... args){
        for (S arg : this.args) {
            System.out.println(arg);
        }
    }
    //static方法可以使用@SafeVarargs标记
    @SafeVarargs
    public static <T> void loopPrintInfo(T ... infos){
        for (T info : infos) {
            System.out.println(info);
        }
    }

}

38.char 型变量中能不能存贮一个中文汉字,为什么?

Java中编译成的class使用的编码是Unicode,它是16位的,所以存储的中文汉字也是16位的。因为char也是16位的,所以可以。

如果使用utf8编码,它是3个字节的,char无法存储。

39.两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?

不对,如果两个对象x和y满足x.equals(y) == true,它们的哈希码(hash code)应当相同。Java对于eqauls方法和hashCode方法是这样规定的:(1)如果两个对象相同(equals方法返回true),那么它们的hashCode值一定要相同;(2)如果两个对象的hashCode相同,它们并不一定相同。

当然,你未必要按照Java规定去做,你可以使得相同对象有不同的HashCode,但是如果你违背了上述原则就会发现在使用容器时,相同的对象可以出现在Set集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)。

40.原生JDBC操作数据库流程?*

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

/*
 * jdbd原生开发步骤,
 */
public class Test {
    public static void main(String[] args) throws Exception {
        //注册驱动,反射方式加载
        Class.forName("com.mysql.jdbc.Driver");
        //设置url
        String url = "jdbc:mysql://127.0.0.1:3306/day08";//person是数据库名,连接是数据库须要开启
        //设置用户名
        String username = "root";
        //设置密码
        String password = "root";
        //获得连接对象
        Connection con = DriverManager.getConnection(url, username, password);
        //System.out.println(con);
        //获得执行者对象
        String sql = "select * from phones";
        PreparedStatement ps = con.prepareStatement(sql);
        //获得结果集
        ResultSet rs = ps.executeQuery();
        //结果集处理,
        while(rs.next()){
            System.out.println(rs.getString("id")+"  "+rs.getString("pinpai")+"  "+rs.getString("xinghao")+"  "+rs.getString("jiage"));
        }
        //释放资源
        rs.close();
        ps.close();
        con.close();
    }
}

1、class.forName()加载数据驱动

2、DriverManager.getConnection()获取数据库连接对象。

3、根据SQL或sql会话对象,有两种方式Statement、PreparedStatement。

4、执行sql处理结果集,如果有参数就设置参数。

5、关闭结果集,关闭会话,关闭资源。

41.构造器(constructor)是否可被重写(override)?*

不能,因为构造器不能被继承。
构造器能被重载Overload。

42. 介绍CMS收集器和G1收集器?

CMS收集器是一种以获取最短回收停顿时间为目标的收集器。基于“标记-清除”算法实现,它的运作过程如下:

  1. 初始标记
  2. 并发标记
  3. 重新标记
  4. 清除

初始标记、重新标记这两个步骤仍然需要“stop the world”,初始标记仅仅只是标记一下GC Roots能直接关联到的对象,熟读很快。
并发标记阶段就是进行GC Roots Tracing。
而重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致不可达的对象但有时存活对象标记为可达,这个阶段的停顿时间一般会比初始标记阶段稍长点,但远比并发标记的时间短。

优点:
并发收集、低停顿。
缺点:

  • CMS收集器对CPU资源非常敏感。在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。
  • CMS收集器无法处理浮动垃圾,可能会出现“Concurrent Mode Failure(并发模式故障)”失败而导致Full GC产生。
  • CMS是一款“标记–清除”算法实现的收集器,容易出现大量空间碎片。当空间碎片过多,将会给大对象分配带来很大的麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。

浮动垃圾:由于CMS并发清理阶段用户线程还在运行着,伴随着程序运行自然就会有新的垃圾不断产生,这部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC中再清理。这些垃圾就是“浮动垃圾”。


G1是一款面向服务端应用的垃圾收集器。基于“标记-整理”算法实现,它的运作过程如下:

  1. 初始标记
  2. 并发标记
  3. 最终标记
  4. 筛选回收

上面几个步骤的运作过程和CMS有很多相似之处初始标记阶段仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS的值,让下一个阶段用户程序并发运行时,能在正确可用的Region中创建新对象,这一阶段需要停顿线程,但是耗时很短。
并发标记阶段是从GC Root开始对堆中对象进行可达性分析,找出存活的对象,这阶段时耗时较长,但可与用户程序并发执行。
最终标记阶段则是为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remenbered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中,这一阶段需要停顿线程,但是可并行执行
最后在筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划

优势:

  • 并行于并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。
  • 分代收集:虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。它能够采用不同的方式去处理新创建的对象和已经存活了一段时间,熬过多次GC的旧对象以获取更好的收集效果。
  • 空间整合:与CMS的“标记–清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的
  • 可预测的停顿:这是G1相对于CMS的另一个大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值