|
@@ -0,0 +1,152 @@
|
|
|
+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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|