JVM的前世今生
前世
jvm的数据区
分别是方法区(Method Area),Java栈(Java stack),本地方法栈(Native Method Stack),堆(Heap),程序计数器(Program Counter Register)
堆
在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为
三个区域:Eden、From Survivor、To Survivor。
新生代。新建的对象都是用新生代分配内存,Eden空间不足的时候,会把存活的对象转移到Survivor中,新生代大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制Eden和Survivor的比例旧生代。用于存放新生代中经过多次垃圾回收仍然存活的对象。
gc的触发条件
充分了解了jvm的内存结构之后,下面我们就来说说什么情况下会触发gc。触发full gc的情况主要有这几种:
(1)System.gc()方法的调用。此方法的调用是建议JVM进行Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加Full GC的频率,也即增加了间歇性停顿的次数。强烈影响系建议能不使用此方法就别使用,让虚拟机自己去管理它的内存,可通过通过-XX:+ DisableExplicitGC来禁止RMI(Java远程方法调用)调用System.gc。
(2)旧生代空间不足。旧生代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出错误:java.lang.OutOfMemoryError: Java heap space 。为避免以上两种状况引起的FullGC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。
(3)Permanet Generation空间满了。Permanet Generation中存放的为一些class的信息等,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出错误信息:java.lang.OutOfMemoryError: PermGen space 。为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。
(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存。如果发现统计数据说之前Minor GC的平均晋升大小比目前old gen剩余的空间大,则不会触发Minor GC而是转为触发full GC。
(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
今生
(1)我们在上传文件写入数据库时,如果同一时间写入上万数据,会导致fullgc
日志如下;
ownerThread current state is BLOCKED, current stackTrace,java.lang.OutOfMemoryError: GC overhead limit exceeded
问题代码:
List<Map<String, Object>> res; try { URL url = new URL(task.getTaskUrl()); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); InputStream inputStream = new BufferedInputStream(conn.getInputStream()); res = ExcelUtil.handleExcel(inputStream); } catch (IOException e){ log.error(String.format("任务%s,下载解析异常{}", task.getId()), e); task.setRemark(e.getMessage()); task.setStatus(BatchQueryStatusEnum.VERIFY_FAIL.getCode()); batchQueryTaskDAO.update(task); return false; }
使用阿里的easyexcel解决fullgc问题
<dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>1.1.2-beat1</version> </dependency>
List<Map<String, String>> res = new ArrayList<>(); try { URL url = new URL(task.getTaskUrl()); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); InputStream inputStream = new BufferedInputStream(conn.getInputStream()); ExcelListener excelListener = new ExcelListener(MAX_COUNT, res); ExcelReader excelReader = EasyExcelFactory.getReader(inputStream, excelListener); excelReader.read();
(2)我们在读数据库写入excel时,如果同一时间写入上万数据,同一时间循环写入大量对象,导致服务宕机
问题代码:
List<BatchQueryDetailDTO> list = batchQueryDetailService.query(id, type == 1); try { XSSFWorkbook wb = this.handle(list, apiName, type); StringBuilder sb = new StringBuilder(); sb.append("批量查询").append(ThreadSafeDateUtil.format(new Date(), "yyyyMMddHHmmss")).append(".xlsx"); String excelName = sb.toString(); ExcelUtil.export(response, wb, excelName); } catch (Exception e) { log.error(String.format("taskId=%d导出批量查询失败,{}", id), e); } }
分页解决大量创建对象问题
int pageSize = 2000; List<BatchQueryDetailDTO> list = new ArrayList<>(); Page<BatchQueryDetailDTO> pageResult = batchQueryDetailService.query(id, 1, pageSize,type == 1); list.addAll(pageResult.getItems()); int totalPage = pageResult.getTotalNumber() / pageSize + 1; for(int i = 2; i <= totalPage; i++) { pageResult = batchQueryDetailService.query(id, i, pageSize,type == 1); if(CollectionUtils.isNotEmpty(pageResult.getItems())){ list.addAll(pageResult.getItems()); } }