栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java面试题

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java面试题

根据我以往的面试经验,总结一些有深度的、常问的面试题供大家参考,基本上纯手敲,如果对你有用希望多多点赞多多支持哈。

目录

多线程相关

1.什么是上下文切换

2.讲一下synchronized关键字的底层原理

3.说一说Java的happens-before原则

4.ThreadLocal 原理

5.ThreadLocal 内存泄露问题

6. AQS 原理分析

虚拟机相关

1.详细说一下对象的创建过程(new一个对象内存里都发生了什么)?

2.你知道哪些常用的垃圾回收算法?

3.如何判断对象是否已经死亡(判断一个对象是否应该被垃圾收集器回收)?

4.说一说Java都有哪些引用类型?

5.介绍下 Java 内存区域(运⾏时数据区)

程序计数器:

Java虚拟机栈:

本地方法栈:

Java虚拟机堆:

方法区(JDK1.8之后变为元空间):

直接内存:

6.如何判断⼀个类是⽆⽤的类?

7.常⻅的垃圾回收器有那些?

Serial收集器

ParNew收集器

Parallel Scavenge收集器

Serial Old收集器

Parallel Old收集器

CMS收集器

G1收集器

8.介绍⼀下类⽂件结构吧!

9.知道类加载的过程吗?

加载

连接

初始化

10.双亲委派模型知道吗?能介绍⼀下吗?

计算机网络

1.TCP,UDP 协议的区别

2.select、poll、epoll 区别

1.支持一个进程所能打开的最大连接数

2.FD剧增后带来的IO效率问题

3. 消息传递方式

3.Session和Cookie的区别?

框架相关

1.谈谈⾃⼰对于 Spring IoC 和 AOP 的理解

Redis相关

1.说说什么是 Redis?

2.Redis 的数据类型有哪些?

3.Redis 为什么设计成单线程的?

4.Redis 和 Memcached 的区别有哪些?

5.Redis 有几种持久化方式?

6.两种持久化方式该如何选择?

7.Redis 有哪几种数据“淘汰”策略?

8.什么是Redis事务?

9.Redis 高可用方案有哪些?

Redis 单副本

Redis 多副本(主从)

Redis Sentinel(哨兵)

Redis Cluster


多线程相关

1.什么是上下文切换

多线程编程中一般线程的个数都会大于CPU的核心数,而一个CPU核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU采取的策略是为每个线程分配时间片并流转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其它线程使用,这个过程就属于一次上下文切换。

2.讲一下synchronized关键字的底层原理

synchronized关键字的底层原理属于JVM层面,synchronized同步语句块的实现是monitorenter和monitorexit指令,其中monitorenter指向同步代码块开始的位置,monitorexit指向同步代码块结束的位置。当执行monitorenter指令时,线程试图获取monitor(monitor对象存在于每个Java对象的对象头中)的持有权。当计数器为0则可成功获取,获取后将锁计数器设置为1.相应的在执行monitorexit指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那么当前线程就要阻塞等待,直到锁被另一个线程释放为止。

synchronized修饰方法取而代之的是ACC_SYNCHRONIZED标识,该标识指明了该方法是一个同步方法。

3.说一说Java的happens-before原则
  • 程序顺序原则:在一个线程内必须保证语义串行性,也就是按照代码顺序执行
  • 锁规则:解锁操作必然发生在后续的同一个锁的加锁操作之前
  • volitile原则:volitile变量的写先发生于读
  • 线程启动规则:线程的start()方法先于它的每一个动作
  • 传递性:A先于B,B先于C,那么A必然先于C
  • 线程终止规则:线程的所有操作先于线程的终结
  • 线程中断规则:对线程interrupt()方法的调用县发生与中断线程的代码检测到中断事件的发生,可通过Thread.interrupt()方法检测到线程是否发生中断
  • 对象终结规则:一个对象的初始化完成先发生与该对象的finalize()方法的开始

4.ThreadLocal 原理

Thread类中有一个threadLocals和一个inheritableThreadLocals变量,它们都是ThreadLocalMap类型的变量,我们可以把ThreadLocalMap类实现的定制化的Hashmap。默认情况下这两个变量都是null,只有当前线程调用ThreadLocal的set或get方法时才会创建他们,实际上我们调用这两个方法时,我们调用的是ThreadLocalMap类对应的get()、set()方法。

5.ThreadLocal 内存泄露问题

ThreadLocalMap中使用的key为ThreadLocal的弱引用。所以当ThreadLocal没有被外部强引用的情况下,在垃圾回收时,key会被清理掉,而value不会被清理掉。这样一来,ThreadLocalMap中就会出现key为null的Entry。假如我们不做任何措施的话,value永远无法被GC回收,这时候就可能发生内存泄漏问题。因此,使用完ThreadLocal方法后,最好手动调用remove()方法。

6. AQS 原理分析

AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

CLH(Craig,Landin and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配。

看个 AQS(AbstractQueuedSynchronizer)原理图:

AQS 使用一个 int 成员变量来表示同步状态,通过内置的 FIFO 队列来完成获取资源线程的排队工作。AQS 使用 CAS 对该同步状态进行原子操作实现对其值的修改。

private volatile int state;//共享变量,使用volatile修饰保证线程可见性

状态信息通过 protected 类型的 getState,setState,compareAndSetState 进行操作

//返回同步状态的当前值
protected final int getState() {
    return state;
}
//设置同步状态的值
protected final void setState(int newState) {
    state = newState;
}
//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);

 AQS定义了两种资源共享方式,Exclusive(独占):只有一个线程能够执行,Share(共享):多个线程可同时执行。

虚拟机相关

1.详细说一下对象的创建过程(new一个对象内存里都发生了什么)?

虚拟机遇到一条new指令时,首先会去检查这个指令的参数是否能在常量池中定义到一个类的符号引用,以此来检查这个类是否被加载、解析和初始化过,如果没有,那么必须先执行类加载的过程。

接下来虚拟机将会为对象分配内存。对象所需内存大小在类加载以后便可以完全确定。假如Java堆中的内存是完全规整的,所有用过的内存放在一边,未用过的放在另一边,中间放着一个指针作为分界点的指示器,那么内存分配就仅仅是把指针向空闲空间那边移动一段与对象大小相等的距离,这种分配方式被称为“指针碰撞”。如果java堆内存是不规整的,已使用的和空闲的空间互相交错,虚拟机就必须维护一个列表,记录哪些内存块是可用的,在分配的时候从列表中找到一块足够大的内存分配给对象实例,并更新列表的记录,这种分配方式被称为“空闲列表”。选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否有压缩整理的功能决定。

还有一个需要考虑的问题是对象分配内存是非常频繁的操作,因此必须要考虑线程安全问题。有两种解决方案,一是采用CAS配合重试机制保证更新操作的原子性;另一种是把内存分配的动作按照线程划分在不同的空间中进行,即每个线程先在Java堆中开辟一小块内存,称为本地线程分配缓冲(简称TLAB)。优先在TLAB上分配内存,只有TLAB用完分配新的TLAB时才会进行同步锁定。

内存分配完成后,虚拟机要将分配到的内存空间都初始化为0值(不包括对象头),接下来设置对象的对象头(对象头中存放着类的元数据信息、哈希码、对象的GC分代年龄和锁标志位),至此,从虚拟机的角度看一个新的对象已经产生了。

2.你知道哪些常用的垃圾回收算法?

标记-清除:该算法分为“标记”和清除两个阶段,首先标记处所有需要回收的所有对象,在标记完成后统一回收所有被标记的对象。

它的不足有两个,一个是效率问题,标记和清除的效率都不高;另一个是空间问题,标记清除以后会留下大量空间碎片,影响分配对象空间的效率。

标记-整理:标记过程依然与标记-清除算法一样,然后所有存活的对象都向一段移动,然后直接清理掉另一端的空间。

复制:复制算法将内存容量分为大小相等的两块,每次只使用其中一块。当这一块的内存用完了,就将所有存活的对象复制到另外一块上面,然后再把已经使用过的空间一次性清理掉。

复制算法的优缺点非常明显,一是对整个半区进行内存回收,内存分配时就不用再考虑内存碎片等情况,运行效率也高,缺点是浪费空间。

分代收集算法:当前主流的垃圾收集器均采用此算法。在新生代中每次收集都有大批对象死去,那就采用复制算法,只需要付出少量的复制成本就可以完成收集。而老年代中对象存活效率高、没有额外空间对它进行分配担保,就使用“标记-清除”或“标记-整理”算法来进行回收。

3.如何判断对象是否已经死亡(判断一个对象是否应该被垃圾收集器回收)?

引用计数法:给对象添加一个引用计数器,每当有一个对象引用它,计数器就加一,当引用失效时便减一;如果引用计数器为0则判断该对象已死。

引用计数法的主要缺点是它很难解决对象的循环引用问题导致对象无法被回收。

可达性分析算法:该算法的基本思想是通过一些列被称为“GC Root”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Root没有任何引用链则判断该对象已死。

GC Root对象主要有以下这几类:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象;
  • 方法区中类静态属性引用的对象;
  • 方法区中常量引用的对象;
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象。

4.说一说Java都有哪些引用类型?

强引用(Strong Reference):就是最常见的引用,通过new创建的对象都属于强引用,这种引用的特点是只要引用一直存在那么它永远不会被回收;

软引用(Soft Reference):软引用是用来描述一些还有用但是非必须的对象,对于软引用关联的对象,在系统将要发出内存溢出异常之前,就会把这些对象列入回收范围进行二次回收。

弱引用(Weak Reference):它的引用比软引用更弱一些,被弱引用关联的对象只能存活到下一次垃圾回收发生之前。

虚引用(Phantom Reference):也被称为幽灵引用或幻影引用,它是最弱的一种引用。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获取一个对象的实例。为一个对象设置虚引用的唯一目的便是能在这个对象被回收时收到一个通知。

5.介绍下 Java 内存区域(运⾏时数据区)

程序计数器:

程序计数器是一块较小的内存空间,线程私有,可以看作当前线程执行字节码的行号指示器。其主要有两个作用:

a.字节码解释器工作时通过改变这个计数器的值来选取下一条要执行的字节码指令,分支、循环、跳转、异常处理等功能都依赖这个计数器来完成;

b.在多线程情况下,程序计数器用于记录当前线程的位置,从而当前线程被切换回来的时候能知道该线程上次运行到哪了,这也就是为何该区域线程私有。

注意:程序计数器是唯⼀⼀个不会出现 OutOfMemoryError 的内存区域,它的⽣命周期随着线程的创建⽽创建,随着线程的结束⽽死亡。

Java虚拟机栈:

该区域也是线程私有的,它的生命周期和线程相同,描述的是Java方法执行的内存模型,每次方法调用的数据都是通过栈传递的。

Java内存可以粗糙的区分为堆内存和栈内存,Java虚拟机栈是由一个个栈帧组成,而每个栈帧中拥有局部变量表、操作数栈(操作数栈主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间)、动态链接、方法出口信息。每一次函数调用都会有一个对应的栈帧被压入Java栈,每一个函数调用结束后(return语句或抛出异常),都会有一个栈帧被弹出。

局部变量表中主要存放了编译器可知的各种数据类型(boolean、byte、char、short、int、float、 long、double )、对象引用(reference类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。

Java虚拟机栈会出现两种异常信息:StackOverFlowError 和 OutOfMemoryError。

  • StackOverFlowError:若Java虚拟机栈的内存大小不允许动态拓展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就会抛出StackOverFlowError异常。
  • OutOfMemoryError:若Java虚拟机栈的内存大小允许动态拓展,且当线程请求栈时内存用完了,无法再动态拓展了,就会抛出OutOfMemoryError异常。

本地方法栈:

和虚拟机栈所发挥的作⽤⾮常相似,区别是: 虚拟机栈为虚拟机执⾏ Java ⽅法(也就是字节码)服务,⽽本地⽅法栈则为虚拟机使⽤到的 Native ⽅法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合⼆为⼀。 本地⽅法被执⾏的时候,在本地⽅法栈也会创建⼀个栈帧,⽤于存放该本地⽅法的局部变量表、操作数栈、动态链接、出⼝信息。 ⽅法执⾏完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError 和OutOfMemoryError 两种异常。

Java虚拟机堆:

Java虚拟机所管理的内存中最大的一块,Java堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例和数组都是在这里分配内存。

Java堆是垃圾收集器管理的主要区域,因此也被称为GC堆。从垃圾回收的角度,由于现在收集器基本都采用分带垃圾收集算法,所以Java堆还可以细分为:新生代和老年代:再细致一点有:Eden空间、From survivor、To survivor空间等。进一步划分是为了更好的回收内存和更快的分配内存。

方法区(JDK1.8之后变为元空间):

方法区和Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

方法区和永久代之间的关系很像Java中接口和类的关系,而永久代就是hotspot虚拟机对虚拟机规范中方法区的一种实现方式。

运行时常量池也是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用)。字面量主要包括文本字符串、声明为final的常量值、基本数据类型的值等数据,符号引用主要有类和结构的完全限定名、字段名称和描述符、方法名称和描述符。

直接内存:

直接内存并不是虚拟机运⾏时数据区的⼀部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使⽤。⽽且也可能导致OutOfMemoryError 异常出现。

JDK1.4 中新加⼊的 NIO(New Input/Output) 类,引⼊了⼀种基于通道(Channel) 与缓存区(Buffer) 的 I/O ⽅式,它可以直接使⽤ Native 函数库直接分配堆外内存,然后通过⼀个存储在Java 堆中的 DirectByteBuffer 对象作为这块内存的引⽤进⾏操作。这样就能在⼀些场景中显著提⾼性能,因为避免了在 Java 堆和 Native 堆之间来回复制数据。

6.如何判断⼀个类是⽆⽤的类?

判定⼀个常量是否是“废弃常量”⽐较简单,⽽要判定⼀个类是否是“⽆⽤的类”的条件则相对苛刻许多。

类需要同时满⾜下⾯3个条件才能算是 “⽆⽤的类” :

  • 该类的所有实例都已经被回收,也就是Java堆中不存在任何该类的实例。
  • 加载该类的ClassLoader已经被回收。
  • 该类对应的java.long.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

7.常⻅的垃圾回收器有那些?

Serial收集器

Serial(串行)收集器是最基本的垃圾收集器,单线程,这意味着它在进行垃圾收集工作的时候必须暂停其他所有工作线程直到它收集结束。

新生代采用复制算法,老年代采用标记-整理算法。

ParNew收集器

Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等)和Serial收集器完全一样。

新生代采用复制算法,老年代采用标记-整理算法。

它是许多运行在server模式下虚拟机的首选,除了Serial收集器外,只有它能与CMS收集器配合工作。

Parallel Scavenge收集器

Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU)。CMS等收集器的关注点更多是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是CPU中用于运行用户代码的时间和CPU总消耗时间的比值。

Serial Old收集器 Serial 收集器的⽼年代版本 ,它同样是⼀个单线程收集器。它主要有两⼤⽤途:⼀种⽤途是在 JDK1.5 以及以前的版本中与Parallel Scavenge 收集器搭配使⽤,另⼀种⽤途是作为 CMS 收集器的后备⽅案。

Parallel Old收集器

Parallel Scavenge收集器的⽼年代版本。使⽤多线程和“标记-整理”算法。在注重吞吐量以及CPU资源的场合,都可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器。

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。

CMS收集器是Hotspot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集器与用户线程基本上同时工作。

CMS收集器是基于标记-清除算法实现的,它的运作过程分为四个步骤:

  • 初始标记:暂停所有的其他线程,并记录下直接与GC Root相连的对象,速度很快;
  • 并发标记:同时开启收集和用户线程,用一个闭包结构去记录可达对象,这个闭包结构并不能保证当前所有的可达对象。因为用户线程可能会不停的更新引用域,所以GC线程无法保证可达性分析的实时性。所以这个算法会跟踪记录这些发生引用更新的地方;
  • 重新标记:重新标记阶段就是为了修正并发标记期间因为用户线程继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的停顿稍长,远远比并发标记阶段时间段;
  • 并发清除:开启用户线程,同时GC线程开始为标记的区域做清扫。

优点:并发收集、低停顿,用户体验最好,缺点:对CPU资源敏感、无法处理浮动垃圾、会产生大量的空间碎片。

G1收集器 G1 (Garbage-First) 是⼀款⾯向服务器的垃圾收集器 , 主要针对配备多颗处理器及⼤容量内存的机器 . 以极⾼概率满⾜ GC 停顿时间要求的同时 , 还具备⾼吞吐量性能特征 . 被视为 JDK1.7 中 HotSpot 虚拟机的⼀个重要进化特征。它具备⼀下特点:
  • 并⾏与并发:G1能充分利⽤CPU、多核环境下的硬件优势,使⽤多个CPU(CPU或者CPU核⼼)来缩短Stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执⾏的GC动作,G1收集器仍然可以通过并发的⽅式让java程序继续执⾏。
  • 分代收集 :虽然 G1 可以不需要其他收集器配合就能独⽴管理整个 GC 堆,但是还是保留了分代的概念。
  • 空间整合 :与 CMS 的 “ 标记 -- 清理 ” 算法不同, G1 从整体来看是基于 “ 标记整理 ” 算法实现的收集 器;从局部上来看是基于 “ 复制 ” 算法实现的。
  • 可预测的停顿 :这是 G1 相对于 CMS 的另⼀个⼤优势,降低停顿时间是 G1 和 CMS 共同的关注点, 但G1 除了追求低停顿外,还能建⽴可预测的停顿时间模型,能让使⽤者明确指定在⼀个⻓度为 M毫秒的时间⽚段内。 G1收集器的运作⼤致分为以下⼏个步骤:
  • 初始标记
  • 并发标记最终标记
  • 初始标记
  • 并发标记最终标记
  • 筛选回收
G1 收集器在后台维护了⼀个优先列表,每次根据允许的收集时间,优先选择回收价值最⼤的 Region( 这也就是它的名字Garbage-First 的由来 ) 。这种使⽤ Region 划分内存空间以及有优先级的区域回收⽅式, 保证了GF 收集器在有限时间内可以尽可能⾼的收集效率(把内存化整为零)。

8.介绍⼀下类⽂件结构吧!

1.魔数:确定这个文件是否为一个能被虚拟机接收的Class文件。

2.Class文件版本:Class文件的版本号,保证编译正常执行。

3.常量池:主要存放两大常量:字面量和符号引用。

4.访问标志:标志用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口,是否是public或abstract类型,如果是类是否声明为final等。

5.当前类索引,父类索引。

6.接口索引集合:用来描述这个类实现了哪些接口,这些接口将按implements后的顺序从左到右排列在接口索引结合中。

7. 字段表集合 :描述接⼝或类中声明的变量。字段包括类级变量以及实例变量,但不包括在⽅法

内部声明的局部变量。 8. ⽅法表集合 :类中的⽅法。 9. 属性表集合 : 在 Class ⽂件,字段表,⽅法表中都可以携带⾃⼰的属性表集合。

9.知道类加载的过程吗?

类加载的过程分为三步:加载>连接>初始化,连接过程又可分为三步:验证>准备>解析。

加载

加载时主要完成下面三件事:

1.通过全限定名获取此类的二进制字节流

2.将字节流所代表的静态存储结构转换为方法区的运行时数据结构

3.在内存中生成一个代表此类的Class对象,作为方法区这些数据的访问入口

一个非数组类的加载阶段是可控性最强的阶段,这一步我们可以去完成还可以自定义类加载器去控制字节流的获取方式。数组类型由Java虚拟机直接创建。

连接

验证阶段主要分为以下4步:

1.文件格式验证:验证字节流是否符合Class文件格式规范,例如:是否以0xCAFEBEBE开头、主次版本号是否在当前虚拟机的处理范围内,常量池中的常量是否有不被支持的类型。

2.元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求

3.字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。比如保证任意时刻操作数栈和指令代码序列都能配合工作。

4.符号引用验证:确保解析动作能够正确执行。

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区内分配。

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要是针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。

初始化

初始化阶段是执行初始化方法 ()的过程,是类加载的最后一步,这一步虚拟机才开始真正执行类中定义的Java程序代码(字节码)。

对于 () 方法的调用,虚拟机会自己确保其在多线程环境中的安全性。因为  () 方法是带锁线程安全,所以在多线程环境下进行类初始化的话可能会引起多个进程阻塞,并且这种阻塞很难被发现。

对于初始化阶段,虚拟机严格规范了有且只有 5 种情况下,必须对类进行初始化(只有主动去使用类才会初始化类):

  1. 当遇到 new 、 getstatic、putstatic 或 invokestatic 这 4 条直接码指令时,比如 new 一个类,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。
    • 当 jvm 执行 new 指令时会初始化类。即当程序创建一个类的实例对象。
    • 当 jvm 执行 getstatic 指令时会初始化类。即程序访问类的静态变量(不是静态常量,常量会被加载到运行时常量池)。
    • 当 jvm 执行 putstatic 指令时会初始化类。即程序给类的静态变量赋值。
    • 当 jvm 执行 invokestatic 指令时会初始化类。即程序调用类的静态方法。
  2. 使用 java.lang.reflect 包的方法对类进行反射调用时如 Class.forname("..."), newInstance() 等等。如果类没初始化,需要触发其初始化。
  3. 初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。
  4. 当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main 方法的那个类),虚拟机会先初始化这个类。
  5. MethodHandle 和 VarHandle 可以看作是轻量级的反射调用机制,而要想使用这 2 个调用, 就必须先使用 findStaticVarHandle 来初始化要调用的类。
  6. 「补充,来自issue745」 当一个接口中定义了 JDK8 新加入的默认方法(被 default 关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。

10.双亲委派模型知道吗?能介绍⼀下吗?

每一个类都有一个对应它的类加载器。系统中的ClassLoader在协同工作的时候会默认使用双亲委派模型。即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载过的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委托给父类加载器的loadClass()处理,因此最终所有的请求都应传送到顶层的启动类记载器BootstrapClassLoader中。当父类加载器无法处理时,才由自己来处理。当父类加载器为null时会使用BootstrapClassLoader作为父类加载器。

双亲委派模式保证了Java程序的稳定运行,可以避免类的重复加载(JVM区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的时两个不同的类),也保证了Java核心API不被篡改。

如果我们不想使用双亲委派机制,我们可以自定义一个类加载器,并继承ClassLoader,之后重载loadClass()方法即可。

计算机网络

1.TCP,UDP 协议的区别
  • TCP是面向连接的,UDP是无连接的,即发送数据之前不需要建立连接
  • TCP提供可靠服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
  • TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的,UDP没有拥塞控制,因此网络发生拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP段话,实时视频会议等)
  • 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
  • TCP首部开销20字节;UPD首部开销只有8个字节
  • TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

2.select、poll、epoll 区别

1.支持一个进程所能打开的最大连接数

select:单个进程所能打开的最大连接数有FD_SETSIZE宏定义,其大小是1024,当然我们可以修改其大小,然后重新编译内核,但是性能可能受到影响。

poll:poll本质上和select没有区别,但是它没有最大连接数的限制,原因是它是基于链表来存储的

epoll:虽然连接数有上限,但是很大,1G内存的机器上可以打开10万左右的连接。

2.FD剧增后带来的IO效率问题

select:因为每次调用都会对连接进行线性遍历,所以随着FD的增加会造成遍历速度以线性指数下降的性能问题。

poll:poll本质上和select没有区别

epoll:因为epoll内核中实现是根据fd上的callback函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epoll没有前面两者性能的线性下降问题。

3.消息传递方式

select:内核需要将消息传递到用户空间,都需要内核拷贝动作

poll:同上

epoll:epoll通过内核和用户空间共享一块内存来实现的。

3.Session和Cookie的区别?

1.cookie数据存放在客户的浏览器上,session数据存放在服务器上

2.cookie不是很安全,别人可以分析放在本地的cookie并进行cookie欺骗,主要考虑安全应该使用session,session会比较占用服务器性能,当访问增多时应用cookie

3.单个cookie在客户端的限制是3K,也就是说一个站点在客户端存放的cookie不能超过3K

框架相关

1.谈谈⾃⼰对于 Spring IoC 和 AOP 的理解

IOC是一种设计思想,就是就是将原本在程序中手动创建对象的控制权,交由Spring框架管理。IOC容器是Spring用来实现IOC的载体,IOC实际上就是个map,map中存放的是各种对象。

将对象之间的依赖关系交给IOC来处理,并由IOC来完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。IOC容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是怎么被创建出来的。

AOP能够将那些与业务无关、却被业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来。便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy去创建代理对象,而对于没有实现接口的对象,Spring AOP会使用Cglib,这时候Spring AOP会使用Cglib生成一个代理对象的子类来处理。

Redis相关

1.说说什么是 Redis?

Redis是一个开源、内存存储的数据结构服务器,可用作数据库、高速缓存和消息队列代理。它支持字符换、哈希表、列表、集合、有序集合、位图等数据类型。内置复制、lua脚本、LRU回收、事务,以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。

2.Redis 的数据类型有哪些?
  • String:最简单的类型(底层使用动态字符串,效率较高),就是普通的set和get,做简单的KV缓存。
  • Hashe:这个是类似map的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是这个对象没有嵌套其它对象)给缓存在redis里,然后每次读写缓存的时候,就可以操作hash里的某个字段。
  • List:有序列表。可以通过list存储一些列表型的数据结构。
  • Sets:无序集合,自动去重。你当然也可以使用Java的hashset进行去重,但是如果你的某个系统部署在多个机器上呢?得基于redis进行全局得set去重。
  • Sorted Set:有序得set。写进去一个分数,自动根据分数排序,底层基于跳表(SkipList)。可以用来做排行榜相关功能。

3.Redis 为什么设计成单线程的?
  • 绝大部分请求是纯粹的内存操作
  • 采用单线程,避免了不必要的上下文切换和竞争条件
  • 非阻塞IO,内部采用epoll,epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,避免IO代价。

4.Redis 和 Memcached 的区别有哪些?
  • Redis和Memcached都是将数据存放在内存中,都是内存数据库。不过Memcache还可以缓存其它东西,例如图片、视频等等。
  • Memcached仅支持key-value结构的数据类型,Redis支持KV、list、set等多种数据类型
  • Memcached单个value最大1m,Redis的单个value最大512m
  • Memcached挂掉以后,数据没了;Redis数据丢失后可以通过aof恢复
  • Memcached没有原生的集群模式,需要依赖客户端实现,Redis原生就支持集群模式Redis Cluster

5.Redis 有几种持久化方式?

Redis提供了两种方式,实现数据的持久化到硬盘。

1.【全量】RDB持久化,是指在指定时间间隔内将内存中的数据集快照写入磁盘。实际操作过程是,fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

优点:

  • 可以灵活设置备份频率和周期。
  • 非常适合冷备份,对于灾难恢复而言,RDB是非常不错的选择,我们可以轻松的将一个单独的文件压缩后再转移到其它存储介质上。
  • 性能最大化。RDB对Redis对外提供读写服务的影响非常小,可以让Redis保持高性能。
  • 恢复更快。相比于AOF机制,RDB的恢复速度锋快,特别是在数据集非常大的时候。

缺点:

  • 如果你想保证数据的高可用性,即最大限度的避免数据丢失, 那么 RDB 将不是一个很好的选择。因为系统一旦在定时持久化 之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。
  • 由于 RDB 是通过 fork 子进程来协助完成数据持久化工作的,因 此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是 1 秒钟。

2.【增量】AOF持久化,以日志形式记录服务器所处理的每一个写操作,以文本的方式记录。

优点:

  • 该机制可以带来更高的数据安全性。Redis中提供了三种同步策略,每秒同步、每修改同步和不同步
  • 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏文件中已经存在的内容
  • 如果AOF文件过大,Redis可以自动启用rewrite机制。即使出现后台重写操作,也不会影响客户端读写。
  • AOF 包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事 实上,我们也可以通过该文件完成数据的重建。

缺点:

  • 对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB在恢复大数据集时速度比AOF的恢复速度要快。
  • 根据同步策略的不同,AOF的运行效率往往会慢于RDB。

6.两种持久化方式该如何选择?

bgsave做镜像全量持久化, AOF做增量持久化。因为bgsave会耗费较长时间,不够实时,在停机的时候会导致大量的数据丢失,所以需要AOF来配合使用。在Redis实例重启时,会使用bgsave持久化文件重新构建内存,再使用AOF重放近期的操作指令来实现完全恢复重启之前的状态。

7.Redis 有哪几种数据“淘汰”策略?

Redis一共有6种数据淘汰策略:

1.noeviction:当到达内存限制时返回错误,并且客户端会发出尝试使用更多内存的命令。

2.allkeys-lru:尝试回收最少使用的键,使得新添加的数据有空间存放。

3.volitile-lru:尝试回收最少使用的键,但仅限于在过期集合的键,使得新添加的数据有空间存放。

4.allkeys-random:回收随机的键,使得新添加的数据有空间存放。

5.volitile-random:回收随机的键,但仅限于在过期集合的键,使得新添加的数据有空间存放。

6.volitile-ttl:回收在过期集合的键,并且优先回收存活时间较短的键,使得新添加的数据有空间存放。

8.什么是Redis事务?

可以一次性执行多条命令,本质上是一组命令的集合。一个事务中的所有命令都会序列化,然后顺序地串行化执行,而不会被插入其他命令。

Redis事务不支持回滚,如果事务中有错误的操作,无法回滚到处理前的状态。在执行完当前事务内的所有指令前,不会同时执行其他客户端的请求。

9.Redis 高可用方案有哪些?

Redis 单副本

Redis 单副本,采用单个 Redis 节点部署架构,没有备用节点实时同步数据, 不提供数据持久化和备份策略,适用于数据可靠性要求不高的纯缓存业务场景。

Redis 多副本(主从)

Redis 多副本,采用主从(replication)部署结构,相较于单副本而言最大的特 点就是主从实例间数据实时同步,并且提供数据持久化和备份策略。主从实例 部署在不同的物理服务器上,根据公司的基础环境配置,可以实现同时对外提 供服务和读写分离策略。

Redis Sentinel(哨兵)

Redis Sentinel 是社区版本推出的原生高可用解决方案,其部署架构主要包括 两部分:Redis Sentinel 集群和 Redis 数据集群。 其中 Redis Sentinel 集群是由若干 Sentinel 节点组成的分布式集群,可以实现 故障发现、故障自动转移、配置中心和客户端通知。Redis Sentinel 的节点数 量要满足 2n+1(n>=1)的奇数个。

Redis Cluster

Redis Cluster 是社区版推出的 Redis 分布式集群解决方案,主要解决 Redis 分 布式方面的需求,比如,当遇到单机内存,并发和流量等瓶颈的时候,Redis Cluster 能起到很好的负载均衡的目的。 Redis Cluster 集群节点最小配置 6 个节点以上(3 主 3 从),其中主节点提供 读写操作,从节点作为备用节点,不提供请求,只作为故障转移使用。 Redis Cluster 采用虚拟槽分区,所有的键根据哈希函数映射到 0~16383 个整 数槽内,每个节点负责维护一部分槽以及槽所印映射的键值数据。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/848367.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号