1.JVM内存结构
1. 方法区(Method Area)
方法区是JVM内存结构的一部分,用于存放类的相关信息,包括:
- 类的结构(字段、方法、常量池等)。
- 字段和方法的描述,如名称、类型、访问修饰符等。
- 静态变量。
- Java虚拟机在运行时加载的类的信息。
方法区通常在堆区的外部,与堆区是分开的。因为主要存放的是类的信息和静态数据,所以这一部分数据的生命周期与类本身相同。
2. 堆(Heap)
堆是JVM中用于动态分配内存的区域,是对象和数组的存储区。堆内存是JVM运行过程中最大的内存区域,所有对象实例和数组都是在这个区域中分配的。堆区又可以分为几个部分:
- 新生代(Young Generation):用于存放新创建的对象,年轻代又可以分为三个部分:Eden空间和两个Survivor空间。新创建的对象首先在Eden区分配,经过垃圾回收后存活下来的对象会被移动到Survivor区。
- 老年代(Old Generation):用于存放长期存活的对象。经过多次垃圾回收,仍然存活的对象会被转移到老年代。
- 持久代(PermGen)/元空间(Metaspace):在旧版本的JVM中,方法区使用持久代来存储类元信息。在Java 8及其之后的版本中,持久代被元空间替代,主要用于存储类的元数据,不再属于Java虚拟机的堆内存,而是使用本地内存。
3. 虚拟机栈(VM Stack)
虚拟机栈用于存储局部变量、操作数栈、动态链接等信息。每个线程都有自己的虚拟机栈。栈是先进后出(LIFO)的结构,栈中的每一个栈帧(Stack Frame)对应一个方法的调用。在方法调用时,相关的局部变量、参数值以及方法返回地址等都会被推入栈中,而在方法返回时,栈帧会被弹出。
4. 本地方法栈(Native Method Stack)
本地方法栈用于支持JVM调用本地(Native)方法,类似于虚拟机栈。它存储的是本地方法的栈帧。不同的JVM实现可能会有所不同,但其功能主要是协助执行 Java 与其他语言(如C、C++)交互的本地方法。
5. 程序计数器(Program Counter Register)
程序计数器是一个较小的内存区域,保存着当前线程所执行的字节码的地址。它是线程私有的,每个线程都有一个程序计数器,以便在多线程环境中跟踪每个线程的执行点。可以理解为一个指针,指向当前正在执行的指令。
2.垃圾回收机制(GC)
垃圾回收算法
垃圾回收算法有多种,常见的有:
-
标记-清除(Mark-and-Sweep):
- 该算法分为两个阶段。第一阶段“标记”会遍历所有可达对象并标记它们,第二阶段“清除”会删除没有被标记的对象。尽管这个方法简单有效,但它可能导致内存碎片。
-
复制(Copying):
- 该算法将内存分为两个空间(通常称为“From”和“To”区域),在进行垃圾回收时复制所有活跃对象到另一个区域。复制后,原来的区域会被清空。该方法避免了内存碎片,但需要更多内存。
-
标记-整理(Mark-and-Compact):
- 该算法与标记-清除结合。首先,它会标记所有可达对象,然后将它们移动到一端,以消除内存碎片,并清理未使用的内存。
-
分代收集(Generational Collection):
- 根据对象的生命周期将内存分为若干代(通常为年轻代和老年代)。新创建的对象通常会在年轻代中变化较快,因此通过频繁回收年轻代来提高效率。而老年代中的对象较少变化,因此不需要频繁检查和回收。
JVM的垃圾回收机制:GC,是Java提供的对于内存自动回收的机制。
GC(Garbage Collection)是Java虚拟机(JVM)中的一项重要功能,用于自动管理堆内存中不再使用的对象,释放其占用的内存空间。GC通过标记和回收无效对象来实现内存的回收和释放,以避免内存泄漏和溢出。
下面是GC的主要工作原理和过程:
1、对象的标记:GC首先标记出所有活动对象,即仍然被引用或可达的对象。它从一组根对象开始,逐步遍历对象图,将可达的对象标记为活动对象,未标记的对象则被认为是无效的。
2、垃圾回收:在标记完成后,GC会对未标记的对象进行回收。具体的回收算法可以是不同的,常见的算法包括标记-复制算法、三色标记算法等。
3、内存压缩和整理:某些垃圾回收算法在回收完对象后,可能会产生内存碎片。为了优化内存使用,GC可能会进行内存压缩和整理操作,使得分配的对象在内存中连续存放,减少内存碎片的影响。
GC的优点是可以自动管理内存,减少了手动内存管理的复杂性,避免了内存泄漏和溢出的问题。但是,GC也会带来一定的性能开销。因此,在开发Java应用程序时,需要合理配置GC的参数和调整垃圾回收策略,以平衡性能和内存的使用。
3.JVM调优的方法?
1. 内存管理调优
-
堆内存大小设置:可以通过 (初始堆大小)和 (最大堆大小)参数来设置堆内存的大小。例如:。根据应用程序的需求来调整堆的大小
-
选择合适的垃圾回收器:不同的垃圾回收器适用于不同的场景,例如:
- G1垃圾回收器():适合大堆内存和低延迟的应用。
- Parallel垃圾回收器():适合高吞吐量的应用。
- CMS垃圾回收器():适合需要最小停顿的应用。
- ZGC和Shenandoah:适合需要极低延迟的应用,但需要JVM版本支持。
-
调整新生代和老年代的比例:可以通过 或 参数来调整新生代与老年代的比例,以优化对象的存活率和垃圾回收频率。
2. 垃圾回收调优
-
影响GC频率和停顿时间的参数:
MaxGCPauseMillis
:设置最大GC停顿时间。GC会尝试满足这个时间限制。GCTimeRatio
:调整应用可用时间与GC时间的比例。
-
监控和分析:使用JVM提供的工具(如JVisualVM、JConsole、GC日志)来监控和分析垃圾回收的情况,识别GC问题。
3. JIT编译优化
-
开启Tiered Compilation:使用,可以提高启动性能。
-
调整JIT编译行为:通过设置编译阈值,以控制何时将方法编译为机器代码。
-
4. 线程调优
-
线程栈大小:可以用参数设置每个线程的栈大小。例如:。适当调整以减少栈溢出和内存使用。
-
线程池管理:如果应用使用线程池,确保合理配置线程数,避免因过多线程上下文切换引起的性能问题。
5. 其他优化选项
-
类加载与内存优化:
- 使用 可保持对象引用的压缩,有助于减少堆内存的使用。
-
禁用或调整assertions:在生产环境中,可以禁用不必要的断言以提高性能:。
-da
6. 性能监控与分析
-
使用JVM监控工具:如Java Mission Control(JMC)和Flight Recorder来分析应用性能,查找瓶颈。
-
Profiling:使用工具(如YourKit、VisualVM等)进行代码剖析,找到性能开销较大的热点代码。
7. 配置文件与启动参数
- 合理配置启动参数:根据具体的应用场景和服务器配置调整JVM的启动参数,比如开启JIT、选择合适的GC、调整堆内存大小等。
4.堆和栈的区别?
1. 结构和用途:
-
栈(Stack):
- 栈是一种先进后出(LIFO, Last In First Out)的数据结构。
- 用于存储局部变量、函数参数和函数调用信息等。在函数调用时,函数的局部变量会压入栈中,函数返回时会将这些变量从栈中弹出。
- 每个线程都有自己的栈,用于跟踪函数调用和局部变量。
-
堆(Heap):
- 堆是一种基于动态存储分配的内存区域,没有特定的结构(可以认为是自由存储区)。
- 用于存储动态分配的对象和数据,如使用 关键字在C++中分配的对象,或在Java中创建的对象。
- 堆内存可以由程序员直接管理,程序员需要手动申请和释放内存,或者由垃圾回收机制(如Java、C#)自动管理。
2. 存储管理:
-
栈:
- 存储在栈内存中的数据管理简单,入栈和出栈操作的时间复杂度都是O(1)。
- 由于栈的大小是固定的(通常由操作系统限定),栈溢出(Stack Overflow)可能导致程序崩溃。
-
堆:
- 堆内存的大小通常只受到系统内存的限制,算法的复杂性和速度依赖于内存分配和回收的实现。
- 堆内存分配需要更多的时间,且容易出现内存泄漏或内存碎片问题。
3. 生命周期:
-
栈:
- 数据的生命周期由函数调用决定。函数结束后,栈上的数据会自动释放。
-
堆:
- 数据的生命周期由程序员控制,只有在不再需要对象时,才能显式地释放空间。如果没有释放,则会造成内存泄漏。
4. 访问速度:
-
栈:
- 由于其结构简单,内存访问速度较快,通常会比堆更快。
-
堆:
- 由于堆内存的管理要复杂得多,非顺序访问可能导致较慢的访问速度。
5.JVM使用命令
jvm 的一些命令_jvm命令-CSDN博客
jinfo
描述:输出给定 java 进程所有的配置信息。包括 java 系统属性和 jvm 命令行标记等。
jstat
jstat -gcutil <pid> <interval>
查看java进程的gc情况
以百分比显示每个区域的内存使用情况;
参数interval表示每多少毫秒刷新一次
6.class.forName和Classload的区别
1.相同点
两者都可以对类进行加载。
对于任意一个类/对象,我们都能够通过反射能够调用这个类的所有属性方法。
2.不同点
抽象类ClassLoader中实现的方法loadClass,loadClass只是加载,不会解析更不会初始化所反射的类。常用于做懒加载,提高加载速度,使用的时候再通过.newInstance()真正去初始化类。
7.谈谈JDK1.8特性有哪些
Lambda表达式 类似于ES6中的箭头函数
接口的默认方法和静态方法
新增方法引用格式
新增Stream类
新的日期API,Datetime,更方便对日期的操作
引入Optional,在SpringData中使用较多,然后再通过get获取值,主要用于防止NPE。
支持Base64
注解相关的改变
支持并行(parallel)数组
对并发类(Concurrency)的扩展。
JavaFX。
JDK1.8常用新特性_jdk1.8的新特性-CSDN博客
8.反射获取类中的所有方法和获取类中的所有属性
getDeclaredMethods()
和getDeclaredFields()
允许你访问类的所有方法和字段,包括私有的。getMethods()
和getFields()
只返回公共方法和字段,且包括继承的父类方法和字段。
9.懒汉和饿汉模式的区别?口述两种模式。
在饿汉式单例模式中,“饿” 体现的是一种急切的状态。就好像一个很饿的人,在看到食物(这里类比于单例对象)的时候,会迫不及待地先把食物拿到手(创建单例对象)。在这个模式下,单例对象在类加载阶段就被创建出来,而不是等到真正需要使用这个对象的时候才去创建。这种方式比较急切,所以被称为 “饿汉模式”。
在懒汉模式下,实例在第一次使用时才进行创建,因此称为“懒汉”,在需要被用的时候被创建,突出一个字“懒”
10.如何保证单例模式在多线程中的线程安全
私有构造函数:通过将构造函数设置为私有,我们禁止了外部类直接实例化Singleton类。这确保了只能通过getInstance()方法来获取实例。
volatile关键字:volatile关键字确保了instance变量的可见性。当一个线程修改了一个volatile变量的值时,其他线程能够立即看到这个变化。这样可以避免线程之间的缓存不一致问题。
双重检查锁定(Double-Checked Locking):为了减少同步开销,我们在第一次检查instance是否为空后,才进入同步块。这是因为大多数情况下,实例已经被创建,不需要进入同步块。只有在第一次创建实例时才会需要同步。
同步块:在同步块内部,我们再次检查instance是否为空,以确保只有一个线程能够创建实例。这是为了防止多个线程同时通过了第一个空检查并尝试创建多个实例。
11.spring运用了什么设计模式?讲一下哪些部分运用到了这些设计模式
- 一、简单工厂模式(Simple Factory)
-
定义:
简单工厂模式:并不属于 GoF(四人组)总结的 23 种设计模式,但它却在实际开发中被频繁使用。其核心是 由一个工厂类根据传入的参数,动态决定创建哪一个产品类的实例。
举例:
Spring 中的 BeanFactory 就是简单工厂模式的体现,根据传入一个唯一的标识来获得 Bean 对象。但是,在传入参数后创建 Bean 还是传入参数前创建 Bean,这个要根据具体情况而定。
- 二、工厂方法模式(Factory Method)
-
定义:
工厂方法模式:定义了一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到子类。
举例:
Spring 使用工厂模式可以通过 BeanFactory 或 ApplicationContext 创建 Bean 对象。两者对比如下:
BeanFactory:延迟注入(使用到某个 Bean 的时候才会注入),相比于 ApplicationContext 来说会占用更少的内存,程序启动速度更快。
ApplicationContext:容器启动的时候,不管你用没用到,一次性创建所有 Bean。BeanFactory 仅提供了最基本的依赖注入支持,ApplicationContext 扩展了 BeanFactory,除了有 BeanFactory 的功能还有额外更多功能,所以一般开发人员使用 AplicationContext 更多。 - 三、单例模式(Singleton)
-
定义:
单例模式:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
举例:
在我们系统中,有一些对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如;程序的行为异常、资源使用过量、或者不一致性的结果。
- 四、适配器模式(Adapter)
-
定义:
适配器模式:将一个接口转换成客户希望的另一个接口,适配器使接口不兼容的那些类可以一起工作。
举例:
Spring AOP 中的适配器模式:
我们知道 Spring AOP 的实现是基于代理模式,但是 Spring AOP 的增强或通知(Advice)使用到了适配器模式,与之相关的接口是 AdvicorAdapter。
- 五、代理模式(Proxy)
-
定义:
代理模式:为其他对象提供一个代理以控制对这个对象的访问。
举例:
代理模式在 AOP 中的应用:
AOP(Aspect-Oriented Programming,面向切面编程):能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如:事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性
- 七、观察者模式(Observer)
-
定义:
观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都得到通知并被自动更新。
举例:
Spring 事件驱动模型就是观察者模式很经典的一个应用。Spring 事件驱动模型非常有用,在很多场景都可以解耦我们的代码。比如我们每次添加商品的时候都需要重新更新商品索引,这个时候就可以利用观察者模式来解决这个问题
- 八、策略模式(Strategy)
-
定义:
策略模式:定义了一系列的算法,并将每一个算法封装起来,使它们可以互相替换。策略模式让算法独立于使用它的客户。
举例:
Spring 框架的资源访问 Resource 接口。该接口提供了更强的资源访问能力,Spring 框架本身大量使用了 Resource 接口来访问底层资源。
- 九、模板方法模式(Template Method)
-
定义:
模板方法模式:在一个方法中定义了一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。
举例:
Spring 中 JdbcTemplate、HibernateTemplate 等以 Template 结尾的对接入内容进行操作的类,它们就使用到了模板方法。一般情况下,我们都是使用继承的方法来实现模板模式,但是 Spring 并没有使用这种方式,而是使用 Callback 模板与模板方法模式配合,既达到了代码复用的效果,同时增加了灵活性。
12.说说你都知道哪些设计模式?最常用的有哪些?总共有几种设计模式?
23种设计模式 - 知乎
13.redis可以储存哪几种数据类型?你的项目里都用redis存储哪些数据
-
字符串类型(String):这是Redis中最基础的数据类型,可以存储任何形式的字符串,包括文本、序列化后的对象、二进制数据等。字符串类型的Value最多可以容纳512MB的数据。
-
哈希类型(Hash):哈希类型可以看作是一个键值对的集合,适合存储对象的信息。例如,一个用户的信息可以存储为哈希类型,包含姓名、密码、年龄等字段。
-
列表类型(List):列表类型是一个简单的字符串列表,可以存储一系列的字符串元素。它支持按照插入顺序排序的字符串列表,常用于实现队列和栈等数据结构。
-
集合类型(Set):集合类型是一个无序的字符串集合,支持添加、删除和判断元素是否存在等操作。集合中的元素是唯一的,常用于实现交集、并集和差集等操作。
-
有序集合类型(Zset):有序集合类型是一个集合,其中的每个成员都关联一个双精度浮点数分数,这使得它既可以像集合一样存储不重复的元素,又可以像排序的列表一样返回有序的数据。常用于排行榜等场景
- 字符串类型:常用于缓存数据库查询结果、实现计数器功能(如网站访问量计数)等。
- 哈希类型:适合存储对象信息,如用户信息、商品信息等。
- 列表类型:常用于实现消息队列、任务队列等功能。
- 集合类型:适用于需要去重的数据存储,如好友关系、标签等。
- 有序集合类型:适用于需要排序的数据存储,如排行榜、评分系统等
14.redis有哪些常用操作?(命令)
键操作
-
SET key value
设置键key
的值为value
。 -
GET key
获取键key
的值。 -
DEL key
删除键key
。 -
EXISTS key
判断键key
是否存在,存在返回 1,不存在返回 0。 -
EXPIRE key seconds
设置键key
的过期时间为seconds
秒。 -
TTL key
获取键key
的剩余过期时间(单位秒)。 -
RENAMENX oldkey newkey
重命名键oldkey
为newkey
,如果newkey
已存在,重命名失败。 -
TYPE key
获取键key
的数据类型。 -
FLUSHDB
删除当前数据库的所有键。 -
FLUSHALL
删除所有数据库的所有键
Pub/Sub(发布/订阅)
Redis 支持发布/订阅模型,用于实时消息传递。
-
PUBLISH channel message
向频道channel
发布消息message
。 -
SUBSCRIBE channel [channel ...]
订阅一个或多个频道。 -
UNSUBSCRIBE [channel ...]
取消订阅一个或多个频道。 -
PSUBSCRIBE pattern [pattern ...]
订阅一个或多个模式匹配的频道。 -
PUNSUBSCRIBE [pattern ...]
取消订阅模式匹配的频道
字符串操作
哈希操作
列表操作
集合操作
有序集合操作
事务操作
其他常用命令
15.redis缓存和数据库怎么保持一致?
1. 读写策略调整
先更新数据库,再更新缓存
原理:这种策略在更新数据时,先对数据库执行更新操作,然后立即更新缓存中的数据。这样可以尽量保证缓存和数据库的数据同步。
缺点:存在一定的风险。如果更新缓存失败,就会导致数据库和缓存数据不一致。而且,在高并发场景下,可能会出现多个线程同时更新缓存,导致缓存数据的更新顺序混乱,进而引发数据不一致的情况。
先删除缓存,再更新数据库
原理:当需要更新数据时,先将缓存中的对应数据删除。这样,下一次读取数据时,由于缓存中没有数据,就会从数据库中读取最新的数据,并将其放入缓存。
缺点:在高并发场景下可能会出现问题。例如,一个线程 A 删除了缓存,但是还没来得及更新数据库,此时另一个线程 B 来读取数据,发现缓存为空,就会从数据库读取旧数据并放入缓存。然后线程 A 再更新数据库,这样就导致了缓存和数据库的数据不一致。
2. 使用缓存更新队列
原理:将缓存更新操作放入一个队列中,由专门的消费者线程来处理这些更新操作。当有数据需要更新时,先将更新消息发送到队列中,这样可以避免多个线程同时更新缓存导致的混乱。
优点:可以实现异步更新缓存,减少对数据库更新操作的直接依赖,提高系统的并发性能。并且能够保证缓存更新的顺序,降低数据不一致的风险。
缺点:增加了系统的复杂性,需要维护一个消息队列和相应的消费者线程。而且如果队列处理不及时,可能会导致缓存更新延迟。
3. 设置合理的缓存过期时间
原理:为缓存中的数据设置过期时间,当缓存过期后,下一次读取数据时就会从数据库中重新获取最新的数据,从而更新缓存。通过合理设置过期时间,可以在一定程度上减少数据不一致的情况。
举例:对于一些数据更新频率较低的业务场景,如电商系统中的商品分类信息,可以设置较长的缓存过期时间,比如一天。而对于更新频率较高的数据,如商品的库存信息,可以设置较短的缓存过期时间,如几分钟。
缺点:如果过期时间设置过长,可能会导致数据不一致的时间过长;如果过期时间设置过短,会频繁地从数据库读取数据,增加数据库的压力。
4. 采用数据库的事务机制与缓存操作结合
原理:在更新数据库的操作中加入事务机制,确保数据库更新成功后再进行缓存操作。如果缓存操作失败,可以通过事务回滚来保证数据的一致性。
示例:在关系型数据库(如 MySQL)中,可以使用存储过程来包裹数据库更新和缓存更新操作。在存储过程中,开启一个事务,先更新数据库表,然后更新缓存。如果缓存更新失败,就回滚事务,从而保证数据库和缓存数据的一致性。
缺点:这种方法会增加数据库的负担,并且需要对业务逻辑进行比较复杂的改造,以适应事务和缓存操作的结合。
5. 读取时对比缓存和数据库数据
原理:在读取数据时,同时获取缓存中的数据和数据库中的数据,然后进行对比。如果发现数据不一致,就以数据库数据为准更新缓存。
缺点:这种方法会增加读取数据的时间成本,因为每次读取都需要从数据库获取数据进行对比。而且在高并发情况下,频繁地对比和更新缓存可能会导致性能下降。
16.redis可以持久化么?如何持久化?
Redis的三种持久化方法详解_redis appendfsync-CSDN博客
17.redis分布式锁怎么使用?
Redis实现分布式锁的7种方案,及正确使用姿势!_redis锁使用-CSDN博客
18.redis缓存数据丢失怎么使用?
Redis如何解决数据丢失问题_redis 数据丢失-CSDN博客
19.redis如果崩溃了如何快速恢复?
Redis宕机了,如何恢复数据_redis宕机数据如何恢复-CSDN博客
20.redis是如何部署的?是单个部署还是集群部署?为什么这么做?
单机模式
新手入门模式。单机模式意味着 Redis 是单点的,部署在一台服务器,挂了就挂了,用在本地测试还可以,但是生产环境就算了。
优势
- 部署简单
- 省钱,一台服务器就可以了
- 不涉及主从复制等,数据强一致
劣势
- 单点意味着稳定性基本上为 0,挂了就挂了
- 吞吐量受限于单机资源
主从模式
当流量越来越大,单台机器资源不能无限增长,就需要水平扩展到多个节点,使用多个节点分散承接读流量。
主从模式为主节点承接写流量,从节点承接读流量,二者数据一致通过主节点异步复制(全量复制 / 增量复制)到从节点。
优势
- 读流量被分摊到多个节点上,读流量支持力度变大
- 当主节点宕机/不可用时,可以手动切换主节点继续提供服务
劣势
- 当主节点宕机/不可用时手动切换节点,切换过程中 redis (主节点)不可用,并且会丢失主节点 / 从节点之间未同步的数据
- 稳定性还是不够,依赖手动切换。不适用于生产。
- 写流量还是让主节点独自承受,写流量还是靠单机资源支撑
哨兵模式
哨兵模式主要解决主从模式中手动切换的部分,本质上哨兵代替了人,通过 gossip 协议监控主节点的健康情况。
优势
- 不用手动切换主节点了,切换过程中虽然 redis 也是不可用的,但是这个时间会极大的降低
劣势
- sentinel 与主节点多了一层心跳检测,有可能 sentinel 与主节点的网络抖动导致重新选举主节点。
- redis 主从节点吞吐因心跳检测可能稍微降低。
集群模式
集群模式主要解决了两个问题:写流量水平扩展 & 哨兵与主节点的网络抖动。
集群模式主要的架构为:主节点平分 16384 个槽,集群支持主节点的动态上线/下线(需要 rehash),主节点与从节点通过心跳关联,主节点失联后从节点有权发起选举成为主节点(raft 算法)。
优势
- 自管理集群内主从节点上下线,减少因外部集群网络抖动之类的发起的无效选举
- 数据按照 slot 存放在多个节点,客户端通过服务端主节点的重定向跳转到具体的槽,可动态调整数据分布
- 减少了集群整体不可用的概率,某一主节点宕机只影响一部分数据的访问
- 写流量 & 数据平分到多个节点,集群的写请求瓶颈得到缓解
劣势
- 集群间状态同步使用 gossip 协议,节点数较多存在较多的心跳网络流量
- 主节点的上线/下线需要进行 rehash ,当节点内数据较多耗时较长
redis 节点间复制有两种:全量复制 & 部分复制
全量复制
出现场景
- 从节点刚上线需要同步主节点的数据
- 从节点切换脑裂后从节点偏移量与主节点不一致的时间点
- 从节点偏移量不在主节点的复制缓冲区中
过程
- 从节点向主节点发起同步数据的请求
- 主节点通过 bgsave 形成当前数据的快照,发给从节点
- 从节点删除历史数据,加载主节点发过来 RDB 文件
- 从节点拉取主节点缓冲区数据,加载到自身的内存中,并更新当前的偏移量
部分复制
出现场景
- 全量复制出现场景之外的场景
- 主从日常复制
过程
- 主节点将命令同步到缓冲区(AOF)
- 从节点拉取缓冲区数据,更新到自身的节点中,并更新当前的偏移量
21.什么是缓存穿透、缓存击穿、缓存雪崩?如何解决?
一、缓存穿透
1、缓存穿透理解 缓存穿透是指查询一个根本不存在的数据,缓存层和持久层都不会命中。在日常工作中出于容错的考虑,如果从持久层查不到数据则不写入缓存层,缓存穿透将导致不存在的数据每次请求都要到持久层去查询,失去了缓存保护后端持久的意义。
缓存穿透示意图:
缓存穿透问题可能会使后端存储负载加大,由于很多后端持久层不具备高并发性,甚至可能造成后端存储宕机。通常可以在程序中统计总调用数、缓存层命中数、如果同一个Key的缓存命中率很低,可能就是出现了缓存穿透问题。 造成缓存穿透的基本原因有两个。第一,自身业务代码或者数据出现问题(例如:set 和 get 的key不一致),第二,一些恶意攻击、爬虫等造成大量空命中(爬取线上商城商品数据,超大循环递增商品的ID)
2、解决方案
2.1 缓存空对象
缓存空对象:是指在持久层没有命中的情况下,对key进行set (key,null)
缓存空对象会有两个问题:
第一,value为null 不代表不占用内存空间,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间,比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。
第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。
2.2 布隆过滤器拦截
在访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来,做第一层拦截,当收到一个对key请求时先用布隆过滤器验证是key否存在,如果存在在进入缓存层、存储层。可以使用bitmap做布隆过滤器。这种方法适用于数据命中不高、数据相对固定、实时性低的应用场景,代码维护较为复杂,但是缓存空间占用少。
布隆过滤器实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。
它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
布隆过滤器拦截的算法描述:
初始状态时,BloomFilter是一个长度为m的位数组,每一位都置为0。
添加元素x时,x使用k个hash函数得到k个hash值,对m取余,对应的bit位设置为1。
判断y是否属于这个集合,对y使用k个哈希函数得到k个哈希值,对m取余,所有对应的位置都是1,则认为y属于该集合(哈希冲突,可能存在误判),否则就认为y不属于该集合。可以通过增加哈希函数和增加二进制位数组的长度来降低错报率。
3、两种方案的对比
二:缓存击穿
1、缓存击穿的理解
系统中存在以下两个问题时需要引起注意:
当前key是一个热点key(例如一个秒杀活动),并发量非常大。 重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的SQL、多次IO、多个依赖等。 在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。
2、解决方案
2.1 分布式互斥锁 只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。set(key,value,timeout)
2. 永不过期 从缓存层面来看,确实没有设置过期时间,所以不会出现热点key过期后产生的问题,也就是“物理”不过期。 从功能层面来看,为每个value设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去更新缓
3、两种方案对比:
分布式互斥锁:这种方案思路比较简单,但是存在一定的隐患,如果在查询数据库 + 和 重建缓存(key失效后进行了大量的计算)时间过长,也可能会存在死锁和线程池阻塞的风险,高并发情景下吞吐量会大大降低!但是这种方法能够较好地降低后端存储负载,并在一致性上做得比较好。
永远不过期:这种方案由于没有设置真正的过期时间,实际上已经不存在热点key产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增大。
三:缓存雪崩 1、概念理解
如果缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩。
这个没有完美解决办法,但可以分析用户行为,尽量让失效时间点均匀分布。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。
2、解决方案
2.1 缓存层高可用: 可以把缓存层设计成高可用的,即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务。利用sentinel或cluster实现。 2.2 做二级缓存,或者双缓存策略: 采用多级缓存,本地进程作为一级缓存,redis作为二级缓存,不同级别的缓存设置的超时时间不同,即使某级缓存过期了,也有其他级别缓存兜底 2.3 数据预热: 可以通过缓存reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀 2.4 加锁排队. 限流– 限流算法. 1.计数 2.滑动窗口 3. 令牌桶Token Bucket 4.漏桶 leaky bucket [1] 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。 业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。 SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。
22.List和set和map的区别?
1. 有序性
List:有序,维护元素的插入顺序,可以通过索引访问。
Set:无序(除 LinkedHashSet 保持插入顺序外),不允许重复元素。
Map:键无序(LinkedHashMap 保持插入顺序,TreeMap 按键排序),值无序。
2. 重复性
List:允许存储重复的元素。
Set:不允许存储重复的元素。
Map:键不允许重复,值可以重复。
3. 访问方式
List:按索引访问,通过 get(index) 方法。
Set:只能通过迭代器遍历,不能按索引访问。
Map:通过键访问相应的值,使用 get(key) 方法。
4. 存储结构
List:单一元素的集合,每个元素都有其位置(索引)。
Set:单一元素的集合,但保证元素的唯一性。
Map:键值对的集合,通过键访问对应的值。
五、常见使用场景举例
List:适用于需要按顺序存储和访问元素的场景,如学生名单、任务队列、订单列表等。
Set:适用于需要存储唯一元素的场景,如用户名列表、唯一标识符集合、无重复的集合运算等。
Map:适用于需要通过键快速查找对应值的场景,如字典、配置参数、映射表等。
23.arrarylist,linkedlist;arraylist内部扩容机制是怎样的?
ArrayList和LinkedList_linkedlist扩容机制-CSDN博客
24.hashmap扩容机制,HashMap的底层原理
(转载) HashMap底层原理 + 扩容机制_hashmap的扩容机制-CSDN博客
25.hashmap的底层用什么储存的?线程是否安全?
HashMap的底层数据结构是数组+链表+红黑树。HashMap使用哈希表来实现键值对的存储,当发生哈希冲突时,会使用链表来存储冲突的键值对。当链表的长度大于8时,会将链表转换为红黑树,以提高查询效率。
HashMap的线程安全性
HashMap本身是线程不安全的。当多个线程同时对HashMap进行操作时,可能会导致数据丢失或覆盖。例如,当一个线程正在进行插入操作时,另一个线程也可能进行插入操作,由于线程调度的原因,可能会导致第二个线程的操作覆盖第一个线程的操作,从而造成数据不一致。
26.final和finally和finalize的区别
27.String、StringgBuilder、StringBuffer的区别
了解String、StringBuilder与StringBuffer之间的区别(入门)-CSDN博客
28.重写equals已经能比较两个对象了,为什么还要重写hashcode方法
1、为了保证一个原则,equals相同的两个对象hashcode必须相同。如果重写了equals而没有重写hashcode,会出现equals相同hashcode不相同这个现象。
2、在散列集合中,是使用hashcode来计算key应存储在hash表的索引,如果重写了equals而没有重写hashcode,会出现两个完全相同的两个对象,hashcode不同,计算出的索引不同,那么这些集合就乱套了。
3、提高效率,当我们比较两个对象是否相同的时候,先比较hashcode是否相同,如果hashcode不相同肯定不是一个对象,如果hashcode不同再调用equals来进行比较,减少比较次数提高效率
【Java面试】为什么重写equals()方法,就一定要重写hashCode()方法?_哔哩哔哩_bilibili
29.基本数据和包装数据类型的区别?
1. 两者之间的区别
1.基本类型在初始化时有默认值,包装类在初始化时可以为空 (如:int a; Interge b; 都不进行赋值操作,a的值为0,b的值为 null )
2.基本类型存储在栈里,包装类型存储在堆里。因为栈的效率更高。
3.包装类是对象,拥有方法和字段,对象的调用是引用对象的地址。
4.基本类型是值传递,包装类是引用传递(即地址值的传递)。
5.向ArrayList,LinkedList中放数据的时候,只能放Object类型和包装类,基本类型放不进去。
6.包装类是不能被继承的,因为被final修饰
2.关于 == 比较
对于基本数据类型,==比较的是值,而对于包装类型,==比较的则是2个对象的内存地址。
2.1.当基本类型和包装类型进行比较时,包装类会进行拆箱并转换成基本类型,这时就是2个基本类型进行比较,结果为true
Integer a=150;
int b=150;
System.out.println(a==b); // true
2.2.当使用2个包装类型进行比较时,会出现4种情况
---第一种:当使用new Integer()新建变量时,2个包装类进行比较,结果为false
因为每次 new Integer()产生的变量都会在堆中新建,不管2个值是否相等都会新建,所以2个对象的地址值并不相同
Integer c=10;
Integer d=new Integer(10);
System.out.println(c==d); //false
Integer e =new Integer(150);
Integer f =new Integer(150);
System.out.println(e==f); //false
---第2种:当使用 Integer.valueOf() 时,会出4种情况,导致比较结果并不相同
因为当变量值在-128~127之间时,生成的Integer变量指向的是java常量池中的对象,而超出范围的对象会进行新建
(不同赋值方式 在-128~ 127 之间)
Integer a=100;
Integer b=Integer.valueOf(100);
System.out.println(a==b); //true
(相同赋值方式 在-128~ 127 之间)
Integer a=Integer.valueOf(110);
Integer b=Integer.valueOf(110);
System.out.println(a==b); // true
(不同赋值方式 不在范围之间)
Integer a= Integer.valueOf(150);
Integer b=150;
System.out.println(a==b); //false
(相同赋值方式 不在范围之间)
Integer a=Integer.valueOf(150);
Integer b=Integer.valueOf(150);
System.out.println(a==b); // false
---第3种:直接赋值操作时,超出 -128~127 时,也会出现不同的结果
(直接赋值 在-128~127之间)
Integer a=120;
Integer b=120;
System.out.println(a==b); // true
(直接赋值 不在-128~127之间)
Integer a=150;
Integer b=150;
System.out.println(a==b); // false
---第4种:除了使用 new Integer()新建变量,其他赋值方式只要在 -128~127之间,都会使用常量池中的变量,值比较结果为true, 超出范围 都会在堆中新建变量,结果为false
3.关于equals
2个基本类型无法比较,无equals方法
两个包装类比较时,先比较的是类型,如果类型相同,在比较具体的值
包装类和基本类型比较时,会将基本类型装箱,在比较类型,在比较具体的值
4.类型转换
类型转换有一个关键点:向上/向下转型
包装类 :无法向下转型,只能向上
基本类型:可以向上转型,向下转型必须使用强制类型转换,会有异常风险
30.什么是多态?举个例子
Java三大特性之一——多态(详细版)-CSDN博客
31.抽象类和接口的区别
抽象类是半抽象的,有构造方法,抽象类可以包含抽象方法和非抽象方法,也可以有成员变量(不仅仅是常量)
类和类之间只能单继承,一个抽象类只能继承一个类(单继承)
接口是完全抽象的,没有构造方法,接口和接口之间支持多继承,一个类可以同时实现多个接口
这在Java 8之前基本正确,但在Java 8及之后,接口可以包含默认方法和静态方法
32.java中的几种基本类型,各占用多少字节?
33.运行时异常有哪些?
Java常见的运行时异常以及解决方案
运行时异常在Java中是RuntimeException
及其子类的实例,它们通常是由程序逻辑错误引起的,而不是外部错误。以下是一些常见的运行时异常,代码示例,以及相应的解决方案:
01 NullPointerException
1.1 异常描述
当对null
引用执行非空操作时抛出。
1.2 代码示例
代码语言:javascript
复制
String text = null;
int length = text.length(); // 这里会抛出NullPointerException
1.3 解决方案
在操作之前检查引用是否为null
。
代码语言:javascript
复制
if (text != null) {int length = text.length();
}
02 IndexOutOfBoundsException
2.1 异常描述
当访问数组或列表的非法索引(如负数或超出范围的索引)时抛出。
2.2 代码示例
代码语言:javascript
复制
List<String> list = new ArrayList<>();
list.get(0); // 这里会抛出IndexOutOfBoundsException,因为列表为空
2.3 解决方案
确保索引在有效范围内
代码语言:javascript
复制
if (!list.isEmpty()) {String firstElement = list.get(0);
}
03 IllegalArgumentException
3.1 异常描述
当方法接收到不合法的参数值时抛出
3.2 代码示例
代码语言:javascript
复制
int result = Math.sqrt(-1); // 这里会抛出IllegalArgumentException,因为负数不能开平方
3.3 解决方案
检查参数是否符合预期的条件。
代码语言:javascript
复制
if (value >= 0) {double result = Math.sqrt(value);
}
04 IllegalStateException
4.1 异常描述
当对象处于不合法的状态时抛出,通常用于控制对象的状态。
4.2 代码示例
代码语言:javascript
复制
try (InputStream input = new FileInputStream("file.txt")) {// ... 一些操作
} // 这里会抛出IllegalStateException,如果文件不存在
4.3 解决方案
确保对象在使用前处于合法状态。
代码语言:javascript
复制
File file = new File("file.txt");
if (file.exists() && !file.isDirectory()) {try (InputStream input = new FileInputStream(file)) {// ... 一些操作}
}
05 ArithmeticException
5.1 异常描述
当发生算术异常,如除以零时抛出。
5.2 代码示例
代码语言:javascript
复制
int divisor = 0;
int quotient = 10 / divisor; // 这里会抛出ArithmeticException
5.3 解决方案
避免除以零或处理除数为零的情况。
代码语言:javascript
复制
if (divisor != 0) {int quotient = 10 / divisor;
}
06 NumberFormatException
6.1 异常描述
当尝试将字符串转换为数字,但字符串不符合数字格式时抛出。
6.2 代码示例
代码语言:javascript
复制
String number = "abc";
int num = Integer.parseInt(number); // 这里会抛出NumberFormatException
6.3 解决方案
确保字符串是有效的数字格式。
代码语言:javascript
复制
String number = "123";
try {int num = Integer.parseInt(number);
} catch (NumberFormatException e) {// 处理无效的数字格式
}
07 ClassCastException
7.1 异常描述
当尝试对对象进行不正确的类型转换时抛出。
7.2 代码示例
代码语言:javascript
复制
Object obj = new Object();
String str = (String) obj; // 这里会抛出ClassCastException
7.3 解决方案
确保类型转换是合法的。
代码语言:javascript
复制
if (obj instanceof String) {String str = (String) obj;
}
08 NoSuchElementException
8.1 异常描述
当从迭代器或枚举中尝试获取下一个元素,但已经没有更多元素时抛出。
8.2 代码示例
代码语言:javascript
复制
List<String> list = new ArrayList<>();
Iterator<String> iterator = list.iterator();
String next = iterator.next(); // 这里会抛出NoSuchElementException
8.3 解决方案
在调用next()
之前检查迭代器是否有更多的元素。
代码语言:javascript
复制
if (iterator.hasNext()) {String next = iterator.next();
}
09 SecurityException
9.1 异常描述
当程序试图执行安全策略不允许的操作时抛出
SecurityException
是Java中的一个运行时异常,它表示程序试图执行一个安全策略不允许的操作。这种异常通常与安全相关的操作有关,比如访问系统资源或者执行一些需要特权的操作。
9.2 代码示例
代码语言:javascript
复制
public class SecurityExceptionExample {public static void main(String[] args) {try {// 尝试打开一个文件,但没有足够的权限File file = new File("/path/to/protected/file.txt");FileReader fileReader = new FileReader(file);// ... 其他操作fileReader.close();} catch (SecurityException e) {// 捕获到SecurityException异常System.out.println("SecurityException caught: " + e.getMessage());}}
}
在这个示例中,我们尝试使用FileReader
类打开一个受保护的文件。如果当前用户没有足够的权限去读取这个文件,就会抛出SecurityException
。在捕获到这个异常后,我们可以记录日志或者通知用户他们没有执行该操作的权限。
需要注意的是,SecurityException
通常是由JVM抛出的,而不是由程序员在代码中显式抛出。此外,某些安全策略可能由Java安全管理层(如Java Security Manager)控制,如果启用了安全管理器,那么任何违反安全策略的操作都可能导致SecurityException
。
在实际开发中,应该确保程序有足够的权限执行它需要的操作,并且在用户没有相应权限时提供适当的错误处理和提示信息。
9.3 解决方案
通常需要修改程序的安全性策略或避免执行不允许的操作。
10 UnsupportedOperationException
10.1 异常描述
当尝试执行不支持的操作时抛出,如在不可变的集合上执行添加或删除操作。
UnsupportedOperationException
是 Java 中的一个运行时异常,它表明某个操作不支持在特定的对象上执行。这通常发生在尝试对不可变对象进行修改,或者在不支持某种操作的集合类型上执行该操作时。
10.2 代码示例
代码语言:javascript
复制
import java.util.Collections;
import java.util.List;public class UnsupportedOperationExceptionExample {public static void main(String[] args) {// 创建一个不可修改的列表List<String> immutableList = Collections.unmodifiableList(List.of("Apple", "Banana", "Cherry"));try {// 尝试修改列表内容,将会抛出 UnsupportedOperationExceptionimmutableList.add("Date");} catch (UnsupportedOperationException e) {// 捕获到 UnsupportedOperationException 异常System.out.println("Caught exception: " + e.getMessage());}}
}
在这个示例中,我们首先使用 Collections.unmodifiableList
方法创建了一个不可修改的列表 immutableList
。当我们尝试使用 add
方法向这个列表中添加新元素时,会抛出 UnsupportedOperationException
,因为 immutableList
是不可变的,不支持添加或删除操作。
为了处理这种异常,我们可以在执行可能会抛出 UnsupportedOperationException
的代码块中使用 try-catch
语句。在 catch
块中,我们可以添加错误处理逻辑,比如记录日志、通知用户或者执行其他恢复操作。
在实际编程中,如果你正在使用的是一个不可变对象或者一个不支持某些操作的集合,那么你需要确保不执行这些不支持的操作,或者在执行前进行检查以避免抛出 UnsupportedOperationException
。同时,如果你自己实现了一个集合类型,并且某些操作对于你的集合来说没有意义,你可以选择抛出这个异常来告知调用者这一点。
10.3 解决方案
使用支持所需操作的合适数据结构或方法。 处理运行时异常的关键是理解为什么会抛出异常,并在代码中采取适当的预防措施。这通常涉及到对输入的验证、对对象状态的管理以及对异常情况的适当处理。通过这些方法,可以提高程序的健壮性和可靠性。
34.对面向对象的理解?面向对象有的特征有哪些?
如何理解面向对象(什么是面向对象?)_面向对象的理解-CSDN博客
35.HTTP报文有哪几部分组成?Get/Post有什么区别
GET 和 POST 的区别_get post-CSDN博客
36.转发(forward)和重定向(redirect)的区别
1、地址栏变化
转发(Forward):地址栏URL不变,客户端不知内部跳转细节。
重定向(Redirect):地址栏URL更新为新URL,客户端发起新的HTTP请求。
2、数据共享
转发(Forward):转发前后页面共享同一个request对象,可轻松传递数据。
重定向(Redirect):因是两个独立请求,需通过URL参数、Session或Cookie等方式传递数据。
3、运用场景
转发(Forward):适用于内部页面跳转,保持用户登录状态或传递敏感信息。
重定向(Redirect):常用于改变浏览器地址栏URL,如登录后跳转、注销后返回首页,或防止表单重复提交。
4、效率对比
转发(Forward):服务器内部处理,仅一次响应,效率较高。
重定向(Redirect):涉及两次服务器到客户端的响应过程,效率相对较低。
5、总结
选择转发或重定向应根据具体需求而定。转发适用于内部逻辑跳转和敏感信息传递,而重定向则更适合用于URL改变和防止表单重复提交的场景。
37.什么是AOP?什么是IOC?
一、什么是IOC?
1、IoC (Inversion of control )就是控制反转/反转控制。它是一种思想不是一个技术实现。主要就是用来解决创建和管理对象的
2、就是不需要通过new 关键字来创建对象,而是通过 IoC 容器(Spring 框架) 来帮助我们实例化对象。我们需要哪个对象,直接从 IoC 容器里面过去即可
3、可以让对象之间的耦合度和依赖程度降低
4、使用 IoC 的思想,我们将对象的控制权(创建、管理)交有 IoC 容器去管理,我们在使用的时候直接向 IoC 容器要就可以了
二、什么是 AOP
1、AOP:Aspect oriented programming 面向切面编程,AOP 是 OOP(面向对象编程)的一种延续。
2、比如在父类 Animal 中的多个方法的相同位置出现了重复的代码,OOP 就解决不了,只能通过AOP来解决
3、AOP 主要用来解决:在不改变原有业务逻辑的情况下,增强横切逻辑代码,从根本上解耦合,避免横切逻辑代码重复
三、JAVA中实现AOP的方式有哪些?
1、第一种是通过原生jdk实现方式,首先要定义接口,实现一个切面代理,原生的AOP需要实现InvocationHandler接口,才能实现AOP
2、第一种是通过cglib切面来实现,相同点是都使用了代理模式,不同点是前者使用了接口代理,后者使用了继承代理,什么意思呢?
四、AOP代理方式有哪些?
1、AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP为代表
2、使用AspectJ的编译时增强,实现AOP,AspectJ是静态代理的增强。所谓的静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强。
3、Spring AOP使用的是动态代理。所谓的动态代理,就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
4、Spring AOP中的动态代理,主要有两种方式:
JDK动态代理和CGLIB动态代理。
1)JDK动态代理通过“反射”来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。
2)CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态地生成某个类的子类。注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
五、IOC控制反转的其他解释
IoC(控制反转)就是将程序中原来 new 对象,交给spring创建,从spring工厂获取对象,使用spring来负责控制对象的生命周期和对象间的关系
Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。
六、AOP面向切面编程的相关术语
Joinpoint(连接点):所谓连接点是指那些被拦截到的点,在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点.
Pointcut(切入点):所谓的切入点是指我们要对哪些Joinpoint进行拦截的定义
Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知,通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的事情)。
Introduction(引介):引介是一种特殊的通知,在不修改类代码的前提下,Introduction可以在运行期为类动态的添加一些方法或Field。
Target代理的目标对象。
Weaving(织rget(目标):入):是指把增强应用到目标对象来创建的代理对象的过程,Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
Aspect(切面):是切入点和通知(引介)的结合。
38.AOP用到什么设计模式
在 AOP 中,代理、装饰器和拦截器等设计模式经常被使用,下面分别介绍它们在 AOP 中的应用:
- 代理模式:代理模式在 AOP 中的应用较为广泛。在 AOP 中,代理模式通常用于实现动态代理,即在程序运行时生成代理对象,代理对象包含了目标对象的所有方法,并且可以在方法执行前后插入额外的逻辑,如日志记录、性能监控、事务管理等。
- 装饰器模式:装饰器模式是一种结构型设计模式,它可以动态地为一个对象添加新的行为,而不需要对原始类进行修改。在 AOP 中,装饰器模式通常用于实现 Aspect,即将一组通用的横切关注点包装在一个 Aspect 中,然后将 Aspect 应用到目标对象中。
- 拦截器模式:拦截器模式是一种结构型设计模式,它可以在不修改原始类的情况下,通过拦截并修改方法的调用,来实现额外的逻辑。在 AOP 中,拦截器模式通常用于实现 Advice,即在特定的 Join Point 上执行特定的操作,例如在方法执行前后记录日志、验证权限、处理异常等
使用场景
- 日志记录:在程序执行期间,记录关键操作的执行情况、错误信息等。
- 安全性检查:在程序执行期间,检查用户的权限或身份验证,以确保只有授权的用户才能执行特定的操作。
- 事务管理:确保多个操作在一个事务中执行,以确保数据的完整性和一致性。
- 性能监控:监控程序执行的性能,包括执行时间、内存使用等指标,以便进行优化。
- 异常处理:在程序执行期间捕获和处理异常,以避免程序崩溃或出现未知错误。
- 缓存管理:管理程序中的缓存,包括缓存的存储、刷新和删除等操作。
- 国际化:管理程序中的国际化资源,以便程序能够在不同的语言环境中正确地运行
39.SpringBean的生命周期
Spring容器系列-bean的生命周期 - 欢乐豆123 - 博客园
40.SpringIOC是怎么实现了依赖注入,有几种依赖注入方式?
Spring IoC——依赖注入_ioc依赖注入-CSDN博客
41.SpringMVC的执行流程
SpringMVC 执行流程整体如下:
执行流程分析
(1)浏览器提交请求到中央调度器。 (2)中央调度器直接将请求转给处理器映射器。 (3)处理器映射器会根据请求,找到处理该请求的处理器,并将其封装为处理器执行链后返回给中央调度器。 (4)中央调度器根据处理器执行链中的处理器,找到能够执行该处理器的处理器适配器。 (5)处理器适配器调用执行处理器。 (6)处理器将处理结果及要跳转的视图封装到一个对象 ModelAndView 中,并将其返回给处理器适配器。 (7)处理器适配器直接将结果返回给中央调度器。 (8)中央调度器调用视图解析器,将 ModelAndView 中的视图名称封装为视图对象。 (9)视图解析器将封装了的视图对象返回给中央调度器。 (10)中央调度器调用视图对象,让其自己进行渲染,即进行数据填充,形成响应对象。 (11)中央调度器响应浏览器。
执行流程中的 API 简要说明
1. DispatcherServlet
中央调度器,也称为前端控制器,在 MVC 架构模式中充当控制器 C, DispatcherServlet 是整个流程的控制中心,由它调用诸如处理器映射器、处理器适配器、视图解析器等其它组件处理用户请求。 中央调度器的存在降低了组件之间的耦合度。
2. HandlerMapping
处理器映射器, 负责根据用户请求找到相应的将要执行的 Handler,即处理器。 即用于完成将用户请求映射为要处理该请求的处理器,并将处理器封装为处理器执行链传给中央调度器。
3. HandlAdapter
处理器适配器, 通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。 中央调度器会根据不同的处理器自动为处理器选择适配器,以执行处理器。
4. Handler
处理器,也称为后端控制器,在 DispatcherServlet 的控制下 Handler 调用 Service 层对具体的用户请求进行处理。由于 Handler 涉及到具体的用户业务请求,所以一般情况下需要程序员根据业务需求自己开发 Handler。
5. ViewResolver
视图解析器, 负责将处理结果生成 View 视图, ViewResolver 首先将逻辑视图名解析为物理视图名,即具体的页面地址,再生成 View 视图对象。最后将处理结果通过页面形式展示给用户。 SpringMVC 框架提供了很多的 View 视图类型,包括: JstlView、 RedirectView 等。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。
42.SpringMVC与springboot的区别?
43.SpringMVC的事物有过哪几种?怎么处理的?
44.SpringBoot在初始化以前执行一个方法,应该怎么做?初始化以后在执行一个方法应该怎么做,有哪几种方法去做
45.在项目中会经常用到哪些注解?讲出十个
46.@Autowired注入和自己new一个对象有什么区别?
47.myBaties防sql注入的操作
48.Mybaties都要用到哪些标签
49.#和$的区别
#原样输出
$占位符