前言
Spring Boot 應用程式的啟動過程涉及複雜的程式碼載入、編譯轉換和物件實例化機制。本文將詳細分析從 Java 原始碼到執行期物件的完整轉換流程,幫助開發者深入理解 Spring Boot 的底層運作原理。
Java 程式碼編譯與載入流程
編譯階段:從原始碼到位元組碼
graph TB
subgraph "編譯階段"
JAVA[Java 原始碼<br/>.java 檔案]
JAVAC[javac 編譯器]
CLASS[位元組碼檔案<br/>.class 檔案]
JAR[打包成 JAR<br/>.jar 檔案]
end
subgraph "載入階段"
CLASSLOADER[類別載入器<br/>ClassLoader]
JVM_MEMORY[JVM 記憶體區域]
METHOD_AREA[方法區<br/>Class 資訊]
HEAP[堆積記憶體<br/>物件實例]
end
JAVA --> JAVAC
JAVAC --> CLASS
CLASS --> JAR
JAR --> CLASSLOADER
CLASSLOADER --> JVM_MEMORY
JVM_MEMORY --> METHOD_AREA
JVM_MEMORY --> HEAP
Java 編譯過程詳解
1/**
2 * Java 編譯過程示例
3 * 從原始碼到位元組碼的轉換
4 */
5public class CompilationProcessDemo {
6
7 // 1. 原始碼階段:人類可讀的 Java 程式碼
8 private static final String GREETING = "Hello Spring Boot";
9 private final List<String> items = new ArrayList<>();
10
11 public CompilationProcessDemo() {
12 System.out.println("建構子被呼叫");
13 }
14
15 public void demonstrateMethod(String parameter) {
16 // 2. 語法分析:編譯器檢查語法正確性
17 String localVariable = "區域變數:" + parameter;
18
19 // 3. 語意分析:型別檢查、變數範圍檢查
20 items.add(localVariable);
21
22 // 4. 程式碼生成:產生對應的位元組碼指令
23 System.out.println(localVariable);
24 }
25
26 /*
27 * 對應的位元組碼(簡化版):
28 *
29 * public void demonstrateMethod(java.lang.String);
30 * Code:
31 * 0: new #7 // class java/lang/StringBuilder
32 * 3: dup
33 * 4: invokespecial #9 // Method java/lang/StringBuilder."<init>":()V
34 * 7: ldc #10 // String 區域變數:
35 * 9: invokevirtual #12 // Method StringBuilder.append:(Ljava/lang/String;)
36 * 12: aload_1
37 * 13: invokevirtual #12 // Method StringBuilder.append:(Ljava/lang/String;)
38 * 16: invokevirtual #16 // Method StringBuilder.toString:()Ljava/lang/String;
39 * 19: astore_2
40 * 20: aload_0
41 * 21: getfield #20 // Field items:Ljava/util/List;
42 * 24: aload_2
43 * 25: invokeinterface #22, 2 // InterfaceMethod List.add:(Ljava/lang/Object;)Z
44 * 30: pop
45 * 31: return
46 */
47}
類別載入器層次結構
1@Component
2public class ClassLoaderAnalyzer {
3
4 public void analyzeClassLoaderHierarchy() {
5 // 1. 啟動類別載入器(Bootstrap ClassLoader)
6 // 載入核心 Java 類別庫,由 C++ 實現
7 System.out.println("=== 類別載入器層次結構分析 ===");
8
9 Class<?> stringClass = String.class;
10 System.out.println("String 類別載入器:" +
11 (stringClass.getClassLoader() == null ? "Bootstrap ClassLoader" :
12 stringClass.getClassLoader().getClass().getName()));
13
14 // 2. 擴展類別載入器(Extension ClassLoader)
15 // 載入 Java 擴展目錄中的類別
16 Class<?> zipEntryClass = java.util.zip.ZipEntry.class;
17 System.out.println("ZipEntry 類別載入器:" +
18 zipEntryClass.getClassLoader().getClass().getName());
19
20 // 3. 應用程式類別載入器(Application ClassLoader)
21 // 載入用戶類路徑上的類別
22 Class<?> thisClass = this.getClass();
23 System.out.println("當前類別載入器:" +
24 thisClass.getClassLoader().getClass().getName());
25
26 // 4. 自訂類別載入器示例
27 CustomClassLoader customLoader = new CustomClassLoader();
28 System.out.println("自訂類別載入器:" +
29 customLoader.getClass().getName());
30
31 // 展示雙親委派機制
32 demonstrateParentDelegationModel();
33 }
34
35 private void demonstrateParentDelegationModel() {
36 System.out.println("\n=== 雙親委派機制示例 ===");
37
38 ClassLoader currentLoader = this.getClass().getClassLoader();
39 int level = 0;
40
41 while (currentLoader != null) {
42 System.out.println("層次 " + level + ": " +
43 currentLoader.getClass().getName());
44 currentLoader = currentLoader.getParent();
45 level++;
46 }
47
48 System.out.println("層次 " + level + ": Bootstrap ClassLoader (null)");
49 }
50
51 // 自訂類別載入器實現
52 public static class CustomClassLoader extends ClassLoader {
53
54 public CustomClassLoader() {
55 super(ClassLoaderAnalyzer.class.getClassLoader());
56 }
57
58 @Override
59 protected Class<?> findClass(String name) throws ClassNotFoundException {
60 System.out.println("自訂類別載入器載入類別:" + name);
61
62 // 在實際應用中,這裡會從特定位置載入類別檔案
63 // 例如:從網路、資料庫或加密檔案中載入
64
65 return super.findClass(name);
66 }
67
68 @Override
69 protected Class<?> loadClass(String name, boolean resolve)
70 throws ClassNotFoundException {
71
72 System.out.println("嘗試載入類別:" + name);
73
74 // 1. 檢查類別是否已經載入
75 Class<?> loadedClass = findLoadedClass(name);
76 if (loadedClass != null) {
77 System.out.println("類別已載入:" + name);
78 return loadedClass;
79 }
80
81 // 2. 雙親委派:先嘗試讓父載入器載入
82 if (!name.startsWith("com.example.custom")) {
83 return super.loadClass(name, resolve);
84 }
85
86 // 3. 自訂載入邏輯(僅針對特定包名)
87 try {
88 return findClass(name);
89 } catch (ClassNotFoundException e) {
90 return super.loadClass(name, resolve);
91 }
92 }
93 }
94}
Spring Boot 應用程式啟動流程
Spring Boot 啟動序列圖
sequenceDiagram
participant Main as 主方法
participant App as SpringApplication
participant Context as ApplicationContext
participant Scanner as ComponentScanner
participant Factory as BeanFactory
participant Loader as ClassLoader
Main->>App: SpringApplication.run()
App->>Context: 創建 ApplicationContext
Context->>Scanner: 掃描 @Component 註解
Scanner->>Loader: 載入類別檔案
Loader-->>Scanner: 返回 Class 物件
Scanner->>Factory: 註冊 Bean 定義
Factory->>Factory: 實例化 Bean
Factory->>Context: 注入依賴
Context-->>App: 啟動完成
App-->>Main: 返回 ApplicationContext
Spring Boot 主要啟動流程
1@SpringBootApplication
2public class SpringBootStartupAnalysis {
3
4 private static final Logger log = LoggerFactory.getLogger(SpringBootStartupAnalysis.class);
5
6 public static void main(String[] args) {
7 // 1. 創建 SpringApplication 實例
8 SpringApplication application = new SpringApplication(SpringBootStartupAnalysis.class);
9
10 // 2. 設定啟動監聽器,監控啟動過程
11 application.addListeners(new ApplicationStartupListener());
12
13 // 3. 執行 Spring Boot 應用程式
14 ConfigurableApplicationContext context = application.run(args);
15
16 // 4. 分析啟動完成後的狀態
17 analyzeApplicationContext(context);
18 }
19
20 private static void analyzeApplicationContext(ConfigurableApplicationContext context) {
21 log.info("=== Spring Boot 應用程式啟動完成分析 ===");
22
23 // 分析 Bean 工廠
24 ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
25 String[] beanNames = beanFactory.getBeanDefinitionNames();
26
27 log.info("已註冊的 Bean 數量:{}", beanNames.length);
28
29 // 分析不同類型的 Bean
30 Map<String, Long> beanTypeCount = Arrays.stream(beanNames)
31 .collect(Collectors.groupingBy(
32 beanName -> {
33 try {
34 Class<?> beanType = beanFactory.getType(beanName);
35 return beanType != null ? beanType.getPackage().getName() : "unknown";
36 } catch (Exception e) {
37 return "error";
38 }
39 },
40 Collectors.counting()
41 ));
42
43 log.info("Bean 類型分布:");
44 beanTypeCount.entrySet().stream()
45 .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
46 .limit(10)
47 .forEach(entry -> log.info(" {}: {} 個", entry.getKey(), entry.getValue()));
48 }
49
50 // 自訂啟動監聽器
51 public static class ApplicationStartupListener implements ApplicationListener<ApplicationEvent> {
52
53 @Override
54 public void onApplicationEvent(ApplicationEvent event) {
55 if (event instanceof ApplicationStartingEvent) {
56 log.info("應用程式開始啟動...");
57 } else if (event instanceof ApplicationEnvironmentPreparedEvent) {
58 log.info("環境準備完成");
59 } else if (event instanceof ApplicationContextInitializedEvent) {
60 log.info("ApplicationContext 初始化完成");
61 } else if (event instanceof ApplicationPreparedEvent) {
62 log.info("應用程式準備完成,開始載入 Bean");
63 } else if (event instanceof ApplicationStartedEvent) {
64 log.info("應用程式啟動完成");
65 } else if (event instanceof ApplicationReadyEvent) {
66 log.info("應用程式就緒,可接受請求");
67 }
68 }
69 }
70}
Bean 生命週期與實例化過程
1@Component
2public class BeanLifecycleDemo implements BeanNameAware, ApplicationContextAware,
3 InitializingBean, DisposableBean {
4
5 private static final Logger log = LoggerFactory.getLogger(BeanLifecycleDemo.class);
6
7 private String beanName;
8 private ApplicationContext applicationContext;
9
10 // 1. 建構子:物件實例化
11 public BeanLifecycleDemo() {
12 log.info("1. 建構子被呼叫:物件實例化");
13 }
14
15 // 2. 設定屬性值(依賴注入)
16 @Autowired
17 private Environment environment;
18
19 @Value("${server.port:8080}")
20 private int serverPort;
21
22 @PostConstruct
23 public void postConstruct() {
24 log.info("2. @PostConstruct 註解方法被呼叫");
25 log.info(" 伺服器埠號:{}", serverPort);
26 }
27
28 // 3. BeanNameAware 介面
29 @Override
30 public void setBeanName(String name) {
31 this.beanName = name;
32 log.info("3. BeanNameAware.setBeanName() 被呼叫,Bean 名稱:{}", name);
33 }
34
35 // 4. ApplicationContextAware 介面
36 @Override
37 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
38 this.applicationContext = applicationContext;
39 log.info("4. ApplicationContextAware.setApplicationContext() 被呼叫");
40 }
41
42 // 5. InitializingBean 介面
43 @Override
44 public void afterPropertiesSet() throws Exception {
45 log.info("5. InitializingBean.afterPropertiesSet() 被呼叫");
46 log.info(" 檢查所有屬性是否設定完成");
47 }
48
49 // 6. @Bean 方法的 initMethod(如果有的話)
50 public void customInitMethod() {
51 log.info("6. 自訂初始化方法被呼叫");
52 }
53
54 // 7. Bean 可以正常使用
55 public void doBusinessLogic() {
56 log.info("7. Bean 已準備就緒,可執行業務邏輯");
57 log.info(" ApplicationContext 中的 Bean 數量:{}",
58 applicationContext.getBeanDefinitionNames().length);
59 }
60
61 // 8. @PreDestroy 註解方法
62 @PreDestroy
63 public void preDestroy() {
64 log.info("8. @PreDestroy 註解方法被呼叫");
65 }
66
67 // 9. DisposableBean 介面
68 @Override
69 public void destroy() throws Exception {
70 log.info("9. DisposableBean.destroy() 被呼叫");
71 }
72
73 // 10. @Bean 方法的 destroyMethod(如果有的話)
74 public void customDestroyMethod() {
75 log.info("10. 自訂銷毀方法被呼叫");
76 }
77}
Spring Boot 自動配置機制
1@Configuration
2@EnableAutoConfiguration
3public class AutoConfigurationAnalyzer {
4
5 private static final Logger log = LoggerFactory.getLogger(AutoConfigurationAnalyzer.class);
6
7 @Bean
8 public CommandLineRunner analyzeAutoConfiguration(ApplicationContext context) {
9 return args -> {
10 log.info("=== Spring Boot 自動配置分析 ===");
11
12 // 1. 分析自動配置類別
13 analyzeAutoConfigurationClasses(context);
14
15 // 2. 分析條件註解
16 analyzeConditionalAnnotations(context);
17
18 // 3. 分析配置屬性
19 analyzeConfigurationProperties(context);
20 };
21 }
22
23 private void analyzeAutoConfigurationClasses(ApplicationContext context) {
24 log.info("分析自動配置類別...");
25
26 // 獲取所有自動配置類別
27 String[] beanNames = context.getBeanDefinitionNames();
28 List<String> autoConfigClasses = Arrays.stream(beanNames)
29 .filter(name -> {
30 try {
31 Class<?> beanClass = context.getType(name);
32 return beanClass != null &&
33 beanClass.getName().contains("AutoConfiguration");
34 } catch (Exception e) {
35 return false;
36 }
37 })
38 .collect(Collectors.toList());
39
40 log.info("發現 {} 個自動配置類別", autoConfigClasses.size());
41 autoConfigClasses.stream()
42 .limit(10) // 顯示前 10 個
43 .forEach(className -> log.info(" - {}", className));
44 }
45
46 private void analyzeConditionalAnnotations(ApplicationContext context) {
47 log.info("分析條件註解...");
48
49 // 這裡可以實作條件註解的分析邏輯
50 // 例如分析 @ConditionalOnClass, @ConditionalOnMissingBean 等
51 }
52
53 private void analyzeConfigurationProperties(ApplicationContext context) {
54 log.info("分析配置屬性...");
55
56 try {
57 // 獲取環境物件
58 Environment environment = context.getEnvironment();
59
60 if (environment instanceof ConfigurableEnvironment) {
61 ConfigurableEnvironment configurableEnv = (ConfigurableEnvironment) environment;
62
63 log.info("作用中的 Profile:{}",
64 Arrays.toString(configurableEnv.getActiveProfiles()));
65
66 // 分析屬性來源
67 MutablePropertySources propertySources = configurableEnv.getPropertySources();
68 log.info("屬性來源數量:{}", propertySources.size());
69
70 propertySources.forEach(propertySource ->
71 log.info(" - {} ({})", propertySource.getName(),
72 propertySource.getClass().getSimpleName())
73 );
74 }
75 } catch (Exception e) {
76 log.error("分析配置屬性時發生錯誤", e);
77 }
78 }
79}
依賴注入與物件實例化
依賴注入類型與實現
1@Service
2public class DependencyInjectionDemo {
3
4 private static final Logger log = LoggerFactory.getLogger(DependencyInjectionDemo.class);
5
6 // 1. 建構子注入(推薦方式)
7 private final UserService userService;
8 private final EmailService emailService;
9
10 public DependencyInjectionDemo(UserService userService, EmailService emailService) {
11 this.userService = userService;
12 this.emailService = emailService;
13 log.info("建構子注入完成");
14 }
15
16 // 2. Setter 注入
17 private NotificationService notificationService;
18
19 @Autowired
20 public void setNotificationService(NotificationService notificationService) {
21 this.notificationService = notificationService;
22 log.info("Setter 注入:NotificationService");
23 }
24
25 // 3. 欄位注入(不推薦)
26 @Autowired
27 private CacheService cacheService;
28
29 // 4. 方法注入
30 private AuditService auditService;
31 private LoggingService loggingService;
32
33 @Autowired
34 public void configureServices(AuditService auditService, LoggingService loggingService) {
35 this.auditService = auditService;
36 this.loggingService = loggingService;
37 log.info("方法注入:AuditService, LoggingService");
38 }
39
40 // 5. 條件性注入
41 @Autowired(required = false)
42 private Optional<MetricsService> metricsService;
43
44 // 6. 集合注入
45 @Autowired
46 private List<MessageProcessor> messageProcessors;
47
48 @Autowired
49 private Map<String, PaymentProcessor> paymentProcessors;
50
51 public void demonstrateInjection() {
52 log.info("=== 依賴注入示例 ===");
53
54 // 展示各種注入的服務
55 log.info("UserService:{}", userService != null ? "已注入" : "未注入");
56 log.info("EmailService:{}", emailService != null ? "已注入" : "未注入");
57 log.info("NotificationService:{}", notificationService != null ? "已注入" : "未注入");
58 log.info("CacheService:{}", cacheService != null ? "已注入" : "未注入");
59 log.info("AuditService:{}", auditService != null ? "已注入" : "未注入");
60 log.info("LoggingService:{}", loggingService != null ? "已注入" : "未注入");
61
62 // 條件性注入檢查
63 log.info("MetricsService:{}", metricsService.isPresent() ? "已注入" : "未注入");
64
65 // 集合注入檢查
66 log.info("MessageProcessor 數量:{}", messageProcessors.size());
67 messageProcessors.forEach(processor ->
68 log.info(" - {}", processor.getClass().getSimpleName())
69 );
70
71 log.info("PaymentProcessor 數量:{}", paymentProcessors.size());
72 paymentProcessors.forEach((name, processor) ->
73 log.info(" - {}: {}", name, processor.getClass().getSimpleName())
74 );
75 }
76
77 // 服務介面定義
78 public interface UserService { }
79 public interface EmailService { }
80 public interface NotificationService { }
81 public interface CacheService { }
82 public interface AuditService { }
83 public interface LoggingService { }
84 public interface MetricsService { }
85 public interface MessageProcessor { }
86 public interface PaymentProcessor { }
87}
88
89// 服務實現類別
90@Service
91class UserServiceImpl implements DependencyInjectionDemo.UserService {
92 private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
93
94 public UserServiceImpl() {
95 log.info("UserServiceImpl 實例化");
96 }
97}
98
99@Service
100class EmailServiceImpl implements DependencyInjectionDemo.EmailService {
101 private static final Logger log = LoggerFactory.getLogger(EmailServiceImpl.class);
102
103 public EmailServiceImpl() {
104 log.info("EmailServiceImpl 實例化");
105 }
106}
107
108@Service
109class NotificationServiceImpl implements DependencyInjectionDemo.NotificationService {
110 private static final Logger log = LoggerFactory.getLogger(NotificationServiceImpl.class);
111
112 public NotificationServiceImpl() {
113 log.info("NotificationServiceImpl 實例化");
114 }
115}
Bean 作用域與生命週期管理
1@Configuration
2public class BeanScopeConfiguration {
3
4 private static final Logger log = LoggerFactory.getLogger(BeanScopeConfiguration.class);
5
6 // 1. Singleton 作用域(預設)
7 @Bean
8 @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
9 public SingletonService singletonService() {
10 log.info("創建 SingletonService 實例");
11 return new SingletonService();
12 }
13
14 // 2. Prototype 作用域
15 @Bean
16 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
17 public PrototypeService prototypeService() {
18 log.info("創建 PrototypeService 實例");
19 return new PrototypeService();
20 }
21
22 // 3. Request 作用域(Web 應用程式)
23 @Bean
24 @Scope(WebApplicationContext.SCOPE_REQUEST)
25 public RequestScopeService requestScopeService() {
26 log.info("創建 RequestScopeService 實例");
27 return new RequestScopeService();
28 }
29
30 // 4. Session 作用域(Web 應用程式)
31 @Bean
32 @Scope(WebApplicationContext.SCOPE_SESSION)
33 public SessionScopeService sessionScopeService() {
34 log.info("創建 SessionScopeService 實例");
35 return new SessionScopeService();
36 }
37
38 // 5. Application 作用域(Web 應用程式)
39 @Bean
40 @Scope(WebApplicationContext.SCOPE_APPLICATION)
41 public ApplicationScopeService applicationScopeService() {
42 log.info("創建 ApplicationScopeService 實例");
43 return new ApplicationScopeService();
44 }
45
46 // 6. 自訂作用域
47 @Bean
48 @Scope("customScope")
49 public CustomScopeService customScopeService() {
50 log.info("創建 CustomScopeService 實例");
51 return new CustomScopeService();
52 }
53
54 // 服務類別定義
55 public static class SingletonService {
56 private final String instanceId = UUID.randomUUID().toString();
57
58 public String getInstanceId() { return instanceId; }
59 }
60
61 public static class PrototypeService {
62 private final String instanceId = UUID.randomUUID().toString();
63 private final LocalDateTime createTime = LocalDateTime.now();
64
65 public String getInstanceId() { return instanceId; }
66 public LocalDateTime getCreateTime() { return createTime; }
67 }
68
69 public static class RequestScopeService {
70 private final String requestId = UUID.randomUUID().toString();
71
72 public String getRequestId() { return requestId; }
73 }
74
75 public static class SessionScopeService {
76 private final String sessionId = UUID.randomUUID().toString();
77
78 public String getSessionId() { return sessionId; }
79 }
80
81 public static class ApplicationScopeService {
82 private final String applicationId = UUID.randomUUID().toString();
83
84 public String getApplicationId() { return applicationId; }
85 }
86
87 public static class CustomScopeService {
88 private final String customId = UUID.randomUUID().toString();
89
90 public String getCustomId() { return customId; }
91 }
92}
93
94// Bean 作用域測試控制器
95@RestController
96@RequestMapping("/api/scope")
97public class BeanScopeTestController {
98
99 private final ApplicationContext applicationContext;
100
101 public BeanScopeTestController(ApplicationContext applicationContext) {
102 this.applicationContext = applicationContext;
103 }
104
105 @GetMapping("/test")
106 public ResponseEntity<Map<String, Object>> testBeanScopes() {
107 Map<String, Object> result = new HashMap<>();
108
109 // 測試 Singleton 作用域
110 BeanScopeConfiguration.SingletonService singleton1 =
111 applicationContext.getBean(BeanScopeConfiguration.SingletonService.class);
112 BeanScopeConfiguration.SingletonService singleton2 =
113 applicationContext.getBean(BeanScopeConfiguration.SingletonService.class);
114
115 result.put("singleton1_id", singleton1.getInstanceId());
116 result.put("singleton2_id", singleton2.getInstanceId());
117 result.put("singleton_same_instance", singleton1 == singleton2);
118
119 // 測試 Prototype 作用域
120 BeanScopeConfiguration.PrototypeService prototype1 =
121 applicationContext.getBean(BeanScopeConfiguration.PrototypeService.class);
122 BeanScopeConfiguration.PrototypeService prototype2 =
123 applicationContext.getBean(BeanScopeConfiguration.PrototypeService.class);
124
125 result.put("prototype1_id", prototype1.getInstanceId());
126 result.put("prototype2_id", prototype2.getInstanceId());
127 result.put("prototype_same_instance", prototype1 == prototype2);
128
129 return ResponseEntity.ok(result);
130 }
131}
記憶體中的物件組織
Spring Boot 應用程式記憶體佈局
1@Component
2public class MemoryLayoutAnalyzer {
3
4 private static final Logger log = LoggerFactory.getLogger(MemoryLayoutAnalyzer.class);
5
6 @Autowired
7 private ApplicationContext applicationContext;
8
9 @EventListener(ApplicationReadyEvent.class)
10 public void analyzeMemoryLayout() {
11 log.info("=== Spring Boot 應用程式記憶體佈局分析 ===");
12
13 // 1. 分析 ApplicationContext 記憶體佔用
14 analyzeApplicationContextMemory();
15
16 // 2. 分析 Bean 實例記憶體分佈
17 analyzeBeanInstanceMemory();
18
19 // 3. 分析類別載入器記憶體
20 analyzeClassLoaderMemory();
21
22 // 4. 分析字串常數池
23 analyzeStringPool();
24 }
25
26 private void analyzeApplicationContextMemory() {
27 log.info("分析 ApplicationContext 記憶體佔用...");
28
29 if (applicationContext instanceof ConfigurableApplicationContext) {
30 ConfigurableApplicationContext configurableContext =
31 (ConfigurableApplicationContext) applicationContext;
32
33 ConfigurableListableBeanFactory beanFactory = configurableContext.getBeanFactory();
34 String[] beanNames = beanFactory.getBeanDefinitionNames();
35
36 log.info("Bean 定義數量:{}", beanNames.length);
37
38 // 分析 Bean 定義記憶體佔用(估算)
39 int avgBeanDefinitionSize = 1024; // 假設每個 Bean 定義平均佔用 1KB
40 long estimatedBeanDefinitionMemory = beanNames.length * avgBeanDefinitionSize;
41
42 log.info("估算 Bean 定義記憶體佔用:{} KB",
43 estimatedBeanDefinitionMemory / 1024);
44 }
45 }
46
47 private void analyzeBeanInstanceMemory() {
48 log.info("分析 Bean 實例記憶體分佈...");
49
50 String[] beanNames = applicationContext.getBeanDefinitionNames();
51 Map<String, Integer> packageBeanCount = new HashMap<>();
52
53 for (String beanName : beanNames) {
54 try {
55 Class<?> beanType = applicationContext.getType(beanName);
56 if (beanType != null) {
57 String packageName = beanType.getPackage() != null ?
58 beanType.getPackage().getName() : "default";
59
60 packageBeanCount.merge(packageName, 1, Integer::sum);
61
62 // 分析特定 Bean 的記憶體位置
63 Object bean = applicationContext.getBean(beanName);
64 int identityHashCode = System.identityHashCode(bean);
65
66 if (beanName.contains("Controller") || beanName.contains("Service")) {
67 log.debug("Bean: {}, 類型: {}, 記憶體位置: {}",
68 beanName, beanType.getSimpleName(), identityHashCode);
69 }
70 }
71 } catch (Exception e) {
72 // 忽略無法獲取的 Bean
73 }
74 }
75
76 // 顯示 Bean 套件分佈
77 log.info("Bean 套件分佈:");
78 packageBeanCount.entrySet().stream()
79 .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
80 .limit(10)
81 .forEach(entry ->
82 log.info(" {}: {} 個", entry.getKey(), entry.getValue())
83 );
84 }
85
86 private void analyzeClassLoaderMemory() {
87 log.info("分析類別載入器記憶體...");
88
89 ClassLoader classLoader = this.getClass().getClassLoader();
90 log.info("目前類別載入器:{}", classLoader.getClass().getName());
91
92 // 嘗試獲取載入的類別數量(這在不同 JVM 實作中可能不同)
93 try {
94 if (classLoader instanceof URLClassLoader) {
95 URLClassLoader urlClassLoader = (URLClassLoader) classLoader;
96 URL[] urls = urlClassLoader.getURLs();
97 log.info("類別載入路徑數量:{}", urls.length);
98
99 for (URL url : urls) {
100 log.debug("類別載入路徑:{}", url.toString());
101 }
102 }
103 } catch (Exception e) {
104 log.debug("無法分析類別載入器詳細資訊:{}", e.getMessage());
105 }
106 }
107
108 private void analyzeStringPool() {
109 log.info("分析字串常數池...");
110
111 // 演示字串常數池的作用
112 String literal1 = "Spring Boot 應用程式";
113 String literal2 = "Spring Boot 應用程式";
114 String constructed = new String("Spring Boot 應用程式");
115
116 log.info("字串字面量 1 記憶體位置:{}", System.identityHashCode(literal1));
117 log.info("字串字面量 2 記憶體位置:{}", System.identityHashCode(literal2));
118 log.info("建構字串記憶體位置:{}", System.identityHashCode(constructed));
119 log.info("字串字面量是否相同實例:{}", literal1 == literal2);
120 log.info("字面量與建構字串是否相同實例:{}", literal1 == constructed);
121
122 // intern() 方法示例
123 String interned = constructed.intern();
124 log.info("intern() 後的字串記憶體位置:{}", System.identityHashCode(interned));
125 log.info("intern() 字串與字面量是否相同實例:{}", literal1 == interned);
126 }
127}
物件引用關係與記憶體管理
1@Service
2public class ObjectReferenceManager {
3
4 private static final Logger log = LoggerFactory.getLogger(ObjectReferenceManager.class);
5
6 // 強引用:正常的物件引用,防止 GC
7 private final List<User> activeUsers = new ArrayList<>();
8
9 // 軟引用:記憶體不足時可能被 GC
10 private final Map<String, SoftReference<User>> cachedUsers = new ConcurrentHashMap<>();
11
12 // 弱引用:下次 GC 時會被回收
13 private final Map<String, WeakReference<User>> temporaryUsers = new ConcurrentHashMap<>();
14
15 // 虛引用:用於追蹤物件被 GC 的時機
16 private final ReferenceQueue<User> userGCQueue = new ReferenceQueue<>();
17 private final Map<String, PhantomReference<User>> phantomUsers = new ConcurrentHashMap<>();
18
19 public void demonstrateReferenceTypes() {
20 log.info("=== 物件引用類型示例 ===");
21
22 User user = new User("張三", "zhang@example.com");
23
24 // 1. 強引用
25 activeUsers.add(user);
26 log.info("添加強引用,使用者:{}", user.getName());
27
28 // 2. 軟引用
29 String userId = user.getId();
30 cachedUsers.put(userId, new SoftReference<>(user));
31 log.info("添加軟引用,使用者 ID:{}", userId);
32
33 // 3. 弱引用
34 temporaryUsers.put(userId, new WeakReference<>(user));
35 log.info("添加弱引用,使用者 ID:{}", userId);
36
37 // 4. 虛引用
38 phantomUsers.put(userId, new PhantomReference<>(user, userGCQueue));
39 log.info("添加虛引用,使用者 ID:{}", userId);
40
41 // 測試引用的行為
42 testReferencesBehavior(userId);
43 }
44
45 private void testReferencesBehavior(String userId) {
46 log.info("測試引用行為...");
47
48 // 檢查各種引用是否還有效
49 log.info("強引用數量:{}", activeUsers.size());
50
51 SoftReference<User> softRef = cachedUsers.get(userId);
52 log.info("軟引用是否有效:{}", softRef != null && softRef.get() != null);
53
54 WeakReference<User> weakRef = temporaryUsers.get(userId);
55 log.info("弱引用是否有效:{}", weakRef != null && weakRef.get() != null);
56
57 // 強制 GC 測試弱引用
58 log.info("執行垃圾回收...");
59 System.gc();
60
61 try {
62 Thread.sleep(100); // 等待 GC 完成
63 } catch (InterruptedException e) {
64 Thread.currentThread().interrupt();
65 }
66
67 log.info("GC 後軟引用是否有效:{}", softRef != null && softRef.get() != null);
68 log.info("GC 後弱引用是否有效:{}", weakRef != null && weakRef.get() != null);
69
70 // 檢查虛引用佇列
71 checkPhantomReferenceQueue();
72 }
73
74 private void checkPhantomReferenceQueue() {
75 Reference<? extends User> phantomRef;
76 while ((phantomRef = userGCQueue.poll()) != null) {
77 log.info("檢測到物件被 GC:虛引用 {}", phantomRef);
78 // 在這裡可以執行清理工作
79 }
80 }
81
82 @Scheduled(fixedRate = 30000) // 每 30 秒執行一次
83 public void cleanupWeakReferences() {
84 // 清理無效的弱引用
85 temporaryUsers.entrySet().removeIf(entry -> {
86 WeakReference<User> ref = entry.getValue();
87 if (ref.get() == null) {
88 log.debug("清理無效的弱引用:{}", entry.getKey());
89 return true;
90 }
91 return false;
92 });
93
94 // 清理無效的軟引用
95 cachedUsers.entrySet().removeIf(entry -> {
96 SoftReference<User> ref = entry.getValue();
97 if (ref.get() == null) {
98 log.debug("清理無效的軟引用:{}", entry.getKey());
99 return true;
100 }
101 return false;
102 });
103
104 log.info("引用清理完成,當前狀態 - 軟引用:{},弱引用:{}",
105 cachedUsers.size(), temporaryUsers.size());
106 }
107
108 // 使用者實體類別
109 public static class User {
110 private final String id;
111 private final String name;
112 private final String email;
113 private final LocalDateTime createTime;
114
115 public User(String name, String email) {
116 this.id = UUID.randomUUID().toString();
117 this.name = name;
118 this.email = email;
119 this.createTime = LocalDateTime.now();
120 }
121
122 // Getter 方法
123 public String getId() { return id; }
124 public String getName() { return name; }
125 public String getEmail() { return email; }
126 public LocalDateTime getCreateTime() { return createTime; }
127
128 @Override
129 public String toString() {
130 return String.format("User{id='%s', name='%s', email='%s'}",
131 id, name, email);
132 }
133
134 @Override
135 protected void finalize() throws Throwable {
136 super.finalize();
137 System.out.println("User 物件被 GC:" + this.name);
138 }
139 }
140}
效能監控與分析
Spring Boot 應用程式記憶體監控
1@RestController
2@RequestMapping("/api/memory")
3public class MemoryMonitoringController {
4
5 private static final Logger log = LoggerFactory.getLogger(MemoryMonitoringController.class);
6
7 @Autowired
8 private ApplicationContext applicationContext;
9
10 @GetMapping("/spring-context")
11 public ResponseEntity<SpringContextMemoryInfo> getSpringContextMemoryInfo() {
12
13 long startTime = System.nanoTime();
14
15 // 分析 Spring Context 記憶體使用
16 ConfigurableListableBeanFactory beanFactory =
17 ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
18
19 String[] beanNames = beanFactory.getBeanDefinitionNames();
20 int singletonCount = 0;
21 int prototypeCount = 0;
22 Map<String, Integer> scopeCount = new HashMap<>();
23
24 for (String beanName : beanNames) {
25 try {
26 BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
27 String scope = beanDefinition.getScope();
28
29 if (scope.isEmpty() || BeanDefinition.SCOPE_SINGLETON.equals(scope)) {
30 singletonCount++;
31 } else if (BeanDefinition.SCOPE_PROTOTYPE.equals(scope)) {
32 prototypeCount++;
33 }
34
35 scopeCount.merge(scope.isEmpty() ? "singleton" : scope, 1, Integer::sum);
36
37 } catch (Exception e) {
38 // 忽略無法獲取定義的 Bean
39 }
40 }
41
42 long analysisTime = System.nanoTime() - startTime;
43
44 SpringContextMemoryInfo info = SpringContextMemoryInfo.builder()
45 .totalBeans(beanNames.length)
46 .singletonBeans(singletonCount)
47 .prototypeBeans(prototypeCount)
48 .scopeDistribution(scopeCount)
49 .analysisTimeNanos(analysisTime)
50 .timestamp(LocalDateTime.now())
51 .build();
52
53 return ResponseEntity.ok(info);
54 }
55
56 @GetMapping("/class-loading")
57 public ResponseEntity<ClassLoadingInfo> getClassLoadingInfo() {
58
59 ClassLoadingMXBean classLoadingBean = ManagementFactory.getClassLoadingMXBean();
60
61 ClassLoadingInfo info = ClassLoadingInfo.builder()
62 .loadedClassCount(classLoadingBean.getLoadedClassCount())
63 .totalLoadedClassCount(classLoadingBean.getTotalLoadedClassCount())
64 .unloadedClassCount(classLoadingBean.getUnloadedClassCount())
65 .verbose(classLoadingBean.isVerbose())
66 .build();
67
68 return ResponseEntity.ok(info);
69 }
70
71 @GetMapping("/object-statistics")
72 public ResponseEntity<ObjectStatistics> getObjectStatistics() {
73
74 Runtime runtime = Runtime.getRuntime();
75 MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
76
77 // 執行 GC 前後的記憶體使用情況
78 long beforeGC = runtime.totalMemory() - runtime.freeMemory();
79
80 // 建議 GC(實際執行由 JVM 決定)
81 runtime.gc();
82
83 try {
84 Thread.sleep(100); // 等待 GC 完成
85 } catch (InterruptedException e) {
86 Thread.currentThread().interrupt();
87 }
88
89 long afterGC = runtime.totalMemory() - runtime.freeMemory();
90
91 // 堆積記憶體詳細資訊
92 MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
93 MemoryUsage nonHeapUsage = memoryBean.getNonHeapMemoryUsage();
94
95 ObjectStatistics stats = ObjectStatistics.builder()
96 .heapUsed(heapUsage.getUsed())
97 .heapCommitted(heapUsage.getCommitted())
98 .heapMax(heapUsage.getMax())
99 .nonHeapUsed(nonHeapUsage.getUsed())
100 .nonHeapCommitted(nonHeapUsage.getCommitted())
101 .nonHeapMax(nonHeapUsage.getMax())
102 .memoryBeforeGC(beforeGC)
103 .memoryAfterGC(afterGC)
104 .memoryFreedByGC(beforeGC - afterGC)
105 .build();
106
107 return ResponseEntity.ok(stats);
108 }
109
110 // 響應物件定義
111 @Builder
112 @Data
113 public static class SpringContextMemoryInfo {
114 private int totalBeans;
115 private int singletonBeans;
116 private int prototypeBeans;
117 private Map<String, Integer> scopeDistribution;
118 private long analysisTimeNanos;
119 private LocalDateTime timestamp;
120 }
121
122 @Builder
123 @Data
124 public static class ClassLoadingInfo {
125 private int loadedClassCount;
126 private long totalLoadedClassCount;
127 private long unloadedClassCount;
128 private boolean verbose;
129 }
130
131 @Builder
132 @Data
133 public static class ObjectStatistics {
134 private long heapUsed;
135 private long heapCommitted;
136 private long heapMax;
137 private long nonHeapUsed;
138 private long nonHeapCommitted;
139 private long nonHeapMax;
140 private long memoryBeforeGC;
141 private long memoryAfterGC;
142 private long memoryFreedByGC;
143 }
144}
總結
Spring Boot 應用程式的程式碼載入與物件實例化是一個複雜而精密的過程,涉及多個層面:
關鍵流程總結
- 編譯階段:Java 原始碼 → 位元組碼 → JAR 檔案
- 載入階段:類別載入器層次結構與雙親委派機制
- 實例化階段:Spring Bean 生命週期與依賴注入
- 記憶體管理:物件在堆積記憶體中的組織與引用關係
效能最佳化建議
- 類別載入最佳化:減少不必要的類別載入,使用懶載入策略
- Bean 作用域選擇:根據需求選擇適當的 Bean 作用域
- 記憶體引用管理:合理使用軟引用、弱引用避免記憶體洩漏
- 監控與分析:定期監控應用程式記憶體使用情況
開發最佳實踐
- 優先使用建構子注入而非欄位注入
- 適當使用
@Lazy
註解延遲 Bean 初始化 - 避免循環依賴,設計清晰的依賴關係
- 實作適當的生命週期回調方法
透過深入理解這些機制,開發者能夠構建更高效、更穩定的 Spring Boot 應用程式。