1.前言
最近在看geowebcache源码,发现这里有这么一段代码,引起了我的注意,这段代码位于如下:
geowebcache->core->org.geowebcache.util.ResponseUtils#writeFixedResponse()
具体代码内容如下,
public static void writeFixedResponse(HttpServletResponse response, int httpCode, String contentType,
Resource resource, CacheResult cacheRes, int contentLength, RuntimeStats runtimeStats) {
response.setStatus(httpCode);
response.setContentType(contentType);
response.setContentLength((int) contentLength);
if (resource != null) {
try {
OutputStream os = response.getOutputStream();
resource.transferTo(Channels.newChannel(os));
runtimeStats.log(contentLength, cacheRes);
} catch (IOException ioe) {
log.debug("Caught IOException: " + ioe.getMessage() + "\n\n" + ioe.toString());
}
}
}
大家注意没有,这里用到了resource.transferTo(Channels.newChannel(os));
,很明显,这里用到了nio的知识。这种写法效果是否明显,我们接下来就做一个实验。
2. 代码
在这里,我们主要提供三种方式进行返回图片,这里我只是做了测试,虽然代码显得便不是那么完善,但不影响下面的测试。
2.1 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.surpass.file</groupId>
<artifactId>image</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>image</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.2 java代码
package cn.surpass.file.image.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.channels.Channels;
/**
* @author surpass
* @version 1.0
* @date 2021/12/6 11:58
*/
@Controller
public class ImageController {
@GetMapping("/imagev1")
public void downImage(HttpServletResponse response) {
File file = new File("C:\\Users\\admin\\Desktop\\ceshi.jpg");
try (FileInputStream fis = new FileInputStream(file)) {
response.setContentType("image/png");
fis.getChannel().transferTo(0,fis.available(),Channels.newChannel(response.getOutputStream()));
} catch (Exception e) {
e.printStackTrace();
}
}
@GetMapping("/imagev2")
public void downImagev2(HttpServletResponse response) {
File file = new File("C:\\Users\\admin\\Desktop\\ceshi.jpg");
try(FileInputStream fis = new FileInputStream(file)) {
response.setContentType("image/png");
byte[] bytes = new byte[fis.available()];
fis.read(bytes);
response.getOutputStream().write(bytes);
}catch (Exception e){
e.printStackTrace();
}
}
@GetMapping("/imagev3")
public void downImagev3(HttpServletResponse response) {
File file = new File("C:\\Users\\admin\\Desktop\\ceshi.jpg");
try {
BufferedImage image = ImageIO.read(file);
response.setContentType("image/png");
OutputStream os = response.getOutputStream();
ImageIO.write(image, "png", os);
os.flush();
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
这里,我写了三个方法(接口),分别是/imagev1
、imagev2
、imagev3
,具体说明如下:
接口名称 | 实现技术方案 |
imagev1 | 使用FileChannel的方法 |
imagev2 | 使用普通的io,fileinputstream->byte[]->response.outputstream |
imagev3 | 使用ImageIO的read和write方法实现 |
当然,代码里并没有对file
变量进行重构,这里不必纠结这些细节。另外,springbootapplication的类我没有列出,这里大家自己补充一下就行。
3. 测试
3.1 工具准备
通过上面的准备,我们基本具备测试的条件,不过再次之前,我们还需要借助jvisualvm工具进行查看。在jvisualvm里,我们需要使用Visual GC工具,默认是没有的,需要手动下载,具体方法如下图:
这里我们可以从可用插件的Visual GC进行下载。这里我已经下载,所以放到了已安装的选项卡。
3.2 启动项目
好了,现在我们可以启动刚才写的springboot项目,打开jvisualvm工具,此时,会有我们刚才启动的进成,再次双击。
在这里,我们主要用到监视
和Visual GC
选项卡。其中Visual GC
就是我们刚才下载的插件。而监视
我们在这里主要用到执行垃圾回收
切换下一个场景。
3.2 测试imagev1
- 我们先执行一下垃圾回收,切回VisualGC选项卡,然后再浏览器我们输入
http://127.0.0.1:8080/imagev1
地址,查看堆内存使用情况。
- 接下来我们狂点浏览器刷新按钮(大约持续20s),看看有什么效果:
通过上面的内存分析,我们发现内存有了一些增长,但是涨幅不大,而且没有发生GC(黄绿的GC time)一栏可以看出。
3.3 测试imagev2
- 我们仍然先执行一下垃圾回收,切回VisualGC选项卡,然后再浏览器我们输入
http://127.0.0.1:8080/imagev2
地址,查看堆内存使用情况,我们发现并没有明显的增长。 - 接下来我们狂点浏览器刷新按钮(大约持续20s),看看有什么效果(如下图)。此时,我们发现当开始快速点击浏览器刷新按钮之后,Eden去会迅速增加内存,直到发生一次GC。
3.4 测试imagev3
- 我们继续先执行一下垃圾回收,切回VisualGC选项卡,然后再浏览器我们输入
http://127.0.0.1:8080/imagev3
地址,查看堆内存使用情况。内存飙升,直接执行了一次GC。 - 接下来我们狂点浏览器刷新按钮(大约持续20s),看看有什么效果(如下图)。是不是有点恐怖,这里会疯狂发生GC,通过内存的变化情况,我们判断这里的大量GC属于Yong GC,即使这样,性能也会收到严重的影响。
4. 总结
序号 | 方法 | 第一次访问(内存情况) | (频繁访问) |
1 | imagev1 | 内存上升不明显 | 内存上升不明显 |
2 | imagev2 | 内存上升不明显 | 内存急剧上升,发生一次Yong GC |
3 | imagev3 | 内存急剧上升,并发生一次Yong GC | 频繁发生Yong GC |