Java面试汇总
Java面试汇总
项目问题
数联网交换机
Netty核心面试题20连问,由浅入深 助你轻松吊打面试官_netty面试题-CSDN博客
Tomcat与Netty比较_netty和tomcat-CSDN博客
programmer club
后端微服务-programmer-club项目的设计与实现心得 | Roger-Lv’s space
项目介绍
大家在求职的过程中,免不了要进行面试题的学习,网上的pdf 很多,大家一般都是基于此来进行背诵或者收集,重复的题目及答案的质量参差不齐,这个个人项目,做一个线上化的面试题网站,来进行资源整合。学习面试的同时,将所学习的技术结合到一起。我们采用的是主流的微服务架构 alibaba,配合主流的中间件,前端主要是以 react 配合 antdesiqn 来进行,以C端为主的一个网站形式。叫他programmer_club,整体为一个社区的形式,主要实现的功能有刷题,练题,交流群,模拟面试。我在这里面设计技术选型,架构设计,功能的设计及落地。其中刷题模块、登录注册鉴权等模块以及优化等是我来进行主要落地实现的。
使用DDD(领域驱动设计)的原因主要有以下几点:
- 提高可维护性
- DDD通过将复杂的业务场景划分为简单的领域,有利于代码的阅读和维护。这有助于开发者更好地组织代码,降低系统复杂度。
- 通过明确领域边界和概念,DDD使得每个领域的代码更加内聚,降低了领域之间的耦合,提高了代码的可维护性。
- 提高可扩展性:
- DDD强调业务领域的划分和建模,领域之间的解耦降低了系统间的合度,有利于未来的功能扩展和新技术引入。
- 清晰的领域模型设计可以更容易地支持新功能的添加和旧功能的修改,而不会影响到其他不相关的领域。
- 提高灵活性:
- 基于领域模型的设计使得系统能够更快地响应业务变化,提高适应性。
- 当业务需求发生变化时,DDD的灵活性允许开发者通过修改或扩展领域模型来应对这些变化,而不需要对整个系统进行大规模的重构。
- 促进团队沟通:
- 通过清晰地定义领域边界,DDD有助于团队之间的交流和协作。每个团队成员都可以更加专注于自己所在的领域,减少跨领域沟通的成本。
- 清晰的领域模型还可以作为团队成员之间的共同语言,促进对业务需求和系统设计的深入讨论和理解。
- 支持复杂业务场景:
- 对于涉及多个业务领域目业务逻辑复杂的项目,DDD能够显著提高系统的可维护性、可扩展性和灵活性。它可以帮助开发者更好地理解和把握业务需求,实现高质量的软件开发。
- 使用业务变化:
- 在业务领域变化频繁的情况下,DDD能够方便地进行模型的调整和优化,以适应不断变化的业务需求。这有助于降低因业务变动导致的开发成本,提高系统的适应能力。
- 支持团队协作:
- 在大型项目中,DDD有助于明确各团队的职责范围,提高团队之间的沟通协作效率。通过将系统划分为不同的领域,每个团队可以专注于自己所在领域的开发,减少了跨团队的依赖和冲突。
DDD通过强调业务领域的划分和建模,提高了软件系统的可维护性、可扩展性和灵活性。这使得DDD成为应对复杂业务场景、团队协作和业务变化的有效方法论。然而,需要注意的是,DDD对开发者经验和领域知识要求较高,开发者需要避免过度设计和降低学习成本。
是怎么在这个项目里面使用satoken的?
这个问题从两个方面来回答的吧:
- 一方面是技术选型涉及的成本,大家都知道权限是每个项目都绕不开的,如何快速的接入,并且具备较好的扩展性,功能完备,使我们最开始需要考虑的一个东西,Sa-Token十分轻量,功能齐全,学习成本低,可以快速接入。
- 另一方面就是复杂性考虑: Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证、权限认证、单点登录、OAuth2.0、分布式Session会话、微服务网关鉴权 等一系列权限相关问题,正好符合我们的微服务分布式项目的场景,无需实现任何接口,无需创建任何配置文件,只需要一句静态代码的调用,便可以完成会话登录认证相比 Shiro、SpringSecurity 等框架的复杂,使用之后就知道 Sa-Token 的 API 设计是多么的好用,
说到 token 必须先说一下 cookie。传统的常规的一般是由 cookie 进行完成,Cookie 有两个特性:可由后端控制写入,每次请求自动提交。这就使得我们在前端代码中,无需任何特殊操作,就能完成鉴权的全部流程。但是在app、小程序等前后端分离场景中,一般是没有 Cookie 这一功能的。所以我们就引入 token 的这个概念。拆解出来主要是两步,一步是登录后,后端返回 token,另一部是前端请求带着 token,将 token 放到 header 里面。实现了 token 的传递之后,token 的生成过程,可以包含各种信息,比如用户的用户名,相关的权限,都可以包含在里面,这样一个 token 就可以帮助我们带来很多信息,鉴权等功能也就非常容易做了,同时还解决了cookie 问题。
session、cookie和token的区别_cookie,session,token的区别-CSDN博客
登录认证是怎么做的
如何对接公众号登录
对接公众号主要是希望用微信的唯一的 openld,来作为唯一的标识,也方便用户的登录。
- 整体流程主要是用户扫公众号码。然后发一条消息:验证码。
- 我们通过 api 回复一个随机的码,存入redis 的主要结构,key是一个前缀+validCode,value是 OpenId。
- 用户在验证码框输入之后,点击登录,进入我们的注册模块,同时关联角色和权限(数据库操作),就实现了网关的统一鉴权,用户就可以进行操作,用户可以根据个人的 openld 来维护个人信息。
- 用户登录成功之后,返回 token(satoken通过openId调用一个自带的方法登录,有一个自带的方法返回token),前端的所有请求都带着 token 就可以访问从服务涉及上,我们开一个新服务,专门用于对接某信的 api 和微信的消息的回调。
你是如何监听用户发给公众号的消息的?
主要是对接公众号的回调消息平台配置。重点主要分为三步:填写服务器配置,验证服务器地址的有效性,依据接口文档实现业务逻辑。
- 在公众号配置界面填写服务器地址(URL)、Token和EncodingAESKey。
- 其中URL是开发者用来接收微信消息和事件的接口URL。
- Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)。
- EncodingAESKey由开发者手动填写或随机生成,将用作消息体加解密密钥。
- 验证消息的确来自微信服务器:某信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数有签名,内容,时间戳之类的,后台服务要通过一样的加密形式来进行校验。
- 依据接口文档实现业务逻辑:验证URL有效性成功后即接入生效,用户每次向公众号发送消息,开发者填写的服务器配置URL将得到微信服务器推送过来的消息和事件,开发者可以依据自身业务逻辑进行响应,如回复消息。
- 在公众号配置界面填写服务器地址(URL)、Token和EncodingAESKey。
回调消息的验证校验是如何做的?
开发者通过检验signature对请求进行校验。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:
1)将token、timestamp、nonce三个参数进行字典序排序
2)将三个参数字符串拼接成一个字符串进行sha1加密
3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
gateway网关是怎么设计的
gateway 网关,作为我们项目的整个流量入口,目前主要实现了路由,负载,统一鉴权,全局过滤器,异常处理这些功能。路由和负载承载了后台微服务的 ur 转发和前缀匹配。统一鉴权主要是配合 satoken,在 gateway 集成 redis,同时实现 satoken 提供的权限读取接口,在其中自定义读取逻辑,实现鉴权的校验。在其中还实现了登录拦截器,用于传递 loginld 到微服务中,借助了 header 的传递。
分布式会话的鉴权在微服务中是怎么做的
分布式会话鉴权的重点主要是如何获取到权限数据,然后进行校验处理,一般其实有三种形式:
- 在网关处集成ORM框架,直接从数据库查询数据。
- 先从Redis中获取数据,获取不到时走ORM框架查询数据库。
- 从Redis中获取缓存数据。
我们采取的是第三种直接从redis 中获取缓存的权限数据,这有一个要求,就是我们的 redis 的高可用性必须要高。因为我们是内部使用,这块可以采取刷新等措施,来处理极端异常情况,例如权限缓存丢失。如果想要保障的话,可以采用第二种方式,也是常见的缓存没有查数据库。从角度来看,个人觉得我们这种场景下,再集成数据库 orm 导致网关有点重,所以我们直接与缓存做交互。
gateway如何实现全局异常处理
gateway 的全局异常处理主要需要我们实现一个接口ErrorWebExceptionHandler,实现其中的 handle 方法在方法内,我们能获取到其中request与response,webhandler 会帮助我们拦住所有异常的情况。然后我们可以在里面做拦截的进一步处理,更改状态码,状态错误信息等等。最后通过 response 可以将其返回出去。
设计模式
讲解java单例模式中的饿汉模式和懒汉模式的区别与使用_饿汉式和懒汉式用那个-CSDN博客
Java基础
ArrayList集合和LinkedList集合底层原理_linkedarraylist底层-CSDN博客
JVM、JRE、JDK的关系
JVM、JRE 和 JDK 是 Java 生态系统中的三个核心组件,它们在 Java 开发和运行时环境中扮演着不同的角色。
JVM (Java Virtual Machine)
Java 虚拟机是一种抽象计算机,它为 Java 程序提供了运行环境。JM 的主要职责是执行 Java 字节码,并将其转换为机器代码,以便在特定平台上运行。JVM 是实现 Java 平台无关性的关键组件。
- 功能:
- 字节码执行: JVM 负责加载、验证和执行Java 字节码。
- 内存管理: JVM 管理堆内存和栈内存,并执行垃圾回收(GarbageCollection)
- 安全性: JVM 提供了安全机制,确保Java 应用在受控环境中运行
JRE (Java Runtime Environment)
Java 运行时环境是一个软件包,它提供了运行 Java 应用程序所需的所有组件。JRE 包含 JVM 以及 Java 类库和其他支持文件。JRE 是运行 Java 应用程序的最低要求。
组成:
- JVM:JRE包含 JVM,用于执行 Java 字节码。
- 核心类库:JRE 包含 Java 标准类库(如java.lang、java.util等),这些类库为 Java 应用提供基础功能。
- 其他支持文件:包括配置文件、资源文件等。
JDK (Java Development Kit)
Java 开发工具包是为 Java 开发者提供的完整开发环境。JDK 包含 JRE 以及开发 Java 应用程序所需的工具和库。JDK是开发和编译 Java 程序的必备工具。
组成:
- JRE:JDK 包含一个完整的 JRE 环境。
- **开发工具:**包括编译器(javac)、调试器(jdb)、打包工具(jar)等,用于开发、编译和调试 Java 程序。
- **额外的库:**提供了额外的库和头文件,用于开发Java 应用程序。
关系:
- JVM 是JRE的一部分:JVM 是JRE中的核心组件,负责执行Java 字节码。
- JRE 是 JDK 的一部分:JRE提供了运行 Java 应用程序所需的环境。而JDK则在此基础上添加了开发工具和额外的库。
总结:
- JVM:执行 Java 字节码的虚拟机,提供内存管理和安全机制。
- JRE:包含 JVM 和核心类库,提供运行 Java 应用程序的环境。
- JDK:包含 JRE 和开发工具,提供开发、编译和调试 Java 应用程序的完整环境。
Java三大特性
Java大特性是面向对象编程(OOP)的核心概念:封装、继承和多态。这些特性使得 Java 程序具有良好的结构和可维护性。
封装(Encapsulation)
封装是将对象的状态(属性)和行为(方法)组合在一起,并对外隐藏对象的内部细节,只暴露必要的接口。通过封装,可以保护对象的状态不被外部直接修改,增强了代码的安全性和可维护性。
- 实现方式:
- 使用private关键字将属性声明为私有的。
- 提供public的 getter 和 setter 方法来访问和修改私有属性。
- 实现方式:
继承(Inheritance)
继承是面向对象编程中的一个机制,通过继承,一个类可以继承另一个类的属性和方法,从而实现代码的重用。被继承的类称为父类(超类),继承的类称为子类(派生类)。
- 实现方式:
- 使用extends关键字来声明一个类继承另一个类
- 实现方式:
多态(Polymorphism)
多态是指同一个方法在不同对象中具有不同的实现方式。多态性允许对象在不同的上下文中以不同的形式表现。多态可以通过方法重载(Overloading)和方法重写(Overriding)来实现。
- 方法重载:在同一个类中,方法名相同但参数列表不同。
- 方法重写:在子类中重新定义父类中的方法。
总结:
- 封装:通过将数据和方法封装在类中,并使用访问控制符来保护数据。
- 继承:通过继承机制,实现代码的重用和扩展。
- 多态:通过方法重载和方法重写,实现同一方法在不同对象中的不同表现。
什么是封装?
封装(Encapsulation)是面向对象编程(OOP)中的一个基本概念。它涉及到将对象的状态(属性)和行为(方法)封装在一个类中,并对外部隐藏内部实现细节,只暴露必要的接口。这种做法有助于提高代码的安全性、可维护性和可重用性。
属性私有化
将类的属性声明为私有(private),以防止外部直接访问和修改这些属性
提供公共的访问方法
通过公共(public)的 getter 和 setter 方法来控制对私有属性的访问和修改。这些方法允许外部代码在受控的情况下读取和修改属性值。
隐藏实现细节
封装还包括隐藏类内部的实现细节,只暴露必要的接口给外部使用者。这有助于降低代码的复杂性,提高模块化程度
封装的优点:
- **数据保护:**通过私有化属性,可以防止外部代码直接修改对象的状态,从而保护数据的完整性简化接口:只暴露必要的方法,隐藏不需要的实现细节,使得类的接口更加简洁明了
- **提高可维护性:**封装使得类的实现细节可以独立于外部代码进行修改,只要接口不变,外部代码不需要做任何改变。
- **增强灵活性: **通过 getter 和 setter 方法,可以在访问或修改属性时添加额外的逻辑,比如数据验证或事件触发。
什么是继承
继承(Inheritance)是面向对象编程(OOP)中的一个核心概念。它允许一个类(子类或派生类)继承另一个类(父类或超类)的属性和方法,从而实现代码的重用和扩展。通过继承,子类可以复用父类的代码,并且可以新增或重写(覆盖)父类的方法以实现特定的功能。
继承的基本概念:
- **父类(Super Class):**被继承的类,提供属性和方法。
- **子类(Sub Class):**继承父类的类,可以复用父类的代码,并且可以新增或重写父类的方法。
- **extends关键字:**用于声明一个类继承另一个类。
继承的特点:
- 单继承: Java 只支持单继承,即一个类只能有一个直接父类。
- **继承层次: **子类可以继续被其他类继承,形成继承层次结构。
- **super关键字: **用于引用父类的属性和方法,特别是在子类重写父类的方法时,可以通过super调用父类的方法。
继承的优点:
- 代码重用: 子类可以复用父类的代码,减少代码重复。
- 代码扩展: 子类可以在继承父类的基础上新增属性和方法,扩展功能。
- **多态性: **通过继承和方法重写,可以实现多态性,使得同一方法在不同对象中具有不同的实现。
继承中的一些注意事项
- **构造方法:**子类的构造方法会调用父类的构造方法。如果父类没有无参构造方法,子类必须显式调用父类的有1参构造方法。
- **方法重写(Overriding):**子类可以重写父类的方法,以提供特定的实现。重写的方法必须具有相同的方法2签名(方法名、参数列表和返回类型)。
- **super关键字:**用于调用父类的构造方法或父类的方法。例如,在子类的构造方法中使用super调用父类的构》造方法。
什么是多态?
多态(Polymorphism)是面向对象编程(OOP)中的一个核心概念,它允许相同的操作在不同的对象上表现出不同的行为。多态性使得一个接口可以有多种实现,从而提高代码的灵活性和可扩展性。
多态的类型
多态主要有两种形式:
- 编译时多态(静态多态)):通过方法重载(Method Overloading)实现:
- 运行时多态(动态多态):通过方法重写(Method Overriding)和接口实现(InterfaceImplementation)实现。
编译时多态(方法重载)
编译时多态是通过方法重载实现的,即同一个类中多个方法具有相同的名称,但参数列表不同。编译器在编译时根据方法的参数列表来决定调用哪个方法。
运行时多态(方法重写)
运行时多态是通过方法重写实现的,即子类重写父类的方法。在运行时,Java 虚拟机根据对象的实际类型调用对应的方法。
接口和抽象类的多态性
多态性还可以通过接口和抽象类实现。子类或实现类可以提供不同的实现,从而实现多态性。
多态的优点
- **代码重用:**通过多态性,可以使用同一个接口或父类来操作不同的对象,减少代码重复。
- **灵活性和可扩展性:**多态性使得代码更加灵活,可以轻松地扩展新的子类或实现类而不影响现有代码。
- **简化代码:**通过多态性,可以使用统一的接口来处理不同的对象,简化代码逻辑。
总结
多态是面向对象编程中的一个重要特性,它允许相同的操作在不同的对象上表现出不同的行为。通过方法重载和方法重写,以及接口和抽象类的实现,多态性提高了代码的灵活性、可扩展性和可维护性。
重载和重写
重载(Overoading)和重写(Overriding)是面向对象编程中两个重要的概念,它们在方法定义和调用时有不同的用途和规则。
重载(Overloading)
定义: 在同一个类中,方法名称相同,但参数列表(参数的类型、数量或顺序)不同的多个方法。
特性:
- **方法名称:**相同。
- **参数列表:**必须不同(参数的类型、数量、或顺序)。
- **返回类型:**可以相同也可以不同。
- **访问修饰符:**可以相同也可以不同。
- **静态/实例方法:**都可以重载。
- **编译时决定:**方法的选择在编译时由编译器根据参数列表决定。
重写(Overriding)
**定义:**在子类中定义一个方法,该方法与父类中的某个方法具有相同的方法名称、参数列表和返回类型,以便在子类中提供该方法的具体实现。
特性:
- 方法名称:相同。
- 参数列表:必须相同。
- 返回类型:必须相同(Java5及以后可以是协变返回类型,即返回类型可以是父类方法返回类型的子类型)。
- 访问修饰符:访问级别不能比父类方法更严格(可以更宽松)。
- 静态/实例方法:只能重写实例方法,不能重写静态方法。
- 运行时决定:方法的选择在运行时由JVM 根据对象的实际类型决定(动态绑定)。
总结
- 重载:
- 发生在同一个类中。
- 方法名称相同,参数列表不同。
- 编译时决定调用哪个方法(静态绑定)。
- 重写:
- 发生在子类和父类之间。
- 方法名称、参数列表和返回类型必须相同(或协变返回类型)。
- 运行时决定调用哪个方法(动态绑定)。
构造器能否被重写?
构造器不能被重写
**定义:**构造器不能被重写(Overridden)。重写是指在子类中提供一个与父类方法具有相同签名的方法,以便在子类中提供该方法的具体实现。但构造器不属于类的继承成员,因此不能被子类重写。
原因:
- **构造器的作用:**构造器的主要作用是初始化对象的状态。每个类都有自己的构造器,用于初始化该类的实例。子类不能直接继承父类的构造器,因为子类的初始化过程可能与父类不同。
- **方法签名不同:**重写要求方法签名(包括方法名称和参数列表)相同,而构造器在子类中的名称与父类不同2(构造器名称必须与类名相同)。
- **构造器不是类成员:**构造器不属于类的成员方法,它们是特殊的初始化方法,不参与继承机制。
构造器可以被重载:
虽然构造器不能被重写,但它们可以被重载(Overloaded)。构造器重载是指在同一个类中定义多个构造器,这些构造器具有相同的名称(类名),但参数列表不同。
结论:
- 构造器不能被重写:因为构造器不属于类的继承成员,并且它们的名称必须与类名相同。
- 构造器可以被重载:在同一个类中,可以定义多个构造器,只要它们的参数列表不同。
String、StringBuilder、StringBuffer的区别
String
特性:
- 不可变性:String对象是不可变的。一旦创建,字符串的内容就不能被改变。任何对字符串的修改都会生成一个新的String对象。
- 线程安全:由于String对象是不可变的,它们是线程安全的,可以在多个线程中安全地共享。
使用场景:
- 适用于字符串内容不会发生变化的场景,例如字符串常量、少量的字符串操作等,
StringBuilder
特性:
- 可变性:StringBuilder对象是可变的,可以对字符串内容进行修改,而不会生成新的对象。
- 非线程安全:StringBuilder不是线程安全的,它的方法没有同步,因此在多线程环境中使用时需要额外注意。
使用场景:
- 适用于在单线程环境中需要频繁修改字符串内容的场景,性能比StringBuffer更高。
StringBuffer
特性:
- 可变性: StringBuffer对象是可变的,可以对字符串内容进行修改,而不会生成新的对象。
- 线程安全: StringBuffer是线程安全的,它的方法是同步的,可以在多线程环境中安全使用。
使用场景:
- 适用于在多线程环境中需要频繁修改字符串内容的场景。
线程安全的原因:
- 在诸多方法上加了同步锁
synchronized
。
JDK1.8的字符串常量拼接是怎样的过程?
编译时优化:
编译时常量折叠:
对于编译时已知的字符串常量,Java 编译器会进行常量折叠(Constant Folding)。这意味着在编译阶段,编译器会直接计算出拼接结果,并将其作为一个单一的字符串常量存储在.class文件中。例如:在编译时,这段代码会被优化为:1
2
3String s1 = "Hello " + "World!";
// 等同于
String s1 = "Hello World!";非常量表达式:
如果拼接的字符串包含变量或方法调用,编译器不能在编译时确定结果,因此需要在运行时进行拼接在 JDK 1.8 中,编译器会将这些拼接操作转换为使用StringBuilder的代码。例如:在编译时,这段代码会被转换为:1
2
3
4
5
6
7
8
9String str1 = "Hello";
String str2 = "World!";
String result = s1 + " " + s2;
//等同于
StringBuilder sb = new StringBuilder();
sb.append(str1);
sb.append(" ");
sb.append(str2);
String result = sb.toString();
运行时处理:
StringBuilder的使用:
在运行时,对于非常量的字符串拼接,StringBuilder被用来构建最终的字符串。StringBuilder是可变的,因此可以高效地进行字符串的拼接操作。
例如:在运行时,StringBuilder会依次调用append方法,将各个部分拼接起来,并最终调用toString方法生成结果字符串。
性能优化:
循环拼接一个长字符串,建议使用StringBuilder,虽然“+”拼接字符串编译后也会变成StringBuilder,但是每次循环处理都会new一个StringBuilder对象,耗时会大大增加。而直接使用StringBuilder,new一次就可以了,效率相对高。
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33/**
* 循环使用+拼接字符串
*/
public void testLoopStringConcatenation03ByPlus() {
long startTime = System.currentTimeMillis();
int count = 10000;
String str = "testLoopStringConcatenation03ByPlus:";
for (int i = 0; i < count; i++) {
str = str + "-" + i;
}
System.out.println(str);
long endTime = System.currentTimeMillis();
System.out.println("testLoopStringConcatenation03ByPlus,拼接字符串" + count + "次,花费" + (endTime - startTime) + "秒");
}
/**
* 测试循环使用StringBuilder拼接字符串耗时
*/
public void testLoopStringConcatenation04ByStringBuilder() {
long startTime = System.currentTimeMillis();
int count = 100000;
StringBuilder stringBuilder = new StringBuilder("testLoopStringConcatenation04ByStringBuilder:");
for (int i = 0; i < count; i++) {
stringBuilder.append("-");
stringBuilder.append(i);
}
String str = stringBuilder.toString();
System.out.println(str);
long endTime = System.currentTimeMillis();
System.out.println("testLoopStringConcatenation04ByStringBuilder,拼接字符串" + count + "次,花费" + (endTime - startTime) + "秒");
}testLoopStringConcatenation03ByPlus
,拼接字符串10000次,花费463秒。testLoopStringConcatenation04ByStringBuilder
,拼接字符串10000次,花费13秒。可以看出,差异明显,不在一个量级了。
总结:
在 JDK 1.8 中,字符串常量的拼接过程包括编译时的常量折叠和运行时的StringBuilder优化。常量折叠在编译时完成,而对于包含变量或方法调用的拼接,编译器会将其转换为StringBuilder操作,以提高运行时的性能。
锁
Synchronized的底层实现原理(原理解析,面试必备)_synchronized底层实现原理-CSDN博客
浅谈synchronized、wait、notify和notifyAll_synchronized,wait,notify-CSDN博客
Synchronized锁升级:无锁-> 偏向锁 -> 轻量级锁 -> 重量级锁_synchronized 有多种锁状态,会从无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁一步步-CSDN博客
AQS
谈谈Java多线程离不开的AQS_java aqs-CSDN博客
CountDownLatch
CountDownLatch介绍和使用【Java多线程必备】-CSDN博客
sleep和wait
线程的 sleep() 方法与 wait() 方法的区别_线程sleep和wait的区别-CSDN博客
Spark
Spark的shuffle阶段和MR的shuffle阶段分别是什么?区别是什么?_.spark 的shuffle 、mr 的shuffle 、flink 的 shuffle 有什么区-CSDN博客
宽依赖之间会划分stage,而Stage之间就是Shuffle
Stage以RDD宽依赖(也就是shuffle)为界
[Spark_Spark中 Stage, Job 划分依据 , Job, Stage, Task 高阶知识_spark stage-CSDN博客](https://blog.csdn.net/u010003835/article/details/132291007?ops_request_misc=%7B%22request%5Fid%22%3A%220A9E7024-0075-4926-A46C-F7E31153B849%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=0A9E7024-0075-4926-A46C-F7E31153B849&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-132291007-null-null.142^v100^pc_search_result_base8&utm_term=spark stage&spm=1018.2226.3001.4187)
MySQL
深入学习MySQL事务:ACID特性的实现原理 - 编程迷思 - 博客园 (cnblogs.com)
Redis
Redis的线程模式_redis io-threads-CSDN博客
处理请求是单线程(瓶颈不是CPU,瓶颈最有可能是机器内存或者网络带宽),但后续网络I/O变成了多线程
Redis中的跳表是怎么回事_redis跳表原理-CSDN博客
Docker
【面试】Docker 面试篇_docker面经-CSDN博客
spark三种模式【Standalone 模式、yarn 运行模式、local(本机)】_standalone模式-CSDN博客
方法论
如何监控慢SQL
启用慢查询日志
慢查询日志是 MySQL 内置的一种功能,用于记录执行时间超过指定阈值的 SQL 查询。
配置 MySQL 配置文件(my.cnf 或 my.ini)
在 MySQL 配置文件中添加或修改以下参数:
1
2
3
4
5
6
7
8
9[mysqld]
slow_query_log=1
slow_query_log_file=/var/log/mysql/slow-query.log
long_query_time =2
log queries not using indexes =1- slow_query_log: 启用慢查询日志。
- slow_query_log_file: 指定慢查询日志文件的路径。
- long_query_time: 定义慢查询的阈值(单位:秒)。
- ·log_queries_not _using_indexes: 记录末使用索引的查询。
重启 MySQL服务
修改配置文件后,重启 MySQL服务以使配置生效:
1
sudo systemctl restart mysql
使用 MySQL内置的性能模式(Performance Schema)
Performance Schema是 MySQL 内置的一个工具,用于收集数据库内部的运行时统计信息。可以通过以下步骤启用和使用 Performance Schema:
启用Performance Scheme
在MySQL配置文件中添加或修改以下参数:
1
2[mysqld]
performance_schema =ON重启MySQL服务
修改配置文件后,重启MySQL服务以使配置生效:
1
sudo systemctl restart mysql
查询慢查询信息
使用以下SQL语句查询慢查询信息:
1
SELECT * FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 10
使用 MySQL 企业监控工具
MySQL提供了企业版的监控工具,如 MySQL Enterprise Monitor,它可以自动收集、分析和报告慢查询信息。
这个工具适合企业环境下的全面监控和管理。
使用第三方监控工具
有许多第三方监控工具可以帮助你监控 MySQL的性能,包括慢查询。这些工具通常提供更丰富的功能和更友好的界面。常见的第三方工具包括:
- **Percona Monitoring and Management (PMM):**一个开源的监控和管理工具,专为 MySQL和MongoDB 设计。
- **New Relic:**一个强大的应用性能监控工具,支持 MySQL 和其他数据库。
- **Datadog:**一个综合性的监控平台,支持 MySQL 和其他数据库。
使用 SQL分析工具
MySQL提供了EXPLAIN语句,用于分析 SQL查询的执行计划。通过分析执行计划,可以识别和优化慢查询
使用EXPLAIN分析查询
1
EXPLAIN SELECT * FROM your_table WHERE your_condition;
EXPLAIN的输出结果包含了查询执行的详细信息,包括使用的索引、扫描的行数等。通过分析这些信息,可以找出查询的性能瓶颈并进行优化。
总结
监控慢 SQL是一个持续的过程,需要结合多种工具和方法。以下是一个综合的监控策略:
- 启用慢查询日志:记录执行时间超过阈值的查询。
- 使用 Performance Schema:收集和分析详细的运行时统计信息。
- 使用企业监控工具:如 MySQLEnterprise Monitor,进行全面的监控和管理。
- 使用第三方监控工具:如 Percona Monitoring and Management(PMM)、New Relic、Datadog 等。
- 使用 SQL 分析工具:如EXPLAIN,分析和优化查询执行计划。
MySQL常用监控指标
QPS: 数据库每秒处理的请求数量
1
show global status where variable_name in('Queries",'uptime');
QPS =(Queries2 -Queries1)/(uptime2 -uptime1)
TPS: 数据库每秒处理的事务数量
1
show global status where variable name in('com insert',com delete',com update','uptime');
事务数TC ≈ ‘com_insert’,’com_delete’,’com_update’
TPS ≈ (TC2 -TC1)/(uptime2 -uptime1)
并发数:数据库实例当前并行处理的会话数量
1
show global status like 'Threads_running';
连接数:连接到数据库会话的数量
1
show global status like 'Threads connected';
缓存命中率:查询命中缓存的比例
innodb缓冲池查询总数:
1
show global status like 'innodb_buffer_pool_read_requests';
innodb从磁盘查询数:
1
show global status like 'innodb_buffer_pool_reads';
生产中配置报警阈值:innodb buffer pool read requests /(innodb buffer pool read requests +innodb buffer pool reads)>0.95
可用性:数据库是否可以正常对外服务
周期性连接数据库并执行 select @@version;
阻塞:当前阻塞的会话数
1
2
3
4
5
6
7
8select waiting_pid as '被阳塞线程',
waiting_query as '被阻塞SQL',
blocking_pid as '阻塞线程',
blocking_query as '阻塞SQL',
wait_age as '阻塞时间’,
sql_ki1l_blocking_query as'建议操作'
from sys.innodb_lock_waits
where(unix_timestamp()-unix_timestamp(wait started))>阻塞秒数慢查询:慢查询情况
开启慢查询日志。my.inf
1
2
3slow_query_log=on
slow_query_log_file=存放目录long_query_time=0.1秒
log_queries_not_using_indexes=on
慢sql优化方向
- 检查查询语句本身。确保使用了合适的索引,避免全表扫描。比如,在WHERE、JOIN或ORDER BY子句中涉及的列上创建索引,这样可以大大提升查询速度。
- 优化数据库设计。比如,使用表分区来处理大表,或者根据实际需要在规范化和反规范化之间做出平衡,以减少复杂的JOIN操作。
- 调整 MVSQL的配置参数。比如,增加 InnoDB 缓冲池的大小,让更多的数据可以缓存在内存中,减少磁盘 //O 操作。同时,根据需要调整查询缓存的大小。
- 使用一些性能分析工具,比如EXPLAIN,来分析查询的执行计划,找出性能瓶颈。PerformanceSchema 也是一个很好的工具,可以帮助收集详细的性能数据。
- 持续的监控和调优是必不可少的。使用一些监控工具,比如Percona Monitoring and Management(PMM) 或 Datadog,来实时监控数据库的性能,并定期审查和优化慢查询,确保数据库始终保持高效。
详细解读:
优化查询语句
使用适当的索引
创建索引: 确保查询使用了适当的索引。对频繁出现在WHERE、JOIN、ORDER BY和GROUP BY子句中的列创建索引。
1
CREATE INDEX idx_column_name on table_name(column_name);
复合索引:对于多列查询,考虑使用复合索引(多列索引)。
1
CREATE INDEX idx_columns ON table_ name(column1,column2);
避免全表扫描
使用合适的过滤条件: 确保WHERE子句中的条件能够有效地利用索引,避免全表扫描。
1
SELECT * FROM table_name WHERE index_colum = 'value';
限制返回的行数:使用LIMIT字句限制返回的行数,减少数据库的负担。
1
SELECT * FROM table name WHERE condition LIMIT 10;
优化JOIN操作
使用小表驱动大表:在JOIN操作中,确保小表在前,大表在后。
1
2SELECT * FROM small_table ST JOIN large
_table LT on ST.id=LT.id;
避免不必要的复杂查询
简化查询:尽量简化查询,避免使用不必要的子查询和嵌套查询。
1
2
3
4# 避免复杂的嵌套查询
SELECT*FROM table_name WHERE id IN(SELECT id FROM another_table WHERE condition);
# 使用JOIN替代
SELECT * FROM table_name JOIN another_table ON table_name.id = another_table.id WHERE condition;
优化数据库设计
规范化与反规范化
- 规范化:确保数据库设计符合第三范式,减少数据几余,
- 反规范化:在某些情况下,为了性能,可以适度反规范化,减少复杂的JOIN操作。
分区表
表分区:对于非常大的表,可以使用表分区,将数据分成更小的部分,提高查询性能。
1
2
3
4
5
6
7
8CREATETABLE orders
order id INT,
order date DATE,
...
)PARTITIONBYRANGE(YEAR(order date))(PARTITION PO VALUES LESS THAN(2020),
PARTITION PI VALUES LESS THAN(2021),
PARTITION P2 VALUES LESS THAN(2022)
);
优化服务器配置
调整 MySQL 配置参数
**调整缓冲池大小:**对于InnoDB 存储引擎,调整innodb_buffer_pool_size参数,使其尽量大(但不要超过物理内存的 70-80%)。
1
[mysqld]innodb_buffer_pool_size = 4G
**调整查询缓存:**根据应用需求,调整查询缓存大小。
1
[mysqld]query_cache_size =64M
使用合适的存储引擎:
- 选择适当的存储引擎:根据应用需求选择合适的存储引警(如InnoDB、MyISAM)。
使用性能分析工具
使用EXPLAIN分析查询
分析执行计划:使用EXPLAIN分析查询的执行计划,识别性能瓶颈。
1
EXPLAIN SELECT*FROM your table WHERE your condition;
使用性能模式(Performance Schema)
收集性能数据:使用 Performance Schema 收集详细的性能数据,分析慢查询。
1
SELECT*FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 10;
监控和调优
持续监控
- **使用监控工具:**使用 MySQLEnterprise Monitor、Percona Monitoring and Management (PMM)、New Relic、Datadog 等工具持续监控数据库性能。
定期调优
- **定期审查查询:**定期审查和优化慢查询,确保数据库性能持续提升。
Tomcat
你是否傻傻分不清SpringBoot默认线程池和内置Tomcat线程池?_springboot 默认线程池-CSDN博客
JVM
JVM的主要组成部分
类加载子系统(五步)
- 描述:类加载子系统负责将 .class 文件加载到内存中,并进行验证、准备、解析和初始化。
- 主要功能:
- 加载:从文件系统或网络中读取 .class 文件。
- 验证:确保字节码文件的正确性和安全性。
- 准备:为类的静态变量分配内存并设置默认初始值。
- 解析:将符号引用转换为直接引用。
- 初始化:执行类的静态初始化块和静态变量的初始化。
运行时数据区(五部分)
JVM 在运行时将内存划分为多个不同的数据区域,每个区域都有特定的用途。
- 方法区(Method Area): 存储已加载的类信息、常量、静态变量、即时编译器编译后的代码等。
- 堆(Heap): 存储所有对象实例和数组,是垃圾收集的主要区域。
- Java 栈(Java Stacks): 每个线程都有自己的 Java 栈,存储局部变量表、操作数栈、动态链接、方法返回地址等信息。
- 本地方法栈(Native Method stacks): 为本地方法调用服务,存储本地方法调用的状态。
- 程序计数器(Program Counter Register):每个线程都有自己的程序计数器,指示当前线程执行的字节码行号。
执行引擎(三部分)
- 描述: 执行引擎负责执行字节码指令,
- 主要组件:
- 解释器(Interpreter): 逐条解释执行字节码指令,速度较慢。
- 即时编译器(Just-In-Time Compiler, JIT): 将热点代码(频繁执行的代码)编译为本地机器码,提高执行速度。
- **垃圾收集器(Garbage Colector, GC):**自动管理内存,回收不再使用的对象,防止内存泄漏。
本地接口
- **描述:**本地接口(通常是Java Native Interface,JNI)允许Java 代码与本地(非Java)代码进行交互。
- 主要功能:
- 调用本地方法(通常是用C或C++编写的)。
- 允许 Java 代码使用操作系统特性或访问硬件。
本地方法库
- 描述:本地方法库是存储本地方法实现的动态链接库(如.d 文件或.so 文件)。
- 主要功能:
- 提供本地方法的具体实现。
- 由本地接口调用以执行本地代码。
总结:
- 类加载子系统: 负责加载、验证、准备、解析和初始化类。
- 运行时数据区: 包括方法区、堆、Java 栈、本地方法栈和程序计数器。
- 执行引擎: 包括解释器、即时编译器和垃圾收集器。
- 本地接口: 允许 Java 代码与本地代码交互。
- 本地方法库: 存储本地方法实现的动态链接库。
JVM堆的内部结构是什么?
JVM 堆是 Java 虚拟机用于存储对象实例和数组的内存区域。堆内存是 JVM 管理的主要内存区域之一,堆内存的管理和优化对 Java 应用程序的性能至关重要。堆内存的内部结构通常分为几个不同的区域,以便更高效地进行内存分配和垃圾回收。
新生代(Young Generation)
新生代用于存储新创建的对象。大多数对象在新生代中创建,并且很快就会被垃圾回收。新生代进一步分为三个区域:
- **Eden 区(Eden Space): **大多数新对象首先分配在 Eden 区。当 Eden 区填满时,会触发一次轻量级的垃圾回收(Minor GC)。
- **幸存者区(Survivor Spaces): **新生代中有两个幸存者区,称为S0(Survivor0)和S1(Survivor 1)。在一次 Minor GC之后,仍然存活的对象会从 Eden 区和当前的幸存者区复制到另一个幸存者区(标记复制)。两个幸存者区会在每次 GC 后交替使用。
老年代(Old Generation)
老年代用于存储生命周期较长的对象。那些在新生代经历了多次垃圾(默认15次)回收仍然存活的对象会被移动到老年代。老年代的垃圾回收相对较少,但每次回收的时间较长,称为 Major GC 或 FuI GC。
永久代(Permanent Generation)和元空间(Metaspace)
- **永久代(Permanent Generation): **在JDK8之前,永久代用于存储类的元数据、常量池、方法信息等。永久代的大小是固定的,容易导致OutOfMemoryError错误。
- **元空间(Metaspace): **从JDK8开始,永久代被元空间取代。元空间不在JVM堆中,而是使用本地内存。元空间的大小可以动态调整,减少了OutOfMemoryError的风险。
JVM堆和栈的区别
在 JVM(Java Virtual Machine)中,堆(Heap)和栈(Stack)是两种不同的内存区域,它们在内存管理和程序执行中扮演着不同的角色。以下是它们的主要区别:
存储内容
- 堆:
- 用于存储所有的对象实例和数组。
- 所有对象实例和数组都在堆中分配内存。
- 栈:
- 用于存储局部变量表、操作数栈、动态链接、方法返回地址)。
- 每个线程都有自己的栈,栈中的数据与线程–对应。
- 堆:
内存管理方式
- 堆:
- 由垃圾收集器(Garbage Collector)进行自动管理,负责分配和回收对象内存。
- 堆内存是全局共享的,所有线程都可以访问堆中的对象。
- 栈:
- 由编译器自动管理,内存分配和释放按照方法调用的顺序进行(LIFO,后进先出)。
- 栈内存是线程私有的,每个线程都有自己的栈,互不干扰。
- 堆:
生命周期:
- 堆:
- 对象在堆中的生命周期由垃圾收集器决定,只要有引用指向对象,对象就会存在对象的生命周期可以跨越多个方法调用,直到没有引用指向它时才会被垃圾收集器回收。
- 栈:
- 局部变量的生命周期与方法调用的生命周期一致,方法调用结束时,栈帧被销毁,局部变量也随之销毁。
- 栈中的数据在方法调用结束后立即释放。
- 堆:
内存大小:
- 堆:
- 适合存储需要较长生命周期的大量对象。
- 通常较大,可以通过 JVM 参数(如-Xms和-Xmx)进行配置。
- 栈:
- 通常较小,每个线程的栈大小可以通过 JVM 参数(如-Xss)进行配置。
- 适合存储短生命周期的小数据。
- 堆:
线程安全
- 堆:
- 由于是全局共享的,堆中的对象在多线程环境下需要进行同步控制,以避免线程安全问题。
- 栈:
- 由于是线程私有的,栈中的数据天然是线程安全的,不需要额外的同步控制。
- 堆:
访问速度
- 堆:
- 访问速度相对较慢,因为需要通过引用进行访问,并且涉及到垃圾收集器的管理。
- 栈:
- 访问速度相对较快,因为栈中数据直接通过栈帧进行访问,且且栈的内存分配和释放效率高。
- 堆:
内存溢出
- 堆:
- 如果堆内存不足,会抛出OutOfMemoryError(如java.lang.OutOfMemoryError: Java heap space)。
- 栈:
- 如果栈内存不足,会抛出StackOverflowError(如java.lang.StackOverflowError)。
- 堆:
总结
- 堆: 用于存储对象实例和数组,由垃圾收集器管理,生命周期较长,内存较大,线程共享。
- 栈: 用于存储局部变量和方法调用信息,由编译器管理,生命周期短,内存较小,线程私有。
JVM运行时的数据区域如何理解?
Java 虚拟机(JM)在运行时将内存划分为若干不同的数据区域,每个区域都有特定的用途。
JVM 运行时数据区域
JVM 运行时数据区域主要包括以下几个部分:
- 方法区(Method Area)
- 堆(Heap)
- Java 栈(Java Stacks)
- 本地方法栈(Native Method Stacks)
- 程序计数器(Program Counter Register)
方法区(Method Area)
- 描述: 方法区是所有线程共享的内存区域,用于存储已被 JVM 加载的类信息、常量、即时编译器静态变量、编译后的代码等数据。
- 功能:
- 存储类的结构信息(如类的名称、访问修饰符、字段描述、方法描述等)。
- 包括字面量和符号引用。存储运行时常量池。
- 存储静态变量
- 存储编译后的代码。
- 在 HotSpot JVM 中,方法区的一部分实现为永久代(PermGen),在 Java8 及以后版本中被称为元空间(Metaspace)。
堆(Heap)
- 描述: 堆是所有线程共享的内存区域,用于存储所有对象实例和数组。
- 功能:
- 动态分配对象内存。
- 垃圾收集器主要在堆上工作,回收不再使用的对象内存。
- 堆通常分为年轻代(Young Generation)和老年代(Old Generation),年轻代又进一步划分为 Eden 区和两个Survivor区(S0和S1)。
Java 栈(Java Stacks)
- 描述: 每个线程都有自己的 Java 栈,栈帧(Stack Frame)在栈中按顺序存储。
- 功能:
- 存储局部变量表、操作数栈、动态链接、方法返回地址等信息。
- 每调用一个方法,就会创建一个新的栈帧,方法执行完毕后栈帧被销毁。
- 栈帧包括:
- 局部变量表:存储方法的局部变量,个包括参数和方法内部的局部变量
- 操作数栈:用于操作数的临时存储。
- 动态链接:指向常量池的方法引用,
- 方法返回地址:方法调用后的返回地址,
本地方法栈(Native Method Stacks)
**描述:**本地方法栈与 Java 栈类似,但它为本地(Native)方法服务。
功能:
- 存储本地方法调用的状态。
- 一些 JVM 使用 C 栈来支持本地方法调用。
程序计数器(Program Counter Register)
- **描述:**每个线程都有自己的程序计数器,是一个很小的内存区域。
- 功能:
- 当前线程所执行的字节码的行号指示器。
- 如果当前执行的是本地方法,这个计数器值为空(Undefined)。
总结
- 方法区: 存储类信息、常量、静态变量、即时编译代码。
- 堆: 存储对象实例和数组,是垃圾收集的主要区域。
- Java 栈: 每个线程有一个,存储方法调用的帧。
- 本地方法栈: 存储本地方法调用的状态。
- 程序计数器: 存储当前线程执行的字节码行号
为什么要分Eden和Survivor?
在JVM 中将新生代(Young Generation)分为 Eden 区和两个Survivor区(S0和S1)的主要原因是为了优化垃圾回收的效率和性能。这种分区策略基于对象的生命周期特点,利用复制算法来减少内存碎片和提高垃圾回收的效率。
优化垃圾回收效率
新生代的垃圾回收通常使用复制算法(Copying Algorithm),这种算法的核心思想是将存活的对象从一个区域复制到另一个区域,而不是在原地进行标记和清除。复制算法的步骤如下:
- **对象分配:**新创建的对象首先分配在 Eden 区。。
- **Minor GC 触发:**当 Eden 区填满时,会触发一次 Minor GC
- **对象复制:**在 Minor GC 过程中,存活的对象会从 Eden 区和当前使用的 Survivor 区(例如 S0)复制到另一个 Survivor 区(例如 S1)。复制完成后,Eden 区和当前使用的 Survivor 区将被清空。
- **区域交换:**两个 Survivor 区在每次 GC 后交替使用。
这种算法的优点是:
- **减少内存碎片: **复制算法通过将存活对象紧密排列在一起,避免了内存碎片的问题。
- **提高回收速度:**复制算法只需要遍历存活对象,而不需要遍历整个内存区域,这使得垃圾回收的速度更快。
优化内存分配
将新生代分为 Eden 区和两个 Survivor 区,能够更好地管理对象的生命周期
大多数对象生命周期短: 大多数新创建的对象很快就会变得不可达并被回收。Eden 区专门用于存储这些短生命周期对象,提高了内存分配和回收的效率。
**幸存者对象管理:**那些在一次或多次 Minor GC后仍然存活的对象会被复制到 Survivor 区。通过在两个Survivor 区之间复制和交换,可以有效管理这些对象的生命周期,直到它们被提升到老年代。
减少GC停顿时间:
复制算法和分区策略有助于减少 GC 停顿时间(GC Pause Time),提高应用程序的响应速度:
- Minor GC 更快速: 由于新生代通常较小,并且复制算法只处理存活对象,Minor GC 的停顿时间通常较短。
- **老年代 GC减少:**通过有效管理新生代的对象,减少了老年代的对象数量和垃圾回收频率,从而减少了Major GC 或 Ful GC 的次数和停顿时间。
示例:
- 假设有一个新生代大小为1GB,其中Eden区占80%(800MB),两个Survivor 区各占 10%(100 MB)。对象首先分配在 Eden 区,当 Eden 区填满时,触发 Minor Gc,将存活对象复制到一个 Survivor 区。下次 GC时,再将存活对象从当前 Survivor 区复制到另一个 Survivor 区。
总结:
- 将新生代分为 Eden 区和两个 Survivor 区的主要目的是优化垃圾回收的效率和性能。通过利用复制算法,可以减少内存碎片,提高垃圾回收速度,并有效管理对象的生命周期,从而减少GC停顿时间,提高应用程序的响应速度。这种分区策略是 JVM 内存管理的重要组成部分,帮助提高Java 应用程序的性能和稳定性。
什么是JVM方法区?
JVM 方法区(Method Area)是 JVM 运行时数据区的一部分,用于存储与类和方法相关的元数据。它是所有线程共享的内存区域,包含了 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等。方法区的内容在JVM 启动时创建,并在 JVM 运行期间动态扩展或收缩。
方法区的主要内容:
- **类信息:**包括类名、父类名、访问修饰符、接口列表等的元数据。
- **运行时常量池:**存储编译期生成的各种字面量和符号引用,这些引用在类加载后被解析为直接引用。
- **静态变量:**类的静态字段,存储类级别的变量。
- **即时编译器编译后的代码:**即时编译器(JIT)将热点代码编译为本地机器码,存储在方法区中。
- **字段和方法信息:**包括字段描述、方法描述、方法字节码、方法的访问修饰符等。
方法区在不同 JVM 实现中的差异:
在不同的 JVM 实现中,方法区的具体实现和管理方式可能有所不同。
以下是一些常见的 JVM 实现方式:
- HotSpot JVM(Java7及之前):方法区实现为永久代(Permanent Generation,PermGen)。永久代的内存空间固定,容易**导致内存溢出(OutOfMemoryError)**。
- HotSpot JVM (Java8及之后):方法区实现为元空间(Metaspace)。元空间使用本地内存(Native Memory),默认情况下可以根据需要动态扩展,减少了内存溢出的风险。
方法区的内存管理:
方法区的内存管理主要包括以下几个方面:
- 类加载: 当一个类被加载时,其相关信息会被存储在方法区中。
- 类卸载:当一个类不再被使用且没有任何引用时,垃圾收集器可以回收方法区中的类元数据。
- 垃圾收集: 方法区的垃圾收集主要针对废弃的类元数据和常量池中的无用常量。相比堆内存的垃圾收集,方法区的垃圾收集频率较低。
方法区相关的异常:
由于方法区存储了大量的类元数据和常量,可能会出现以下异常:
- OutOfMemoryError: PermGen space: 在Java7及之前的版本中,永久代空间不足时会抛出此异常。
- OutOfMemoryError: Metaspace:在 Java8 及之后的版本中,元空间内存不足时会抛出此异常。
总结:
- 方法区是 JVM 运行时数据区的一部分,用于存储类和方法的元数据。
- 方法区存储类信息、运行时常量池、静态变量、即时编译器编译后的代码以及字段和方法信息。
- 在不同的 JVM 实现中,方法区的管理方式可能不同,例如 HotSpot JVM 在 Java8 之前使用永久代(PermGen),在Java8之后使用元空间(Metaspace)。
- 方法区的内存管理包括类加载、类卸载和垃圾收集。
- 常见的异常包括OutOfMemoryError: PermGen space和OutOfMemoryError: Metaspace.
Java双亲委派机制是什么
JVM 的双亲委派机制(Parent Delegation Model)是一种类加载机制,用于确保 Java 类加载过程的安全性和一致性。
它的主要思想是:每个类加载器在加载类时,首先将请求委派给父类加载器,只有当父类加载器无法完成加载时,才由当前类加载器尝试加载类。
双亲委派机制的工作流程:
- 启动类加载器(Bootstrap ClassLoader): 负责加载Java 核心库(位于JAVA HOME/lib目录下的类库,如rt.jar)。
- 扩展类加载器(Extension ClassLoader): 负责加载Java 扩展库(位于JAVA HOME/ib/ext目录下的类库)。
- 应用程序类加载器(Application ClassLoader): 负责加载应用程序类路径(classpath)上的类。
启动->扩展->应用程序类
加载类的具体步骤如下:
- 当前类加载器收到类加载请求:当一个类加载器收到加载类的请求时,它不会立即尝试加载该类。
- 将请求委派给父类加载器:当前类加载器首先将加载请求委派给父类加载器。
- 父类加载器处理请求:
- 如果父类加载器存在,则父类加载器会继续将请求向上委派,直到到达启动类加载器。
- 启动类加载器尝试加载类,如果成功,则返回类的引用。
- 父类加载器无法加载类:如果启动类加载器无法加载该类,加载失败返回到子类加载器。
- 当前类加载器尝试加载类:如果父类加载器无法加载该类,则由当前类加载器尝试加载。
通过这种机制,可以确保核心类库不会被篡改,避免了类的重复加载和类的冲突问题。
(委派是向上委派的,执行是从上到下的。)
双亲委派机制的优点:
安全性:通过将类加载请求逐级向上委派,可以避免核心类库被篡改或替换,确保系统安全。
避免类的重复加载:确保每个类只被加载一次,避免类的重复加载和类的冲突问题。
提高加载效率:通过委派机制,可以利用已经加载的类,提高类加载的效率。
双亲委派机制的例外(自定义类加载器和一些框架中):
尽管双亲委派机制是 Java 类加载的标准机制,但在某些情况下,这一机制会被打破。例如:
- 自定义类加载器:某些自定义类加载器可能会覆盖默认的双亲委派机制,直接加载类。
- OSGi 框架:OSGi 框架中,类加载机制更加复杂,可能会打破双亲委派机制。
- SPl(Service Provider Interface):在某些服务提供者接口的实现中,可能需要打破双亲委派机制来加载服务实现类。
总结:
- 双亲委派机制是 Java 类加载过程中的一个重要机制,通过将类加载请求逐级向上委派,确保了类加载的安全性和一致性。
Java双亲委派机制的作用
保证 Java 核心库的安全性
通过双亲委派机制,Java 核心库(如java.lang.Object等)由启动类加载器(Bootstrap ClassLoader)加载。由于启动类加载器是在 JVM 启动时由本地代码实现的,并且它加载的类路径是固定的系统核心库路径,因此可以确呆这些核心类不会被篡改或替换。这样,系统的安全性和稳定性得到了保障。避免类的重复加载
双亲委派机制确保了每个类只会被加载一次。如果一个类已经被父类加载器加载过,那么子类加载器就不会再重复加载这个类。这样可以避免类的重复加载,提高类加载的效率,并减少内存消耗。
保证类加载的一致性
通过双亲委派机制,可以确保同一个类在整个 JVM 中只有一个定义。这样可以避免类的冲突和不一致问题。例如,如果应用程序和第三方库中都定义了一个相同的类名,通过双亲委派机制可以确保最终加载的是位于更高层次的类加载器中的类,从而避免冲突。
提高类加载的效率
双亲委派机制通过将类加载请求逐级向上委派,可以利用已经加载的类,提高类加载的效率。父类加载器在加载类时,如果该类已经被加载过,那么直接返回该类的引用,从而减少了重复加载的开销。
支持动态扩展
双亲委派机制允许在不同的类加载器中加载不同的类,从而支持动态扩展。例如,应用程序类加载器(Application ClassLoader)可以加载应用程序特定的类,而扩展类加载器(Extension ClassLoader)可以加载扩展库中的类,这样可以方便地进行动态扩展和模块化开发。
总结:
安全性->避免重复加载->避免冲突保持一致性->提高加载效率->支持动态扩展。
Spring MVC
什么是Spring MVC
Spring MVC是 Spring 框架中的一个模块,用于构建基于 Web 的应用程序。它遵循 Model-View-Controler(MVC)设计模式,将业务逻辑、用户界面和数据分离,以促进代码的可维护性和可扩展性。
- 模型(Model)
模型代表应用程序的数据和业务逻辑。它通常包含数据对象(如POJO)和服务层(如 Spring 服务)来处理业务逻辑。模型负责从数据库或其他数据源获取数据,并将数据传递给视图以显示给用户。 - 视图(View)
视图负责展示数据,通常是 HTML 页面或其他类型的用户界面。Spring MVC支持多种视图技术,包括 JSP、Thymeleaf、FreeMarker 等。视图从模型获取数据并将其呈现给用户。 - 控制器(Controller)
控制器处理用户请求并决定将数据传递给哪个视图。它接收用户输入,调用模型进行处理,并选择合适的视图来显示结果。控制器通常使用@Controller 注解来标识,并使用 @RequestMapping 注解来映射 URL 请求。
Spring MVC 的工作流程
- 用户请求: 用户通过浏览器发送 HTTP 请求到服务器。
- 前端控制器(DispatcherServlet): Spring MVC的前端控制器 DispatcherServlet 拦截所有请求并进行分发。
- 处理器映射(Handler Mapping): 根据请求 URL,DispatcherServlet 查找相应的控制器。
- 控制器处理: 控制器处理请求,调用服务层或数据访问层以获取数据,并将数据封装到型中。
- 视图解析器(View Resolver): 控制器返回视图名称, DispatcherServlet 使用视图解析器将视图名称解析为实际的视图对象。
- 视图渲染: 视图对象负责将模型数据渲染为用户界面,通常是 HTML页面。
- 响应返回: 渲染后的视图返回给 DispatcherServlet,DispatcherServlet 将最终的响应发送回用户浏览器。
核心组件
- **DispatcherServlet:**前端控制器,负责接收并分发请求
- **Controller:**处理用户请求,包含业务逻辑。
- ModelAndView: 包含模型数据和视图名称的对象。
- **View Resolver:**将视图名称解析为实际的视图对象。
- **Handler Mapping:**根据请求 URL 查找相应的控制器。
SpringMVC的原理及执行流程
组件
Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,它大量使用了Spring框架中提供的设计模式。Spring MVC框架的核心组件包括:
- DispatcherServet:前端控制器,负责接收请求并根据映射关系调用相应的控制器
- HandlerMapping:负责根据请求的URL到HandlerMapping中找到映射的处理器(Controller)。
- HandlerAdapter:负责根据处理器,生成处理器适配器,通过适配器调用实际的处理器。
- Controller:处理器,执行相应的业务逻辑操作,并返回ModelAndView对象。
- ModelAndView:包含了视图逻辑名和模型数据的对象,是连接控制器和视图的桥梁。
- ViewResolver:负责解析视图名到具体视图实现类的映射,根据视图名称找到对应的视图实现类。
- View:视图,负责渲染数据并展示给用户。
执行流程
Spring MVC 的执行流程大致可以分为以下几个步骤
- 发送请求到DispatcherServlet: 用户向服务器发送请求,请求被DispatcherServlet捕获。
- 查找Handler: DispatcherServlet根据请求URL到HandlerMapping中查找映射的处理器(Controller)。
- 调用HandlerAdapter: DispatcherServlet根据处理器,到HandlerAdapter中找到对应的处理器适配器。
- 执行Controller: 处理器适配器调用实际的处理器(Controler)执行业务逻辑操作,并返回ModelAndView对象。
- 处理ModelAndView: DispatcherServlet根据ModelAndView中的视图名称,到ViewResolver中找到对应的视图实现类。
- 渲染视图: 视图实现类根据ModelAndView中的数据和视图模板渲染视图。
- 返回响应到客户端: DispatcherServet将渲染后的视图返回给客户端。
什么是DispatcherServlet
DispatcherServlet 充当前端控制器(Front Controller),负责接收所有进入的 HTTP 请求并将它们分派给适当的处理器进行处理。 DispatcherServlet 是实现 MVC 模式的关键部分,负责协调整个请求处理流程。
主要职责
- **请求接收和分派:**拦截所有进入的 HTTP 请求并将它们分派给适当的控制器(Controller)
- **处理器映射:**根据请求 URL,查找相应的处理器(通常是控制器方法)。
- **视图解析:**将控制器返回的视图名称解析为实际的视图对象。
- **请求处理:**调用处理器进行请求处理,并将处理结果封装到模型中。
- **视图渲染:**将模型数据传递给视图对象进行渲染,并生成最终的响应。
工作流程
以下是 DispatcherServlet 的详细工作流程:
- 初始化:
- 在应用程序启动时, DispatcherServlet被初始化。它加载 Spring 应用程序上下文,配置处理器映射、视图解析器等组件。
- 接收请求:
- 用户通过浏览器发送 HTTP 请求到服务器。
- DispatcherServlet 拦截所有符合配置的 URL 模式的请求。
- 处理器映射:
- DispatcherServlet 使用处理器映射器(Handler Mapping)根据请求 URL 查找相应的处理器(Controller)。
- 调用处理器:
- 找到处理器后, Dispatcherservlet 调用处理器的方法进行请求处理。
- 处理器执行业务逻辑,通常会调用服务层或数据访问层获取数据,并将数据封装到型中。
- 视图解析:
- 处理器处理完请求后,返回一个包含视图名称和模型数据的ModelAndview 对象。
- DispatcherServlet 使用视图解析器(View Resolver)将视图名称解析为实际的视图对象。
- 视图渲染:
- 视图对象负责将模型数据渲染为用户界面,通常是 HTML 页面。
- 响应返回:
- 渲染后的视图返回给 DispatcherServlet,Dispatcherservlet 将最终的响应发送回用户浏览器。
什么是Handler Mapping
Handler Mapping 负责将 HTTP 请求映射到相应的处理器(通常是控制器方法)。当DispatcherServlet接收到一个请求时,它会使用 Handler Mapping 来确定哪个处理器应该处理这个请求。
主要职责
- **请求映射:**根据请求的 URL、HTTP 方法、请求参数等信息,查找并确定相应的处理器。
- **处理器返回:**返回一个包含处理器对象和处理器拦截器链的 HandlerExecutionchain 对象。
工作流程
- 请求到达Dispatcherservlet:当一个HTTP 请求到达 DispatcherServlet 时,它会首先交给 Handler Mapping 进行处理。
- **查找处理器:**Handler Mapping根据请求的 URL、HTTP 方法等信息查找匹配的处理器。
- 返回处理器: Handler Mapping 返回一个 HandlerExecutionchain 对象,其中包含处理器(通常是控制器方法)和处理器拦截器链。
- 处理请求: DispatcherServlet 使用找到的处理器来处理请求,并生成响应。
常见的 Handler Mapping 实现
- BeanNameUrlHandlerMapping:
- 通过 bean 的名称来映射处理器。
- 例如,bean 名称为/hello 的处理器会处理 /hello 请求。
- SimpleUrlHandlerMapping:
- 通过显式配置的 URL 路径来映射处理器。
- 可以在 Spring 配置文件中指定 URL 到处理器的映射关系。
- DefaultAnnotationHandlerMapping(过时):
- 通过注解(如 @RequestMapping )来映射处理器。
- 在较新的 Spring 版本中被 RequestMappingHandlerMapping 取代。
- RequestMappingHandlerMapping
- 这是最常用的 Handler Mapping 实现。
- 通过注解(如 @RequestMapping、@GetMapping、@PostMapping 等)来映射处理器支持复杂的请求映射规则,包括路径变量、请求参数、请求头等。
总结
Handler Mapping 负责将 HTTP 请求映射到相应的处理器。通过使用不同的 Handler Mapping 实现,开发者可以灵活地配置请求映射规则,以满足各种应用需求。最常用的 Handler Mapping 实现是 RequestMappingHandlerMapping,它通过注解提供了强大的请求映射功能。
什么是Handler Adapter
Handler Adapter 负责将处理器(Handler)适配为具体的处理方法。 Handler Adapter 的主要作用是根据处理器的类型和具体实现,执行相应的处理逻辑。Handler Adapter是 DispatcherServlet 和具体处理器之间的桥梁。
主要职责
- 处理器执行:调用处理器的方法来处理请求。
- 返回模型和视图:处理完请求后,返回一个ModelAndView 对象,包含视图名称和模型数据。
工作流程
- 请求到达 DispatcherServlet:当一个 HTTP 请求到达 DispatcherServlet 时,它会先通过HandlerMapping 找到对应的处理器。
- 选择 Handler Adapter: DispatcherServlet根据处理器的类型选择合适的 Handler Adapter 。
- 执行处理器: Handler Adapter 调用处理器的方法来处理请求。
- **返回结果:**处理完请求后,Handler Adapter返回一个 ModelAndView 对象,DispatcherServlet。
再根据这个对象生成最终的响应。
常见的 Handler Adapter 实现
- HttpRequestHandlerAdapter
- 用于处理实现 HttpRequestHandler 接口的处理器。
- 例如实现了 HttpRequestHandler接口的处理器。
- SimpleControllerHandlerAdapter
- 用于处理实现Controller 接口的处理器。
- 例如实现了 Controller接口的处理器。
- RequestMappingHandlerAdapter
- 最常用的 Handler Adapter 实现。
- 用于处理使用 @RequestMapping 注解的控制器方法。
- 支持复杂的请求映射规则和数据绑定。
总结Handler Adapter
负责将处理器适配为具体的处理方法。通过使用不同的 Handler Adapter
实现,SpringMVC
可以灵活地支持多种类型的处理器。最常用的 Handler Adapter
实现是 RequestMappingHandlerAdapter
它支持通过注解定义的控制器方法,并提供了强大的请求处理功能。
什么是View Resolver
View Resolver
负责将逻辑视图名称解析为具体的视图对象(如JSP、Thymeleaf
模板等)。ViewResolver
的主要作用是根据控制器返回的视图名称,找到相应的视图资源,并将其渲染成最终的 HTML
响应。
主要职责
- 视图名称解析: 将控制器返回的逻辑视图名称解析为具体的视图对象。
- 视图对象返回: 返回一个 view 对象,该对象可以用来染模型数据
工作流程
- **控制器处理请求: **当一个HTTP 请求到达 DispatcherServlet 时,它会通过 Handler Adapter 调用控制器的方法来处理请求。
- **返回视图名称: **控制器方法处理完请求后,会返回一个包含视图名称和模型数据的 ModelAndView 对象。
- 视图名称解析: DispatcherServlet使用 View Resolver 将逻辑视图名称解析为具体的视图对象。
- 渲染视图: view 对象使用模型数据来渲染最终的 HTML 响应。
Spring MVC 中的@Controller注解有什么作用
@controller
注解用于标记一个类作为控制器组件。控制器是处理 HTTP 请求的核心组件,它负责接收请求处理业务逻辑并返回视图或数据响应。
主要作用
- 标识控制器类: @contro1ler 注解告诉 Spring 该类是一个控制器,应该由 Spring 容器管理。
- 处理请求:控制器类中的方法通过映射注解(如 @RequestMapping、@GetMapping、@PostMapping等)处理 HTTP 请求。
相关注解
在Spring MVC中,除了 @controller,还有一些常用的注解用于处理请求
- @RequestMapping
- 用于定义请求 URL 和 HTTP 方法的映射。
- 可以应用于类级别和方法级别。
- @PutMapping@DeleteMapping@GetMapping、@PostMapping
- 分别用于处理GET、POST、PUTDELETE 请求。
- 是 @RequestMapping 的快捷方式。
- @RequestParam
- 用于绑定请求参数到方法参数。
- 可以指定参数名称、是否必需以及默认值。
- @PathVariable
- 用于绑定 URL 路径中的变量到方法参数。
- @ModelAttribute
- 用于将请求参数绑定到模型对象,并将模型对象添加到模型中。
- @ResponseBody
- 用于将方法的返回值直接作为 HTTP 响应体。
- 常用于返回 JSON 或 XML 数据。
总结
@controller 注解在 Spring MVC 中用于标记一个类为控制器组件,控制器负责处理 HTTP 请求并返回视图或数据响应。通过结合使用各种映射注解(如 @RequestMapping、@GetMapping 等)和参数绑定注解(如 @RequestParam@PathVariable 等),可以灵活地处理不同类型的请求和参数。