1、Java中的静态变量和实例变量有何区别?静态方法和实例方法有何区别?
静态变量和实例变量的区别:
静态变量属于类,实例变量属于对象;
静态变量只有一份,被所有对象共享,实例变量每个对象都有一份;
静态变量可以直接通过类名访问,实例变量需要通过对象名访问;
静态方法和实例方法的区别:
静态方法属于类,实例方法属于对象;
静态方法可以直接通过类名调用,实例方法需要通过对象名调用;
静态方法中不能使用 this 关键字,因为 this 表示当前对象,而静态方法没有对象;
静态对象和实例对象在存储方式上也是不同的
静态变量存储在方法区(Method Area)中,也称为永久代(PermGen),即在类加载时就已经被分配了内存空间,所有该类的对象共享同一份静态变量的内存空间。
而实例变量则存储在 Java 堆(Java Heap)中,即每个对象都有自己的实例变量的内存空间,当对象被创建时,实例变量也随之被分配内存空间。
需要注意的是,从 JDK 8 开始,永久代被移除了,取而代之的是元空间(Metaspace),静态变量的存储方式也变成了存储在元空间中。但是,与永久代不同,元空间并不是虚拟机运行时数据区域的一部分,而是使用本地内存来实现的。
2、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 关键字用于在代码中手动抛出异常。
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份好像面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处即可】免费获取
1、error和exception的区别
error和exception都继承于throwable类
error一般是虚拟机相关的问题,如系统崩溃,内存空间不足,方法调用栈溢出等,这种问题一旦出现,就代表这无法修复的错误,是非常严重的。
exception表示程序可处理的异常,遇见这种问题,应该尽可能的解决异常,使程序恢复运行。
exception又分为运行时异常和编译时异常,编译时异常表示语法都没用办法通过,运行时异常表示的是只有在程序运行时,才会出现的异常,比如数组下标越界,没找到类异常等。遇见异常时,尽量使用try…cache进行异常捕获并处理,保证程序的正常运行。
泛型
泛型(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:有序并且保证排序,线程不安全
List接口下有ArrayList、LinkedList
- ArrayList:查、改快,线程不安全,初始长度没有指定默认长度的时候,长度为0,并在第一次添加元素时初始化,初始长度为10,再次扩容会先copy数组,并扩容为原数组的1.5倍。
- LinkedList:增、删快,线程不安全,插入元素是将元素置于链表末尾,属于尾插法。
Map接口下有HashMap、TreeMap、LinkedHashMap和HashTable
- HashMap:无序双列集合,线程不安全
- TreeMap:可排序双列集合,线程不安全
- LinkedHashMap:有序并且保证排序的双列集合,线程不安全
- HashTable:无序双列集合,线程安全
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冲突后是尾插法。
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份好像面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处即可】免费获取
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算法进行安全实现线程安全。
线程
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。
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份好像面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处即可】免费获取
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 是一个不存储元素的阻塞队列,仅用于线程之间直接传输任务。
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份好像面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处即可】免费获取
15、创建线程池中的handler(拒绝策略)有哪些可供选择,他们的区别是什么
AbortPolicy(默认策略):
当线程池无法执行新任务时,会抛出 RejectedExecutionException 异常。
这是默认的拒绝策略。
CallerRunsPolicy:
当线程池无法执行新任务时,会使用调用线程来执行该任务。
这可能会降低整体的处理速度,但可以保证不会丢失任务。
DiscardPolicy:
当线程池无法执行新任务时,会丢弃被拒绝的任务,不做任何处理。
使用这个策略可能会导致任务丢失,不建议在需要保证任务不丢失的情况下使用。
DiscardOldestPolicy:
当线程池无法执行新任务时,会丢弃队列中最旧的任务,然后尝试重新提交新任务。
这样可以腾出空间给新任务,但可能会丢失一些等待时间较长的任务。
自定义拒绝策略:
可以通过实现 RejectedExecutionHandler 接口来自定义拒绝策略,重写 rejectedExecution() 方法来定义具体的处理逻辑。
JavaEE
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: 是应用程序中处理用户交互的部分。通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份好像面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处即可】免费获取
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内存区。
15、什么是类加载器,类加载器有哪些?
类加载器是一个用来加载类文件的类。Java源代码通过javac编译器编译成类文件。然后JVM执行加载后的字节码。类加载器负责加载文件系统、网络或其他来源的类文件。
加载器 | 说明 |
---|---|
启动类加载器(BootstrapClassLoader) | 用来加载 Java 核心类库,无法被 Java 程序直接引用。 |
扩展类加载器(ExtensionsClassLoader) | 用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。 |
系统类加载器(SystemClassLoader) | 根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java应用的类都是由它来完成加载的。 |
用户自定义类加载器 | 通过继承 java.lang.ClassLoader 类的方式实现。 |
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份好像面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处即可】免费获取