问题现象

首先用户反映,有数据不一致情况产生,为了查询什么原因导致的数据不一致问题,
扒拉出来日志看了一通,发现有个简单的查询耗时特别长,平时只要几ms返回结果的
确调用了200多s,查了各个服务的日志,发现并没有什么问题,把日志展开了看,发现
有5分钟时间,系统各种超时,各种错误,包括连接数据库超时,连接redis超时,等等
就考虑看一下GC日志,这一看不要紧,发现这5分钟时间GC日志刷了上千条FullGC,如下图所示:

回收前后内存变化不大,基本没回收掉内存,但还在不断的回收,因为使用的CMS内存回收器,超过最大内存的80%时会进行FullGC,所以JVM一直在傻傻的回收内存空间,应用程序几乎暂停。

好了,基本找到为什么会报错了,但是为什么会频繁的FullGC呢,这个问题又困扰了很久

#mermaid-svg-xQYhGRG5fNbhktW1 .label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);fill:#333;color:#333}#mermaid-svg-xQYhGRG5fNbhktW1 .label text{fill:#333}#mermaid-svg-xQYhGRG5fNbhktW1 .node rect,#mermaid-svg-xQYhGRG5fNbhktW1 .node circle,#mermaid-svg-xQYhGRG5fNbhktW1 .node ellipse,#mermaid-svg-xQYhGRG5fNbhktW1 .node polygon,#mermaid-svg-xQYhGRG5fNbhktW1 .node path{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-xQYhGRG5fNbhktW1 .node .label{text-align:center;fill:#333}#mermaid-svg-xQYhGRG5fNbhktW1 .node.clickable{cursor:pointer}#mermaid-svg-xQYhGRG5fNbhktW1 .arrowheadPath{fill:#333}#mermaid-svg-xQYhGRG5fNbhktW1 .edgePath .path{stroke:#333;stroke-width:1.5px}#mermaid-svg-xQYhGRG5fNbhktW1 .flowchart-link{stroke:#333;fill:none}#mermaid-svg-xQYhGRG5fNbhktW1 .edgeLabel{background-color:#e8e8e8;text-align:center}#mermaid-svg-xQYhGRG5fNbhktW1 .edgeLabel rect{opacity:0.9}#mermaid-svg-xQYhGRG5fNbhktW1 .edgeLabel span{color:#333}#mermaid-svg-xQYhGRG5fNbhktW1 .cluster rect{fill:#ffffde;stroke:#aa3;stroke-width:1px}#mermaid-svg-xQYhGRG5fNbhktW1 .cluster text{fill:#333}#mermaid-svg-xQYhGRG5fNbhktW1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#ffffde;border:1px solid #aa3;border-radius:2px;pointer-events:none;z-index:100}#mermaid-svg-xQYhGRG5fNbhktW1 .actor{stroke:#ccf;fill:#ECECFF}#mermaid-svg-xQYhGRG5fNbhktW1 text.actor>tspan{fill:#000;stroke:none}#mermaid-svg-xQYhGRG5fNbhktW1 .actor-line{stroke:grey}#mermaid-svg-xQYhGRG5fNbhktW1 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333}#mermaid-svg-xQYhGRG5fNbhktW1 .messageLine1{stroke-width:1.5;stroke-dasharray:2, 2;stroke:#333}#mermaid-svg-xQYhGRG5fNbhktW1 #arrowhead path{fill:#333;stroke:#333}#mermaid-svg-xQYhGRG5fNbhktW1 .sequenceNumber{fill:#fff}#mermaid-svg-xQYhGRG5fNbhktW1 #sequencenumber{fill:#333}#mermaid-svg-xQYhGRG5fNbhktW1 #crosshead path{fill:#333;stroke:#333}#mermaid-svg-xQYhGRG5fNbhktW1 .messageText{fill:#333;stroke:#333}#mermaid-svg-xQYhGRG5fNbhktW1 .labelBox{stroke:#ccf;fill:#ECECFF}#mermaid-svg-xQYhGRG5fNbhktW1 .labelText,#mermaid-svg-xQYhGRG5fNbhktW1 .labelText>tspan{fill:#000;stroke:none}#mermaid-svg-xQYhGRG5fNbhktW1 .loopText,#mermaid-svg-xQYhGRG5fNbhktW1 .loopText>tspan{fill:#000;stroke:none}#mermaid-svg-xQYhGRG5fNbhktW1 .loopLine{stroke-width:2px;stroke-dasharray:2, 2;stroke:#ccf;fill:#ccf}#mermaid-svg-xQYhGRG5fNbhktW1 .note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-xQYhGRG5fNbhktW1 .noteText,#mermaid-svg-xQYhGRG5fNbhktW1 .noteText>tspan{fill:#000;stroke:none}#mermaid-svg-xQYhGRG5fNbhktW1 .activation0{fill:#f4f4f4;stroke:#666}#mermaid-svg-xQYhGRG5fNbhktW1 .activation1{fill:#f4f4f4;stroke:#666}#mermaid-svg-xQYhGRG5fNbhktW1 .activation2{fill:#f4f4f4;stroke:#666}#mermaid-svg-xQYhGRG5fNbhktW1 .mermaid-main-font{font-family:"trebuchet ms", verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-xQYhGRG5fNbhktW1 .section{stroke:none;opacity:0.2}#mermaid-svg-xQYhGRG5fNbhktW1 .section0{fill:rgba(102,102,255,0.49)}#mermaid-svg-xQYhGRG5fNbhktW1 .section2{fill:#fff400}#mermaid-svg-xQYhGRG5fNbhktW1 .section1,#mermaid-svg-xQYhGRG5fNbhktW1 .section3{fill:#fff;opacity:0.2}#mermaid-svg-xQYhGRG5fNbhktW1 .sectionTitle0{fill:#333}#mermaid-svg-xQYhGRG5fNbhktW1 .sectionTitle1{fill:#333}#mermaid-svg-xQYhGRG5fNbhktW1 .sectionTitle2{fill:#333}#mermaid-svg-xQYhGRG5fNbhktW1 .sectionTitle3{fill:#333}#mermaid-svg-xQYhGRG5fNbhktW1 .sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-xQYhGRG5fNbhktW1 .grid .tick{stroke:#d3d3d3;opacity:0.8;shape-rendering:crispEdges}#mermaid-svg-xQYhGRG5fNbhktW1 .grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-xQYhGRG5fNbhktW1 .grid path{stroke-width:0}#mermaid-svg-xQYhGRG5fNbhktW1 .today{fill:none;stroke:red;stroke-width:2px}#mermaid-svg-xQYhGRG5fNbhktW1 .task{stroke-width:2}#mermaid-svg-xQYhGRG5fNbhktW1 .taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-xQYhGRG5fNbhktW1 .taskText:not([font-size]){font-size:11px}#mermaid-svg-xQYhGRG5fNbhktW1 .taskTextOutsideRight{fill:#000;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-xQYhGRG5fNbhktW1 .taskTextOutsideLeft{fill:#000;text-anchor:end;font-size:11px}#mermaid-svg-xQYhGRG5fNbhktW1 .task.clickable{cursor:pointer}#mermaid-svg-xQYhGRG5fNbhktW1 .taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-xQYhGRG5fNbhktW1 .taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-xQYhGRG5fNbhktW1 .taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-xQYhGRG5fNbhktW1 .taskText0,#mermaid-svg-xQYhGRG5fNbhktW1 .taskText1,#mermaid-svg-xQYhGRG5fNbhktW1 .taskText2,#mermaid-svg-xQYhGRG5fNbhktW1 .taskText3{fill:#fff}#mermaid-svg-xQYhGRG5fNbhktW1 .task0,#mermaid-svg-xQYhGRG5fNbhktW1 .task1,#mermaid-svg-xQYhGRG5fNbhktW1 .task2,#mermaid-svg-xQYhGRG5fNbhktW1 .task3{fill:#8a90dd;stroke:#534fbc}#mermaid-svg-xQYhGRG5fNbhktW1 .taskTextOutside0,#mermaid-svg-xQYhGRG5fNbhktW1 .taskTextOutside2{fill:#000}#mermaid-svg-xQYhGRG5fNbhktW1 .taskTextOutside1,#mermaid-svg-xQYhGRG5fNbhktW1 .taskTextOutside3{fill:#000}#mermaid-svg-xQYhGRG5fNbhktW1 .active0,#mermaid-svg-xQYhGRG5fNbhktW1 .active1,#mermaid-svg-xQYhGRG5fNbhktW1 .active2,#mermaid-svg-xQYhGRG5fNbhktW1 .active3{fill:#bfc7ff;stroke:#534fbc}#mermaid-svg-xQYhGRG5fNbhktW1 .activeText0,#mermaid-svg-xQYhGRG5fNbhktW1 .activeText1,#mermaid-svg-xQYhGRG5fNbhktW1 .activeText2,#mermaid-svg-xQYhGRG5fNbhktW1 .activeText3{fill:#000 !important}#mermaid-svg-xQYhGRG5fNbhktW1 .done0,#mermaid-svg-xQYhGRG5fNbhktW1 .done1,#mermaid-svg-xQYhGRG5fNbhktW1 .done2,#mermaid-svg-xQYhGRG5fNbhktW1 .done3{stroke:grey;fill:#d3d3d3;stroke-width:2}#mermaid-svg-xQYhGRG5fNbhktW1 .doneText0,#mermaid-svg-xQYhGRG5fNbhktW1 .doneText1,#mermaid-svg-xQYhGRG5fNbhktW1 .doneText2,#mermaid-svg-xQYhGRG5fNbhktW1 .doneText3{fill:#000 !important}#mermaid-svg-xQYhGRG5fNbhktW1 .crit0,#mermaid-svg-xQYhGRG5fNbhktW1 .crit1,#mermaid-svg-xQYhGRG5fNbhktW1 .crit2,#mermaid-svg-xQYhGRG5fNbhktW1 .crit3{stroke:#f88;fill:red;stroke-width:2}#mermaid-svg-xQYhGRG5fNbhktW1 .activeCrit0,#mermaid-svg-xQYhGRG5fNbhktW1 .activeCrit1,#mermaid-svg-xQYhGRG5fNbhktW1 .activeCrit2,#mermaid-svg-xQYhGRG5fNbhktW1 .activeCrit3{stroke:#f88;fill:#bfc7ff;stroke-width:2}#mermaid-svg-xQYhGRG5fNbhktW1 .doneCrit0,#mermaid-svg-xQYhGRG5fNbhktW1 .doneCrit1,#mermaid-svg-xQYhGRG5fNbhktW1 .doneCrit2,#mermaid-svg-xQYhGRG5fNbhktW1 .doneCrit3{stroke:#f88;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}#mermaid-svg-xQYhGRG5fNbhktW1 .milestone{transform:rotate(45deg) scale(0.8, 0.8)}#mermaid-svg-xQYhGRG5fNbhktW1 .milestoneText{font-style:italic}#mermaid-svg-xQYhGRG5fNbhktW1 .doneCritText0,#mermaid-svg-xQYhGRG5fNbhktW1 .doneCritText1,#mermaid-svg-xQYhGRG5fNbhktW1 .doneCritText2,#mermaid-svg-xQYhGRG5fNbhktW1 .doneCritText3{fill:#000 !important}#mermaid-svg-xQYhGRG5fNbhktW1 .activeCritText0,#mermaid-svg-xQYhGRG5fNbhktW1 .activeCritText1,#mermaid-svg-xQYhGRG5fNbhktW1 .activeCritText2,#mermaid-svg-xQYhGRG5fNbhktW1 .activeCritText3{fill:#000 !important}#mermaid-svg-xQYhGRG5fNbhktW1 .titleText{text-anchor:middle;font-size:18px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-xQYhGRG5fNbhktW1 g.classGroup text{fill:#9370db;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}#mermaid-svg-xQYhGRG5fNbhktW1 g.classGroup text .title{font-weight:bolder}#mermaid-svg-xQYhGRG5fNbhktW1 g.clickable{cursor:pointer}#mermaid-svg-xQYhGRG5fNbhktW1 g.classGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-xQYhGRG5fNbhktW1 g.classGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-xQYhGRG5fNbhktW1 .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5}#mermaid-svg-xQYhGRG5fNbhktW1 .classLabel .label{fill:#9370db;font-size:10px}#mermaid-svg-xQYhGRG5fNbhktW1 .relation{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-xQYhGRG5fNbhktW1 .dashed-line{stroke-dasharray:3}#mermaid-svg-xQYhGRG5fNbhktW1 #compositionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-xQYhGRG5fNbhktW1 #compositionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-xQYhGRG5fNbhktW1 #aggregationStart{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-xQYhGRG5fNbhktW1 #aggregationEnd{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-xQYhGRG5fNbhktW1 #dependencyStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-xQYhGRG5fNbhktW1 #dependencyEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-xQYhGRG5fNbhktW1 #extensionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-xQYhGRG5fNbhktW1 #extensionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-xQYhGRG5fNbhktW1 .commit-id,#mermaid-svg-xQYhGRG5fNbhktW1 .commit-msg,#mermaid-svg-xQYhGRG5fNbhktW1 .branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-xQYhGRG5fNbhktW1 .pieTitleText{text-anchor:middle;font-size:25px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-xQYhGRG5fNbhktW1 .slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-xQYhGRG5fNbhktW1 g.stateGroup text{fill:#9370db;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-xQYhGRG5fNbhktW1 g.stateGroup text{fill:#9370db;fill:#333;stroke:none;font-size:10px}#mermaid-svg-xQYhGRG5fNbhktW1 g.statediagram-cluster .cluster-label text{fill:#333}#mermaid-svg-xQYhGRG5fNbhktW1 g.stateGroup .state-title{font-weight:bolder;fill:#000}#mermaid-svg-xQYhGRG5fNbhktW1 g.stateGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-xQYhGRG5fNbhktW1 g.stateGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-xQYhGRG5fNbhktW1 .transition{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-xQYhGRG5fNbhktW1 .stateGroup .composit{fill:white;border-bottom:1px}#mermaid-svg-xQYhGRG5fNbhktW1 .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px}#mermaid-svg-xQYhGRG5fNbhktW1 .state-note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-xQYhGRG5fNbhktW1 .state-note text{fill:black;stroke:none;font-size:10px}#mermaid-svg-xQYhGRG5fNbhktW1 .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.7}#mermaid-svg-xQYhGRG5fNbhktW1 .edgeLabel text{fill:#333}#mermaid-svg-xQYhGRG5fNbhktW1 .stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-xQYhGRG5fNbhktW1 .node circle.state-start{fill:black;stroke:black}#mermaid-svg-xQYhGRG5fNbhktW1 .node circle.state-end{fill:black;stroke:white;stroke-width:1.5}#mermaid-svg-xQYhGRG5fNbhktW1 #statediagram-barbEnd{fill:#9370db}#mermaid-svg-xQYhGRG5fNbhktW1 .statediagram-cluster rect{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-xQYhGRG5fNbhktW1 .statediagram-cluster rect.outer{rx:5px;ry:5px}#mermaid-svg-xQYhGRG5fNbhktW1 .statediagram-state .divider{stroke:#9370db}#mermaid-svg-xQYhGRG5fNbhktW1 .statediagram-state .title-state{rx:5px;ry:5px}#mermaid-svg-xQYhGRG5fNbhktW1 .statediagram-cluster.statediagram-cluster .inner{fill:white}#mermaid-svg-xQYhGRG5fNbhktW1 .statediagram-cluster.statediagram-cluster-alt .inner{fill:#e0e0e0}#mermaid-svg-xQYhGRG5fNbhktW1 .statediagram-cluster .inner{rx:0;ry:0}#mermaid-svg-xQYhGRG5fNbhktW1 .statediagram-state rect.basic{rx:5px;ry:5px}#mermaid-svg-xQYhGRG5fNbhktW1 .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#efefef}#mermaid-svg-xQYhGRG5fNbhktW1 .note-edge{stroke-dasharray:5}#mermaid-svg-xQYhGRG5fNbhktW1 .statediagram-note rect{fill:#fff5ad;stroke:#aa3;stroke-width:1px;rx:0;ry:0}:root{--mermaid-font-family: '"trebuchet ms", verdana, arial';--mermaid-font-family: "Comic Sans MS", "Comic Sans", cursive}#mermaid-svg-xQYhGRG5fNbhktW1 .error-icon{fill:#522}#mermaid-svg-xQYhGRG5fNbhktW1 .error-text{fill:#522;stroke:#522}#mermaid-svg-xQYhGRG5fNbhktW1 .edge-thickness-normal{stroke-width:2px}#mermaid-svg-xQYhGRG5fNbhktW1 .edge-thickness-thick{stroke-width:3.5px}#mermaid-svg-xQYhGRG5fNbhktW1 .edge-pattern-solid{stroke-dasharray:0}#mermaid-svg-xQYhGRG5fNbhktW1 .edge-pattern-dashed{stroke-dasharray:3}#mermaid-svg-xQYhGRG5fNbhktW1 .edge-pattern-dotted{stroke-dasharray:2}#mermaid-svg-xQYhGRG5fNbhktW1 .marker{fill:#333}#mermaid-svg-xQYhGRG5fNbhktW1 .marker.cross{stroke:#333}:root { --mermaid-font-family: "trebuchet ms", verdana, arial;}#mermaid-svg-xQYhGRG5fNbhktW1 {color: rgba(0, 0, 0, 0.75);font: ;}

分布式系统数据不一致
找系统报错
没啥问题
时间段内大量超时
查看GC日志
大量FullGC

思考可能是内存泄漏,或者是某个远程接口阻塞导致大量请求堆积,查看代码发现,代码中resttemplate设置的超时时间都是60s超时,而整个GC持续了5分钟,如果内存泄漏,内存会一直不回收,最终导致的结果是内存占满,OutOfMemoryError, 但是这个摸不着头脑的FullGC最终把内存回收掉了,所以应该不是内存泄漏导致的。
猜想可能是某个业务占用内存特别大,并且短时间内回收不掉。

查阅了好久的代码以及日志,最终定位到一段图片压缩的代码,原代码如下:

import net.coobird.thumbnailator.Thumbnails;
public class ImageProcessor {/*** 压缩图片 bytes -> bytes* @param imageContent image bytes* @param size 压缩尺寸* @return resized bytes* @throws IOException e*/static byte[] resize(byte[] imageContent, int size) throws IOException {ByteArrayOutputStream out = new ByteArrayOutputStream();Thumbnails.of(new ByteArrayInputStream(imageContent)).size(size, size).useExifOrientation(true).outputFormat(FileTypeUtil.JPG).toOutputStream(out);return out.toByteArray();}
}

最开始以为是ByteArrayOutputStream流没关闭造成的,查看了ByteArrayOutputStream的源码:

/*** Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in* this class can be called after the stream has been closed without* generating an <tt>IOException</tt>.*/public void close() throws IOException {}

close方法不做任何的操作,因为这个是纯内存操作的流,没有跟文件系统打交道,这个地方数据量不需要关闭,用完等待垃圾回收就可以了。
既然不是这里的问题,那我对这个方法做个测试吧,使用了如下的方法打印了内存占用情况:

private static void getFreeMemoryPercentage() {// 虚拟机级内存情况查询long vmFree = 0;long vmUse = 0;long vmTotal = 0;long vmMax = 0;int byteToMb = 1024 * 1024;Runtime rt = Runtime.getRuntime();vmTotal = rt.totalMemory() / byteToMb;vmFree = rt.freeMemory() / byteToMb;vmMax = rt.maxMemory() / byteToMb;vmUse = vmTotal - vmFree;System.out.println("JVM内存已用的空间为:" + vmUse + " MB");System.out.println("JVM内存的空闲空间为:" + vmFree + " MB");System.out.println("JVM总内存空间为:" + vmTotal + " MB");System.out.println("JVM总内存空间为:" + vmMax + " MB");System.out.println("======================================");// 操作系统级内存情况查询OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();String os = System.getProperty("os.name");long physicalFree = osmxb.getFreePhysicalMemorySize() / byteToMb;long physicalTotal = osmxb.getTotalPhysicalMemorySize() / byteToMb;long physicalUse = physicalTotal - physicalFree;System.out.println("操作系统的版本:" + os);System.out.println("操作系统物理内存已用的空间为:" + physicalFree + " MB");System.out.println("操作系统物理内存的空闲空间为:" + physicalUse + " MB");System.out.println("操作系统总物理内存:" + physicalTotal + " MB");// 获得线程总数ThreadGroup parentThread;int totalThread = 0;for (parentThread = Thread.currentThread().getThreadGroup(); parentThread.getParent() != null; parentThread = parentThread.getParent()) {totalThread = parentThread.activeCount();}System.out.println("获得线程总数:" + totalThread);}

main方法如下:

   public static void main(String[] args) throws IOException {byte[] b = Files.readAllBytes(Paths.get("C:\\Users\\zhangwb\\Desktop\\微信图片_20200717174522.png"));getFreeMemoryPercentage();System.out.println("压缩前大小:" + b.length);byte[] a = resize(b, 1920);getFreeMemoryPercentage();System.out.println("压缩后大小:" + a.length);}

执行这个方法打印出来的日志如下:

JVM内存已用的空间为:22 MB
JVM内存的空闲空间为:223 MB
JVM总内存空间为:245 MB
JVM总内存空间为:3616 MB
======================================
操作系统的版本:Windows 10
操作系统物理内存已用的空间为:2538 MB
操作系统物理内存的空闲空间为:13730 MB
操作系统总物理内存:16268 MB
获得线程总数:2
压缩前大小:11398750
JVM内存已用的空间为:423 MB
JVM内存的空闲空间为:228 MB
JVM总内存空间为:651 MB
JVM总内存空间为:3616 MB
======================================
操作系统的版本:Windows 10
操作系统物理内存已用的空间为:2640 MB
操作系统物理内存的空闲空间为:13628 MB
操作系统总物理内存:16268 MB
获得线程总数:2
压缩后大小:255408

可以看到读出来的数据内存占用22M,图片压缩后JVM内存飙升到423M,多了将近20倍!!
图片压缩后大小小了不少,但是内存却多了这么多,这个可能是导致内存飙升,并且一直FullGC的真正元凶。

终于找到问题了,那如何解决呢

替换掉图片压缩的方法,改用GraphicsMagick处理图片,修改完之后的代码如下:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.math.NumberUtils;
import org.im4java.core.ConvertCmd;
import org.im4java.core.IMOperation;/*** @author prague* @description 使用GraphicsMagick处理图片*/
@Slf4j
public class GmImageProcessor {/*** 临时文件前缀-原始文件*/public static final String SOURCE_PREFIX = "imgCut";/*** 临时文件前缀-转换文件*/public static final String TARGET_PREFIX = "imgCutResult";/*** 所有输出文件都指定为JPEG*/private static final String TARGET_IMG_SUFFIX = ".jpg";public static byte[] resize(byte[] imageContent, int size) throws IOException {Path sourcePath = null;Path targetPath = null;try {sourcePath = loadFile(imageContent);IMOperation op = new IMOperation();targetPath = Files.createTempFile(TARGET_PREFIX, TARGET_IMG_SUFFIX);op.size(size, size);op.addImage(sourcePath.toAbsolutePath().toString());op.resize(size, size, ">");op.p_profile("*");op.addImage(targetPath.toAbsolutePath().toString());log.debug("输出文件路径:{}", targetPath);ConvertCmd convertCmd = new ConvertCmd(true);convertCmd.run(op);return Files.readAllBytes(targetPath);} catch (Exception e) {log.error("压缩图片异常", e);} finally {deleteFile(sourcePath);deleteFile(targetPath);}return null;}/*** 旋转图** @param degree 逆时针*/static byte[] rotateImage(byte[] imageContent, int degree) {Path sourcePath = null;Path targetPath = null;try {sourcePath = loadFile(imageContent);IMOperation op = new IMOperation();//2020-07-28 16:13:06 add by gaotx 兼容瑞琪逆时针度数op.rotate((double) degree);op.addImage(sourcePath.toAbsolutePath().toString());targetPath = Files.createTempFile("imgCutResult", TARGET_IMG_SUFFIX);log.debug("输出文件路径:{}", targetPath);op.addImage(targetPath.toAbsolutePath().toString());ConvertCmd cmd = new ConvertCmd(true);log.debug(op.toString());cmd.run(op);return Files.readAllBytes(targetPath);} catch (Exception e) {BusiExceptionUtils.marshException(e);} finally {deleteFile(sourcePath);deleteFile(targetPath);}return null;}/*** 把一张大图切成多张小图,期间要保持源文件,完成后删除源文件, 每次切图完成后,删除临时目标文件*/static byte[] cut(Path sourcePath, int startX, int startY, int endX, int endY, int degree) {Path outPath = null;try {IMOperation op = new IMOperation();op.addImage(sourcePath.toAbsolutePath().toString());int width = NumberUtils.max(endX - startX, 0);int height = NumberUtils.max(endY - startY, 0);op.crop(width, height, startX, startY);if (degree != 0) {//2020-07-28 16:13:06 add by gaotx 兼容瑞琪逆时针度数op.rotate((double) degree);}outPath = Files.createTempFile(TARGET_PREFIX, FileTypeUtil.JPG);log.debug("输出文件路径:{}", outPath);op.addImage(outPath.toAbsolutePath().toString());ConvertCmd convert = new ConvertCmd(true);log.debug(op.toString());convert.run(op);return Files.readAllBytes(outPath);} catch (Exception e) {// TODO 处理异常} finally {deleteFile(outPath);}return null;}/*** bytes -> path** @param imageContent bytes* @return path*/static Path loadFile(byte[] imageContent) {Path sourcePath = null;String suffix = "." + bytes(imageContent);try {sourcePath = Files.createTempFile(SOURCE_PREFIX, suffix);log.debug("当前临时文件路径={}", sourcePath);Files.write(sourcePath, imageContent);} catch (IOException e) {log.error(e.getMessage(), e);}return sourcePath;}
/*** byte数组转换成16进制字符串*/public static String bytes(byte[] src) {StringBuilder stringBuilder = new StringBuilder();if (src == null || src.length <= 0) {return null;}// 防止调用的时候没有进行截取前几位if (src.length > 4) {src = Arrays.copyOfRange(src, 0, 4);}for (byte b : src) {// 以十六进制(基数 16)无符号整数形式返回一个整数参数的字符串表示形式,并转换为大写String hv = Integer.toHexString(b & 0xFF).toUpperCase();if (hv.length() < 2) {stringBuilder.append(0);}stringBuilder.append(hv);}return getFileType(stringBuilder.toString());}/*** 根据文件路径获取文件头信息,因为根据文件的后缀名识别文件类型并不准确 424D--->BMP (bmp) ,  FFD8FF--->JPEG (jpg) , 89504E47---> PNG* (png)** @return 文件头信息*/public static String getFileType(String bytes) {if (bytes != null) {if (bytes.contains("FFD8FF")) {return "jpg";}if (bytes.contains("89504E47")) {return "png";}if (bytes.contains("424D")) {return "bmp";}if (bytes.contains("25504446")) {return "pdf";}}return null;}/*** 删除文件** @param imagePath imagePath*/static void deleteFile(Path imagePath) {try {if (imagePath != null) {Files.deleteIfExists(imagePath);}log.info("临时文件清理成功");} catch (IOException e) {log.error(e.getMessage(), e);}}}

windows系统调试需要下载安装http://www.graphicsmagick.org/,linux环境请自行百度,并且在代码中设置path:
convertCmd.setSearchPath(“C:\Program Files\GraphicsMagick-1.3.35-Q16”);

否则调用会报找不到gm文件的异常
ok,我们对同样的图片进行同样大小的压缩,得出内存占用情况如下:

JVM内存已用的空间为:22 MB
JVM内存的空闲空间为:223 MB
JVM总内存空间为:245 MB
JVM总内存空间为:3616 MB
======================================
操作系统的版本:Windows 10
操作系统物理内存已用的空间为:2762 MB
操作系统物理内存的空闲空间为:13506 MB
操作系统总物理内存:16268 MB
获得线程总数:2
压缩前大小:11398750
14:16:10.583 [main] DEBUG com.yonyou.einvoice.common.util.image.GmImageProcessor - 当前临时文件路径=C:\Users\zhangwb\AppData\Local\Temp\imgCut742249801216682093.png
14:16:10.625 [main] DEBUG com.yonyou.einvoice.common.util.image.GmImageProcessor - 输出文件路径:C:\Users\zhangwb\AppData\Local\Temp\imgCutResult2449445347052121528.jpg
14:16:16.003 [main] INFO com.yonyou.einvoice.common.util.image.GmImageProcessor - 临时文件清理成功
14:16:16.004 [main] INFO com.yonyou.einvoice.common.util.image.GmImageProcessor - 临时文件清理成功
JVM内存已用的空间为:29 MB
JVM内存的空闲空间为:216 MB
JVM总内存空间为:245 MB
JVM总内存空间为:3616 MB
======================================
操作系统的版本:Windows 10
操作系统物理内存已用的空间为:2665 MB
操作系统物理内存的空闲空间为:13603 MB
操作系统总物理内存:16268 MB
获得线程总数:2
压缩后大小:209797

可以看出,图片压缩的更小了,JVM内存基本没有占用,堆外内存也基本没占用。

缺点:需要在服务器上安装插件,如果是私有部署比较麻烦,需要安装人员安装插件,否则图片压缩报错。

上线后就没再出现过内存占用暴增的情况了。

思考:你所看到的错误,并不一定是真正的错误,特别是超时的错误,有可能是其它原因导致的应用暂停,比如频繁的FullGC,比如,线程栈出现的死锁等问题。一定要找到真正的问题,并且经过自己验证过的,所有问题都不是凭空产生的,一定是有原因的,可以使用科学的知识解释的。

记一次摸不着头脑的FullGC问题 (Thumbnails压缩图片占用巨大内存)相关推荐

  1. java使用Thumbnails压缩图片

    java使用Thumbnails压缩图片 有时候为了缓解服务器上的存储压力,我们需要将图片进行压缩以换取空间. 下面使用的是google提供的压缩方式: 一.导包 <!-- 压缩图片大小 --& ...

  2. Thumbnails压缩图片到指定大小

    网上看了很多demo,很多都是照搬别人的代码,不管有没有问题,有的甚至递归不关流,还有的递归疯狂往自己磁盘写文件,递归一次写一次,我自己把网上的demo整理改了下发出来. /** * @Descrip ...

  3. 使用 Thumbnails 压缩图片

    PC上线的商城,最近推出了手机版(App & 微信公众号) 商品图片之前适配的PC版,尺寸较大,在手机端直接浏览会比较占用比较高的带宽导致速度变慢,本想让美工重新上传手机适配版的,商品繁多,重 ...

  4. 浅谈Thumbnails压缩gif图片质量的实现方式

    Thumbnails是一个比较大众的图片处理工具,类似的工具还有hutool,可以对图片进行裁剪.缩放.旋转.格式转换.水印等.然而它只提供单张图片的压缩,对于gif的压缩,却是需要我们自己去处理. ...

  5. 通过google插件Thumbnails实现图片指定大小压缩

    前言: 1.由于商户进件时,上游对图片大小有要求(500kb以下),而我们平台图片过大(10M以上),所以必须通过程序将图片压缩后再上传: 2.java api可以通过ImageIO实现图片压缩,但效 ...

  6. 使用Thumbnails等比例压缩图片

    如何使用Thumbnails等比例压缩图片 我是用MultipartFile来接收文件的 具体思路 直接上代码 我是用MultipartFile来接收文件的 最近项目中有一个需要是需要把上传的图片进行 ...

  7. thumbnails java_在JAVA中使用Thumbnails为图片加水印

    在JAVA中使用Thumbnails为图片加水印 将D盘下面的cat.jpg作为水印加在2.jpg上面,输出新的图片2_cat.jpg到D盘下面 1.java类import java.io.File; ...

  8. 使用Thumbnails压缩或放大图片大小(java)

    首先看下缩放图片的核心代码,其实只有一行而已 //ins表示ByteArrayInputStream形式的图片 //scale中的数据就是缩小或者放大的比例,比如小于1则表示压缩,大于1表示放大 // ...

  9. Thumbnails框架图片缩略处理

    最近跟师父一起做一个需求,被分配到一个优化需求,将用户上传的图片做一份缩略图存储到阿里云服务器,再把地址存入数据库. 采用了Thumbnails框架对图片进行缩略处理. 由于第一次使用这个框架 参考了 ...

最新文章

  1. BCH交易量快速增长,年内增幅超比特币和莱特币
  2. python怎么导入视频-python 给视频添加马赛克
  3. python简单代码hello-树莓派完成简单的编程(四)
  4. 从互联网的旁观者,转为互联网的建设者,推动者!!!
  5. 人工智能的炒作_为什么人工智能被过度炒作?
  6. 编写程序,定义一个方法,能够判断一个1~9999之间的数是否是回文数。
  7. python的datetime.strptime_Python strptime()和时区?
  8. 【Stimulsoft Reports Server教程】创建报表快照
  9. 063 模块的四种形式
  10. 我的世界服务器自动西瓜,我的世界自动化红石教程 全自动西瓜农场
  11. 布同:如何循序渐进学习Python语言
  12. python中国大学慕课网_高级语言程序设计(Python)中国大学慕课搜题网站
  13. HCU混动控制器,HEV串并联(IMMD) 混动车辆 simulink stateflow模型包含工况路普输入,驾驶员模型
  14. 贪心算法求解:王者荣耀购买点券最优策略
  15. tdm的应用计算机,2020计算机考研:TDM时分复用技术备考小知识点
  16. etc/xinetd.d目录介绍
  17. make j* make j4 make j8 区别
  18. 数据治理服务及数据治理应用解决方案
  19. Android从零单排之免费短信验证
  20. java压缩包加密上传,解密下载

热门文章

  1. @Transactional 详解 示例
  2. 一个简单的百度换肤效果
  3. 再见吧 buildSrc, 拥抱 Composing builds 提升 Android 编译速度
  4. 做自媒体18个月,倒欠38万,一个自媒体创作者的自述
  5. 为什么要建议用自增列做主键
  6. Java学习第一天:jkd安装、环境变量配置和第一个程序
  7. 使用opengl实现爆炸特效
  8. Java中接口如何继承接口呢?
  9. Myeclipse 是如何启动tomcat服务
  10. 机器学习——线性模型学习