JAVA基础面试题

文章目录

JVM

类加载机制

类加载过程是指JVM虚拟机把.class文件中类信息加载进内存,并解析生成对应的class对象的过程。(反射对象)

Java类的加载顺序

有继承关系的加载顺序:

首先加载父类静态变量和静态代码块,再加载子类静态变量和静态代码块。

加载父类普通变量和语句块,加载父类构造方法。

加载子类普通变量和语句块,加载子类构造方法。

类加载过程

类加载的过程主要分为三个部分:加载、链接、初始化这三个阶段。

加 载阶段:指的是把class字节码文件通过类加载器装载入内存中。

连接阶段:分成3个小阶段,分别是:验证,准备,解析

验证Class文件是否合格,准备给静态变量赋值,解析符号引用转换成直接引用。

初始化阶段:初始化类的静态变量。

类加载器的代理模式

双亲委托机制:

当某个类收到类加载器的请求时,会将请求委托给自己的父类,直到委托到最高级父类,如果父类能够加载就加载,不能加载就返回到子类进行加载。

JVM内存图

img

JVM栈 : 每当启动一个新线程的时候,java虚拟机都会为它分配一个java栈。java以栈帧为单位保存线程的运行状态。虚拟机只会对java栈执行两种操作:以栈帧为单位的压栈或者出栈

堆内存 : 存储的全部是对象信息。

方法区 :在方法区中存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。

本地方法栈 : 和java栈的作用差不多,只不过是为JVM使用到的native方法服务的(线程的start方法调用start0(),实际上是操作系统在启动线程)

native方法其实就是调用非java代码的接口

程序计数器:用于保存某个线程的字节码执行位置。

什么是内存泄露

内存空间在使用完毕后未释放,结果导致一直占据该内存单元。

常见的内存泄露

1、内存分配未成功,却使用了它

2、内存分配成功,但尚未初始化就引用它

3、内存分配成功且初始化,但操作越过了内存的边界

4、忘记释放内存,造成内存泄漏

5、释放了内存却继续使用它

6、该对象已经没有使用了,但是没有被gc回收

为什么会导致内存溢出

对象一直被引用,jvm无法对其进行回收,创建新的对象时,无法从堆中获取足够的内存分配给对象,这时候就会导致内存溢出。

垃圾回收机制

垃圾回收可以有效的防止内存泄露,有效的使用空闲的内存。

回收算法

1.引用计数法

GC会记录有多少个引用指向对象,如果当前对象没有被引用,表示该对象为垃圾可进行回收。缺点是无法解决循环引用的问题。

2.可达性分析法

通过一系列的GC Root的对象作为起点,开始进行搜索,搜索所经过的路径叫做引用链,当有对象没有被任何引用链经过,那么这个对象就为垃圾。

什么是 GC Roots 呢?

GC Roots是一些由堆外指向堆内的引用,比如虚拟机栈中引用的对象。(栈指向堆)

GC回收的时间

①堆内存满了就会回收,②堆没满也可以回收,但是要等CPU空闲,调用System.gc()

回收方法

1.标记清楚算法

先标记要回收的对象,再删除。

缺点:回收速度慢,会产生大量不连续的内存碎片。

2.复制算法

将内存一分为二,只使用其中一份,将仍然有效的对象复制到另外一份中,把原来的空间删除。

优点:不会产生内存碎片。

缺点:只能使用一半的内存。

3.标记整理算法

先标记要回收的对象,删除后将有效对象进行整理。

优点:不会产生内存碎片。

缺点:回收速度效率低。

4.分代收集算法(JVM使用最多的一种算法)

新生代:目的是回收那些生命周期短的对象。

老年代:存储生命周期长的对象,占用的内存一般是新生代的两倍。

新生代中:每次垃圾收集时都有大批的对象需要回收,所以采用复制算法。

老年代中:每次垃圾收集时对象存活率高,所以采用标记清楚或者标记整理算法。

你了解的垃圾收集器有哪些,分别用的什么垃圾回收算法?

Serial 收集器(复制算法)

Serial Old 收集器(标记-整理算法)

面试问题

  1. 你能讲讲GC垃圾回收算法吗
  2. 你了解的垃圾收集器有哪些,分别用的什么垃圾回收算法
  3. 你讲一下JVM的组成部分吗
  4. 元空间存的是什么
  5. GC root有哪些
  6. 讲一下类的加载机制

JAVA基础

object类有哪些方法

getClass方法、toString方法、finalize方法、equals方法、hashCode方法、wait方法、notify方法、notifyAll方法。

为什么重写equals()方法就必须重写hashCode()方法?

如果重写了equals方法,而没有重写hashcode方法,会出现equals相等的对象,hashcode不相等的情况。

谈谈你对面向对象的理解?

面向对象主要有三大基本特性和六大设计原则:

三大基本特征:

封装:将对象的属性和行为封装起来并私有化,对外提供set/get方法。

继承:通过继承,可以在不编写原有类的情况下,对原有类功能进行扩展。

多态:多态是指同一个状态的不同表现形式,包括参数化多态,变量的声明,方法的返回值。

六大设计原则(低耦合,高内聚的思想):

单一职责原则:一个类只做一件事。

开放封闭原则:一个模块的扩展性是开放的,而更改性是封闭的。

里氏替换原则:子类应当可以替换父类在所有地方。

依赖倒置原则:写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。

public static void main(String[] args) {
    Student stu = new Student();
    stu.study(new JavaCourse());
    stu.study(new DesignPatternCourse());
}
public class Wmyskxz {

    public void study(ICourse course) {
        course.study();
    }
}
public class JavaCourse implements ICourse {

    @Override
    public void study() {
        System.out.println("「我没有三颗心脏」同学正在学习「Java」课程");
    }
}
public class DesignPatternCourse implements ICourse {

    @Override
    public void study() {
        System.out.println("「我没有三颗心脏」同学正在学习「设计模式」课程");
    }
}

接口分离原则:一个接口只做一个功能

迪米特法则:对象与对象之间应该使用尽可能少的方法来关联。

重载(overload)和重写(override)的区别?重载的方法能否根据返回类型进行区分?

重载:在同一个类中,方法同名,但参数不同,对返回值没有要求。

重写:在父类与子类中,方法名,参数和返回值必须相同,子类访问修饰符权限要大于或者等于父类,子类抛出的异常不能大于父类。

&和&&,|和|| 的区别

&普通与,前面返回false后还需要判断,&&短路与,前面返回false后不再判断。

|普通或,前面返回false后还需要判断,||短路或,前面返回false后不再判断。

String是否可以被继承

不能,因为String被final修饰,final修饰的类叫最终类,不能被继承。

final关键字

final修饰的类叫最终类,不能被继承。

final修饰的方法不能被重写。

final修饰的变量叫做常量,常量必须初始化,初始化后值不可改变。

接口和抽象类的区别

(1)定义抽象类的关键字是abstract class,而定义接口的关键字是interface
(2)继承抽象类的关键字是extends,而实现接口的关键字是implements
(3)继承抽象类支持单继承,而实现接口支持多实现
(4)抽象类中可以有构造方法,而接口中不能有构造方法
(5)抽象类中可以有成员变量,而接口中只可以有常量
(6)抽象类中可以有成员方法,而接口中只可以有抽象方法
(7)抽象类中增加方法可以不影响子类,而接口中通常都影响子类
(8)从jdk1.8开始允许接口出现非抽象方法,但必须使用default关键字修饰

抽象类能使用 final 修饰吗?
不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类

普通类和抽象类有哪些区别?
普通类不能包含抽象方法,抽象类可以包含抽象方法。
抽象类不能直接实例化,普通类可以直接实例化。

内部类

1.为什么使用内部类?
第一个特点,内部类可以访问所在外部类的私有属性和方法。
第二个特点,内部类可以不被同一个包中的其他类访问。
第三个特点,可以联合外部类解决多继承问题。

2.内部类分类:
(一).成员内部类:

(二).静态内部类: 是 static 修饰的内部类

(三).方法内部类:其作用域仅限于方法内,方法外部无法访问该内部类

(四).匿名内部类:
一、什么是匿名内部类?
顾名思义就是没有名字内部类。

二、为什么出现了匿名内部类?
假如一个类只用了一次,那么单独为其创建一个类岂不是很麻烦,所以出现了只能使用一次并且没有名字的匿名内部类。

三、匿名内部类有什么特点?
必须继承一个父类或实现一个接口

Static关键字

被static静态修饰的成员方法,成员变量,成员内部类都是随着类文件的加载而加载到方法区中,且只加载一次。如果需要调用,直接用类名去调用。

一.static关键字的用途
方便在没有创建对象的情况下来进行调用(方法/变量)。

static方法
static方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用。

static变量
static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
作用:
1.对象之间共享值
2.方便访问变量,对类名直接访问变量

static代码块
static关键字还有一个比较关键的作用就是:用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。

基本数据类型

整数型:byte 1字节,short 2字节,int 4字节, long 8字节

浮点型:float 4字节,double 8字节

字符型:char 2字节

Boolean:布尔型

谈谈你对JAVA中装箱和拆箱的理解?

装箱就是将基本数据类型转换成对应的包装器类型;拆箱就是将包装器类型转换成对应的基本数据类型;

就拿int和Integer举例子,装箱时调用ValueOf(),拆箱时调用IntValue();

并且Integer存在一个(-128~127)的缓存区,通过valueOf方法创建的Integer对象的时候,如果数值在[-128,127]之间,便返回缓冲池中已经存在的对象的引用,否则随机创建一个新的Integer对象。

以下 Integer 代码输出的结果是?
Integer age = 10;
Integer age2 = 10;
Integer age3 = 133;
Integer age4 = 133;
System.out.println((age == age2) "," (age3 == age4)); 

输出的结果是true和false,因为Integer中存在一个(-128~127)的缓存区,数值在此区间的直接在缓存区中获取,超过了这个区间的会新建一个对象。

short s=2;s=s+ 1; 会报错吗?short s=2;s +=1; 会报错吗?

1.会报错,s+1 == short + int = int,而Int不能自动转换成short,会损失精度。

2.不会报错,+= 相当于 (short)(s+1),将int类型的数据强转为short。

为什么需要包装类?包装类的值比较要使用什么方法?

分为Character、Number、Boolean 三类 Number里面除了Integer特殊其他都是基本数据类型大写

int的默认值是0,而Integer得默认值是null,在某些业务场景下,0也代表着一种状态。

包装类本身也是一个对象,相比于基本数据类型,包装类可以调用更多得方法,比如equals,hashcode,parse,valueof等等。

包装类可以放到集合中,而基本数据类型不能,因为集合中使用泛型,所以只能放引用数据类型。

某些包装类存在缓存区。

“==” 和 equals 的区别是什么?

==对于基本类型比的是值,对于引用类型比的是地址。

equals默认情况下是引用比较,但是很多类都重写了equals方法,所以重写后比较的是值。

String、StringBuffer、StringBuilder 的区别?

String是最终类,不可变。

StringBuffer和StringBuilder是可变的,StringBuffer是线程安全的,但是比StringBuilder性能低一点,StringBuilder线程不安全,但是性能最高。在通常情况下,一般使用StringBuilder较多。

什么是字符串常量池?

字符串常量池是为了解决频繁创建和销毁导致资源浪费的一种解决方案。在创建字符串的时候先到字符串常量池中查找,如果没有再创建,如果有直接返回地址。

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

不对

(1)如果两个对象相同(equals 方法返回 true),那么它们的hashCode 值一定要相同;

(2)如果两个对象的 hashCode 相同,它们并不一定相同。

Exception 和 Error 有什么区别?

Exception和Error都来自同一个父类(Throwable),Error类和他的子类代表了JVM本身的错误,程序无法解决。

EXception类有两个子类,IOException和RuntimeException属于运行时异常,可以被Java异常捕获。

总体上,异常分为两类,检查型异常和非检查型异常。

非检查异常:Error和RuntimeException在编译时不会提示异常,不要求在程序中处理这些异常。

检查异常:除了Error 和 RuntimeException的其它异常,都必须使用try-catch-finally或者throws等处理异常。

throw 和 throws 的区别是什么?

throw用于方法内手动抛出异常,throws用于方法声明,声明可能要抛出的各种异常。

final,finally、finalize 的区别是什么?

final是个关键字

finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
finalize是一个方法,该方法一般由垃圾回收器来调用,当我们调用System的gc()方法的时候,由垃圾回收器调用finalize(),回收垃圾。

Java 中的 this 和 super 有哪些区别?

this代表当前对象,super代表父类对象。

this只在当前类有效,代表可以调用当前类的属性和方法。

super代表可以调用父类的属性和方法。

在静态方法中可以使用 this 或 super 吗?为什么?

不可以,this和super必须依赖对象才可以调用,而在静态方法中不需要创建对象,直接通过类名调用。

可序列化接口(Serializalbe)的用途是什么?

实现序列化接口代表这个类可以序列化。

常用的序列化方式都有哪些?

对象序列化和JSON字符串。

线程

线程的四种初始化方式

1.继承Thread类。

2.实现Runnable接口。无返回结果

3.实现Callable接口+FutureTask,有返回值。

4.线程池。

线程的生命周期,线程状态?

1.新建状态,刚刚new出来的线程。

2.就绪状态,调用start方法,等待CPU分配。

3.运行状态,获取到了CPU进入运行状态。

4.阻塞状态,在运行状态的时候,调用sleep(),wait()进入阻塞状态,当调用notify或者notifyall的时候唤醒线程。

5.死亡状态,正常执行完或者被强制性终止的线程,会被销毁释放资源。

sleep和wait方法的区别?

sleep属于Thread类,wait属于Object类。

sleep会在一段时间后自动唤醒,在此期间不会释放锁。而wait必须调用notify或者notifyall方法唤醒,调用wait方法后会释放锁资源。

谈谈你对线程安全的理解?

当多个线程访问一个对象时,如果不用进行额外的同步控制或者其他操作,调用这个对象的行为都可以获得正确的结果,我们就可以说这个对象是线程安全的。

线程池(预先创建好线程放在线程池中,提高线程的利用率)

在线程池中维护一个稳定的线程数量,有任务需要执行就到线程池中申请一个线程去执行,可以达到资源的控制。

使用线程池的好处

1.方便统一管理线程。

2.提高线程的利用率

3.提高线程的响应速度

线程池的7个核心参数

corePoolSize:核心线程数量。

maximumPoolSize:最大线程数量。

keepAliveTime:空闲线程存活时间。

unit:空闲线程存活时间单位。

workQueue:等待队列(一般使用LinkedBlockingQuene,基于链表的无界阻塞队列(最大容量为Interger.MAX),按照FIFO排序)。

threadFactory:线程工厂。

handle:拒绝处理机制(一般使用AbortPolicy ,直接丢弃任务,并抛出RejectedExecutionException异常。)

​ 四种拒绝处理机制: ①拒绝抛出异常 , ②拒绝不抛出异常, ③放弃当前正在执行的线程,将另一个线程加入对列

​ ④第四种不怎么记得了

异步编程

Java8新增了CompletableFuture提供对异步计算的支持。

runAsync 和 supplyAsync的区别

runAsync方法不支持返回值,supplyAsync可以支持返回值。

whenComplete 和 whenCompleteAsync 的区别

whenComplete是继续使用当前线程继续执行任务。

whenCompleteAsync 是使用线程池来执行。

exceptionally

任务出现异常后调用,在这个方法中可以改变人物的返回值。

handle

handle 是执行任务完成时对结果的处理。

如果exceptionnal和handle同时存在,handle的返回值会覆盖exceptionnally的返回值。

多任务组合

allof,所有任务完成后返回future。

anyof就是只要有一个任务执行完成后就返回future并将第一个完成的参数带着一起返回。

JAVA容器

Java容器有哪些?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5Aa4UnTj-1672020849660)(JAVA基础.assets/image-20210904135837549.png)]

List:

ArrayList:有序的,可重复的。(底层采用数组)1.5倍扩容,是实现了基于动态数组的数据结构,每个元素在内存中存储地址是连续的。

LinkedList:有序的,可重复的。(底层采用双向链表),每个元素在内存中存储地址是离散的。

Set:

​ HashSet:无序的,不可重复的。(底层采用HashMap的Key存值)

​ TreeSet:无序的但可排序的,不可重复的。(底层采用TreeMap的Key存值)

Map:

​ HashMap:无序的,key不允许重复,value可以重复。(底层采用数组+链表/红黑树的结构)

​ TreeMap:无序的但可排序的,key不允许重复,value可以重复。(底层采用二叉树的结构存值)

ArrayList和LinkedList性能对比

ArrayList底层是数组,查询快,插入删除慢。

LinkedList底层是双向链表,查询慢,插入和删除快。

ArrayList具体如何扩容

首先,先新建一个数组,长度设置为原数组的1.5倍,再将原数组迁移到新数组中。

ArrayList和Vector的区别

ArrayList按照1.5倍扩容,Vector按照两倍扩容。

ArrayList线程不安全,但是效率高,更通用。

Vector线程安全,但是效率低。

Vector在JAVA1.0就推出了,而ArrayList在JAVA1.2的时候才推出。

哪些集合类是线程安全的?

Vector、Hashtable、Stack 都是线程安全的,ConcurrentHashMap是HashMap对应的线程安全类。

HashSet类是如何实现添加元素保证不重复的?

去重性:通过重写hashcode()和equals()方法实现去重。

往HashSet集合中添加数据的时候,先根据key调用hashcode方法得到一个hash值,将这个hash值对数组长度(默认16)取余得到数组索引位置,再调用equals方法判断是否存在相同key,如果存在则覆盖,不存在则插入。

HashMap 底层

当发生hash冲突时,放入链表中,当链表数据大于8时,转换为红黑树结构

说一下HashMap的实现原理?

当传入key时,HashMap会根据key调用hashcode方法得到一个hash值,将这个hash值对数组长度(默认16)取余得到数组索引位置,根据hash值将value保存在数组中,如果计算的hash值有重复,再调用equals方法判断是否相同,如果相同则覆盖,不相同则插入。当数组中的数据超过8个的时候,链表会转为红黑二叉树来提高查询效率。

TreeMap怎么实现可排序的?

如果是用无参构造创建的TreeMap默认采用自然选择器,实现Comparable接口,重写排序方法。如果用有参构造创建,要求自定义排序其Comparator对象作为参数传入参数中。(返回负数排在前面,返回正数排在后面)

反射

什么是反射?

反射是将类的属性,方法和构造方法一一映射为对象。

属性 --> Field对象

方法 --> Method对象

构造方法 --> Constructor对象

什么是反射机制

在运行状态中,对于任何一个类都能动态获取数据并调用对象属性和方法的能力就是反射机制。

哪里用到反射机制?

很多底层框架都用到了,比如Spring的注入。

反射机制的优缺点?

优点:可以动态执行方法,访问属性。

缺点:对性能有影响,慢于直接执行代码。

动态代理是什么?有哪些应用?

动态代理是运行时动态生成代理类。

SpringAop,JAVA注解对象。

怎么实现动态代理?

有两种实现方式:

第一种,JDK原生动态代理,实现InvocationHandler接口,通过反射调用invoke()方法调用目标代码。

第二种,CGLIB动态代理,原理是生成目标类的子类对象实现,通过反射调用invoke()方法调用目标代码。

如何使用Java的反射?

通过Class.forName(“全限定类名”)创建一个对象。

Clazz.getConstructor()获取构造器对象。

Clazz.getDeclaredFields()获取属性对象。

Clazz.getDeclareMethod()获取方法对象,使用invoke()调用方法。

setAccessible(True),让私有的方法可以执行。

面试问题

  1. 讲一下HashMap底层原理
  2. HashSet的底层呢?
  3. 讲一下线程池的核心参数

何一个类都能动态获取数据并调用对象属性和方法的能力就是反射机制。

哪里用到反射机制?

很多底层框架都用到了,比如Spring的注入。

反射机制的优缺点?

优点:可以动态执行方法,访问属性。

缺点:对性能有影响,慢于直接执行代码。

动态代理是什么?有哪些应用?

动态代理是运行时动态生成代理类。

SpringAop,JAVA注解对象。

怎么实现动态代理?

有两种实现方式:

第一种,JDK原生动态代理,实现InvocationHandler接口,通过反射调用invoke()方法调用目标代码。

第二种,CGLIB动态代理,原理是生成目标类的子类对象实现,通过反射调用invoke()方法调用目标代码。

如何使用Java的反射?

通过Class.forName(“全限定类名”)创建一个对象。

Clazz.getConstructor()获取构造器对象。

Clazz.getDeclaredFields()获取属性对象。

Clazz.getDeclareMethod()获取方法对象,使用invoke()调用方法。

setAccessible(True),让私有的方法可以执行。

面试问题

  1. 讲一下HashMap底层原理
  2. HashSet的底层呢?
  3. 讲一下线程池的核心参数
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值