• Java 后端面试指南


    面试指南

    TMD,一个后端为什么要了解那么多的知识,真是服了。啥啥都得了解


    MySQL

    MySQL索引可能在以下几种情况下失效:

    1. 不遵循最左匹配原则:在联合索引中,如果没有使用索引的最左前缀,即查询条件中没有包含联合索引的第一列,那么索引将会失效。
    2. 使用了OR操作符:即使在查询条件中使用了联合索引的全部列,如果这些列之间是使用OR操作符连接的,索引也可能会失效。
    3. 数据类型转换:如果在查询条件中对字段进行了隐式的类型转换,比如将字符类型的字段与数字进行比较,这可能导致索引失效。
    4. 使用了函数或表达式:在查询条件中对字段使用了函数或表达式,如WHERE YEAR(date_column) = 2023,这样会使得索引失效。
    5. 选择性低的索引:如果索引的选择性很低,即索引列的值重复率很高,数据库优化器可能会选择全表扫描而不是使用索引。
    6. 索引列上有函数或计算:当索引列上有函数或计算时,MySQL无法使用索引来查找行。
    7. 全表扫描更快的情况:当MySQL优化器估计出全表扫描比使用索引更快时,系统会选择不使用索引。这通常发生在表中数据量较少,或者查询返回大部分甚至所有行的情况下。

    总的来说,索引失效可能会导致查询性能下降,因此在设计查询语句和索引时需要特别注意以上情况,以确保索引能够发挥其应有的作用。

    索引不适合哪些场景

    1. 数据量少的不适合加索引
    2. 更新比较频繁的也不适合加索引
    3. 区分度低的字段不适合加索引(如性别)

    日常工作中你是怎么优化 SQL 的?

    在日常工作中使用SQL时,优化是一个持续的过程,旨在确保数据库查询的效率和性能。以下是一些常见的优化策略:

    1. 使用EXPLAIN分析查询

      • 使用EXPLAIN命令来分析SQL语句的执行计划,从而理解MySQL如何执行查询,哪些地方可能成为瓶颈。
    2. 正确地创建和使用索引

      • 确保为经常用于搜索和排序的列创建索引。
      • 避免创建不必要的索引,以减少插入、更新和删除操作的成本。
      • 使用复合索引来优化多列的查询条件。
      • 定期审查现有索引的有效性,删除不再使用的索引。
    3. 编写高效的SQL语句

      • 避免在WHERE子句中对字段进行函数转换或计算。
      • 减少使用OR操作符,尤其是在索引列上,因为它可能导致全表扫描。
      • 使用连接(JOIN)而不是子查询,以便更有效地利用索引。
      • 仅选择需要的列,而不是使用SELECT *
    4. 优化数据模型

      • 规范化表结构以避免数据冗余。
      • 在必要时使用反规范化来减少连接操作,提高查询性能。
    5. 使用分区和分表

      • 对于大型表,考虑使用分区来提高查询性能。
      • 在数据量非常大的情况下,可以考虑分表来分散负载。
    6. 调整数据库配置

      • 根据服务器的硬件资源和应用需求调整MySQL的配置参数,如缓冲池大小、连接数等。
    7. 监控和诊断

      • 使用慢查询日志来识别低效的查询。
      • 使用性能监控工具来跟踪数据库的性能指标。
    8. 批量操作和事务控制

      • 使用批量操作来减少数据库的I/O次数。
      • 合理使用事务,确保数据的一致性,同时避免长事务导致的锁竞争。
    9. 避免使用锁定的查询

      • 尽量使用读已提交隔离级别,避免不必要的行级锁。
    10. 定期维护

      • 定期运行OPTIMIZE TABLE来整理表空间,特别是对于经常修改的表。
      • 定期检查并修复表的错误。
    11. 学习和使用新的数据库特性

      • 保持对MySQL新特性的了解,如窗口函数、CTE(公共表表达式)等,这些可以帮助编写更高效的查询。

    通过上述方法,可以显著提高SQL查询的性能。然而,每个数据库和应用场景都是独特的,因此可能需要根据具体情况调整优化策略。

    什么是前缀索引

    前缀索引是一种数据库优化技术,通过为列的部分信息(前缀)添加索引来提高查询效率和减少索引文件的大小

    以下是前缀索引的一些关键信息:

    1. 定义:前缀索引是指在数据库中为某个字段的前面几个字符创建索引,而不是为整个字段值创建索引。
    2. 优势:前缀索引可以有效减小索引文件的大小,从而提高索引查询的速度,尤其是在处理大型数据表时。
    3. 适用条件:当前缀部分具有较高区分度时,即不同的记录在前缀部分有较多不同值时,前缀索引更加有效。
    4. 创建方法:在MySQL中,可以通过ALTER TABLE或CREATE INDEX语句指定前缀长度来创建前缀索引。
    5. 局限性:使用前缀索引时,不能在某些操作中使用,如ORDER BY、GROUP BY以及覆盖索引,因为只有部分字段被索引。
    6. 选择前缀长度:选择合适的前缀长度是一个关键因素,需要通过计算字段值的区分度来确定。区分度越高,所需的前缀长度越短。
    7. 示例应用:例如,如果要为user表中的email字段的前10个字符创建索引,可以使用类似以下的SQL语句:
    CREATE INDEX idx_email_prefix ON user(email(10));
    
    • 1

    总的来说,前缀索引是一种在数据库性能优化中常用的技术,它可以在保证查询效率的同时减少索引的存储空间。然而,它并不适用于所有场景,需要根据具体的数据特性和查询需求来决定是否使用以及如何使用前缀索引。

    OPTIMIZE TABLE 是什么?

    OPTIMIZE TABLE是MySQL中用于改善表性能的命令。它的主要作用是整理表的空间使用,减少碎片,提高数据访问效率。在表中进行大量插入、删除或更新操作后,可能会产生空间碎片,导致表的性能下降。使用OPTIMIZE TABLE命令可以重新组织表的数据,释放未使用的空间,让数据更紧凑地存储。

    以下是使用OPTIMIZE TABLE的基本语法:

    OPTIMIZE TABLE table_name;
    
    • 1

    其中,table_name是要优化的表的名称。

    OPTIMIZE TABLE命令执行的操作包括:

    1. 整理表空间:通过整理表的磁盘空间,减少碎片,使得数据行紧密排列,从而提高I/O效率。
    2. 更新统计信息:更新表的统计信息,帮助优化器选择更有效的执行计划。
    3. 重建索引:如果表的索引已经损坏或者因为碎片而效率低下,OPTIMIZE TABLE可以重建索引,提高索引的效率。
    4. 减少文件碎片:对于使用InnoDB存储引擎的表,OPTIMIZE TABLE可以减少文件碎片,提高空间利用率。

    需要注意的是,OPTIMIZE TABLE命令需要对表具有一定的锁定时间,在此期间表无法进行写入操作。因此,建议在系统负载较低的时段执行此命令,以减少对应用的影响。

    此外,对于使用InnoDB存储引擎的表,OPTIMIZE TABLE命令的效果可能不如预期,因为InnoDB会自动进行页的合并和分裂来管理碎片。在这种情况下,可以考虑使用ALTER TABLE命令来更改表的压缩模式,以提高空间利用率和性能。

    InnoDB 与 MyISAM 的区别

    下面是InnoDB和MyISAM两种存储引擎在关键特性上的对比表格:

    特性InnoDBMyISAM
    事务支持支持完整的ACID事务不支持事务
    行级锁支持行级锁,提高并发性能仅支持表级锁
    外键约束支持外键约束不支持外键
    数据文件和索引使用聚集索引,数据文件存放在主键索引的叶子节点上使用非聚集索引,数据文件和索引分开存储
    全文索引支持全文索引,但需要额外配置和插件支持全文索引,且无需额外配置
    数据恢复能力具有崩溃后的数据恢复能力遇到系统崩溃时,数据恢复能力较弱
    缓存支持数据和索引的缓存只缓存索引
    表空间所有表共享一个表空间,方便管理每个表单独存储为.frm、.MYD、.MYI文件
    适用场景适用于需要高并发、事务完整性保障的应用适用于读取密集型以及不要求事务的应用

    注意:这个表格中的信息可能随着MySQL版本的更新而有所变化。例如,InnoDB在MySQL 5.6版本以后开始支持全文索引,但通常认为MyISAM在全文索引方面更为成熟。此外,MyISAM由于其设计简单,在某些读密集的场景下可能有较好的性能表现,但随着现代硬件的发展和多核处理器的普及,InnoDB的性能优势愈发明显。

    数据库索引的原理,为什么要用 B+树?

    数据库索引的原理是利用数据结构对数据进行排序,以便快速查找。在数据库中,B+树是最常用的索引结构,因为它具有以下优点:

    1. 减少磁盘I/O操作:B+树的设计能够有效地减少访问节点的次数,因为每个节点可以存储多个元素,这意味着在查找过程中需要的磁盘I/O操作更少。每次磁盘访问都是昂贵的,因此减少I/O操作次数对于性能至关重要。
    2. 增加存储效率:B+树的非叶子节点不存储数据,只存储索引,而所有数据都保存在叶子节点中。这样的设计使得每个节点可以存储更多的索引,从而使整个树的高度降低,进一步减少了I/O操作的次数。
    3. 提高查询稳定性:由于所有数据都存在于叶子节点,并且叶子节点之间通过链表连接,这使得范围查询更加高效。在B+树中进行范围查询时,只需遍历叶子节点的链表即可,而在二叉树中可能需要进行二次遍历。
    4. 方便数据插入和删除:B+树的结构允许在不影响其他部分的情况下插入和删除数据,这有助于保持树的平衡,从而维持高效的查询性能。

    为什么不用一般二叉树?

    不使用一般二叉树作为数据库索引的主要原因在于磁盘I/O操作和存储效率。让我们具体来看一下这些方面:

    1. 磁盘I/O操作:一般二叉树的节点只包含两个子节点的引用(在二分查找的情况下)以及数据,这意味着每次查找都可能涉及到对磁盘的多次访问。因为数据库系统通常运行在磁盘上,而不是内存中,所以减少对磁盘的访问次数是提高性能的关键。

    2. 存储效率:一般二叉树的节点存储了数据以及指向子节点的指针,这导致存储密度较低。相比之下,B+树的非叶子节点仅存储键值,没有实际的数据,这使得每个节点可以拥有更多的键,降低了树的高度,提高了存储效率。

    3. 分支因子:由于二叉树的结构限制,每个节点只有两个分支。在B+树中,每个节点可以有更多的分支,这增加了分支因子,并减少了树的高度。

    4. 查询性能稳定性:在二叉树中,范围查询可能会导致性能不稳定,因为需要遍历多个不连续的节点。而在B+树中,由于所有叶子节点通过指针连接成一个有序链表,范围查询的性能更为稳定。

    5. 维护成本:一般二叉树在插入和删除操作后可能需要重新平衡,这个过程可能相对复杂。B+树通过其设计来简化节点的分裂和合并过程,使得维护成本更低。

    因此,虽然一般二叉树在理论的查找效率上可能与B+树相当,但在实际的数据库系统中,B+树提供了更好的性能,特别是在处理大量数据时,能够提供更高的数据访问效率和更低的存储成本。

    为什么不是平衡二叉树?

    不使用平衡二叉树作为数据库索引的原因主要在于磁盘I/O操作的优化和范围查询的效率。B+树相对于平衡二叉树有以下优势:

    1. 减少磁盘I/O操作:B+树的高度较低,通常在2-4层之间,这意味着在查找记录时最多只需要2-4次磁盘I/O操作。而平衡二叉树的高度通常会更高,因为它每个节点只存储一个数据项,这会导致更多的磁盘访问,从而降低了性能。
    2. 提高范围查询效率:B+树的叶子节点通过指针连接成一个有序链表,这使得进行范围查询时非常高效。例如,在查找大于等于某个值的所有数据时,一旦找到该值,就可以通过叶子节点的指针连续获取所有相关数据,而不需要像在平衡二叉树中那样回溯到父节点。
    3. 增加存储效率:B+树的非叶子节点不存储实际数据,只存储键值,这样可以在每个节点中存储更多的键,从而降低树的整体高度。平衡二叉树的每个节点存储了数据,这限制了每个节点的键数,导致树的高度增加。
    4. 方便数据插入和删除:B+树的结构允许在不影响其他部分的情况下插入和删除数据,这有助于保持树的平衡,而平衡二叉树在插入和删除数据时需要更多的旋转操作来维持平衡,这会增加维护成本。

    综上所述,虽然平衡二叉树提供了快速的查找性能,但是B+树在数据库索引中更为常用,因为其结构更加适合磁盘I/O的特性,并且在处理范围查询时更加高效。

    为什么不是 B 树?

    数据库索引选择使用B+树而不是B树,主要是因为B+树在磁盘I/O操作和存储效率方面具有更明显的优势。具体分析如下:

    1. 磁盘I/O优化:B+树通过增加分支因子,减少树的高度,从而减少了查找数据时所需的磁盘I/O次数。由于磁盘读取是相对较慢的操作,这种优化对于性能至关重要。
    2. 提高查询效率:B+树的所有叶子节点都在同一层,并且通过指针相连,这为范围查询提供了便利。在B树中,范围查询可能需要多次遍历不同层的节点,效率较低。
    3. 减少内存开销:B+树的非叶子节点不存储实际数据,只存储键值,这样可以减少内存的使用,提高缓存的效率。
    4. 方便维护:B+树的叶子节点包含所有键值,而非叶子节点仅作为索引,这使得节点的分裂和合并操作更加简单,便于维护。

    总的来说,虽然B树和B+树都是平衡多路查找树,但是B+树在数据库索引中的应用场景下,因其结构特点,提供了更好的性能优势。这也是为什么关系型数据库普遍采用B+树作为索引结构的原因。

    总结

    综上所述,B+树通过其特有的结构优势,能够提供更高效的数据访问路径,尤其是在处理大量数据时,这些优势使得B+树成为数据库索引的首选结构。

    事务的隔离级别有哪些?

    • 读未提交(Read Uncommitted)
    • 读已提交(Read Committed)
    • 可重复读(Repeatable Read)
    • 串行化(Serializable)

    MySQL 的默认隔离级别是什么?

    Mysql 默认的事务隔离级别是可重复读(Repeatable Read)

    MySQL中的Explain命令

    MySQL中的Explain命令用于查看查询的执行计划

    Explain命令在MySQL中扮演着重要的角色,它能够帮助开发者理解SQL语句的执行路径和成本,从而对查询进行优化。使用Explain非常简单,只需在SQL查询语句前加上EXPLAIN关键字即可。Explain的结果会以表格形式返回,展示查询执行的细节信息。从MySQL 5.6版本开始,Explain也支持非SELECT语句的解释。

    Explain输出结果中包含多个字段,每个字段代表不同的信息:

    1. id:标识每个SELECT子句的唯一ID。
    2. select_type:表示查询的类型,例如简单查询、主查询、子查询等。
    3. table:指出查询将访问哪张表。
    4. type:显示了如何查找数据,比如全表扫描、索引扫描或者范围扫描等。
    5. possible_keys:可能应用在这张表上的索引。
    6. key:实际使用的索引。如果为NULL,则没有使用索引。
    7. key_len:使用的索引的长度。
    8. ref:哪个字段或常数与key一起被使用。
    9. rows:预计需要读取的行数,这个数值越小,表明查询效率越高。
    10. filtered:过滤后剩余的行数百分比,这个值越大越好。
    11. extra:额外的信息,比如是否使用了临时表、是否进行了排序等。

    总之,通过分析这些字段的内容,我们可以了解查询的性能瓶颈所在,并据此采取相应的优化措施,如添加或调整索引、改写查询逻辑等。

    如果某个表有近千万数据,CRUD 比较慢,如何优化?

    如果某个表有近千万数据,CRUD操作比较慢,可以考虑以下几种优化方法:

    1. 索引优化:为经常用于查询条件的字段创建索引,可以加快查询速度。但是要注意不要创建过多的索引,因为索引也会占用存储空间和影响写入性能。

    2. 分库分表:将数据分散到多个数据库或表中,可以提高并发处理能力和扩展性。可以根据业务需求选择合适的分库分表策略,如按照时间、地域、用户等进行分片。

    3. 读写分离:将读操作和写操作分别分配给不同的数据库服务器,可以提高系统的并发处理能力。可以通过主从复制或者使用专门的读写分离中间件实现。

    4. 缓存优化:将热点数据缓存在内存中,可以减少对数据库的访问次数,提高系统性能。可以使用分布式缓存框架如Redis来实现。

    5. SQL优化:优化SQL语句,避免使用子查询、临时表等可能导致性能下降的操作。可以使用Explain命令分析SQL执行计划,找出性能瓶颈并进行优化。

    6. 硬件升级:增加服务器的内存、CPU等硬件资源,可以提高系统的处理能力。

    7. 数据库参数调优:根据服务器的硬件配置和业务需求,调整数据库的参数设置,如缓冲区大小、连接数等,以提高数据库的性能。

    8. 数据压缩:对存储的数据进行压缩,可以减少存储空间的占用,提高I/O效率。

    9. 分区表:将大表分成多个小表,可以提高查询性能。可以根据业务需求选择合适的分区键,如按照时间、地域等进行分区。

    10. 垂直拆分:将一个大表拆分成多个小表,每个小表只包含部分字段,可以减少查询时需要扫描的数据量,提高查询速度。

    总之,针对具体的业务场景和系统状况,可以采用多种方法进行优化,以达到提高CRUD性能的目的。

    Mysql 主从复制原理

    MySQL主从复制是一种数据同步机制,允许数据从一个MySQL数据库服务器(主节点)复制到一个或多个其他服务器(从节点)。这种机制常用于实现数据的热备份、负载均衡和扩展。

    以下是MySQL主从复制的基本原理:

    1. binlog(二进制日志): 在主服务器上,每当有数据变更发生时,如INSERT、UPDATE或DELETE操作,这些变更会被记录在binlog中。
    2. 读取binlog: 从服务器连接到主服务器,并请求主服务器发送新的binlog事件。这个过程可以通过IO线程来完成。
    3. relay log(中继日志): 从服务器接收到来自主服务器的binlog事件后,会将这些事件写入到自己的relay log中。
    4. 应用binlog: 从服务器的另一个线程,称为SQL线程,会读取relay log中的事件,并将它们依次应用到从服务器的数据库中,从而保持与主服务器的数据一致性。

    此外,在实际应用中,主从复制可能会存在一定的延时,即从服务器的数据更新可能落后于主服务器。这种延时通常被称为同步延时。为了减少这种延时,可以采用半同步复制的方式,即在主服务器上等待至少一个从服务器确认接收到binlog事件后才认为该事件提交成功。
    在这里插入图片描述
    主从复制分了五个步骤进行:
    步骤一:主库的更新事件(update、insert、delete)被写到 binlog
    步骤二:从库发起连接,连接到主库。
    步骤三:此时主库创建一个 binlog dump thread,把 binlog 的内容发送到从库。
    步骤四:从库启动之后,创建一个 I/O 线程,读取主库传过来的 binlog 内容并写入到 relay log
    步骤五:还会创建一个 SQL 线程,从 relay log 里面读取内容,从Exec_Master_Log_Pos 位置开始执行读取到的更新事件,将更新内容写入到slave 的 db

    Hash索引和B+树索引的区别是什么?

    Hash索引和B+树索引是数据库中常用的两种索引类型,它们在查询效率和数据组织等方面存在一些区别。具体分析如下:

    1. 查询效率:对于等值查询,Hash索引可以提供更快的查找速度,因为它通过一次散列运算就能直接定位到数据的存储位置。而B+树索引需要从根节点开始,通过逐层遍历找到叶子节点,这个过程涉及到多次磁盘I/O操作。
    2. 数据组织:B+树索引的所有数据都存储在叶子节点,并且叶子节点之间通过指针相连,这使得范围查询非常高效。而Hash索引由于其数据组织方式,不适合进行范围查询。
    3. 冲突处理:当多个不同的键值散列到同一个索引位置时,会发生冲突。Hash索引通常使用链表来解决冲突,这可能导致在最坏情况下,查询效率降低。B+树索引则没有这种冲突问题。
    4. 插入和删除:由于B+树的结构特性,插入和删除操作可以保持较高的效率,尤其是在保持页面填充率的情况下。而Hash索引在处理大量插入和删除导致冲突增多时,性能可能会受到影响。

    总的来说,Hash索引在等值查询方面具有明显的优势,尤其是在查询效率上。然而,B+树索引在范围查询和有序性方面表现更好,且更适合处理大量数据的插入和删除。在实际应用中,选择哪种索引取决于具体的应用场景和需求。

    count(1)、count(*) 与 count(列名) 的区别?

    在SQL中,COUNT(1)COUNT(*)COUNT(列名)都是用来统计记录数量的函数,但它们之间存在一些细微的区别:

    • COUNT(1):这个函数会统计表中的所有记录数,包括那些所有列都为NULL的记录。换句话说,只要数据库中有这条记录,无论记录的内容如何,COUNT(1)都会将其计入总数。
    • COUNT(*):这个函数与COUNT(1)的行为相同,也会统计表中的所有记录数,包括那些所有列都为NULL的记录。在大多数数据库系统中,COUNT(*)COUNT(1)的效率是相同的。
    • COUNT(列名):这个函数会统计指定列中非NULL值的数量。如果某条记录的指定列值为NULL,则这条记录不会被计入总数。这在统计某一特定列的有效数据时非常有用。

    总的来说,COUNT(1)COUNT(*)在功能上是等价的,它们都会计算表中的所有记录,而COUNT(列名)则会忽略掉指定列中值为NULL的记录。在实际使用时,选择哪种方式取决于你想要统计的内容。如果你想统计所有记录,可以使用COUNT(1)COUNT(*);如果你想统计特定列的非NULL值的数量,那么应该使用COUNT(列名)

    mysql 中 int(20)和 char(20)以及 varchar(20)的区别?

    在MySQL中,INT(20)CHAR(20)VARCHAR(20)是三种不同的数据类型,它们用于存储不同类型的数据,并且在存储方式和空间占用上也有所不同。

    1. INT(20)

      • 数据类型:整数(Integer)。
      • 存储范围:通常为-2147483648到2147483647(取决于具体的数据库系统)。
      • 显示长度:括号中的数字表示显示宽度,即在结果集中显示的字符数,但这并不影响实际存储的空间大小或值的范围。
      • 空间占用:通常占用4个字节的存储空间。
    2. CHAR(20)

      • 数据类型:定长字符串(Character)。
      • 存储范围:最多可以存储20个字符。
      • 显示长度:括号中的数字表示字段可以存储的最大字符数。
      • 空间占用:无论实际存储的数据长度如何,都会占用20个字符的存储空间。如果实际数据长度不足20个字符,剩余的空间会用空格填充。
    3. VARCHAR(20)

      • 数据类型:变长字符串(Variable Character)。
      • 存储范围:最多可以存储20个字符。
      • 显示长度:括号中的数字表示字段可以存储的最大字符数。
      • 空间占用:根据实际存储的数据长度动态分配存储空间,最大不超过20个字符。不会像CHAR那样浪费空间。

    需要注意的是,这些数据类型的具体实现可能会因不同的数据库管理系统而有所差异。在实际使用时,应根据数据的性质和应用场景选择合适的数据类型。

    update 变更前后的值相同的话,执行过程是什么样子的

    在MySQL中,当使用UPDATE语句更新数据时,如果变更前后的值相同,实际上数据库不会执行任何数据的更改操作。这是因为数据库系统在执行UPDATE语句时,会首先检查新值与当前值是否相同。

    具体来说,以下是执行过程的简化描述:

    1. 语法解析:数据库系统首先对UPDATE语句进行语法解析,确认语句的结构正确无误。
    2. 权限验证:数据库系统会检查执行该语句的用户是否具有相应的权限,以进行数据更新操作。
    3. 锁定行:为了维护数据的一致性,数据库系统可能会锁定要更新的数据行,防止其他事务同时修改同一数据。
    4. 生成新的行版本:数据库系统会生成一个新的行版本,包含更新后的值。
    5. 比较新旧值:数据库系统会比较新的值和当前值是否相同。
    6. 决定是否更新:如果新旧值相同,数据库系统会认为没有实际的数据变更需要执行,因此不会进行任何数据的更改操作。这意味着原数据保持不变,不会产生任何日志记录或触发器执行等副作用。
    7. 释放锁:最后,数据库系统会释放之前锁定的数据行。
      需要注意的是,尽管没有实际的数据变更操作被执行,但这个过程仍然涉及到一系列的步骤和资源消耗,包括解析、权限验证、锁定等。因此,在实际开发中,应尽量避免编写可能导致无意义更新的语句,以提高数据库性能和效率。

    请解释第一、第二、第三范式,并举例说明。

    数据库范式是关系型数据库设计中的一组规则,用于确保数据的逻辑一致性和减少数据冗余。以下是第一、第二、第三范式的解释以及各自的举例说明:

    1. 第一范式(1NF)
    • 解释:第一范式要求数据库表中的每一列都是不可分割的基本数据项,即每个字段都是原子性的,不能再分解成更小的部分。这个范式的目的是消除重复的组和确保每个数据项都是最小的、不可分割的单位。
    • 举例:如果一个表格中有一个列是“地址”,而地址包含了街道、城市和邮编,那么这个表就不符合第一范式。为了符合第一范式,应该将地址拆分成三个独立的列:街道、城市和邮编。
    1. 第二范式(2NF)
    • 解释:在满足第一范式的基础上,第二范式要求数据库表中的所有非主键列都完全依赖于主键,而不是部分依赖。这意味着一个表中只能有一个主键,且其他字段必须依赖于这个完整的主键。
    • 举例:如果有一个订单详情表,其中包含订单编号、产品ID、数量和价格,如果订单编号和产品ID共同作为主键,那么数量和价格应该只依赖于这两个字段的组合,而不是其中的任何一个字段。
    1. 第三范式(3NF)
    • 解释:在满足第二范式的基础上,第三范式要求数据库表中的所有字段都不依赖于其他非主键字段。这是通过消除传递依赖来实现的,即确保所有非主键列都直接依赖于主键,而不是通过其他列间接依赖。
    • 举例:如果在一个员工表中,有员工ID、部门ID、部门名称和员工姓名等字段,其中员工ID是主键,部门ID是一个外键。为了满足第三范式,应该将部门名称从员工表中移除,并在一个单独的部门表中存储部门ID和部门名称的信息。这样,员工表中的部门名称就不再依赖于员工ID,而是直接依赖于部门ID。

    总的来说,通过遵循这些范式,可以设计出结构良好、数据冗余低、更新异常少的数据库。在实际应用中,通常会根据具体的业务需求和数据使用情况来决定是否需要严格遵循这些范式,或者进行适当的反范式化以优化性能。

    使用TEXT类型来存储JSON数据违反数据库的第一范式吗?

    使用TEXT类型来存储JSON数据并不违反数据库的第一范式(1NF)

    数据库的第一范式主要是针对关系型数据库设计的,它要求表中的每一列都是不可分割的基本数据项,即每个字段都是原子性的。而TEXT类型通常用于存储字符串数据,包括JSON格式的字符串。当将JSON数据作为字符串整体存储在TEXT类型的列中时,它被视为一个单一的值,而不是多个独立的数据项。因此,从数据库设计的角度来看,使用TEXT类型存储JSON字符串是符合第一范式的。

    需要注意的是,虽然从技术上讲,使用TEXT类型存储JSON数据不违反第一范式,但在实际的数据库设计和应用中,可能会有其他的考虑因素。例如,有的公司数据库规范建议“尽可能不使用TEXT类型”,这可能是因为TEXT类型不适合索引,查询效率低,且不易进行数据完整性校验。而对于需要存储JSON数据的场景,一些数据库系统提供了专门的JSON数据类型,这些类型可以提供更好的数据校验和查询性能。

    总的来说,从数据库范式的角度来看,使用TEXT类型存储JSON数据并不违反第一范式,但在实际应用中,应当根据具体需求和数据库系统的功能特性来选择合适的数据类型。

    WITH AS 语法详解

    【Mysql】WITH AS 语法详解

    消息队列

    什么是消息队列

    你可以把消息队列理解为一个使用队列来通信的组件。它的本质,就是个转发
    器,包含发消息、存消息、消费消息的过程。最简单的消息队列模型如下:在这里插入图片描述
    我们通常说的消息队列,简称 MQ(Message Queue),它其实就指消息
    中间件,当前业界比较流行的开源消息中间件包括:
    RabbitMQ、RocketMQ、Kafka。

    消息队列有哪些使用场景?/为什么使用消息队列?

    1. 应用解耦
    2. 流量削峰
    3. 异步处理
    4. 消息通讯
    5. 远程调用

    消息队列如何解决消息丢失问题?

    一个消息从生产者产生,到被消费者消费,主要经过这 3 个过程:
    在这里插入图片描述

    因此如何保证 MQ 不丢失消息,可以从这三个阶段阐述:

    • 生产者保证不丢消息
    • 存储端不丢消息
    • 消费者不丢消息

    生产者保证不丢消息

    生产端如何保证不丢消息呢?确保生产的消息能到达存储端。
    如果是 RocketMQ 消息中间件,Producer 生产者提供了三种发送消息的方式,分别是:

    • 同步发送
    • 异步发送
    • 单向发送

    生产者要想发消息时保证消息不丢失,可以:

    • 采用同步方式发送,send 消息方法返回成功状态,就表示消息正常到达了存储端Broker。
    • 如果 send 消息异常或者返回非成功状态,可以重试。
    • 可以使用事务消息,RocketMQ 的事务消息机制就是为了保证零丢失来设计的

    存储端不丢消息

    如何保证存储端的消息不丢失呢? 确保消息持久化到磁盘。大家很容易想到就
    是刷盘机制。
    刷盘机制分同步刷盘异步刷盘

    • 生产者消息发过来时,只有持久化到磁盘,RocketMQ 的存储端 Broker 才返回一
      个成功的 ACK 响应,这就是同步刷盘。它保证消息不丢失,但是影响了性能。
    • 异步刷盘的话,只要消息写入 PageCache 缓存,就返回一个成功的 ACK 响应。
      这样提高了 MQ 的性能,但是如果这时候机器断电了,就会丢失消息。

    Broker 一般是集群部署的,有 master 主节点和 slave 从节点。消息到Broker 存储端,只有主节点和从节点都写入成功,才反馈成功的 ack 给生产者。这就是同步复制,它保证了消息不丢失,但是降低了系统的吞吐量。与之对应的就是异步复制,只要消息写入主节点成功,就返回成功的 ack,它速度快,但是会有性能问题。

    消费阶段不丢消息

    消费者执行完业务逻辑,再反馈会 Broker 说消费成功,这样才可以保证消费
    阶段不丢消息。

    消息队列有可能发生重复消费,如何避免,如何做到幂等?

    消息队列是可能发生重复消费的。

    • 生产端为了保证消息的可靠性,它可能往 MQ 服务器重复发送消息,直到拿到成功
      的 ACK。
    • 再然后就是消费端,消费端消费消息一般是这个流程:拉取消息、业务逻辑处理、
      提交消费位移。假设业务逻辑处理完,事务提交了,但是需要更新消费位移时,消
      费者挂了,这时候另一个消费者就会拉到重复消息了。

    如何幂等处理重复消息呢?

    幂等处理重复消息,简单来说,就是搞个本地表,带唯一业务标记的,利用主
    键或者唯一性索引,每次处理业务,先校验一下就好啦。又或者用 redis 缓存
    下业务标记,每次看下是否处理过了。

    如何处理消息队列的消息积压问题?

    消息积压是因为生产者的生产速度,大于消费者的消费速度。遇到消息积压问
    题时,我们需要先排查,是不是有 bug 产生了。
    如果不是 bug,我们可以优化一下消费的逻辑,比如之前是一条一条消息消费
    处理的话,我们可以确认是不是可以优为批量处理消息。
    如果还是慢,我们可
    以考虑水平扩容,增加 Topic 的队列数,和消费组机器的数量,提升整体消费
    能力

    如果是 bug 导致几百万消息持续积压几小时。有如何处理呢? 需要解决
    bug,临时紧急扩容,大概思路如下:

    1. 先修复 consumer 消费者的问题,以确保其恢复消费速度,然后将现有consumer 都停掉。
    2. 新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue数量。
    3. 然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的 10 倍数量的 queue。
    4. 接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据。
    5. 等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的consumer 机器来消费消息。

    消息队列技术选型,Kafka 还是 RocketMQ,还是RabbitMQ?

    先可以对比下它们优缺点:
    在这里插入图片描述

    • RabbitMQ 是开源的,比较稳定的支持,活跃度也高,但是不是 Java 语言开发的。
    • 很多公司用 RocketMQ,是阿里出品的。
    • 如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的。

    如何保证数据一致性,事务消息如何实现?

    一条普通的 MQ 消息,从产生到被消费,大概流程如下:

    1. 生产者产生消息,发送带 MQ 服务器
    2. MQ 收到消息后,将消息持久化到存储系统。
    3. MQ 服务器返回 ACK到生产者。
    4. MQ 服务器把消息 push 给消费者
    5. 消费者消费完消息,响应 ACK
    6. MQ 服务器收到 ACK,认为消息消费成功,即在存储中删除消息。

    我们举个下订单的例子吧。订单系统创建完订单后,再发送消息给下游系统。如果订单创建成功,然后消息没有成功发送出去,下游系统就无法感知这个事情,出导致数据不一致。
    如何保证数据一致性呢?可以使用事务消息。一起来看下事务消息是如何实现的吧。
    在这里插入图片描述

    1. 生产者产生消息,发送一条半事务消息到 MQ 服务器
    2. MQ 收到消息后,将消息持久化到存储系统,这条消息的状态是待发送状态。
    3. MQ 服务器返回 ACK 确认到生产者,此时 MQ 不会触发消息推送事件
    4. 生产者执行本地事务
    5. 如果本地事务执行成功,即 commit 执行结果到 MQ 服务器;如果执行失败,发
      送 rollback。
    6. 如果是正常的 commit,MQ 服务器更新消息状态为可发送;如果是 rollback,即
      删除消息。
    7. 如果消息状态更新为可发送,则 MQ 服务器会 push 消息给消费者。消费者消费完
      就回 ACK。
    8. 如果 MQ 服务器长时间没有收到生产者的 commit 或者 rollback,它会反查生产
      者,然后根据查询到的结果执行最终状态。

    Redis

    缓存穿透&缓存雪崩

    缓存穿透

    缓存穿透:指的是在缓存中没有找到要查询的数据,导致每次查询都需要直接查询数据库,从而导致数据库压力过大。这种情况通常发生在查询的数据不存在或者缓存过期时。

    例如,假设有一个商品查询接口,当用户查询一个不存在的商品时,缓存中没有该商品的信息,因此每次查询都会直接查询数据库。如果大量用户同时查询不存在的商品,就会导致数据库压力过大,甚至可能导致数据库崩溃。

    为了解决缓存穿透问题,可以采取以下措施:

    • 缓存空值:在缓存中存储一个空值或默认值,当查询的数据不存在时,直接返回缓存中的空值或默认值,避免直接查询数据库。
    • 使用布隆过滤器:布隆过滤器可以快速判断一个数据是否存在于数据库中,从而避免直接查询数据库。当查询的数据不存在于布隆过滤器中时,直接返回空值或默认值。

    缓存雪崩

    缓存雪崩:指的是在短时间内大量缓存失效,导致大量请求直接查询数据库,从而导致数据库压力过大。这种情况通常发生在缓存过期时间集中或者缓存服务器故障时。

    例如,假设有一个商品详情页面,该页面的缓存过期时间为 10 分钟。当缓存过期后,大量用户同时访问该页面,就会导致大量请求直接查询数据库,从而导致数据库压力过大,甚至可能导致数据库崩溃。

    为了解决缓存雪崩问题,可以采取以下措施:

    • 分散缓存过期时间:将缓存的过期时间分散开,避免大量缓存同时失效。
    • 使用缓存预热:在系统启动时,将一些热点数据预先加载到缓存中,避免在系统启动后大量请求直接查询数据库。
    • 使用缓存失效降级:当缓存失效时,暂时关闭缓存,直接查询数据库,并在后台异步更新缓存。

    总之,缓存穿透和缓存雪崩都是缓存系统中常见的问题,需要根据具体情况采取相应的措施来解决。

    Redis 集群是如何确定某个 K 存在某个节点的?

    在 Redis 集群中,确定某个键(K)存在于哪个节点,是通过一种称为哈希槽(Hash Slot)的机制来实现的。

    Redis 集群将整个键空间划分为 16384 个哈希槽,每个节点负责一部分哈希槽。当客户端要存储一个键值对时,它会根据键计算出一个哈希值,并将其映射到对应的哈希槽上。然后,客户端会将请求发送到负责该哈希槽的节点上。

    具体来说,Redis 采用 CRC16 算法对键进行哈希运算,然后将得到的哈希值对 16384 取模,得到的结果就是对应的哈希槽编号。这样,每个键都会被映射到唯一的哈希槽上,而每个哈希槽又对应着唯一的节点。

    通过这种方式,Redis 集群可以实现数据的分布式存储和查询,并且可以很容易地进行扩容和缩容。当集群中的节点数量发生变化时,只需要重新分配哈希槽,就可以实现数据的自动迁移和负载均衡。

    Redis的过期策略

    Redis的过期策略包括定期删除、惰性删除和内存淘汰机制。具体来说:

    1. 定期删除:这是由Redis内部定时任务触发的删除策略,每隔一段时间,Redis会扫描数据库中的键,找出那些已经过期的键并删除它们。

    2. 惰性删除:这种策略是在访问某个键时,如果发现该键已经过期,Redis会立即删除它。这种策略不会主动去查找过期键,而是在键被访问的时候才进行判断和删除。

    3. 内存淘汰机制:当Redis内存不足以容纳新写入数据时,会根据一定的算法选择淘汰部分数据以释放空间。Redis提供了多种内存淘汰算法:

      • noeviction:不进行数据淘汰,新写入操作会报错。默认的内存淘汰策略
      • allkeys-lru:从所有键中选择最近最少使用的键进行淘汰。
      • allkeys-random:从所有键中随机选择键进行淘汰。
      • volatile-lru:从设置了过期时间的键中选择最近最少使用的键进行淘汰。
      • volatile-random:从设置了过期时间的键中随机选择键进行淘汰。
      • volatile-ttl:从设置了过期时间的键中选择剩余生存时间最短的键进行淘汰。

    综上所述,了解这些策略有助于优化Redis的使用,确保数据的有效管理和内存的高效利用。

    如何查找Redis中的big key

    要查找Redis中的big key,可以采用以下几种方法:

    1. 使用--bigkeys命令:这是Redis自带的命令,可以扫描整个key空间并统计string、list、set、zset和hash这几种数据类型中每种类型里最大的key。这个命令特别适合于分析string类型,因为string类型统计的是value的字节数。但需要注意的是,如果元素个数少,不一定value就大;反之,如果元素个数多,也不一定代表value就大。
    2. 使用memory命令:从Redis 4.0版本开始支持,可以用来查看某个key占用的内存大小。这个命令可以帮助识别出那些占用内存较多的key。

    此外,在处理big key时需要注意,big key可能会导致Redis性能问题,特别是在高并发的场景下。因此,定期检查和优化big key对于维护Redis的性能至关重要。

    Redis的持久化方式

    Redis的持久化方式主要有两种:RDB和AOF。以下是对这两种方式的详细说明:

    1. RDB(Redis DataBase)
    • RDB是一种全量持久化方式,它会周期性地将内存中的数据以二进制格式保存到磁盘上的RDB文件。
    • RDB文件是一个经过压缩的二进制文件,包含了数据库在某个时间点的数据快照。
    • RDB快照有助于实现紧凑的数据存储,适合用于备份和恢复。
    • 优点:RDB快照在恢复大数据集时速度较快,因为它是全量的数据快照。由于RDB文件是压缩的二进制文件,它在磁盘上的存储空间相对较小。适用于数据备份和灾难恢复。
    • 缺点:可能会丢失最后一次快照之后的所有数据。
    • 触发方式:可以通过执行savebgsave命令手动触发,也可以通过配置文件设置自动触发条件。
    1. AOF(Append Only File)
    • AOF持久化记录服务器执行的所有写命令,并在服务重启时通过命令重放来还原数据。
    • AOF文件是一个明文文件,记录了所有写操作,因此具有较高的数据完整性。
    • 优点:AOF记录了所有的写操作,因此可以提供更高的数据安全性。在系统崩溃后,可以通过重放日志恢复所有操作。
    • 缺点:AOF文件的大小可能会比RDB文件大,因为它记录了每个写操作。
    • 触发方式:AOF的写入策略可以通过配置文件设置,包括每次写入(同步)、每秒写入或者不主动写入由操作系统决定。

    此外,还有一种混合持久化的方式,它兼顾了RDB和AOF的特性,可以在保证数据安全性的同时,也保证了数据的恢复效率。

    总的来说,在选择持久化方式时,需要根据具体的应用场景和需求来决定。如果重视数据的安全性,可以选择AOF;如果重视恢复的效率和存储空间的利用,可以选择RDB。

    Redis和Memcached的差异

    Redis和Memcached都是高性能的内存数据库,用于缓存数据以加快应用程序的访问速度。它们之间的区别如下表所示:

    特性RedisMemcached
    数据结构支持多种数据结构,包括字符串、列表、集合、有序集合和哈希表仅支持简单的键值对结构
    持久化支持RDB快照和AOF日志两种持久化方式不支持持久化
    数据高可用主从复制模式,哨兵模式,Cluster模式等实现高可用通过客户端分片实现高可用
    数据备份AOF文件可读性好,有利于数据恢复数据存储在内存中,服务器宕机可能导致数据丢失
    内存管理采用自适应内存管理策略,根据数据大小动态调整内存使用采用固定大小的内存块进行分配,可能导致内存浪费
    过期策略为每个key设置过期时间,精确到毫秒级别为每个key设置过期时间,过期时间的最小粒度为1秒钟
    事务处理支持简单的事务功能不支持事务
    管道技术支持管道技术,提升批量操作效率支持管道技术
    发布/订阅支持发布/订阅模式,实现消息的广播和通知不支持
    脚本支持支持Lua脚本,扩展了Redis的功能不支持

    综上所述,Redis提供了更加丰富的数据结构和功能,适用于需要复杂数据操作和持久化的应用场景;而Memcached则更专注于简单的键值缓存,适用于需要快速访问的场景。

    网络


    对称加密与非对称加密有什么区别?

    对称加密:指加密和解密使用同一密钥,优点是运算速度较快,缺点是如何安
    全将密钥传输给另一方。常见的对称加密算法有:DES、AES 等。
    在这里插入图片描述

    非对称加密:指的是加密和解密使用不同的密钥(即公钥和私钥)。公钥与私
    钥是成对存在的,如果用公钥对数据进行加密,只有对应的私钥才能解密。常
    见的非对称加密算法有 RSA。在这里插入图片描述

    DNS 的解析过程?

    DNS,英文全称是 domain name system,域名解析系统,是 Internet上作为域名和 IP 相互映射的一个分布式数据库。它的作用很明确,就是可以根据域名查出对应的 IP 地址。在浏览器缓存、本地 DNS 服务器、根域名服务器都是怎么查找的,大家回答的时候都可以说下哈。
    DNS 的解析过程如下图:
    在这里插入图片描述

    forward 和 redirect 的区别?

    • 直接转发方式(Forward) ,客户端和浏览器只发出一次请求,
      Servlet、HTML、JSP 或其它信息资源,由第二个信息资源响应该请求,在请
      求对象 request 中,保存的对象对于每个信息资源是共享的。
    • 间接转发方式(Redirect) 实际是两次 HTTP 请求,服务器端在响应第一次
      请求的时候,让浏览器再向另外一个 URL 发出请求,从而达到转发的目的。

    Redirect 的工作原理:在这里插入图片描述
    forward 的工作原理
    在这里插入图片描述

    聊聊 SQL 注入?

    SQL 注入是一种代码注入技术,一般被应用于攻击 web 应用程序。它通过在web 应用接口传入一些特殊参数字符,来欺骗应用服务器,执行恶意的 SQL命令,以达到非法获取系统信息的目的。它目前是黑客对数据库进行攻击的最常用手段之一

    请详细介绍一下 TCP 的三次握手机制?

    在这里插入图片描述

    • 第一次握手(SYN=1, seq=x),发送完毕后,客户端就进入 SYN_SEND 状态
    • 第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1), 发送完毕后,服务器
      端就进入 SYN_RCV 状态。
    • 第三次握手(ACK=1,ACKnum=y+1),发送完毕后,客户端进入
      ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态。

    TCP 握手为什么是三次,为什么不能是两次?不能是四次

    思路: TCP 握手为什么不能是两次,为什么不能是四次呢?为了方便理解,我
    们以男孩子和女孩子谈恋爱为例子:两个人能走到一起,最重要的事情就是相
    爱,就是我爱你,并且我知道,你也爱我,接下来我们以此来模拟三次握手的
    过程:
    在这里插入图片描述

    为什么握手不能是两次呢?

    如果只有两次握手,女孩子可能就不知道,她的那句我也爱你,男孩子是否收
    到,恋爱关系就不能愉快展开。

    为什么握手不能是四次呢?

    因为握手不能是四次呢?因为三次已经够了,三次已经能让双方都知道:你爱
    我,我也爱你。而四次就多余了。

    TCP 四次挥手过程?

    在这里插入图片描述
    TCP 四次挥手过程

    1. 第一次挥手(FIN=1,seq=u),发送完毕后,客户端进入 FIN_WAIT_1 状态。
    2. 第二次挥手(ACK=1,ack=u+1,seq =v),发送完毕后,服务器端进入CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态。
    3. 第三次挥手(FIN=1,ACK1,seq=w,ack=u+1),发送完毕后,服务器端进入LAST_ACK 状态,等待来自客户端的最后一个 ACK。
    4. 第四次挥手(ACK=1,seq=u+1,ack=w+1),客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT 状态,等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。

    TCP 挥手为什么需要四次呢?

    思路: TCP 挥手为什么需要四次呢?为了方便大家理解,再举个生活的例子吧。
    小明和小红打电话聊天,通话差不多要结束时,小红说,“我没啥要说的了”。小明回答,“我知道了”。但是小明可能还有要说的话,小红不能要求小明跟着她自己的节奏结束通话,于是小明可能又叽叽歪歪说了一通,最后小明说,“我说完了”,小红回答,“我知道了”,这样通话才算结束。
    在这里插入图片描述

    TCP 四次挥手过程中,为什么需要等待 2MSL,才进入CLOSED 关闭状态

    在这里插入图片描述

    2MSL,two Maximum Segment Lifetime,即两个最大段生命周期。

    假设主动发起挥手的是客户端,那么需要 2MSL 的原因是:

    1. 为了保证客户端发送的最后一个 ACK 报文段能够到达服务端。 这个 ACK 报
      文段有可能丢失,因而使处在 LAST-ACK 状态的服务端就收不到对已发送的
      FIN + ACK 报文段的确认。服务端会超时重传这个 FIN+ACK 报文段,而客
      户端就能在 2MSL 时间内(超时 + 1MSL 传输)收到这个重传的 FIN+ACK
      报文段。接着客户端重传一次确认,重新启动 2MSL 计时器。最后,客户端和
      服务器都正常进入到 CLOSED 状态。
    2. 防止已失效的连接请求报文段出现在本连接中。客户端在发送完最后一个
      ACK 报文段后,再经过时间 2MSL,就可以使本连接持续的时间内所产生的所
      有报文段都从网络中消失。这样就可以使下一个连接中不会出现这种旧的连接
      请求报文段。

    说说 TCP 是如何确保可靠性的呢?

    • 首先,TCP 的连接是基于三次握手,而断开则是基于四次挥手。确保连接和断开的
      可靠性。
    • 其次,TCP 的可靠性,还体现在有状态;TCP 会记录哪些数据发送了,哪些数据被接收了,哪些没有被接受,并且保证数据包按序到达,保证数据传输不出差错。
    • 再次,TCP 的可靠性,还体现在可控制。它有数据包校验、ACK 应答、超时重传(发送方)、失序数据重传(接收方)、丢弃重复数据、流量控制(滑动窗口)和拥塞控制等机制。

    请简述 TCP 和 UDP 的区别

    1. TCP 面向连接(如打电话需要先拨号),UDP 面向无连接(即发送数据前不需要建立连接)。
    2. TCP 提供可靠的服务,UDP 无法保证。
    3. TCP 面向字节流,而 UDP 面向报文。
    4. TCP 数据传输慢,UDP 数据传输快
    5. TCP 是点对点连接的,UDP 可以一对一,一对多,多对多都可以。
    6. TCP 适用于邮件、网页等,UDP 适用于语音广播等。

    Java

    String,Stringbuffer,StringBuilder 的区别

    • String:
      • String 类是一个不可变的类,一旦创建就不可以修改。
      • String 是 final 类,不能被继承
      • String 实现了 equals()方法和 hashCode()方法
    • StringBuffer:
      • 继承自 AbstractStringBuilder,是可变类。
      • StringBuffer 是线程安全的
      • 可以通过 append 方法动态构造数据。
    • StringBuilder:
      • 继承自 AbstractStringBuilder,是可变类。
      • StringBuilder 是非线性安全的。
      • 执行效率比 StringBuffer 高。

    Java中的几种基本数据类型是什么,各自占用多少字节呢

    • byte:占用1个字节(8位),取值范围从-128到127。
    • short:占用2个字节(16位),取值范围从-32768到32767。
    • int:占用4个字节(32位),取值范围从-2147483648到2147483647。
    • long:占用8个字节(64位),取值范围从-9223372036854775808到9223372036854775807。
    • float:占用4个字节(32位),取值范围从-3.4e+38到3.4e+38。
    • double:占用8个字节(64位),取值范围从-1.7e+308到1.7e+308。
    • char:占用2个字节(16位),用于表示Unicode字符,取值范围从’\u0000’到’\uffff’。
    • boolean:虽然理论上占用1位(1/8字节),在实际应用中通常按1个字节处理

    JDK 9版本前后,双亲委派模型的变化

    在JDK 9版本前后,双亲委派模型的变化主要体现在类加载器的结构调整和模块化系统的引入。具体如下:

    1. 类加载器结构调整:在JDK 9之前,Java的类加载器通常分为三种:Bootstrap ClassLoader、Extension ClassLoader和Application ClassLoader。其中,Bootstrap ClassLoader负责加载核心的Java类库,Extension ClassLoader负责加载扩展类库,而Application ClassLoader则负责加载应用程序级别的类。在JDK 9之后,Extension ClassLoader被移除,取而代之的是Platform ClassLoader和Application ClassLoader。
    2. 模块化系统的引入:JDK 9引入了Java模块化系统(Java Platform Module System),这是为了实现更好的封装隔离机制。模块化系统的引入也意味着JVM对类加载架构进行了调整,这在一定程度上改变了双亲委派模型的工作方式。
    3. 安全性考虑:双亲委派模型的主要目的是为了保证Java官方类库的安全性,防止被开发者覆盖。在JDK 9之前,lib和lib\ext中的类库不会被破坏,而在JDK 9之后,由于模块化系统的引入,这种保护机制有所改变,但仍然保持了对核心类库的保护。
    4. 破坏双亲委派的方式:虽然双亲委派模型旨在保证安全性,但在某些情况下,如SPI机制和OSGi热替换机制,双亲委派模型会被破坏。JDK 9之后的模块化系统也可以看作是对双亲委派模型的一种破坏,因为它允许更多的灵活性和可配置性。

    总结来说,JDK 9的发布对双亲委派模型带来了显著的变化,这些变化旨在提高系统的模块化和可维护性,同时也考虑到了安全性和兼容性的需求。

    可以打破双亲委派模型吗

    可以打破双亲委派模型

    双亲委派模型是Java类加载器的一个核心概念,它确保了类加载的层次性和安全性。但在某些情况下,开发者可能会出于特定需求打破这一模型。以下是一些打破双亲委派模型的常见做法:

    1. 自定义类加载器:通过重写ClassLoader类的findClass()方法,可以实现对无法被父类加载器加载的类进行加载。如果需要直接打破双亲委派模型,可以重写loadClass()方法,但这通常涉及到更复杂的实现和潜在的风险。
    2. SPI机制:Service Provider Interface(SPI)机制允许第三方提供的JAR文件中的类可以被加载,即使它们位于CLASSPATH中。这是通过线程上下文类加载器来实现的,它可以在运行时决定使用哪个类加载器来加载类,从而绕过双亲委派模型的限制。
    3. 模块化系统:Java 9引入的模块化系统允许更加灵活的类可见性和加载策略,这在一定程度上也改变了双亲委派模型的工作方式。模块系统可以指定模块间的依赖关系和访问权限,从而实现对类加载流程的更精细控制。
    4. OSGi平台:OSGi服务平台是一个支持模块化的Java框架,它允许在运行时动态加载、更新和卸载模块。这种动态性要求能够打破双亲委派模型,以便在不同模块间隔离类加载过程。
    5. 应用服务器:许多应用服务器(如Tomcat)为了实现热部署和隔离不同的应用程序,也会打破双亲委派模型。它们通常会使用自定义的类加载器来加载应用程序的类,而不是依赖于系统的类加载器。

    需要注意的是,打破双亲委派模型可能会导致一些问题,例如安全问题和类版本冲突等。因此,在决定打破双亲委派模型时,应当仔细评估潜在的风险和收益。

    Tomcat为什么打破双亲委派模型

    Tomcat打破双亲委派模型的原因主要是为了实现不同Web应用程序之间的隔离性。具体原因如下:

    • 应用隔离性:Tomcat作为一个Web服务器,通常会部署多个Web应用程序。这些应用程序可能会包含相同类名的类或引用不同版本的同一个JAR包。如果遵循双亲委派模型,一个类只能被加载一次,这会导致潜在的冲突和版本控制问题。为了确保每个Web应用程序能够在自己的类加载器环境中独立运行,避免类版本冲突和类名冲突,Tomcat需要打破双亲委派模型。
    • 热部署:Tomcat支持热部署,即在不重启服务器的情况下部署或更新Web应用程序。为了实现这一点,每个Web应用程序必须能够在不同的类加载器中独立加载和卸载,这也要求打破双亲委派模型。

    此外,Tomcat通过使用自定义的WebAppClassLoader来实现这一机制。WebAppClassLoader会先于父类加载器尝试加载类,这样就能保证Web应用程序的类加载优先级高于系统类加载器,从而实现了应用程序间的隔离。

    综上所述,Tomcat打破双亲委派模型是为了提供更好的Web应用程序隔离性和灵活性,这对于运行多版本的JAR包和实现热部署等功能至关重要。

    布隆过滤器的实现原理

    布隆过滤器的实现原理基于哈希函数和位数组

    布隆过滤器是一种空间效率极高的概率型数据结构,它利用哈希函数的特性来检测一个元素是否属于某个集合。具体来说,布隆过滤器的工作过程包括两个核心步骤:元素的添加和元素的查询。

    • 元素添加:当一个元素需要被添加到布隆过滤器中时,会通过K个不同的哈希函数对该元素进行计算,得到K个哈希值。这些哈希值对应到位数组中的特定位置,然后将这些位置的值设置为1。
    • 元素查询:在查询一个元素时,同样使用那K个哈希函数计算出对应的位数组位置,然后检查这些位置是否都为1。如果所有位置都是1,那么元素可能属于集合;如果有任何一个位置是0,那么元素肯定不在集合中。

    需要注意的是,由于哈希函数的冲突和位数组的空间限制,布隆过滤器存在一定的误判率。这意味着在某些情况下,布隆过滤器可能会错误地判断一个不属于集合的元素为其成员。这个误判率与位数组的大小和使用的哈希函数数量有关:位数组越大,哈希函数越多,误判率就越低,但相应地占用的空间也会更大。

    综上所述,布隆过滤器通过哈希函数和位数组的结合,实现了一种空间和时间效率都非常高的数据结构,尤其适用于处理大规模数据集和快速检索的场景。然而,它在提供高效性能的同时,也引入了误判的可能性,这在设计系统时需要权衡考虑。

    CAP理论

    CAP理论指出,分布式系统无法同时满足一致性(Consistency)、可用性(Availability)和分区容忍性(Partition tolerance) 这三个要求。

    CAP理论是分布式计算领域的一个重要概念,它描述了在分布式系统中三个核心特性之间的关系和权衡:

    1. 一致性(Consistency):一致性是指分布式系统中的所有节点在同一时刻对某个数据的访问和修改结果是一致的。简单来说,就是数据的一致性,保证所有用户访问到的数据都是最新的。
    2. 可用性(Availability):可用性是指分布式系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求,系统都必须在有限的时间内给出回应,即系统提供的服务始终是可响应的。
    3. 分区容忍性(Partition tolerance):分区容忍性指的是分布式系统应该具有良好的分区适应能力,即使在消息传递发生延迟或故障时,系统也能够保持运行并接受新的请求。

    综上所述,根据CAP理论,任何分布式系统只能在这三个指标中选择满足其中的两项。例如,一个系统如果要求高度的一致性和可用性,那么在出现网络分区时,系统可能无法保持这两点;反之,如果系统设计强调分区容忍性和可用性,则在网络故障时可能会牺牲一致性。因此,设计分布式系统时需要根据实际需求和场景来决定在这三者之间如何取舍和平衡。

    BASE理论

    BASE理论是针对分布式系统的高可用性和一致性之间的权衡提出的实践性原则

    BASE理论是在CAP理论的基础上演化而来的,它更加符合大规模互联网服务的实际需求。在分布式系统中,CAP理论指出无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)。而在实际的互联网服务中,通常选择牺牲一定程度的一致性来保证系统的可用性和分区容错性。

    以下是BASE理论的核心内容:

    • 基本可用性(Basically Available):系统大部分时间都是可用的,但允许在特定情况下(如系统升级、故障恢复等)出现短暂的不可用状态。
    • 软状态(Soft State):系统的状态不需要时刻保持一致,允许存在中间状态,最终会达到一致。
    • 最终一致性(Eventual Consistency):系统的各个副本之间可能暂时不一致,但随着时间的推移,所有副本最终将达到一致的状态。

    综上所述,BASE理论提供了一种更为灵活的处理方式,适用于那些对一致性要求不是特别严格,但需要高可用性和良好用户体验的互联网应用。

    负载均衡策略

    常见的负载均衡策略包括以下几种:

    1. 轮询(Round Robin):这是最简单的负载均衡策略之一,它按照顺序将每个新的请求分发给后端服务器,依次循环。这种策略适用于后端服务器性能相近且每个请求处理时间大致相同的情况。
    2. 随机(Random):服务消费者每次会任意访问一个服务提供者,并且从概率角度看每个提供者被访问的概率一致。这种策略简单且易于实现,但可能导致服务器间的负载不均。
    3. 加权轮询(Weighted Round Robin):与普通轮询类似,但是可以为每台服务器分配一个权重值,权重越高的服务器处理的请求越多。这种策略适用于服务器性能不均等的情况。
    4. 最少连接(Least Connections):将新的请求发送给当前连接数最少的服务器。这种策略适用于处理长连接或不同请求处理时间差异较大的场景。在最小连接数负载均衡策略中,当请求第一次进来时,负载均衡器还不知道每个服务器的当前连接数,因此无法直接选择具有最少连接数的服务器。在这种情况下,负载均衡器通常会使用轮询策略来选择一个服务器来处理请求。 最小连接数负载均衡策略是最符合分布式系统的负载均衡策略
    5. 源地址哈希(Source IP Hash):根据客户端的IP地址进行哈希计算,然后将请求发送到对应的服务器。这种策略可以保证同一客户端的请求总是发送到同一台服务器,适用于需要保持客户端会话的场景。
    6. 一致性哈希(Consistent Hash):通过一致性哈希算法将所有服务器映射到一个环形空间上,根据请求的哈希值来确定处理该请求的服务器。这种策略在服务器增减时能够保持较好的负载均衡效果。
      综上所述,选择合适的负载均衡策略需要考虑多种因素,包括服务器的性能、请求的特点、系统的可扩展性以及成本等。在实际应用中,通常需要根据具体场景和需求来选择最合适的负载均衡策略。

    JVM

    垃圾回收器

    垃圾回收器(Garbage Collector,简称GC)是Java虚拟机(JVM)用来自动管理内存的机制,主要负责回收堆内存中不再使用的对象,以释放内存资源

    垃圾回收器使用不同的算法来处理新生代和老年代的内存回收。具体来说:

    • 新生代回收:新生代通常使用复制算法,该算法将新生代分为Eden区和两个Survivor区(S0和S1)。新创建的对象首先在Eden区分配,当Eden区满时,触发Minor GC。存活的对象被复制到Survivor区,如此往复,每经历一次GC,对象年龄增加。达到一定年龄阈值(如15岁)的对象会晋升到老年代。
    • 老年代回收:老年代通常容纳生命周期较长、大对象或已经从新生代晋升的对象。当老年代空间不足时,会发生Full GC,这个过程比Minor GC慢很多,因为它涉及的内存区域更大、对象更多。

    JVM提供多种垃圾回收器,它们适用于不同场景和需求:

    • Serial收集器:它是最简单的收集器,使用单线程进行垃圾回收,会暂停所有应用线程。适合客户端应用。
    • ParNew收集器:它是Serial的多线程版本,用于提高回收效率。
    • Parallel Scavenge收集器:它关注于降低垃圾收集的停顿时间,通过多线程并行处理提高效率。
    • Serial Old收集器:它是Serial收集器用于老年代的版本,同样是单线程工作。
    • Parallel Old收集器:它是Parallel Scavenge用于老年代的版本,多线程并行收集。
    • CMS收集器:它是一种并发标记清除的收集器,试图减少应用程序暂停时间。
    • G1收集器:它将堆划分为多个区域,可以预测停顿时间,避免Full GC,适合大内存服务器应用。

    总的来说,选择合适的垃圾回收器需要根据应用程序的特点和性能要求来决定。例如,对于响应时间敏感的应用,可以选择CMS或G1以避免长时间的停顿。而对于吞吐量优先的应用,可以选择Parallel Scavenge或Parallel Old。在实际使用中,还可以根据监控数据和分析结果调整垃圾回收策略,以获得最佳的性能表现。

    垃圾回收算法

    垃圾回收算法是自动内存管理机制的核心,它们负责识别和回收那些不再被程序使用的对象所占用的内存。以下是一些常见的垃圾回收算法:

    1. 引用计数法:这是一种简单的垃圾回收算法,它通过为每个对象维护一个引用计数来工作。每当有一个新的引用指向该对象时,计数增加;当一个引用不再指向该对象时,计数减少。当对象的引用计数降至零时,意味着该对象不再被使用,可以被回收。这种方法的优点是实现简单,但缺点是无法处理循环引用的情况,即当两个或更多的对象彼此引用形成一个闭环时,即使这些对象不再被外部引用,它们的引用计数也不会为零,因此不会被回收。
    2. 复制算法:这种算法将内存分为两个相等的部分,每次只使用其中一半。当这一半的内存用完后,就将还在使用的对象复制到另一半中,然后清除掉已经使用过的那一半内存。这种方法通常用于新生代的垃圾回收,因为它的效率较高,但是内存利用率较低。
    3. 标记-清除法:这种算法首先标记出所有活动的对象(即仍在使用中的对象),然后清除掉未被标记的对象。这种方法适用于老年代的垃圾回收,它可以提高内存利用率,但是可能会产生内存碎片。
    4. 标记-压缩法:这种方法在标记-清除法的基础上进行了改进,它在标记活动对象的同时,会将这些对象向一端移动,从而避免了内存碎片的问题。
    5. 分代算法:这种算法基于这样一个观察:不同生命周期的对象往往具有不同的特性。因此,它将内存分为几个代(通常是新生代和老年代),并根据每个代的特点采用不同的回收策略。
    6. Serial收集器:这是一种单线程收集器,它在进行垃圾回收时会暂停应用程序的运行。它的优点是简单高效,但是由于它会暂停应用程序,所以不适合对响应时间要求较高的场景。

    综上所述,垃圾回收算法的目标是在不影响程序运行效率的前提下,尽可能地释放不再使用的内存。每种算法都有其适用的场景和优缺点,现代垃圾回收器通常会结合使用多种算法,以达到最佳的性能和效率。

    什么是三色标记算法(阿里真题)

    三色标记算法是一种高效的垃圾回收方法,它通过将对象分为白色、灰色和黑色三种状态来识别哪些对象是垃圾。具体来说:

    • 白色对象:是指那些尚未被垃圾回收器访问过的对象。
    • 灰色对象:是当前已经被垃圾回收器访问过,但还没有对其引用的所有对象进行处理的。
    • 黑色对象:是完全被处理过的对象,即已经访问了该对象以及它引用的所有对象。

    这个算法的核心在于将垃圾回收的过程分为多个阶段,以确保在不影响应用程序运行的情况下进行垃圾回收。这些阶段包括:

    • 初始标记阶段:在这一阶段,所有从GC Roots直接可达的对象都会被标记为灰色。这个过程需要暂停应用程序的执行,即所谓的"Stop the World"。
    • 并发标记阶段:在这一阶段,垃圾回收器会并发地遍历灰色对象,并将它们引用的对象标记为灰色,同时将这些对象转变为黑色。这个过程不需要暂停应用程序。
    • 重新标记阶段:这一阶段是为了处理在并发标记阶段由于应用程序的运行而产生的新对象。这个阶段通常也需要"Stop the World"。
    • 并发清除阶段:最后,系统会清除所有仍然标记为白色的对象,因为这些对象被认为是垃圾。

    总的来说,三色标记算法通过这种分阶段的方法,能够在不显著影响应用程序性能的同时,有效地进行垃圾回收。这种方法特别适用于现代的多核处理器和大规模堆内存的应用场景。

    CMS垃圾回收器的垃圾收集过程

    CMS垃圾回收器的垃圾收集过程包括初始标记、并发标记、重新标记和并发清除四个阶段。具体如下:

    1. 初始标记:这是垃圾回收过程的第一步,CMS垃圾回收器会暂停所有的应用线程(Stop the World),然后从根对象开始标记所有直接可达的对象。这个过程通常是非常快速的。
    2. 并发标记:在初始标记之后,CMS垃圾回收器会与应用程序并发运行,继续标记从根对象可达的所有存活对象。这个步骤是为了减少整个垃圾回收过程中应用程序的停顿时间。
    3. 重新标记:由于在并发标记阶段应用程序仍在运行,可能会有新的对象产生或原有的对象状态改变,因此需要重新标记那些可能发生变化的对象。这个阶段同样需要暂停应用程序,但停顿时间也相对较短。
    4. 并发清除:最后,CMS垃圾回收器会清除掉所有未被标记的对象,即这些对象被认为是垃圾,可以回收它们占用的内存。这个阶段也是与应用程序并发执行的,以减少对应用程序的影响。

    总的来说,CMS垃圾回收器的设计目标是尽量减少垃圾回收过程中应用程序的停顿时间,它主要适用于对响应时间要求较高的应用场景。然而,由于CMS在进行垃圾回收时不会压缩堆内存,可能会导致内存碎片问题。此外,如果堆内存不足,CMS可能会触发Full GC,这时的停顿时间会比较长。因此,在使用CMS垃圾回收器时,需要根据应用程序的特点和性能要求来合理配置JVM参数,以确保系统的稳定性和效率。

    G1垃圾回收器的垃圾收集过程

    G1 垃圾回收器是 Java 9 中引入的一种垃圾收集器,它是一种服务器端的垃圾收集器,适用于大内存和多处理器的环境。G1 垃圾回收器的垃圾收集过程分为以下几个阶段:

    1. 初始标记:暂停所有用户线程,标记所有根对象(如线程栈中的对象、静态变量、全局变量等),这个阶段会触发一次年轻代垃圾回收。
    2. 并发标记:与用户线程并发执行,标记所有可达对象。
    3. 最终标记:暂停所有用户线程,标记所有在并发标记阶段被遗漏的可达对象。
    4. 筛选回收:与用户线程并发执行,根据标记结果,选择需要回收的区域,并将存活对象复制到新的区域。

    在 G1 垃圾回收器中,垃圾收集过程是并发进行的,因此可以减少垃圾收集对应用程序性能的影响。同时,G1 垃圾回收器还支持预测垃圾收集,可以根据应用程序的行为和内存使用情况,动态调整垃圾收集的时间和区域,以提高垃圾收集的效率。

    G1垃圾回收器

    G1垃圾回收器(Garbage-First)是Java HotSpot虚拟机中的一种垃圾回收器,它旨在满足低延迟和高吞吐量的需求,特别适合多核CPU和大内存的应用环境。以下是G1垃圾回收器的一些关键特点:

    • 分区式堆结构:G1将Java堆分为多个大小相等的独立区域,这些区域被称为Region。这种设计使得G1能够并行地在多个Region中进行垃圾回收,从而提高了效率。
    • 分代收集:虽然G1采用了分区式的堆结构,但它仍然遵循分代收集的原则,将对象分为年轻代和老年代。年轻代中有Eden区和Survivor区,但与传统的分代收集器不同,G1不要求这些区域在堆中是连续的。
    • 预测性暂停时间:G1垃圾回收器的一个显著特点是它可以预测垃圾回收的暂停时间,这对于需要实时响应的应用程序来说非常重要。
    • 增量式收集:G1在进行垃圾回收时,不会一次性处理整个堆空间,而是选择一部分Region进行处理,这样可以减少单次垃圾回收的暂停时间
    • 并发标记:在G1的垃圾回收过程中,标记阶段可以与应用程序线程并发执行,这有助于减少应用程序的停顿时间。
    • 优化点:G1垃圾回收器针对大内存堆的场景进行了优化,能够有效地管理大量的内存区域,提高垃圾回收的效率。

    总的来说,G1垃圾回收器通过其独特的设计和优化,为大内存、多核处理器的服务器端应用提供了高效的垃圾回收解决方案。它是官方推荐用于代替CMS收集器的选项,尤其是在对延迟敏感的应用中。

    G1垃圾回收器和CMS 垃圾回收器的区别

    G1垃圾回收器与CMS垃圾回收器在内存结构、收集范围和使用场景等方面存在显著差异。具体如下:

    1. 内存结构:G1将堆内存划分为多个Region,这些区域是逻辑上连续的,但物理上可以不连续。每个Region可以是Eden、Survivor或Old区的一部分,而CMS则遵循传统的分代模型,有连续的内存空间分配给新生代和老年代。
    2. 收集范围:G1可以在任何Region中进行垃圾回收,而CMS只针对老年代进行垃圾回收。这意味着G1可以更灵活地管理内存,而CMS则专注于老年代的垃圾收集。
    3. STW(Stop-The-World):G1在进行垃圾回收时会尝试限制STW的时间,使得停顿可预测,这对于响应时间敏感的应用非常重要。相比之下,CMS的STW通常发生在并发标记阶段结束后,且持续时间较长。
    4. 使用场景:由于G1提供了预测性的停顿时间,它适用于对延迟敏感的应用程序,特别是在大内存和多核服务器环境中。而CMS适用于对响应时间要求较高的应用场景,尤其是当应用程序的内存使用相对稳定时。

    总的来说,G1和CMS都是为了满足不同应用场景下的垃圾回收需求而设计的。G1通过区域划分和预测性停顿时间优化了垃圾回收过程,而CMS则专注于减少应用程序的停顿时间。在选择垃圾回收器时,应根据应用程序的具体需求和特点来决定使用哪种回收器。

    有了CMS,为什么还要引入G1

    CMS 垃圾收集器和 G1 垃圾收集器都是 Java 垃圾收集器,它们都可以用于垃圾收集和内存管理。但是,它们有一些不同的特点和适用场景。

    CMS 垃圾收集器是一种老年代垃圾收集器,它使用标记-清除算法进行垃圾收集。CMS 垃圾收集器的优点是可以与用户线程并发执行,因此可以减少垃圾收集对应用程序性能的影响。但是,CMS 垃圾收集器也有一些缺点,例如在垃圾收集过程中会产生大量的内存碎片,需要进行内存整理,这会导致应用程序暂停。

    G1 垃圾收集器是一种新的垃圾收集器,它使用标记-整理算法进行垃圾收集。G1 垃圾收集器的优点是可以更好地处理大内存和多处理器的环境,同时可以减少垃圾收集对应用程序性能的影响。G1 垃圾收集器还可以根据应用程序的行为和内存使用情况,动态调整垃圾收集的时间和区域,以提高垃圾收集的效率。

    因此,引入 G1 垃圾收集器是为了更好地处理大内存和多处理器的环境,同时可以减少垃圾收集对应用程序性能的影响。

    Java 应用程序发生 OOM(Out Of Memory)异常时,如果抓取转储Heap Dump

    当 Java 应用程序发生 OOM(Out Of Memory)异常时,可以通过设置 JVM 参数来抓取堆转储文件(Heap Dump),以便进行内存分析和诊断。以下是抓取堆转储文件的常见步骤:

    1. 在启动 Java 应用程序时,添加以下 JVM 参数:
    -XX:+HeapDumpOnOutOfMemoryError
    -XX:HeapDumpPath=/path/to/heapdump.hprof
    
    • 1
    • 2

    上述参数中的-XX:+HeapDumpOnOutOfMemoryError表示当发生 OOM 异常时,JVM 会自动生成堆转储文件。-XX:HeapDumpPath=/path/to/heapdump.hprof指定了堆转储文件的保存路径和文件名。你可以根据实际情况修改路径和文件名。

    1. 运行 Java 应用程序,触发 OOM 异常。

    2. OOM 异常发生后,JVM 会将堆转储文件生成到指定的路径。

    3. 使用 Java 内存分析工具(如 Eclipse MAT、Java Mission Control、HeapHero 等)打开堆转储文件进行分析。

    请注意,抓取堆转储文件可能会导致一定的性能开销,因此只应在开发和测试环境中使用,或者在生产环境中出现问题时临时启用。同时,堆转储文件的大小可能很大,需要确保有足够的磁盘空间来保存。

    一些常见的jvm参数配置

    JVM提供了众多的参数用于调优和监控Java应用程序的运行,以下是一些补充的常用JVM参数及其含义:

    参数含义
    -Xms设置JVM初始堆内存大小。例如,-Xms20m表示初始堆大小为20MB。
    -Xmx设置JVM最大可用内存大小。例如,-Xmx20m表示最大堆大小为20MB。
    -Xmn设置新生代的大小。例如,-Xmn10m表示新生代大小为10MB。
    -Xss设置每个线程的堆栈大小。例如,-Xss128k表示每个线程栈大小为128KB。
    -verbose:gc输出每次垃圾收集的信息。
    -XX:+UseConcMarkSweepGC使用CMS垃圾收集器。
    -XX:+UseParallelGC使用并行垃圾收集器。
    -XX:+UseSerialGC使用串行垃圾收集器。
    -XX:NewRatio设置老年代与新生代的比例。例如,-XX:NewRatio=3表示老年代是新生代的3倍大。
    -XX:SurvivorRatio设置Eden区与Survivor区的比例。例如,-XX:SurvivorRatio=8表示Eden区是Survivor区的8倍大。
    -XX:MaxPermSize设置永久代的最大值。
    -XX:MaxMetaspaceSize设置元空间的最大值。
    -XX:MetaspaceSize设置元空间的初始值。
    -XX:+PrintGCDetails打印详细的GC信息。
    -XX:+PrintGCDateStamps在GC日志中添加时间戳。
    -XX:+HeapDumpOnOutOfMemoryError在发生内存溢出时生成堆转储文件。
    -XX:+TraceClassLoading跟踪类的加载过程。
    -XX:+UseG1GC使用G1垃圾收集器。

    除了上述参数,还有更多专门的参数用于特定的调优场景,例如与编译器优化、内存屏障、JIT编译相关的参数等。在实际工作中,根据应用的需求和系统环境,可能需要调整这些参数以获得最佳的运行效果。了解这些参数的作用可以帮助开发者更好地控制Java应用程序的性能和行为。

    实战问题

    cpu过高如何快速定位是哪一句代码导致的问题

    1. 使用top命令查看系统的 CPU 使用情况。在终端中输入top命令,然后按下1键,可以查看每个 CPU 核心的使用情况。如果某个进程的 CPU 使用率过高,可以通过top命令的PID列找到该进程的进程 ID(PID)
    2. top -H -p PID,这是一个 Linux 命令,用于显示特定进程(PID)的线程信息。其中:
      top:是一个常用的系统监控工具,用于实时显示系统的进程信息。
      -H:表示以线程模式显示。
      -p PID:指定要显示的进程的 PID。
      通过执行top -H -p PID命令,你可以查看指定进程的线程信息,包括每个线程的 ID、CPU 使用率、内存使用情况等。这对于调试和性能分析非常有用,特别是在处理多线程应用程序时。
    3. linux 将线程id转换为16进制 printf '0x%x\n' XXX ,会输出16进制的线程PID
    4. jstack 进程PID|grep 16进制线程PID -A 20
      此时就可以看到具体是哪句代码的产生的问题了;

    内存飙高问题怎么排查

    好的,要排查内存飙高问题,可以从以下几个方面入手:

    1. 使用top命令:top命令可以实时显示系统中最活跃的进程,并提供了一些有用的信息,如进程的 CPU 使用率、内存使用情况等。通过观察top命令的输出,可以找到内存使用过高的进程。
    2. 使用ps命令:ps命令可以列出系统中正在运行的进程。通过添加-aux参数,可以显示每个进程的详细信息,包括进程的 ID、CPU 使用率、内存使用情况等。通过观察ps命令的输出,可以找到内存使用过高的进程。
    3. 使用free命令:free命令可以显示系统的内存使用情况,包括物理内存、交换内存、空闲内存等。通过观察free命令的输出,可以了解系统的内存使用情况。
    4. 使用jmap命令:jmap命令可以生成 Java 进程的内存转储文件,并将其保存到指定的文件中。通过分析内存转储文件,可以找到内存泄漏的对象。
    5. 使用内存分析工具:有许多内存分析工具可以帮助你查找内存泄漏的原因。例如,Java 中的Heapdump工具可以生成 Java 堆转储文件,然后使用MAT(Memory Analyzer Tool)工具分析堆转储文件,找出内存泄漏的对象。

    请注意,以上方法仅适用于 Linux 系统。如果你使用的是其他操作系统,可能需要使用不同的工具和命令。

    如何查看某个特定端口是否被占用

    要查看某个特定端口是否被占用,您可以通过以下步骤进行操作:

    1. 打开命令提示符:您需要以管理员身份运行命令提示符。在Windows系统中,可以通过按下Win+R组合键,输入cmd并回车来打开命令提示符窗口。如果您的系统是Mac或Linux,可以打开终端应用程序。
    2. 查看所有端口占用情况:在命令提示符或终端中输入netstat -ano命令并回车。这个命令会列出所有端口的使用情况。在Mac或Linux系统中,可能需要使用sudo netstat -ano来获取更详细的信息。
    3. 查看指定端口占用情况:如果您想要查看特定端口的情况,可以使用netstat -anp | grep 端口号命令。例如,如果您想查看端口号为8080的端口是否被占用,可以输入netstat -anp | grep 8080
    4. 分析结果:在命令的输出结果中,找到与您要查询的端口号相对应的行。如果该端口处于LISTEN状态,说明它已经被占用。您还可以看到占用该端口的进程ID(PID)。
    5. 根据PID查看进程或程序:如果您想知道哪个程序或进程占用了该端口,可以根据PID查找。在Windows系统中,可以使用tasklist | findstr PID命令;在Mac或Linux系统中,可以使用ps -p PID命令。
    6. 结束相应进程:如果需要释放该端口,您可以根据PID结束相应的进程。在Windows系统中,可以使用taskkill /F /PID 进程号命令;在Mac或Linux系统中,可以使用kill -9 PID命令。

    总的来说,通过以上步骤,您可以有效地检查特定端口是否被占用,并采取相应的措施。在执行这些操作时,请确保您有足够的权限,并且小心操作,以免影响系统的正常运行。

    Docker

    常见命令

    Docker 常见命令包括容器操作、镜像操作、网络和数据卷操作,以及日志和事件操作等。具体如下:

    1. 容器操作命令:
    • docker start <容器名或ID>: 启动一个或多个已经被停止的容器。
    • docker stop <容器名或ID>: 停止一个运行中的容器。
    • docker restart <容器名或ID>: 重启容器。
    • docker ps: 列出所有正在运行的容器。
    • docker ps -a: 列出所有的容器,包括没有运行的。
    • docker inspect <容器名或ID>: 查看容器的详细信息。
    • docker exec -it <容器名或ID> /bin/bash: 进入容器的交互式终端。
    1. 镜像操作命令:
    • docker pull <镜像名>:<标签>: 从 Docker 仓库拉取镜像。
    • docker push <镜像名>:<标签>: 将镜像推送到 Docker 仓库。
    • docker build -t <镜像名>:<标签> : 根据 Dockerfile 构建镜像。
    • docker images: 列出本地所有的镜像。
    1. 网络操作命令:
    • docker network ls: 列出所有网络。
    • docker network create <网络名>: 创建一个新的网络。
    1. 数据卷操作命令:
    • docker volume create <卷名>: 创建一个新的数据卷。
    • docker volume rm <卷名>: 删除一个数据卷。
    1. 日志和事件操作命令:
    • docker logs -f <容器名或ID>: 查看容器的日志。
    • docker events: 查看 Docker 的事件。

    以上是一些常用的 Docker 命令,对于使用 Docker 进行开发、部署和管理容器化应用程序非常实用。

  • 相关阅读:
    分治&暴力求解最近点对问题 + 时间性能量化分析
    获取所有非manager的员工emp_no
    元数据概述
    10 Using Implicit Rules
    JAVA经典百题之判断质数
    MySQL数据库学习【进阶篇】
    快速搭建 SpringCloud Alibaba Nacos 配置中心!
    css强制显示一行
    什么是项目管理,如何做好项目管理?
    leetcode-合并二叉树-90
  • 原文地址:https://blog.csdn.net/weixin_37264997/article/details/136164862