|
@@ -1,152 +0,0 @@
|
|
|
-package com.jeeplus.modules.centerservice.config;
|
|
|
-
|
|
|
-import javax.servlet.ServletContextEvent;
|
|
|
-import javax.servlet.ServletContextListener;
|
|
|
-import javax.servlet.annotation.WebListener;
|
|
|
-import java.lang.management.ManagementFactory;
|
|
|
-import java.lang.management.MemoryPoolMXBean;
|
|
|
-import java.lang.management.MemoryUsage;
|
|
|
-import java.time.LocalDateTime;
|
|
|
-import java.time.LocalTime;
|
|
|
-import java.time.temporal.ChronoUnit;
|
|
|
-import java.util.List;
|
|
|
-import java.util.concurrent.Executors;
|
|
|
-import java.util.concurrent.ScheduledExecutorService;
|
|
|
-import java.util.concurrent.TimeUnit;
|
|
|
-
|
|
|
-@WebListener
|
|
|
-public class GcServletListener implements ServletContextListener {
|
|
|
-
|
|
|
- private ScheduledExecutorService scheduler;
|
|
|
-
|
|
|
- @Override
|
|
|
- public void contextInitialized(ServletContextEvent sce) {
|
|
|
- scheduler = Executors.newScheduledThreadPool(1);
|
|
|
- System.out.println("✅ GC 任务已启动");
|
|
|
-
|
|
|
- long initialDelay = getInitialDelay();
|
|
|
- System.out.println("⏳ GC 第一次运行时间:" + LocalDateTime.now().plusSeconds(initialDelay));
|
|
|
-
|
|
|
- scheduler.scheduleAtFixedRate(() -> {
|
|
|
- LocalTime now = LocalTime.now();
|
|
|
-
|
|
|
- // 仅在凌晨 2:00 - 5:00 之间执行 GC
|
|
|
- if (now.isBefore(LocalTime.of(16, 45)) || now.isAfter(LocalTime.of(20, 0))) {
|
|
|
- System.out.println("⏸️ 当前时间不在 02:00 - 05:00 之间,跳过 GC");
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- System.out.println("🔄 GC 任务执行时间:" + LocalDateTime.now());
|
|
|
- printMemoryUsage("GC 前内存状态");
|
|
|
-
|
|
|
- // 1. 创建老年代对象(通过长期存活对象)
|
|
|
- System.out.println("创建老年代对象...");
|
|
|
- List<byte[]> oldGenObjects = new java.util.ArrayList<>();
|
|
|
- for (int i = 0; i < 6; i++) {
|
|
|
- byte[] data = new byte[500 * 1024 * 1024]; // 分配 500MB 对象
|
|
|
- oldGenObjects.add(data);
|
|
|
- System.gc(); // 触发 Minor GC,让对象进入老年代
|
|
|
- try {
|
|
|
- Thread.sleep(100);
|
|
|
- } catch (InterruptedException e) {
|
|
|
- System.out.println("创建老年代对象存在错误");
|
|
|
- throw new RuntimeException(e);
|
|
|
- }
|
|
|
- }
|
|
|
- System.out.println("执行完了创建老年代对象");
|
|
|
- oldGenObjects.clear(); // 清除引用
|
|
|
- oldGenObjects = null; // 释放列表引用
|
|
|
-
|
|
|
-
|
|
|
- // 2. 手动触发 Full GC
|
|
|
- triggerFullGcAndShrink();
|
|
|
-
|
|
|
- // 3. 打印内存使用情况
|
|
|
- printMemoryUsage("Full GC 后");
|
|
|
-
|
|
|
- // 优化 GC 触发逻辑
|
|
|
- for (int i = 1; i <= 3; i++) { // 减少循环次数(原 8 次过多)
|
|
|
- System.gc();
|
|
|
- System.runFinalization();
|
|
|
-
|
|
|
- // 分配并立即释放大内存块,尝试触发堆内存收缩
|
|
|
- try {
|
|
|
- byte[] memoryHog = new byte[500 * 1024 * 1024]; // 分配 100MB
|
|
|
- memoryHog = null; // 释放引用
|
|
|
- } catch (OutOfMemoryError e) {
|
|
|
- System.out.println("⚠️ 内存不足,跳过分配测试块");
|
|
|
- }
|
|
|
-
|
|
|
- System.out.println("🚀 GC 执行第 " + i + " 次");
|
|
|
- printMemoryUsage("本次 GC 后内存状态");
|
|
|
-
|
|
|
- try {
|
|
|
- Thread.sleep(3000); // 延长间隔(原 1 秒过短)
|
|
|
- } catch (InterruptedException e) {
|
|
|
- e.printStackTrace();
|
|
|
- }
|
|
|
- }
|
|
|
- }, initialDelay, 3 * 60, TimeUnit.SECONDS); // 每 10 分钟运行一次
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 强制触发 Full GC 并尝试释放内存给操作系统
|
|
|
- */
|
|
|
- private static void triggerFullGcAndShrink() {
|
|
|
- System.out.println("触发 Full GC...");
|
|
|
- System.gc(); // 建议 JVM 执行 GC(不保证立即执行)
|
|
|
- System.runFinalization();
|
|
|
-
|
|
|
- // 分配并释放大内存块,促使 JVM 释放内存
|
|
|
- try {
|
|
|
- byte[] memoryHog = new byte[1024 * 1024 * 500]; // 分配 500MB
|
|
|
- memoryHog = null;
|
|
|
- } catch (OutOfMemoryError e) {
|
|
|
- System.out.println("内存不足,跳过分配测试块");
|
|
|
- }
|
|
|
-
|
|
|
- System.gc(); // 再次触发 GC
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void contextDestroyed(ServletContextEvent sce) {
|
|
|
- if (scheduler != null) {
|
|
|
- System.out.println("🛑 GC 任务已关闭");
|
|
|
- scheduler.shutdown();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 计算当前时间到最近的 01:50 的秒数
|
|
|
- */
|
|
|
- private long getInitialDelay() {
|
|
|
- LocalTime now = LocalTime.now();
|
|
|
- LocalTime nextRunTime = LocalTime.of(1, 50);
|
|
|
-
|
|
|
- if (now.isBefore(nextRunTime)) {
|
|
|
- return ChronoUnit.SECONDS.between(now, nextRunTime);
|
|
|
- } else {
|
|
|
- // 如果已经过了 02:00,则计算到次日 02:00 的时间
|
|
|
- return ChronoUnit.SECONDS.between(now, nextRunTime.plusHours(24));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 打印老年代内存使用情况
|
|
|
- */
|
|
|
- private void printMemoryUsage(String phase) {
|
|
|
- for (MemoryPoolMXBean pool : ManagementFactory.getMemoryPoolMXBeans()) {
|
|
|
- if (pool.getName().toLowerCase().contains("old") || pool.getName().toLowerCase().contains("tenured")) {
|
|
|
- MemoryUsage usage = pool.getUsage();
|
|
|
- System.out.printf("%s - %s: Used=%dMB, Committed=%dMB, Max=%dMB%n",
|
|
|
- phase, pool.getName(), usage.getUsed() / 1024 / 1024,
|
|
|
- usage.getCommitted() / 1024 / 1024, usage.getMax() / 1024 / 1024);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|