Spring Boot 程式碼載入深度解析:從編譯到物件實例化完整流程

前言

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 應用程式的程式碼載入與物件實例化是一個複雜而精密的過程,涉及多個層面:

關鍵流程總結

  1. 編譯階段:Java 原始碼 → 位元組碼 → JAR 檔案
  2. 載入階段:類別載入器層次結構與雙親委派機制
  3. 實例化階段:Spring Bean 生命週期與依賴注入
  4. 記憶體管理:物件在堆積記憶體中的組織與引用關係

效能最佳化建議

  • 類別載入最佳化:減少不必要的類別載入,使用懶載入策略
  • Bean 作用域選擇:根據需求選擇適當的 Bean 作用域
  • 記憶體引用管理:合理使用軟引用、弱引用避免記憶體洩漏
  • 監控與分析:定期監控應用程式記憶體使用情況

開發最佳實踐

  • 優先使用建構子注入而非欄位注入
  • 適當使用 @Lazy 註解延遲 Bean 初始化
  • 避免循環依賴,設計清晰的依賴關係
  • 實作適當的生命週期回調方法

透過深入理解這些機制,開發者能夠構建更高效、更穩定的 Spring Boot 應用程式。