자바의 garbage collector는 자원이 필요하지 않을 때 JVM에서 해제를 하는 것으로 보이며, 따라서 여전히 운영체제한테 제어권을 넘기지 않는 것으로 보인다. 이 경우, 파일을 열어서 특정 처리를 한 후 파일에 연결된 변수가 파괴되어도 그 파일의 제어권이 아직 JVM에 잡혀 있어서 다른 프로그램이 그 파일을 수정하지 못하는 문제가 발생할 수도 있다. 이런 경우를 방지하기 위해서는 꼭 close 나 finalize, terminate 와 같은 함수를 호출해 준다.
우선 이 글은 다소 추정에 의한 것임을 미리 밝힌다. 자바로 Hello, World! 찍은 다음 곧바로 테스트 해보는 것일만큼 나는 자바 코딩을 거의 처음 한 것이기 때문이다.
문제의 발단은 Cytoscape 에서 엑셀로 된 node attribute 파일을 import 한 후 그 excel 파일을 수정한 후 저장하려 할 때 지속적으로 공유 위반(sharing violation)이 일어나는 것에서 시작했다. 이런 일이 꽤 잦았는데, 그 때마다 나는 운영체제나 이 노트북 자체의 문제로 생각해서 짜증만 내는 선에서 그쳤었는데, 어제 곰곰히 생각해 보니, 아마도 지역 변수를 사용하여 연 파일이 스택에서 삭제가 되었음에도 불구하고 아직 garbage collector에 의해 제대로 해제가 되지 않았기 때문이 아닐까, 하는 생각이 들었고, 그래서 테스트 해보고 검색을 해 본 결과 위 첫 단락과 같은 것을 알게 되었다.
즉, 비록 지역 변수를 이용하여 파일을 열었음에도 불구하고, 스택이 파괴되면서 그 지역 변수가 사라졌을 때도 제어권은 여전히 운영체제로 넘어가지 않을 수 있는 것이다. 즉, 비록 스택에서 파괴되면서 JVM이 그 변수에 연결된 자원을 해제했을지라도 그것은 JVM 입장에서 그렇게 한 것이고, 아직 제어권이 OS로 넘어간 것은 아니다. 그래서 그러한 경우, JVM 위에서 다시 그 파일을 열어서 수정할 수는 있겠지만(왜냐 하면 JVM 위에선 제대로 해제가 된 것이니까), 아예 별도의 다른 프로그램(JVM에 의존하지 않고 돌아가는)에서는 그 파일을 제대로 조절할 수 없게 되는 것이다. 다음과 같은 자바 코드를 보자.
private void jButton1MouseClicked(java.awt.event.MouseEvent evt) {
// TODO add your handling code here:
String curdir = System.getProperty("user.dir");
JOptionPane.showMessageDialog(mainPanel, curdir);
FileInputStream in = null;
FileOutputStream out = null;
try{
in = new FileInputStream("aa.xlsx");
out = new FileOutputStream("copied_aa_by_java.xlsx");
int c = 0;
while((c = in.read()) != -1){
out.write(c);
}
}
catch (IOException e){
if(in != null){
try {
in.close();
} catch (IOException ex) {
//Logger.getLogger(DesktopApplication1View.class.getName()).log(Level.SEVERE, null, ex);
}
}
if(out != null){
try {
out.close();
} catch (IOException ex) {
// Logger.getLogger(DesktopApplication1View.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
/*
if(in != null){
try {
in.close();
} catch (IOException ex) {
//Logger.getLogger(DesktopApplication1View.class.getName()).log(Level.SEVERE, null, ex);
}
}
if(out != null){
try {
out.close();
} catch (IOException ex) {
// Logger.getLogger(DesktopApplication1View.class.getName()).log(Level.SEVERE, null, ex);
}
}
*/
}
// TODO add your handling code here:
String curdir = System.getProperty("user.dir");
JOptionPane.showMessageDialog(mainPanel, curdir);
FileInputStream in = null;
FileOutputStream out = null;
try{
in = new FileInputStream("aa.xlsx");
out = new FileOutputStream("copied_aa_by_java.xlsx");
int c = 0;
while((c = in.read()) != -1){
out.write(c);
}
}
catch (IOException e){
if(in != null){
try {
in.close();
} catch (IOException ex) {
//Logger.getLogger(DesktopApplication1View.class.getName()).log(Level.SEVERE, null, ex);
}
}
if(out != null){
try {
out.close();
} catch (IOException ex) {
// Logger.getLogger(DesktopApplication1View.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
/*
if(in != null){
try {
in.close();
} catch (IOException ex) {
//Logger.getLogger(DesktopApplication1View.class.getName()).log(Level.SEVERE, null, ex);
}
}
if(out != null){
try {
out.close();
} catch (IOException ex) {
// Logger.getLogger(DesktopApplication1View.class.getName()).log(Level.SEVERE, null, ex);
}
}
*/
}
위 코드는 버튼의 click event에 연결한 handler이다. 매우 안정적인 것처럼 보인다. 그러나 문제가 있는데, 만약 try 구문에서 예외가 걸리지 않으면 catch 블럭이 실행이 되지 않고, 따라서 각 파일에 대한 close method가 호출되지 않는다. 만약 C++의 경우였다면 어차피 함수가 끝나면서 변수가 사라지기 때문에 close가 호출될테고, 따라서 저 함수가 실행된 이후에 다른 프로그램으로 위에서 사용된 파일(aa.xlsx)을 열고 수정하여 저장할 수 있다. 테스트를 위하여 다음과 같은 코드를 이용했다.
위의 자바 코드와 비교했을 때, 지역적으로 사용된 파일이 함수가 호출된 이후 사라진다는 점에 있어서는 완전히 똑같다. 그러나 c++의 경우엔 aa.xlsx 파일을 당연히 수정하여 저장할 수 있지만 자바 코드는 다음과 같은 접근 거부가 일어 난다.
위에서 표시된 sharing violoation은 결국 이런 의미일 것이다. 일반적으로 파일을 열 때 사용하는 공유 방법은 읽기만 공유, 쓰기만 공유, 읽기/쓰기 공유, 삭제 공유, 공유하지 않음, 이 있다. 즉, 한 프로그램이 공유하지 않는 모드로 파일을 열면 다른 프로그램이 그 파일을 읽거나 쓰기 위해 열 수 없다. 만약 읽기 공유 모드로 파일을 열었다면 그 파일을 읽기만 하는 프로그램은 동시에 그 파일을 열어서 읽을 수 있다. 그런데 위의 경우, 자바의 FileInputStream 은 아마도 읽기 공유 모드로 파일을 열테고, 그렇게 열린 파일을 엑셀에서 수정하여 '저장'하려 했으니 엑셀은 쓰기 공유까지 된 경우에만 위 작업을 마칠 수 있었겠지만 현재는 읽기만 공유되어 있었으므로 sharing violation 이 일어나는 것으로 추정된다. 1
반면 위의 자바 코드에서 주석으로 처리한 부분을 살리면 예외에 상관없이 무조건 파일을 close 하게 되는데, 그렇게 되면 위와 같은 공유 위반 문제가 발생하지 않는다. 아마도 JVM이 close method 가 호출되면 곧바로 제어권을 운영체제로 넘기는 것으로 보인다. 물론 이런 일은 c++과 같은 경우 stack에서 파괴되면서 저절로 일어나는 일일테지만 자바의 경우는 보다 명시적으로 이런 작업을 해 주어야 한다.
결과적으로 자바를 사용할 때 close나 terminate 등과 같은 method가 있을 경우 그러한 것을 명시적으로 호출해 주는 것이 좋아 보인다. 무조건 garbage collector만 믿을 수는 없겠지.
ps. 내가 여전히 자바보다는 C++에 머무는 이유는, STL과 바로 이 garbage collector 때문이다. 다 쓴 메모리는 그때그때 곧바로 해제해 주고 싶거든. 난 내가 직접 해제하는 것을 선호해서 smart pointer 같은 것도 사용하지 않는다.
- 좀 불명확한데, 한 프로그램이 파일을 열고 있는 상태에서 다른 프로그램이 그 파일을 지울 수 있다는 것을 의미한다. 리눅스에서 vi 가 그렇게 파일을 여는 것으로 보인다. vi 로 파일을 열어 놓은 상태에서 그 파일을 지울 수 있다. [본문으로]
'컴퓨터 > 전산, 그 외' 카테고리의 다른 글
운영체제 사용시 느낀 점 (0) | 2011.06.05 |
---|---|
백만 개의 데이터(파일)을 다룰 수 있다 (1) | 2011.04.02 |
웹페이지 마우스 오른쪽 클릭을 막아야 하는가? (1) | 2011.02.25 |
아... STL의 algorithm (0) | 2011.02.09 |
프로그램에 익숙해지기 (1) | 2010.11.05 |