Spring & Spring Boot

[springboot] 파일 다운로드 구현할 때, 메모리 잡히는 것 해결 방법!!

김먼저 2022. 9. 14. 16:42
상황

 

byte[] totalByte = Files.readAllBytes(Paths.get(resourceMasterEntity.getPath()));
....
BufferedOutputStream outStream = new BufferedOutputStream(response.getOutputStream());
....
outStream.write(totalByte);

 

 

파일을 byte로 저장한 뒤,

 

 

response로 보내려는 목적으로 작성했고 실제로 작동을 했으나

 

 

서버가 터졌습니다......ㅠㅠ

 

 

이유를 알아보고자 해당 API가 동작할 때 서버 cpu와 메모리를 관찰했습니다.

 

 

 

.......?!?!

 

 

실제로 CPU 사용량의  %가 100을 훌쩍 넘겼으며 CPU 사용량이 비정상적일 때 나타나는

 

 

Controller 중복호출을 하는 현상까지 발생!!

 

 

 

원인

 

정확하게 파악하진 못했지만,

 

byte[] totalByte = Files.readAllBytes(Paths.get(resourceMasterEntity.getPath()));

 

에서 Files.readAllBytes에서 가져온 byte[]이 메모리에 쌓여있었기 때문!!

 

 

희안하게 null을 해줘도 메모리에 올라간 byte가 없어지질 않네요...(totalByte는 지역변수)

 

API 호출 할 때 마다 메모리 용량이 파일 크기만큼 커지는 Magic ㅋㅋㅋㅋㅋㅋ

 

(....다른 기술블로그보면 잘만 된다던데 왜....ㅁ너랴ㅣㅁㅈㄿㅁ냚ㄴ)

 

 

반년차도 안된 저는 빠르게 다른 방법을 강구해봅니다.....음....

 

..!!!!

 

 

해결 방법

 

FileInputStream fis = null;
        BufferedInputStream bis = null;

        //FileOutputStream fos = null;
        BufferedOutputStream bos = null;

        Date d = null;

        try{
            // FileInputStream ("파일경로") 객체를 생성 후 BufferedInputStream 객체를 생성
            fis = new FileInputStream(resourceMasterEntity.getPath());
            bis = new BufferedInputStream(fis);

            // FileOutputStream 으로 출력할 파일 ("response.getOutputStream()") 객체 생성 후 BufferedOutputStream 객체
            //fos = new FileOutputStream(response.getOutputStream());
            bos = new BufferedOutputStream(response.getOutputStream());

            int i = 0;
            d = new Date();
            long start = d.getTime();

            // 1바이트씩 읽어서 버퍼에 담는다.
            while((i=bis.read())!=-1){
                bos.write(i);
            }

            d = new Date();
            long end = d.getTime();
            log.info("File Download - Time : {} sec", (end-start)/1000);

            //bos.flush();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 마지막에 FileInputStream / FileOutputStream을 닫아준다.
            // BufferedInputStream / BufferedOutputStream 도 닫아준다.
            if(bis != null) try{bis.close();}catch(IOException e){}
            if(fis != null) try{fis.close();}catch(IOException e){}

            // BufferedOutputStream 이 close() 되면서 버퍼의 내용을 출력한다.
            if(bos != null) try{bos.close();}catch(IOException e){}
            //if(fos != null) try{fos.close();}catch(IOException e){}
        }

 

Java에서 대용량파일을 효과적으로 전송하는 방법이 2가지 정도 있는데

 

그 중 한가지인  "BufferedInputStream / BufferedOutputStream"을 사용했습니다.

 

아무튼 생각보다 속도도 빠르고 메모리도 거의 차지하지 않는 좋은 방법으로 해결했습니다!!

 

 

 

*Ref

https://xzio.tistory.com/310

 

[JAVA] ByteStream : BufferedInputStream / BufferedOutputStream (파일 복사 예제)

ByteStream : BufferedInputStream / BufferedOutputStream (파일 복사 예제) BufferedInputStream과 BufferedOutputStream은 FilterStream을 상속하여 실제 필터 기능을 제공하는 클래스이다. [JAVA] ByteStrea..

xzio.tistory.com

https://www.amitph.com/java-read-write-large-files-efficiently/

 

Java Large Files - Efficient Processing

Guide to Optimal ways of Java Large Files Processing. Compare between the fasted and the most memory efficient ways to read and write files

www.amitph.com