Browse Source

char占用堆内存且长期不释放优化调整

user5 2 months ago
parent
commit
07c70ced26

+ 55 - 30
src/main/java/com/jeeplus/common/utils/IdcardUtils.java

@@ -312,21 +312,33 @@ public class IdcardUtils extends StringUtils {
 	 * @return 验证码是否符合
 	 */
 	public static boolean validateTWCard(String idCard) {
+		if (idCard == null || idCard.length() != 10) {
+			return false;
+		}
+
 		String start = idCard.substring(0, 1);
 		String mid = idCard.substring(1, 9);
-		String end = idCard.substring(9, 10);
+		char end = idCard.charAt(9);  // 直接获取字符,避免创建 `String`
+
 		Integer iStart = twFirstCode.get(start);
-		Integer sum = iStart / 10 + (iStart % 10) * 9;
+		if (iStart == null) {
+			return false;  // 避免 `NullPointerException`
+		}
+
+		int sum = iStart / 10 + (iStart % 10) * 9;
 		char[] chars = mid.toCharArray();
-		Integer iflag = 8;
+		int iflag = 8;
+
 		for (char c : chars) {
-			sum = sum + Integer.valueOf(c + "") * iflag;
+			sum += Character.getNumericValue(c) * iflag;  // 直接转换字符为数字
 			iflag--;
 		}
-		return (sum % 10 == 0 ? 0 : (10 - sum % 10)) == Integer.valueOf(end) ? true
-				: false;
+
+		int checkDigit = (sum % 10 == 0) ? 0 : (10 - sum % 10);
+		return checkDigit == Character.getNumericValue(end);
 	}
 
+
 	/**
 	 * 验证香港身份证号码(存在Bug,部份特殊身份证无法检查)
 	 * <p>
@@ -342,35 +354,38 @@ public class IdcardUtils extends StringUtils {
 	 * @return 验证码是否符合
 	 */
 	public static boolean validateHKCard(String idCard) {
+		if (idCard == null || idCard.length() < 8 || idCard.length() > 9) {
+			return false;
+		}
+
+		// 去掉括号
 		String card = idCard.replaceAll("[\\(|\\)]", "");
-		Integer sum = 0;
+
+		int sum = 0;
+
 		if (card.length() == 9) {
-			sum = (Integer.valueOf(card.substring(0, 1).toUpperCase()
-					.toCharArray()[0]) - 55)
-					* 9
-					+ (Integer.valueOf(card.substring(1, 2).toUpperCase()
-							.toCharArray()[0]) - 55) * 8;
+			sum = (card.charAt(0) - 55) * 9 + (card.charAt(1) - 55) * 8;
 			card = card.substring(1, 9);
 		} else {
-			sum = 522 + (Integer.valueOf(card.substring(0, 1).toUpperCase()
-					.toCharArray()[0]) - 55) * 8;
+			sum = 522 + (card.charAt(0) - 55) * 8;
 		}
+
 		String mid = card.substring(1, 7);
-		String end = card.substring(7, 8);
-		char[] chars = mid.toCharArray();
-		Integer iflag = 7;
-		for (char c : chars) {
-			sum = sum + Integer.valueOf(c + "") * iflag;
+		char end = card.charAt(7);  // 直接获取 `char`,避免 `String` 创建
+
+		int iflag = 7;
+		for (char c : mid.toCharArray()) {
+			sum += Character.getNumericValue(c) * iflag;
 			iflag--;
 		}
-		if (end.toUpperCase().equals("A")) {
-			sum = sum + 10;
-		} else {
-			sum = sum + Integer.valueOf(end);
-		}
-		return (sum % 11 == 0) ? true : false;
+
+		// 直接比较 `char`,避免 `String` 创建
+		sum += (end == 'A' || end == 'a') ? 10 : Character.getNumericValue(end);
+
+		return (sum % 11 == 0);
 	}
 
+
 	/**
 	 * 将字符数组转换成数字数组
 	 * 
@@ -379,18 +394,28 @@ public class IdcardUtils extends StringUtils {
 	 * @return 数字数组
 	 */
 	public static int[] converCharToInt(char[] ca) {
+		if (ca == null) {
+			return null;  // 处理空输入,避免空指针异常
+		}
+
 		int len = ca.length;
 		int[] iArr = new int[len];
-		try {
-			for (int i = 0; i < len; i++) {
-				iArr[i] = Integer.parseInt(String.valueOf(ca[i]));
+
+		for (int i = 0; i < len; i++) {
+			int num = Character.getNumericValue(ca[i]);
+
+			// 确保字符是有效数字 (0-9)
+			if (num < 0 || num > 9) {
+				throw new NumberFormatException("Invalid character in input: " + ca[i]);
 			}
-		} catch (NumberFormatException e) {
-			e.printStackTrace();
+
+			iArr[i] = num;
 		}
+
 		return iArr;
 	}
 
+
 	/**
 	 * 将身份证的每位和对应位的加权因子相乘之后,再得到和值
 	 * 

+ 17 - 18
src/main/java/com/jeeplus/common/utils/StringUtils.java

@@ -400,34 +400,33 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
 	 */
 
 	public static String getPinYinHeadChar(String zn_str, int caseType) {
-		if(zn_str != null && !zn_str.trim().equalsIgnoreCase("")) {
+		if (zn_str != null && !zn_str.trim().isEmpty()) {
 			char[] strChar = zn_str.toCharArray();
-			// 汉语拼音格式输出类
+
+			// 拼音输出格式配置
 			HanyuPinyinOutputFormat hanYuPinOutputFormat = new HanyuPinyinOutputFormat();
-			// 输出设置,大小写,音标方式等
-			if(1 == caseType) {
-				hanYuPinOutputFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
-			} else {
-				hanYuPinOutputFormat.setCaseType(HanyuPinyinCaseType.UPPERCASE);
-			}
+			hanYuPinOutputFormat.setCaseType(caseType == 1 ? HanyuPinyinCaseType.LOWERCASE : HanyuPinyinCaseType.UPPERCASE);
 			hanYuPinOutputFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
 			hanYuPinOutputFormat.setVCharType(HanyuPinyinVCharType.WITH_V);
-			StringBuffer pyStringBuffer = new StringBuffer();
-			for(int i=0; i<strChar.length; i++) {
-				char c = strChar[i];
-				char pyc = strChar[i];
-				if(String.valueOf(c).matches("[\\u4E00-\\u9FA5]+")) {//是中文或者a-z或者A-Z转换拼音
+
+			StringBuffer pyStringBuffer = new StringBuffer(strChar.length);
+
+			// 遍历字符串
+			for (char c : strChar) {
+				// 检查是否为中文字符
+				if (Character.toString(c).matches("[\\u4E00-\\u9FA5]")) {
 					try {
-						String[] pyStirngArray = PinyinHelper.toHanyuPinyinStringArray(strChar[i], hanYuPinOutputFormat);
-						if(null != pyStirngArray && pyStirngArray[0]!=null) {
-							pyc = pyStirngArray[0].charAt(0);
-							pyStringBuffer.append(pyc);
+						String[] pyStringArray = PinyinHelper.toHanyuPinyinStringArray(c, hanYuPinOutputFormat);
+						if (pyStringArray != null && pyStringArray.length > 0) {
+							// 仅取拼音首字母
+							pyStringBuffer.append(pyStringArray[0].charAt(0));
 						}
-					} catch(BadHanyuPinyinOutputFormatCombination e) {
+					} catch (BadHanyuPinyinOutputFormatCombination e) {
 						e.printStackTrace();
 					}
 				}
 			}
+
 			return pyStringBuffer.toString();
 		}
 		return null;

+ 56 - 55
src/main/java/com/jeeplus/modules/API/maillist/ColleagueController.java

@@ -1,8 +1,6 @@
 package com.jeeplus.modules.API.maillist;
 
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -64,60 +62,63 @@ public class ColleagueController extends BaseController{
 	 */
 	    @ResponseBody
 		@RequestMapping(value = "getMailListColleagueList", method=RequestMethod.GET)
-		public AjaxJson findAllColleague (String companyId,HttpServletRequest request, HttpServletResponse response, RedirectAttributes redirectAttributes)
-		{  
-		 AjaxJson j=new AjaxJson();
-		 List<User> userList = systemService.findUserByCompanyId(companyId);
-         List<Map<String,Object>> mapList = Lists.newArrayList();
-         for(User u :userList){
-        	 Map<String,Object> map = Maps.newHashMap();
-        	 Office office = officeDao.get(u.getOffice());
-
-        	 map.put("id", u.getId());
-        	 map.put("photo", u.getPhoto());
-        	 map.put("name", u.getName());
-        	 map.put("mobile", u.getMobile());
-			 map.put("isgly", 0);
-             if(office!=null){
-                 map.put("officeName", office.getName());
-             }else{
-                 map.put("officeName", "");
-             }
-        	 if(u.getName() != null && !u.getName().trim().equalsIgnoreCase("")) {
-        		 char[] strChar = u.getName().trim().toCharArray();         		 
-        		 if(StringUtils.isChinese(strChar[0])){
-        			 String enName =  StringUtils.getPinYinHeadChar(u.getName().trim(),1);
-        			// System.out.println(enName.charAt(0));
-        			 map.put("initial",enName.charAt(0));
-        		 }else{
-        			// System.out.println(strChar[0]);
-        			 map.put("initial",strChar[0]);
-        		 }
-        	 }else{
-        		 map.put("initial","");
-        	 }
-        	 Role role1 = new Role();
-     		 role1.setUser(u);
-			 role1.setCompany(new Office(companyId));
-     		 List<Role> roleList=roleDao.findList(role1);
-     		 if (roleList!=null && roleList.size()!=0){
-				 for(Role role : roleList){
-					 if(org.apache.commons.lang3.StringUtils.isNotBlank(role.getEnname()) && role.getEnname().endsWith("gly")||role.getEnname().equals("system")){
-						 map.put("isgly", 1);
-					 }
-				 }
-			 }else {
-				 map.put("isgly", 0);
-			 }
+		public AjaxJson findAllColleague(String companyId, HttpServletRequest request, HttpServletResponse response, RedirectAttributes redirectAttributes) {
+			AjaxJson j = new AjaxJson();
+			List<User> userList = systemService.findUserByCompanyId(companyId);
+			List<Map<String, Object>> mapList = new ArrayList<>();
 
-        	 mapList.add(map); 
-         }         
-         j.put("data",mapList);
-		 j.setMsg("获取公司人员列表成功");
-		 j.setErrorCode(ErrorCode.code_1004);
-		 return j;
+			for (User u : userList) {
+				Map<String, Object> map = new HashMap<>();
+				Office office = officeDao.get(u.getOffice());
+
+				map.put("id", u.getId());
+				map.put("photo", u.getPhoto());
+				map.put("name", u.getName());
+				map.put("mobile", u.getMobile());
+				map.put("isgly", 0);
+				map.put("officeName", (office != null) ? office.getName() : "");
+
+				// 处理姓名首字母
+				if (u.getName() != null && !u.getName().isEmpty()) {
+					String trimmedName = u.getName().trim();
+					char firstChar = trimmedName.charAt(0);
+
+					if (StringUtils.isChinese(firstChar)) {
+						String enName = StringUtils.getPinYinHeadChar(trimmedName, 1);
+						map.put("initial", enName.isEmpty() ? "" : enName.charAt(0));
+					} else {
+						map.put("initial", firstChar);
+					}
+				} else {
+					map.put("initial", "");
+				}
+
+				// 处理角色信息
+				Role role1 = new Role();
+				role1.setUser(u);
+				role1.setCompany(new Office(companyId));
+
+				List<Role> roleList = roleDao.findList(role1);
+				if (roleList != null) {
+					for (Role role : roleList) {
+						String enname = role.getEnname();
+						if (enname != null && (enname.endsWith("gly") || enname.equals("system"))) {
+							map.put("isgly", 1);
+							break;  // 提前终止循环,减少无谓计算
+						}
+					}
+				}
+
+				mapList.add(map);
+			}
+
+			j.put("data", mapList);
+			j.setMsg("获取公司人员列表成功");
+			j.setErrorCode(ErrorCode.code_1004);
+			return j;
 		}
-	 /**
+
+	/**
 	  * 获取成员信息
 	  */
 	 @ResponseBody

+ 152 - 0
src/main/java/com/jeeplus/modules/centerservice/config/GcServletListener.java

@@ -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);
+            }
+        }
+    }
+}
+
+
+
+
+

+ 6 - 2
src/main/java/com/jeeplus/modules/projectcontentinfo/service/ProjectReportDataService.java

@@ -1764,10 +1764,14 @@ public class ProjectReportDataService extends CrudService<ProjectReportDataDao,
 		if(StringUtils.isNotBlank(type) && StringUtils.isNotBlank(year)){
 			Integer newNumberSuffix = 0;
 			String getNumberStr = "";
-			if("造价审核".equals(type)){
+			if("造".equals(type)){
 				getNumberStr = "苏兴造字【" + year + "】";
-			}else if("工程咨询".equals(type)){
+			}else if("咨字".equals(type)){
 				getNumberStr = "苏兴咨字【" + year + "】";
+			}else if("内审字".equals(type)){
+				getNumberStr = "兴光内审字【" + year + "】";
+			}else if("价字".equals(type)){
+				getNumberStr = "苏兴价字【" + year + "】";
 			}
 			if(StringUtils.isNotBlank(getNumberStr)){
 

+ 28 - 17
src/main/java/com/jeeplus/modules/tools/utils/SignaturePostUtil.java

@@ -521,28 +521,39 @@ public class SignaturePostUtil {
         return result;
     }
 
-    public static String decodeUnicode(String asciicode) {
-        String[] asciis = asciicode.split ("\\\\u");
-        String nativeValue = asciis[0];
-        try
-        {
-            for ( int i = 1; i < asciis.length; i++ )
-            {
-                String code = asciis[i];
-                nativeValue += (char) Integer.parseInt (code.substring (0, 4), 16);
-                if (code.length () > 4)
-                {
-                    nativeValue += code.substring (4, code.length ());
+    public static String decodeUnicode(String asciiCode) {
+        if (asciiCode == null || !asciiCode.contains("\\u")) {
+            return asciiCode; // 无需处理的情况
+        }
+
+        StringBuilder result = new StringBuilder();
+        int len = asciiCode.length();
+
+        for (int i = 0; i < len; i++) {
+            char c = asciiCode.charAt(i);
+            if (c == '\\' && i + 1 < len && asciiCode.charAt(i + 1) == 'u') {
+                // 解析 \\uXXXX
+                if (i + 5 < len) { // 确保 \\u 后面有 4 位十六进制字符
+                    String unicodeStr = asciiCode.substring(i + 2, i + 6);
+                    try {
+                        int unicodeVal = Integer.parseInt(unicodeStr, 16);
+                        result.append((char) unicodeVal);
+                        i += 5; // 跳过 \\uXXXX
+                    } catch (NumberFormatException e) {
+                        result.append("\\u").append(unicodeStr); // 解析失败,保留原字符
+                    }
+                } else {
+                    result.append(c); // 非法 \\u 结构,直接保留
                 }
+            } else {
+                result.append(c);
             }
         }
-        catch (NumberFormatException e)
-        {
-            return asciicode;
-        }
-        return nativeValue;
+
+        return result.toString();
     }
 
+
     /**
      * 根据文件创建合同信息
      * @param srcFile

+ 4 - 2
src/main/webapp/WEB-INF/tags/table/importExcelNumber.tag

@@ -18,8 +18,10 @@
 			<label class="layui-form-label  abc" style="vertical-align:middle;display:inline-block;"><span class="require-item" style="color:red;font-size: 20px;">*</span>导入报告类型</label>
 			<select id="type" name="type">
 				<option value="">请选择</option>
-				<option value="造价审核" selected="">造价审核</option>
-				<option value="工程咨询">工程咨询</option>
+				<option value="造字" selected="">造字</option>
+				<option value="咨字">咨字</option>
+				<option value="内审字">内审字</option>
+				<option value="价字">价字</option>
 			</select>
 		</div>
 		<div class="layui-form-item" style="text-align:center; line-height: 20px;">