This commit is contained in:
Qiu
2026-06-19 14:45:07 +08:00
commit a94cd3e890
428 changed files with 35429 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
---
title: 主页
date: 2024-01-01 12:03:35
tags: [主页]
---
# 主页
你好Hexo
- dfs
> sdfdfdf
>
> dsf
`fsdf`
<mark>mark</mark>
**fsf**
* dfdsf
* sdf
* sdf
i = 3 , j = 4;
l = 1 , r = 12;
n = 6;
1,1
l = 6 , r = 12;
n = 9;
2,0
l = 6 , r = 9;
n = 7;
1,2
l = 6 , r = 7;
n = 6;
1,1
+19
View File
@@ -0,0 +1,19 @@
---
title: 升级计划
date: 2025-09-18 11:59:06
tags: [smt]
---
## SMT 管理软件
* 修改前端组件,不要加、减
![image-20250918120112380](升级计划/image-20250918120112380.png)
* 修改优惠金额名称,改为“优惠/其他金额”
![289e2835660f4a4cd62f72b4b5ea1050](升级计划/289e2835660f4a4cd62f72b4b5ea1050.png)
## 查看客户订单,报错
![image-20250919205930175](升级计划/image-20250919205930175.png)
Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

+26
View File
@@ -0,0 +1,26 @@
---
title: 官网开发计划
date: 2025-09-11
tags: [vue,java]
---
# 开发计划
**9-11 周四**
* 基础页面开发:定价、优惠价、订单查询、个人中心
* 并进行审核
**9-12 周五**
* 后端开发:完成数据库设计、增删改查任务
* 对接支付宝业务
**9-13 周六**
* 对接管理员后台,并设计前端页面
**9-13 周日**
* 后端对接,并进行测试
+19
View File
@@ -0,0 +1,19 @@
月要货计划---srm---执行中
日要货计划(必须有月计划)
告诉SRM什么时间要
人工创建采购计划 -- 审批 --> 采购单 --> 生成SAP订单
告诉SRM要什么
人工创建**日送货计划**关联多个采购单
对高梁/稻壳自动发起质检任务、自动下发到地磅
给地磅系统提供接口,称重
+41
View File
@@ -0,0 +1,41 @@
---
title: 招聘
date: 2025-09-12
tags: [招聘]
---
# 面试
【深信服26届校园招聘启动】
🙋A股上市,8000+员工规模,业务涵盖AI、网安、云计算三条高成长赛道!
【热招岗位】
🙋市场类:1000+offer(不卡学历 /不卡专业)
- 客户经理(不限专业,均可投递)
- 售前产品经理(限理工科)
⭐薪资:本科:22w+;硕士:25w+
🙋研发类:1000+offer(工作地点:80%在深圳,20%在长沙)
- 开发岗:C/C++、Python、Go、Java开发工程师
- 人工智能岗:AI工程师
⭐薪资:SP/SSP40w-100w
【为非计算机专业学生定向提供500+offer】
🔥【加入深信服的4个理由】
✅赛道前景更广阔:AI+云计算+网络安全,共同参与数字产业浪潮
✅成长更迅速:专属培养通道、破格提拔、提供实习机会,更多一份培养与成长
✅工作氛围更简单:简单、务实、平等的氛围,内部不允许有“xxx总”的称呼,
✅高成长高收入:工作回报更丰富,行业领先的薪资、股权激励机会
内推码:NTAhG13
投递地址:https://hr.sangfor.com/campuszp
注:秋招岗位数量有限,投递页面可点击“职位”选择“应届生”跳转详情页,超多优质秋招实习岗位,静候你的加入!
26届秋招合集#小程序://一键投递小助手/kZ8x2O8B5A2QV8r,也可点击小程序一键投递!更快投递中意岗位
[【武汉 Java开发(校招,武汉)招聘】-广东亿迅科技有限公司武汉招聘信息-猎聘](https://www.liepin.com/lptjob/76892667?d_sfrom=search_job_comp_prime_pc&d_ckId=cbc6095d07bd888997eb831f4d572690&d_curPage=0&d_pageSize=6&d_headId=8ff9fe89e31006ee71776d706a5ab55f&d_posi=3&pgRef=c_pc_company_home_page%3Ac_pc_company_home_job_listcard%406_76892667%3A1%3Agw.dc7f9588-1864046902)
+19
View File
@@ -0,0 +1,19 @@
---
title: 学校待办
date: 2024-12-31
categories: [工作, 待办]
tags: [待办]
---
- [ ] nio、netty、juc学习完
- [ ] javaweb、spring、springmvc、mybatys、springgboot、redis
- [ ] 设计模式
- [ ] 中间件:rabbitMQ、elasticsearch
- [ ] 微服务:
- [ ] AiTest项目,复习文件断点问题
- [ ] 改善湖师云项目
+218
View File
@@ -0,0 +1,218 @@
---
title: DeviceTransfer
date: 2025-04-07
categories: [工作, 项目]
tags: [项目]
---
```Plain
请为我生成一个基于 Spring Boot 的后端项目,项目名称为 "DeviceTransfer",实现设备间消息和文件传输功能,使用 Redis、RabbitMQ 和 MySQL 作为辅助技术。以下是具体要求:
1. **项目结构**
- 包名:`com.example.devicetransfer`
- 结构:
- `Application.java`Spring Boot 启动类
- `config/`
- `WebSocketConfig.java`:配置 WebSocket,支持 STOMP 协议
- `RabbitMQConfig.java`:配置 RabbitMQ 交换机和队列
- `RedisConfig.java`:配置 Redis 客户端
- `MyBatisConfig.java`:配置 MyBatis 数据访问层
- `controller/`
- `FileController.java`:处理 HTTP 文件上传、下载及传输控制
- `websocket/`
- `NotificationController.java`:处理 WebSocket 实时通知
- `service/`
- `DeviceService.java`:管理设备状态
- `FileService.java`:处理文件传输逻辑(包括分片、断点续传、暂停)
- `MessageService.java`:处理消息发送和通知
- `mq/`
- `producer/FileMessageProducer.java`:发送消息到 RabbitMQ
- `consumer/FileMessageConsumer.java`:消费 RabbitMQ 消息
- `repository/`
- `DeviceRepository.java`:使用 Redis 存储设备状态
- `mapper/FileMetadataMapper.java`:使用 MyBatis 操作 MySQL 文件元数据
- `model/`
- `Device.java`:设备实体类
- `FileMetadata.java`:文件元数据类(映射到 MySQL)
- `util/`
- `FileChunkUtil.java`:大文件分片和断点续传工具类
- `resources/`
- `application.yml`:配置文件
- `mapper/FileMetadataMapper.xml`MyBatis SQL 映射文件
2. **功能需求**
- **文件传输**
- HTTP POST `/upload`:支持大文件分片上传,保存到 `uploads/` 目录,返回文件 ID。
- 参数:`file`(分片内容)、`fileId`(文件唯一标识)、`chunkIndex`(分片索引)、`totalChunks`(总分片数)。
- 支持断点续传:记录已上传分片,客户端可续传未完成部分。
- 支持中途暂停/取消:HTTP DELETE `/upload/cancel/{fileId}` 取消上传并清理分片。
- HTTP GET `/download/{fileId}`:根据文件 ID 下载,支持分片下载和断点续传。
- 参数:`Range` 头支持范围请求(如 `Range: bytes=0-1048575`)。
- 支持暂停:客户端可随时中断请求。
- **实时通知**
- WebSocket 端点 `/ws`,订阅 `/topic/messages`,发送消息到 `/app/send`。
- 在线设备通过 WebSocket 接收通知,离线设备消息存入 RabbitMQ。
- **设备管理**
- 设备通过 WebSocket 注册(发送 userId 和 deviceId)。
- Redis 存储用户设备状态(键格式:`user:{userId}:devices`)。
- **离线支持**
- 文件上传后,若目标设备离线,消息发送到 RabbitMQ 的 `file-queue`。
- 设备上线时,消费队列消息并通过 WebSocket 推送。
- **数据持久化**
- 使用 MySQL 存储文件元数据,表名 `file_metadata`,字段:
- `id` (主键)、`file_id` (唯一标识)、`file_name`、`file_path`、`user_id`、`total_chunks`、`uploaded_chunks`、`status` (上传状态:uploading/completed/canceled)、`upload_time`。
- MyBatis 操作数据库,定义增删改查 SQL。
3. **技术要求**
- 使用 Spring Boot 2.x 或 3.x。
- Redis 用于存储设备状态(键格式:`user:{userId}:devices`)和分片进度(键格式:`file:{fileId}:chunks`)。
- RabbitMQ 使用 Direct 交换机(`file-exchange`)、队列(`file-queue`)、路由键(`file.routing.key`)。
- MySQL 通过 MyBatis 持久化文件元数据。
- 文件存储在本地 `uploads/` 目录,分片文件命名格式:`{fileId}_{chunkIndex}`,完成后合并为 `{fileId}`。
- 不配置 TLSHTTP 和 WebSocket 使用非加密协议)。
4. **依赖**
- `spring-boot-starter-web`
- `spring-boot-starter-websocket`
- `spring-boot-starter-data-redis`
- `spring-boot-starter-amqp`
- `mybatis-spring-boot-starter`
- `mysql-connector-java`
5. **其他说明**
- 提供基本的错误处理和日志记录(使用 SLF4J)。
- 代码注释清晰,说明每个类的作用。
- 生成完整的 Maven `pom.xml` 文件。
- **大文件分片和断点续传**:
- 客户端上传分片,服务端记录进度到 Redis`file:{fileId}:chunks` 存储已上传分片索引)。
- 续传时,客户端查询已上传分片(GET `/upload/status/{fileId}`),跳过已完成部分。
- 暂停/取消:DELETE `/upload/cancel/{fileId}` 删除分片并更新状态为 canceled。
- **下载支持**
- 服务端响应 `Range` 头,返回指定范围的分片。
- 客户端可暂停下载,续传时通过 `Range` 请求剩余部分。
请根据以上要求生成完整的项目代码,确保结构清晰、可运行,并支持未来扩展(如引入 Netty 或替换 RabbitMQ 为 Kafka)。
```
请为我生成一个基于 Spring Boot 的后端项目,项目名称为 "DeviceTransfer",实现设备间消息和文件传输功能,使用 Redis、RabbitMQ 和 MySQL 作为辅助技术。以下是具体要求:
1. **项目结构**
- 包名:`com.qgs.devicetransfer`
- 结构:
- `Application.java`Spring Boot 启动类
- `config/`
- `WebSocketConfig.java`:配置 WebSocket,支持 STOMP 协议
- `RabbitMQConfig.java`:配置 RabbitMQ 交换机和队列
- `RedisConfig.java`:配置 Redis 客户端
- `controller/`
- `FileController.java`:处理 HTTP 文件上传、下载及传输控制
- `websocket/`
- `NotificationController.java`:处理 WebSocket 实时通知
- `service/`
- `DeviceService.java`:管理设备状态
- `FileService.java`:处理文件传输逻辑(包括分片、断点续传、暂停)
- `MessageService.java`:处理消息发送和通知
- `mq/`
- `producer/FileMessageProducer.java`:发送消息到 RabbitMQ
- `consumer/FileMessageConsumer.java`:消费 RabbitMQ 消息
- `repository/`
- `DeviceRepository.java`:使用 Redis 存储设备状态
- `mapper/FileMetadataMapper.java`:使用 MyBatis 操作 MySQL 文件元数据
- `model/`
- `Device.java`:设备实体类
- `FileMetadata.java`:文件元数据类(映射到 MySQL
- `util/`
- `FileChunkUtil.java`:大文件分片和断点续传工具类
- `resources/`
- `application.yml`:配置文件
- `mapper/FileMetadataMapper.xml`MyBatis SQL 映射文件
2. **功能需求**
- **文件传输**
- HTTP POST `/upload`:支持大文件分片上传,保存到 `uploads/` 目录,返回文件 ID。
- 参数:`file`(分片内容)、`fileId`(文件唯一标识)、`chunkIndex`(分片索引)、`totalChunks`(总分片数)。
- 支持断点续传:记录已上传分片,客户端可续传未完成部分。
- 支持中途暂停/取消:HTTP DELETE `/upload/cancel/{fileId}` 取消上传并清理分片。
- HTTP GET `/download/{fileId}`:根据文件 ID 下载,支持分片下载和断点续传。
- 参数:`Range` 头支持范围请求(如 `Range: bytes=0-1048575`)。
- 支持暂停:客户端可随时中断请求。
- **实时通知**
- WebSocket 端点 `/ws`,订阅 `/topic/messages`,发送消息到 `/app/send`
- 在线设备通过 WebSocket 接收通知,离线设备消息存入 RabbitMQ。
- **设备管理**
- 设备通过 WebSocket 注册(发送 userId 和 deviceId)。
- Redis 存储用户设备状态(键格式:`user:{userId}:devices`)。
- **离线支持**
- 文件上传后,若目标设备离线,消息发送到 RabbitMQ 的 `file-queue`
- 设备上线时,消费队列消息并通过 WebSocket 推送。
- **数据持久化**
- 使用 MySQL 存储文件元数据,表名 `file_metadata`,字段:
- `id` (主键)、`file_id` (唯一标识)、`file_name``file_path``user_id``total_chunks``uploaded_chunks``status` (上传状态:uploading/completed/canceled)、`upload_time`
- MyBatis 操作数据库,定义增删改查 SQL。
3. **技术要求**
- 使用 Spring Boot 3.4.4
- Redis 用于存储设备状态(键格式:`user:{userId}:devices`)和分片进度(键格式:`file:{fileId}:chunks`)。
- RabbitMQ 使用 Direct 交换机(`file-exchange`)、队列(`file-queue`)、路由键(`file.routing.key`)。
- MySQL 通过 MyBatis 持久化文件元数据。
- 文件存储在本地 `uploads/` 目录,分片文件命名格式:`{fileId}_{chunkIndex}`,完成后合并为 `{fileId}`
- 不配置 TLSHTTP 和 WebSocket 使用非加密协议)。
4. **其他说明**
- 提供基本的错误处理和日志记录(使用 SLF4J)。
- 代码注释清晰,说明每个类的作用。
- **大文件分片和断点续传**
- 客户端上传分片,服务端记录进度到 Redis(`file:{fileId}:chunks` 存储已上传分片索引)。
- 续传时,客户端查询已上传分片(GET `/upload/status/{fileId}`),跳过已完成部分。
- 暂停/取消:DELETE `/upload/cancel/{fileId}` 删除分片并更新状态为 canceled。
- **下载支持**
- 服务端响应 `Range` 头,返回指定范围的分片。
- 客户端可暂停下载,续传时通过 `Range` 请求剩余部分。
请根据以上要求生成完整的项目代码,确保结构清晰、可运行,并支持未来扩展(如引入 Netty 或替换 RabbitMQ 为 Kafka)。
```Java
CREATE TABLE file_metadata (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
file_id VARCHAR(64) NOT NULL UNIQUE,
file_name VARCHAR(255) NOT NULL,
file_path VARCHAR(255) NOT NULL,
user_id VARCHAR(64) NOT NULL,
total_chunks INT NOT NULL,
uploaded_chunks INT NOT NULL,
status VARCHAR(20) NOT NULL,
upload_time VARCHAR(30) NOT NULL,
INDEX idx_file_id (file_id),
INDEX idx_user_id (user_id)
);
```
登录
- http → 登录获得token
ws请求、连接都携带token
连接后、上线:
- 查看历史消息(可限制天数、已发送消息)、redis查询离线消息,进行返回;
推送消息:客户端接收离线消息并返回确认,服务器接收确认消息,更新sql状态、redis状态
发送消息:
- 接收消息,**基础校验,防止恶意请求**,写入消息队列,
- 消费队列处理信息,**深度校验(防止越权、内容监控、广告)**
- 写入MySQL
- 判断是否在线,在线则进行消息推送
- 不在线保存到redis中
推送机制
- 推送成功,修改数据库内容
- 推送失败,重试
+66
View File
@@ -0,0 +1,66 @@
---
title: LocalTransfer
date: 2024-12-19
categories: [工作, 项目]
tags: [项目]
---
文件传输器
- 电脑互传
- 自定义协议
- 魔数:Q
- 类型:1
- 正文长度:
- 消息正文:
- 请求
- 文字 —> ack
- 文件 —> ack
- 分片、断点
请求
- 上传
- 文件名:123.mp4
- 分片:1
- 起始位:0
- 长度:1024
- 内容:10101001
- 断点续传(断开时,记录传输传输状态)
- 文件名:123.mp4
使用Java编写一个文件传输软件,网络通信使用netty,用于2台设备之间文件传输,文件传输满足多线程分片上传、断点续传;使用上,只需要输入对方IP,就可以进行连接,并且设备在接收文件的同时也可以发送文件,并且有进度条显示
1个文件
1个连接
按线程分片
使用JavaFX编写界面,要求支持多文件进度显示(每秒的下载速度、大小)并且可以暂停,在接收文件的同时,可以上传文件给对方;界面要求简洁、美观、符合当下审美
- 跨设备互传
- 扫码或输入地址,后端响应界面
- 对文件的预览,请求
- 上传
- 下载
- 功能描述:电脑开启后端程序,选择一个开放文件夹,手机在浏览器输入地址,后端响应页面给手机,手机上可以对开放文件夹进行预览、下载;手机还可以上传文件到开放文件夹;前端通过HTML、css、js、Bootstrap、jQuery进行开发,后端使用springboot进行处理
+66
View File
@@ -0,0 +1,66 @@
---
title: QDrop
date: 2024-12-18
categories: [工作, 项目]
tags: [项目]
---
想法
- 打开软件,对设备进行互相认识
- 可以删除设备
- 发送消息,发送文件
- 同一网络直接发送
- 文本直接受
- 文件直接保存
- 不同网络则不待办
- 连接网络,则直接发送
开发一个软件,功能如下
1、打开软件,同一局域网的设备进行添加、也可以删除设备
2、向设备之间发送消息,发送文件;同一局域网直接发送;不同网络则为待办任务,连接同一网络,则直接发送
技术栈
1、不使用(依赖)服务器
2、使用java语言、网络通信框架使用netty
先以控制台为前端,主要开发后端功能
设备发现:
- 使用UDP广播在局域网内发现设备
- 维护在线设备列表
- 定期检查设备在线状态
消息通信:
- 使用Netty框架建立TCP连接
- 支持文本消息和文件传输
- 离线消息存储和重发机制
控制台界面:
- 显示在线设备列表
- 发送消息和文件的命令行接口
- 查看消息历史记录
修改端口或设备名称
文件加密、文件压缩
使用javaFX编写前端代码,暂且不用和后端相结合,主界面分为三部分,上边是一排建立连接的设备,显示每个设备的圆框头像、名称,头像对应设备类型,头像地址在resource/images/下,并且在线设备的圆框是被亮光包围,最右侧是一个加号按钮,点击提供其他功能;中间是通信内容,要求通信的消息标签类似telegram、whatsAPP,自己发送的消息是淡绿色背景、黑色字体、带时间;下面部分是一个选择本地文件的按钮、输入框、发送按钮
+105
View File
@@ -0,0 +1,105 @@
---
title: ruoyi项目
date: 2025-01-22
categories: [工作, 项目]
tags: [项目]
---
Redis环境安装
- [https://www.runoob.com/redis/redis-install.html](https://www.runoob.com/redis/redis-install.html)
```Shell
redis-server --service-install redis.windows.conf
```
产品管理
- 产品名称、产品价格、产品图片、数量
员工管理
- 姓名、电话、邮箱、qq、微信、职位、入职时间、职位
客户管理
- 姓名、联系方式
订单管理
- 订单号、客户ID、客户名称、订单状态、备注、下单时间、交付时间
- 负责人、职位
- 负责人、职位
```SQL
产品管理:产品名称、产品价格、产品图片、数量
员工管理:姓名、电话、邮箱、qq、微信、职位、入职时间、职位
客户管理:姓名、联系方式
订单管理:订单号、客户ID、客户名称、订单状态、备注、下单时间、交付时间、负责人(多个)、职位
```
## 若依框架
### **权限管理**
- 用户管理(sys_user
- 角色管理(sys_role
- 菜单管理(sys_menu
菜单类型
- 目录菜单:点击只有展开效果,无页面显示
- 页面菜单:显示页面效果
- 按钮菜单:一个点击事件
实现流程:
创建菜单 —> 创建角色 —> 分配用户
### **数据字典**
- 静态数据管理
- 功能:字典类型管理、字典数据管理
- 字典类型表(sys_dict_type
- 字典数据表(sys_dict_data
- 特点:修改时,将表中的属性值改为字典键
### 参数设置
- 验证码是否打开
- 注册功能是否打开
### 通知公告(自行开发)
### 日志管理
## 创建新的后台管理项目
### 新建子模块
- 子模块pom文件导入ruoyi核心依赖
```XML
<!-- 核心模块-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-framework</artifactId>
</dependency>
```
- 主模块pom文件导入子模块
```XML
<!--新增模块-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-smt</artifactId>
<version>${ruoyi.version}</version>
</dependency>
```
+656
View File
@@ -0,0 +1,656 @@
---
title: smt项目
date: 2025-02-04
categories: [工作, 项目]
tags: [项目]
---
## 数据库设计
- 功能表(权限表)
- 手动输入权限
- 角色表
- 自己创建角色、选择权限
- 角色和权限表
- 用户表
- 用户和角色表
## 权限管理
![image 43.png](smt项目/image43.png)
## 业务模块
- 员工管理
- 员工信息
- 客户管理
- 客户信息
- 账号管理
- 订单管理
- 商品管理
- 商品信息
权限管理核心部分
不需要Security
- beforeRouter对请求进行拦截
## 简述
目前是vue3+ element-plus + pinia +axios 的项目环境,请编写一个公司官网的前端界面,该公司提供的主要服务是smt贴片、电子元器件售卖服务;官网功能至少有公司介绍、贴片下单、产品售卖等服务(可自行拓展),而且要求页面符合现代审美、简洁大气;并适配电脑、手机设备端;并编写readme文档,记录项目实现进度、接口请求(使用RestFul风格)
产品优势:
**各位老板,你们是不是还在为小批量加工费而发愁:担心费用太高,价格不透明,手工贴片质量没法保障...这些都是因为传统加工计价标准不透明导致的,厂家没有看到实物是绝对不会给您报价的。**
**说好一个点是0.013元的,可自己估算的加工费远远小于厂家的实际费用。原因是因为你所有物料都是按实际点算,而厂家对于二三极管以及芯片都是按多个点来算的。这就是你们的估价与厂家不一样的原因**
**公司服务原则:**
**你的满意是我们最大的追求!!!**
**开机费,**
**无最低消费,无最小数量,专业生产加工。**
## 优势
# **打破行业爆利.让利给客户。**
**公司目前是全自动化流程:**
**目前的设备有:**
**自动上板机,全自动印刷机,自动贴片机,自动下板机。波峰焊机。**
为解决以上报价难的问题,我公司采取了公正,透明,客观的报价体系。具体方法如下:
报价分两部份组成:工程费 + 加片点数的数费用。
这两个参数客户都可以自己核算的。
# **工程费标准:**
以下收费标准是有铅,单面贴片的标准,如是双面贴片,则第二面工程费是第一面的半价,也就是下面标准的1.5倍。
物料种类以贴片料种类为准,(双面的就是以两种物料种类之和为准),插件料种类不计入工程标准中。所以希望大家在提供BOM时把插件料和贴片费的种类分开,以免统计出错。
1-5种: 单面工程费:150元;
6-10种:单面工程费:200元;
11-25种:单面工程费:250元;
26-30种:单面工程费:300元;
31-35种:单面工程费:350元;
36-40种:单面工程费:400元;
41-45种:单面工程费:450元;
46-50种:单面工程费:500元;
51-55种:单面工程费:550元;
56-60种:单面工程费:600元;
61-65种:单面工程费:650元;
66-70种:单面工程费:700元;
71-75种:单面工程费:750元;
76-80种:单面工程费:800元;
81-85种:单面工程费:850元;
86-90种:单面工程费:900元;
91-95种:单面工程费:950元;
96-100种:单面工程费:1000元;
以后往后退:每增加5种物料,工程费加50元。
**焊盘机贴费用标准:**  
加工点数:不任焊盘大小,元件引脚的大小,每个焊盘的单价是:0.0065.例如:电阻是两个脚,二极管也是两件引脚,大的252封的引脚也是算3个脚; IC每个引脚也算一个焊盘。
编带,管装,托盘类的元件都能机贴。如不能机贴的则按手放元件的标准计价,
**焊盘手放费用标准:**  
如果元件不能机贴的,则需要手放,手放的单价是0.02元一个焊盘。建议客户尽量买编带,管装,托盘类的元件。
**插件费用标准:**  
人工焊接标准:
每个孔0.05元,元件种类不计贴片元件种类中。
波峰焊标准:
每个孔0.045,,封胶纸留,一条胶纸算一个孔位.
注意事项是:只限于单面贴片,而且贴片和插件在同一面.
以上报价标准都是按有铅的标准 来报的,红胶工艺与有铅工艺报价一样,无铅报价是在有铅的基础上的总价上上涨10%。
# **重要信息:**
# **当一款板的焊盘总数*0.0065的费用超过2000元,可以免相应这款板的工程费。**
# **单款贴片费用超过五千元(不包括插件费),加工费有返点。**
# **注意事项:**
**1.元件损耗问题: 小批量生产,如是样品料,阻容物料:0603,0805以上的了物料,都需要比实际用量多200个,贵重物料可以按需发货,或多一定数量,下单时告诉我公司跟单人员。如物料没有达到上面的要求,有可能会产生物料整理费(200-500元),具体是多少要看物料种类的多少。**
**2. 最小生产面积:由于生产线都是全自动化流水作业,所以建议客户在生产PCB时要拼板和加工艺边。拼板后面积在15至20CM比较合适。**
**3.钢网: 本公司代开,75元张(只限3747,其它规格另报),也可以贵公司直接提供,大小在3747以上的规格。**
**4.交货周期: 物料齐后,无插件定单,3-5天。有插件定单:5-7天。具体时间可以与工作人员沟通。**
**5.产品品质:正常情况下是目测,如客户品质要求比较高的,可以联系工作人员,进入AOI检测,由于AOI检测需要做程序,可能会产生产一定的费用。批量生产时免费过AOI 。**
**6.售后问题:由于计价都比较客观,公正,透明,如在计价过程中有出入的支持多退少补。如生产品质有问题,客户可以发顺丰到付件到公司进行相应的程序。生产加工原本没有什么利润了,公司没有理由不去把客户的产品做来,因为没有做好,公司还要亏快递费呢。**
**下单流程:**
**报价-->物料-->钢网-->电子档资料--> 公司安排生产**
   **1.报价:**
**客户提供BOM,告诉我们是单面还是双面贴片就行了,不需要PCB资料就要吧报价.由于本公司报价方案比较透明,客观,客户也可以按网上标准自己来核价,如有不明白的地方可以随时联系我们。需要代购物料,要提供BOM及参数,我们会报价给贵公司**
**2.物料:**
**贵公司把物料快递过来,或其它方式送到我公司,如是我公司代购的。这个步骤可能省掉**
**3.钢网.**
**贵公司可以自己提供,在3747(带外框)以上的规格,我公司也可代开(需要提供开网资料)**
**4.电子档资料.**
**BOM表,单板PCB或坐标资料,需要我公司代开钢网的要提供开网资料。**
**5.公司安排生产:**
**来料清点,安排上机,QC,打包,发货**
## AI
非常好,样式我非常满意,
现在改进一下smt页面,根据这段文字"为解决以上报价难的问题,我公司采取了公正,透明,客观的报价体系。具体方法如下:
报价分两部份组成:工程费 + 加片点数的数费用。
这两个参数客户都可以自己核算的。
# **工程费标准:**
以下收费标准是有铅,单面贴片的标准,如是双面贴片,则第二面工程费是第一面的半价,也就是下面标准的1.5倍。
物料种类以贴片料种类为准,(双面的就是以两种物料种类之和为准),插件料种类不计入工程标准中。所以希望大家在提供BOM时把插件料和贴片费的种类分开,以免统计出错。
1-5种: 单面工程费:150元;
6-10种:单面工程费:200元;
11-25种:单面工程费:250元;
26-30种:单面工程费:300元;
31-35种:单面工程费:350元;
36-40种:单面工程费:400元;
41-45种:单面工程费:450元;
46-50种:单面工程费:500元;
51-55种:单面工程费:550元;
56-60种:单面工程费:600元;
61-65种:单面工程费:650元;
66-70种:单面工程费:700元;
71-75种:单面工程费:750元;
76-80种:单面工程费:800元;
81-85种:单面工程费:850元;
86-90种:单面工程费:900元;
91-95种:单面工程费:950元;
96-100种:单面工程费:1000元;
以后往后退:每增加5种物料,工程费加50元。
- *焊盘机贴费用标准:**
加工点数:不任焊盘大小,元件引脚的大小,每个焊盘的单价是:0.0065.例如:电阻是两个脚,二极管也是两件引脚,大的252封的引脚也是算3个脚; IC每个引脚也算一个焊盘。
编带,管装,托盘类的元件都能机贴。如不能机贴的则按手放元件的标准计价,
- *焊盘手放费用标准:**
如果元件不能机贴的,则需要手放,手放的单价是0.02元一个焊盘。建议客户尽量买编带,管装,托盘类的元件。
- *插件费用标准:**
人工焊接标准:
每个孔0.05元,元件种类不计贴片元件种类中。
波峰焊标准:
每个孔0.045,,封胶纸留,一条胶纸算一个孔位.
注意事项是:只限于单面贴片,而且贴片和插件在同一面.
以上报价标准都是按有铅的标准 来报的,红胶工艺与有铅工艺报价一样,无铅报价是在有铅的基础上的总价上上涨10%。
# **重要信息:**
# **当一款板的焊盘总数*0.0065的费用超过2000元,可以免相应这款板的工程费。**
# **单款贴片费用超过五千元(不包括插件费),加工费有返点。**
# **注意事项:**
- *1.元件损耗问题: 小批量生产,如是样品料,阻容物料:0603,0805以上的了物料,都需要比实际用量多200个,贵重物料可以按需发货,或多一定数量,下单时告诉我公司跟单人员。如物料没有达到上面的要求,有可能会产生物料整理费(200-500元),具体是多少要看物料种类的多少。**
- *2. 最小生产面积:由于生产线都是全自动化流水作业,所以建议客户在生产PCB时要拼板和加工艺边。拼板后面积在15至20CM比较合适。**
- *3.钢网: 本公司代开,75元张(只限3747,其它规格另报),也可以贵公司直接提供,大小在3747以上的规格。**
- *4.交货周期: 物料齐后,无插件定单,3-5天。有插件定单:5-7天。具体时间可以与工作人员沟通。**
- *5.产品品质:正常情况下是目测,如客户品质要求比较高的,可以联系工作人员,进入AOI检测,由于AOI检测需要做程序,可能会产生产一定的费用。批量生产时免费过AOI 。**
- *6.售后问题:由于计价都比较客观,公正,透明,如在计价过程中有出入的支持多退少补。如生产品质有问题,客户可以发顺丰到付件到公司进行相应的程序。生产加工原本没有什么利润了,公司没有理由不去把客户的产品做来,因为没有做好,公司还要亏快递费呢。**
- *下单流程:**
- *报价-->物料-->钢网-->电子档资料--> 公司安排生产**
**1.报价:**
- *客户提供BOM,告诉我们是单面还是双面贴片就行了,不需要PCB资料就要吧报价.由于本公司报价方案比较透明,客观,客户也可以按网上标准自己来核价,如有不明白的地方可以随时联系我们。需要代购物料,要提供BOM及参数,我们会报价给贵公司**
- *2.物料:**
- *贵公司把物料快递过来,或其它方式送到我公司,如是我公司代购的。这个步骤可能省掉**
- *3.钢网.**
- *贵公司可以自己提供,在3747(带外框)以上的规格,我公司也可代开(需要提供开网资料)**
- *4.电子档资料.**
- *BOM表,单板PCB或坐标资料,需要我公司代开钢网的要提供开网资料。**
- *5.公司安排生产:**
- *来料清点,安排上机,QC,打包,发货**“修改smt页面的服务介绍、流程、设备数据,并修改下单表单的内容,并添加注意事项、定价规则等内容(样式自行发挥)
## 跨域
- 代码
```Java
//不使用security的跨域设置
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
// corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedOrigin("http://localhost:8081"); // 指定前端来源
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
source.registerCorsConfiguration("/**",corsConfiguration);
return new CorsFilter(source);
}
}
```
## 订单支付
9021000145688875
### 下单流程
**定价类**
用户报价 —>
通知后台审核 —>
通过前台更新订单 —>
支付金额更新订单,通知后台制作 —>
等待快递 —>
待审核、待支付、待收货、制作中、已发货、已完成
**商品类**
下单支付 —>
通知后台 —>
等待收货即可
## 定时任务
- 不需要导包,springboot包含了
将配送中、超过的订单7天自动完成
订单
基础信息(订单基本+用户信息)
- 订单编号
- 创建时间
- 客户名称
- 客户电话
- 订单总金额
- 当前状态
订单详情(订单基本+用户信息)
- 订单配置
| | |
|---|---|
|项目|值|
|贴片类型|单面贴片|
|工艺要求|有铅工艺|
|贴片物料种类|12|
|手工焊盘数|12|
- 订单地址
- 客户备注信息
付款信息(订单基本+支付信息)
- 支付方式
- 支付金额金额
- 支付时间
收货信息
- 收货人
- 收货快照
- 收货时间
加工信息
- 当前进度
- 环节、生产负责人(多环节不同负责人)
发货信息
- 物流信息(物流公司、运单号)
- 发货时间
- 收货地址
- 快递费
完成信息
- 相关单据(发票等)
- 完成时间
## 功能
全部
- 查看详情
待审核
- 审核通过
- 驳回
- 修改订单:金额
待付款
- 通知用户
- 已付款(设置付款方式)
- 取消订单
待收货
- 公司代购
- 已收货(上传照片)
制作中
- 配置流水线
- 制作完成
待发货
- 填写快递单号(信息)
- 自提
已发货
- 已签收
## 显示
- 显示内容:
- 订单商品清单(品名、规格、数量、单价)
- 客户备注信息
- 操作按钮:
- 审核通过
- 驳回
- 修改订单
待付款
- 显示内容:
- 订单商品清单
- 应付金额
- 支付截止时间
- 操作按钮:
- 确认付款
- 取消订单
- 修改支付方式
待收货
- 显示内容:
- 原材料清单
- 预计到货时间
- 供应商信息
- 操作按钮:
- 确认收货
- 延迟到货
- 查看物流
加工中
- 显示内容:
- 生产进度
- 预计完成时间
- 生产负责人
- 质检记录
- 操作按钮:
- 更新进度
- 质检记录
- 异常报备
已发货
- 显示内容:
- 物流信息(物流公司、运单号)
- 发货时间
- 预计送达时间
- 收货地址
- 操作按钮:
- 查看物流
- 确认送达
完成
- 显示内容:
- 完成时间
- 验收记录
- 评价信息
- 相关单据(发票等)
- 操作按钮:
- 查看评价
- 开具发票
- 申请售后
## 数据库
```Plain
const types = ['已完成', '待审核', '待付款', '待收货', '制作中', '待发货', '已发货'];
```
数据库表
- smt_order_desc 规格表
- smt_order_delivery 收货表
- smt_order_pay 支付表
- smt_order_process 负责人表
- smt_order_shipping 物流表
使用vue3js+element-plus,编写一个订单、项目表格,表格头部显示订单号、订单名称、流水线、操作;操作那列有配置流水线,其中流水线那列比较特别,要显示多条消息,流水线项目名、状态、操作(已完成按钮),但是按钮是否可点击,取决于流水线项目状态是否为进行
```Plain
<template>
<div>
</div>
</template>
<script setup>
</script>
```
官网请求
- 查看商品
- 查看商品详情
- 加入购物车、移除购物车
- 地址管理
- 增加、删、改、查
- 消息管理
- 查看、已读、删除、生成
- 登录、退出登录
- 个人信息修改
- 订单结算界面:立即购买商品:购物车结算商品
- 付款 —> 创建订单、自动跳转支付宝付款
- 在线定价
- 订单管理
- 查看、付款
| | | | | | |
|---|---|---|---|---|---|
||X|chatGpt|claude|通义|得分|
|qodes|7|7|7|6|27|
|qodix|6|6|6|4|22|
|qodedev|5|5|4|5|19|
|qodis|4|2|5|7|18|
|qoade|3|4|1|3|11|
|qohd|2|1|2|1|6|
|qoyd|1|3|3|2|9|
Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

@@ -0,0 +1,87 @@
---
title: 加密数据中心
date: 2025-04-23
categories: [工作, 项目]
tags: [项目]
---
# 📦 安全数据中心 — 端对端加密存储系统
本项目是一个**端对端加密的数据中心平台**,支持存储**文字、文件**等多类型数据。所有数据加密、解密过程均在客户端完成,服务器仅存储密文,保障用户隐私安全。
---
## ✨ 功能特点
- 📑 支持文字、文件加密存储
- 🔒 每个用户独立 RSA 密钥对
- 🗝️ 私钥本地解锁、自定义密钥派生 AES 密钥
- 📁 全端对端加密,服务器零明文存储
- ⚙️ 多重加密机制,保障数据安全
---
## 🔐 加密流程
### 📌 私钥生成与存储
1. 用户注册时,客户端生成一对 RSA 公钥 / 私钥。
2. 用户设置自定义**解锁密钥**。
3. 使用 PBKDF2Password-Based Key Derivation Function 2)算法,基于解锁密钥派生 AES 密钥。
4. 使用该 AES 密钥对用户私钥进行加密,将密文私钥上传服务器保存。
---
### 📌 数据加密
1. 客户端生成**随机 AES 密钥**。
2. 使用随机 AES 密钥对数据(文字 / 文件)进行加密。
3. 使用用户 RSA 公钥,对随机 AES 密钥进行加密。
4. 将**加密后的数据**、**加密后的 AES 密钥**上传服务器。
---
## 🔓 解密流程
1. 用户输入自定义**解锁密钥**。
2. 使用 PBKDF2 派生 AES 密钥。
3. 使用该 AES 密钥解密服务器存储的**私钥密文**,获得用户私钥。
4. 使用私钥解密服务器返回的**随机 AES 密钥密文**。
5. 使用解密得到的 AES 密钥,解密对应数据。
---
## 📦 技术栈
- 📜 前端:Vue 3 + Element Plus
- ☁️ 服务端:Spring Boot(或其他支持 HTTPS 的后端)
- 🔐 加密算法:RSA、AES、PBKDF2
- 📚 密钥管理:端到端加密,服务器不保存明文私钥及解锁密钥
---
## 📌 安全说明
- 所有私钥、解锁密钥不在服务端存储
- 数据加解密操作全在客户端执行
- 服务端仅存储密文数据和加密后的 AES 密钥
- 多重对称 + 非对称加密,保障数据链路和静态数据双重安全
---
## 📷 项目截图
> 📌 此处可放网站首页、上传界面、数据管理界面截图
---
## 📖 License
MIT License
---
## 📬 联系我
如有任何问题或建议,欢迎联系开发者 👋
+341
View File
@@ -0,0 +1,341 @@
---
title: 啥都评
date: 2025-04-30
categories: [工作, 项目]
tags: [项目]
---
**10天开发计划**(7-15天区间里,10天足够完成可运行版本,预留时间可调度缓冲或提前做优化)。按你的技术栈:Vue 3 + Spring Boot + MySQL,目标是**开发出一个可部署、可使用的“啥都评”基础版**。
---
## 📅 10天开发计划(第一阶段)
---
- [ ] 📌 第 1 天:环境搭建 & 项目初始化
- 详情
- 搭建开发环境(JDK、MySQL、IDEA、Node、Nginx
- 新建 Spring Boot 项目,配置好 dev 配置、数据库连接、JWT 安全框架
- 新建 Vue 3 项目,安装 Element Plus / Tailwind CSS
- 搭建基础路由、Axios 封装、前后端联调代理配置
👉 **输出**:能跑起来的空白前后端项目+数据库连接成功
---
- [ ] 📌 第 2 天:用户系统开发
- 详情
**后端**
- 用户注册、登录、登出接口(JWT token)
- 用户表、token 验证、权限拦截
**前端**
- 登录/注册页面、表单校验、token 持久化
- 登录态前端路由守卫
👉 **输出**:登录注册功能能跑,登录后进入主页
---
- [ ] 📌 第 3 天:正经评 - 条目管理与展示
- 详情
**后端**
- 创建 `evaluation_item` 表,新增/获取条目接口
- 获取评分综合、维度分接口
**前端**
- 正经评页面,展示条目列表、详情页
- 发起评价页面(只管理员可发)
👉 **输出**:正经评页面展示+详情页基本完成
---
- [ ] 📌 第 4 天:正经评 - 评分与评论
- 详情
**后端**
- `user_score` 表,提交评分接口、评论接口、获取评论列表接口
- 评论点赞、举报接口
**前端**
- 条目评分组件(多维度打分)
- 评论展示、发布、点赞、举报功能
👉 **输出**:正经评评分+评论功能上线
---
- [ ] 📌 第 5 天:趣味评 - 发帖与展示
- 详情
**后端**
- `fun_topics` 表,发布趣味评接口、获取列表接口、详情接口
**前端**
- 趣味评广场页面,展示帖子列表
- 发帖页面,文字/图片/是否开启评论/投票选项
👉 **输出**:趣味评发帖+广场浏览功能完成
---
- [ ] 📌 第 6 天:趣味评 - 评论与互动
- 详情
**后端**
- `fun_comments` 表,发布评论、点赞、举报、设精选接口
- 评论回复机制(仅一级)
**前端**
- 趣味评帖子详情页,评论发布、回复、点赞、举报
- 精选评论标记(发帖人可设)
👉 **输出**:趣味评评论互动功能上线
---
- [ ] 📌 第 7 天:搜索、榜单、推荐功能
- 详情
**后端**
- 条目、帖子关键词模糊搜索接口
- 按热度、点赞数、评论数排行接口
**前端**
- 搜索框联想、结果展示页
- 首页榜单区域、推荐话题展示
👉 **输出**:搜索与榜单功能上线
---
- [ ] 📌 第 8 天:管理后台(简易版)
- 详情
**后端**
- 用户管理、举报内容列表、违规处理接口
**前端**
- 简易管理后台页面(可进首页手动跳转)
👉 **输出**:基本管理功能可用
---
- [ ] 📌 第 9 天:前端界面美化、细节完善
- 详情
- Tailwind / Element Plus 样式统一调整
- 评分动画、按钮 loading、响应式适配
- 图片上传预览、发帖限制校验
- 评论时间格式化、美化样式
👉 **输出**:整体前端页面清爽、可交互、无重大 bug
---
- [ ] 📌 第 10 天:联调测试 & 部署打包
- 详情
- 前后端联调,修复接口返回问题
- 前端打包 + Spring Boot 打包
- Docker/Nginx 配置部署测试
- 生成部署文档
👉 **输出**:项目部署运行,进入试用阶段
---
## 📊 最终效果:
- 用户能注册、登录、发帖、评分、评论
- 正经评+趣味评+搜索+榜单功能可用
- 管理后台能查看用户、举报、封禁
- 支持 PC + 手机访问,界面基础美化
- 可部署的正式版本,后续可升级优化
---
## 📦 项目目录结构草稿
### 后端(Spring Boot
```Plain
shadouping-server/
├── src/main/java/com/shadouping/
│ ├── config/ // 配置类
│ ├── controller/ // 控制器层
│ ├── service/ // 服务层
│ ├── dao/ // Mapper接口
│ ├── entity/ // 实体类
│ ├── security/ // 安全配置
│ ├── util/ // 工具类
│ └── ShadoupingApplication.java
├── resources/
│ ├── application.yml
│ └── static/
└── Dockerfile
```
### 前端(Vue 3 + Vite
```Plain
shadouping-web/
├── src/
│ ├── api/ // axios 接口封装
│ ├── assets/ // 静态资源
│ ├── components/ // 通用组件
│ ├── pages/ // 页面组件
│ ├── router/ // 路由配置
│ ├── store/ // pinia 状态管理
│ ├── utils/ // 工具方法
│ └── main.js
├── public/
└── vite.config.js
```
---
## 🗄️ 数据库建表 SQL
```SQL
-- 用户表
CREATE TABLE user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
avatar VARCHAR(255),
role TINYINT DEFAULT 0, -- 0普通 1管理员
status TINYINT DEFAULT 0,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 正经评条目表
CREATE TABLE evaluation_item (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(100) NOT NULL,
category VARCHAR(50),
description TEXT,
cover_img VARCHAR(255),
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 正经评分记录表
CREATE TABLE user_score (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT,
item_id BIGINT,
score DECIMAL(3,1),
comment TEXT,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 正经评评论点赞表
CREATE TABLE score_comment_like (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT,
score_id BIGINT
);
-- 趣味评话题表
CREATE TABLE fun_topic (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT,
title VARCHAR(100) NOT NULL,
content TEXT,
img VARCHAR(255),
allow_comment TINYINT DEFAULT 1,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 趣味评评论表
CREATE TABLE fun_comment (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
topic_id BIGINT,
user_id BIGINT,
content TEXT,
parent_id BIGINT DEFAULT 0,
is_selected TINYINT DEFAULT 0,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 举报表
CREATE TABLE report (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
reporter_id BIGINT,
type VARCHAR(50), -- topic/comment/score
target_id BIGINT,
reason VARCHAR(255),
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
```
---
## 📑 前后端接口清单
### 📌 用户接口
- POST /api/user/register 注册
- POST /api/user/login 登录
- GET /api/user/info 查询个人信息
- GET /api/user/list 管理员查用户列表
### 📌 正经评接口
- GET /api/item/list 条目列表
- GET /api/item/{id} 条目详情
- POST /api/item/add 新增条目
- POST /api/item/score 提交评分
- GET /api/item/score/list 某条目评论
- POST /api/item/score/like 点赞评论
### 📌 趣味评接口
- GET /api/topic/list 话题列表
- GET /api/topic/{id} 话题详情
- POST /api/topic/add 发布话题
- POST /api/topic/comment 发布评论
- POST /api/topic/comment/like 点赞评论
- POST /api/topic/comment/select 精选评论
### 📌 举报接口
- POST /api/report 举报
- GET /api/report/list 管理员查举报记录
### 📌 搜索 & 榜单接口
- GET /api/search 搜索条目/话题
- GET /api/rank/item 正经评榜单
- GET /api/rank/topic 趣味评榜单
+81
View File
@@ -0,0 +1,81 @@
---
title: 外卖项目
date: 2025-02-11
categories: [工作, 项目]
tags: [项目]
---
## 开发流程
**需求分析**
- 需求规格说明书、产品原型
**设计**
- UI设计、数据库设计、接口设计
**编码**
- 项目代码、单元测试
**测试**
- 测试用例、测试报告
**上线运维**
- 软件环境安装、配置
### 角色分工
- 项目经理:对整个项目负责,任务分配、把控进度
- 产品经理:进行需求调研,输出需求调研文档、产品原型等
- UI设计师:根据产品原型输出界面效果图
- 架构师:项目整体架构设计、技术选型等
- 开发工程师:代码实现
- 测试工程师:编写测试用例,输出测试报告
- 运维工程师:软件环境搭建、项目上线
### 软件环境
- 开发环境(development):开发人员在开发阶段使用的环境,一般外部用户无法访问
- 测试环境(testing):专门给测试人员使用的环境,用于测试项目,一般外部用户无法访问
- 生产环境(production):即线上环境,正式提供对外服务的环境
## 项目介绍
架构图
- 管理端
- 员工管理
- 官网管理
- 商品管理
- 订单管理
- 用户管理
- 数据统计
- 来单提示
- 用户端
- 微信登录
- 商品浏览
- 购物车
- 用户下单
- 微信支付
- 历史订单
- 地址管理
- 用户催单
**技术选型**
![image 42.png](外卖项目/image42.png)
**整体价格**
![image 1 24.png](外卖项目/image124.png)
![image 2 16.png](外卖项目/image216.png)
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

@@ -0,0 +1,327 @@
---
title: 文件共享系统
date: 2024-12-20
categories: [工作, 项目]
tags: [项目]
---
这个项目描述的目标是创建一个简单的文件共享和管理系统,允许通过用户浏览器访问电脑上开放的文件夹,进行文件预览、下载和上传。后端使用Spring Boot进行处理,前端使用HTML、CSS、JavaScript、Bootstrap和jQuery进行开发。接下来,我们将从前端和后端的角度来分析该项目的设计。
### **项目概述**
1. **后端**:后端程序运行在电脑上,负责处理浏览器的请求。它提供文件夹预览、文件下载、文件上传等功能,并将这些功能以API的形式暴露给前端。后端使用Spring Boot作为框架,可以快速实现RESTful API。
2. **前端**:前端运行在浏览器上,用户通过浏览器输入地址,访问并与后端交互。前端使用HTML、CSS、JavaScript、Bootstrap和jQuery构建用户界面,实现文件夹预览、文件上传、文件下载等功能。
---
### **1. 项目功能分析**
### **文件夹选择与开放(后端处理)**
- 用户通过电脑上的后端程序选择一个文件夹进行共享,后端程序需要监听该文件夹,并提供接口以供手机浏览器访问。
- 后端需要提供一个文件夹路径的管理接口,允许用户动态选择不同的文件夹进行开放。
### **浏览器端访问(前端页面)**
- 手机用户在浏览器中输入地址,后端响应一个文件浏览器页面,该页面展示开放文件夹中的文件列表,并提供文件预览、下载和上传功能。
- 通过**Bootstrap**和**jQuery**,前端可以快速实现响应式布局和交互式界面。
### **文件预览(前端展示)**
- 前端展示文件夹中的文件列表,可能包括文件名、文件类型(如图片、文本等)、文件大小等。
- 对于图片文件,前端可以使用`<img>`标签进行预览,其他类型的文件则可以提供下载链接或提示。
- 使用**Bootstrap**的卡片组件或表格展示文件列表。
### **文件下载(前端请求)**
- 当手机用户点击某个文件时,前端通过Ajax向后端发起请求,后端返回文件内容。
- 后端使用Spring Boot的文件流功能将文件传输给前端,浏览器触发文件下载。
### **文件上传(前端交互)**
- 前端提供一个文件上传表单,用户可以选择文件并点击上传按钮。
- 上传功能通过HTML表单实现,文件通过POST请求提交给后端。
- 后端接收上传的文件并保存到开放的文件夹中,可以使用Spring Boot的文件上传功能(如`MultipartFile`)。
- **jQuery**用于简化表单提交,可能还会用到Ajax进行无刷新上传。
### **文件操作安全性**
- 确保文件上传和下载的路径安全,避免恶意文件的执行。
- 可能需要对上传的文件类型进行限制,确保只允许上传特定类型的文件(如图片、文档等)。
---
### **2. 技术栈分析**
### **前端技术**
- **HTML/CSS**:用于构建页面结构和样式,创建一个清晰直观的界面。CSS将帮助你实现响应式设计,确保手机端的良好展示。
- **Bootstrap**:提供响应式布局和UI组件(如表格、卡片、按钮、导航栏等)。Bootstrap的网格系统将确保前端页面在不同设备上良好的显示效果。
- **jQuery**:简化DOM操作和Ajax请求,特别是在处理用户交互(如点击按钮、文件选择、文件上传等)时,可以有效减少JavaScript的复杂性。
- **JavaScript**:处理用户交互和Ajax请求,如发送文件下载请求、提交文件上传等。
### **后端技术**
- **HTTP服务器**
- 提供静态资源(HTML、CSS、JavaScript等)。
- 响应设备的连接请求,分发必要的页面和资源。
- **WebRTC信令服务器**
- 使用WebSocket实现信令通道,负责交换WebRTC的SDP和候选者信息,建立P2P连接。
- **文件操作接口**
- 提供REST API,支持文件目录预览、文件下载、文件上传操作。
- API 基于开放文件夹动态生成,确保实时性。
---
### **3. 系统交互流程**
1. **初始化与文件夹选择**
- 后端程序启动后,提供一个接口供用户选择要共享的文件夹。后端将记录此文件夹路径,并保持该路径的文件开放状态。
- 后端提供一个REST API接口,前端通过GET请求获取文件夹中所有文件的列表。
2. **前端展示文件夹内容**
- 手机浏览器输入后端程序的地址后,后端返回一个包含文件列表的页面,文件列表展示在Bootstrap的表格或卡片组件中。
- 前端通过Ajax请求后端获取文件夹内容(如文件名、大小、类型等),并在页面中展示。
3. **文件预览**
- 对于图片文件,前端直接展示图像。
- 对于其他类型的文件(如PDF、文本文件等),前端提供下载链接或简单的文本预览功能。
4. **文件下载**
- 当用户点击某个文件的下载链接时,前端通过Ajax请求后端,并获取文件内容。
- 后端通过文件流返回文件,浏览器提示用户下载该文件。
5. **文件上传**
- 前端提供一个文件上传表单,允许用户选择文件并提交。文件通过POST请求传递给后端。
- 后端接收到文件后,将其存储到开放的文件夹中,确保文件的安全存储和处理。
---
### **4. 安全性与权限控制**
- **文件路径限制**:后端应确保用户不能上传到系统敏感目录或访问其他用户的文件。
- **文件类型验证**:为了防止恶意文件的上传,后端应验证上传文件的类型,如仅允许图片、PDF等常见类型。
- **文件访问权限**:可能需要根据用户角色或会话控制文件的上传、下载权限,确保只有授权用户可以访问文件夹内容。
---
### **5. 性能与优化**
- **异步处理**:对于文件上传和下载,前端可以使用Ajax异步请求,不阻塞页面的其他操作。
- **大文件传输**:对于较大的文件,后端可以实现分块传输或支持流式下载,避免一次性加载导致性能问题。
---
### **总结**
这个项目涉及的技术栈和功能实现相对简单,但也需要考虑文件管理、安全性和性能等问题。通过Spring Boot实现后端API,通过HTML、CSS、JavaScript、Bootstrap和jQuery开发前端页面,可以构建一个基本的文件共享系统,允许用户在手机浏览器中访问和操作电脑上的共享文件夹
分析一个该项目,不需要给我代码,项目描述:电脑安装应用程序,选择一个开放文件夹,其他设备通过浏览器输入地址,远程服务器响应页面给设备,设备与电脑通过WebRTC进行p2p连接,连接成功后,该设备可以对开放文件夹进行预览、下载、上传文件到开放文件夹;前端通过HTML、css、js、Bootstrap、jQuery进行开发,后端使用springboot进行处理,不需要数据库存储信息
```SQL
------------------
16
1
4ID
4
4
------------------
------------------
```
```SQL
------------------
16
1
4ID
4
4
------------------
10101010101001
------------------
```
```SQL
------------------
16
1
4ID
```
```SQL
------------------
16
1
4ID
4
4
------------------
------------------
```
```SQL
------------------
16
1
4ID
4
4
------------------
------------------
```
```SQL
------------------
16
1
4ID
4
4
------------------
10101010101001
------------------
```
`**${}**`:在模板字面量中,`${}` 语法用来插入 JavaScript 表达式的结果。
优化
- [ ] 添加多个下载管道
- [ ] 添加加载动画
- [ ] 对正在加载的资源进行管理,减少反复加载
- [ ] 客户端切换网络自动重连
- [ ] 浏览器端添加状态通知
- [ ] 前端使用vue进行重写
- [ ] 对数据进行加密
- [ ] 对房间管理进行优化
- [ ] 创建房间时判断,判断是否有效
### 见习调研报告
### 调研背景
本次见习活动的主要内容是参观中科光芯公司,了解其在高新技术领域的运营服务及技术应用。作为一家专业从事光电子器件制造、5G通信技术、量子计算、云计算和物联网等领域的企业,中科光芯在信息技术产业链中扮演着重要角色。通过此次见习,我希望能更深入地理解这些技术在实际中的应用,尤其是与计算机科学与技术相关的交叉领域。
### 公司介绍
中科光芯公司是一家以科技研发为核心的高新技术企业,业务涵盖以下几个方面:
1. **光电子器件制造与销售**:公司专注于光电子器件的研发和生产,这些器件是现代通信技术(如光纤通信)不可或缺的核心组件。
2. **5G通信技术服务**:公司致力于提供高效的5G通信解决方案,支持通信网络的搭建与优化。
3. **量子计算技术服务**:量子计算是未来计算机科学的重要方向之一,公司通过研究量子算法和硬件架构推动这一技术的应用。
4. **云计算与物联网技术服务**:公司为企业和个人用户提供先进的云计算设备及相关技术支持,同时拓展物联网在智能家居、智能交通等领域的应用。
5. **数字技术服务与信息技术咨询**:针对行业客户的需求,公司提供定制化的数字技术支持和咨询服务。
### 技术与应用分析
**1. 光电子器件与计算机科学的结合**
光电子器件是现代网络通信中的核心技术,而计算机科学在这一领域的应用主要集中于数据传输协议、信号处理算法和器件控制系统的设计。例如,光电子器件的信号处理需要通过高效的算法和软件支持,这对于计算机专业的学生来说,是一个非常有潜力的研究方向。
**2. 5G与边缘计算**
5G通信技术的高速、低延迟特性,为边缘计算的发展提供了强有力的支持。在参观过程中,我们了解到5G基站和设备的研发流程。这些基站需要运行高度优化的软件系统,来实现快速数据处理和传输。这让我认识到,计算机科学在优化网络协议、开发高效算法和实现实时数据处理方面的重要性。
**3. 云计算与大数据**
云计算技术的核心在于分布式计算和存储架构,而大数据技术的重点是如何高效地存储和分析海量数据。公司展示了一些云计算设备的运行原理和技术特点,例如虚拟化技术和容器化部署。这让我更加明确了未来在云计算相关领域的职业发展方向。
**4. 量子计算的挑战与机遇**
量子计算是计算机科学的一个前沿方向。公司在这一领域的研究让我认识到,虽然量子计算目前尚处于实验阶段,但其在解决复杂算法和优化问题上的潜力是巨大的。这一领域需要扎实的计算机科学基础,并结合量子物理的原理进行跨学科研究。
### 调研结论
通过本次调研,我认识到计算机科学与其他技术领域之间的深度融合正在塑造未来的信息技术产业。这些技术的开发与应用需要从算法设计到硬件支持的全方位配合,同时也需要具备解决复杂工程问题的能力。未来,我将更加注重计算机基础知识与实践能力的结合,尤其是在算法优化和高性能计算方面深入学习。
---
### 见习总结
### 总结背景
本次见习活动以参观中科光芯公司为主,通过对其生产、研发和服务的实地了解,我深刻体会到理论知识与实际技术之间的紧密联系,同时对计算机科学与其他技术领域的结合有了更全面的认识。
### 见习内容回顾
1. **了解企业文化和工作环境**
参观过程中,公司工作人员为我们详细介绍了企业的发展历程、核心价值观及团队协作模式。从中我了解到,中科光芯非常重视创新与合作,员工的专业背景涵盖了计算机科学、光电子、通信工程等多个领域。这样多学科融合的团队结构是解决复杂技术问题的基础。
2. **技术学习与实践体验**
公司为我们展示了光电子器件的制造过程及其在通信领域的应用。通过实地观察,我对光电子技术的复杂性及其对现代通信的重要性有了更深的理解。此外,在5G基站研发和云计算设备演示环节,公司技术人员详细讲解了相关设备的运行原理,并分享了研发过程中的挑战与解决方案。这让我更加体会到扎实的专业知识对于技术创新的重要性。
3. **职业规划的启发**
见习过程中,我了解到,计算机科学与光电子、量子计算、物联网等技术的结合是未来的发展趋势。例如,量子计算的研究需要深入的算法优化能力,而物联网则要求对分布式系统和实时数据处理有较强的把握。这些信息让我对未来的学习方向和职业规划有了更明确的目标。
### 个人收获与感悟
**1. 理论与实践结合的重要性**
此次见习让我认识到,单纯的理论知识无法完全满足实际工作的需要,必须通过实践来加深对技术的理解。例如,在光电子器件的信号处理和控制系统开发中,需要将编程技巧与电子学原理相结合,这对我的学习方式提出了新的要求。
**2. 跨学科知识的重要性**
计算机科学不仅仅是一个独立的学科,它与其他技术领域有着千丝万缕的联系。例如,光电子器件的高效运行需要优化的算法支持,而量子计算则需要跨越计算机科学和物理学的界限进行研究。这让我更加重视跨学科知识的学习。
**3. 团队协作能力的培养**
在公司参观中,我看到不同背景的工程师如何在同一个项目中高效协作,这让我认识到团队协作能力的重要性。无论是在研发还是实施阶段,计算机科学专业的学生都需要具备与他人沟通和合作的能力。
### 总结展望
本次见习不仅让我对专业知识有了更深刻的理解,也让我明确了未来的努力方向。在未来的学习中,我将更加注重以下几点:
1. 深入学习与实践结合的技术,尤其是在算法设计和系统优化方面。
2. 拓展跨学科知识,例如量子计算和物联网领域的前沿技术。
3. 提升团队协作能力,与不同专业背景的同学和同事更好地合作。
通过这次见习,我更加确信,计算机科学的未来充满挑战和机遇。只有不断学习、勇于实践,才能在这一领域有所建树。
+926
View File
@@ -0,0 +1,926 @@
---
title: 网盘项目
date: 2024-10-07
categories: [工作, 项目]
tags: [项目]
---
- 描述·
- 单人云盘、team云盘、局域网使用
- 存储
- 文件信息:MySQL
- 文件存储:磁盘
- 模式
- 局域网使用
- 仅开放一个文件夹,提供用户使用,只允许上传和查看
- 互联网使用
- 允许多个用户一起使用
- 基础功能
- 登录
- root用户
- 普通用户
- 查询文件
- 简单查询
- 条件程序(SQL
- 上传文件
- 下载文件
- 删除文件
- 链接分享
- 用户管理(root用户)
- 创建用户
- 修改用户文件权限
- 删除用户
命令表
|命令|描述|使用|
|---|---|---|
|ls|当前目录下的文件|ls|
|pwd|当前位置|pwd|
|show|查看文件结构|show|
|stat 文件名|查看文件具体信息|stat xxx|
|cd /x/|切换目录|2条|
|mkdir xx|创建目录|2条|
|rm xxx|删除文件、文件夹|2条|
|mv xx xx|移动|三条|
|rename xx xx|重命名|三条|
|up xx xx|上传文件|三条|
|down xx xx|下载文件|三条|
```SQL
myproject/
src/
com/
example/
Main.java
Helper.java
bin/
```
![ff3c9978-5098-479a-921b-ba655bced945.png](网盘项目/ff3c9978-5098-479a-921b-ba655bced945.png)
```SQL
cd /xx/xx
cd ../xx
cd ./xx
cd ..
cd .
cd /
```
![image 41.png](网盘项目/image41.png)
![image 1 23.png](网盘项目/image123.png)
![image 2 15.png](网盘项目/image215.png)
```SQL
/
src/
com/
example/
Main.java
Helper.java
bin/
/
|--- 我的资源
|
```
通信协议
```SQL
ls
cd
mv
up
down
ls
id
LTV 1+8
byte
()
byte
long
byte[length]
buffer满了
12 121221323
128 12323231231232132132321121321321321321322121
12 121221323
12 121
2
21323
128 123232312312321321323
21121321321321321322121
128 12323
23
123123
2132
132321121321321321321322121
```
1. **返回值为正数**
- 表示成功读取了数据,返回值是实际读取的字节数。
- 例如,如果读取了 10 个字节,返回值为 10。
2. **返回值为 0**
- 表示没有数据可读,但连接仍然处于打开状态。
- 这种情况通常发生在对端还没有发送数据,或者数据已经全部读取完毕但对端还没有关闭连接。
3. **返回值为 -1**
- 表示对端已经关闭了连接,没有更多的数据可读。
- 这是判断对端已经断开连接的关键标志。
transferTo和transferFrom都是用于通道间直接传输数据,效率高(可能使用零拷贝):
```Java
// transferTo: 从source通道传输到target通道
sourceChannel.transferTo(position, count, targetChannel);
// transferFrom: 从source通道接收数据到target通道
targetChannel.transferFrom(sourceChannel, position, count);
```
主要特点:
1. 适用于FileChannel、SocketChannel等
2. position指定开始位置
3. count指定最大传输字节
4. 返回实际传输字节数
5. SocketChannel传输可能不完整,需要循环传输
需要代码示例吗?
**请求结构**
```JavaScript
下载请求
长度:76(1)
类型:10(1)
用户id:533909ec-2b8f-4b90-87ec-77c81cbc6fa3(36)
文件地址://DJI_20241028195835_0071_D.MP4(39)
```
```JavaScript
下载响应
长度:44(1)
类型:0(1)
请求长度:20(4)
文件实际地址:E://DJI_20241028195835_0071_D.MP4(39)
```
```JavaScript
下载请求
长度:44(1)
类型:11(1)
第几份:0(4)
文件地址:E://DJI_20241028195835_0071_D.MP4(39)
```
```JavaScript
下载响应
长度:13(1)
类型:0(1)
起始位:0(8)
文件长度:1024*1024*100(4)
---------------------------------
文件内容:100101010
```
```JavaScript
上传请求
长度:76(1)
类型:20(1)
用户id:533909ec-2b8f-4b90-87ec-77c81cbc6fa3(36)
文件地址://DJI_20241028195835_0071_D.MP4(39)
```
```JavaScript
上传响应
长度:25(1)
类型:0(1)
文件保存地址:/java/data/DJI_20241028195835_0071_D.MP4(24)
```
```JavaScript
上传请求
长度:37(1)
类型:10(1)
起始位:0(8)
长度:1024*1024*10(4)
文件保存地址:/java/data/DJI_20241028195835_0071_D.MP4(24)
--------------------------------
文件内容:100101010
```
```JavaScript
上传响应
长度:1(1)
类型:0(1)
```
---
其他请求
```JavaScript
查看请求show 文件
长度:37(1)
类型:7(1)
用户id:533909ec-2b8f-4b90-87ec-77c81cbc6fa3(36)
文件保存地址:E://result/DJI.MP4(24)
```
```JavaScript
下载请求
长度:76(1)
类型:10(1)
用户id:533909ec-2b8f-4b90-87ec-77c81cbc6fa3(36)
文件地址://DJI_20241028195835_0071_D.MP4(39)
```
```JavaScript
下载响应
长度:48(1)
类型:0(1)
文件长度:2027481740(8)
文件保存地址:/java/data/DJI_20241028195835_0071_D.MP4(24)
```
```JavaScript
下载请求
长度:52(1)
类型:11(1)
起始位:0(8)
文件长度:1024*1024*100(4)
文件地址:/java/data/DJI_20241028195835_0071_D.MP4(24)
```
```JavaScript
下载响应
长度:1(1)
类型:0(1)
---------------------------------
文件内容:100101010
```
```JavaScript
上传请求
长度:76(1)
类型:20(1)
用户id:533909ec-2b8f-4b90-87ec-77c81cbc6fa3(36)
文件地址://DJI_20241028195835_0071_D.MP4(39)
```
```JavaScript
上传响应
长度:25(1)
类型:0(1)
文件保存地址:/java/data/DJI_20241028195835_0071_D.MP4(24)
```
```JavaScript
上传请求
长度:37(1)
类型:21(1)
起始位:0(8)
长度:1024*1024*10(4)
文件保存地址:/java/data/DJI_20241028195835_0071_D.MP4(24)
--------------------------------
文件内容:100101010
```
```JavaScript
上传响应
长度:1(1)
类型:0(1)
```
### **如何将tinyint值NULL更改为默认的'0**
- 默认处可以输入,如果无法修改,先设置为可以为null再操作
![image 3 14.png](网盘项目/image314.png)
使用netty开发一个分片上传、下载系统,客户端发出下载请求,请求包含文件必要信息(文件名、起始位、文件长度等),服务端接收消息进行响应,响应内容保存文件必要信息、文件内容,客户端接收响应,根据响应保存文件;上传逻辑类似
```Java
public void channelRead(ChannelHandlerContext ctx, Object o) throws Exception {
ByteBuf buf=(ByteBuf)o;
if (!receivingFile) {
// 处理元数据
metadata = FileMetadata.fromBytes(buf);
System.out.println("客户端接受了消息: "+metadata);
prepareFileReceiving(metadata);
receivingFile = true;
ctx.fireChannelRead(buf);
return;
}
buf = buf.unwrap();
//分了两次
if(buf.readableBytes()==0) {
//需要继续处理
ctx.fireChannelRead(buf);
return;
}
System.out.println("==========================文件处理!");
int read = fileChannel.write(buf.nioBuffer());
System.out.println("====================="+read);
}
```
发送byte请求
- 传入bytebuf,对象自己转成byte
接收byte请求
- 传入byteBuf,返回对象
移除handle,系统会将剩余的handle都处理了!
```JavaScript
下载请求
是否有文件:false
起始位:0
长度:1024
服务器文件地址:/java/data/DJI.MP4
用户文件地址:E:/result/Df.MP4
```
```JavaScript
下载响应
是否有文件:true
起始位:0
长度:1024
服务器文件地址:/java/data/DJI.MP4
用户文件地址:E:/result/Df.MP4
---------------------------------
文件内容:100101010
```
```JavaScript
上传请求
是否有文件:true
起始位:0
长度:1024
服务器文件地址:/java/data/DJI.MP4
用户文件地址:E:/result/Df.MP4
---------------------------------
文件内容:100101010
```
```JavaScript
上传响应
状态:true
消息:保存成功
```
分片请求
检查是否有分片
```JavaScript
下载请求
是否有文件:false
起始位:0
长度:1024
服务器文件地址:/java/data/DJI.MP4
用户文件地址:E:/result/Df.MP4
```
```JavaScript
下载响应
是否有文件:true
起始位:0
长度:1024
服务器文件地址:/java/data/DJI.MP4
用户文件地址:E:/result/Df.MP4
---------------------------------
文件内容:100101010
```
```JavaScript
上传请求
是否有文件:true
起始位(第几片):1
长度:1024
服务器文件地址:/tmp/md5/1
用户文件地址:E:/result/Df.MP4
---------------------------------
文件内容:100101010
```
```JavaScript
上传响应
状态:true
消息:保存成功
```
```JavaScript
合并请求
是否有文件:false
起始位(总片数):18
长度:1024
服务器文件地址:/tmp/md5/1
用户文件地址:E:/result/Df.MP4
```
```JavaScript
上传响应
状态:true
消息:保存成功
```
- 明天功能
- ~~下载指定id~~
- ~~优化结构~~
- ~~生成idTree~~
- 分享功能(下周做)
- 指定id,生成链接
数据库设计
发送文件
```JavaScript
命令:send file
ip:192.168.1.100
测试成功
输入本地文件地址
```
接收文件
```JavaScript
命令receive file
ip:192.168.1.100
回车关闭
```
以下是一个典型网盘程序的8个功能模块,它们涵盖了大多数网盘应用的核心功能:
**1. 用户管理模块**
- **功能描述**:提供用户账户注册、登录功能
- **具体功能**
- 用户注册和登录
- 用户信息修改
- 查看空间
- 修改保存地址配置
- 密码找回(❓)
**2. 文件/夹管理模块**
- **功能描述**:创建、修改、删除文件夹
- **具体功能**
- 创建文件夹
- 移动、删除、重命名文件/夹
3. **文件查看与信息模块**
- **功能描述**:通过命令行显示文件的基本信息、文件结构属性
- **具体功能**
- 列出文件夹树、列表结构
- 查看文件的详细信息(如大小、修改时间、类型等)
**4. 搜索模块**
- **功能描述**:支持用户在网盘中搜索文件
- **具体功能**
- 文件名搜索(模糊查询)
- 按类型(图片、视频、文档等)搜索
**5. 文件上传、下载模块**
- **功能描述**:关于文件上传、下载
- **具体功能**
- 自定义通信协议
- 支持文件、文件夹上传下载
- 提供下载速度、进度显示
- md5校验避免重复文件
- 大文件分片传输
- ~~文件加密~~
**6. 文件分享模块**
- **功能描述**:允许用户将文件或文件夹分享给其他用户或生成共享链接。
- **具体功能**
- 生成共享链接(可设置有效期)
- 分享文件的管理(取消分享)
- 通过链接进行保存、下载
**7.管理员管理模块**
- **功能描述**:提供管理员对整个网盘系统的全面控制,包括用户管理、删除文件的恢复、存储空间分配和其他高级管理任务。
- **具体功能**
- 管理所有用户账号:创建、删除和修改用户账号
- 管理删除文件:查看和恢复用户删除的文件
- 分配和调整用户的存储空间配额
- 对异常操作进行干预(如锁定账号或限制操作)
- ~~查看和统计所有用户的存储使用情况~~
- ~~审查用户的操作日志(如文件的上传、删除、分享等操作)~~
**8.局域网文件传输模块**
- **功能描述**:提供用户在同一局域网内的直接文件传输功能,支持快速的面对面文件共享,避免互联网依赖。
- **具体功能**
- 支持pc客户端半双工通信(便于接收文件),基于自定义协议,文件上传效率搞
- 支持跨设备的局域网通信,基于http协议,上传效率底
**总结**
这8个功能模块涵盖了网盘应用的各个方面,从文件操作到安全保护,提供了一个完整的网盘系统功能集合。每个模块都可以根据具体需求进行扩展和定制,以满足不同用户群体的使用需求。
下载速度显示
~~速度显示~~
分享
- 查看分享表
- 取消分享
- 设置分享时间
- 设置密码
注册,以及空间显示
完成分享测试
管理员
前端界面优化(ai
- 选择上传
- 速度显示
pc、手机传输
- 手机端
- 上传:扫码选择上传
- 下载:
- pc端
- 上传
- 下载
```Java
_ _ ____ _ _ _ _ .-~~~-.
| | | | _ \| \ | | | | | .-~~' __ `~~-.
| |__| | |_) | \| | | | | / .-~ ~-. \
| __ | _ <| . ` | | | | | .-' `-. |
| | | | |_) | |\ | |__| | \ / \ /
|_| |_|____/|_| \_|\____/ `-.~ ._ .,., ,..~-'
.--.-.
( ( )__
(_, \ ) ,_)
'-'--`--'
.-~~~-.
.-~~' __ `~~-.
/ .-~ ~-. \
| .-' `-. |
\ / \ /
`-.~ ._ .,., ,..~-'
```
对服务端异常进行处理,将序列化错误、连接中断
处理服务器端异常
![image 4 13.png](网盘项目/image413.png)
连接异常
![image 5 12.png](网盘项目/image512.png)
登录报错
![image 6 10.png](网盘项目/image610.png)
```Java
# 登录mysql在默认安装时如果没有让我们设置密码则直接回车就能登录成功
sudo mysql -uroot -p
# 设置密码 mysql8.0
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '新密码';
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'hbnu';
# 设置密码 mysql5.7
set password=password('新密码');
# 配置IP 5.7
grant all privileges on *.* to root@"%" identified by "密码";
# 刷新缓存
flush privileges;
```
题目描述
假设系统中有A、B、C三类资源,且有五个并发进程,要求输入系统空闲资源数Avaiable,以及每个进程运行所需的资源总量Caim、已经分配得到的资源量Alocation,利用银行家算法计算资源总量Resource和需求矩阵Need。
输入格式
程序要求输入六行,以回车符号作为分隔,第一行是三个整数,整数之间以空格作为分隔,表示当的系统A、B、C三类空闲资源数Avallable,下面的五行分别表示每个进程运行所需的资源总量Caim和已经分配得到的资源量Alocatin;每行有7个数据,以空格作为分隔。首先输入一个字符串(长度小于等于10),为进程名;第2、3、4个数掘类型为整型,表示相应进程运行所需A、B、C三种资源总量Caim;第5、6、7个数据类型为整型,表示相应进程已经分配得到的A、B、C三种资源量Alocation。
输出格式
输出六行,以回车符号作为分隔,第一行是三个整数,整数之间以空格作为分隔,表示系统的A、B、C三类资源总量Resource,下面的五行分别表示每个进程运行还需要的资源量:每行有4个数据,以空格作为分隔。首先输出一个字符串(长度小于等于10),为进程名;第2、3、4个数据类型为整型,表示相应进程运行还需的A、B、C三种资源量。
输入样例
3 3 2
P0 7 5 3 0 1 0
P1 3 2 2 2 0 0
P2 9 0 2 3 0 2
P3 2 2 2 2 1 1
P4 4 3 3 0 0 2
输出样例
10 5 7
P0 7 4 3
P1 1 2 2
P2 6 0 0
P3 0 1 1
P4 4 3 1
![QQ_1733367586038.png](网盘项目/QQ_1733367586038.png)
题目描述
假设系统中有A、B、C三类资源,且有四个并发进程,要求输入资源总量Resource,以及每个进程运行所需的资源总量Ciaim和已经分配得到的资源量Alocation,利用银行家算法判断当前状态是否为安全状态,若为安全状态则给出一个安全序列。
输入格式
程序要求输入五行,以回车符号作为分隔,第一行是三个整数,整数之间以空格作为分隔,表示A、B、C三类资源的总量,下面的四行分别表示每个进程运行所需的资源总量Claim和已经分配得到的资源量Alocation;每行有7个数据,以空格作为分限。首先输入一个字符串(长度小于等于10),为进程名;第2、3、4个数据类型为整型,表示相
应进程运行所需A、B、C三种资源总量Claim;第5、6、7个数据类型为整型,表示相应进程已经分配得到的A、B、C三种资源量Alocation。
输出格式
输出一个字符串。若当前为不安全状态则输出为"false”(不含双引号,所有字母皆为小写);若当前为安全状态则输出一个安全序列,进程名之间用空格作为分隔。
输入样例
9 5 7
P1 5 3 4 2 1 3
P2 9 5 2 2 1 1
P3 3 2 2 2 2 1
P4 6 4 1 1 1 1
输出样例
p3 p1 p4 p2
题目描述
当采用可变分区分配方案对512KB的内存进行管理时,要求输入多个进程已经占用分区信息,多个进程内存回收信息,输出显示空闲分区的个数。
输入格式
程序要求输入3行,以回车符号作为分隔,第一行是一个整数n(10>n>0),表示n个进程已经占用分区;第二行是2n个整数,以空格作为分隔,依次按地址递增对应第一行n
个进程(进程编号依次为p1、p2、p3….pn,p为小写字母)已经占用分区的起始地址和存储容量(单位为K8)。第三行是一个字符串,用进程编号表示所回收的进程,编号之间用空格作为分隔符。
输出格式
输出一个整数表示回收后空闲分区的个数,若有回收的进程不存在,则输出“false”(不含双引号,所有字母皆为小写)
输入样例
2
0 60 100 220
p1
输出样例
2
题目描述
分页式存储系统中利用位示图对8*8块的存储器进行管理(块下标从0到63)。若已有块号为1、3、5、7、9、11、13、15、17、19的十个内存块被占用。现有四个进程要申请内存空间(每个进程最多为54个页面,编号为0到53也即申请的内存空间最多为54块)以装入。若系统有足够的空间能满足进程所申请的空间则技照从上到下,从左到右的顺序扫描位示图选择空块将其装入,否则进程在外存等待。
输入格式
程序要求输入2行,以回车符号作为分隔,第一行是四个整型数,以空格作为分隔,分别表示四个进程(进程编号依次为1、2、3、4)所申请的内存块数。第二行是2个整
数,以空格作为分隔。第一个整数N(1<=N<=4)表示所输入的是进程编号为N,第二个是整数M(0<-M<=53),表示进程编号为N的M号页面。
输出格式
若进程N装入了内存且页号M合理,则输出为一个整数,表示该页面所装入的内存块号:若进程N装入了内存但页号M有错,则输出字符串"eror”(不含双引号,所有字母皆为小写);若进程N在外存等待,则输出字符串“wait"(不含双引号,所有字母皆为小写)
输入样例
16 16 16 16
4 1
输出样例
wait
题目描述
要求输入一个柱面访问请求序列以及当前磁头所在柱面号和移动方向,输出采用电梯调度算法时移动臂响应的柱面访问序列。
输入格式
程序要求输入3行,以回车符号作为分隔,第一行是2个整型数n,m,之间用空格隔开,n表示当前磁头所在的柱面号,m表示第二行输入m个数;第二行是m个整数,数之间以空格作为分隔,表示柱面访问请求序列;第三行是数字-1或1,当为-1时表示移动臂向柱面号减小方向移动,当为1时表示移动暋向桂而号增大方向移动。
输出格式
输出m个整数,数之间以空格作为分隔,采用电梯调度算法时移动臂响应的柱面访问序列。
输入样例
15 10
24 38 2 110 43 36 5 11 6 180
-1
输出样例
11 6 5 2 24 36 38 43 110 180
========================================
题目描述
要求输入4个进程的信息,若进程调度采用短作业优先SJF调度算法(若所需运行时间一样,则按照输入顺序执行),请输出各个进程的周转时间
输入格式
程序要求输入4行,以回车符号作为分隔,每行有3个数据,以空格作为分隔。首先输入一个字符串(长度小于等于10),为进程名,第2个数据类型为整型,表示进程到达的时刻,第3个数据类型为整型,表示进程所需运行的时间
输出格式
按照输入的顺序依次输出各个进程的周转时间,各个整数之间用一个空格作为分隔。
输入样例
P1 0 9
P2 0 4
P3 0 10
P4 0 8
输出样例
21 4 31 12
题目描述
要求输入4个进程的信息,若进程调度采用最短剩余时间优先SRTF调度算法(若所需运行时间一样,则技照输入顺序执行。),请按照执行过程的顺序输出各个进程的名称
输入格式
程序要求输入4行,以回车符号作为分隔,每行有3个数据,以空格作为分观,首先输入一个字符串(长度小于等于10),为进程名,第2个数据类型为整型,表示进程到达的时刻,第3个数据类型为整型,表示进程所需运行的时间,
输出格式
按照执行过程的顺序输出各个进程的名称即一个字符串,进程名称之间用一个空格作为分隔。
输入样例
P1 0 9
P2 0 4
P3 0 10
P4 0 8
输出样例
P2 P4 P1 P3
题目描述
分页式存储系统中利用位示图对8*8块的存储器进行管理(块下标从0到63)。若已有块号为1、3、5、7、9、11、13、15、17、19的十个内存块被占用。现有四个进程要申请内存空间(每个进程最多为54个页面,编号为0到53也即申请的内存空间最多为54块)以装入。若系统有足够的空间能满足进程所申请的空间则技照从上到下,从左到右的顺序扫描位示图选择空块将其装入,否则进程在外存等待。
输入格式
程序要求输入2行,以回车符号作为分隔,第一行是四个整型数,以空格作为分隔,分别表示四个进程(进程编号依次为1、2、3、4)所申请的内存块数。第二行是2个整数,以空格作为分隔。第一个整数N(1<=N<=4)表示所输入的是进程编号为N,第二个是整数M(0<=M<=53),表示进程编号为N的M号页面。
输出格式
若进程N装入了内存且页号M合理,则输出为一个整数,表示该页面所装入的内存块号:若进程N装入了内存但页号M有错,则输出字符串"eror”(不含双引号,所有字母皆为小写);若进程N在外存等待,则输出字符串“wait"(不含双引号,所有字母皆为小写)
输入样例
16 16 16 16
4 1
输出样例
wait
管理员功能
- 用户
- 查看用户 ls user
- id 名称 注册时间 上次登录 使用空间 总空间 是否注销
- 封号 close id
- 恢复 start id
- 彻底删除用户 delete user id
- 用户下的所有文件都删除
- 增加空间 space add id XG
- 文件
- 查看所有用户删除的文件 ls file
- fid 用户名称 文件名称 类型
- 查看服务器所有无用文件 ls sfile
- fid 文件名称 类型 大小
- 删除所有用户删除文件 delete fileall
- 删除所有无用文件 delete sfile
- 获得所有文件的地址,数据库将记录去除,通知服务器将文件全部清除
- 恢复用户文件夹 rollback uid
```Java
FROM openjdk:17
WORKDIR /app
COPY FileServer.jar /app/FileServer.jar
EXPOSE 8972
CMD ["java", "-jar", "FileServer.jar"]
# 在Dockerfile所在目录执行
docker build -t FileServer:1.0 .
curl https://www.baidu.com
```
回滚数据判断文件夹名
写一个下载、上传工具
- 上传API
- 下载API
- 分片上传
- 断点续传
前端
:发送文件md5,检查分片进度请求(判断上传了几个),返回值为已经上传几个分片
:分片上传(上传未上传部分)
:整合请求
Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

@@ -0,0 +1,106 @@
---
title: 通用权限管理项目
date: 2025-02-21
categories: [工作, 项目]
tags: [项目]
---
## 前端
- 封装菜单、菜单项目,动态路由
- 封装http请求
- 封装弹窗
- 封装提示框
数据持久化插件
[开始 | Pinia Plugin Persistedstate](https://prazdevs.github.io/pinia-plugin-persistedstate/zh/guide/)
动态路由
![image 44.png](通用权限管理项目/image44.png)
动态菜单
## 后端
角色
- 简单的增删改查
用户
- 简单的增删改查
- 和角色的一个联查
菜单
- 简单的增删改查
- 将菜单进行手动拼接成Tree
## 简单的增删改查
### 增
- 将数据库表映射成对象
- 编写通用增加接口: int add(T t)
- 动态sql
### 改
- 编写通用修改接口: int edit(T t)
- 动态Sql
### 删
- 通过id删除:int delete(int id)
### 查
- 普通查询
- 分页:pagehelper
对于菜单分配的设计问题
角色查看分配菜单
方案一(role_id
- 返回角色对应的菜单id(表示选中)
- 直接返回==所有菜单==(树结构)
- 优点:简单
- 缺点:用户可以看到所有权限,并可以进行修改
方案二(user_id,role_id
- 返回角色对应的菜单id(表示选中)
- 返回==用户对应的菜单====(树结构)==
- 优点:用户只可以看见自己的权限,并且修改也只可以在此基础上
- 缺点:需要设置是否是为管理员字段(保证至少一个显示完整菜单)
## 后端进阶
- 验证码图片生成
持久化数据
- 持久化token、持久化userId、userName
### SpringSecurity
基于一种维持会话的方式(session、jwt)进行防止直接访问后端请求
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

@@ -0,0 +1,58 @@
---
title: hexo与md完美结合
date: 2025-09-15 04:57:10
tags: [hexo]
---
## 前言
想搭建一个博客,看了很多如halo、hexo、hugo、mkdocs、Jekyll等多种博客;都不满足我的需求
**我的需求如下**
- 完美支持md,目前主流语法
- 占用内存下,小服务器能跑
- 颜值不要太难看
## 搭建hexo
### 安装nodejs
自行安装
设置url按标题生成
`hexo/_config.yml` 里加:
```yaml
permalink: :title.html
```
**为了较为完美的实现md语法**,自定义脚本实现链接
脚本存储位置:blog\scripts\md-link-fix.js
```js
hexo.extend.filter.register('before_post_render', function (data) {
if (!data.content) return data;
// 处理 md 链接
data.content = data.content.replace(
/\[([^\]]+)\]\(([^)]+)\.md\)/g,
function (match, text, path) {
return `[${text}](${path}.html)`;
}
);
// 处理图片 (匹配 ![alt](文件夹/文件.png) → 不动,只确保不拼 .html)
data.content = data.content.replace(/!\[([^\]]*)\]\(([^)]+)\)/g,
(match, alt, path) => {
return `![${alt}](${path})`;
});
// console.log("过滤器已运行:", data.source);
return data;
});
```
目前图片链接处理可能还是会导致异常,问题在于会修稿所有链接,所以可能会误伤其他链接
@@ -0,0 +1,12 @@
---
title: hexo测试
date: 2025-10-11 19:57:10
tags: [hexo]
---
![image-20251011202431088](hexo测试/image-20251011202431088.png)
[链接测试](./hexo部署.md)
[链接测试](../../../编程技术/java/JavaSE/IO流/装饰器设计模式(Decorator Pattern).md)
Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

@@ -0,0 +1,29 @@
---
title: hexo简化操作
date: 2025-09-15 04:53:57
tags: [hexo]
---
### 简化运行操作
**1、一条命令运行 `hexo clean、hexo g、hexo s`**
`package.json` 里加一个脚本命令:
```json
{
"scripts": {
"dev": "hexo clean && hexo g && hexo s"
}
}
```
以后直接执行:
```
npm run dev
```
就等同于 `hexo clean && hexo g && hexo s`
(或者在 `bash/zsh` 下自己写 alias`alias hcs="hexo clean && hexo g && hexo s"`
### 自动生成 Front-matter
@@ -0,0 +1,26 @@
---
title: hexo设置脚本自动创建元数据
date: 2025-09-15 04:50:51
tags: [hexo]
---
# hexo设置脚本自动创建元数据
## 步骤如下
### 编写创建脚本
查看操作系统编码格式,中文操作系统,编码为GBK
### 写入注册表
注册表编码格式为UTF-16 LE with BOM
查看图标位置
```
计算机\HKEY_CLASSES_ROOT\Typora.Typora 1.3.6中文直装版.md\DefaultIcon
```
## 展示效果
右键鼠标 —> 选中“创建hexo文章” —> 弹出弹窗,输入名称
@@ -0,0 +1,41 @@
---
title: hexo部署
date: 2025-09-14
tags: [hexo]
---
**1️⃣ Hexo Admin 在服务器上直接公网编写文章**
- 可行,但要注意安全:
- Hexo Admin 本质是一个本地后台,默认是运行在 Node.js 服务器上的。
- 如果你把它部署在公网服务器:
- 你可以直接在浏览器访问 http://你的服务器IP:端口/admin,可视化编写文章。
- 写完文章后点击发布,它会生成静态文件(public/)。
- 你只需要在服务器上配置好 Nginx/Apache 指向 public/ 就能上线。
- 安全注意事项:
- 默认 Hexo Admin 用户名/密码可能简单,需要设置强密码。
- 最好用 反向代理 + HTTPS + IP 限制 或 VPN 保护后台。
- 如果直接暴露在公网,存在被攻击风险。
✅ 总结:Hexo Admin 可以在服务器上实现公网编辑和直接生成,但需要严格安全配置。
**2️⃣ 利用 GitHub 平台,个人仓库 + 自动部署**
- 完全可以:
- GitHub 上可以创建个人仓库存放 Hexo 博客源文件(source/_posts 等)。
- 编写文章后:
- 本地 hexo g 生成静态文件
- hexo d 可以配置自动推送到 GitHub Pages(仓库的 gh-pages 分支)
或者通过 CI/CD 自动把 public/ 部署到你自己的服务器。
- 好处:
- GitHub 保存一份 Markdown 原始内容(版本控制)
- 同时可以自动上线到你的网站(GitHub Pages 或 VPS
- 流程示例:
- 本地写文章 → 保存到仓库
- GitHub 自动触发 Actions
- 生成 Hexo 静态文件
- 推送到部署服务器 / GitHub Pages
- 网站更新完成,访问即见效果
✅ 总结:GitHub 仓库可以个人使用,同时实现“保存一份 + 自动上线”。
@@ -0,0 +1,24 @@
---
title: 开发IDE
date: 2025-06-13
tags: [IDE]
---
## IDEA
### JRebel
启动报错
![image 10.png](开发IDE/image10.png)
解决方案
![image 1 7.png](开发IDE/image17.png)
出处:[https://stackoverflow.com/questions/54191079/jrebel-jvmti-error-when-running-spring-server](https://stackoverflow.com/questions/54191079/jrebel-jvmti-error-when-running-spring-server)
## VScode
Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

@@ -0,0 +1,176 @@
---
title: git基本命令
date: 2024-12-10
tags: [git]
---
## 基本操作
1、初始化:`git init`
2、添加(全部文件到)暂存区:`git add .`
- 相当于交给git管理文件
撤销暂存区:`git reset`
3、添加到本地仓库:`git commit -m 'msg'`
4、添加到远程仓库:`git push -u origin "master"`
- 前提是添加了远程仓库,如下
`git remote add origin` `[https://gitee.com/QGS_Code/file-system.git](https://gitee.com/QGS_Code/file-system.git)`
取消git管理
- 删除.git
更换远程仓库
- git remote -v
- git remote set-url origin [https://github.com/yourusername/new-repository.git](https://github.com/yourusername/new-repository.git)
### 可以设置 `git status` 忽略未关心目录里的改动
比如前端不关心 backend 改动,可以临时忽略:
```Shell
git update-index --assume-unchanged backend/*
```
恢复:
```Shell
git update-index --no-assume-unchanged backend/*
```
反过来一样。
---
git版本控制
本地记录
- master分支:A — B — C
- 其他分支:A — B — C — D — E
远程记录
- 远程masterA— B — C
- 其他远程分支:A — B — C — D — E
### git fetch
这会更新本地的**远程跟踪分支**
### git **merge** master
把master的内容**合并**到当前分支,生成一个新版本,一般都是将其他分支合(新功能)并到主分支
- 图解
👉 `M` 是 merge commit,表示 `comment_dev``master` 的 C 合进来了。
```Java
A---B---C (master)
\
D---E (comment_dev)
A---B---C (master)
\ \
D---E---M (comment_dev)
更新内容
A---B---C (master)
\
D---E (comment_dev)
A---B---C---M (master)
\ \
D---E (comment_dev)
```
### git pull
=**git fetch + git merge**
### git **rebase** master
基于最新版本开发(可能要解决冲突),**变基,**合并代码的另一种方式
- 将 `**comment_dev**` 的提交 `**D**` 和 `**E**` 暂存起来。
- 将 `**comment_dev**` 的指针移动到 `**master**` 的最新提交 `**G**`。
- 尝试将 `**D**` 和 `**E**` 重新应用到 `**G**` 之上。
```Java
A---B---C (master)
\
D---E (comment_dev)
A---B---C (master)
\
D---E (comment_dev)
```
## 📌 注意 ⚠️
- **rebase 之后必须强推(push -f**
因为提交历史改了,普通 push 会被拒绝。
```Shell
git push -f origin feature-branch
```
- 不要对**公共分支**(比如 dev、master)执行 rebase,会影响其他人。
### 本地 `master` 回退到你想要的版本
比如:
```Shell
git checkout master
git reset --hard 4e5f6g7 # 你要回去的那个 commit
```
---
### 📊 2️⃣ 强制推送覆盖远程 master
```Shell
git push -f origin master
```
查看提交记录(q 退出)
```Shell
git log --oneline --graph --decorate
```
## 📦 总结:
| | | | |
|---|---|---|---|
|操作|远程新提交|本地 `A`|本地 `origin/A`|
|`fetch`|获取到了|不变|更新到最新|
|`merge origin/A`|会合并|和 `origin/A` 合并|-|
|`pull`(默认 `fetch+merge`|获取并合并|会更新|同步|
@@ -0,0 +1,40 @@
---
title: 常用软件
date: 2025-08-17
tags: [计算机使用]
---
## 虚拟机
VMware Workstation Pro
## 文档编辑器
typora 处理md
sublime text 任何格式编辑器
## IDE
idea
> [!info] JETBRA.IN CHECKER | IPFS
>
> [https://3.jetbra.in/](https://3.jetbra.in/)
查看readme
[https://www.jetbrains.com/idea/download/](https://www.jetbrains.com/idea/download/)
下载历史版本
## 图形化界面
navicat (mysql)
resp (redis)
@@ -0,0 +1,140 @@
---
title: 操作系统重装
date: 2024-11-06
tags: [计算机使用]
---
## 必要软件
- Xshell
- Finamshell
- telegram
- clash
- qq
- StarUML
- ioDraw
- typora
- notion
- navicat
- visual studio code
- chrome
- edge
- idea
- wireshark
- apifox
- resp
- onenote
### 制作启动盘
一个16G硬盘,下载下面内容制作成启动pan
[下载 Windows 10](https://www.microsoft.com/zh-cn/software-download/windows10?msockid=3c106fe98cf1616a01e17ac78dd16015)
电脑启动,进入BIOS,并选择U盘启动,使用**UEFI 启动模式**
磁盘分区需要时GPT,如果是**MBR 分区表,进行格式化转格式**
📌 方法①:把硬盘从 MBR 转成 GPT ✅(推荐)📌 操作:
1. 在 Win10 安装界面,按下 **Shift + F10** 打开命令行
2. 输入 `diskpart` → 回车
3. 输入 `list disk` → 回车
4. 找到你的系统盘编号(比如 `Disk 0`
5. 输入:
```Shell
select disk 0
clean
convert gpt
exit
```
6. 关闭命令行窗口
7. 回到安装界面,点击【刷新】或新建分区继续安装
✔️ 这样就是 GPT 分区表,UEFI 就能正常装了
⚠️ 注意:`clean` 会清空整块硬盘所有分区,数据务必提前备份
### 激活系统
### 激活office
## 绑定类型
📌 📖 Windows 和 Office 激活方式是否**绑定机器** or **绑定微软账号**,取决于激活类型
---
### 📦 📌 一、Windows 激活绑定规则
|激活类型|绑定对象|说明|
|---|---|---|
|**数字许可证 (数字权利)**|📌 绑定**硬件设备 ID** 和(可选)微软账号|硬件 ID 改变激活失效,但绑定微软账号后可通过账户迁移|
|**OEM 激活**|📌 绑定**主板 BIOS**|出厂预装的 Windows,换主板必定失效|
|**KMS 批量授权**|📌 不绑定账号,只绑定**KMS服务器和机器**|180天续期,靠局域网或公网KMS服务器激活|
|**MAK 批量激活**|📌 绑定**机器硬件ID**|激活码次数有限,换硬件需重新激活|
### 📌 查看自己是否有数字许可证绑定账号
设置 → 更新和安全 → 激活
- 如果显示【使用数字许可证与您的 Microsoft 帐户关联】,说明是账号+设备双绑定
---
### 📦 📌 二、Office 激活绑定规则
|激活类型|绑定对象|说明|
|---|---|---|
|**Office 365 / Microsoft 365**|📌 必须绑定**微软账号**|在线激活,账号随设备登录即可,重装设备用账号激活|
|**零售版 (Retail)**|📌 可选绑定**微软账号**,一般绑定机器|通常建议绑定,便于重装迁移。激活次数有限制|
|**批量授权版 (VL/KMS)**|📌 不绑定账号,只绑定机器激活状态|180天 KMS 激活,靠服务器续期,不关联账号|
|**MAK 批量激活**|📌 绑定机器硬件ID|激活次数有限,换硬件需重新激活|
### 📌 查看 Office 是否绑定账号
打开 Word / Excel → 【账户】
- 如果右上角头像显示微软账号,说明已绑定
- 如果显示“未登录”,就是本地激活型(如KMS)
---
### 📌 📣 总结一句
|项目|绑定账号|绑定机器|
|---|---|---|
|Windows 数字许可证|✅|✅(硬件ID)|
|Windows KMS|❌|✅|
|Office 365|✅|❌|
|Office 零售版|可选|通常绑定|
|Office KMS|❌|✅|
---
### 📦 📌 最后提醒
👉 **数字许可证**:绑定硬件ID + 微软账号,重装系统后只要硬件不变,联网+账号就能自动激活
👉 **Office 365**:完全依赖微软账号,设备随便换
👉 **KMS 批量版**:跟账号无关,靠局域网或公网 KMS 服务器续期
+33
View File
@@ -0,0 +1,33 @@
---
title: Read
date: 2024-08-25
tags: [生活记录]
---
## 书签
- [ ] [[multithread]]
[[multithread]]
- [ ] [https://javaguide.cn/javaguide/contribution-guideline.html](https://javaguide.cn/javaguide/contribution-guideline.html)
> [!info] 贡献指南
> 欢迎参与 JavaGuide 的维护工作,这是一件非常有意义的事情。详细信息请看:JavaGuide 贡献指南 。 你可以从下面几个方向来做贡献: 修改错别字,毕竟内容基本都是手敲,难免会有笔误。 对原有内容进行修改完善,例如对某个面试问题的答案进行完善、对某篇文章的内容进行完善。 新增内容,例如新增面试常问的问题、添加重要知识点的详解。 目前的贡献奖.
> [https://javaguide.cn/javaguide/contribution-guideline.html](https://javaguide.cn/javaguide/contribution-guideline.html)
- [ ] [https://crossoverjie.top/JCSprout/#/collections/HashSet](https://crossoverjie.top/JCSprout/#/collections/HashSet)
> [!info] JCSprout
> Description
> [https://crossoverjie.top/JCSprout/#/collections/HashSet](https://crossoverjie.top/JCSprout/#/collections/HashSet)
- [ ] [https://developers.pub/wiki/1002310/1016256](https://developers.pub/wiki/1002310/1016256)
> [!info] 开发者客栈-帮助开发者面试的平台-顽强网络
> 一个帮助开发者面试的平台。开发者客栈集成各类面试经验分享和面试题专业解答、免费简历生成和下载、一线互联网公司内推等多功能多元化平台,致力于构建一个专业度高、知识共享、内容丰富的有爱社区。
> [https://developers.pub/wiki/1002310/1016256](https://developers.pub/wiki/1002310/1016256)
## 已读
@@ -0,0 +1,47 @@
---
title: 失地证信息
date: 2024-09-10
tags: [失地证]
---
- 打印申请表
- 填表需知:此表一式一份。
1、申请需如实填写以上信息;
2、学历栏在对应栏内打“√”;
3、学校证明栏需加盖学校公章;
4、字迹清楚,不得涂改。
- 提供材料
- [x] 学生**社保卡**账号复印件;
- [x] 学生**身份证**复印件;
- [x] 学费**缴款**凭证原件。
- [x] 录取通知书复印件或学籍证明(附件1、附件2);
- [x] 失地农民证复印件;
- 数据
- 失地证号:**赣蓉K21010021**
- 参考图片
![封面.png](失地证信息/封面.png)
封面
![公章.png](失地证信息/公章.png)
公章
![信息.png](失地证信息/信息.png)
信息
![参考.png](失地证信息/参考.png)
参考
![参考1.png](失地证信息/参考1.png)
参考
Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

@@ -0,0 +1,100 @@
---
title: 加快发展新质生产力的路径与实践探讨
date: 2024-9-15
tags: [学校]
---
摘要
新质生产力是推动我国经济高质量发展的核心动力,也是全面建设社会主义现代化强国的重要支撑。本文围绕加快发展新质生产力的主题,探讨了新质生产力的内涵及其对经济发展的重要意义,分析了当前制约其发展的主要问题,并从政策支持、科技创新、产业结构优化和人才培养等角度提出了具体路径与实践策略。通过对理论与实践的结合分析,本文旨在为促进我国新质生产力的全面发展提供借鉴和参考。
新质生产力;高质量发展;科技创新;产业升级;现代化
正文
引言
关键词
在当前全球化加速、科技革命不断迭代的背景下,新质生产力已成为全球经济竞争的重要领域。作为经济增长的新动能,新质生产力不仅包括技术进步和生产方式的创新,还涉及到经济结构转型、资源配置效率提升以及生态友好型发展的综合体系。如何加快发展新质生产力,已成为我国实现经济高质量发展和迈向现代化的必由之路。
一、新质生产力的内涵与意义
1. 新质生产力的内涵
新质生产力相较于传统生产力更强调以下几个方面:
- 科技驱动:以人工智能、大数据、区块链等新兴技术为核心驱动力。
- 绿色发展:注重资源节约和生态保护,实现可持续发展。
- 价值链提升:推动从低端加工制造向高端智能制造转型,提升产品和服务的附加值。
1. 发展新质生产力的意义
- 推动经济转型升级:新质生产力能够突破传统产业的瓶颈,为经济注入新活力。
- 提升国际竞争力:增强我国在全球产业链、供应链中的核心地位。
- 实现可持续发展目标:通过技术创新与绿色发展路径,促进人与自然的和谐共生。
二、当前制约新质生产力发展的主要问题
尽管我国在新质生产力发展方面取得了显著进步,但仍面临一些制约因素:
1. 科技创新能力不足
尽管我国在高铁、5G等领域处于世界领先地位,但在核心技术研发和原创性科技成果方面仍有待突破。
1. 产业结构优化不足
我国产业链存在低端过剩、高端不足的问题,传统产业转型升级相对缓慢。
1. 人才供给与培养不平衡
高端创新型人才缺乏,技术工人队伍建设有待加强,特别是在新兴领域的人才储备尚显不足。
1. 政策支持与市场环境不完善
在政策设计和市场环境上,部分地区和行业还存在创新投入不足、制度创新滞后的问题。
三、加快发展新质生产力的路径与实践
1. 强化科技创新驱动
- 加大科研投入:提升国家对基础研究和关键技术研发的资金投入比例,鼓励企业加大研发力度。
- 构建创新平台:搭建产学研一体化的创新平台,促进技术研发与产业应用的深度融合。
- 完善知识产权保护:通过法律和政策手段保障创新主体的合法权益,激发创新活力。
1. 推动产业结构优化升级
- 大力发展高端制造业:聚焦航空航天、新能源汽车、集成电路等领域,打造全球领先的产业集群。
- 培育战略性新兴产业:发展新一代信息技术、绿色环保、新材料等新兴产业,提升经济增长韧性。
- 推动传统产业智能化改造:通过数字化、智能化手段提高传统产业的效率和竞争力。
1. 加强人才培养与引进
- 完善人才培养体系:加大对工科教育和职业技术教育的支持力度,打造一支技术过硬、创新能力强的专业人才队伍。
- 优化人才引进政策:通过全球招聘和高端人才引进计划吸引优秀海外人才,为新质生产力提供智力支持。
- 营造创新创业环境:完善配套政策和服务体系,激发高校、科研机构和企业的创新潜力。
1. 优化政策支持与市场环境
- 加强政策协调性:制定与新质生产力发展相适应的政策体系,涵盖金融支持、税收优惠和产业引导等方面。
- 构建良好市场环境:破除垄断和行业壁垒,鼓励公平竞争,激发中小企业的创新活力。
- 强化区域协同发展:推动东部发达地区与中西部地区的联动发展,共同打造区域性新质生产力增长极。
四、案例分析与实践经验
1. 国内经验:深圳创新经济的启示
深圳作为我国科技创新的标杆城市,通过营造良好的政策环境和市场机制,吸引了大量高科技企业和人才集聚,成功构建了涵盖基础研究、技术开发和产业应用的创新生态体系。
1. 国际经验:德国工业4.0的借鉴
德国通过实施“工业4.0”战略,全面推动制造业数字化、智能化转型,为我国传统产业升级提供了有益的参考经验。
五、结论与展望
加快发展新质生产力是我国实现经济高质量发展和现代化建设的重要途径。在未来的实践中,我们需要持续深化科技创新、优化产业结构、完善政策支持,并加强国际合作,积极吸取全球先进经验。通过全社会的共同努力,我国必将实现从传统经济大国向创新经济强国的转型,为实现中华民族伟大复兴的中国梦提供坚实支撑。
参考文献
@@ -0,0 +1,24 @@
---
title: 发言稿
date: 2024-9-15
tags: [学校]
---
同学们好!我叫邱国帅,专科就读于文化学院 计算机应用技术专业,专升本英语成绩为85分,专业课105分,最终上岸湖北师范大学,计算机科学与技术专业,
相信大部分人选择专升本的目的就是提升学历,我和大家也一样,我是从去年9月开始备考,当时就以湖北师范大学为目标,虽然有一定考取难度,但我专业课有一定的基础,加上我相信新东方对我的英语的有一定保障,所以便坚定的走下去
关于英语的学习,我认为词汇是重中之重,这个只有靠自己没有什么捷径可言,个人推荐使用不背单词app,我在上面累计背诵时长1w6k多分钟,语法方面老师就显的就比较关键,不同老师讲的语法效果真的很不一样,像郭璁老师语法讲的就通俗易懂、详略得当,特别感谢助教吴雨桐老师,每次遇到语法问题都会为我耐心解答,还有翻译夏斌老师、写作刘爽老师、阅读韩勤勤老师等宝藏老师,都对我的英语有很大的帮助
最后,希望学弟学妹们,可以坚持走下去,相信我,每天坚持学习,你就已经战胜了90%的对手,加油!
同学们好!我叫邱国帅,专科就读于文化学院 计算机应用技术专业,专升本英语成绩为85分,专业课105分,最终上岸湖北师范大学,计算机科学与技术专业,
相信大多数同学选择专升本的目的都是为了提升学历,我也不例外。从去年9月开始,我便以湖北师范大学为目标,尽管考取有一定难度,但我对自己的专业基础有信心,同时也相信新东方的英语课程会为我的英语学习保驾护航。所以便坚定的走下去
对于这次专升本的英语成绩,虽然分数不是特别高,但达到了我的预期,因此我还是感到比较满意的。关于英语学习,我认为词汇是最重要的基础,这部分没有捷径,完全靠自己的积累。我个人推荐使用“不背单词”APP,我在这款软件上累计背诵时长超过16000分钟。至于语法学习,老师的讲解就显得至关重要了,不同的老师教学效果确实有差异。比如新东方郭璁老师的语法课,讲解得通俗易懂、重点突出,非常感谢他的帮助。同时,我也特别想感谢助教吴雨桐老师,她每次都耐心解答我遇到的语法问题。还有翻译课的夏斌老师、写作课的刘爽老师、阅读课的韩勤勤老师等,他们都是帮助我提升英语水平的宝藏老师。
最后,我想对学弟学妹们说,坚持就是胜利。每天保持学习的习惯,你已经战胜了90%的对手。相信自己,加油!
+103
View File
@@ -0,0 +1,103 @@
---
title: 论文
date: 2024-9-15
categories: [生活记录, 学校]
tags: [学校]
---
论文题目:加快发展新质生产力的路径与实践探讨
摘要
新质生产力是推动我国经济高质量发展的核心动力,也是全面建设社会主义现代化强国的重要支撑。本文围绕加快发展新质生产力的主题,探讨了新质生产力的内涵及其对经济发展的重要意义,分析了当前制约其发展的主要问题,并从政策支持、科技创新、产业结构优化和人才培养等角度提出了具体路径与实践策略。通过对理论与实践的结合分析,本文旨在为促进我国新质生产力的全面发展提供借鉴和参考。
关键词
新质生产力;高质量发展;科技创新;产业升级;现代化
正文
引言
在当前全球化加速、科技革命不断迭代的背景下,新质生产力已成为全球经济竞争的重要领域。作为经济增长的新动能,新质生产力不仅包括技术进步和生产方式的创新,还涉及到经济结构转型、资源配置效率提升以及生态友好型发展的综合体系。如何加快发展新质生产力,已成为我国实现经济高质量发展和迈向现代化的必由之路。
一、新质生产力的内涵与意义
1. 新质生产力的内涵
新质生产力相较于传统生产力更强调以下几个方面:
- 科技驱动:以人工智能、大数据、区块链等新兴技术为核心驱动力。
- 绿色发展:注重资源节约和生态保护,实现可持续发展。
- 价值链提升:推动从低端加工制造向高端智能制造转型,提升产品和服务的附加值。
1. 发展新质生产力的意义
- 推动经济转型升级:新质生产力能够突破传统产业的瓶颈,为经济注入新活力。
- 提升国际竞争力:增强我国在全球产业链、供应链中的核心地位。
- 实现可持续发展目标:通过技术创新与绿色发展路径,促进人与自然的和谐共生。
二、当前制约新质生产力发展的主要问题
尽管我国在新质生产力发展方面取得了显著进步,但仍面临一些制约因素:
1. 科技创新能力不足
尽管我国在高铁、5G等领域处于世界领先地位,但在核心技术研发和原创性科技成果方面仍有待突破。
1. 产业结构优化不足
我国产业链存在低端过剩、高端不足的问题,传统产业转型升级相对缓慢。
1. 人才供给与培养不平衡
高端创新型人才缺乏,技术工人队伍建设有待加强,特别是在新兴领域的人才储备尚显不足。
1. 政策支持与市场环境不完善
在政策设计和市场环境上,部分地区和行业还存在创新投入不足、制度创新滞后的问题。
三、加快发展新质生产力的路径与实践
1. 强化科技创新驱动
- 加大科研投入:提升国家对基础研究和关键技术研发的资金投入比例,鼓励企业加大研发力度。
- 构建创新平台:搭建产学研一体化的创新平台,促进技术研发与产业应用的深度融合。
- 完善知识产权保护:通过法律和政策手段保障创新主体的合法权益,激发创新活力。
1. 推动产业结构优化升级
- 大力发展高端制造业:聚焦航空航天、新能源汽车、集成电路等领域,打造全球领先的产业集群。
- 培育战略性新兴产业:发展新一代信息技术、绿色环保、新材料等新兴产业,提升经济增长韧性。
- 推动传统产业智能化改造:通过数字化、智能化手段提高传统产业的效率和竞争力。
1. 加强人才培养与引进
- 完善人才培养体系:加大对工科教育和职业技术教育的支持力度,打造一支技术过硬、创新能力强的专业人才队伍。
- 优化人才引进政策:通过全球招聘和高端人才引进计划吸引优秀海外人才,为新质生产力提供智力支持。
- 营造创新创业环境:完善配套政策和服务体系,激发高校、科研机构和企业的创新潜力。
1. 优化政策支持与市场环境
- 加强政策协调性:制定与新质生产力发展相适应的政策体系,涵盖金融支持、税收优惠和产业引导等方面。
- 构建良好市场环境:破除垄断和行业壁垒,鼓励公平竞争,激发中小企业的创新活力。
- 强化区域协同发展:推动东部发达地区与中西部地区的联动发展,共同打造区域性新质生产力增长极。
四、案例分析与实践经验
1. 国内经验:深圳创新经济的启示
深圳作为我国科技创新的标杆城市,通过营造良好的政策环境和市场机制,吸引了大量高科技企业和人才集聚,成功构建了涵盖基础研究、技术开发和产业应用的创新生态体系。
1. 国际经验:德国工业4.0的借鉴
德国通过实施“工业4.0”战略,全面推动制造业数字化、智能化转型,为我国传统产业升级提供了有益的参考经验。
五、结论与展望
加快发展新质生产力是我国实现经济高质量发展和现代化建设的重要途径。在未来的实践中,我们需要持续深化科技创新、优化产业结构、完善政策支持,并加强国际合作,积极吸取全球先进经验。通过全社会的共同努力,我国必将实现从传统经济大国向创新经济强国的转型,为实现中华民族伟大复兴的中国梦提供坚实支撑。
参考文献
+14
View File
@@ -0,0 +1,14 @@
---
title: 摄影
date: 2024-10-27
tags: [生活记录]
---
### 配置1
- 4k/60帧
- auto
- 0.0-0.3EV、ISO100-1600
- 8
- -2、-1
+22
View File
@@ -0,0 +1,22 @@
---
title: 面试
date: 2025-02-20
tags: [生活记录]
---
javaWeb
[https://www.bilibili.com/video/BV1CL4y1i7qR?vd_source=7755fe49534ae933cbf5d23064211159&p=92&spm_id_from=333.788.videopod.episodes](https://www.bilibili.com/video/BV1CL4y1i7qR?vd_source=7755fe49534ae933cbf5d23064211159&p=92&spm_id_from=333.788.videopod.episodes)
spring
[https://www.bilibili.com/video/BV1Kv4y1x7is?vd_source=7755fe49534ae933cbf5d23064211159&spm_id_from=333.788.videopod.episodes&p=26](https://www.bilibili.com/video/BV1Kv4y1x7is?vd_source=7755fe49534ae933cbf5d23064211159&spm_id_from=333.788.videopod.episodes&p=26)
mybatis
[https://www.bilibili.com/video/BV1ME421w7Ms?spm_id_from=333.788.videopod.episodes&vd_source=7755fe49534ae933cbf5d23064211159&p=18](https://www.bilibili.com/video/BV1ME421w7Ms?spm_id_from=333.788.videopod.episodes&vd_source=7755fe49534ae933cbf5d23064211159&p=18)
springMVC
[https://www.bilibili.com/video/BV1Lh4y1M7kx?spm_id_from=333.788.videopod.episodes&vd_source=7755fe49534ae933cbf5d23064211159&p=17](https://www.bilibili.com/video/BV1Lh4y1M7kx?spm_id_from=333.788.videopod.episodes&vd_source=7755fe49534ae933cbf5d23064211159&p=17)
@@ -0,0 +1,647 @@
---
title: JUC笔记
date: 2024-08-25
tags: [JUC]
---
- JUCjava.util.concurrent 并发编程
![image 30.png](JUC笔记/image30.png)
## 常见线程安全类
- String
- Integer
- StringBuffer
- Random
- Vector
- Hashtable
- java.util.concurrent包下的类(juc
这里说它们是线程安全的是指,**多个线程调用它们同一个实例**的某个方法时,是线程安全的。但是自己手动组合方法是线程不安全的
> [!important] String、Integer是不可变线程,因为值是不可修改
### 效率问题
1. 单核 cpu 下,多线程不能实际提高程序运行效率,只是为了能够在不同的任务之间切换,不同线程轮流使用
cpu ,不至于一个线程总占用 cpu,别的线程没法干活
2. 多核 cpu 可以并行跑多个线程,但能否提高程序运行效率还是要分情况的
有些任务,经过精心设计,将任务拆分,并行执行,当然可以提高程序的运行效率。但不是所有计算任
务都能拆分(参考后文的【阿姆达尔定律】)
也不是所有任务都需要拆分,任务的目的如果不同,谈拆分和效率没啥意义
3. IO 操作不占用 cpu,只是我们一般拷贝文件使用的是【阻塞 IO】,这时相当于线程虽然不用 cpu,但需要一
直等待 IO 结束,没能充分利用线程。所以才有后面的【非阻塞 IO】和【异步 IO】优化
> 注意
> 需要在多核 cpu 才能提高效率,单核仍然时是轮流执行
### 原子性
- 原子操作可以保证原子性,加锁本质是保证了多线程修改操作为原子操作
- 原子操作:一个操作或者多个操作组合在一起,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。特点:不可打断、一致性
- 两个线程对一个变量进行修改,产生线程问题就是因为修改不是原子操作
例如对于 i++、i-- 而言(i 为静态变量),实际会产生如下的 JVM 字节码指令:
```Java
//i++操作
getstatic i // 获取静态变量i的值
iconst_1 // 准备常量1
iadd // 自增
putstatic i // 将修改后的值存入静态变量i
//i--操作
getstatic i // 获取静态变量i的值
iconst_1 // 准备常量1
isub // 自减
putstatic i // 将修改后的值存入静态变量i
```
多线程,指令交错执行
```Java
// 假设i的初始值为0
getstatic i // 线程2-获取静态变量i的值 线程内i=0
getstatic i // 线程1-获取静态变量i的值 线程内i=0
iconst_1 // 线程1-准备常量1
iadd // 线程1-自增 线程内i=1
putstatic i // 线程1-将修改后的值存入静态变量i 静态变量i=1
iconst_1 // 线程2-准备常量1
isub // 线程2-自减 线程内i=-1
putstatic i // 线程2-将修改后的值存入静态变量i 静态变量i=-1
```
## synchronized原理
### Monitor 概念
- Java对象头:内存结构
- 普通对象
```Java
|--------------------------------------------------------------|
| Object Header (64 bits) |
|------------------------------------|-------------------------|
| Mark Word (32 bits) | Klass Word (32 bits) |
|------------------------------------|-------------------------|
```
- 数组对象
```Java
|---------------------------------------------------------------------------------|
| Object Header (96 bits) |
|--------------------------------|-----------------------|------------------------|
| Mark Word(32bits) | Klass Word(32bits) | array length(32bits) |
|--------------------------------|-----------------------|------------------------|
```
- 其中 Mark Word 结构为
```Java
|-------------------------------------------------------|--------------------|
| Mark Word (32 bits) | State |
|-------------------------------------------------------|--------------------|
| hashcode:25 | age:4 | biased_lock:0 | 01 | Normal |
|-------------------------------------------------------|--------------------|
| thread:23 | epoch:2 | age:4 | biased_lock:1 | 01 | Biased |
|-------------------------------------------------------|--------------------|
| ptr_to_lock_record:30 | 00 | Lightweight Locked |
|-------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:30 | 10 | Heavyweight Locked |
|-------------------------------------------------------|--------------------|
| | 11 | Marked for GC |
|-------------------------------------------------------|--------------------|
```
```Java
|--------------------------------------------------------------------|--------------------|
| Mark Word (64 bits) | State |
|--------------------------------------------------------------------|--------------------|
| unused:25 | hashcode:31 | unused:1 | age:4 | biased_lock:0 | 01 | Normal |
|--------------------------------------------------------------------|--------------------|
| thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | 01 | Biased |
|--------------------------------------------------------------------|--------------------|
| ptr_to_lock_record:62 | 00 | Lightweight Locked |
|--------------------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:62 | 10 | Heavyweight Locked |
|--------------------------------------------------------------------|--------------------|
| | 11 | Marked for GC |
|--------------------------------------------------------------------|--------------------|
```
- Monitor锁,记录了持有对象,阻塞线程,等待线程,Monitor是==操作系统提供的==,由c++实现的,每个锁对象关联一个Monitor锁(**锁对象保存了Monitor的地址**
![image 1 15.png](JUC笔记/image115.png)
- Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态
- BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间片
- BLOCKED 线程会在 Owner 线程释放锁时唤醒
- WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但唤醒后并不意味者立刻获得锁,仍需进入EntryList 重新竞争
- 每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(==重量级==)之后,该**对象头**的Mark Word 中就被设置指向 Monitor 对象的指针
![image 2 8.png](JUC笔记/image28.png)
**synchronized的3种状态**
- 线程与对象绑定,是在线程中使用对象锁,而不是调用对象
### 偏向锁
- 偏向锁从 Java 6 引入,在 Java 15 被废弃,Java 16-17 默认关闭,最终在 Java 18 中被完全移除
- 17中,不使用锁就是默认状态,使用就是轻量锁
- **默认是偏向锁**(对象头后3位:101),解决锁重入问题,即只有一个线程多次获取(已经获取)锁对象的情况
- 锁升级
- 当调用**hashcode**方法时,由于偏向锁代替了hash值的位置,对象头会恢复hash值正常,所以将锁升级为轻量级锁
- 当多个线程使用锁时(以该对象为锁,错开使用,无竞争),锁期间为轻量锁
- 调用**wait/notify**时,这是重量级锁的方法
- 批量重偏向:当同一个类的**多个对象频繁地发生重偏向时**(重偏向阈值:20),JVM 将超出阈值的对象==偏向锁重置==,而不是升级为轻量锁
- 详解
- 当锁对象与其他线程绑定,此时锁变为轻量级锁(00),解除时,变为初始状态(001),当多个相同类的对象修改绑定(集合中的元素),从第20个开始,对象进行重偏向(101),将与新的线程绑定
- 101(偏向锁) ——> 001(无状态) 101(偏向锁) ——> 101(偏向锁)
> [!important] 总结:批量重偏向会以class为单位,为每个class维护一个偏向锁撤销计数器,每一次该class的对象发生偏向撤销操作时,该计数器+1,当这个值达到重偏向阈值(默认20)时,JVM就认为该class的偏向锁有问题,因此会进行批量重偏向。
- 批量撤销:当一个类的所有对象进行了40次重偏向时,那么这个类下的对象再偏向、创建都会赋默认状态(001)
### 轻量级锁
- 高版本jdk默认是轻量级锁(对象头后2位:00),当一个对象锁**没有竞争**,那么这个锁就是轻量级锁,锁被占用(发生竞争)升级为重量级锁
- 详情
- 在即将开始执行同步代码块中的内容时,会首先检查对象的Mark Word,查看锁对象是否被其他线程占用,如果没有任何线程占用,那么会在当前线程中所处的栈帧中建立一个名为锁记录(Lock Record)的空间,用于复制并存储对象目前的Mark Word信息(官方称为Displaced Mark Word)。接着,虚拟机将使用**CAS操作**将对象的Mark Word更新为轻量级锁状态(数据结构变为指向Lock Record的指针,指向的是当前的栈帧)
- 如果CAS操作失败了的话,那么说明可能这时有线程已经进入这个同步代码块了,这时虚拟机会再次检查对象的Mark Word,是否指向当前线程的栈帧,如果是,说明不是其他线程,而是当前线程已经有了这个对象的锁,直接放心大胆进同步代码块即可。如果不是,那确实是被其他线程占用了。
- 这时,轻量级锁一开始的想法就是错的(这时有对象在竞争资源,已经赌输了),所以说只能将锁膨胀为重量级锁,按照重量级锁的操作执行(注意锁的膨胀是不可逆的)
![image 3 7.png](JUC笔记/image37.png)
### 重量级锁
- 当对象锁,发生竞争时,对象锁要关联一个Monitor对象,管理锁的状态
**自旋优化**
- 重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。说白了就是,等一下
- 详情
| | | |
|---|---|---|
|线程 1 core 1 上)|对象 Mark|线程 2 core 2 上)|
|-|10(重量锁)|-|
|访问同步块,获取 monitor|10(重量锁)|-|
|成功(加锁)|10(重量锁)|-|
|执行同步块|10(重量锁)|-|
|执行同步块|10(重量锁)|访问同步块,获取 monitor|
|执行同步块|10(重量锁)|自旋重试|
|执行完毕|10(重量锁)|自旋重试|
|成功(解锁)|01(无锁)|自旋重试|
|-|10(重量锁)|成功(加锁)|
|-|10(重量锁)|执行同步块|
|-|||
## 保护性设计模式
**join原理**
![image 4 7.png](JUC笔记/image47.png)
- 当线程结束时,会调用notifyAll方法,释放资源和锁
### park&unpark
- 基本使用:它们是 `**LockSupport**` 类中的**静态方法**
```Java
// 暂停当前线程
LockSupport.park();
// 恢复某个线程的运行
LockSupport.unpark(暂停线程对象)
```
- 正常:先 `park` 再 `unpark` ,但是,先`unpark`再`park`也可以,不过`park`将暂停不了
- 特点:
- 与 `Object` 的 `wait` & `notify` 相比`wait``notify` 和 `notifyAll` 必须配合 `Object Monitor` 一起使用,而 `park``unpark` 不必
- `park` & `unpark` 是以线程为单位来【阻塞】和【唤醒】线程,而 notify 只能随机唤醒一个等待线程,notifyAll是唤醒所有等待线程,就不那么【**精确**】
- `park` & `unpark` 可以先 `unpark`,而 `wait` & `notify` 不能先 `notify`
- **原理**
- 具体是靠c/c++实现,每个线程都有自己的一个Parker对象,由三部分组成`_counter`、`_cond`和`_mutex`
- `_counter` :计数,1:表示线程继续,0:表示线程暂停
- `_cond` : 条件变量,当线程暂停时存储在这里
- `_mutex` 互斥锁
- 小故事理解
- 线程就像一个旅人,Parker 就像他随身携带的背包,条件变量就好比背包中的帐篷。`_counter`就好比背包中的备用干粮(0 为耗尽,1 为充足)
- 调用 `park`就是要看需不需要停下来歇息
- 如果备用干粮耗尽,那么钻进帐篷歇息
- 如果备用干粮充足,那么不需停留,继续前进
- 调用`unpark`,就好比令干粮充足
- 如果这时线程还在帐篷,就唤醒让他继续前进
- 如果这时线程还在运行,那么下次他调用 `park`时,仅是消耗掉备用干粮,不需停留继续前进因为背包空间有限,多次调用 `unpark`仅会补充一份备用干粮
![image 5 6.png](JUC笔记/image56.png)
park方法
- 当线程调用`**park**`方法,检查`_counter`的值
- 为0,则线程获取`_mutex`互斥锁,进入`_cond`条件变量,阻塞线程,设置`_counter=0`
- 为1,则线程无需阻塞,继续运行,设置`_counter=0`
![image 6 4.png](JUC笔记/image64.png)
unpark方法
- 当线程调用`**unpark**` 方法,将`_counter=1` ,并尝试唤醒 `_cond`条件变量中的线程
- `_cond` 中有线程,则恢复运行,设置 `_counter=0`
- `_cond` 中无线程,`_counter` 值不变
- 总结:park方法,一定会将`_cond` 值设置为0,unpark方法,无阻塞线程时,才能将`_cond` 值设置为1
## 活跃性问题
### 死锁
- 一个线程需要同时获取多把锁,这时就容易发生死锁
- 解决方式
- 按序加锁
- 超时释放锁:使用ReentrantLock
- 使用工具检测是否死锁
- 检测死锁可以使用 jconsole工具,或者使用 jps 定位进程 id,再用 jstack 定位死锁
### 活锁
- 活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束
### 哲学家就餐问题
- 有五位哲学家,围坐在圆桌旁。他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。如果筷子被身边的人拿着,自己就得等待
![image 7 4.png](JUC笔记/image74.png)
## ReentrantLock
- 可重入锁(ReentrantLock) 比synchronized更灵活,体现在可以不用包裹整个代码块中
```Java
synchronized(){
if(){
//内容
}
}
```
```Java
lock.lock();
if(){
//内容
lock.unlock();
}
```
- 可重入锁(嵌套自己🔒)
- `lock.lock()`
- **可打断**(阻塞状态)
- 使用`lock.lockInterruptibly()` ,允许线程在等待获取锁的过程(阻塞状态)中被打断,普通锁无法打断,打断后报异常
- 当线程获取`lockInterruptibly`锁后,则与`lock`方法效果一样,此时其他线程调用`thread.interrupt()`方法,无法中断线程
- **锁超时**(谦让锁)
- `lock.tryLock()` ,返回布尔值,表示是否获取到锁
- `lock.tryLock(long timeout, TimeUnit unit)` ,返回布尔值,表示是否获取到锁,可打断
- 公平锁
- 默认是不公平锁,创建实例时,设置为公平锁(true)
- 一般不设置,影响性能
- **条件变量**(线程通信)
- 一个ReentrantLock实例可以创建**多个条件变量**,条件变量相等于`WaitSet`区域,使线程等待更加细分,不用多个线程在一个区域
- 使用:要先获得锁,在对应条件变量中等待
```Java
//创建条件变量(休息室)
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
//线程首先获取锁
lock.lock();
//进入等待状态,也可以设置时间
condition1.await();
//叫醒等待线程
condition1.signal();
condition1.signalAll();
```
- wait/notify→await/signal
## 内存
### java内存模型
- 原理详解
因为 java是对硬件虚拟化,所有**对 ,以及编译器、CPU指令重排机制这些提示效率的方式进行了实现**,所以定义了一套规范,抽象了线程和主内存之间的关系,规定了从 Java 源代码到 CPU 可执行指令,的这个转化过程要遵守哪些和并发相关的原则和规范,主要目的是为了简化多线程编程,增强程序可移植性的
- JMM(java Memory Model)JMM 是一种规范,定义了 Java 程序中各个变量(包括实例字段、静态字段和数组元素)的访问方式。它通过抽象的主内存(堆)和工作内存(栈)模型,确保线程间的内存**可见性**和**有序性**
- 了解java内存规范,有利于开发者编写**线程安全的程序**
- JMM 体现在以下几个方面
- 原子性 - 保证指令不会受到**线程上下文切**换的影响
- 可见性 - 保证指令不会受 **cpu 缓存**的影响
- 有序性 - 保证指令不会受 **cpu 指令并行优化**的影响
### 可见性
- 现象:当一个线程对公共变量进行修改,另一个线程中数据无法同步(线程不安全)
```Java
static boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(run){
// ....
}
});
t.start();
sleep(1);
run = false; // 线程t不会如预想的停下来
}
```
- 循环中有 `System.out.println()`输出语句也可以退出线程,因为输出语句中用到了锁
- 产生原因:t线程频繁读取run的值,**JIT编译器将run的值写入t的工作内存中的缓冲区(**模仿CPU高速缓冲区),其他线程修改的是主内存的值,所以导致数据不一致
![image 8 4.png](JUC笔记/image84.png)
- 解决方式:**[[JUC]]、s**ynchronized
- 使用场景:仅用在**一个写线程,多个读线程**的情况
> [!important] synchronized 语句块既可以**保证代码块的原子性**,也同时**保证代码块内变量的可见性**。但缺点是synchronized 是属于重量级操作,性能相对更低
>
> - 使用synchronized锁,JVM 会插入内存屏障指令。这些内存屏障会**刷新本地线程的缓存**,确保线程可以**看到共享变量的最新值**
### 有序性
- Java 源代码会经历 **编译器优化重排 —> 指令并行重排 —> 内存系统重排** 的过程,最终才变成操作系统可执行的指令序列。**指令重排序可以保证串行语义一致,但是没有义务保证多线程间的语义也一致** ,所以在多线程下,指令重排序可能会导致一些问题。
- 现象:**双重检查实现对象单例**无法保证单例
```Java
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
```
无法保证单例的原因是这条语句`**uniqueInstance = new Singleton();**` ,也就是new对象的过程,
- new对象对应的指令:1、创建对象 2、调用构造方法(初始化)3、赋值给静态变量
```Java
17: new #3 // class cn/itcast/n5/Singleton
20: dup
21: invokespecial #4 // Method "<init>":()V
24: putstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton;
//堆内创建对象,返回地址存入操作数栈
//复制一份地址,存入操作数栈
//调用构造方法,地址出栈
//将地址赋给静态变量,地址出栈
```
- 由于指令重排,导致先3,后2,当其他线程进入方法时,`if (uniqueInstance == null)` 不满足,因为此时静态变量被赋值为未初始化对象,所以**其他线程可能会获得未初始化的对象**,使用该对象就可能触发空指针异常
- 所以,当在多线程环境下多数据的修改会出现难以预料的情况!
- 解决方式:**[[JUC]]、s**ynchronized
### **volatile**
- 是一个**修饰符**,修饰成员变量(多线程环境),它可以避免线程访问时,访问工作内存(缓存)中的值,==**直接访问主内存中的值**==**,**保证了==**可见性**==,它还可以==**禁止指令重排**==,保证了==**有序性**==
- volatile 的底层实现原理是**内存屏障**Memory BarrierMemory Fence
- 对 volatile 变量的(赋值)写指令后会加入写屏障
- 写屏障(sfence)保证在==**写屏障之前的指令不进行重排**==**,并且**==**对共享变量的改动,都同步到主存中**==
- 对 volatile 变量的(读取)读指令前会加入读屏障
- 读屏障(lfence)保证在==**读屏障之后的指令不进行重排**==**,并且**==**对共享变量的读取,加载的主存中最新数据**==
> [!important] 重要的是要注意,synchronized 并不阻止块内部的指令重排,volatile 提供了更严格的重排序规则,
>
> **它不允许 volatile 变量的读写与其他内存操作重排**,synchronized 的重排序规则相对没那么严格
### Happens-before
- 是 Java 内存模型(JMM)中的一个核心**概念**,用于定义操作之间的内存可见性保证。它是一种偏序关系,用来**描述程序中不同操作的执行顺序和可见性**
- 定义:如果一个操作 A happens-before 另一个操作 B,那么 ==**A 的结果对 B 是可见的**==,且 A 的执行顺序排在 B 之前
- 主要作用:
- 确保内存可见性:保证一个线程的写操作对其他线程是可见的。
- 防止重排序:限制编译器和处理器对指令进行重排序的能力
- 常见情况
a. 程序顺序规则:
在同一个线程中,按照程序的顺序,前面的操作 happens-before 后面的操作。
b. 监视器锁规则:
一个锁的解锁 happens-before 于后续对这个锁的加锁。
c. volatile 变量规则:
对一个 volatile 变量的写操作 happens-before 于后续对这个变量的读操作。
d. 线程启动规则:
Thread 对象的 start() 方法 happens-before 于这个线程的每一个动作。
e. 线程终止规则:
线程中的所有操作都 happens-before 于其他线程检测到这个线程已经终止。
f. 中断规则:
一个线程调用另一个线程的 interrupt() 方法 happens-before 于被中断线程检测到中断事件的发生。
g. 终结器规则:
一个对象的初始化完成 happens-before 于它的 finalize() 方法的开始。
h. 传递性:
如果 A happens-before B,且 B happens-before C,那么 A happens-before C。
## 无锁
### CAS
- 使用`CAS`机制解决并发修改问题
```Java
//修改共享数据
AtomicInteger balance;//封装的共享数据
while(true) {
// 比如拿到了旧值 1000
int prev = balance.get();
// 在这个基础上 1000-10 = 990
int next = prev - amount;
/*
compareAndSet 正是做这个检查,在 set 前,先比较 prev 与当前值- 不一致了,next 作废,返回 false 表示失败
比如,别的线程已经做了减法,当前值已经被减成了 990
那么本线程的这次 990 就作废了,进入 while 下次循环重试- 一致,以 next 设置为新值,返回 true 表示成功
*/
if (balance.compareAndSet(prev, next)) break;
}
```
- Compare And Swap,它是一种用于实现多线程环境下的**无锁并发**编程技术,是基于 的思想,不怕共享变量被修改,保障每一次修改是成功的(原子修改),无锁并发、无阻塞并发,
- 底层 ,封装数据对应的变量,由volatitle修饰
- CAS 的底层是`lock cmpxchg` 指令(X86 架构),在单核 CPU 和多核 CPU 下都能够保证【比较-交换】的原子性
- 效率问题:因为线程需要一直运行(只有就绪、运行状态),所以线程数小于cpu核心数时才能提升效率
- 使用
**原子整数**
- 对基本数据类型进行了封装,比如AtomicBoolean、AtomicInteger、AtomicLong使用类似,以AtomicInteger为例
- 构造方法:
- 常用方法:
| | |
|---|---|
|方法名|描述|
||获取值|
||修改数据,返回是否修改成功,可能修改失败|
||返回自增后的值/返回值再自增|
|||
||返回增加data后的值/返回值再增加data|
|||
||修改数据|
**原子引用**
- 对引用对象进行封装,如AtomicReference、AtomicMarkableReference、AtomicStampedReference,每次修改对象时,**更新的实际上是对象的引用地址**
上同
AtomicReference的·使用·
- ABA问题:线程无法感知数据被修改(指修改完,值不变),可以使用版本号记录
AtomicStampedReference基于**版本号**的方法,每次修改需要修改版本号,告知共享数据是否被修改
AtomicMarkableReference维护一个**布尔值**表示状态,只关心数据是否被修改而不看次数
**原子数组**
- 因为原子引用每次修改的都是地址,而数组的修改是对内容的修改,所以推出原子数组AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
字段更新器
原子累加器
### 🚀 什么是 CAS
**Compare And Swap** 是一种原子操作,用于在多线程环境中安全地更新变量,避免使用互斥锁(`synchronized` 或 `mutex`)造成的性能瓶颈。
它的基本思路就是:
> “我想把一个值改成新值,但前提是它现在还是旧值。”
---
### 🔧 CAS 的原理
假设你有一个变量 `V`,你想把它从旧值 `A` 改成新值 `B`CAS 会做这样的操作:
```Plain
if (V == A) {
V = B;
return true; // 修改成功
} else {
return false; // 修改失败,有别的线程动过它
}
```
这个操作是 **原子的**(不会被中断),通常由硬件指令(如 x86 的 `CMPXCHG`)直接支持。
---
### 📦 应用场景
- Java 中的 `AtomicInteger`, `AtomicReference` 等类底层就是用 CAS 实现的。
- 实现无锁数据结构(如无锁队列、无锁栈)
- 避免使用传统的锁,提高并发性能
---
### ⚠️ CAS 的问题
1. **ABA 问题**
- 如果变量从 A 改成 B,然后又改回 A,CAS 认为它没变,其实发生了两次变化。
- 解决方法:使用 **版本号** 或 **带时间戳的引用**,比如 Java 的 `AtomicStampedReference`。
2. **自旋问题**
- 如果一直 CAS 失败(比如多个线程争抢同一个变量),会反复尝试,造成高 CPU 占用。
3. **只能操作一个变量**
- 无法同时原子地更新多个变量,除非封装成一个整体结构。
---
## 悲观锁、乐观锁
### 悲观锁
- 悲观锁总是假设最坏的情况,认为共享资源每次被访问的时候就会出现问题(比如共享数据被修改),所以每次在获取资源操作的时候都会上锁,这样其他线程想拿到这个资源就会阻塞直到锁被上一个持有者释放。也就是说,**共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程**。
- 像 Java 中`synchronized`和`ReentrantLock`等独占锁就是悲观锁思想的实现。
### 乐观锁
- 乐观锁总是假设最好的情况,认为共享资源每次被访问的时候不会出现问题,线程可以不停地执行,无需加锁也无需等待,只是在提交修改的时候去验证对应的资源(也就是数据)是否被其它线程修改了(具体方法可以使用版本号机制或 CAS 算法)
- 在 Java 中`java.util.concurrent.atomic`包下面的原子变量类(比如`AtomicInteger`、`LongAdder`)就是使用了乐观锁的一种实现方式 **CAS** 实现的。
Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

@@ -0,0 +1,708 @@
---
title: JVM中篇
date: 2024-08-25
tags: [JVM]
---
## JVM内存管理
在传统的C/C++开发中,我们经常通过使用申请内存的方式来创建对象或是存放某些数据,但是这样也带来了一些额外的问题,我们要在何时释放这些内存,怎么才能使得内存的使用最高效,因此,内存管理是一个非常严肃的问题。
比如我们就可以通过C语言动态申请内存,并用于存放数据:
```C
\#include <stdlib.h>#include <stdio.h>int main(){
//动态申请4个int大小的内存空间
int* memory = malloc(sizeof(int) * 4);
//修改第一个int空间的值
memory[0] = 10;
//修改第二个int空间的值
memory[1] = 2;
//遍历内存区域中所有的值
for (int i = 0;i < 4;i++){
printf("%d ", memory[i]);
}
//释放指针所指向的内存区域
free(memory);
//最后将指针赋值为NULL
memory = NULL;
}
```
而在Java中,这种操作实际上是不允许的,Java只支持直接使用基本数据类型和对象类型,至于内存到底如何分配,并不是由我们来处理,而是JVM帮助我们进行控制,这样就帮助我们节省很多内存上的工作,虽然带来了很大的便利,但是,一旦出现内存问题,我们就无法像C/C++那样对所管理的内存进行合理地处理,因为所有的内存操作都是由JVM在进行,**只有了解了JVM的内存管理机制,我们才能够在出现内存相关问题时找到解决方案。**
## 内存管理划分
既然要管理内存,那么肯定不会是杂乱无章的,JVM对内存的管理采用的是分区治理,不同的内存区域有着各自的职责所在,在虚拟机运行时,内存区域如下划分:![点击查看图片来源](JVM中篇/CP4yv1iqrfjmXzW.jpg)
我们可以看到,内存区域一共分为5个区域,其中方法区和堆是所有线程共享的区域,随着虚拟机的创建而创建,虚拟机的结束而销毁,而虚拟机栈、本地方法栈、程序计数器都是线程之间相互隔离的,每个线程都有一个自己的区域,并且线程启动时会自动创建,结束之后会自动销毁。内存划分完成之后,我们的JVM执行引擎和本地库接口,也就是Java程序开始运行之后就会根据分区合理地使用对应区域的内存了。
> 大致划分 详情🔗:[https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.5](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.5)
### 程序计数器
- **目的**:首先我们来介绍一下程序计数器,它和我们的传统8086 CPU中PC寄存器的工作差不多,因为JVM虚拟机目的就是==**实现物理机那样的程序执行**==。在8086 CPU中,PC作为程序计数器,负责储存内存地址,该地址指向下一条即将执行的指令,每解释执行完一条指令,PC寄存器的值就会自动被更新为下一条指令的地址,进入下一个指令周期时,就会根据当前地址所指向的指令,进行执行。
- **作用**:而JVM中的程序计数器可以看做是==**当前线程所执行字节码的行号指示器(记录行号)**==,而行号正好就指的是某一条指令,字节码解释器在工作时也会改变这个值,来指定下一条即将执行的指令,程序计数器因为只需要记录很少的信息,所以只占用很少一部分内存。
- 因为Java的多线程也是依靠时间片轮转算法进行的,因此一个CPU同一时间也只会处理一个线程,当某个线程的时间片消耗完成后,会自动切换到下一个线程继续执行,而==**当前线程的执行位置会被保存到当前线程的程序计数器**==中,当下次轮转到此线程时,又继续根据之前的执行位置继续向下执行。
### 虚拟机栈
- 虚拟机栈就是一个非常关键的部分,看名字就知道它是一个**栈结构**,每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(其实就是栈里面的一个元素:**方法栈帧**),栈帧中包括了当前方法的一些信息,比如局部变量表、操作数栈、动态链接、方法出口等。
![image-20230306164822720](JVM中篇/As1NGy6BwhKJ9kY.webp)
- 其中局部变量表就是我们方法中的局部变量,之前我们也进行过演示,实际上局部变量表在class文件中就已经定义好了,操作数栈就是我们之前字节码执行时使用到的栈结构; 每个栈帧还保存了一个**可以指向当前方法所在类**的运行时常量池,目的是:当前方法中如果需要调用其他方法的时候,能够从运行时常量池中找到对应的符号引用,然后将符号引用转换为直接引用,然后就能直接调用对应方法,这就是动态链接(我们还没讲到常量池,暂时记住即可,建议之后再回顾一下),最后是方法出口,也就是方法该如何结束,是抛出异常还是正常返回。
- 这里我们来模拟一下整个虚拟机栈的运作流程,我们先编写一个测试类:
```Java
public class Main {
public static void main(String[] args) {
int res = a();
System.out.println(res);
}
public static int a(){
return b();
}
public static int b(){
return c();
}
public static int c(){
int a = 10;
int b = 20;
return a + b;
}
}
```
当我们的主方法执行后,会依次执行三个方法`a() -> b() -> c() -> 返回`,我们首先来观察一下反编译之后的结果:
```Plain
{
public com.test.Main(); \#这个是构造方法
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/test/Main;
public static void main(java.lang.String[]); \#主方法
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: invokestatic #2 // Method a:()I
3: istore_1
4: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
7: iload_1
8: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
11: return
LineNumberTable:
line 5: 0
line 6: 4
line 7: 11
LocalVariableTable:
Start Length Slot Name Signature
0 12 0 args [Ljava/lang/String;
4 8 1 res I
public static int a();
descriptor: ()I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: invokestatic #5 // Method b:()I
3: ireturn
LineNumberTable:
line 10: 0
public static int b();
descriptor: ()I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: invokestatic #6 // Method c:()I
3: ireturn
LineNumberTable:
line 14: 0
public static int c();
descriptor: ()I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=0
0: bipush 10
2: istore_0
3: bipush 20
5: istore_1
6: iload_0
7: iload_1
8: iadd
9: ireturn
LineNumberTable:
line 18: 0
line 19: 3
line 20: 6
LocalVariableTable:
Start Length Slot Name Signature
3 7 0 a I
6 4 1 b I
}
```
可以看到在编译之后,我们整个方法的最大操作数栈深度、局部变量表都是已经确定好的,当我们程序开始执行时,会根据这些信息封装为对应的栈帧,我们从`main`方法开始看起:
![image-20230306164838564](JVM中篇/rRtxbFZXDkmGci4.webp)
接着我们继续往下,到了 `0: invokestatic #2 // Method a:()I`时,需要调用方法`a()`,这时当前方法就不会继续向下运行了,而是去执行方法`a()`,那么同样的,将此方法也入栈,注意是放入到栈顶位置,`main`方法的栈帧会被压下去:
![image-20230306164848411](JVM中篇/SZJWH8l5xByTjr7.webp)
这时,进入方法a之后,又继而进入到方法b,最后在进入c,因此,到达方法c的时候,我们的虚拟机栈变成了:
![image-20230306164859169](https://oss.itbaima.cn/internal/markdown/2023/03/06/FdS8U4lHVjCLKwD.png)
现在我们依次执行方法c中的指令,最后返回a+b的结果,在方法c返回之后,也就代表方法c已经执行结束了,栈帧4会自动出栈,这时栈帧3就得到了上一栈帧返回的结果,并继续执行,但是由于紧接着马上就返回,所以继续重复栈帧4的操作,此时栈帧3也出栈并继续将结果交给下一个栈帧2,最后栈帧2再将结果返回给栈帧1,然后栈帧1就可以继续向下运行了,最后输出结果。
![image-20230306164908882](JVM中篇/FBpxKWIbuY7ftq4.webp)
### 本地方法栈
- 本地方法栈与虚拟机栈作用差不多,这里不多做介绍。
### 堆
- 堆是整个Java应用程序共享的区域,也是整个虚拟机最大的一块内存空间,而此区域的职责就是**存放和管理对象和数组**,而我们马上要提到的垃圾回收机制也是主要作用于这一部分内存区域。
### 方法区
- 方法区也是整个Java应用程序共享的区域,它用于存储所有的类信息、常量、静态变量、动态编译缓存等数据,可以大致分为两个部分,一个是**类信息表**,一个是**运行时常量池**。方法区也是我们要重点介绍的部分。
![image 53.png](JVM中篇/image53.png)
- 首先类信息表中存放的是当前应用程序加载的所有类信息,包括类的版本、字段、方法、接口等信息,同时会将编译时生成的常量池数据全部存放到运行时常量池中。当然,常量也并不是只能从类信息中获取,==在程序运行时,也有可能会有新的常量进入到常量池==。
- 其实我们的String类正是利用了常量池进行优化,这里我们编写一个测试用例:
```Java
public static void main(String[] args) {
String str1 = new String("abc");
String str2 = new String("abc");
System.out.println(str1 == str2);
System.out.println(str1.equals(str2));
}
```
得到的结果也是显而易见的,由于`str1`和`str2`是单独创建的两个对象,那么这两个对象实际上会在堆中存放,保存在不同的地址:
![image-20230306164934743](JVM中篇/tnqFSxzEcB4U9WL.webp)
所以当我们使用`==`判断时,得到的结果`false`,而使用`equals`时因为比较的是值,所以得到`true`。现在我们来稍微修改一下:
```Java
public static void main(String[] args) {
String str1 = "abc";
String str2 = "abc";
System.out.println(str1 == str2);
System.out.println(str1.equals(str2));
}
```
现在我们没有使用new的形式,而是直接使用双引号创建,那么这时得到的结果就变成了两个`true`,这是为什么呢?这其实是因为我们直接使用双引号赋值,会先在常量池中查找是否存在相同的字符串,若存在,则将引用直接指向该字符串;若不存在,则在常量池中生成一个字符串,再将引用指向该字符串:
![image-20230306164942208](JVM中篇/GTgbpIYdCyK3RLj.webp)
实际上两次调用String类的`intern()`方法,和上面的效果差不多,也是第一次调用会将堆中字符串复制并放入常量池中,第二次通过此方法获取字符串时,会查看常量池中是否包含,如果包含那么会直接返回常量池中字符串的地址:
```Java
public static void main(String[] args) {
//不能直接写"abc",双引号的形式,写了就直接在常量池里面吧abc创好了
String str1 = new String("ab")+new String("c");
String str2 = new String("ab")+new String("c");
System.out.println(str1.intern() == str2.intern());
System.out.println(str1.equals(str2));
}
```
![image-20230306164954716](JVM中篇/EOC5ilkr396BHNy.webp)
所以上述结果中得到的依然是两个`true`。在JDK1.7之后,稍微有一些区别,在调用`intern()`方法时,当常量池中没有对应的字符串时,不会再进行复制操作,而是将其直接修改为指向当前字符串堆中的的引用:(也就是说,1.7之前是在字符串常量池中,新建一个String对象保存,1.7之后不创建对象,而是使用引用类型,那么该字符串将一直保存在堆中)
![image-20230306165005169](JVM中篇/2fXENphit4OZvVk.webp)
![image 1 31.png](JVM中篇/image131.png)
```Java
public static void main(String[] args) {
//不能直接写"abc",双引号的形式,写了就直接在常量池里面吧abc创好了
String str1 = new String("ab")+new String("c");
System.out.println(str1.intern() == str1);
}
```
```Java
public static void main(String[] args) {
String str1 = new String("ab")+new String("c");
String str2 = new String("ab")+new String("c");
System.out.println(str1 == str1.intern());
System.out.println(str2.intern() == str1);
}
```
所以最后我们会发现,`str1.intern()`和`str1`都是同一个对象,结果为`true`。
- 值得注意的是,在JDK7之后,**字符串常量池从方法区移动到了堆中,**也很好理解,毕竟实例已经在堆内存中
### 总结
最后我们再来进行一个总结,各个内存区域的用途:
- (线程独有)程序计数器:保存当前程序的执行位置。
- (线程独有)虚拟机栈:通过栈帧来**维持方法调用顺序**,帮助控制程序有序运行。
- (线程独有)本地方法栈:同上,作用与本地方法。
- 堆:所有的对象和数组都在这里保存。
- 方法区:类信息、即时编译器的代码缓存、运行时常量池。
当然,这些内存区域划分仅仅是概念上的,具体的实现过程我们后面还会提到。
## 内存泄漏和栈溢出
- 内存泄漏简单理解,就是==**堆**==**内存不足**,是指当创建过多对象,没有及时进行垃圾回收清空,即存在多个”**泄漏对象**“导致内存不足(`OutOfMemoryError`),称为内存泄漏,数组容量过大也会引起该问题
- 解决方式:可以通过设置堆内存进行调整:`-Xms最小值 -Xmx最大值`
- 例子
```Java
public static void main(String[] args) {
int[] a = new int[Integer.MAX_VALUE];
}
```
这里我们申请了一个容量为21亿多的int型数组,显然,如此之大的数组不可能放在我们的堆内存中,所以程序运行时就会这样:
```Java
Exception in thread "main" java.lang.OutOfMemoryError: Requested array size exceeds VM limit
at com.test.Main.main(Main.java:5)
```
这里得到了一个`OutOfMemoryError`错误,也就是我们常说的内存溢出错误。我们可以通过参数来控制堆内存的最大值和最小值:
```Plain
-Xms最小值 -Xmx最大值
```
比如我们现在限制堆内存为固定值1M大小,并且在抛出内存溢出异常时保存当前内存堆转储快照:
![image-20230306165041598](JVM中篇/r5IsmTk3DZfXA26.webp)
注意堆内存不要设置太小,不然连虚拟机都不足以启动,
接着我们编写一个一定会导致内存溢出的程序:
```Java
public class Main {
public static void main(String[] args) {
List<Test> list = new ArrayList<>();
while (true){
list.add(new Test()); //无限创建Test对象并丢进List中
}
}
static class Test{ }
}
```
在程序运行之后:
```Plain
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid35172.hprof ...
Heap dump file created [12895344 bytes in 0.028 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:267)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:241)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:233)
at java.util.ArrayList.add(ArrayList.java:464)
at com.test.Main.main(Main.java:10)
```
可以看到错误出现原因正是`Java heap space`,也就是堆内存满了,并且根据我们设定的VM参数,堆内存保存了快照信息。我们可以在IDEA内置的Profiler中进行查看:
![image-20230306165105858](JVM中篇/C71bdyIY2JVpLKz.webp)
可以很明显地看到,在创建了360146个Test对象之后,堆内存蚌埠住了,于是就抛出了内存溢出错
- **栈溢出**是指栈中的栈帧满了,一般是递归过多或无限递归引发的
- 解决方式:修改代码逻辑,设置栈内存`-Xss`来设定栈容量
- 例子
```Java
public class Main {
public static void main(String[] args) {
test();
}
public static void test(){
test();
}
}
```
这很明显是一个永无休止的程序,并且会不断继续向下调用test方法本身,那么按照我们之前的逻辑推导,无限地插入栈帧那么一定会将虚拟机栈塞满,所以,当栈的深度已经不足以继续插入栈帧时,就会这样:
```Plain
Exception in thread "main" java.lang.StackOverflowError
at com.test.Main.test(Main.java:12)
at com.test.Main.test(Main.java:12)
at com.test.Main.test(Main.java:12)
at com.test.Main.test(Main.java:12)
at com.test.Main.test(Main.java:12)
at com.test.Main.test(Main.java:12)
....以下省略很多行
```
这也是我们常说的栈溢出,它和堆溢出比较类似,也是由于容纳不下才导致的,我们可以使用`-Xss`来设定栈容量。
## 申请堆外内存
- 除了堆内存可以存放对象数据以外,我们也可以申请堆外内存(直接内存),也就是不受JVM管控的内存区域,这部分区域的内存需要我们自行去申请和释放。
- 实际上本质就是JVM通过C/C++调用`malloc`函数申请的内存,当然得我们自己去释放了。不过虽然是直接内存,不会受到堆内存容量限制,但是依然会受到本机最大内存的限制,所以还是有可能抛出`OutOfMemoryError`异常。
- 这里我们需要提到一个堆外内存操作类:`Unsafe`,就像它的名字一样,虽然Java提供堆外内存的操作类,但是实际上它是不安全的,只有你完全了解底层原理并且能够合理控制堆外内存,才能安全地使用堆外内存(这个类不能直接使用,通过反射获得)
- 例子
注意这个类不让我们new,也没有直接获取方式(压根就没想让我们用):
```Java
public final class Unsafe {
private static native void registerNatives();
static {
registerNatives();
sun.reflect.Reflection.registerMethodsToFilter(Unsafe.class, "getUnsafe");
}
private Unsafe() {}
private static final Unsafe theUnsafe = new Unsafe();
@CallerSensitive
public static Unsafe getUnsafe() {
Class<?> caller = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
throw new SecurityException("Unsafe"); //不是JDK的类,不让用。
return theUnsafe;
}
```
所以我们这里就通过反射给他giao出来:
```Java
public static void main(String[] args) throws IllegalAccessException {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
}
```
成功拿到Unsafe类之后,我们就可以开始申请堆外内存了,比如我们现在想要申请一个int大小的内存空间,并在此空间中存放一个int类型的数据:
```Java
public static void main(String[] args) throws IllegalAccessException {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
//申请4字节大小的内存空间,并得到对应位置的地址
long address = unsafe.allocateMemory(4);
//在对应的地址上设定int的值
unsafe.putInt(address, 6666666);
//获取对应地址上的Int型数值
System.out.println(unsafe.getInt(address));
//释放申请到的内容
unsafe.freeMemory(address);
//由于内存已经释放,这时数据就没了
System.out.println(unsafe.getInt(address));
}
```
我们可以来看一下`allocateMemory`底层是如何调用的,这是一个native方法,我们来看C++源码:
```C++
UNSAFE_ENTRY(jlong, Unsafe_AllocateMemory0(JNIEnv *env, jobject unsafe, jlong size)) {
size_t sz = (size_t)size;
sz = align_up(sz, HeapWordSize);
void* x = os::malloc(sz, mtOther); //这里调用了os::malloc方法
return addr_to_java(x);
} UNSAFE_END
```
接着来看:
```C++
void* os::malloc(size_t size, MEMFLAGS flags) {
return os::malloc(size, flags, CALLER_PC);
}
void* os::malloc(size_t size, MEMFLAGS memflags, const NativeCallStack& stack) {
...
u_char* ptr;
ptr = (u_char*)::malloc(alloc_size); //调用C++标准库函数 malloc(size)
....
// we do not track guard memory
return MemTracker::record_malloc((address)ptr, size, memflags, stack, level);
}
```
所以,我们上面的Java代码转换为C代码,差不多就是这个意思:
```C
\#include <stdlib.h>#include <stdio.h>int main(){
int * a = malloc(sizeof(int));
*a = 6666666;
printf("%d\n", *a);
free(a);
printf("%d\n", *a);
}
```
- 所以说,直接内存实际上就是JVM申请的一块额外的内存空间,但是它并不在受管控的几种内存空间中,当然这些**内存依然属于是JVM的**,由于JVM提供的堆内存会进行垃圾回收等工作,效率不如直接申请和操作内存来得快,一些比较追求极致性能的框架会用到堆外内存来提升运行速度,如nio框架
---
## 垃圾回收机制
[[垃圾回收机制]]
## 元空间
- JDK8之前,Hotspot虚拟机的方法区实际上是永久代实现的。在JDK8之后,Hotspot虚拟机不再使用永久代,而是采用了全新的元空间。类的元信息被存储在元空间中。元空间没有使用堆内存,而是与堆不相连的本地内存区域。所以,理论上系统可以使用的内存有多大,元空间就有多大,所以不会出现永久代存在时的内存溢出问题。这项改造也是有必要的,永久代的调优是很困难的,虽然可以设置永久代的大小,但是很难确定一个合适的大小,因为其中的影响因素很多,比如类数量的多少、常量数量的多少等。
![image-20230306165703340](JVM中篇/2RD3AnPvbh1lQ5N.webp)
因此在JDK8时直接将本地内存作为元空间(**Metaspace**)的区域,物理内存有多大,元空间内存就可以有多大,这样永久代的空间分配问题就讲解了,所以最终它变成了这样:
![image-20230306165714662](JVM中篇/iVcYMU9jdn2NC6Z.webp)
到此,我们对于JVM内存区域的讲解就基本完成了。
## 引用类型
### 强引用
- 我们知道,在Java中,如果变量是一个对象类型的,那么它实际上存放的是对象的引用,但是如果是一个基本类型,那么存放的就是基本类型的值。实际上我们平时代码中类似于`Object o = new Object()`这样的的引用类型,细分之后可以称为`**强引用**`。
- 我们通过前面的学习可以明确,如果方法中存在这样的`强引用`类型,现在需要回收强引用所指向的对象,那么要么此==方法运行结束,要么引用连接断开==,否则被引用的对象是无法被判定为可回收的,因为我们说不定后面还要使用它。
- 所以,当JVM内存空间不足时,JVM宁愿抛出OutOfMemoryError使程序异常终止,也不会靠随意回收具有强引用的“存活”对象来解决内存不足的问题。
除了强引用之外,Java也为我们提供了三种额外的引用类型。
### 软引用
- 软引用不像强引用那样不可回收,**当 JVM 认为内存不足时**,会去试图回收软引用指向的对象,即JVM 会确保在抛出 `OutOfMemoryError` 之前,清理软引用指向的对象。当然,如果内存充足,那么是不会轻易被回收的。
- 我们可以通过以下方式来创建一个软引用:使用`**SoftReference**`类
```Java
public class Main {
public static void main(String[] args) {
//强引用写法:Object obj = new Object();
//软引用写法:
SoftReference<Object> reference = new SoftReference<>(new Object());
//使用get方法就可以获取到软引用所指向的对象了
System.out.println(reference.get());
}
}
```
可以看到软引用还存在一个带队列的构造方法,软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
这里进行一个测试,首先我们需要设定一下参数,来限制最大堆内存为10M,并且打印GC日志:
```Plain
-XX:+PrintGCDetails -Xms10M -Xmx10M
```
接着运行以下代码:
```Java
public class Main {
public static void main(String[] args) {
ReferenceQueue<Object> queue = new ReferenceQueue<>();
SoftReference<Object> reference = new SoftReference<>(new Object(), queue);
System.out.println(reference);
try{
List<String> list = new ArrayList<>();
while (true) list.add(new String("lbwnb"));
}catch (Throwable t){
System.out.println("发生了内存溢出!"+t.getMessage());
System.out.println("软引用对象:"+reference.get());
System.out.println(queue.poll());
}
}
}
```
运行结果如下:
```Plain
java.lang.ref.SoftReference@232204a1
[GC (Allocation Failure) [PSYoungGen: 3943K->501K(4608K)] 3943K->2362K(15872K), 0.0050615 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 3714K->496K(4608K)] 5574K->4829K(15872K), 0.0049642 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 3318K->512K(4608K)] 7652K->7711K(15872K), 0.0059440 secs] [Times: user=0.03 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) --[PSYoungGen: 4608K->4608K(4608K)] 11807K->15870K(15872K), 0.0078912 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]
[Full GC (Ergonomics) [PSYoungGen: 4608K->0K(4608K)] [ParOldGen: 11262K->10104K(11264K)] 15870K->10104K(15872K), [Metaspace: 3207K->3207K(1056768K)], 0.0587856 secs] [Times: user=0.24 sys=0.00, real=0.06 secs]
[Full GC (Ergonomics) [PSYoungGen: 4096K->1535K(4608K)] [ParOldGen: 10104K->11242K(11264K)] 14200K->12777K(15872K), [Metaspace: 3207K->3207K(1056768K)], 0.0608198 secs] [Times: user=0.25 sys=0.01, real=0.06 secs]
[Full GC (Ergonomics) [PSYoungGen: 3965K->3896K(4608K)] [ParOldGen: 11242K->11242K(11264K)] 15207K->15138K(15872K), [Metaspace: 3207K->3207K(1056768K)], 0.0972088 secs] [Times: user=0.58 sys=0.00, real=0.10 secs]
[Full GC (Allocation Failure) [PSYoungGen: 3896K->3896K(4608K)] [ParOldGen: 11242K->11225K(11264K)] 15138K->15121K(15872K), [Metaspace: 3207K->3207K(1056768K)], 0.1028222 secs] [Times: user=0.63 sys=0.01, real=0.10 secs]
发生了内存溢出!Java heap space
软引用对象:null
java.lang.ref.SoftReference@232204a1
Heap
PSYoungGen total 4608K, used 4048K [0x00000007bfb00000, 0x00000007c0000000, 0x00000007c0000000)
eden space 4096K, 98% used [0x00000007bfb00000,0x00000007bfef40a8,0x00000007bff00000)
from space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
to space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
ParOldGen total 11264K, used 11225K [0x00000007bf000000, 0x00000007bfb00000, 0x00000007bfb00000)
object space 11264K, 99% used [0x00000007bf000000,0x00000007bfaf64a8,0x00000007bfb00000)
Metaspace used 3216K, capacity 4500K, committed 4864K, reserved 1056768K
class space used 352K, capacity 388K, committed 512K, reserved 1048576K
```
可以看到,当内存不足时,软引用所指向的对象被回收了,所以`get()`方法得到的结果为null,并且软引用对象本身被丢进了队列中。
### 弱引用
- 弱引用比软引用的生命周期还要短,**在进行垃圾回收时**,不管当前内存空间是否充足,都会回收它的内存。
- 我们可以像这样创建一个弱引用:使用`**WeakReference**`类
```Java
public class Main {
public static void main(String[] args) {
WeakReference<Object> reference = new WeakReference<>(new Object());
System.out.println(reference.get());
}
}
```
使用方法和软引用是差不多的,但是如果我们在这之前手动进行一次GC:
```Java
public class Main {
public static void main(String[] args) {
SoftReference<Object> softReference = new SoftReference<>(new Object());
WeakReference<Object> weakReference = new WeakReference<>(new Object());
//手动GC
System.gc();
System.out.println("软引用对象:"+softReference.get());
System.out.println("弱引用对象:"+weakReference.get());
}
}
```
可以看到,弱引用对象直接就被回收了,而软引用对象没有被回收。同样的,它也支持ReferenceQueue,和软引用用法一致,这里就不多做介绍了。
`WeakHashMap`正是一种类似于弱引用的HashMap类,如果Map中的Key没有其他引用那么此Map会自动丢弃此键值对。
```Java
public class Main {
public static void main(String[] args) {
Integer a = new Integer(1);
WeakHashMap<Integer, String> weakHashMap = new WeakHashMap<>();
weakHashMap.put(a, "yyds");
System.out.println(weakHashMap);
a = null;
System.gc();
System.out.println(weakHashMap);
}
}
```
可以看到,**当变量a的引用断开后**,这时只有WeakHashMap本身对此对象存在引用,所以在GC之后,这个键值对就自动被舍弃了。所以说这玩意,就挺适合拿去做缓存的。
### 虚引用(鬼引用)
- 虚引用相当于没有引用,随时都有可能会被回收。
- 看看它的源码,非常简单:`**PhantomReference**`
```Java
public class PhantomReference<T> extends Reference<T> {
/**
* Returns this reference object's referent. Because the referent of a
* phantom reference is always inaccessible, this method always returns
* <code>null</code>.
*
* @return <code>null</code>
*/
public T get() {
return null;
}
/**
* Creates a new phantom reference that refers to the given object and
* is registered with the given queue.
*
* <p> It is possible to create a phantom reference with a <tt>null</tt>
* queue, but such a reference is completely useless: Its <tt>get</tt>
* method will always return null and, since it does not have a queue, it
* will never be enqueued.
*
* @param referent the object the new phantom reference will refer to
* @param q the queue with which the reference is to be registered,
* or <tt>null</tt> if registration is not required
*/
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
```
也就是说我们无论调用多少次`get()`方法得到的永远都是`null`,因为虚引用本身就不算是个引用,相当于这个对象不存在任何引用,并且只能使用带队列的构造方法,以便对象被回收时接到通知
最后,Java中4种引用的级别由高到低依次为: **强引用 > 软引用 > 弱引用 > 虚引用**
Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

@@ -0,0 +1,154 @@
---
title: JVM前篇
date: 2024-08-25
tags: [JVM]
---
## 前言
jvm规范
![image 52.png](JVM前篇/image52.png)
## 概述
首先我们要了解虚拟机的具体定义,我们所接触过的虚拟机有安装操作系统的虚拟机,也有我们的Java虚拟机,而它们所面向的对象不同,Java虚拟机只是面向单一应用程序的虚拟机,但是它和我们接触的系统级虚拟机一样,我们也可以为其分配实际的硬件资源,比如最大内存大小等。
并且Java虚拟机并没有采用传统的PC架构,比如现在的**HotSpot虚拟机**,实际上采用的是`**基于栈的指令集架构**`,而我们的传统程序设计一般都是`基于``**寄存器**``的指令集架构`,这里我们需要回顾一下`计算机组成原理`中的CPU结构:
![image 1 30.png](JVM前篇/image130.png)
> 省略了C语言在不同架构下编译出的汇编程序
C语言在不同的CPU架构下,实际上得到的汇编代码也不一样,并且在arm架构下并没有和x86架构一样的寄存器结构,因此只能使用不同的汇编指令操作来实现。所以这也是为什么C语言不支持跨平台的原因,依赖于硬件的支持。
Java利用了JVM,它提供了很好的平台无关性(当然,JVM本身是不跨平台的),我们的Java程序编译之后,并不是可以由平台直接运行的程序,而是由JVM运行,同时,我们前面说了,JVM(如HotSpot虚拟机),实际上采用的是`基于栈的指令集架构`,它并没有依赖于寄存器,而是更多的利用操作栈来完成,这样不仅设计和实现起来更简单,并且也能够更加方便地实现跨平台,不太依赖于硬件的支持。
> 省略分析字节码(class)文件分析
实际上我们发现,JVM执行的命令基本都是**入栈出栈**等,而且大部分指令都是没有==操作数(字面量)==的,传统的汇编指令有一操作数、二操作数甚至三操作数的指令,Java相比C编译出来的汇编指令,执行起来会更加复杂,实现某个功能的指令条数也会更多,所以Java的执行效率实际上是不如C/C++的,虽然能够很方便地实现跨平台,但是性能上大打折扣,所以在性能要求比较苛刻的Android上,采用的是**定制版的JVM**,并且是==基于寄存器的指令集架构==。此外,在某些情况下,我们还可以使用JNI机制来通过Java调用C/C++编写的程序以提升性能(也就是本地方法,使用到native关键字)
## jvm历史
JVM会根据当前代码的进行判断,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”。为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time Compiler
![image 2 20.png](JVM前篇/image220.png)
## JVM启动流程
![image 3 17.png](JVM前篇/image317.png)
## JNI调用本地方法
Java还有一个**JNI**机制,它的全称:==Java Native Interface==,即Java本地接口。它允许在Java虚拟机内运行的Java代码与其他编程语言(如C/C++和汇编语言)编写的程序和库进行交互(在Android开发中用得比较多)比如我们现在想要让C语言程序帮助我们的Java程序实现a+b的运算,首先我们需要创建一个本地方法:
```Java
public class Main {
public static void main(String[] args) {
System.out.println(sum(1, 2));
}
//本地方法使用native关键字标记,无需任何实现,交给C语言实现
public static native int sum(int a, int b);
}
```
创建好后,接着点击构建(编译)按钮,会出现一个out文件夹,也就是生成的class文件在其中,接着我们直接生成对应的C头文件:
```Shell
javah -classpath out/production/SimpleHelloWorld -d ./jni com.test.Main
```
生成的头文件位于jni文件夹下:
```C
/* DO NOT EDIT THIS FILE - it is machine generated */
\#include <jni.h>/* Header for class com_test_Main */
\#ifndef _Included_com_test_Main
\#define _Included_com_test_Main
\#ifdef __cplusplus
extern "C" {
\#endif/*
* Class: com_test_Main
* Method: sum
* Signature: (II)V
*/
JNIEXPORT void JNICALL Java_com_test_Main_sum
(JNIEnv *, jclass, jint, jint);
\#ifdef __cplusplus
}
\#endif#endif
```
接着我们在CLion中新建一个C++项目,并引入刚刚生成的头文件,并导入jni相关头文件(在JDK文件夹中)首先修改CMake文件:
```Plain
cmake_minimum_required(VERSION 3.21)
project(JNITest)
include_directories(/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/include)
include_directories(/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/include/darwin)
set(CMAKE_CXX_STANDARD 14)
add_executable(JNITest com_test_Main.cpp com_test_Main.h)
```
接着就可以编写实现了,首先认识一下引用类型对照表:
![image 4 15.png](JVM前篇/image415.png)
所以我们这里直接返回a+b即可:
```C++
\#include "com_test_Main.h"JNIEXPORT jint JNICALL Java_com_test_Main_sum
(JNIEnv * env, jclass clazz, jint a, jint b){
return a + b;
}
```
接着我们就可以将cpp编译为动态链接库,在MacOS下会生成`.dylib`文件,Windows下会生成`.dll`文件,我们这里就只以MacOS为例,命令有点长,因为还需要包含JDK目录下的头文件:
```Shell
gcc com_test_Main.cpp -I /Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/include -I /Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/include/darwin -fPIC -shared -o test.dylib -lstdc++
```
编译完成后,得到`test.dylib`文件,这就是动态链接库了。
最后我们再将其放到桌面,然后在Java程序中加载:
```Java
public class Main {
static {
System.load("/Users/nagocoler/Desktop/test.dylib");
}
public static void main(String[] args) {
System.out.println(sum(1, 2));
}
public static native int sum(int a, int b);
}
```
运行,成功得到结果:
```shell
/home/nagocoler/jdk-jdk8-b120/build/linux-x86_64-normal-server-slowdebug/jdk/bin/java Main
Hello World!
Process finished with exit code 0
```
### 总结
- 在Java中编写方法接口(即native修饰的方法)
- 编译生成class文件,根据clas文件生成JNI头文件(C头文件),其中包含你在C代码中需要实现的函数声明(生成一个c头文件,头文件说明了需要的函数)
- 创建C项目,引入生成的头文件,编写需要实现的函数
- 使用==CMake==将C编译成动态链接共享库
- 运行Java程序(需要调用系统类对共享库进行加载)
通过了解JVM的一些基础知识,我们心目中大致有了一个JVM的模型,在下一章,我们将继续深入学习JVM的内存管理机制和垃圾收集器机制,以及一些实用工具。
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

@@ -0,0 +1,837 @@
---
title: JVM后篇
date: 2024-08-25
tags: [JVM]
---
# 类与类加载
## 类文件结构
- 概述
在我们学习C语言的时候,我们的编程过程会经历如下几个阶段:写代码、保存、编译、运行。实际上,最关键的一步是**编译**,因为只有经历了编译之后,我们所编写的代码才能够翻译为机器可以直接运行的二进制代码,并且在不同的操作系统下,我们的代码都需要进行一次编译之后才能运行。
> 如果全世界所有的计算机指令集只有x86一种,操作系统只有Windows一种,那也许就不会有Java语言的出现。
随着时代的发展,人们迫切希望能够在不同的操作系统、不同的计算机架构中运行同一套编译之后的代码。本地代码不应该是我们编程的唯一选择,所以,越来越多的语言选择了与操作系统和机器指令集无关的中立格式作为编译后的存储格式。
“一次编写,到处运行”,Java最引以为傲的口号,标志着平台不再是限制编程语言的阻碍。
实际上,Java正式利用了这样的解决方案,将源代码编译为平台无关的中间格式,并通过对应的Java虚拟机读取和运行这些中间格式的编译文件,这样,我们只需要考虑不同平台的虚拟机如何编写,而Java语言本身很轻松地实现了跨平台。
现在,越来越多的开发语言都支持将源代码编译为`.class`字节码文件格式,以便能够直接交给JVM运行,包括Kotlin(安卓开发官方指定语言)、Groovy、Scala等。
![image-20230306165746693](JVM后篇/u2K8Y5yU1zf9LQ4.webp)
那么,让我们来看看,我们的源代码编译之后,是如何保存在字节码文件中的。
---
### 类文件信息
- 查看class文件,进行解析
我们之前都是使用`javap`命令来对字节码文件进行反编译查看的,那么,它以二进制格式是怎么保存呢?我们可以使用WinHex软件(Mac平台可以使用[010 Editor](https://www.macwk.com/soft/010-editor))来以十六进制查看字节码文件。
```Java
public class Main {
public static void main(String[] args) {
int i = 10;
int a = i++;
int b = ++i;
}
}
```
找到我们在IDEA中编译出来的class文件,将其拖动进去:
![image-20230306165815432](JVM后篇/QOvi5YpnaHTxrVU.webp)
可以看到整个文件中,全是一个字节一个字节分组的样子,从左上角开始,一行一行向下读取。可以看到在右侧中还出现了一些我们之前也许见过的字符串,比如"<init>"、"Object"等。
实际上Class文件采用了一种类似于C中结构体的伪结构来存储数据(当然我们直接看是看不出来的),但是如果像这样呢?
```Plain
Classfile /Users/nagocoler/Develop.localized/JavaHelloWorld/target/classes/com/test/Main.class
Last modified 2022-2-23; size 444 bytes
MD5 checksum 8af3e63f57bcb5e3d0eec4b0468de35b
Compiled from "Main.java"
public class com.test.Main
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#21 // java/lang/Object."<init>":()V
#2 = Class #22 // com/test/Main
#3 = Class #23 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 LocalVariableTable
#9 = Utf8 this
#10 = Utf8 Lcom/test/Main;
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 args
#14 = Utf8 [Ljava/lang/String;
#15 = Utf8 i
#16 = Utf8 I
#17 = Utf8 a
#18 = Utf8 b
#19 = Utf8 SourceFile
#20 = Utf8 Main.java
#21 = NameAndType #4:#5 // "<init>":()V
#22 = Utf8 com/test/Main
#23 = Utf8 java/lang/Object
{
public com.test.Main();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 11: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/test/Main;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=4, args_size=1
0: bipush 10
2: istore_1
3: iload_1
4: iinc 1, 1
7: istore_2
8: iinc 1, 1
11: iload_1
12: istore_3
13: return
LineNumberTable:
line 13: 0
line 14: 3
line 15: 8
line 16: 13
LocalVariableTable:
Start Length Slot Name Signature
0 14 0 args [Ljava/lang/String;
3 11 1 i I
8 6 2 a I
13 1 3 b I
}
SourceFile: "Main.java"
```
乍一看,是不是感觉还真的有点结构体那味?
而结构体中,有两种允许存在的数据类型,一个是无符号数,还有一个是表。
- 无符号数一般是基本数据类型,==用u1、u2、u4、u8来表示,表示1个字节~8个字节的无符号数==。可以表示数字、索引引用、数量值或是以UTF-8编码格式的字符串。
- 表包含多个无符号数,并且以"_info"结尾。
我们首先从最简的开始看起。
![image-20230306165846303](JVM后篇/4nFVBxOIo6QKjHA.webp)
- 首先,我们可以看到,==前4个字节(共32位)组成了魔数==(其实就是表示这个文件是一个JVM可以运行的字节码文件,除了Java以外,其他某些文件中也采用了这种魔数机制来进行区分,这种方式比直接起个文件扩展名更安全)
- 字节码文件的魔数为:==**CAFEBABE**==(这名字能想出来也是挺难的了,毕竟4个bit位只能表示出A-F这几个字母)
- 紧接着魔数的==后面4个字节存储的是字节码文件的版本号==,注意前两个是次要版本号(现在基本都不用了,都是直接Java8、Java9这样命名了),后面两个是主要版本号,这里我们主要看主版本号,比如上面的就是34,注意这是以16进制表示的,我们把它换算为10进制后,得到的结果为:`34 -> 3*16 + 4 = 52`,其中`52`代表的是`JDK8`编译的字节码文件(51是JDK7、50是JDK6、53是JDK9,以此类推)
- JVM会根据版本号决定是否能够运行,比如JDK6只能支持版本号为1.1~6的版本,也就是说必须是Java6之前的环境编译出来的字节码文件,否则无法运行。又比如我们现在安装的是JDK8版本,它能够支持的版本号为1.1~8,那么如果这时我们有一个通过Java7编译出来的字节码文件,依然是可以运行的,所以说Java版本是向下兼容的。
- 紧接着,就是类的常量池了,这里面存放了类中所有的常量信息(注意这里的常量并不是指我们手动创建的final类型常量,而是程序运行一些需要用到的常量数据,比如==字面量和符号引用等==)由于常量的数量不是确定的,所以在最开始的位置会存放常量池中常量的数量(是从1开始计算的,不是0,比如这里是18,翻译为10进制就是24,所以实际上有23个常量)
接着再往下,就是常量池里面的数据了,每一项常量池里面的数据都是一个表,我们可以看到他们都是以_info结尾的:
![image-20230306165906177](JVM后篇/i857GLYJ1fSIKz4.webp)
我们来看看一个表中定义了哪些内容:
![image-20230306165923282](JVM后篇/tizymh9BjYAFp3x.webp)
首先上来就会有一个1字节的无符号数,它用于表示当前常量的类型(常量类型有很多个)这里只列举一部分的类型介绍:
| | | |
|---|---|---|
|类型|标志|描述|
|CONSTANT_Utf8_info|1|UTF-8编码格式的字符串|
|CONSTANT_Integer_info|3|整形字面量(第一章我们演示的很大的数字,实际上就是以字面量存储在常量池中的)|
|CONSTANT_Class_info|7|类或接口的符号引用|
|CONSTANT_String_info|8|字符串类型的字面量|
|CONSTANT_Fieldref_info|9|字段的符号引用|
|CONSTANT_Methodref_info|10|方法的符号引用|
|CONSTANT_MethodType_info|16|方法类型|
|CONSTANT_NameAndType_info|12|字段或方法的部分符号引用|
实际上这些东西,虽然我们不知道符号引用是什么东西,我们可以观察出来,这些东西或多或少都是存放类中一些名称、数据之类的东西。
比如我们来看第一个`CONSTANT_Methodref_info`表中存放了什么数据,这里我只列出它的结构表(详细的结构表可以查阅《深入理解Java虚拟机 第三版》中222页总表):
| | | | |
|---|---|---|---|
|常量|项目|类型|描述|
|CONSTANT_Methodref_info|tag|u1|值为10|
||index|u2|指向声明方法的类描述父CONSTANT_Class_info索引项|
||index|u2|指向名称及类型描述符CONSTANT_NameAndType_info索引项|
比如我们刚刚的例子中:
![image-20230306165936467](JVM后篇/WORY4BSMzhcQNyt.webp)
可以看到,第一个索引项指向了第3号常量,我们来看看三号常量:
![image-20230306165947568](JVM后篇/AbpBRLIwczTyqMP.webp)
| 常量 | 项目 | 类型 | 描述 |
| ------------------- | ----- | ---- | ------------------------ |
| CONSTANT_Class_info | tag | u1 | 值为7 |
| | index | u2 | 指向全限定名常量项的索引 |
那么我们接着来看23号常量又写的啥:
![image-20230306170009857](JVM后篇/oVg2cwD4Gjrfab7.webp)
可以看到指向的UTF-8字符串值为`java/lang/Object`这下搞明白了,首先这个方法是由Object类定义的,那么接着我们来看第二项u2 `name_and_type_index`,指向了21号常量,也就是字段或方法的部分符号引用:
![image-20230306170021693](JVM后篇/2hbcO5flxzodN9J.webp)
|常量|项目|类型|描述|
|---|---|---|---|
|CONSTANT_NameAndType_info|tag|u1|值为12|
||index|u2|指向字段或方法名称常量项的索引|
||index|u2|指向字段或方法描述符常量项的索引|
其中第一个索引就是方法的名称,而第二个就是方法的描述符,描述符明确了方法的参数以及返回值类型,我们分别来看看4号和5号常量:
![image-20230306170037268](JVM后篇/aB8fu1GUNwYMPLh.webp)
可以看到,方法名称为"<init>",一般构造方法的名称都是<init>,普通方法名称是什么就是什么,方法描述符为"()V",表示此方法没有任何参数,并且返回值类型为void,描述符对照表如下:
![image-20230306170051403](JVM后篇/V1RyLUTBgfFMk69.webp)
比如这里有一个方法`public int test(double a, char c){ ... }`,那么它的描述符就应该是:`(DC)I`,参数依次放入括号中,括号右边是返回值类型。再比如`public String test(Object obj){ ... }`,那么它的描述符就应该是:`(Ljava/lang/Object;)Ljava/lang/String`,注意如果参数是对象类型,那么必须在后面添加`;`
对于数组类型,只需要在类型最前面加上`[`即可,有几个维度,就加几个,比如`public void test(int[][] arr)`,参数是一个二维int类型数组,那么它的描述符为:`([[I)V`
所以,这里表示的,实际上就是此方法是一个无参构造方法,并且是属于Object类的。那么,为什么这里需要Object类构造方法的符号引用呢?还记得我们在JavaSE中说到的,每个类都是直接或间接继承自Object类,所有类的构造方法,必须先调用父类的构造方法,但是如果父类存在无参构造,默认可以不用显示调用`super`关键字(当然本质上是调用了的)。
所以说,当前类因为没有继承自任何其他类,那么就默认继承的Object类,所以,在当前类的默认构造方法中,调用了父类Object类的无参构造方法,因此这里需要符号引用的用途显而易见,就是因为需要调用Object类的无参构造方法。
我们可以在反编译结果中的方法中看到:
```Plain
public com.test.Main();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 11: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/test/Main;
```
其中`invokespecial`(调用父类构造方法)指令的参数指向了1号常量,而1号常量正是代表的Object类的无参构造方法,虽然饶了这么大一圈,但是过程理清楚,还是很简单的。
虽然我们可以直接查看16进制的结果,但是还是不够方便,但是我们也不能每次都去使用`javap`命令,所以我们这里安装一个IDEA插件,来方便我们查看字节码中的信息,名称为`jclasslib Bytecode Viewer`
![image-20230306170130977](JVM后篇/28BVwcadNEjCnLi.webp)
安装完成后,我们可以在我们的IDEA右侧看到它的板块,但是还没任何数据,那么比如现在我们想要查看Main类的字节码文件时,可以这样操作:
![image-20230306170141372](JVM后篇/3TvqOXGsDIPbzlh.webp)
首先在项目中选中我们的Main类,然后点击工具栏的视图,然后点击`Show Bytecode With Jclasslib`,这样右侧就会出现当前类的字节码解析信息了。注意如果修改了类的话,那么需要你点击运行或是构建,然后点击刷新按钮来进行更新。
接着我们来看下一个内容,在常量池之后,紧接着就是访问标志,访问标志就是类的种类以及类上添加的一些关键字等内容:
![image-20230306170153604](JVM后篇/HE7QJpfbh6sOLji.webp)
可以看到它只占了2个字节,那么它是如何表示访问标志呢?
![image-20230306170207034](JVM后篇/lY6sJwicX5d2FVg.webp)
比如我们这里的Main类,它是一个普通的class类型,并且访问权限为public,那么它的访问标志值是这样计算的:
`ACC_PUBLIC | ACC_SUPER = 0x0001 | 0x0020 = 0x0021`(这里进行的是按位或运算),可以看到和我们上面的结果是一致的。
再往下就是类索引、父类索引、接口索引:
![image-20230306170218340](JVM后篇/WskFX26NDco8Jgv.webp)
可以看到它们的值也是指向常量池中的值,其中2号常量正是存储的当前类信息,3号常量存储的是父类信息,这里就不再倒推回去了,由于没有接口,所以这里接口数量为0,如果不为0还会有一个索引表来引用接口。
接着就是字段和方法表集合了:
![image-20230306170228985](JVM后篇/p6wAqsZneuI8Wo3.webp)
由于我们这里没有声明任何字段,所以我们先给Main类添加一个字段再重新加载一下:
```Java
public class Main {
public static int a = 10;
public static void main(String[] args) {
int i = 10;
int a = i++;
int b = ++i;
}
}
```
![image-20230306170307227](JVM后篇/dzmcQUyFrH9kTWZ.webp)
现在字节码就新增了一个字段表,这个字段表实际上就是我们刚刚添加的成员字段`a`的数据。
可以看到一共有四个2字节的数据:
![image-20230306170316904](JVM后篇/Phy8UoMGiLkpdsf.webp)
首先是`access_flags`,这个与上面类标志的计算规则是一样的,表还是先列出来吧:
![image-20230306170332081](JVM后篇/uCrZ8aG2EoxLM5c.webp)
第二个数据`name_index`表示字段的名称常量,这里指向的是5号常量,那么我们来看看5号常量是不是字段名称:
![image-20230306170345473](JVM后篇/LOycXsCrm4vAjDk.webp)
没问题,这里就是`a`,下一个是`descirptor_index`,存放的是描述符,不过这里因为不是方法而是变量,所以描述符直接写对应类型的标识字符即可,比如这里是`int`类型,那么就是`I`。
最后,`attrbutes_count`属性计数器,用于描述一些额外信息,这里我们暂时不做介绍。
接着就是我们的方法表了:
![image-20230306170359554](JVM后篇/O4XnFbfRShkc8WZ.webp)
可以看到方法表中一共有三个方法,其中第一个方法我们刚刚已经介绍过了,它的方法名称为`<init>`,表示它是一个构造方法,我们看到最后一个方法名称为`<clinit>`,这个是类在初始化时会调用的方法(是隐式的,自动生成的),它主要是用于静态变量初始化语句和静态块的执行,因为我们这里给静态成员变量a赋值为10,所以会在一开始为其赋值:
![image-20230306170411225](JVM后篇/zkJVicf764bFPXd.webp)
而第二个方法,就是我们的`main`方法了,但是现在我们先不急着去看它的详细实现过程,我们来看看它的属性表。
属性表实际上类中、字段中、方法中都可以携带自己的属性表,属性表存放的正是我们的代码、本地变量等数据,比如main方法就存在4个本地变量,那么它的本地变量存放在哪里呢:
![image-20230306170426154](JVM后篇/dPoWa74AeYJcIG1.webp)
可以看到,属性信息呈现套娃状态,在此方法中的属性包括了一个Code属性,此属性正是我们的Java代码编译之后变成字节码指令,然后存放的地方,而在此属性中,又嵌套了本地变量表和源码行号表。
可以看到code中存放的就是所有的字节码指令:
![image-20230306170436483](JVM后篇/c7ebHq3TBxudK1A.webp)
这里我们暂时不对字节码指令进行讲解(其实也用不着讲了,都认识的差不多了)。我们接着来看本地变量表,这里存放了我们方法中要用到的局部变量:
![image-20230306170452941](JVM后篇/heoLwXRVpuBYbZn.webp)
可以看到一共有四个本地变量,而第一个变量正是main方法的形参`String[] args`,并且表中存放了本地变量的长度、名称、描述符等内容。当然,除了我们刚刚认识的这几个属性之外,完整属性可以查阅《深入理解Java虚拟机 第三版》231页。
最后,类也有一些属性:
![image-20230306170510722](JVM后篇/fDTVYpAlaBF6j91.webp)
此属性记录的是源文件名称。
这样,我们对一个字节码文件的认识差不多就结束了,在了解了字节码文件的结构之后,是不是感觉豁然开朗?
### 字节码指令
- 查看class文件,分析指令
- 虚拟机的指令是由一个字节长度的、代表某种特定操作含义的数字(操作码,类似于机器语言),操作后面也可以携带0个或多个参数一起执行。我们前面已经介绍过了,JVM实际上并不是面向寄存器架构的,而是面向操作数栈,所以大多数指令都是不带参数的。
由于之前已经讲解过大致运行流程,这里我们就以当前的Main类中的main方法作为教材进行讲解:
```Java
public static void main(String[] args) {
int i = 10;
int a = i++;
int b = ++i;
}
```
可以看到,main方法中首先是定义了一个int类型的变量i,并赋值为10,然后变量a接收`i++`的值,变量b接收`++i`的值。
那么我们来看看编译成字节码之后,是什么样的:
![image-20230306170531318](JVM后篇/1wOpRXoQklGN9Ez.webp)
- 首先第一句,`bipush`,将10送至操作数栈顶。
- 接下来将操作数栈顶的数值存进1号本地变量,也就是变量i中。
- 接着将变量i中的值又丢向操作数栈顶
- 这里使用`iinc`指令,将1号本地变量的值增加1(结束之后i的值就是11了)
- 接着将操作数栈顶的值(操作数栈顶的值是10)存入2号本地变量(这下彻底知道i++到底干了啥才会先返回后自增了吧,从原理角度来说,实际上i是先自增了的,但由于这里取的是操作数栈中的值,所以说就得到了i之前的值)
- 接着往下,我们看到++i是先直接将i的值自增1
- 然后在将其值推向操作数栈顶
![image-20230306170553756](JVM后篇/kOPlftdWADqyoHL.webp)
而从结果来看,`i++`操作确实是先返回再自增的,而字节码指令层面来说,却是截然相反的,只是结果一致罢了。
### ASM字节码编程
- 使用ASM框架(Java类)编写Class文件
- 既然字节码文件结构如此清晰,那么我们能否通过编程,来直接创建一个字节码文件呢?如果我们可以直接编写一个字节码文件,那么我们就可以省去编译的过程。ASM(某些JDK中内置)框架正是用于支持字节码编程的框架。
比如现在我们需要创建一个普通的Main类(暂时不写任何内容)
首先我们来看看如何通过编程创建一个Main类的字节码文件:
```Java
public class Main {
public static void main(String[] args) {
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
}
}
```
首先需要获取`ClassWriter`对象,我们可以使用它来编辑类的字节码文件,在构造时需要传入参数:
- 0 这种方式不会自动计算操作数栈和局部临时变量表大小,需要自己手动来指定
- ClassWriter.COMPUTE_MAXS(1) 这种方式会自动计算上述操作数栈和局部临时变量表大小,但需要手动触发。
- ClassWriter.COMPUTE_FRAMES(2) 这种方式不仅会计算上述操作数栈和局部临时变量表大小,而且会自动计算StackMapFrames
这里我们使用`ClassWriter.COMPUTE_MAXS`即可。
接着我们首先需要指定类的一些基本信息:
```Java
public class Main {
public static void main(String[] args) {
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
//因为这里用到的常量比较多,所以说直接一次性静态导入:import static jdk.internal.org.objectweb.asm.Opcodes.*;
writer.visit(V1_8, ACC_PUBLIC,"com/test/Main", null, "java/lang/Object",null);
}
}
```
这里我们将字节码文件的版本设定位Java8,然后修饰符设定为`ACC_PUBLIC`代表`public class Main`,类名称注意要携带包名,标签设置为`null`,父类设定为Object类,然后没有实现任何接口,所以说最后一个参数也是`null`。
接着,一个简答的类字节码文件就创建好了,我们可以尝试将其进行保存
```Java
public class Main {
public static void main(String[] args) {
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
writer.visit(V1_8, ACC_PUBLIC,"com/test/Main", null, "java/lang/Object",null);
//调用visitEnd表示结束编辑
writer.visitEnd();
try(FileOutputStream stream = new FileOutputStream("./Main.class")){
stream.write(writer.toByteArray()); //直接通过ClassWriter将字节码文件转换为byte数组,并保存到根目录下
} catch (IOException e) {
e.printStackTrace();
}
}
}
```
可以看到,在IDEA中反编译的结果为:
```Java
package com.test;
public class Main {
}
```
我们知道,正常的类在编译之后,如果没有手动添加构造方法,那么会自带一个无参构造,但是我们这个类中还没有,所以我们来手动添加一个无参构造方法:
```Java
//通过visitMethod方法可以添加一个新的方法
writer.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
```
可以看到反编译的结果中已经存在了我们的构造方法:
```Java
package com.test;
public class Main {
public Main() {
}
}
```
但是这样是不合法的,因为我们的构造方法还没有添加父类构造方法调用,所以说我们还需要在方法中添加父类构造方法调用指令:
```Plain
public com.test.Main();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 11: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/test/Main;
```
我们需要对方法进行详细编辑:
```Java
//通过MethodVisitor接收返回值,进行进一步操作
MethodVisitor visitor = writer.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
//开始编辑代码
visitor.visitCode();
//Label用于存储行号
Label l1 = new Label();
//当前代码写到哪行了,l1得到的就是多少行
visitor.visitLabel(l1);
//添加源码行数对应表(其实可以不用)
visitor.visitLineNumber(11, l1);
//注意不同类型的指令需要用不同方法来调用,因为操作数不一致,具体的注释有写
visitor.visitVarInsn(ALOAD, 0);
visitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
visitor.visitInsn(RETURN);
Label l2 = new Label();
visitor.visitLabel(l2);
//添加本地变量表,这里加的是this关键字,但是方法中没用到,其实可以不加
visitor.visitLocalVariable("this", "Lcom/test/Main;", null, l1, l2, 0);
//最后设定最大栈深度和本地变量数
visitor.visitMaxs(1, 1);
//结束编辑
visitor.visitEnd();
```
我们可以对编写好的class文件进行反编译,看看是不是和IDEA编译之后的结果差不多:
```Plain
{
public com.test.Main();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/test/Main
LineNumberTable:
line 11: 0
}
```
可以看到和之前的基本一致了,到此为止我们构造方法就编写完成了,接着我们来写一下main方法,一会我们就可以通过main方法来运行Java程序了。比如我们要编写这样一个程序:
```Java
public static void main(String[] args) {
int a = 10;
System.out.println(a);
}
```
看起来很简单的一个程序对吧,但是我们如果手动去组装指令,会极其麻烦!首先main方法是一个静态方法,并且方法是public权限,然后还有一个参数`String[] args`,所以说我们这里要写的内容有点小多:
```Java
//开始安排main方法
MethodVisitor v2 = writer.visitMethod(ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
v2.visitCode();
//记录起始行信息
Label l3 = new Label();
v2.visitLabel(l3);
v2.visitLineNumber(13, l3);
//首先是int a = 10的操作,执行指令依次为:
// bipush 10 将10推向操作数栈顶
// istore_1 将操作数栈顶元素保存到1号本地变量a中
v2.visitIntInsn(BIPUSH, 10);
v2.visitVarInsn(ISTORE, 1);
Label l4 = new Label();
v2.visitLabel(l4);
//记录一下行信息
v2.visitLineNumber(14, l4);
//这里是获取System类中的out静态变量(PrintStream接口),用于打印
v2.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
//把a的值取出来
v2.visitVarInsn(ILOAD, 1);
//调用接口中的抽象方法println
v2.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
//再次记录行信息
Label l6 = new Label();
v2.visitLabel(l6);
v2.visitLineNumber(15, l6);
v2.visitInsn(RETURN);
Label l7 = new Label();
v2.visitLabel(l7);
//最后是本地变量表中的各个变量
v2.visitLocalVariable("args", "[Ljava/lang/String;", null, l3, l7, 0);
v2.visitLocalVariable("a", "I", null, l4, l7, 1);
v2.visitMaxs(1, 2);
//终于OK了
v2.visitEnd();
```
可以看到,虽然很简单的一个程序,但是如果我们手动去编写字节码,实际上是非常麻烦的,但是要实现动态代理之类的操作(可以很方便地修改字节码创建子类),是不是感觉又Get到了新操作(其实Spring实现动态代理的CGLib框架底层正是调用了ASM框架来实现的),所以说了解一下还是可以的,不过我们自己肯定是没多少玩这个的机会了。
---
## 类加载机制
现在,我们已经了解了字节码文件的结构,以及JVM如何对内存进行管理,现在只剩下最后一个谜团等待解开了,也就是我们的类字节码文件到底是如何加载到内存中的,加载之后又会做什么事情。
### 类加载过程
- 详情
首先,要加载一个类,一定是出于某种目的的,比如我们要运行我们的Java程序,那么就必须要加载主类才能运行主类中的主方法,又或是我们需要加载数据库驱动,那么可以通过反射来将对应的数据库驱动类进行加载。
所以,一般在这些情况下,如果类没有被加载,那么会被自动加载:
- 使用new关键字创建对象时
- 使用某个类的静态成员(包括方法和字段)的时候(当然,final类型的静态字段有可能在编译的时候被放到了当前类的常量池中,这种情况下是不会触发自动加载的)
- 使用反射对类信息进行获取的时候(之前的数据库驱动就是这样的)
- 加载一个类的子类时
- 加载接口的实现类,且接口带有`default`的方法默认实现时
比如这种情况,那么需要用到另一个类中的成员字段,所以就必须将另一个类加载之后才能访问:
```Java
public class Main {
public static void main(String[] args) {
System.out.println(Test.str);
}
public static class Test{
static {
System.out.println("我被初始化了!");
}
public static String str = "都看到这里了,不给个三连+关注吗?";
}
}
```
这里我们就演示一个不太好理解的情况,我们现在将静态成员变量修改为final类型的:
```Java
public class Main {
public static void main(String[] args) {
System.out.println(Test.str);
}
public static class Test{
static {
System.out.println("我被初始化了!");
}
public final static String str = "都看到这里了,不给个三连+关注吗?";
}
}
```
可以看到,在主方法中,我们使用了Test类的静态成员变量,并且此静态成员变量是一个final类型的,也就是说不可能再发生改变。那么各位觉得,Test类会像上面一样被初始化吗?
按照正常逻辑来说,既然要用到其他类中的字段,那么肯定需要加载其他类,但是这里我们结果发现,并没有对Test类进行加载,那么这是为什么呢?我们来看看Main类编译之后的字节码指令就知道了:
![image-20230306170622970](JVM后篇/JyFWfPbBvIK5zMe.webp)
很明显,这里使用的是`ldc`指令从常量池中将字符串取出并推向操作数栈顶,也就是说,在编译阶段,整个`Test.str`直接被替换为了对应的字符串(因为final不可能发生改变的,编译就会进行优化,直接来个字符串比你去加载类在获取快得多不是吗,反正结果都一样),所以说编译之后,实际上跟Test类半毛钱关系都没有了。
所以说,当你在某些情况下疑惑为什么类加载了或是没有加载时,可以从字节码指令的角度去进行分析,一般情况下,只要遇到`new`、`getstatic`、`putstatic`、`invokestatic`这些指令时,都会进行类加载,比如:
![image-20230306170635704](JVM后篇/IRo9i6hntA2jQ3X.webp)
这里很明显,是一定会将Test类进行加载的。除此之外,各位也可以试试看数组的定义会不会导致类被加载。
好了,聊完了类的加载触发条件,我们接着来看一下类的详细加载流程。
![image-20230306170654350](JVM后篇/UIV6fJknmM4bojP.webp)
首先类的生命周期一共有7个阶段,而首当其冲的就是加载,加载阶段需要获取此类的二进制数据流,比如我们要从硬盘中读取一个class文件,那么就可以通过文件输入流来获取类文件的`byte[]`,也可以是其他各种途径获取类文件的输入流,甚至网络传输并加载一个类也不是不可以。然后交给类加载器进行加载(类加载器可以是JDK内置的,也可以是开发者自己撸的,后面会详细介绍)类的所有信息会被加载到方法区中,并且在堆内存中会生成一个代表当前类的Class类对象(那么思考一下,同一个Class文件加载的类,是唯一存在的吗?),我们可以通过此对象以及反射机制来访问这个类的各种信息。
数组类要稍微特殊一点,通过前面的检验,我没发现数组在创建后是不会导致类加载的,数组类型本身不会通过类加载器进行加载的,不过你既然要往里面丢对象进去,那最终依然是要加载类的。
接着我们来看验证阶段,验证阶段相当于是对加载的类进行一次规范校验(因为一个类并不一定是由我们使用IDEA编译出来的,有可能是像我们之前那样直接用ASM框架写的一个),如果说类的任何地方不符合虚拟机规范,那么这个类是不会验证通过的,如果没有验证机制,那么一旦出现危害虚拟机的操作,整个程序会出现无法预料的后果。
验证阶段,首先是文件格式的验证:
- 是否魔数为CAFEBABE开头。
- 主、次版本号是否可以由当前Java虚拟机运行
- Class文件各个部分的完整性如何。
- ...
有关类验证的详细过程,可以参考《深入理解Java虚拟机 第三版》268页。
接下来就是准备阶段了,这个阶段会为类变量分配内存,并为一些字段设定初始值,注意是系统规定的初始值,不是我们手动指定的初始值。
再往下就是解析阶段,此阶段是将常量池内的符号引用替换为直接引用的过程,也就是说,到这个时候,所有引用变量的指向都是已经切切实实地指向了内存中的对象了。
到这里,链接过程就结束了,也就是说这个时候类基本上已经完成大部分内容的初始化了。
最后就是真正的初始化阶段了,从这里开始,类中的Java代码部分,才会开始执行,还记得我们之前介绍的`<clinit>`方法吗,它就是在这个时候执行的,比如我们的类中存在一个静态成员变量,并且赋值为10,或是存在一个静态代码块,那么就会自动生成一个`<clinit>`方法来进行赋值操作,但是这个方法是自动生成的。
全部完成之后,我们的类就算是加载完成了。
### 类加载器
- 详情
Java提供了类加载器,以便我们自己可以更好地控制类加载,我们可以自定义类加载器,也可以使用官方自带的类加载器去加载类。对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性。
也就是说,一个类可以由不同的类加载器加载,并且,不同的类加载器加载的出来的类,即使来自同一个Class文件,也是不同的,只有两个类来自同一个Class文件并且是由同一个类加载器加载的,才能判断为是同一个。默认情况下,所有的类都是由JDK自带的类加载器进行加载。
比如,我们先创建一个Test类用于测试:
```Java
package com.test;
public class Test {
}
```
接着我们自己实现一个ClassLoader来加载我们的Test类,同时使用官方默认的类加载器来加载:
```Java
public class Main {
public static void main(String[] args) throws ReflectiveOperationException {
Class<?> testClass1 = Main.class.getClassLoader().loadClass("com.test.Test");
CustomClassLoader customClassLoader = new CustomClassLoader();
Class<?> testClass2 = customClassLoader.loadClass("com.test.Test");
//看看两个类的类加载器是不是同一个
System.out.println(testClass1.getClassLoader());
System.out.println(testClass2.getClassLoader());
//看看两个类是不是长得一模一样
System.out.println(testClass1);
System.out.println(testClass2);
//两个类是同一个吗?
System.out.println(testClass1 == testClass2);
//能成功实现类型转换吗?
Test test = (Test) testClass2.newInstance();
}
static class CustomClassLoader extends ClassLoader {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try (FileInputStream stream = new FileInputStream("./target/classes/"+name.replace(".", "/")+".class")){
byte[] data = new byte[stream.available()];
stream.read(data);
if(data.length == 0) return super.loadClass(name);
return defineClass(name, data, 0, data.length);
} catch (IOException e) {
return super.loadClass(name);
}
}
}
}
```
通过结果我们发现,即使两个类是同一个Class文件加载的,只要类加载器不同,那么这两个类就是不同的两个类。
所以说,我们当时在JavaSE阶段讲解的每个类都在堆中有一个唯一的Class对象放在这里来看,并不完全正确,只是当前为了防止各位初学者搞混。
实际上,JDK内部提供的类加载器一共有三个,比如上面我们的Main类,其实是被AppClassLoader加载的,而JDK内部的类,都是由BootstrapClassLoader加载的,这其实就是为了实现双亲委派机制而做的。
![image-20230306170728531](JVM后篇/RFaE7s5CnmylgkT.webp)
有关双亲委派机制,我们在JavaSE阶段反射板块已经讲解过了,所以说这就不多做介绍了。
- 为什么要自定义类加载器
Java 提供自定义类加载器的机制有几个重要的原因。让我们深入探讨这个问题:
1. 扩展类加载方式:
- 默认的类加载器从文件系统加载类,但有时我们需要从其他源加载类,如网络、数据库等。
- 自定义类加载器允许我们定义特殊的类加载逻辑。
2. 实现类的隔离:
- 在同一个 JVM 中,不同的应用可能需要使用同一个类的不同版本。
- 自定义类加载器可以为每个应用加载独立的类版本,实现类的隔离。
3. 提高安全性:
- 可以在加载类之前进行安全检查,如验证数字签名。
- 可以实现自定义的安全策略,控制哪些类可以被加载。
4. 实现热部署:
- 在不重启 JVM 的情况下,通过自定义类加载器可以重新加载更新后的类。
- 这在需要频繁更新的系统中非常有用,如开发环境或某些服务器应用。
5. 懒加载:
- 可以实现按需加载类,而不是在启动时就加载所有类。
- 这可以提高应用的启动速度和资源利用效率。
6. 修改字节码:
- 在加载类的过程中,可以动态修改类的字节码。
- 这对于一些特殊需求如性能监控、代码增强等非常有用。
7. 实现插件化架构:
- 在插件系统中,可以使用自定义类加载器来加载和管理插件。
- 这样可以实现插件的动态加载和卸载。
8. 处理特殊的类加载需求:
- 某些框架或库可能需要特殊的类加载机制。
- 例如,一些 Web 服务器使用自定义类加载器来隔离不同的 Web 应用。
9. 跨应用程序边界共享类:
- 在某些情况下,可能需要在不同的应用程序间共享类,自定义类加载器可以实现这一点。
10. 实现类的版本控制:
- 可以加载特定版本的类,而不受系统类路径中可能存在的其他版本影响。
11. 优化类加载性能:
- 在某些情况下,自定义的类加载策略可能比默认的更高效。
12. 动态代理和AOP的实现:
- 一些高级的编程技术,如动态代理和面向切面编程(AOP),often利用自定义类加载器来动态生成和加载类。
示例:自定义网络类加载器
以下是一个简单的自定义类加载器示例,它从网络加载类:
```Java
public class NetworkClassLoader extends ClassLoader {
private String baseUrl;
public NetworkClassLoader(String baseUrl) {
this.baseUrl = baseUrl;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
String url = baseUrl + "/" + name.replace('.', '/') + ".class";
URL classUrl = new URL(url);
try (InputStream is = classUrl.openStream()) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int ch;
while ((ch = is.read()) != -1) {
baos.write(ch);
}
byte[] bytes = baos.toByteArray();
return defineClass(name, bytes, 0, bytes.length);
}
} catch (IOException e) {
throw new ClassNotFoundException("Class " + name + " not found", e);
}
}
}
```
这个例子展示了如何创建一个从网络加载类的自定义类加载器。它从指定的 URL 下载类文件,然后使用 `defineClass` 方法将字节码转换为 Class 对象。
总结来说,Java 提供自定义类加载器机制,极大地增强了 Java 平台的灵活性和可扩展性,使得开发者能够实现各种复杂的类加载策略,满足不同场景下的特殊需求。
Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Some files were not shown because too many files have changed in this diff Show More