Java 运行时动态能力

反射

反射就是程序在运行时查看、调用类的信息。

正常调用:

UserService service = new UserService();
service.getUser();

反射调用:

Class<?> clazz = Class.forName("UserService");
Object obj = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getMethod("getUser");
method.invoke(obj);

简单理解:

普通代码:我提前知道要调用谁
反射:运行时再根据类名、方法名去找并调用

Spring、ORM、序列化框架大量使用反射。

动态代理

动态代理就是运行时临时生成一个“代理对象”,在原方法前后加逻辑。

原始调用:

UserService.getUser()

代理后:

ProxyUserService.getUser()
  ↓
before()
  ↓
UserService.getUser()
  ↓
after()

常见用途:

事务
权限校验
日志
RPC 调用
链路追踪

比如 Spring AOP 的事务:

原始方法:
createOrder()

代理后:
beginTransaction()
createOrder()
commitTransaction()

字节码增强

字节码增强是不改 .java 源码,而是在 JVM 加载 .class 字节码时修改它。

原始方法:
UserService.getUser()

增强后:
startSpan()
UserService.getUser()
endSpan()

APM Agent、性能监控、代码覆盖率工具经常用这个能力。

运行时类加载

Java 的类不一定在程序启动时全部加载,而是用到时再加载。

程序启动
  ↓
用到 UserService
  ↓
JVM 加载 UserService.class

这让 Java 可以支持插件化、动态扩展、按需加载。

例如:

Class.forName("com.demo.PluginA");

含义是:

运行时根据类名加载一个类

SPI 扩展

SPI 是 Service Provider Interface,服务扩展机制。

它允许框架定义接口,具体实现由外部提供。

框架定义:
PaymentProvider

业务实现:
AliPayProvider
WechatPayProvider

运行时自动发现实现:

扫描配置文件
  ↓
找到实现类
  ↓
加载并使用

典型例子:

JDBC Driver
Dubbo 扩展点
日志框架实现

Spring 注解扫描

Spring 启动时会扫描类上的注解,然后自动创建对象、建立依赖关系。

例如:

@Service
public class UserService {
}

Spring 扫描后:

发现 @Service
  ↓
创建 UserService 对象
  ↓
放进 Spring 容器

再比如:

@Autowired
private UserDao userDao;

含义是:

Spring 自动把 UserDao 注入进来

ORM 映射

ORM 是 Object Relational Mapping,对象关系映射。

它把 Java 对象和数据库表关联起来。

class User {
    Long id;
    String name;
}

对应数据库:

user 表
id    name
1     Tom

你写:

userDao.getById(1);

ORM 帮你转换成:

SELECT id, name FROM user WHERE id = 1;

常见 ORM:

MyBatis
Hibernate
JPA

序列化框架

序列化就是把对象转换成可以传输或存储的格式。

Java 对象:

User{id=1, name="Tom"}

序列化成 JSON:

{"id":1,"name":"Tom"}

反序列化就是反过来:

JSON 字符串
  ↓
Java 对象

常见框架:

Jackson
Fastjson
Gson
Protobuf

APM Agent 插桩

APM 是 Application Performance Monitoring,应用性能监控。

Agent 插桩就是在应用运行时自动加入监控逻辑。

原始调用:

OrderService.createOrder()

插桩后:

startSpan()
recordStartTime()
OrderService.createOrder()
recordCost()
endSpan()

它可以自动记录:

接口耗时
数据库耗时
RPC 调用
异常信息
调用链路

一句话总结:

这些能力都依赖 Java/JVM 的运行时动态性:
程序运行时可以加载类、扫描注解、创建代理、反射调用、增强字节码、自动插入监控逻辑。

Java中的分代垃圾回收算法

Java中的分代垃圾回收算法是针对不同生命周期的对象采取不同的回收策略,主要将堆内存分为新生代和老年代两部分,分别采用不同的回收算法。

1. 新生代(Young Generation)的垃圾回收算法:

新生代主要存放新创建的对象,其特点是对象的生命周期较短。因此,针对新生代的垃圾回收算法主要考虑了尽快回收新生代中的短命对象,以提高内存利用率和降低垃圾回收的成本。

1.1 复制算法(Copying Algorithm):
  • 新生代采用的主要垃圾回收算法是复制算法,也称为标记-复制算法。这种算法将新生代的内存空间划分为两个相等的区域:Eden区、From区和To区。当对象被分配到Eden区时,当Eden区满了之后,触发Minor GC,将存活的对象复制到To区,同时清空Eden区和From区。对象经过多次Minor GC仍然存活的,会被移到老年代中。
1.2 优点:
  • 适用于新生代中对象生命周期短的情况,可以高效地回收短命对象。
  • 简单高效,内存分配连续,减少了碎片化。
1.3 缺点:
  • 由于需要复制存活的对象,所以内存开销较大。
  • 需要额外的空间来存放复制后的对象。

2. 老年代(Old Generation)的垃圾回收算法:

老年代主要存放对象生命周期较长的对象,由于对象存活时间较长,采用复制算法将会浪费大量的空间。因此,老年代通常采用其他类型的垃圾回收算法。

2.1 标记-清除算法(Mark-Sweep Algorithm):
  • 首先,标记阶段对所有存活的对象进行标记。然后,在清除阶段,清理掉未标记的对象,即将未标记的对象所占用的内存释放掉。但这种方法可能会产生内存碎片,降低了内存分配的效率。
2.2 标记-整理算法(Mark-Compact Algorithm):
  • 类似于标记-清除算法,但在清除阶段之后,会对存活的对象进行整理,将它们向内存的一端移动,从而消除了内存碎片,提高了内存的利用率。
2.3 优点:
  • 适用于老年代中存活对象较多的情况。
  • 能够避免内存碎片化,提高内存利用率。
2.4 缺点:
  • 清除阶段的效率较低,需要遍历所有对象进行标记。
  • 需要额外的内存空间来存放标记信息。

总的来说,Java的分代垃圾回收算法将堆内存分为新生代和老年代两部分,并针对不同生命周期的对象采用不同的垃圾回收算法,以提高垃圾回收的效率和内存利用率。

IDEA 目录如何平铺

勾选Compact Middle Packages

请添加图片描述

Idea单一个窗口同时打开不同文件夹下的多个maven项目依赖导不进来问题解决

第一步:idea打开多个文件夹的父级文件夹
第二步: 把你想要的项目添加为Maven项目(右键单机pom.xml,将其add as Maven Project即可 )
第三步:重复第二步添加第二个、第三个、第四个、、、、项目
请添加图片描述

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐