2025最新JAVA面试八股文,万字长文!

 一、基础篇

1、面向对象和面向过程的区别

面向过程:是分析解决问题的步骤,然后用函数把这些步骤一步一步地实现,然后在使用的时候一 一调用则可。性能较高,所以单片机、嵌入式开发等一般采用面向过程开发.

面向对象:是把构成问题的事务分解成各个对象,而建立对象的目的也不是为了完成一个个步骤, 而是为了描述某个事物在解决整个问题的过程中所发生的行为。面向对象有封装、继承、多态的特 性,所以易维护、易复用、易扩展。可以设计出低耦合的系统。   但是性能上来说,比面向过程要低。

2、简单说说封装、继承与多态

封装:

  • 概念:封装是面向对象编程中的一种基本思想。它将对象的数据(属性)和操作数据的方法(行为)捆绑在一起,形成一个独立的对象(类)。同时,封装可以隐藏对象的内部实现细节,只对外提供有限的接口供其他对象访问。

继承:

  • 概念:继承是一种允许新类(子类)继承现有类(父类)的属性和方法的机制。子类可以继承父类的公共属性和方法,并且可以添加自己的新属性和方法,也可以重写父类的方法来实现特定的功能。

多态:

  • 概念:多态是指允许不同的对象对同一消息做出响应,具体调用哪个对象的方法在运行时根据对象的实际类型动态确定。多态分为编译时多态(方法重载)和运行时多态(方法覆盖)。

  • 举例:还是以动物类为例。动物类有一个叫的方法(void call())。狗类和猫类都继承自动物类,并且都覆盖了叫的方法。狗类的叫方法是汪汪叫,猫类的叫方法是喵喵叫。当有一个动物类型的引用指向狗对象时,调用animal.call()就会发出汪汪的叫声;当这个引用指向猫对象时,调用animal.call()就会发出喵喵的叫声。这就是运行时多态,具体调用哪个类的叫方法是在运行时根据对象的实际类型(狗或猫)动态确定的。

3、标识符的命名规则

标识符的含义:  是指在程序中,我们自己定义的内容,譬如,类的名字,方法名称以及变量名称等 等,都是标识符。

命名规则:(硬性要求)  标识符可以包含英文字母,0-9的数字,$以及_ 标识符不能以数字开头 标 识符不是关键字

命名规范:(非硬性要求)  类名规范:首字符大写,后面每个单词首字母大写(大驼峰式)。   变量

名规范:首字母小写,后面每个单词首字母大写(小驼峰式)。   方法名规范:同变量名。

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处即可】免费获取

4Java自动装箱与拆箱

装箱就是自动将基本数据类型转换为包装器类型(int-->Integer);调用方法:Integer的 valueOf(int) 方法

拆箱就是自动将包装器类型转换为基本数据类型(Integer-->int)。调用方法:Integer的 intValue方法

5、 方法重载和方法重写的区别

重写(Override)

        从字面上看,重写就是重新写一遍的意思。其实就是在子类中把父类本身有的方法重新写一遍。子 类继承了父类原有的方法,但有时子类并不想原封不动的继承父类中的某个方法,所以在方法名,  参数列表,返回类型(除过子类中方法的返回值是父类中方法返回值的子类时)都相同的情况下,  对方法体进行修改或重写,这就是重写。但要注意子类函数的访问修饰权限不能少于父类的。

重载(Overload )

        在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同甚至是参数顺序不)则视为重载。同时,重载对返回类型没有要求,可以相同也可以不同,但不能通过返回类型是否相同来判断重载

6、 equals与 == 的区别

== 比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是 否是指相同一个对象。比较的是真正意义上的指针操作。

equals用来比较的是两个对象的内容是否相等,由于所有的类都是继承自java.lang.Object类的,所 以适用于所有对象,如果没有对该方法进行重写的话,调用的仍然是Object类中的方法,而Object  中的equals方法返回的却是==的判断。

7、 Hashcode的作用

        java的集合有两类,一类是List,还有一类是Set。前者有序可重复,后者无序不重复。当我们在set 中插入的时候怎么判断是否已经存在该元素呢,可以通过equals方法。但是如果元素太多,用这样  的方法就会比较满。

        于是有人发明了哈希算法来提高集合中查找元素的效率。这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希码,可以将哈希码分组,每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储的那个区域。

        hashCode方法可以这样理解:它返回的就是根据对象的内存地址换算出的一个值。这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。

8 StringStringBuffer 和 StringBuilder 的区别是什?

        String是只读字符串,它并不是基本数据类型,而是一个对象。从底层源码来看是一个final类型的 字符数组,所引用的字符串不能被改变,一经定义,无法再增删改。每次对String的操作都会生成 新的String对象。

        StringBuilder 和 StringBuffer:都是可变的(mutable)。它们可以被多次修改,而不会创建新的对象.StringBuffer:是线程安全的。它提供了同步方法,确保在多线程环境下对字符串的修改是安全的。但是,这种同步机制会带来性能开销。StringBuilder:是线程不安全的。它没有提供同步方法,因此在多线程环境下使用时需要手动同步。由于没有同步开销,StringBuilder在单线程环境下的性能优于StringBuffer

        操作少量字符数据用 String;单线程操作大量数据用 StringBuilder;多线程操作大量数据用 StringBuffer。

9ArrayListlinkedList的区别

ArrayList

        基于动态数组实现。底层是一个数组,当数组满了之后,会自动扩容,通常扩容为原数组的 1.5 倍(具体扩容策略在不同版本的 JDK 中可能有所不同)。

 
  1. ArrayList<Integer> list = new ArrayList<>();

  2. list.add(1); // 底层会创建一个数组,并将元素添加到数组中

  3. list.add(2);

AI写代码

LinkedList

        基于双向链表实现。每个元素是一个节点,每个节点包含数据部分和指向前后节点的指针。

 
  1. LinkedList<Integer> list = new LinkedList<>();

  2. list.add(1); // 底层会创建一个节点,并将元素添加到节点中

  3. list.add(2);

AI写代码

10、 HashMapHashTable的区别                                           

1、两者父类不同

HashMap是继承自AbstractMap类,而Hashtable是继承自Dictionary类。不过它们都实现了同时 实现了map、Cloneable(可复制)、  Serializable(可序列化)这三个接口。

2、对外提供的接口不同

Hashtable比HashMap多提供了elments() 和contains() 两个方法。   elments() 方法继承自 Hashtable的父类Dictionnary。elements() 方法用于返回此Hashtable中的value的枚举。

contains()方法判断该Hashtable是否包含传入的value。它的作用与containsValue()一致。事实 上,contansValue() 就只是调用了一下contains() 方法。

3、对null的支持不同

Hashtable:key和value都不能为null。

HashMap :key可以为null,但是这样的key只能有一个,因为必须保证key的唯一性;可以有多个 key值对应的value为null。

4、安全性不同

HashMap是线程不安全的,在多线程并发的环境下,可能会产生死锁等问题,因此需要开发人员自 己处理多线程的安全问题。

Hashtable是线程安全的,它的每个方法上都有synchronized 关键字,因此可直接用于多线程中。

虽然HashMap是线程不安全的,但是它的效率远远高于Hashtable,这样设计是合理的,因为大部 分的使用场景都是单线程。当需要多线程操作的时候可以使用线程安全的ConcurrentHashMap。

ConcurrentHashMap虽然也是线程安全的,但是它的效率比Hashtable要高好多倍。因为 ConcurrentHashMap使用了分段锁,并不对整个数据进行锁定。

5、初始容量大小和每次扩充容量大小不同

6、计算hash值的方法不同

11、 Collection包结构,与Collections的区别                            

        Collection是集合类的上级接口,子接口有 Set、 List、 LinkedList、ArrayList、Vector、Stack、 Set; 是集合类的一个帮助类,  它包含有各种有关集合操作的静态多态方法,用于实现对各种集合的搜索、排序、线程安全化等操作。此类不能实例化,就像一个工具类,服务于Java的 Collection框架。

12、深拷贝和浅拷贝的区别是什么?

  浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指 向原来的对象.换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象.

  深拷贝:被复制对象的所有变量都含有与原来的对象相同的值.而那些引用其他对象的变量将指向 被复制过的新对象.而不再是原有的那些被引用的对象.换言之.深拷贝把要复制的对象所引用的   对象都复制了一遍.

13nal有哪些用法?

final也是很多面试喜欢问的地方,但我觉得这个问题很无聊,通常能回答下以下5点就不错了:

  被final修饰的类不可以被继承

  被final修饰的方法不可以被重写

  被final修饰的变量不可以被改变.如果修饰引用,那么表示引用不可变,引用指向的内容可变.

  被final修饰的方法,JVM会尝试将其内联,以提高运行效率

  被final修饰的常量,在编译阶段会存入常量池中.

        除此之外,编译器对final域要遵守的两个重排序规则更好:在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序.

14 static都有哪些用法?

所有的人都知道static关键字这两个基本的用法:静态变量和静态方法.也就是被static所修饰的变量/ 方法都属于类的静态资源,类实例所共享.

除了静态变量和静态方法之外,static也用于静态块,多用于初始化操作:

public calss PreCache{

static{

//执行相关操作

}

}

此外static也多用于修饰内部类,此时称之为静态内部类.

最后一种用法就是静态导包,即 import static .import static是在JDK 1.5之后引入的新特性,可以用来指定导入某个类中的静态资源,并且不需要使用类名,可以直接使用资源名。

15try catch finally try里有return nally还执行么?

执行,并且finally的执行早于try里面的return

结论:

1、不管有木有出现异常,finally块中代码都会执行;

2、当try和catch中有return时,finally仍然会执行;

3、finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的 值保存起来,管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数   返回值是在finally执行前确定的;

4、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。

16、多态的作用

        多态的实现要有继承、重写,父类引用指向子类对象。它的好处是可以消除类型之间的耦合关系,增加类的可扩充性和灵活性。

        多态允许你通过统一的接口来处理不同类型的对象,这样在添加新的类型时,不需要修改现有的代码,只需要实现相同的接口或继承相同的父类即可。这使得代码的扩展性大大增强。

17、什么是反射?

       

        反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象, 都能够调用它的任意一个方法。在java中,只要给定类的名字,就可以通过反射机制来获得类的所 有信息。

I   这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

        应用场景有:要操作权限不够的类属性和方法时、实现自定义注解时、动态加载第三方jar包时、按需加载类,节省编译和初始化时间;
        获取class对象的方法有:class.forName(类路径),类.class(),对象的getClass()

18、Java创建对象得五种方式?

(1)new关键字   (2)Class.newInstance  (3)Constructor.newInstance

(4)Clone方法   (5)反序列化

19、 简述线程、程序、进程的基本概念。以及他们之间关系是什?

线程与进程相似,但线程是一个比进程更小的执行单位。  一个进程在其执行的过程中可以产生多个 线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代 码。

进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序 即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算 机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如 CPU 时间,内存空   间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。  线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而 各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系  统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同  时执行一个以上的程序段。

20、用过 ArrayList 吗?说一下它有什么特点?

        只要是搞 Java 的肯定都会回答“用过”。所以,回答题目的后半部分——ArrayList 的特点。可以从这 几个方面去回答:

        Java 集合框架中的一种存放相同类型的元素数据,是一种变长的集合类,基于定长数组实现,当加入数据达到一定程度后,会实行自动扩容,即扩大数组大小。底层是使用数组实现,添加元素。

如果 add(o),添加到的是数组的尾部,如果要增加的数据量很大,应该使用ensureCapacity() 方法,该方法的作用是预先设置 ArrayList 的大小,这样可以大大提高初始化速度。

如果使用 add(int,o),添加到某个位置,那么可能会挪动大量的数组元素,并且可能会触发扩 容机制。

        高并发的情况下,线程不安全。多个线程同时操作 ArrayList ,会引发不可预知的异常或错误。ArrayList 实现了 Cloneable 接口,标识着它可以被复制。注意:ArrayList 里面的 clone() 复制其实是浅复制。

21、有数组了为什么还要搞个 ArrayList 呢?

        通常我们在使用的时候,如果在不明确要插入多少数据的情况下,普通数组就很尴尬了,因为你不知道需要初始化数组大小为多少,而 ArrayList 可以使用默认的大小,当元素个数到达一定程度后,会自动扩容。

可以这么来理解:我们常说的数组是定死的数组,ArrayList 却是动态数组。

22、说说Hashtable 与 HashMap 的区别

本来不想这么写标题的,但是无奈,面试官都喜欢这么问 HashMap。

1. 出生的版本不一样,Hashtable 出生于 Java 发布的第一版本 JDK 1.0 ,HashMap 出生于 JDK 1.2。

2. 都实现了 Map、Cloneable、Serializable(当前 JDK 版本 1.8 )。

3. HashMap 继承的是 AbstractMap,并且 AbstractMap 也实现了 Map 接口。 Hashtable 继承

Dictionary。

4. Hashtable 中大部分 public 修饰普通方法都是 synchronized 字段修饰的,是线程安全的, HashMap 是非线程安全的。

5. Hashtable 的 key 不能为 null ,value 也不能为 null,这个可以从 Hashtable 源码中的 put 方 法看到,判断如果 value 为 null 就直接抛出空指针异常,在 put 方法中计算 key 的 hash 值之 前并没有判断 key 为 null 的情况,那说明,这时候如果 key 为空,照样会抛出空指针异常。

6. HashMap 的 key 和 value 都可以为 null。在计算 hash 值的时候,有判断,如果 key==null  ,则其  hash=0 ;至于 value 是否为 null ,根本没有判断过。

7. Hashtable 直接使用对象的 hash 值。hash 值是 JDK 根据对象的地址或者字符串或者数字算出 来的 int 类型的数值。然后再使用除留余数法来获得最终的位置。然而除法运算是非常耗费时   间的,效率很低。  HashMap 为了提高计算效率,将哈希表的大小固定为了 2 的幂,这样在取   模预算时,不需要做除法,只需要做位运算。位运算比除法的效率要高很多。

8. Hashtable、 HashMap 都使用了 Iterator。而由于历史原因,Hashtable 还使用了 Enumeration 的方式。

9. 默认情况下,初始容量不同,Hashtable 的初始长度是 11,之后每次扩充容量变为之前的    2n+1( n 为上一次的长度)而 HashMap 的初始长度为 16 ,之后每次扩充变为原来的两倍。

23、红黑树有哪几个特征?

紧接上个问题,面试官很有可能会问红黑树,下面把红黑树的几个特征列出来:

24、说说你平时是怎么处理 Java 异常的

try-catch-finally

  try 块负责监控可能出现异常的代码

   catch 块负责捕获可能出现的异常,并进行处理

  finally 块负责清理各种资源,不管是否出现异常都会执行

  其中 try 块是必须的,catch 和 finally 至少存在一个标准异常处理流程

二、JVM篇

1、知识点汇总

JVM是Java运行基础,面试时一定会遇到JVM的有关问题,内容相对集中,但对只是深度要求较高.

其中内存模型,类加载机制,GC是重点方面.性能调优部分更偏向应用,重点突出实践能力.编译器优化

和执行模式部分偏向于理论基础,重点掌握知识点.

需了解 内存模型各部分作用,保存哪些数据.

类加载双亲委派加载机制,常用加载器分别加载哪种类型的类.

GC分代回收的思想和依据以及不同垃圾回收算法的回收思路和适合场景.

性能调优常有JVM优化参数作用,参数调优的依据,常用的JVM分析工具能分析哪些问题以及使用方法.

执行模式解释/编译/混合模式的优缺点,Java7提供的分层编译技术,JIT即时编译技术,OSR栈上替

换,C1/C2编译器针对的场景,C2针对的是server模式,优化更激进.新技术方面Java10的graal编译器

编译器优化javac的编译过程,ast抽象语法树,编译器优化和运行器优化.

2、知识点详解:

1、JVM内存模型:

线程独占:栈,本地方法栈,程序计数器 线程共享:堆,方法区

2、栈:

又称方法栈,线程私有的,线程执行方法是都会创建一个栈阵,用来存储局部变量表,操作栈,动态链接,方 法出口等信息.调用方法时执行入栈,方法返回式执行出栈.

3、本地方法栈

与栈类似,也是用来保存执行方法的信息.执行Java方法是使用栈,执行Native方法时使用本地方法栈.

4、程序计数器

保存着当前线程执行的字节码位置,每个线程工作时都有独立的计数器,只为执行Java方法服务,执行 Native方法时,程序计数器为空.

5、堆

JVM内存管理最大的一块,对被线程共享, 目的是存放对象的实例,几乎所欲的对象实例都会放在这里,   当堆没有可用空间时,会抛出OOM异常.根据对象的存活周期不同,JVM把对象进行分代管理,由垃圾回收器进行垃圾的回收管理

6、方法区:

又称非堆区,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器优化后的代码等数据.1.7 的永久代和1.8的元空间都是方法区的一种实现

7、JVM 内存可见性

3、说说类加载与卸载

(1)加载 :把字节码通过二进制的方式转化到方法区中的运行数据区

(2)连接:

 验证:验证字节码文件的正确性。

 准备:正式为类变量在方法区中分配内存,并设置初始值,final类型的变量在编译时已经赋值了

 解析:将常量池中的符号引用(如类的全限定名)解析为直接引用(类在实际内存中的地址) 

(3)初始化 :执行类构造器(不是常规的构造方法),为静态变量赋初值并初始化静态代码块。

(4)卸载:卸载类

 篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处即可】免费获取

4、简述一下JVM的内存模型

1.JVM内存模型简介

JVM定义了不同运行时数据区,他们是用来执行应用程序的。某些区域随着JVM启动及销毁,另外一 些区域的数据是线程性独立的,随着线程创建和销毁。jvm内存模型总体架构图如下:

        JVM在执行Java程序时,会把它管理的内存划分为若干个的区域,每个区域都有自己的用途和创建销毁时间。如下图所示,可以分为两大部分,线程私有区和共享区。下图是根据自己理解画的一个 JVM内存模型架构图:

5、说说堆和栈的区别

        栈是运行时单位,代表着逻辑,内含基本数据类型和堆中对象引用,所在区域连续,没有碎片;堆是存储单位,代表着数据,可被多个栈共享(包括成员中基本数据类型、引用和引用对象),所在 区域不连续,会有碎片。

1、功能不同

栈内存用来存储局部变量和方法调用,而堆内存用来存储Java中的对象。无论是成员变量,局部变 量,还是类变量,它们指向的对象都存储在堆内存中。

2、共享性不同

栈内存是线程私有的。   堆内存是所有线程共有的。

3、异常错误不同

如果栈内存或者堆内存不足都会抛出异常。   栈空间不足:java.lang.StackOverFlowError。 堆空间 不足:java.lang.OutOfMemoryError。

4、空间大小

栈的空间大小远远小于堆的。

6、什么是Java虚拟机?为什么Java被称作是平台无关的编程语 

        Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字 节码文件   Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。

7、如何判断对象可以被回收?

判断对象是否存活一般有两种方式:

  引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计 数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。

  可达性分析(Reachability Analysis):从GC Roots开始向下搜索,搜索所走过的路径称为引 用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对   象。

8、说一下 JVM 有哪些垃圾回收器?

        如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。下图展示了 7种作用于不同分代的收集器,其中用于回收新生代的收集器包括Serial、 PraNew、 Parallel、Scavenge,回收老年代的收集器包括Serial Old、 Parallel Old、CMS,还有用于回收整个Java堆的 G1收集器。不同收集器之间的连线表示它们可以搭配使用。

JVM 中有以下几种常见的垃圾回收器:

Serial 收集器
   - 它是一个单线程的收集器,只使用一个线程去执行垃圾回收任务。在垃圾回收期间,所有用户线程都会被暂停(Stop - The - World,STW)。例如,在新生代回收过程中,它会逐个检查新生代中的对象,判断对象是否还存活。如果对象不再被任何引用链所连接,就将其标记为可回收对象,然后进行回收。
   - 主要采用标记 - 复制算法。新生代被划分为 Eden 区和两个 Survivor 区(From 和 To)。在垃圾回收时,将 Eden 区和 From Survivor 区中存活的对象复制到 To Survivor 区,然后清理 Eden 区和 From Survivor 区。之后交换 From 和 To Survivor 区的角色。
 

ParNew 收集器
   - 它是 Serial 收集器的多线程版本。在新生代回收时,可以启动多个线程同时进行垃圾回收。它也是基于标记 - 复制算法。与 Serial 收集器类似,它会先标记新生代中的存活对象,然后将存活对象复制到 Survivor 区。不过,由于多线程的特性,在复制对象的过程中,多个线程可以并行地从 Eden 区和 From Survivor 区复制对象到 To Survivor 区,从而提高回收效率。
 

Parallel Scavenge 收集器
   - 和 ParNew 收集器一样,它也是多线程的新生代收集器。不过,它的目标是达到一个可控制的吞吐量。它采用标记 - 复制算法。在垃圾回收过程中,它会根据系统的配置(如 -XX:GCTimeRatio 参数设置垃圾回收时间占总运行时间的比例)来调整新生代的大小和垃圾回收的频率。例如,如果设置 -XX:GCTimeRatio=9,意味着垃圾回收时间占总运行时间的 1/10,它会通过调整新生代的大小来尽量满足这个比例要求。
 

Serial Old 收集器
1. **工作原理**
   - 它是 Serial 收集器的老年代版本。它是单线程的,采用标记 - 整理算法。在垃圾回收时,先标记老年代中的存活对象,然后将存活对象向内存的一端进行整理,清理掉中间的空白区域。这种方式可以避免内存碎片化的问题,因为老年代的对象相对比较稳定,不会频繁地创建和销毁。
2. **适用场景**
   - 通常和 Serial 收集器或者 ParNew 收集器配合使用。当新生代的对象晋升到老年代后,可以使用 Serial Old 收集器来回收老年代。它适用于对老年代垃圾回收要求不高的场景,比如一些小型应用或者单核处理器的系统,因为单线程的特性,它的资源消耗相对较小。

Parallel Old 收集器
   - 它是 Parallel Scavenge 收集器的老年代版本,采用多线程的标记 - 整理算法。多个线程可以并行地标记老年代中的存活对象,然后将存活对象整理到内存的一端。这种并行的方式可以提高老年代垃圾回收的效率,减少垃圾回收时的停顿时间。
 

G1(Garbage - First)收集器
   - 适合大内存、多核处理器的系统。例如,在一些大型的云计算平台或者大数据处理平台中,堆内存可能非常大,G1 收集器可以高效地管理内存,通过分区域回收的方式,减少垃圾回收的停顿时间,同时避免内存碎片化问题。
 

9、什么情况下会内存溢出?

堆内存溢出:(1)当对象一直创建而不被回收时(2)加载的类越来越多时(3)虚拟机栈的线程越来越多时

栈溢出:方法调用次数过多,一般是递归不当造成

10、对象的创建过程

(1)检查类是否已被加载,没有加载就先加载类

(2)为对象在堆中分配内存,使用CAS方式分配,防止在为A分配内存时,执行当前地址的指针还没有来得及修改,对象B就拿来分配内存。

(3)初始化,将对象中的属性都分配0值或null

(4)设置对象头

(5)为属性赋值和执行构造方法

11、说说什么是垃圾回收GC?

        GC 是 Garbage Collection 的缩写,即垃圾回收。它是 Java 虚拟机(JVM)自动管理内存的一种机制。在 Java 程序运行过程中,会不断创建对象,当这些对象不再被使用时,如果不进行回收,就会占用内存空间,导致内存泄漏等问题。GC 的作用就是自动识别并回收这些不再使用的对象所占用的内存,使程序能够高效地运行,而无需程序员手动管理内存。

12、GC如何判断对象可以被回收?

(1)引用计数法:已淘汰,为每个对象添加引用计数器,引用为0时判定可以回收,会有两个对象相互引用无法回收的问题

(2)可达性分析法:从GCRoot开始往下搜索,搜索过的路径称为引用链,若一个对象GCRoot没有任何的引用链,则判定可以回收

  GCRoot有:虚拟机栈中引用的对象,方法区中静态变量引用的对象,本地方法栈中引用的对象

三、Java多线程篇

1、说说什么是Java多线程

        在 Java 中,线程是程序执行的最小单位。它可以执行一个任务或者一系列任务。线程是 CPU 调度的基本单位,多个线程可以并发执行,从而提高程序的执行效率。例如,在一个文本编辑器程序中,可以有一个线程专门用于处理用户的输入,另一个线程用于自动保存文档,这样可以让用户在输入文字的同时,程序后台自动保存文档,提高用户体验。

2、说说线程与进程的区别

进程:

        进程是操作系统进行资源分配和调度的一个独立单位。它包含了程序运行所需的所有资源,如内存空间、文件描述符等。一个 Java 应用程序运行时就是一个进程,比如启动一个 Java Web 服务,它就是一个独立的进程。

线程:

        线程是进程中的一个执行单元,一个进程可以包含多个线程。线程共享所属进程的资源,如内存空间。在 Java 中,线程的创建和管理是由 Java 虚拟机(JVM)和操作系统共同完成的。例如,在一个 Java Web 服务进程中,可以有多个线程处理不同的 HTTP 请求,这些线程共享进程的内存空间,包括类加载器加载的类信息、静态变量等。

3、Java 中创建线程的方式

(一)继承 Thread 类

        创建一个继承自 Thread 类的子类。重写 run() 方法,在 run() 方法中编写线程要执行的任务代码。创建 Thread 子类的实例对象。调用 start() 方法启动线程。

(二)实现 Runnable 接口

        创建一个实现了 Runnable 接口的类。实现 run() 方法,在 run() 方法中编写线程要执行的任务代码。创建 Runnable 实现类的实例对象。创建 Thread 类的实例对象,并将 Runnable 实现类的实例对象作为参数传递给 Thread 类的构造方法。调用 Thread 类实例对象的 start() 方法启动线程。

(三)使用 Callable 和 Future

        创建一个实现了 Callable 接口的类。实现 call() 方法,在 call() 方法中编写线程要执行的任务代码,并且可以返回一个结果。创建 Callable 实现类的实例对象。使用 Executors 工具类创建一个 ExecutorService 线程池。调用 ExecutorService 的 submit() 方法提交 Callable 任务,该方法会返回一个 Future 对象。通过 Future 对象的 get() 方法获取线程执行的结果。

4、什么是线程上下文切换

当一个线程被剥夺cpu使用权时,切换到另外一个线程执行

5、什么是死锁

死锁指多个线程在执行过程中,因争夺资源造成的一种相互等待的僵局

6、什么是AQS锁?


        AQS是一个抽象类,可以用来构造锁和同步类,如ReentrantLock,Semaphore,CountDownLatch,CyclicBarrier。

        AQS的原理是,AQS内部有三个核心组件,一个是state代表加锁状态初始值为0,一个是获取到锁的线程,还有一个阻塞队列。当有线程想获取锁时,会以CAS的形式将state变为1,CAS成功后便将加锁线程设为自己。当其他线程来竞争锁时会判断state是不是0,不是0再判断加锁线程是不是自己,不是的话就把自己放入阻塞队列。这个阻塞队列是用双向链表实现的

        可重入锁的原理就是每次加锁时判断一下加锁线程是不是自己,是的话state+1,释放锁的时候就将state-1。当state减到0的时候就去唤醒阻塞队列的第一个线程。

7、有哪些常见的AQS锁

        AQS(AbstractQueuedSynchronizer)是 Java 并发包 `java.util.concurrent.locks` 中的一个核心类,用于构建锁和其他同步组件。AQS 提供了一种基于 FIFO 等待队列的机制,用于管理线程的获取和释放资源的顺序,并提供了一些模板方法供子类实现具体的同步策略。以下是一些常见的基于 AQS 实现的锁:

ReentrantLock(可重入锁)
        允许同一个线程多次获取锁,每次获取锁时计数器加一,每次释放锁时计数器减一,直到计数器为 0 时才完全释放锁。支持公平锁(按照请求顺序分配锁)和非公平锁(允许插队)。
        使用场景:替代 `synchronized` 关键字,提供更灵活的锁定机制。 

ReentrantReadWriteLock(可重入读写锁)
        将读操作与写操作分离,允许多个读操作并发执行,但写操作是排他的,即只有一个线程可以进行写操作,并且写操作会阻塞所有其他读写操作。
        使用场景:适用于读多写少的情况,提高并发性能。 

Semaphore(信号量)
        用于控制同时访问某个资源的线程数量。它可以用来限制同时执行的线程数,或者实现资源池的管理。
        使用场景:限制资源的并发访问,例如数据库连接池、线程池等。 

CountDownLatch
        允许一个或多个线程等待其他线程完成一组操作后再继续执行。初始化时设定一个计数值,每个事件完成后调用 `countDown()` 方法使计数递减,当计数达到零时,所有因调用 `await()` 方法而等待的线程被释放。
        使用场景:确保某些操作在继续之前完成,比如等待所有子线程完成任务后主线程再继续。 

CyclicBarrier(循环屏障)
        允许一组线程相互等待,直到所有线程都达到某个公共屏障点后再继续执行。它常用于分阶段的任务并行执行。
        使用场景:分阶段的任务并行执行,例如多线程计算任务的每个阶段都需要所有线程完成后再进入下一个阶段。 

8Thread类中的yield方法有什么作用?

        Yield方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法 而且只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU,执行yield()的线程有可   能在进入到暂停状态后马上又被执行。

9Java线程池中submit() 和 execute()方法有什么区别?

        两个方法都可以向线程池提交任务, execute()方法的返回类型是void,它定义在Executor接口中,    而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了 Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些  方法。

10、说一说自己对于 synchronized 关键字的了解

        synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。

        另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一 个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优 化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。

11、sleep()和wait()的区别

 (1)wait()是Object的方法,sleep()是Thread类的方法

(2)wait()会释放锁,sleep()不会释放锁

(3)wait()要在同步方法或者同步代码块中执行,sleep()没有限制

(4)wait()要调用notify()或notifyall()唤醒,sleep()自动唤醒

12volatile关键字的作用?

        volatile 是 Java 中的一个关键字,用于修饰变量。它主要解决多线程环境下的可见性和禁止指令重排序的问题。

        volatile关键字保证变量的可见性和有序性,不保证原子性。使用了 volatile 修饰变量后,在变量修改后会立即同步到主存中,每次用这个变量前会从主存刷新。

13、什么是CAS锁

        CAS锁可以保证原子性,思想是更新内存时会判断内存值是否被别人修改过,如果没有就直接更新。如果被修改,就重新获取值,直到更新完成为止。这样的缺点是:

(1)只能支持一个变量的原子操作,不能保证整个代码块的原子操作 

(2)CAS频繁失败导致CPU开销大

(3)ABS问题:线程1和线程2同时去修改一个变量,将值从A改为B,但线程1突然阻塞,此时线程2将A改为B,然后线程3又将B改成A,此时线程1将A又改为B,这个过程线程2是不知道的,这就是ABA问题,可以通过版本号或时间戳解决

14、说说ThreadLocal原理?

   ThreadLocal 是 Java 中的一个类,用于创建线程局部变量。每个线程可以访问自己内部的变量副本,而不会与其他线程的变量副本发生冲突。这在多线程编程中非常有用,可以避免线程之间的数据共享和同步问题。

使用场景

  1. 避免共享资源的同步问题:在多线程环境中,共享资源的同步是一个常见的问题。使用 ThreadLocal 可以为每个线程提供独立的变量副本,从而避免了线程之间的竞争条件。
  2. 管理线程上下文:在一些场景中,需要在多个方法调用之间传递线程上下文信息,例如事务管理、用户身份信息等。ThreadLocal 可以方便地实现这一点,而不需要在每个方法参数中传递这些信息。

15、多线程有什么用?

        一个可能在很多人看来很扯淡的一个问题:我会用多线程就好了,还管它有什么用?在我看来,这 个回答更扯淡。所谓"知其然知其所以然" ,"会用"只是"知其然" ,"为什么用"才是"知其所以然" ,只 有达到"知其然知其所以然"的程度才可以说是把一个知识点运用自如。  OK,下面说说我对这个问题 的看法:

(1)发挥多核CPU的优势

        随着工业的进步,现在的笔记本、台式机乃至商用的应用服务器至少也都是双核的,4核、 8核甚至 16核的也都不少见,如果是单线程的程序,那么在双核CPU上就浪费了50%,在4核CPU上就浪费   了75%。单核CPU上所谓的"多线程"那是假的多线程,同一时间处理器只会处理一段逻辑,只不过  线程之间切换得比较快,看着像多个线程"同时"运行罢了。多核CPU上的多线程才是真正的多线程,它能让你的多段逻辑同时工作,多线程,可以真正发挥出多核CPU的优势来,达到充分利用 CPU的目的。

(2)防止阻塞

        从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,反而会因为在单核CPU上运 行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核CPU我们还是要应用多线程, 就是为了防止阻塞。试想,如果单核CPU使用单线程,那么只要这个线程阻塞了,比方说远程读取 某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。

(3)便于建模

        这是另外一个没有这么明显的优点了。假设有一个大的任务A ,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务A分解成几个小任务,任务B、任务C、任务 D ,分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值