Java
Java基础
1、java跨平台原理
java中.java文件在执行编译后,产生.class文件,.class文件是在JVM虚拟机中运行的,而JVM虚拟机可以安装在各个主流系统上,所以编译后的.class文件可以运行在各个系统。
2、java中int占几个字节 int和integer的区别
在java中int是中基本类型的其中一种,占4字节,32位,定义后初始化数据为0。
Integer为int类型的包装类,里面定义了许多关于int类型的方法,同时也可以达到存储int类型数据的目的,拥有自动装箱,自动拆箱功能,定义后初始化数据为null,当存储数据在-128到127时,Integer对象是在IntegerCache.cache产生,会复用已有对象,此时使用进行判断,两个是同一个对象。当不在这个区间内,则会重新new一个Integer对象,使用判断后结果为false。
3、==与equals区别 equals和hashcode的区别与联系
实际上equals方法的底层是==去实现的,当比较的数据类型为基本类型时则不能使用基本类型.equals,需要使用==才能进行比较。
使用equals或==对类型为String的数据进行判断时比较的是字符串内容,当使用equals或==对引用数据进行判断时,比较的是地址在内存中的地址值,当重写equals后可以自己指定比较内容,同时也可以使用hashcode作为比较值。当需要比较大量数据时,使用重写equals方法并结合hashcode进行查找效率最高。
4、面向对象的几个特征
抽象: 将一类对象的共同特征总结出来构造类的过程,抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
封装: 把一个对象的属性私有化,但是同时也提供一些可以被外界访问的属性的方法,如果不希望外界访问,则也不必提供方法给外界。
继承: 使用已存在的类的定义作为基础,建立新类的技术,继承父类的子类可以增加自己独有的属性和方法,同时也可以使用父类的属性和功能,但不能选择性的继承父类,通过继承可以减少代码冗余,增加代码复用。要注意的是java是单继承多实现。
子类拥有父类非private的属性和方法。
子类可以拥有自己的属性和方法,可以在父类的基础上进行扩展。
子类可以用自己的方式实现父类的方法,也就是方法重写。
多态: 指程序中定义的引用变量所指向的具体类型在编程时不确定,而是在运行期间才确定,即一个引用变量到底指向哪个实例对象,该引用变量发出的方法调用到底是那个类中的实现方法,必须由程序运行期间才能决定。
5、抽象类和接口的区别
抽象类是定义要被继承的子类的通用特性的,接口只能定义方法。
抽象类是对类的抽象,是一种模板,接口是对行为的抽象,是对行为的规范。
相同点:
接口和抽象类都不能实例化,只能被继承或实现。
都包含抽象方法,并且子类必须重写这些方法。(1.8以后接口引入默认方法和静态方法,默认方法不用被强制实现)
不同点:
声明关键字不同。
实现关键字不同。
抽象类可以有构造器,接口没有。
抽象类的方法可以有各种访问修饰符,接口访问修饰符必须是public
一个类最多继承一个抽象类,但是可以实现多个接口
6、什么是字符串常量池?
字符串常量池位于堆内存,专门存放字符串常量,可以提高内存使用率,避免多块空间存储相同字符串。在初始化字符串时会在内存中检查字符串常量池,如果常量池已存在该字符串,则返回它的引用,如果不存在,则实例化一个字符串放入池中,并返回其引用。
篇幅限制下面就只能给大家展示小册部分内容了。包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取
7、字符串是可变的吗?
字符串是不可变的,java中String类是用final修饰的一个常量char数组,所以不可修改。表面看上去的可以修改实际上是在字符串常量池中新建了一个字符串,并返回了该字符串的引用。
8、String、StringBuffer和StringBuilder的区别是什么?
String底层是被final修饰过的所以是不可变的,StringBuffer和StringBuilder虽然也是用char数组实现,但是没有用final修饰,所以是可变的,并且拥有一些独有的字符串处理方法
StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以它是线程安全的,而StringBuilder没有加锁,所以它不是安全的。
StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并引用,相同情况下使用StringBuilder要比StringBuffer效率要高。
9、访问权限修饰符的区别
| 修饰符 | 当前类 | 同包 | 子类 | 其他包 |
|---|---|---|---|---|
| public | ✔ | ✔ | ✔ | ✔ |
| producted | ✔ | ✔ | ✔ | ✖ |
| default | ✔ | ✔ | ✖ | ✖ |
| private | ✔ | ✖ | ✖ | ✖ |
10、&和&&的区别
&和&&都是逻辑运算符,从某种意义上来讲,两个效果是相同的,但是&&拥有短路特性,当有多个逻辑表达式进行运算时,如果第一个逻辑表达式就是false,那么不管后面是否正确,都不在执行。同理 | 和 || 也是一样的,只要第一个为true那么后面都不在执行了。
11、是否可以继承String
不可以,String类是被final修饰过的,并且继承String本身就是一种错误的行为。
12、当一个对象作为参数传入一个方法后,这个方法可以改变传入对象的属性值,那么这里对象的传递是值传递还是引用传递
是值传递,因为java中不存在引用传递,当一个对象实例被传入方法中,参数的值是该对象的引用,对象的属性可能会被改变,但是对象引用的改变是不会影响到调用者的。
13、重载和重写的区别
重载是相同的方法名,参数列表的类型、数量、顺序不同,并且与返回值无关,可以有不同修饰符。
重写是将继承的父类方法重写覆盖掉父类方法,所以要求,方法名,参数列表都与父类方法名和参数列表相同
注:构造方法不能被重写,被final修饰的方法不能被重写,声明为static的方法不能被重写,但是可以被再次声明
14、char类型中能否存入一个中文汉字
可以,因为java使用的默认编码为Unicode,直接使用字符在字符集的编号,一个charl类型占两字节,所以放一个中文汉字是没问题的。
15、抽象方法是否可以被static、native、synchronized修饰
都不可以
首先抽象方法是要被子类重写的,而static方法无法被重写,这是相互矛盾的。
native是有本地方法实现的,而抽象方法是没有实现的,所以也不可以。
synchronized和实现的细节有关,而抽象方法不设计实现细节,因此也是矛盾的。
16、什么是多态
多态是父类或接口定义的引用变量可以指向子类实现,而程序调用的方法在执行期间才会动态绑定,就是引用变量所指向的具体实例对象的方法,也就是对象内存中正在运行的那个对象,而不是引用变量的类型中定义的那个方法。
17、Java中的静态变量和实例变量有何区别?静态方法和实例方法有何区别?
静态变量和实例变量的区别:
静态变量属于类,实例变量属于对象;
静态变量只有一份,被所有对象共享,实例变量每个对象都有一份;
静态变量可以直接通过类名访问,实例变量需要通过对象名访问;
静态方法和实例方法的区别:
静态方法属于类,实例方法属于对象;
静态方法可以直接通过类名调用,实例方法需要通过对象名调用;
静态方法中不能使用 this 关键字,因为 this 表示当前对象,而静态方法没有对象;
静态对象和实例对象在存储方式上也是不同的
静态变量存储在方法区(Method Area)中,也称为永久代(PermGen),即在类加载时就已经被分配了内存空间,所有该类的对象共享同一份静态变量的内存空间。
而实例变量则存储在 Java 堆(Java Heap)中,即每个对象都有自己的实例变量的内存空间,当对象被创建时,实例变量也随之被分配内存空间。
需要注意的是,从 JDK 8 开始,永久代被移除了,取而代之的是元空间(Metaspace),静态变量的存储方式也变成了存储在元空间中。但是,与永久代不同,元空间并不是虚拟机运行时数据区域的一部分,而是使用本地内存来实现的。
18、Java中如何进行对象的序列化(Serialization)和反序列化(Deserialization)?
对象的序列化(Serialization)是指将对象转换为字节流的过程,而反序列化(Deserialization)则是指将字节流转换为对象的过程。通过序列化和反序列化,可以实现对象的持久化存储、网络传输等功能。
实现 Serializable 接口:要使一个类可序列化,需要让该类实现 Serializable 接口。Serializable 接口是一个标记接口,没有定义任何方法,只是作为标记告诉编译器这个类可以被序列化。
序列化:使用 ObjectOutputStream 将对象序列化为字节流,并将字节流写入文件或发送给网络。
进行反序列化:使用 ObjectInputStream 从字节流中读取数据,并将其反序列化为对象。
异常
java异常分为运行时异常和编译时异常
编译时异常是可以被手动处理掉的,大部分是可以预见性的异常,如果是提前知道怎么处理异常,则可以使用try…cache捕获并处理异常,如果不知道如何处理,则定义该方法是声明时抛出该异常。
运行时异常则是只有在代码运行时才发出的异常,比如类转换异常,数组下标越界异常、除数为0数学异常等,这种异常在出现时会被系统自动捕获,可以手动try…cache进行处理,或者直接交给程序自动处理。
try-catch 块:try-catch 块用于捕获和处理可能抛出异常的代码块。try 块中编写可能引发异常的代码,catch 块用于捕获并处理异常。
throws 关键字:throws 关键字用于声明方法可能抛出的异常,让调用该方法的代码去处理异常。
finally 块:finally 块用于定义无论是否发生异常都会执行的代码,通常用于释放资源或清理工作。
throw 关键字:throw 关键字用于在代码中手动抛出异常。
1、error和exception的区别
error和exception都继承于throwable类
error一般是虚拟机相关的问题,如系统崩溃,内存空间不足,方法调用栈溢出等,这种问题一旦出现,就代表这无法修复的错误,是非常严重的。
exception表示程序可处理的异常,遇见这种问题,应该尽可能的解决异常,使程序恢复运行。
exception又分为运行时异常和编译时异常,编译时异常表示语法都没用办法通过,运行时异常表示的是只有在程序运行时,才会出现的异常,比如数组下标越界,没找到类异常等。遇见异常时,尽量使用try…cache进行异常捕获并处理,保证程序的正常运行。
篇幅限制下面就只能给大家展示小册部分内容了。包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取
泛型
泛型(Generic)是一种在编译时期约束集合类接受的元素类型的机制。通过泛型,可以使代码更加通用、可重用,并提高代码的类型安全性。
使用泛型可以带来以下好处:
代码复用性:通过泛型,可以编写更通用的类和方法,适用于不同类型的数据,避免了重复编写相似的代码。
类型安全性:使用泛型可以在编译时发现类型错误,避免在运行时出现类型转换异常或其他类型相关的错误。
减少强制类型转换:使用泛型可以避免频繁进行类型转换操作,使代码更加简洁清晰。
在 Java 中,泛型主要应用于以下几个方面:
泛型类(Generic Class):定义一个泛型类可以接受任意类型的数据,例如 class MyGenericClass<T>。
泛型方法(Generic Method):定义一个泛型方法可以接受任意类型的参数,例如 public <T> void myGenericMethod(T t)。
泛型接口(Generic Interface):定义一个泛型接口可以让实现类指定具体的类型,例如 interface MyGenericInterface<T>。
泛型通配符(Generic Wildcards):使用通配符 ? 可以表示未知类型,在一些情况下可以灵活地处理不同类型的数据。### 反射
1、什么是反射?
在程序运行状态时,都能够知道任何一个类的所有属性和方法,对于任意一个对象,都能调用它的任意一个方法和属性,这种动态获取对象类或对象信息,并动态调用对象的方法被称为反射。
2、反射的优缺点
优点:可以在运行期间就对类或对象进行判断,动态加载,提高代码灵活度。
缺点:相当于手动操作JVM进行操作,不如JVM自动操作效率高。
3、反射机制的应用场景
在平时的项目开发过程中,很少直接使用反射,但是在框架设计、模块化开发等都是通过反射调用相对应的字节码,在spring的动态代理中就使用到了反射机制,spring就是通过读取配置文件后,通过全限定类名和反射获得对象实例。
4、通过反射获取Clazz的几种方式
- 使用 Class.forName 静态方法,传入类的全限定类名
- 通过类使用
.class方法 - 通过实例对象的
.getClass()方法获取
5、通过反射创建实例对象几种方式
- 首先获取类的字节码文件,其次调用
clazz.newInstance()获取实例 - 首先获取类的字节码文件,其次获取构造方法
clazz.getConstructor(),通过constroctor.newInstance()创建实例对象
6、如何通过反射获取私用属性
首先通过反射获取class对象
通过class对象获取属性 getDeclaredField(filedName)
给属性设置可以访问 field.setAccessible(true);
集合
1 、简述集合体系

Collection接口 和 Map接口继承于Iterator接口
set接口 和 List接口继承于Collection接口
set接口下有HashSet、TreeSet和LinkedHashSet
- HashSet:无序,集合内元素不可重复,线程不安全
- TreeSet:指定排序算法后可以进行排序,线程不安全
- LinkedHashSet:有序并且保证排序,线程不安全
- 1
- 2
- 3
List接口下有ArrayList、LinkedList
- ArrayList:查、改快,线程不安全,初始长度没有指定默认长度的时候,长度为0,并在第一次添加元素时初始化,初始长度为10,再次扩容会先copy数组,并扩容为原数组的1.5倍。
- LinkedList:增、删快,线程不安全,插入元素是将元素置于链表末尾,属于尾插法。
- 1
- 2
Map接口下有HashMap、TreeMap、LinkedHashMap和HashTable
- HashMap:无序双列集合,线程不安全
- TreeMap:可排序双列集合,线程不安全
- LinkedHashMap:有序并且保证排序的双列集合,线程不安全
- HashTable:无序双列集合,线程安全
- 1
- 2
- 3
- 4
2、hashMap和hashTable区别
HashMap和HashTable都继承于Map接口,都是双列集合,两者的区别在于:
- HashMap线程不安全,hashTable线程安全
- HashMap的键和值允许为null值,而HashTable键和值都不允许为null。
- 解决hash冲突的方式不同
3、ArrayList和Vector的区别
两个类都继承了List接口,都属于单列有序集合。
Vector是Java出现的时候就存在的类,而ArrayList是后面更新后才出现的。java也推荐我们优先使用ArrayList,但是在使用前要考虑线程安全问题,因为ArrayList是线程不安全的,而Vector是线程安全的,如果没有多线程问题则选择ArrayList,这样效率高于Vector。
ArrayList和Vector在创建时都有一个初始容量大小,当存储的数据超过初始容量时,会自动对集合进行扩容,Vector默认每次增长为原来的2倍,ArrayList是增长为原来的1.5倍,但是Vector可以手动设置增长的空间大小,ArrayList不能手动设置增长空间大小。
4、Array和ArrayList有什么区别?什么时候应该用Array而不是ArrayList?
Array大小是固定的,ArrayList大小是动态变化的。
ArrayList处理固定大小的基本数据类型时,这种方式效率较慢。
在实际应用场景中,如果提前知道需要存储的数量,并且后期不会在改变大小的时候可以使用数组,如果后期对存储容量有动态变化的时候则使用ArrayList。
5、HashMap的底层原理
在jdk1.7之前HashMap的底层原理是由数组+链表实现的,当创建出来HashMap时,是一个数组,数组中的每一个元素是一个单向链表的头指针,指向一个entry键值对,当一个键值对放入hashMap时会根据键的hashcode选择放入哪一个数组元素,也就是选择放入哪一个单向链表中,如果出现两个entry的hash值一样,这样就产生了hash冲突,那么新放入的entry键值对则使用头插法,插入在表头。
jdk1.8之后采用数组+链表+红黑树实现,在基础思想上添加了红黑树进行优化,当链表长度大于等于阈值(8)时,链表转化为红黑树,利用红黑树的自平衡在查找性能上得到提升。当链表长度小于于等于阈值(6)时,红黑树转化为链表。HashMap的初始长度是16,每次自动扩展或手动扩展时,长度必须是2的幂,1.8之后遇到hash冲突后是尾插法。
6、set里的元素是不能重复的,那么底层是用什么方法区分重复与否呢?
set底层先使用hashcode值和将要加入的hashcode值进行比较,如果相同则继续使用equals方法进行比较。
7、HashSet和TreeSet的区别
HashSet底层是有HashMap实现的,在HashSet的构造方法中初始化了一个HashMap,利用HashMap的键值不唯一,使用HashMap的键来存储值,因此当存储的值重复时会返回false。
TreeSet是有树形结构,基于TreeMap实现的,所以存储是有序的,但是同样是线程不安全的。
8、LinkedHashMap的实现原理
LinkedHashMap也是基于HashMap实现的,只是它额外定义了一个Entry header,这个header是独立出来的一个链表头指针。每个entry添加了两个属性,before和after和header结合起来形成了一个双向链表,由此就实现了插入顺序或访问顺序排序。默认排序即插入的顺序。
9、ConcurrentHashMap与Hashtable的区别
两者都是线程安全的,但是底层对于线程安全实现方式不同,Hashtable是对表结构进行上锁,其他操作需要等待执行完毕后才能访问,1.8之前ConcurrentHashMap是采用分离锁形式,没有对整表进行锁定,而是对局部进行锁定,不影响其他线程对表的其他地方操作。1.8之后ConcurrentHashMap采用CAS算法进行安全实现线程安全。
线程
篇幅限制下面就只能给大家展示小册部分内容了。包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取
1、进程、线程的关系与区别
进程是包含多个线程的集合,每一个程序在执行时都是一个进程。
线程是进程中最小的数据单位,每个指令都是一个线程。
进程可以没有线程,但是线程必须要存在于进程,即线程依赖于进程。
2、创建线程的几种方式
- 继承Thread类,重写run方法,实例化线程,调用start执行。
- 实现Runable接口,实现run方法,实例化线程(需要借助Thread),调用start执行。(无返回值)
- 实现Callable接口,实例化FutureTask对象包装Callable的实例化对象,使用实例化FutureTask对象作为Thread对象的target创建并启动新线程,Callable接口的call()方法可以返回执行结果。
- 使用匿名内部类:可以通过创建匿名内部类来实现线程的创建和启动,这种方式在简单的场景下可以更加简洁
3、什么是守护线程
守护线程是为了服务用户线程的存在,比如jvm虚拟机会等待用户线程执行完成后关闭,但是不会等待GC线程执行完再关闭。
4、Thread类中的start()和run()方法有什么区别?
start()方法用来创建线程,底层也是调用了run()方法,这和直接调用run()方法不一样,当你调用run()方法时,是在当前线程调用,使用start()方法是启动一个新线程。
5、什么导致线程阻塞
导致线程阻塞的原因大体上来说是因为需要的资源没有就绪,所以会陷入阻塞状态,需要等待资源就绪后才会继续执行。常见的阻塞原因有以下几种
- sleep():sleep() 允许 指定以毫秒为单位的一段时间作为参数,它使得线程在指定的时间内进入阻塞 状态,不能得到CPU 时间,指定的时间一过,线程重新进入可执行状态。
- wait()和notify():两个方法配套使用,wait() 使得线程进入阻塞状态,它有两种形式,一种允许指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者当对应的 notify() 被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的 notify() 被调用。
6、wait()与sleep()的区别
- sleep()来自Thread类,和wait()来自Object类
- sleep()睡眠后不让出系统资源,wait()让其他线程可以占用CPU
- sleep需要指定一个睡眠时间,时间一到会自动唤醒.而wait()需要配合notify()使用
7、什么是线程局部变量ThreadLocal
线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享,是一种实现线程安全的方式
8、什么是乐观锁和悲观锁
- 乐观锁:认为不是每一次操作都需要上锁,但是在执行操作之前会判断是否会对数据执行修改,一般会使用版本号机制或者CAS机制实现
- 悲观锁,认为每一次操作都需要上锁,比如synchronized,不管三七二十一就给加上锁了
9、版本号机制和CAS机制的区别
- 版本号机制会在取出记录时携带版本号,在修改版本号时会与版本号对比,如果版本号不一致,则说明出现问题,抛出异常。如果更新操作执行成功则版本号自增。
- CAS机制在更新前先取出值作为一个缓存,在更新时,会对更新的值做比较 ,如果更新前的值和更新后的值相同则执行更新,否则抛出异常。
10、volatile关键字
被volatile修饰的共享变量保证了不同线程对该变量操作的内存可见性,禁止指令重排序。
就是当你写一个 volatile 变量之前,会插入一个写屏障,读一个 volatile 变量之前,会插入一个读屏障。在你写一个 volatile 变量时,能保证任何线程都能看到你写的值,在写之前,也能保证任何数值的更新对所有线程是可见的
11、线程池的类别
- FixedThreadPool(固定大小线程池):固定大小的线程池,线程数量固定不变。适用于需要控制并发线程数量的场景。
- CachedThreadPool(缓存线程池):线程数量可动态调整,新任务到来时会创建新线程。适用于执行大量短期异步任务的场景
3.SingleThreadExecutor(单线程线程池):只有一个工作线程的线程池,保证所有任务按照指定顺序执行。适用于需要顺序执行任务且在单线程中执行的场景。 - ScheduledThreadPool(定时任务线程池):定时执行任务或者周期性执行任务的线程池。适用于需要定时执行任务的场景。
- WorkStealingPool(工作窃取线程池):JDK 1.8 引入的一种线程池,使用 ForkJoinPool 实现。每个线程都有自己的任务队列,当自己的任务执行完毕后,会去其他线程的队列中窃取任务执行。
- CustomThreadPool(自定义线程池):可以根据具体需求自定义线程池,如设置核心线程数、最大线程数、任务队列类型等参数。
12、如何正确创建一个线程池
创建线程池的常见方法包括使用 Executors 工厂类和直接使用 ThreadPoolExecutor 类。
根据阿里巴巴的《Java 开发手册》,在企业开发中,推荐使用 ThreadPoolExecutor 类直接创建线程池,而不建议使用 Executors 工厂类来创建线程池。这是因为 Executors 工厂类虽然提供了一些便捷的方法来创建线程池,但在某些情况下可能会引发一些意想不到的问题。
具体来说,Executors 工厂类创建的线程池存在一些问题:
FixedThreadPool 和 CachedThreadPool 的风险:Executors.newFixedThreadPool() 和 Executors.newCachedThreadPool() 使用的是无界队列,如果任务提交速度过快,可能导致内存溢出。
SingleThreadExecutor 的风险:Executors.newSingleThreadExecutor() 使用的也是无界队列,如果任务提交速度过快,也可能导致内存溢出。
相比之下,使用 ThreadPoolExecutor 类可以更加灵活地配置线程池的参数,包括核心线程数、最大线程数、工作队列类型等,从而更好地控制线程池的行为,避免出现意外情况。因此,在阿里巴巴企业中,建议使用 ThreadPoolExecutor 类来创建线程池,以确保线程池的稳定性和可靠性。
13、线程池的核心参数有哪些
创建一个线程池时,可以通过设置一些核心参数来配置线程池的行为。以下是线程池的一些核心参数:
corePoolSize(核心线程数):
线程池中同时执行的核心线程数量。
核心线程会一直存活,即使没有任务需要执行。
当有新任务提交时,如果核心线程数还没有达到限制,将创建新的核心线程来执行任务。
maximumPoolSize(最大线程数):
线程池中允许的最大线程数量。
当任务提交数量超过核心线程数时,线程池可以创建新的线程来执行任务,直到达到最大线程数。
若任务继续增加,超出最大线程数的任务会被拒绝执行。
keepAliveTime(线程空闲时间):
当线程池中的线程数量超过核心线程数,并且空闲时间达到 keepAliveTime,多余的线程会被销毁。
设置时间单位,如 TimeUnit.MILLISECONDS。
workQueue(任务队列):
用于存储待执行任务的阻塞队列。
不同的队列类型可选择,如 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue 等。
当任务提交数量超过核心线程数时,新任务会被添加到队列中等待执行。
threadFactory(线程工厂):
用于创建新的线程。
可以自定义线程的命名、优先级等属性。
handler(拒绝策略):
当线程池已达到最大线程数并且队列也已满时,新任务无法被提交时的处理策略。
常见的处理策略有 AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy 等。
除了上面提到的几个核心参数外,线程池还有其他一些可选参数可以根据实际情况进行配置。以下是一些常用的可选参数:
allowCoreThreadTimeOut(允许核心线程超时):
若为 true,则核心线程也会在 keepAliveTime 时间内超时并被回收。
默认为 false。
rejectedExecutionHandler(拒绝策略):
当任务无法被提交时的处理策略。
ThreadPoolExecutor 中提供了 4 种拒绝策略,分别是 AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy。
在使用自定义拒绝策略时,需要实现 RejectedExecutionHandler 接口,并重写 rejectedExecution() 方法。
keepAliveTime 和 TimeUnit 的组合设置:
keepAliveTime 参数的单位可以通过 TimeUnit 枚举值进行设置,包括 NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS、MINUTES、HOURS 和 DAYS。
不同的 TimeUnit 设置对应的 keepAliveTime 值不同,如 TimeUnit.SECONDS 对应的 keepAliveTime 值为秒数。
threadFactory 和 rejectedExecutionHandler 的默认实现:
ThreadPoolExecutor 提供了默认的线程工厂和拒绝策略实现,如果不需要自定义,可以直接使用默认实现。
setMaximumPoolSize() 方法:
在运行过程中,可以通过 setMaximumPoolSize() 方法动态地修改最大线程数。这个方法可能会影响到线程池的并发性能,需要慎重使用。
14、创建线程池中的workQueue(任务队列)有哪些可供选择,他们的区别是什么
ArrayBlockingQueue、LinkedBlockingQueue 和 SynchronousQueue 是 Java 中常用的三种阻塞队列实现,它们有以下区别:
ArrayBlockingQueue:
ArrayBlockingQueue 是一个基于数组的有界阻塞队列。
它在构造时需要指定容量,即队列中可以存储的元素数量。
如果队列已满,则插入操作将被阻塞,直到队列中有空闲位置。
如果队列为空,则移除操作将被阻塞,直到队列中有元素可供移除。
ArrayBlockingQueue 是线程安全的,适用于固定大小的线程池。
LinkedBlockingQueue:
LinkedBlockingQueue 是一个基于链表的可选界阻塞队列。
在构造时,可以选择不指定容量,或者指定一个上限。如果不指定,则容量默认为 Integer.MAX_VALUE,即无界队列。
插入操作将一直成功,除非队列已满。
移除操作将一直成功,除非队列为空。
LinkedBlockingQueue 是线程安全的,适用于无限制大小或大容量的线程池。
SynchronousQueue:
SynchronousQueue 是一个不存储元素的阻塞队列。
每个插入操作必须等待一个对应的移除操作,反之亦然。
插入和移除操作是成对的,无法独立进行。
SynchronousQueue 是线程安全的,适用于线程池中的任务移交。
总结:
ArrayBlockingQueue 是一个有界阻塞队列,固定大小,适用于固定大小的线程池。
LinkedBlockingQueue 是一个可选界阻塞队列,默认情况下无界,适用于无限制大小或大容量的线程池。
SynchronousQueue 是一个不存储元素的阻塞队列,仅用于线程之间直接传输任务。
15、创建线程池中的handler(拒绝策略)有哪些可供选择,他们的区别是什么
AbortPolicy(默认策略):
当线程池无法执行新任务时,会抛出 RejectedExecutionException 异常。
这是默认的拒绝策略。
CallerRunsPolicy:
当线程池无法执行新任务时,会使用调用线程来执行该任务。
这可能会降低整体的处理速度,但可以保证不会丢失任务。
DiscardPolicy:
当线程池无法执行新任务时,会丢弃被拒绝的任务,不做任何处理。
使用这个策略可能会导致任务丢失,不建议在需要保证任务不丢失的情况下使用。
DiscardOldestPolicy:
当线程池无法执行新任务时,会丢弃队列中最旧的任务,然后尝试重新提交新任务。
这样可以腾出空间给新任务,但可能会丢失一些等待时间较长的任务。
自定义拒绝策略:
可以通过实现 RejectedExecutionHandler 接口来自定义拒绝策略,重写 rejectedExecution() 方法来定义具体的处理逻辑。
JavaEE
篇幅限制下面就只能给大家展示小册部分内容了。包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取
1、http get post的区别
http是超文本传输协议,它规定通过浏览器访问服务器时要遵循请求发送的规则,要求请求的格式要有请求行,请求头,请求体。get方法没有请求体
get和post则是发送请求的两种不同的方法
get请求可以被缓存,可以保存在历史浏览记录中,可以被收藏为书签,响应速度比post快,但是缺乏安全性,请求的数据会在地址栏中显示,请求长度限制最大4k。
post请求不可以被缓存,不会保留在历史浏览记录中,不能被收藏为书签,请求长度没有限制,请求数据不会显示在地址栏上,请求头比get更大。
2、Servlet生命周期
servlet包含四个生命周期
- 加载和实例化:当Servlet容器监听到有请求时,会实例化相应的servlet。
- 初始化:当示例化后,将会调用init()方法对servlet对象进行一定的初始化处理
- 处理请求:当成功初始化后,开始处理用户的请求,并返回给用户
- 销毁服务:当Servlet容器检测到已经执行完,就会调用destory()方法释放这个Servlet实例对象。
3、jsp和Servlet的相同点和不同点
jsp是servlet的技术加强,所有的jsp文件都会被解析成一个继成HttpServlet的类,这个类可以对外访问,并且可以动态的生成HTML,XML,或其他格式的web文档返回给用户。
servlet是应用在java文件中,处理用户请求,以HTML,XML,或其他格式返回给用户。
jsp多侧重于页面展示,servlet侧重处理业务逻辑。
4、内置对象和四大作用域
jsp拥有9个内置对象,4个作用域
内置对象
- request: 代表对象的请求对象,包含form表单的数据,浏览器信息等。
- response: 代表对客户端的相应对象,封装返回给用户的数据,发送重定向编码。
- session: 代表用户的一个连接对象,用于追踪用户会话,同时可以将用户的一些数据放入session域中,只要用户不断开此次连接,就可以一直读取session域中的数据。
- pageContext: 代表当前页面的一切属性,同时可以通过这个对象获取其他8个对象。。
- application: 提供了关于服务器版本,应用参数等和资源的绝对路径等。
- out: 代表输出流对象,用作向浏览器返回文本一级的数据,可以直接动态生成html文件。
- config: 代表servlet的配置信息对象,可以获取servlet的配置信息。
- page: 代表当前页面自身对象。
- exception: 代表jsp运行期间产生的异常对象。
作用域
- application: web程序的全局范围 自启动就存在,直到服务器关闭才销毁。
- session: 用作同一个会话中使用,会话关闭后,session域销毁。
- request: 只能在同一个一个请求中转发。
- pageContext: 只能在当前jsp页面中使用的数据。
5、Session和Cookie的区别
两者同是追踪回话的一种方式,最直观的区别就在于Session是在服务器端存储用户的登录信息,Cookie是在客户端浏览器存储用户的登录信息。
Seesion的机制决定了用户只能获取自己的session,其他用户的seesion不可见,各客户的session相互独立不可见。seesion的使用比cookie要方便,但是会对服务器产生一定的压力。
6、MVC模式和MVC各部分实现
MVC是目前B/S架构中最常见的设计思想,利用分层思想将程序的整个运行流程分为三层,方便开发中的逻辑处理,实现每一层处理不同的业务。
M: 用于处理应用程序数据逻辑的部分。通常模型对象负责在数据库中存取数据。
V: view代表向用户展示的页面数据。
C: 是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。
JVM虚拟机
1、java类加载过程
Java类的生命周期为:加载、验证、准备、解析、初始化、使用、卸载七个生命周期,其中,加载、验证、准备、解析、初始化可以称之为类的加载过程。
首先将class文件转为二进制流,接着验证是否为class文件和文件的正确性,验证完毕后在内存中生成该类对象,接着对类中的属性进行预定义,在局部变量表中进行分配存储。解析主要完成符号引用到直接引用的转换动作,有可能在初始化之后开始解析。初始化是对类中的变量进行初始化,加载构造方法,剩余的都又jvm进行初始,之后才开始执行类中定义的java程序代码。开始使用,最后卸载。
2、描述JVM加载Class文件的原理机制
当运行指定程序时JVM会按照一定规则编译并加载class文件,组织成为一个完整的java应用程序,这个过程由类加载器完成。一般有隐式加载(使用new的方式创建对象)和显示加载(利用class.forName()创建对象)两种方式。
类的加载是动态的,它不会一次性将所有的类加载完后再执行,而是保证基础类的加载,至于其他类则在需要时加载。
3、java内存分配

java内存分为以下5个区域:
- 堆:是jvm中最大的一块内存区域,用于存放对象实例,数组等。
堆中内存又分为新生代老年代,内存分配占比为 1:2。
新生代又分为Eden区和两块Survior区,内存占比为 8:1:1 - 栈:也叫虚拟机栈,可以理解为一个线程,在这个线程里执行的每个方法都是一个栈帧,而一个栈帧又包含了【局部变量表,操作数栈,动态链接,返回地址】。
- 方法区 :方法区中又分为静态方法区和非静态方法区。静态方法区用来存储已经被JVM加载的常量,静态变量等,是可以被多个线程共享的。非静态方法区是用于存储需要实体对象调用的方法。
- 本地方法栈:非Java语言实现的本地方法的堆栈,提供jvm和本机操作系统的功能接口。
- 程序计数器:保存有当前正在执行的JVM指令的地址。
4、GC是什么?为什么要有GC?
GC是JVM虚拟机中的垃圾回收器,可以自动监测堆内存中是否存在垃圾对象,从而达到自动回收内存的目的,尽量避免内存溢出。Java 语言没有提供释放已分配内存的显示操作方法。
5、简述java垃圾回收机制
在 Java 中垃圾回收由虚拟机自行执行。JVM 有一个垃圾回收线程,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫描垃圾对象,将他们回收。
6、如何判断一个对象是否存活
判断一个对象是否存活有两种方法:
引用计数法:给每一个对象设置一个引用计数器,当有一个地方引用这个对象时,计数器加一,引用失效时,计数器减一。当一个对象的引用计数器为零时,说明此对象没有被引用,也就是“死对象”,将会被垃圾回收。
引用计数法有一个缺陷就是无法解决循环引用问题,所以主流的虚拟机都没有采用这种算法。
可达性算法:从一个被称为 GC Roots 的对象开始向下搜索,如果一个对象到 GC Roots 没有任何引用链相连时,则说明此对象不可用。当一个对象不可达 GC Root 时,这个对象并不会立马被回收,若要被真正的回收需要经历两次标记。当经历两次标记后该对象将被移除” 即将回收”集合,等待回收。
7、垃圾回收的优点以及原理
在程序开发过程中,最让人头疼的就是内存回收,但是java引入了垃圾回收机制,使此类为题迎刃而解,使开发人员在开发中不用再考虑内存管理。
垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。不可预知的情况下对内存中已经死亡的或者长时间没有使用的对象进行清除和回收,开发者不能手动的调用垃圾回收器对某个对象进行垃圾回收。
回收机制有分代复制垃圾回收和标记垃圾回收,增量垃圾回收。
8、垃圾回收器可以马上回收内存吗?有什么办法可以主动通知JVM进行垃圾回收?
当程序员创建对象时,GC 就开始监控这个对象的地址、大小以及使用情况。GC 采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式判断是不是不可引用的垃圾对象,当确定是垃圾对象后GC会回收这些内存空间。而程序员虽然可以手动执行System.gc()但是java不能保证一定会销毁对象。
9、java中存在内存泄漏吗?
内存泄露就是指一个不再被程序使用的对象或变量一直被占据在内存中。Java 中有垃圾回收机制,它可以保证一个对象不再被引用的时候,对象将被垃圾回收器从内存中清除。
而java中使用有向图进行垃圾回收管理,可以消除引用循环的问题。
当然java也存在内存泄漏的可能,比如创建了一个对象,以后一直不再使用这个对象,这个对象却一直被引用,即这个对象无用但是却无法被垃圾回收器回收的,这就是 java中可能出现内存泄露的情况。
10、谈谈深copy和浅copy
深拷贝就是更改copy后的对象数据,原对象数据不变。浅拷贝就是更改copy后的对象数据原对象数据也跟着改变。简单来说就是深拷贝复制对象的值,浅拷贝是复制对象的地址。
11、如果对象的引用被置为null,垃圾收集器是否会立即释放对象占用的内存
如果对象的引用被置为null,只是断开了当前线程栈帧中对该对象的引用关系,而垃圾收集器是运行在后台的线程,只有当用户线程运行到安全点或者安全区域才会扫描对象引用关系,扫描到对象没有被引用则会标记对象,这时候仍然不会立即释放该对象内存,因为有些对象是可恢复的(在 finalize方法中恢复引用 )。只有确定了对象无法恢复引用的时候才会清除对象内存。
12、对象什么时候可以被垃圾回收
当对象没有变量引用的时候,这个对象就可以被回收了。
13、简述java内存分配与回收测率以及MinorGC
• 对象优先在堆的 Eden 区分配
• 大对象直接进入老年代
• 长期存活的对象将直接进入老年代
当 Eden 区没有足够的空间进行分配时,虚拟机会执行一次Minor GC 。Eden 区的对象生存期短,所以可能会频繁触发MinorGC,触发MinorGC后会将未被释放掉的对象放入S0或S1内存,如果要放入对象大于S0或S1内存的50%,则跳过S1或S0直接放入老年代。
14、JVM的永久代中会发生垃圾回收吗?
垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发Full GC
注:Java 8 中已经移除了永久代,新加了一个叫做元数据区的native内存区。
篇幅限制下面就只能给大家展示小册部分内容了。包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取
15、什么是类加载器,类加载器有哪些?
类加载器是一个用来加载类文件的类。Java源代码通过javac编译器编译成类文件。然后JVM执行加载后的字节码。类加载器负责加载文件系统、网络或其他来源的类文件。
| 加载器 | 说明 |
|---|---|
| 启动类加载器(BootstrapClassLoader) | 用来加载 Java 核心类库,无法被 Java 程序直接引用。 |
| 扩展类加载器(ExtensionsClassLoader) | 用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。 |
| 系统类加载器(SystemClassLoader) | 根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java应用的类都是由它来完成加载的。 |
| 用户自定义类加载器 | 通过继承 java.lang.ClassLoader 类的方式实现。 |
计算机网络
网络基础知识
1、什么是HTTP和HTTPS?它们有什么区别?
HTTP(Hypertext Transfer Protocol)是一种用于在Web浏览器和Web服务器之间传输数据的协议。它是基于客户端-服务器模型的,使用TCP作为传输协议,通过URL来定位资源,并使用请求-响应的方式进行通信。
HTTPS(Hypertext Transfer Protocol Secure)是在HTTP的基础上添加了安全性支持的协议。它通过使用SSL(Secure Sockets Layer)或TLS(Transport Layer Security)协议对通信进行加密,从而确保传输过程中的数据安全性。
主要区别如下:
安全性:HTTP是明文协议,数据传输过程中的内容是不加密的,易受到窃听和篡改的风险。而HTTPS通过使用SSL/TLS协议对数据进行加密,确保传输过程中数据的机密性和完整性,提供更高的安全性。
端口号:HTTP默认使用端口号80进行通信,而HTTPS默认使用端口号443。这样可以使得网络设备能够根据端口号来区分HTTP和HTTPS流量,从而进行相应的处理。
证书:在使用HTTPS时,服务器需要拥有一个有效的数字证书。该证书由受信任的第三方机构颁发,用于验证服务器的身份。这样可以防止中间人攻击,确保通信的安全性。
HTTPS相比于HTTP提供了更高的安全性,适用于需要保护用户隐私和敏感数据的场景,如登录、支付等。而HTTP则适用于不涉及敏感信息传输的普通网页浏览等场景。为了确保数据的安全性,使用HTTPS仍然需要注意其他方面的安全措施,如防止跨站脚本攻击(XSS)、点击劫持等。
2、TCP和UDP之间有什么区别?你能举出一些使用TCP和UDP的具体应用场景吗?
连接性:TCP是面向连接的协议,通信前需要建立连接、传输数据、然后释放连接;而UDP是无连接的协议,通信时不需要建立连接,直接发送数据包。
可靠性:TCP提供可靠的数据传输,通过确认和重传机制来确保数据的完整性和顺序性;UDP不提供数据传输的可靠性,数据包可能丢失或乱序,并不关心对方是否收到数据包的情况。
面向字节流和面向报文:TCP是面向字节流的协议,将数据视为字节流进行传输;UDP是面向报文的协议,每个数据包都是一个完整的报文。
拥塞控制:TCP具有拥塞控制机制,可以根据网络情况动态调整传输速率;UDP没有拥塞控制,数据包只能以发送者设定的速率传输。
TCP常用于需要可靠传输的场景,如网页浏览、电子邮件传输、文件下载等。
UDP常用于对实时性要求较高、容忍少量数据丢失的场景,如直播的音视频流媒体传输、在线游戏、VoIP通话等。
3、解释一下OSI模型中每一层的功能以及它们之间的关系。
物理层(Physical Layer):负责传输比特流,主要涉及物理介质、数据传输速率、电压等物理特性。
数据链路层(Data Link Layer):负责在相邻节点之间传输数据帧,提供可靠的数据传输服务。包括物理寻址、错误检测与纠正等功能。
网络层(Network Layer):负责在不同网络之间传输数据包,实现数据的路由和转发。包括逻辑寻址、路由选择、拥塞控制等功能。
传输层(Transport Layer):负责端到端的数据传输,提供可靠的数据传输服务。包括数据分段、流量控制、错误恢复等功能。常见的协议有TCP和UDP。
会话层(Session Layer):负责建立、管理和终止会话连接,确保数据的顺序传输和同步。处理会话层的协议如SIP、NetBIOS等。
表示层(Presentation Layer):负责数据格式转换、加密解密、数据压缩等操作,确保数据在不同系统之间的兼容性和可靠性。
应用层(Application Layer):提供用户接口和网络应用服务,包括HTTP、FTP、SMTP等协议,实现用户与网络的交互。
4、什么是IP地址?IPv4和IPv6有何不同?
IP地址(Internet Protocol address)是用来标识网络上设备的数字地址,它是计算机在网络上的唯一标识,类似于现实世界中的门牌号码。IP地址可以分为IPv4和IPv6两种版本。
IPv4是目前广泛使用的IP地址协议版本,它由32位二进制数表示,通常以每8位二进制数为一组,用十进制数表示
IPv6是下一代IP地址协议版本,它由128位二进制数表示,通常以每16位二进制数为一组,用十六进制数表示
IPv4和IPv6主要的不同点在于地址长度和表示方式上的差异,IPv6拥有更大的地址空间和更多的功能特性,能够更好地支持互联网的持续发展和扩张
5、什么是DNS?它的作用是什么?描述DNS解析过程。
DNS(Domain Name System)是互联网中用于将域名解析为对应IP地址的分布式数据库系统,它提供了域名和IP地址之间的映射关系,使得用户可以通过使用易记的域名来访问互联网上的各种服务和资源。
DNS的主要作用包括以下几个方面:
域名解析:将用户输入的域名转换成对应的IP地址,以便能够在网络中定位到特定的主机或服务器。
逆向解析:将IP地址反向解析成对应的域名,用于确定某个IP地址对应的主机名或域名。
6、什么是路由器?它在计算机网络中扮演什么样的角色?
路由器(Router)是一种网络设备,用于在不同网络之间传输数据包,并根据目标地址选择合适的路径进行转发。路由器在计算机网络中扮演着非常重要的角色,它主要用于实现不同网络之间的互联互通,确保数据能够在网络中准确、快速地传输。
路由器在计算机网络中的主要角色包括:
数据包转发:路由器接收到数据包后,会根据数据包中的目标IP地址,查找路由表确定最佳路径,然后将数据包转发到相应的下一跳路由器或目标主机。
网络分割:路由器可以将一个大的网络划分为多个子网,实现对不同子网的管理和控制,提高网络性能和安全性。
网络连接:路由器可以连接不同类型的网络,如LAN(局域网)与WAN(广域网),实现不同网络之间的通信交换。
数据包过滤:路由器可以根据配置的访问控制列表(ACL)等规则,对数据包进行过滤和检查,保护网络安全。
负载均衡:路由器可以根据负载情况,动态调整数据包的传输路径,实现负载均衡,提高网络性能和可靠性。
篇幅限制下面就只能给大家展示小册部分内容了。包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】即可免费获取
172万+

被折叠的 条评论
为什么被折叠?



