방금 인터뷰를했는데 Java 로 메모리 누수 를 만들라는 요청을 받았습니다 .
말할 것도없이, 나는 하나를 만들기 시작하는 방법에 대한 실마리가 전혀없는 바보 같은 느낌이 들었다.
예는 무엇입니까?
방금 인터뷰를했는데 Java 로 메모리 누수 를 만들라는 요청을 받았습니다 .
말할 것도없이, 나는 하나를 만들기 시작하는 방법에 대한 실마리가 전혀없는 바보 같은 느낌이 들었다.
예는 무엇입니까?
답변:
다음은 순수 Java에서 실제 메모리 누수 (코드를 실행하여 액세스 할 수 없지만 여전히 메모리에 저장된 객체)를 생성하는 좋은 방법입니다.
ClassLoader
.new byte[1000000]
대한 강력한 참조를 정적 필드에 저장 한 다음 자신에 대한 참조를에 저장합니다 ThreadLocal
. 추가 메모리를 할당하는 것은 선택 사항이지만 (클래스 인스턴스를 사용하는 것으로 충분 함) 누출 작업이 훨씬 빨라집니다.ClassLoader
로드 된 클래스에 대한 모든 참조를 지 웁니다 .ThreadLocal
Oracle JDK에서 구현 된 방식으로 인해 메모리 누수가 발생합니다.
Thread
에는 개인 필드 threadLocals
가 있으며 실제로 스레드 로컬 값을 저장합니다.ThreadLocal
객체에 대한 약한 참조 이므로 해당 ThreadLocal
객체가 가비지 수집 된 후에 는 해당 항목이 맵에서 제거됩니다.ThreadLocal
객체를 가리킬 때 해당 객체 는 스레드가 존재하는 한 가비지 수집되거나 맵에서 제거되지 않습니다.이 예에서 강력한 참조 체인은 다음과 같습니다.
Thread
객체 → threadLocals
맵 → 클래스 예 → 클래스 → 정적 ThreadLocal
필드 → ThreadLocal
객체.
( ClassLoader
실제로 누수를 만드는 데 역할을하는 것은 아니며,이 추가 참조 체인으로 인해 누수가 악화 될뿐입니다 : example class → ClassLoader
→로드 된 모든 클래스 특히 많은 JVM 구현에서는 더욱 악화되었습니다. Java 7은 클래스와 ClassLoader
s가 permgen에 직접 할당되어 결코 가비지 수집되지 않았기 때문에 )
이 패턴의 변형은 Tomcat과 같은 응용 프로그램 컨테이너 ThreadLocal
가 어떤 식 으로든 자신을 다시 사용하는 응용 프로그램을 자주 재배치하는 경우 체와 같이 메모리가 누출되는 이유 입니다. 이것은 여러 가지 미묘한 이유로 발생할 수 있으며 종종 디버그 및 / 또는 수정하기가 어렵습니다.
업데이트 : 많은 사람들이 계속 요구하기 때문에이 동작을 보여주는 예제 코드가 있습니다 .
정적 필드 유지 객체 참조 [esp final field]
class MemorableClass {
static final ArrayList list = new ArrayList(100);
}
String.intern()
긴 문자열을 호출
String str=readString(); // read lengthy string any source db,textbox/jsp etc..
// This will place the string in memory pool from which you can't remove
str.intern();
(닫히지 않은) 열린 스트림 (파일, 네트워크 등)
try {
BufferedReader br = new BufferedReader(new FileReader(inputFile));
...
...
} catch (Exception e) {
e.printStacktrace();
}
닫히지 않은 연결
try {
Connection conn = ConnectionFactory.getConnection();
...
...
} catch (Exception e) {
e.printStacktrace();
}
기본 메소드를 통해 할당 된 메모리와 같이 JVM의 가비지 수집기에서 도달 할 수없는 영역
웹 응용 프로그램에서 일부 개체는 응용 프로그램이 명시 적으로 중지되거나 제거 될 때까지 응용 프로그램 범위에 저장됩니다.
getServletContext().setAttribute("SOME_MAP", map);
noclassgc
사용하지 않는 클래스 가비지 콜렉션을 방지하는 IBM JDK 의 옵션 과 같이 올바르지 않거나 부적절한 JVM 옵션
IBM jdk 설정을 참조하십시오 .
close()
보통 종료 자에서 호출되지 않음). 스레드는 차단 작업 일 수 있으므로). 닫지 않는 것은 좋지 않은 일이지만 누출을 일으키지 않습니다. 닫히지 않은 java.sql.Connection은 동일합니다.
intern
해시 테이블 내용 에 대한 참조가 약한 것처럼 보입니다 . 따라서, 그것은 되는 쓰레기 누수를 제대로 수집하지. (그러나 IANAJP) mindprod.com/jgloss/interned.html#GC
할 수있는 간단한 일이 잘못된 (또는 존재하지 않는)와 HashSet에 사용하는 것 hashCode()
또는 equals()
다음 "중복"을 계속 추가. 복제본을 무시하는 대신 세트는 계속 커져서 제거 할 수 없습니다.
이러한 잘못된 키 / 요소를 걸어 두려면 다음과 같은 정적 필드를 사용할 수 있습니다
class BadKey {
// no hashCode or equals();
public final String key;
public BadKey(String key) { this.key = key; }
}
Map map = System.getProperties();
map.put(new BadKey("key"), "value"); // Memory leak even if your threads die.
아래에는 잊혀진 리스너의 표준 사례, 정적 참조, 해시 맵의 가짜 / 수정 가능 키 또는 수명주기를 종료 할 기회없이 스레드가 멈춘 경우 외에 Java가 유출되는 명백한 사례가 있습니다.
File.deleteOnExit()
-항상 끈을 새고 char[]
(가) 나중에 적용되지 않도록, ; @Daniel, 투표 할 필요는 없습니다.관리되지 않는 스레드의 위험을 대부분 보여주기 위해 스레드에 집중하고 스윙을 만지고 싶지 않습니다.
Runtime.addShutdownHook
스레드 그룹 클래스의 버그로 인해 시작되지 않은 스레드가 수집되지 않을 수 있습니다. JGroup은 GossipRouter에서 누출이 있습니다.
a를 작성하지만 시작하지는 않으면 Thread
위와 동일한 카테고리로 이동합니다.
스레드를 만들면 ContextClassLoader
and 및 AccessControlContext
plus ThreadGroup
및 any를 상속받습니다. InheritedThreadLocal
이러한 모든 참조는 클래스 로더에 의해로드 된 전체 클래스와 모든 정적 참조 및 ja-ja와 함께 잠재적 누출입니다. 이 효과는 매우 단순한 ThreadFactory
인터페이스 를 특징으로하는 전체 jucExecutor 프레임 워크에서 볼 수 있지만 대부분의 개발자는 숨어있는 위험에 대한 단서가 없습니다. 또한 많은 라이브러리가 요청에 따라 스레드를 시작합니다 (업계에서 많이 사용되는 라이브러리는 너무 많습니다).
ThreadLocal
캐시; 그것들은 많은 경우에 악합니다. 모든 사람들이 ThreadLocal을 기반으로 한 간단한 캐시를 많이 보았을 것입니다. 나쁜 소식 : 스레드가 컨텍스트 ClassLoader의 수명을 예상보다 더 많이 계속한다면 순수한 작은 누출입니다. 실제로 필요한 경우가 아니면 ThreadLocal 캐시를 사용하지 마십시오.
ThreadGroup.destroy()
ThreadGroup에 스레드 자체가없는 경우 호출 하지만 여전히 하위 ThreadGroup을 유지합니다. 스레드 그룹이 상위에서 제거되지 못하게하는 누수로 인해 모든 하위 항목을 열거 할 수 없게됩니다.
WeakHashMap 및 값 (in)을 사용하면 키를 직접 참조합니다. 힙 덤프 없이는 찾기가 어렵습니다. Weak/SoftReference
보호 대상 객체에 대한 하드 참조를 유지할 수있는 모든 확장에 적용됩니다 .
java.net.URL
HTTP (S) 프로토콜과 함께 사용 하고 from (!)에서 리소스를로드합니다. 이것은 특별하며, KeepAliveCache
현재 스레드의 컨텍스트 클래스 로더를 누출시키는 ThreadGroup 시스템에 새 스레드를 만듭니다. 스레드는 살아있는 스레드가 없을 때 첫 번째 요청에 따라 생성되므로 운이 좋거나 누수가 발생할 수 있습니다. 누출은 이미 Java 7에서 수정되었으며 스레드를 작성하는 코드는 컨텍스트 클래스 로더를 올바르게 제거합니다. 더 적은 경우가 있습니다 (ImageFetcher와 같은, 또한 고정 유사한 스레드를 만드는).
생성자 를 InflaterInputStream
전달 new java.util.zip.Inflater()
하고 (예 PNGImageDecoder
를 들어) end()
팽창기를 호출하지 않습니다 . 음, 그냥 생성자와 함께 생성자를 전달하면 new
우연히 ... 그리고 close()
스트림을 호출 해도 생성자 매개 변수로 수동으로 전달 된 인플레이터가 닫히지 않습니다. 이것은 파이널 라이저가 필요하다고 생각할 때 릴리스되기 때문에 실제 누출이 아닙니다. 그 순간까지 네이티브 메모리를 너무 많이 먹어서 리눅스 oom_killer가 프로세스를 무의식적으로 죽일 수 있습니다. 주요 문제는 Java의 최종화가 매우 신뢰할 수 없으며 G1이 7.0.2까지 악화되었다는 것입니다. 이야기의 도덕 : 가능한 한 빨리 기본 리소스를 해제하십시오. 파이널 라이저는 너무 가난합니다.
와 같은 경우입니다 java.util.zip.Deflater
. Deflater는 Java에서 메모리가 부족한 상태이므로 훨씬 더 나쁩니다. 즉, 항상 수백 KB의 기본 메모리를 할당하는 15 비트 (최대) 및 8 개의 메모리 레벨 (9는 최대)을 사용합니다. 다행히도 Deflater
널리 사용되지 않으며 JDK에는 오용이 없습니다. 항상 전화를 end()
수동으로 작성하는 경우 Deflater
나 Inflater
. 마지막 두 가지 중 가장 중요한 부분은 일반적인 프로파일 링 도구를 통해 찾을 수 없다는 것입니다.
(요청에 따라 더 많은 시간 낭비자를 추가 할 수 있습니다.)
행운을 빌어 안전 유지; 누출은 악하다!
Creating but not starting a Thread...
Yikes, 나는 몇 세기 전에 이것에 심하게 물렸다! (Java 1.3)
unstarted
뿐 아니라 스레드 그룹이 파괴되는 것을 막아줍니다 (더 적은 악하지만 여전히 누출)
ThreadGroup.destroy()
ThreadGroup에 스레드 자체가 없을 때 호출 하는 것은 ..." 는 매우 미묘한 버그입니다. 내 컨트롤 GUI에서 스레드를 열거해도 아무것도 보이지 않았지만 스레드 그룹과 아마도 적어도 하나의 자식 그룹은 사라지지 않았기 때문에 나는 이것을 몇 시간 동안 쫓아 갔다.
여기에있는 대부분의 예는 "너무 복잡합니다". 그들은 엣지 케이스입니다. 이 예제에서 프로그래머는 실수 (등호 / 해시 코드를 재정의하지 않는 것과 같이)를 실수로 만들었거나 JVM / JAVA (정적으로 클래스로드 ...)의 코너 사례에 물린 적이 있습니다. 나는 그것이 면접관이 원하거나 가장 일반적인 경우가 아니라고 생각합니다.
그러나 메모리 누수에 대한 간단한 사례가 있습니다. 가비지 수집기는 더 이상 참조되지 않은 항목 만 해제합니다. 우리는 Java 개발자로서 메모리에 관심이 없습니다. 필요할 때 할당하고 자동으로 해제합니다. 좋아.
그러나 오래 지속되는 응용 프로그램은 공유 상태를 갖는 경향이 있습니다. 정적, 단일 등 무엇이든 될 수 있습니다. 종종 사소한 응용 프로그램은 복잡한 객체 그래프를 만드는 경향이 있습니다. 참조를 null로 설정하는 것을 잊어 버리거나 컬렉션에서 하나의 객체를 제거하는 것을 잊어 버리면 메모리 누수가 발생하기에 충분합니다.
물론 모든 종류의 리스너 (예 : UI 리스너), 캐시 또는 오래 지속되는 공유 상태는 올바르게 처리하지 않으면 메모리 누수가 발생하는 경향이 있습니다. 이해할 수있는 것은 Java 코너 사례가 아니거나 가비지 수집기의 문제가 아니라는 것입니다. 디자인 문제입니다. 수명이 긴 객체에 리스너를 추가하도록 설계되었지만 더 이상 필요하지 않은 경우 리스너를 제거하지 않습니다. 우리는 객체를 캐시하지만 캐시에서 객체를 제거하는 전략은 없습니다.
계산에 필요한 이전 상태를 저장하는 복잡한 그래프가있을 수 있습니다. 그러나 이전 상태 자체는 이전 상태와 연결되어 있습니다.
우리는 SQL 연결이나 파일을 닫아야합니다. null에 대한 적절한 참조를 설정하고 컬렉션에서 요소를 제거해야합니다. 적절한 캐싱 전략 (최대 메모리 크기, 요소 수 또는 타이머)이 있어야합니다. 리스너에게 알릴 수있는 모든 객체는 addListener 및 removeListener 메소드를 모두 제공해야합니다. 이러한 알리미가 더 이상 사용되지 않으면 리스너 목록을 지워야합니다.
메모리 누수는 실제로 가능하며 완벽하게 예측할 수 있습니다. 특별한 언어 기능이나 코너 케이스가 필요 없습니다. 메모리 누수는 무언가 빠졌거나 설계 문제가 있음을 나타내는 지표입니다.
WeakReference
있습니다. 하나에서 다른. 객체 참조에 여분의 비트가있는 경우 "대상에 대한 관심"표시기를 갖는 것이 도움이 될 수 있습니다.
PhantomReference
물건에 관심이있는 사람이없는 것으로 밝혀지면 시스템에 (와 비슷한 방법으로 ) 알림을 제공하도록 합니다. WeakReference
다소 가까워 지지만 사용하기 전에 강력한 참조로 변환해야합니다. 강한 참조가 존재하는 동안 GC주기가 발생하면 목표가 유용한 것으로 간주됩니다.
답은 전적으로 면접관이 생각한 것에 달려 있습니다.
실제로 Java 유출이 가능합니까? 물론 다른 대답에는 많은 예가 있습니다.
그러나 여러 가지 메타 질문이있을 수 있습니까?
귀하의 메타 질문을 "이 인터뷰 상황에서 사용할 수있는 답변은 무엇입니까?" 따라서 Java 대신 인터뷰 기술에 중점을 둘 것입니다. 인터뷰에서 질문에 대한 답을 알지 못하는 상황을 Java 누출 방법을 알아야 할 필요가있는 곳보다 반복 할 가능성이 더 큽니다. 희망적으로 이것은 도움이 될 것입니다.
면접을 위해 개발할 수있는 가장 중요한 기술 중 하나는 질문을 적극적으로 듣고 면담 자와 함께 그들의 의도를 추출하는 법을 배우는 것입니다. 이를 통해 원하는 방식으로 질문에 답변 할 수있을뿐만 아니라 의사 소통에 필수적인 의사 소통 능력이 있음을 알 수 있습니다. 그리고 동등한 재능을 가진 많은 개발자들 사이에서 선택을 할 때마다, 나는 매번 응답하기 전에 듣고, 생각하고, 이해하는 사람을 고용 할 것입니다.
다음은 JDBC를 이해하지 못하는 경우 무의미한 예 입니다. 또는 JDBC는 가까운 개발자 기대 방법 이상 Connection
, Statement
그리고 ResultSet
그것들을 폐기하거나 참조를 잃고, 대신의 구현에 의존하기 전에 인스턴스를 finalize
.
void doWork()
{
try
{
Connection conn = ConnectionFactory.getConnection();
PreparedStatement stmt = conn.preparedStatement("some query"); // executes a valid query
ResultSet rs = stmt.executeQuery();
while(rs.hasNext())
{
... process the result set
}
}
catch(SQLException sqlEx)
{
log(sqlEx);
}
}
위의 문제는 Connection
객체가 닫히지 않았으므로 가비지 수집기가 돌아 와서 도달 할 수 없을 때까지 물리적 연결이 열린 상태로 유지된다는 것입니다. GC는 finalize
메소드 를 호출 하지만 finalize
적어도 구현 된 것과 같은 방식으로 구현하지 않은 JDBC 드라이버 Connection.close
가 있습니다. 결과적으로 도달 할 수없는 오브젝트가 수집되어 메모리가 회수되는 동안 오브젝트와 연관된 자원 (메모리 포함) Connection
이 단순히 회수되지 않을 수 있습니다.
이러한 경우 Connection
의 finalize
방법은 없습니다 정리 모든 것을 수행, 하나는 실제로 데이터베이스 서버가 결국 연결이 살아 아니라는 것을 파악 될 때까지 데이터베이스 서버에 대한 물리적 연결 (여러 가비지 컬렉션 사이클을 지속됩니다 사실을 발견 그것 경우 ) 닫혀 있어야합니다.
JDBC 드라이버가 구현하더라도 finalize
최종화 중에 예외가 발생할 수 있습니다. 결과적으로 현재 "휴면"오브젝트와 연관된 메모리 finalize
는 한 번만 호출되므로 보증 되지 않습니다 .
위의 객체 종료 중 예외가 발생하는 시나리오는 메모리 누수 (객체 부활)로 이어질 수있는 다른 시나리오와 관련이 있습니다. 개체 부활은 종종 다른 개체에서 마무리되지 않은 개체에 대한 강력한 참조를 만들어 의도적으로 수행됩니다. 객체 부활이 잘못 사용되면 다른 메모리 누수 소스와 함께 메모리 누수가 발생합니다.
당신이 쓸 수있는 더 많은 예가 있습니다-
List
목록에 추가 만하고 삭제하지 않는 인스턴스 관리 (더 이상 필요없는 요소는 제거해야 함) 또는Socket
s 또는 File
s를 열지 만 더 이상 필요하지 않을 때 닫지 않습니다 (위의 Connection
클래스 관련 예제와 유사 ).Connection.close
모든 SQL 호출의 finally 블록에 넣을 때까지 SQL 데이터베이스의 연결 제한에 많이 도달했습니다 . 여분의 재미를 위해 데이터베이스에 대한 너무 많은 호출을 방지하기 위해 Java 측에서 잠금이 필요한 장기 실행 Oracle 저장 프로 시저를 호출했습니다.
잠재적 인 메모리 누수의 가장 간단한 예 중 하나이며이를 피하는 방법은 ArrayList.remove (int)의 구현입니다.
public E remove(int index) {
RangeCheck(index);
modCount++;
E oldValue = (E) elementData[index];
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index + 1, elementData, index,
numMoved);
elementData[--size] = null; // (!) Let gc do its work
return oldValue;
}
직접 구현했다면 더 이상 사용되지 않는 배열 요소를 지우고 elementData[--size] = null
싶습니까 ( )? 그 참조는 거대한 물체를 살릴 수 있습니다 ...
더 이상 필요하지 않은 객체에 대한 참조를 유지할 때마다 메모리 누수가 발생합니다. Java에서 메모리 누수 가 어떻게 나타나는지와 이에 대해 수행 할 수있는 작업에 대한 예제는 Java 프로그램에서 메모리 누수 처리를 참조하십시오 .
...then the question of "how do you create a memory leak in X?" becomes meaningless, since it's possible in any language.
나는 당신이 그 결론을 그리는 방법을 모르겠습니다 . 있다어떤 정의로든 Java에서 메모리 누수를 만드는 방법 더 적습니다 . 여전히 유효한 질문입니다.
sun.misc.Unsafe 클래스 를 사용하면 메모리 누수가 발생할 수 있습니다. 실제로이 서비스 클래스는 다른 표준 클래스 (예 : java.nio 클래스)에서 사용됩니다. 이 클래스의 인스턴스를 직접 만들 수는 없지만 리플렉션을 사용하여이를 수행 할 수 있습니다 .
Eclipse IDE에서 코드가 컴파일되지 않음-명령을 사용하여 컴파일 javac
(컴파일 중에 경고가 표시됨)
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import sun.misc.Unsafe;
public class TestUnsafe {
public static void main(String[] args) throws Exception{
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field f = unsafeClass.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
System.out.print("4..3..2..1...");
try
{
for(;;)
unsafe.allocateMemory(1024*1024);
} catch(Error e) {
System.out.println("Boom :)");
e.printStackTrace();
}
}
}
여기에서 답변을 복사 할 수 있습니다 : Java에서 메모리 누수를 일으키는 가장 쉬운 방법은 무엇입니까?
"컴퓨터 과학에서 메모리 누수 (또는이 맥락에서 누수)는 컴퓨터 프로그램이 메모리를 소비하지만 운영 체제로 다시 해제 할 수 없을 때 발생합니다." (위키 백과)
쉬운 대답은 : 당신은 할 수 없습니다. Java는 자동 메모리 관리를 수행하며 필요하지 않은 자원을 비 웁니다. 이것을 막을 수는 없습니다. 항상 리소스를 해제 할 수 있습니다. 수동 메모리 관리가있는 프로그램에서는 다릅니다. malloc ()을 사용하여 C에서 약간의 메모리를 얻을 수 있습니다. 메모리를 비우려면 malloc이 리턴 한 포인터가 필요하며 free ()를 호출하십시오. 그러나 더 이상 포인터를 쓰지 않거나 (수명을 초과하거나 수명을 초과 한 경우) 불행히도이 메모리를 해제 할 수 없으므로 메모리 누수가 발생합니다.
지금까지 다른 모든 대답은 실제로 메모리 누수가 아니라는 정의에 있습니다. 그들은 모두 무의미한 물건으로 메모리를 빨리 채우는 것을 목표로합니다. 그러나 언제든지 생성 한 객체를 역 참조하여 메모리를 확보 할 수 있습니다-> NO LEAK. acconrad의 답변 은 그의 솔루션이 가비지 수집기를 끝없는 루프로 강제로 "충돌"시키는 것이므로 효과적으로 인정해야하지만 매우 가깝습니다.
긴 대답은 다음과 같습니다. JNI를 사용하여 Java 용 라이브러리를 작성하면 메모리 누수가 발생할 수 있습니다. JNI는 수동 메모리 관리가 가능하므로 메모리 누수가 발생할 수 있습니다. 이 라이브러리를 호출하면 Java 프로세스에서 메모리가 누출됩니다. 또는 JVM에 버그가있어 JVM이 메모리를 잃을 수 있습니다. JVM에 버그가있을 수 있습니다. 가비지 수집이 그다지 쉽지 않기 때문에 알려진 버그가있을 수도 있지만 여전히 버그입니다. 설계 상으로는 불가능합니다. 그러한 버그에 의해 영향을받는 자바 코드를 요구할 수도 있습니다. 미안하지만 하나도 모르고 다음 Java 버전에서는 더 이상 버그가 아닐 수 있습니다.
다음은 http://wiki.eclipse.org/Performance_Bloopers#String.substring.28.29 를 통한 단순하고 불쾌한 것 입니다.
public class StringLeaker
{
private final String muchSmallerString;
public StringLeaker()
{
// Imagine the whole Declaration of Independence here
String veryLongString = "We hold these truths to be self-evident...";
// The substring here maintains a reference to the internal char[]
// representation of the original string.
this.muchSmallerString = veryLongString.substring(0, 1);
}
}
부분 문자열은 원래의 훨씬 긴 문자열의 내부 표현을 나타내므로 원본은 메모리에 유지됩니다. 따라서 StringLeaker를 사용하는 한 단일 문자 문자열을 잡고 있다고 생각하더라도 전체 원본 문자열도 메모리에 있습니다.
원래 문자열에 대한 원치 않는 참조를 저장하지 않는 방법은 다음과 같습니다.
...
this.muchSmallerString = new String(veryLongString.substring(0, 1));
...
추가적인 악영향을 .intern()
위해 하위 문자열 을 사용할 수도 있습니다 .
...
this.muchSmallerString = veryLongString.substring(0, 1).intern();
...
그렇게하면 StringLeaker 인스턴스가 삭제 된 후에도 원래의 긴 문자열과 파생 된 하위 문자열이 모두 메모리에 유지됩니다.
muchSmallerString
( StringLeaker
객체가 파괴 되기 때문에 ) 긴 문자열도 해제됩니다. 내가 메모리 누수라고하는 것은이 JVM 인스턴스에서 절대 해제 될 수없는 메모리입니다. 그러나 메모리를 해제하는 방법을 스스로 보여주었습니다 this.muchSmallerString=new String(this.muchSmallerString)
. 실제 메모리 누수로 수행 할 수있는 작업이 없습니다.
intern
그렇지 경우는 "메모리 누수"보다 "메모리 놀라움"일 수 있습니다. .intern()
그러나 하위 문자열을 사용하면 더 긴 문자열에 대한 참조가 유지되고 해제 될 수없는 상황이 발생합니다.
GUI 코드에서 이것의 일반적인 예는 위젯 / 컴포넌트를 생성하고 정적 / 애플리케이션 범위 객체에 리스너를 추가 한 후 위젯이 파괴 될 때 리스너를 제거하지 않는 경우입니다. 메모리 누수뿐만 아니라 듣고있는 것이 이벤트를 발생시킬 때 성능이 저하 될뿐 아니라 모든 이전 리스너도 호출됩니다.
서블릿 컨테이너 (Tomcat, Jetty, Glassfish 등)에서 실행중인 웹 응용 프로그램을 가져옵니다. 앱을 10 회 또는 20 회 연속으로 재배포하십시오 (서버의 자동 배포 디렉토리에서 WAR을 간단히 터치하기에 충분할 수 있습니다).
아무도 실제로 이것을 테스트하지 않으면 응용 프로그램 자체를 정리하지 않았기 때문에 몇 번의 재배포 후에 OutOfMemoryError가 발생할 가능성이 높습니다. 이 테스트를 통해 서버에서 버그를 발견 할 수도 있습니다.
문제는 컨테이너의 수명이 응용 프로그램의 수명보다 길다는 것입니다. 컨테이너가 응용 프로그램의 객체 또는 클래스에 대한 모든 참조를 가비지 수집 할 수 있는지 확인해야합니다.
웹앱의 배포 제거를 유지하는 참조가 하나만있는 경우 해당 클래스 로더와 결과적으로 웹앱의 모든 클래스를 가비지 수집 할 수 없습니다.
응용 프로그램에서 시작한 스레드, ThreadLocal 변수, 로깅 어 펜더는 클래스 로더 누수를 일으키는 일반적인 용의자 중 일부입니다.
아마도 JNI를 통해 외부 네이티브 코드를 사용하고 있습니까?
순수한 Java를 사용하면 거의 불가능합니다.
그러나 이는 더 이상 메모리에 액세스 할 수없는 "표준"유형의 메모리 누수에 관한 것이지만 여전히 응용 프로그램에서 소유합니다. 대신 사용하지 않는 객체에 대한 참조를 유지하거나 나중에 닫지 않고 스트림을 열 수 있습니다.
PermGen 및 XML 구문 분석과 관련하여 멋진 "메모리 누수"가 발생했습니다. 태그 이름에 String.intern ()을 사용하여 XML 구문 분석기를 사용하여 XML 구문 분석기를 사용했습니다 (어떤 것이 있는지 기억이 나지 않습니다). 고객 중 한 명이 XML 속성이나 텍스트가 아닌 태그 이름으로 데이터 값을 저장하는 것이 좋았으므로 다음과 같은 문서가있었습니다.
<data>
<1>bla</1>
<2>foo</>
...
</data>
실제로, 그들은 숫자가 아닌 더 긴 텍스트 ID (약 20 자)를 사용했습니다.이 ID는 고유하고 하루에 10-15 백만의 비율로 들어 왔습니다. 그것은 하루에 200MB의 쓰레기를 만듭니다. 다시는 필요하지 않으며 GCed도 아닙니다 (PermGen에 있기 때문에). permgen을 512MB로 설정 했으므로 메모리 부족 예외 (OOME)가 도착하는 데 약 2 일이 걸렸습니다.
메모리 누수 란 무엇입니까?
전형적인 예 :
객체 캐시는 일을 망칠 수있는 좋은 출발점입니다.
private static final Map<String, Info> myCache = new HashMap<>();
public void getInfo(String key)
{
// uses cache
Info info = myCache.get(key);
if (info != null) return info;
// if it's not in cache, then fetch it from the database
info = Database.fetch(key);
if (info == null) return null;
// and store it in the cache
myCache.put(key, info);
return info;
}
캐시가 커지고 커집니다. 그리고 곧 전체 데이터베이스가 메모리로 빨려 들어갑니다. 더 나은 디자인은 LRUMap을 사용합니다 (최근에 사용한 객체 만 캐시에 유지).
물론 일을 훨씬 더 복잡하게 만들 수 있습니다.
자주 발생하는 일 :
이 Info 객체에 다른 객체에 대한 참조가 있고 다른 객체에 대한 참조가있는 경우 어떤 방식으로도 (디자인이 잘못되어) 일종의 메모리 누수로 간주 할 수 있습니다.
아무도 내부 클래스 예제를 사용하지 않았다는 것이 흥미로웠다. 내부 수업이있는 경우 그것은 본질적으로 포함하는 클래스에 대한 참조를 유지합니다. 물론 Java WILL이 결국 정리하기 때문에 기술적으로 메모리 누수가 아닙니다. 그러나 이로 인해 클래스가 예상보다 오래 걸려있을 수 있습니다.
public class Example1 {
public Example2 getNewExample2() {
return this.new Example2();
}
public class Example2 {
public Example2() {}
}
}
이제 Example1을 호출하고 Example2를 폐기하는 Example2를 가져와도 본질적으로 여전히 Example1 오브젝트에 대한 링크가 있습니다.
public class Referencer {
public static Example2 GetAnExample2() {
Example1 ex = new Example1();
return ex.getNewExample2();
}
public static void main(String[] args) {
Example2 ex = Referencer.GetAnExample2();
// As long as ex is reachable; Example1 will always remain in memory.
}
}
또한 특정 시간보다 오래 존재하는 변수가 있다면 소문도 들었습니다. Java는 항상 존재한다고 가정하고 코드에서 더 이상 도달 할 수 없으면 실제로 정리하려고 시도하지 않습니다. 그러나 그것은 완전히 검증되지 않았습니다.
최근에 log4j에 의한 메모리 누수 상황이 발생했습니다.
Log4j에는 NDC (Nested Diagnostic Context) 라고하는이 메커니즘이 있으며, 이는 서로 다른 소스의 인터리브 로그 출력을 구별하는 도구입니다. NDC가 작동하는 단위는 스레드이므로 다른 스레드의 로그 출력을 개별적으로 구분합니다.
스레드 별 태그를 저장하기 위해 log4j의 NDC 클래스는 스레드 객체 자체가 키를 갖는 Hashtable을 사용합니다 (스레드 ID가 아니라). 따라서 NDC 태그가 스레드에 매달린 모든 객체를 메모리에 유지할 때까지 객체는 또한 메모리에 남아 있습니다. 웹 애플리케이션에서는 NDC를 사용하여 로그 ID를 요청 ID로 태그 지정하여 단일 요청과 로그를 별도로 구분합니다. NDC 태그를 스레드와 연결하는 컨테이너는 요청에서 응답을 반환하는 동안 NDC 태그를 제거합니다. 요청을 처리하는 동안 다음 코드와 같은 하위 스레드가 생성 될 때 문제가 발생했습니다.
pubclic class RequestProcessor {
private static final Logger logger = Logger.getLogger(RequestProcessor.class);
public void doSomething() {
....
final List<String> hugeList = new ArrayList<String>(10000);
new Thread() {
public void run() {
logger.info("Child thread spawned")
for(String s:hugeList) {
....
}
}
}.start();
}
}
따라서 NDC 컨텍스트는 생성 된 인라인 스레드와 연관되었습니다. 이 NDC 컨텍스트의 핵심이었던 스레드 개체는 hugeList 개체가 매달려있는 인라인 스레드입니다. 따라서 스레드가 수행 한 작업을 완료 한 후에도 hugeList에 대한 참조는 NDC 컨텍스트 Hastable에 의해 유지되어 메모리 누수가 발생합니다.
면접관은 아마도 아래 코드와 같은 순환 참조를 찾고 있었을 것입니다 (실제로 참조 계산을 사용하는 아주 오래된 JVM에서는 메모리가 누출되어 더 이상은 아닙니다). 그러나 매우 모호한 질문이므로 JVM 메모리 관리에 대한 이해를 과시 할 수있는 최고의 기회입니다.
class A {
B bRef;
}
class B {
A aRef;
}
public class Main {
public static void main(String args[]) {
A myA = new A();
B myB = new B();
myA.bRef = myB;
myB.aRef = myA;
myA=null;
myB=null;
/* at this point, there is no access to the myA and myB objects, */
/* even though both objects still have active references. */
} /* main */
}
그런 다음 참조 카운트를 사용하면 위 코드에서 메모리가 누출 될 수 있다고 설명 할 수 있습니다. 그러나 대부분의 최신 JVM은 더 이상 참조 계산을 사용하지 않으며 대부분이 메모리를 수집하는 스윕 가비지 수집기를 사용합니다.
다음으로 다음과 같이 기본 고유 자원이있는 오브젝트 작성에 대해 설명 할 수 있습니다.
public class Main {
public static void main(String args[]) {
Socket s = new Socket(InetAddress.getByName("google.com"),80);
s=null;
/* at this point, because you didn't close the socket properly, */
/* you have a leak of a native descriptor, which uses memory. */
}
}
그런 다음 이것이 기술적으로 메모리 누수라고 설명 할 수 있지만 실제로 누수는 기본 원시 자원을 할당하는 JVM의 원시 코드로 인해 발생하며 Java 코드에서 해제되지 않았습니다.
하루가 끝나면 최신 JVM을 사용하여 일반적인 JVM 인식 범위를 벗어난 기본 리소스를 할당하는 Java 코드를 작성해야합니다.
정적지도를 만들고 하드 참조를 계속 추가하십시오. 그것들은 결코 GC되지 않을 것입니다.
public class Leaker {
private static final Map<String, Object> CACHE = new HashMap<String, Object>();
// Keep adding until failure.
public static void addToCache(String key, Object value) { Leaker.CACHE.put(key, value); }
}
해당 클래스의 finalize 메소드에서 클래스의 새 인스턴스를 작성하여 이동 메모리 누수를 작성할 수 있습니다. 종료자가 여러 인스턴스를 만드는 경우 보너스 포인트. 다음은 힙 크기에 따라 몇 초에서 몇 분 사이에 전체 힙을 유출하는 간단한 프로그램입니다.
class Leakee {
public void check() {
if (depth > 2) {
Leaker.done();
}
}
private int depth;
public Leakee(int d) {
depth = d;
}
protected void finalize() {
new Leakee(depth + 1).check();
new Leakee(depth + 1).check();
}
}
public class Leaker {
private static boolean makeMore = true;
public static void done() {
makeMore = false;
}
public static void main(String[] args) throws InterruptedException {
// make a bunch of them until the garbage collector gets active
while (makeMore) {
new Leakee(0).check();
}
// sit back and watch the finalizers chew through memory
while (true) {
Thread.sleep(1000);
System.out.println("memory=" +
Runtime.getRuntime().freeMemory() + " / " +
Runtime.getRuntime().totalMemory());
}
}
}
나는 아무도 이것을 아직 말하지 않았다고 생각합니다 : finalize () 메서드를 재정 의하여 finalize ()이 참조를 어딘가에 저장하여 객체를 부활시킬 수 있습니다. 가비지 콜렉터는 오브젝트에서 한 번만 호출되므로 오브젝트는 절대 파괴되지 않습니다.
finalize()
호출되지 않지만 참조가 더 이상 없으면 객체가 수집됩니다. 가비지 콜렉터도 '호출'되지 않습니다.
finalize()
메소드는 JVM에서 한 번만 호출 할 수 있지만 오브젝트를 부활시킨 다음 다시 참조 해제하면 다시 쓰레기를 수집 할 수는 없습니다. finalize()
메소드 에 자원 종료 코드가있는 경우이 코드가 다시 실행되지 않아 메모리 누수가 발생할 수 있습니다.
최근에 더 미묘한 종류의 리소스 누수가 발생했습니다. 클래스 로더의 getResourceAsStream을 통해 리소스를 열면 입력 스트림 핸들이 닫히지 않았습니다.
음, 당신은 바보라고 말할 수 있습니다.
글쎄, 이것이 흥미로운 점은이 방법으로 JVM의 힙이 아닌 기본 프로세스의 힙 메모리를 누출시킬 수 있다는 것입니다.
Java 코드에서 참조 할 파일이있는 jar 파일 만 있으면됩니다. jar 파일이 클수록 메모리가 더 빨리 할당됩니다.
다음 클래스를 사용하여 이러한 항아리를 쉽게 만들 수 있습니다.
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class BigJarCreator {
public static void main(String[] args) throws IOException {
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File("big.jar")));
zos.putNextEntry(new ZipEntry("resource.txt"));
zos.write("not too much in here".getBytes());
zos.closeEntry();
zos.putNextEntry(new ZipEntry("largeFile.out"));
for (int i=0 ; i<10000000 ; i++) {
zos.write((int) (Math.round(Math.random()*100)+20));
}
zos.closeEntry();
zos.close();
}
}
BigJarCreator.java라는 파일에 붙여넣고 명령 행에서 컴파일하고 실행하십시오.
javac BigJarCreator.java
java -cp . BigJarCreator
정보 : 현재 작업중인 디렉토리에 두 개의 파일이있는 jar 아카이브가 있습니다.
두 번째 클래스를 만들어 봅시다 :
public class MemLeak {
public static void main(String[] args) throws InterruptedException {
int ITERATIONS=100000;
for (int i=0 ; i<ITERATIONS ; i++) {
MemLeak.class.getClassLoader().getResourceAsStream("resource.txt");
}
System.out.println("finished creation of streams, now waiting to be killed");
Thread.sleep(Long.MAX_VALUE);
}
}
이 클래스는 기본적으로 아무것도하지 않지만 참조되지 않은 InputStream 객체를 만듭니다. 이러한 객체는 즉시 가비지 수집되므로 힙 크기에 영향을 미치지 않습니다. 이 예제에서는 jar 파일에서 기존 리소스를로드하는 것이 중요하며 여기서 크기는 중요합니다!
의심스러운 경우 위의 클래스를 컴파일하고 시작하지만 적절한 힙 크기 (2MB)를 선택하십시오.
javac MemLeak.java
java -Xmx2m -classpath .:big.jar MemLeak
참조가 유지되지 않기 때문에 여기에서 OOM 오류가 발생하지 않으며 위 예제에서 ITERATIONS를 선택한 크기에 관계없이 응용 프로그램이 계속 실행됩니다. 응용 프로그램이 대기 명령에 도달하지 않으면 프로세스의 메모리 소비 (맨 위 (RES / RSS) 또는 프로세스 탐색기)가 증가합니다. 위의 설정에서 약 150MB의 메모리를 할당합니다.
응용 프로그램이 안전하게 재생되도록하려면 입력 스트림이 생성 된 곳에서 바로 닫으십시오.
MemLeak.class.getClassLoader().getResourceAsStream("resource.txt").close();
프로세스는 반복 횟수와 상관없이 35MB를 초과하지 않습니다.
아주 간단하고 놀라운.
많은 사람들이 제안했듯이, Resource Leaks는 JDBC 예제와 같이 상당히 쉽게 발생합니다. 실제 메모리 누수는 좀 더 어려워집니다. 특히 JVM의 깨진 비트에 의존하지 않는 경우 ...
설치 공간이 매우 커서 객체에 액세스 할 수없는 객체를 만드는 아이디어는 실제 메모리 누수가 아닙니다. 아무것도 접근 할 수 없다면 가비지 수집되며, 접근 할 수 있다면 누출이 아닙니다 ...
한 가지 방법은 을 사용 하고 여전히 않는 경우 나도 몰라 - - 일 생각으로는 세 깊은 원형 체인을하는 것입니다. 객체 A에서 객체 B에 대한 참조가있는 것처럼 객체 B에는 객체 C에 대한 참조가 있으며 객체 C는 객체 A에 대한 참조가 있습니다. -A와 B가 다른 것에 의해 접근 할 수 없지만 3 방향 체인을 처리 할 수없는 경우 안전하게 수집 할 수 있습니다.
잠재적으로 큰 메모리 누수를 생성하는 또 다른 방법은에 대한 참조를 보유 Map.Entry<K,V>
하는 것 TreeMap
입니다.
왜 이것이 TreeMap
s 에만 적용되는지를 판단하기는 어렵지만 구현을 살펴보면 그 이유는 다음과 같습니다. TreeMap.Entry
상점은 형제를 참조하므로 a TreeMap
를 수집 할 준비가되었지만 다른 클래스는 그것 Map.Entry
의 전체 맵은 메모리에 유지됩니다.
실제 시나리오 :
큰 TreeMap
데이터 구조 를 반환하는 DB 쿼리가 있다고 상상해보십시오 . 사람들은 일반적으로 TreeMap
요소 삽입 순서가 유지되므로 s를 사용 합니다.
public static Map<String, Integer> pseudoQueryDatabase();
쿼리를 여러 번 호출하고 각 쿼리에 대해 (즉, 각 Map
반환 에 대해 ) Entry
어딘가에 저장 하면 메모리가 계속 증가합니다.
다음 랩퍼 클래스를 고려하십시오.
class EntryHolder {
Map.Entry<String, Integer> entry;
EntryHolder(Map.Entry<String, Integer> entry) {
this.entry = entry;
}
}
신청:
public class LeakTest {
private final List<EntryHolder> holdersCache = new ArrayList<>();
private static final int MAP_SIZE = 100_000;
public void run() {
// create 500 entries each holding a reference to an Entry of a TreeMap
IntStream.range(0, 500).forEach(value -> {
// create map
final Map<String, Integer> map = pseudoQueryDatabase();
final int index = new Random().nextInt(MAP_SIZE);
// get random entry from map
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue().equals(index)) {
holdersCache.add(new EntryHolder(entry));
break;
}
}
// to observe behavior in visualvm
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
public static Map<String, Integer> pseudoQueryDatabase() {
final Map<String, Integer> map = new TreeMap<>();
IntStream.range(0, MAP_SIZE).forEach(i -> map.put(String.valueOf(i), i));
return map;
}
public static void main(String[] args) throws Exception {
new LeakTest().run();
}
}
각 pseudoQueryDatabase()
호출 후에 는 map
인스턴스를 수집 할 준비가되어야하지만 적어도 하나 Entry
는 다른 곳에 저장되므로 발생하지 않습니다 .
jvm
설정 에 따라으로 인해 초기 단계에서 응용 프로그램이 중단 될 수 있습니다 OutOfMemoryError
.
이 visualvm
그래프에서 메모리가 어떻게 계속 커지는 지 알 수 있습니다 .
해시 된 데이터 구조 ( HashMap
)에서도 마찬가지 입니다.
를 사용할 때의 그래프 HashMap
입니다.
해결책? 를 저장하는 대신 키 / 값을 직접 저장하십시오 (아마도 이미 수행했듯이) Map.Entry
.
나는 더 광범위한 기준을 작성했습니다 여기에 .
스레드는 종료 될 때까지 수집되지 않습니다. 그들은 역할을 뿌리 가비지 콜렉션. 그것들은 단순히 잊어 버렸거나 참조를 지우는 것으로 간단하게 되찾지 않는 몇 안되는 물건 중 하나입니다.
작업자 스레드를 종료하는 기본 패턴은 스레드에서 볼 수있는 일부 조건 변수를 설정하는 것입니다. 스레드는 변수를 주기적으로 확인하고이를 종료 신호로 사용할 수 있습니다. 변수가 선언 volatile
되지 않으면 스레드가 변수 변경 사항을 볼 수 없으므로 종료를 알 수 없습니다. 또는 일부 스레드가 공유 객체를 업데이트하려고하지만 잠금을 시도하는 동안 교착 상태가 발생한다고 상상해보십시오.
소수의 스레드 만있는 경우 프로그램이 제대로 작동하지 않기 때문에 이러한 버그가 분명합니다. 필요에 따라 더 많은 스레드를 작성하는 스레드 풀이있는 경우 사용되지 않는 / 중단 된 스레드가 발견되지 않고 무한정 누적되어 메모리 누수가 발생합니다. 스레드는 응용 프로그램에서 다른 데이터를 사용할 가능성이 있으므로 직접 참조하는 데이터가 수집되는 것을 방지합니다.
장난감 예제로 :
static void leakMe(final Object object) {
new Thread() {
public void run() {
Object o = object;
for (;;) {
try {
sleep(Long.MAX_VALUE);
} catch (InterruptedException e) {}
}
}
}.start();
}
System.gc()
당신이 좋아하는 모든 것을 부르십시오 . 그러나 전달 된 객체 leakMe
는 결코 죽지 않을 것입니다.
(* 편집 *)
올바른 예제는 스레드가 풀링되는 환경에서 ThreadLocal 변수를 사용할 수 있다고 생각합니다.
예를 들어, 서블릿에서 ThreadLocal 변수를 사용하여 다른 웹 구성 요소와 통신하고 컨테이너에 의해 스레드가 생성되고 유휴 상태의 개체는 풀에 유지됩니다. 올바르게 정리되지 않은 경우 ThreadLocal 변수는 동일한 웹 구성 요소가 해당 값을 겹쳐 쓸 때까지 계속 존재합니다.
물론 일단 식별되면 문제를 쉽게 해결할 수 있습니다.
면접관은 순환 참조 솔루션을 찾고 있었을 것입니다.
public static void main(String[] args) {
while (true) {
Element first = new Element();
first.next = new Element();
first.next.next = first;
}
}
이것은 참조 카운트 가비지 수집기의 고전적인 문제입니다. 그런 다음 JVM이이 제한이없는 훨씬 더 정교한 알고리즘을 사용한다고 정중하게 설명합니다.
웨스 탈레
first
은 유용하지 않으며 가비지 수집되어야합니다. 에서는 레퍼런스 카운팅 (자체) 그것에 활성 기준이 있기 때문에 가비지 컬렉터 개체 freeed되지 않는다. 무한 루프는 누출을 시연하기 위해 여기에 있습니다. 프로그램을 실행할 때 메모리가 무한정 증가합니다.