基础知识
JVM内存结构
- 堆(Heap):存储对象实例和数组。
- 方法区(Method Area):存储类信息、常量、静态变量等。
- 程序计数器(Program Counter):当前线程所执行的字节码的行号指示器。
- 虚拟机栈(VM Stack):每个方法执行时都会创建一个栈帧,用于存储局部变量和操作数栈。
- 本地方法栈(Native Method Stack):为Native方法服务。
类加载机制
- 加载(Loading):将.class文件加载到内存中。
- 验证(Verification):确保加载的类信息符合JVM规范。
- 准备(Preparation):为类的静态变量分配内存,并设置默认初始值。
- 解析(Resolution):将符号引用转换为直接引用。
- 初始化(Initialization):执行类构造器()方法。
按需加载
Java类的加载发生在首次主动使用该类时,这被称为"按需加载"。"主动使用"包括以下几种情况:
- 创建类的实例。
- 访问类中的静态字段(不是静态方法)。
- 调用类的静态方法。
- 使用 java.lang.reflect 包的方法主动引用类。
- 初始化某个类的子类。
- 其他一些隐式的引用,比如接口实现、异常处理等。
项目思考
项目中的工具类(没有被spring管理的),static代码块是否是项目启动时就执行的?
项目中使用到了RestTemplate,并封装了工具类,部分代码如下
RestUtil.java
java
public class RestUtil {
//代码省略
/**
* RestAPI 调用器
*/
private final static RestTemplate RT;
static {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(30000);
requestFactory.setReadTimeout(4000000);
RT = new RestTemplate(requestFactory);
// 解决乱码问题
RT.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
// 新增个性化日志
List<ClientHttpRequestInterceptor> interceptors = Optional.ofNullable(RT.getInterceptors()).orElse(new ArrayList<>());
// 优化直接获取bean 抛异常的写法
//获取自定义拦截器
Map<String, RestTemplateLogInterceptor> beans = SpringContextUtils.getApplicationContext().getBeansOfType(RestTemplateLogInterceptor.class);
if(!CollectionUtils.isEmpty(beans)){
beans.forEach((k, v) -> interceptors.add(v));
}
RT.setInterceptors(interceptors);
}
//代码省略
public static <T> ResponseEntity<T> request(String url, HttpMethod method, HttpHeaders headers, JSONObject variables, JSONObject params, Class<T> responseType) {
...
return RT.exchange(url, method, request, responseType);
}
}
答: 不是项目启动时执行的,是调用RestUtil.request 方法的时候执行的。
总结:上述代码并没有被spring管理,并且没有符合"按需加载"的所有条件,所以当项目启动完成后,RestUtil这个类并没有被JVM加载到内存里,也就意味着这个类并没有执行"类加载过程"。
测试验证
先建springboot项目结构如图
TestUtil.java
java
public class TestUtil {
private static List<String> testList = new ArrayList<>();
private static String name;
static {
for (int i = 0; i < 1000000; i++) {
testList.add("test" + i);
}
}
public static void setName(String name) {
TestUtil.name = name;
}
}
TestController.java
java
@RestController
public class TestController {
@GetMapping("/status")
public String status() {
TestUtil.setName("xxx");
return "ok";
}
}
代码说明:
- 工具类的setName方法只是为了把此类加载到内存中,来验证项目思考的内容。
接下来是使用jvisualvm工具对堆空间变化的截图
- 项目启动完成(这里设置了最大堆内存 -Xmx200m)
- 执行http://localhost:8080/status(就是为了调用工具类,让其执行类加载流程)。
从上述测试结果可以看出完全符合项目思考的内容。