Jinqq's Home

证明自己

一、书名和作者

  • 书名:《Scrum要素》(The Elements of Scrum
  • 作者:Chris Sims,Hillary Louise Johnson

二、书籍概览

  • 主要论点和结构
    本书是一部系统阐述敏捷项目管理框架Scrum的核心概念及其实践要点的书籍。全书以简单易懂的语言和丰富的实践案例,围绕Scrum的基本角色(如产品负责人、Scrum Master和开发团队)、关键活动(如每日站会、冲刺规划会、回顾会)以及核心价值观展开。作者用逐步引导的方式帮助读者掌握Scrum的关键要素,强调透明性、持续改进与团队协作的必要性。
  • 目标读者和应用场景
    本书面向对Scrum感兴趣的项目管理者、团队负责人以及软件开发团队成员,尤其适合希望通过敏捷方法改进团队生产力的从业者。其应用场景涵盖从初学者对Scrum的入门学习,到经验丰富的实践者对现有流程的改进优化。
阅读全文 »

消息队列的作用

  1. 异步
  2. 解耦
  3. 削峰

RocketMQ的消息模型

image-20250311201626219
  1. 每个主题下有多个队列
  2. 一个消费者组共同消费一个主题,一个队列只会被一个消费者消费
  3. 不同消费者组中,每个消费者维护消费的队列已消费的位置(offset),防止重复消费

RocketMQ的架构图

image-20250311202049950

NameServer:注册中心,进行Broker管理和路由管理。Broker会将自己的信息注册到NameServer中,消费者和生产者从NameServer中获取路由表然后照着路由表的信息和对应的Broker进行通信。

Broker:消息队列服务器,负责消息的存储、投递、查询,保证高可用性。一个Topic分布在多个Broker上,而每个Broker可以配置多个Topic。

Producer:生产者。

Consumer:消费者。

image-20250311202625113

  1. Broker做了集群和主从部署。slave定时从master同步数据,当master宕机时,slave可以提供消费服务,但不能写入消息。
  2. nameserver做了集群部署,且去中心化。每个broker与所有nameserver保持长连接,并且在每隔 30 秒broker会向所有nameserver发送心跳,心跳包含了自身的 Topic 配置信息。
  3. 在生产者需要向 Broker 发送消息的时候,需要先从 NameServer获取关于 Broker 的路由信息,然后通过 轮询 的方法去向每个队列中生产数据以达到 负载均衡 的效果。
  4. 消费者通过 NameServer 获取所有 Broker的路由信息后,向 Broker 发送 Pull请求来获取消息数据。Consumer可以以两种模式启动—— 广播(Broadcast)和集群(Cluster)。广播模式下,一条消息会发送给 同一个消费组中的所有消费者 ,集群模式下消息只会发送给一个消费者。

RocketMQ功能

普通消息

软件成分分析系统

**项目简介:**开发基于包管理器构建过程的软件成分分析系统,采用BuildScan动态检测技术,支持Java/JS/Python/Go多语言依赖 关系解析,实现漏洞风险识别、许可证合规分析、软件资产统一管理,并自动化生成符合行业标准的SBOM(软件物料清单)及 可视化安全报告。

**技术栈:**SpringBoot,PostgreSQL,Redis,RocketMQ,maven/gradle/npm/pip/go modules包管理器

主要功能:

  1. 提取项目中的配置文件,基于包管理器构建完整依赖树,并即时返回初步结果。

  2. 采用 RocketMQ 将组件信息采集、漏洞匹配和许可证识别任务异步拆分,并结合多线程并发处理,加速整体数据处理流程。

  3. 依据CPE规范构建组件标识,通过PostgreSQL的pg_trgm插件+GIN索引实现相似度高效匹配漏洞信息。

  4. 聚合多源许可证数据,分析权利/义务条款并构建冲突检测规则引擎。

  5. 生成符合CycloneDX/SPDX规范的软件物料清单,支持XML/JSON多格式输出

异步处理

在 SCA(软件成分分析)系统的异步设计中,我们利用 RocketMQ 进行解耦,将 组件查询、缺失组件爬取、漏洞匹配 这三个 I/O 密集型任务异步化,提高系统的吞吐量和并发能力。

具体来说,我们采用 消息队列 设计,将 解析依赖树后 的组件查询任务推送到 component-query-queue,如果组件缺失,则发送爬取任务到 component-crawl-queue,每一个组件查询完成后,再异步执行 vulnerability-match-queue 进行漏洞匹配。

在实现上,我们使用 RocketMQ 生产者推送任务,消费者监听队列 进行异步处理,并结合 线程池并发查询 以及 Redis 幂等性校验,确保任务高效执行,同时防止重复消费或数据不一致的问题。

这样,整个 SCA 任务执行过程能够更快响应,避免同步阻塞,提高系统扩展性和稳定性,同时也为后续增加更多语言或优化流程提供了良好的架构支持。

多语言框架

SCA 系统需要分析 Java、Python、JavaScript、Go 四种语言的项目,每种语言的处理方式不同,但总体流程相同,具体包括以下步骤:

  1. 上传项目
  2. 生成依赖树(执行包管理器命令)
  3. 解析依赖树,(然后将组件查询任务推送到 component-query-queue,最后返回前端)
  4. 查询组件信息,(将缺失组件爬取任务推送到 component-crawl-queue,然后结束)
  5. 爬取缺失组件信息,(同时匹配许可证),线程池处理,并将漏洞匹配任务推送到vulnerability-match-queue ,然后结束
  6. 匹配组件的漏洞信息,结束

具体的,首先定义一个统一的sca处理器,和一个统一的消费者。然后定义不同语言的策略,和一个工厂用来根据语言生成策略。上传项目后,sca处理器根据语言,从工厂获得对应语言的策略,然后调用策略的方法生成依赖树、解析依赖树,然后将组件查询任务推送到 component-query-queue,并返回前端。

消费者接受消息后,首先根据topic选择,

漏洞匹配

CPE

CPE(Common Platform Enumeration)是一个用于标识软件和硬件平台的标准化命名系统。CPE 使用特定格式来表示一个软件、硬件或操作系统的标识符。通常,CPE 标识符遵循类似于下面的格式:

1
cpe:2.3:a:apache:http_server:2.4.49:*:*:*:*:*:*:*
  • cpe:2.3:表示使用的是 CPE 版本 2.3。
  • a:表示组件类型(软件/硬件/OS)
  • apache:厂商名。
  • http_server:产品名。
  • 2.4.49:版本号。
  • *:*:*:*:*:*:*:更新版本、发行版、语言、软件类型、硬件架构、其他等

pg_trgm 插件

pg_trgm 插件是 PostgreSQL 中用于加速模糊匹配和相似度计算的工具,它使用 trigram(三元组)来将字符串分解为连续的三字符片段,并使用这些片段来计算两个字符串之间的相似度。通过这种方式,pg_trgm 可以帮助我们计算字符串间的相似度,即使它们存在拼写错误、字符交换或省略。

在这个过程中,pg_trgm 插件可以对 CPE 标识符进行拆解,以便通过类似的字符串或不完全匹配找到相似的漏洞信息。CPE 标识符可能会有一些拼写错误或不同版本的表达方式,因此使用 pg_trgm 插件能够有效捕捉到相似的组件信息。

具体的原理:例如对于hello,可以分为{“ h”, “ he”, “hel”, “ell”, “llo”, “lo “},另一个字符串也同样划分后,通过计算 **两个三元组集合的交集 / 并集 **作为相似度。

GIN索引(倒排索引)

GIN(Generalized Inverted Index)是 PostgreSQL 的一种索引类型,用于加速处理包含多值数据的字段,如数组、文本搜索、JSONB 等。GIN 索引为每个值构建倒排索引,这使得查询速度显著提高,特别是在需要进行模糊匹配、全文搜索时。

倒排索引会记录每个值在哪些文档(或行)中出现。比如,对于一个文本字段,你的倒排索引可能看起来像这样:

  • word1 -> {1, 2, 5}
  • word2 -> {1, 3}
  • word3 -> {2, 4}

这个倒排结构允许快速定位包含某个单词的行。

整体流程

  1. 根据组件名、厂商、版本等信息构建自定义cpe,其中有含版本号和不含版本号两个cpe

  2. 根据自定义cpe,到cve_cpe表中进行相似度查询,找到所有对应的cve_cpe。

    2.1. trgm将cpe拆解为一个个三元组

    2.2. 每一个三元组都建立了倒排索引,可以快速定位到匹配的行

    2.3. 再计算这些行的cpe与自定义cpe的相似度,筛选出相似度高的

  3. 对这些找到的cve_cpe进行召回,限定cpe中的单词至少需精准包含组件名称单词的半数以上以防止误判,以及检查组件版本是否处于规定的漏洞影响起始和终止版本之间

  4. 根据cve_cpe中的cve,到cve表中查到对应的漏洞

RocketMQ

顺序性

Rocketmq有两种顺序消费模式。

全局顺序(Global Order)

  • 所有消息 只能进入 同一个队列(Queue),并且由 一个消费者 进行顺序消费。
  • 优点:保证全局严格顺序。
  • 缺点:吞吐量受限,单个消费者成为性能瓶颈。

分区顺序(Partition Order)

  • 不同业务标识(如订单 ID) 绑定到特定 消息队列(Queue),同一个 Queue 内保证顺序,但不同 Queue 可并行消费。
  • 优点:既保证局部顺序,又能提高吞吐量。
  • 缺点:如果一个 Queue 处理变慢,可能会影响该 Queue 内的所有消息。

分区消费实现

生产者在sendMessage时,自定义一个MessageQueueSelector消息队列选择器,在里面手动选择queue,例如可以:

1
2
3
4
5
SendResult sendResult = producer.send(msg, (mqs, msg1, arg) -> {
int orderId = (int) arg; // 从 arg 获取 orderId
int queueIndex = orderId % mqs.size(); // 计算要进入的队列索引
return mqs.get(queueIndex);
}, orderId); // 这里传入 orderId 作为 arg

消息发送的可靠性

RocketMQ 提供了几种不同的 发送消息的模式,可以保证消息是否成功发送到 Broker。

同步发送(Sync)

  • 同步发送是最常见的方式,生产者发送消息后,会 阻塞等待 Broker 的返回结果,直到收到确认信息。同步发送能确保消息确实到达 Broker,并提供准确的发送结果。
  • 同步发送在发送失败时,通常会依赖于 重试机制。RocketMQ 提供了自动重试的功能,在发生发送失败时,它会 自动重试 多次,直到达到最大重试次数,或者成功发送消息。

异步发送(Async)

  • 异步发送是另一种方式,生产者发送消息后不会等待返回结果,而是 通过回调函数 处理发送结果。虽然不阻塞,但可能会因为消息发送失败而需要在回调中处理。
  • 异步发送的失败需要在回调函数中显式处理,RocketMQ 不会自动重试。

一次消息发送(Oneway)

  • 在这种模式下,生产者 不等待任何确认,直接将消息发送到 Broker。适用于 不关心消息是否成功发送 的场景。

消息保存的可靠性

  • 异步/同步刷盘
  • 异步/同步主从复制

消息接收的可靠性

取消自动,消费完成后手动ack

延迟重试(每次延迟增大)

多次重试失败后进入死信队列,手动处理或定时任务处理

幂等

redis id / 数据库id

Redis过期key删除策略

常用的过期数据的删除策略就下面这几种:

  1. 惰性删除:只会在取出/查询 key 的时候才对数据进行过期检查。这种方式对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。
  2. 定期删除:周期性地随机从设置了过期时间的 key 中抽查一批,然后逐个检查这些 key 是否过期,过期就删除 key。相比于惰性删除,定期删除对内存更友好,对 CPU 不太友好。
  3. 延迟队列:把设置过期时间的 key 放到一个延迟队列里,到期之后就删除 key。这种方式可以保证每个过期 key 都能被删除,但维护延迟队列太麻烦,队列本身也要占用资源。
  4. 定时删除:每个设置了过期时间的 key 都会在设置的时间到达时立即被删除。这种方法可以确保内存中不会有过期的键,但是它对 CPU 的压力最大,因为它需要为每个键都设置一个定时器。

Redis 采用的那种删除策略呢?

Redis 采用的是 定期删除+惰性/懒汉式删除 结合的策略,这也是大部分缓存框架的选择。定期删除对内存更加友好,惰性删除对 CPU 更加友好。两者各有千秋,结合起来使用既能兼顾 CPU 友好,又能兼顾内存友好。

也就是,首先惰性删除,在查询一个 key 的时候,Redis 首先检查该 key 是否存在于过期字典中(时间复杂度为 O(1)),如果不在就直接返回,在的话需要判断一下这个 key 是否过期,过期直接删除 key 然后返回 null。

然后除此之外,Redis会有定期删除,也就是周期性地随机从设置了过期时间的key中抽查一批,看这些key是否过期。并且Redis设置了下面几个参数:

1
2
3
4
5
6
7
#define ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC 25 /* Max % of CPU to use. */
#define ACTIVE_EXPIRE_CYCLE_ACCEPTABLE_STALE 10 /* % of stale keys after which we do extra efforts. */
#define ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP 20 /* Keys for each DB loop. */
# 默认为 10
hz 10
# 默认开启
dynamic-hz yes

依次的含义如下:

  • 每次执行时间最多为25ms,超过阈值则中断这一次定期删除循环,以避免使用过多的 CPU 时间。

  • 如果这一批过期的 key 比例超过一个比例,就会重复执行此删除流程,以更积极地清理过期 key。相应地,如果过期的 key 比例低于这个比例,就会中断这一次定期删除循环,避免做过多的工作而获得很少的内存回收。

  • 每次会随机选择 20 个设置了过期时间的 key 判断是否过期。

  • 定期删除的频率是由 hz 参数控制的。hz 默认为 10,代表每秒执行 10 次,也就是每秒钟进行 10 次尝试来查找并删除过期的 key。

    hz 的取值范围为 1~500。增大 hz 参数的值会提升定期删除的频率。如果你想要更频繁地执行定期删除任务,可以适当增加 hz 的值,但这会加 CPU 的使用率。根据 Redis 官方建议,hz 的值不建议超过 100,对于大部分用户使用默认的 10 就足够了。

  • dynamic-hz 这个参数开启之后 Redis 就会在 hz 的基础上动态计算一个值。Redis 提供并默认启用了使用自适应 hz 值的能力,

Redis内存淘汰机制

Redis 的内存淘汰策略只有在运行内存达到了配置的最大内存阈值时才会触发,这个阈值是通过redis.confmaxmemory参数来定义的。64 位操作系统下,maxmemory 默认为 0 ,表示不限制内存大小。32 位操作系统下,默认的最大内存值是 3GB。

Redis 提供了 6 种内存淘汰策略:

  1. volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。
  2. volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。
  3. volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。
  4. volatile-lfu:从已设置过期时间的数据集中挑选最不经常使用的数据淘汰。
  5. allkeys-lru:从数据集中移除最近最少使用的数据淘汰。
  6. allkeys-random:从数据集中任意选择数据淘汰。
  7. allkeys-lfu:从数据集中移除最不经常使用的数据淘汰。
  8. no-eviction(默认内存淘汰策略):禁止驱逐数据,当内存不足以容纳新写入数据时,新写入操作会报错。

Redis持久化机制

Redis支持三种持久化方式:

  • RDB(快照)
  • AOF(只追加文件)
  • RDB 和 AOF 混合持久化

RDB

Redis 可以通过创建快照来获得存储在内存里面的数据在 某个时间点 上的副本。Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。

Redis 提供了两个命令来生成 RDB 快照文件:

  • save : 同步保存操作,会阻塞 Redis 主线程;
  • bgsave : fork 出一个子进程,子进程执行,不会阻塞 Redis 主线程,默认选项。

AOF

开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到 AOF 缓冲区中,然后再写入到 AOF 文件中(此时还在系统内核缓存区未同步到磁盘),最后再根据持久化方式( fsync策略)的配置来决定何时将系统内核缓存区的数据同步到硬盘中的。

AOF 持久化功能的实现可以简单分为 5 步:

  1. 命令追加(append):所有的写命令会追加到 AOF 缓冲区中。
  2. 文件写入(write):将 AOF 缓冲区的数据写入到 AOF 文件中。这一步需要调用write函数(系统调用),write将数据写入到了系统内核缓冲区之后直接返回了(延迟写)。注意!!!此时并没有同步到磁盘。
  3. 文件同步(fsync):AOF 缓冲区根据对应的持久化方式( fsync 策略)向硬盘做同步操作。这一步需要调用 fsync 函数(系统调用), fsync 针对单个文件操作,对其进行强制硬盘同步,fsync 将阻塞直到写入磁盘完成后返回,保证了数据持久化。
  4. 文件重写(rewrite):随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。
  5. 重启加载(load):当 Redis 重启时,可以加载 AOF 文件进行数据恢复。
image-20250310143135656

三种AOF刷盘策略

在 Redis 的配置文件中存在三种不同的 AOF 持久化方式( fsync策略),它们分别是:

  1. Always:主线程调用 write 执行写操作后,后台线程立即会调用 fsync 函数同步 AOF 文件(刷盘),fsync 完成后线程返回,这样会严重降低 Redis 的性能(write + fsync)。
  2. Everysec:主线程调用 write 执行写操作后立即返回,由后台线程每秒钟调用 fsync 函数(系统调用)同步一次 AOF 文件(write+fsyncfsync间隔为 1 秒)
  3. No:主线程调用 write 执行写操作后立即返回,让操作系统决定何时进行同步,Linux 下一般为 30 秒一次(write但不fsyncfsync 的时机由操作系统决定)。

AOF为什么是在执行完命令之后记录日志?

关系型数据库(如 MySQL)通常都是执行命令之前记录日志(方便故障恢复),而 Redis AOF 持久化机制是在执行完命令之后再记录日志。

  • 避免额外的检查开销,AOF 记录日志不会对命令进行语法检查;
  • 在命令执行完之后再记录,不会阻塞当前的命令执行。

这样也带来了风险:

  • 如果刚执行完命令 Redis 就宕机会导致对应的修改丢失;
  • 可能会阻塞后续其他命令的执行(AOF 记录日志是在 Redis 主线程中进行的)。

AOF重写

当 AOF 变得太大时,Redis 能够在后台自动重写 AOF 产生一个新的 AOF 文件,这个新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。

由于 AOF 重写会进行大量的写入操作,为了避免对 Redis 正常处理命令请求造成影响,Redis 将 AOF 重写程序放到子进程里执行。

AOF 文件重写期间,Redis 还会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新 AOF 文件期间,记录服务器执行的所有写命令。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新的 AOF 文件保存的数据库状态与现有的数据库状态一致。最后,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来完成 AOF 文件重写操作。

AOF校验

AOF 校验机制是 Redis 在启动时对 AOF 文件进行检查,以判断文件是否完整,是否有损坏或者丢失的数据。这个机制的原理其实非常简单,就是通过使用一种叫做 校验和(checksum) 的数字来验证 AOF 文件。这个校验和是通过对整个 AOF 文件内容进行 CRC64 算法计算得出的数字。如果文件内容发生了变化,那么校验和也会随之改变。因此,Redis 在启动时会比较计算出的校验和与文件末尾保存的校验和(计算的时候会把最后一行保存校验和的内容给忽略点),从而判断 AOF 文件是否完整。如果发现文件有问题,Redis 就会拒绝启动并提供相应的错误信息。AOF 校验机制十分简单有效,可以提高 Redis 数据的可靠性。

RDB vs AOF

数据安全性

  • AOF提供更好的数据安全性,根据刷盘策略,当Redis服务宕机时,也只会丢失近一秒或者最后一次写入的数据。
  • RDB在两次快照之间,如果Redis服务宕机,这段数据会完全丢失。

占据空间

  • AOF存储每一次写命令,通常AOF文件都会更大,占据更多磁盘空间。
  • RDB存储经过压缩的二进制内容,文件更小。

恢复速度

  • 使用AOF文件恢复数据,需要重写执行每一条命令,速度非常慢(特别是如果没有进行AOF重写)。
  • 使用RDB文件恢复数据,直接解析还原数据即可,速度非常快。

性能影响

  • AOF的always刷盘策略会频繁进行磁盘IO吗,会对Redis的写入性能造成一定影响。
  • RDB使用bgsave生成策略时,创建子线程进行,不会阻塞主线程,对服务性能影响较小。

Redis线程模型

Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型,这套事件处理模型对应的是 Redis 中的文件事件处理器(file event handler)。由于文件事件处理器(file event handler)是单线程方式运行的,所以我们一般都说 Redis 是单线程模型。

Redis 通过 IO 多路复用程序 来监听来自客户端的大量连接(或者说是监听多个 socket),它会将感兴趣的事件及类型(读、写)注册到内核中并监听每个事件是否发生。I/O 多路复用技术的使用让 Redis 不需要额外创建多余的线程来监听客户端的大量连接,降低了资源的消耗。

为什么使用单线程模型?

  • 单线程编程容易并且更容易维护;
  • Redis 的性能瓶颈不在 CPU ,主要在内存和网络;
  • 避免了多线程之间的竞争,省去了多线程切换带来的时间和性能上的开销,而且也不会导致死锁问题。
  • 通过 I/O 多路复用机制,实现了一个 Redis 线程处理多个 IO 流的效果。

Redis哪些地方有多线程?

多线程处理网络IO

在Redis6.0之后,也采用了多个IO线程来处理网络请求,这是因为随着网络硬件的性能提升,Redis的瓶颈有时会出现在网络IO处理上。但是 Redis 的多线程只是在网络数据的读写这类耗时操作上使用了,执行命令仍然是单线程顺序执行。

后台线程

Redis有三个后台线程来处理一些费时的操作:

  • 通过 bio_close_file 后台线程来释放 AOF / RDB 等过程中产生的临时文件资源。
  • 通过 bio_aof_fsync 后台线程调用 fsync 函数将系统内核缓冲区还未同步到到磁盘的数据强制刷到磁盘( AOF 文件)。
  • 通过 bio_lazy_free后台线程释放大对象(已删除)占用的内存空间。

一 内存分配

Java 堆是垃圾收集器管理的主要区域,因此也被称作 GC 堆(Garbage Collected Heap)

image-20250306165524985

新生代

大多数情况下,对象在新生代中 Eden 区分配。当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC。通过 Minor GC 之后,Eden 区中绝大部分对象会被回收,而那些无需回收的存活对象,将会进到 Survivor 的 From 区,如果 From 区不够,则直接进入 To 区。

阅读全文 »

线程独有:

  • 程序计数器

  • 虚拟机栈,每个栈帧包含:

    • 局部变量表
    • 操作数栈
    • 动态链接
    • 程序返回地址
  • 本地方法栈

线程共享:

  • 方法区:jdk1.7为永久代,在运行时数据区中;jdk1.8为元空间,在本地内存中

    • 类信息
    • JIT代码缓存
    • 运行时常量池
  • 静态变量、字符串常量池:jdk1.6在永久代中,jdk1.7移动至堆中

  • 直接内存:在本地内存中

JVM功能

JVM(Java Virtual Machine),即Java虚拟机,其职责是运行Java字节码文件。

其功能主要有三个:

  • 解释和运行:对字节码文件中的指令,实时的解释成机器码,让计算机执行。
  • 内存管理:自动为对象、方法等分配内存;自动的垃圾回收机制,回收不再使用的对象。
  • 即时编译(JIT):对热点代码进行优化,提升执行效率。

Java需要实时解释,主要是为了支持跨平台特性。即同一份字节码文件在不同的平台上,安装不同的JVM,让JVM解释运行,实现跨平台。

阅读全文 »

一 类的生命周期

加载->验证->准备->解析->初始化->使用->卸载

image-20250305165311851

二 加载

类加载过程由类加载器完成。每个 Java 类都有一个引用指向加载它的 ClassLoader。不过,数组类不是通过 ClassLoader 创建的,而是 JVM 在需要的时候自动创建的,数组类通过getClassLoader()方法获取 ClassLoader 的时候和该数组的元素类型的 ClassLoader 是一致的。

加载过程主要完成:

  1. 通过全类名获取定义此类的二进制字节流。
  2. 将字节流所代表的静态存储结构转换为方法区的运行时数据结构。
  3. 在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口。
阅读全文 »

一 字节码文件的组成

字节码文件由以下几个部分组成:

  • 基本信息

  • 常量池

  • 访问标识

  • 字段

  • 方法

  • 属性

二 基本信息

魔数 Magic

软件使用文件的头几个字节来校验文件的类型,字节码文件的前4个字节为CAFEBABE

阅读全文 »

进程 vs. 线程的区别

对比维度 进程(Process) 线程(Thread)
基本概念 进程是 资源分配的最小单位,代表一个程序的运行实例。 线程是 CPU 调度的最小单位,是进程内部的执行单元。
资源 进程有 独立的地址空间,拥有自己的堆、栈、数据段等资源。 线程共享进程的资源(如内存、文件句柄等),但有自己独立的栈和寄存器
开销 创建、销毁进程需要分配和回收资源开销较大 线程创建、销毁开销较小,因为它共享进程的资源。
切换开销 进程切换涉及 页表切换、资源回收与分配上下文切换成本高 线程切换只涉及 寄存器、栈指针 的切换,上下文切换成本较低
通信方式 进程间通信(IPC,如管道、消息队列、共享内存等)复杂,需要操作系统支持。 线程间通信(共享全局变量、锁等)简单,但需要同步控制(如 synchronizedLock)。
稳定性 一个进程崩溃不会影响其他进程,稳定性高 线程共享进程资源,一个线程崩溃可能影响整个进程,稳定性较低
并发性 进程并行执行(多个 CPU 核心可同时执行多个进程)。 线程并发执行(多个线程共享 CPU 资源,提高执行效率)。
阅读全文 »
0%