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();
        }
    }
}

这里,我写了三个方法(接口),分别是/imagev1imagev2imagev3,具体说明如下:

接口名称

实现技术方案

imagev1

使用FileChannel的方法

imagev2

使用普通的io,fileinputstream->byte[]->response.outputstream

imagev3

使用ImageIO的read和write方法实现

当然,代码里并没有对file变量进行重构,这里不必纠结这些细节。另外,springbootapplication的类我没有列出,这里大家自己补充一下就行。

3. 测试

3.1 工具准备

通过上面的准备,我们基本具备测试的条件,不过再次之前,我们还需要借助jvisualvm工具进行查看。在jvisualvm里,我们需要使用Visual GC工具,默认是没有的,需要手动下载,具体方法如下图:

response设置图片格式_jvm


这里我们可以从可用插件的Visual GC进行下载。这里我已经下载,所以放到了已安装的选项卡。

3.2 启动项目

好了,现在我们可以启动刚才写的springboot项目,打开jvisualvm工具,此时,会有我们刚才启动的进成,再次双击。

response设置图片格式_java_02


在这里,我们主要用到监视Visual GC选项卡。其中Visual GC就是我们刚才下载的插件。而监视我们在这里主要用到执行垃圾回收切换下一个场景。

response设置图片格式_jvm_03

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