A topology of memory leaks on the JVM

36
A topology of memory leaks on the JVM

description

A topology of memory leaks on the Java virtual machine. This presentation covers all types of memory leaks that can occur on the JVM and categorizes them after the memory region of their occurrence. It further gives some advice on how such leaks can be avoided and presents some debugging techniques.

Transcript of A topology of memory leaks on the JVM

Page 1: A topology of memory leaks on the JVM

A topology ofmemory leaks on the JVM

Page 2: A topology of memory leaks on the JVM

HotSpot JVM process

heap perm gen

native

stack C sta. user

managed

string pool

method area

pc reg.

code cache

young gen

ten. gen

const. pool

eden

sr.1

sr.2

-Xms -Xmx

-Xss -Xsx

-XX:PermSize -XX:MaxPermSize

(operating system)

Java 6 -> 7

Java 7 -> 8

Page 3: A topology of memory leaks on the JVM

genuine leaks

HotSpot JVM process

heap perm gen

native

stack C sta. user

managed

string pool

method area

pc reg.

code cache

young gen

ten. gen

const. pool

eden

sr.1

sr.2

Page 4: A topology of memory leaks on the JVM

Field unsafe = Unsafe.class .getDeclaredField("theUnsafe");unsafe.setAccessible(true);Unsafe theUnsafe = (Unsafe) unsafe.get(null);

Allocating native memory with sun.misc.Unsafe1. Getting hold of “the unsafe“

2. Allocating native memory

final int intSize = 4; long arraySize = (1L + Integer.MAX_VALUE) * intSize; long index = unsafe.allocateMemory(arraySize); Random random = new Random(); for(long l = 0L; l < arraySize; l++) unsafe.putInt(random.nextInt());

3. (Hopefully) releasing native memory

// Without me, the memory leaksunsafe.freeMemory(index);

Page 5: A topology of memory leaks on the JVM

I don‘t do sun.misc.Unsafe

1. But some of your favorite tools do:1. Kryo (used by e.g. Twitter‘s Storm, Apache Hive)2. EhCache‘s “big memory“ (used by e.g. Hibernate)3. JDK (e.g. direct byte buffers)4. General purpose (e.g. GSON, Snappy)

2. Unsafe to become public API in Java 9 (competition with CLR?)

3. JNI leaks have the same effect

4. Java 8 removes “perm gen“ in favor of native memory “meta space“

Page 6: A topology of memory leaks on the JVM

symptoms of a genuine leakNo exception required by JVMS. However, JVM will be untypical slow when:

1. Creating a thread:Requires allocation of new stacks / pc register.

2. Loading a class / JIT:Requires native memory.

3. Doing I/O:Often requires native memory.

4. Calling a native method:Allocates resources on native stack / user space. Swapping can delayexecution.

5. Garbage collection:Tenured generation collection needs broad access such that OS must swap back the heap. Rule of thump: All JVM heaps must be swapped back into memory. (Leaked memory will stay swapped out.)

Page 7: A topology of memory leaks on the JVM

Heap leaks

HotSpot JVM process

heap perm gen

native

stack C sta. user

managed

string pool

method area

pc reg.

code cache

young gen

ten. gen

const. pool

eden

sr.1

sr.2

Page 8: A topology of memory leaks on the JVM

class FixedSizeStack {

private final Object[] objectPool = new Object[10]; private int pointer = -1;

public Object pop() { if(pointer < 0) throw new NoSuchElementException(); return objectPool[pointer--]; }

public Object peek() { if(pointer < 0) throw new NoSuchElementException(); return objectPool[pointer]; }

public void push(Object object) { if(pointer > 8) throw new IllegalStateException("stack overflow"); objectPool[++pointer] = object; } }

Page 9: A topology of memory leaks on the JVM

Element 5

Element 4

Element 3

Element 2

Element 1

Element 0

Stack

All memory leaks in Java are linked to reference life cycles.

Page 10: A topology of memory leaks on the JVM

Immutable Stack Element 0

Immutable Stack Element 1

Immutable Stack Element 2

always prefer

immutability

Page 11: A topology of memory leaks on the JVM

public String substring(int beginIndex, int endIndex) { // omitted argument validation return ((beginIndex == 0) && (endIndex == count)) ? this : new String(offset + beginIndex, endIndex - beginIndex, value); }

String(int offset, int count, char[] value) { this.value = value; this.offset = offset; this.count = count; }

JDK 6

Page 12: A topology of memory leaks on the JVM

XML, anybody?

org.xml.sax.DocumentHandler { // several callback methods void characters(char[] ch, int start, int length); }

Webpage character blob

DocumentHandler few letter word

is defined by

produces

handles

Page 13: A topology of memory leaks on the JVM

public String substring(int beginIndex, int endIndex) { // omitted argument validation int subLen = endIndex - beginIndex; return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen); }

public String(char[] value, int offset, int count) { // omitted argument validation this.value = Arrays.copyOfRange(value, offset, offset + count); }

JDK 7

make

defensive

copies instead

of reusing

super sets

Page 14: A topology of memory leaks on the JVM

interface Message extends Serializable { String getInfo(); }

class ExampleActivity extends Activity { @Override public void onCreate(Bundle bundle) { startService( new Intent(this, getClass()) .putExtra( "foo", new Message() { @Override public String getInfo() { return "bar"; } })); } }

A classic Android leak

Page 15: A topology of memory leaks on the JVM

class ExampleActivity$1 implements Message {

private ExampleActivity this$0; ExampleActivity$1(ExampleActivity this$0) { this.this$0 = this$0; } @Override public String getInfo() { return "bar"; } }

new Message() { @Override public String getInfo() { return "bar"; } }

uncompiled

desugared

avoid non-

static inner

classes

Non-static inner classes

Page 16: A topology of memory leaks on the JVM

class Foo { void bar() { List<String> list = Arrays.asList("foo", "bar"); list.forEach(LambdaMetafactory .INVOKEDYNAMIC(Foo::lambda$1) .make()); } private static void lambda$1(String s) { System.out.println(s); } }

class Foo { void bar() { List<String> list = Arrays.asList("foo", "bar"); list.forEach(s -> { System.out.println(s); }); } }

uncompiled

desugared

Java 8 lambda expressions

Page 17: A topology of memory leaks on the JVM

class Foo { final String prefix; // constructor omitted void bar() { List<String> list = Arrays.asList("foo", "bar"); list.forEach(s -> { System.out.println( prefix + ":" + s) }); } }

uncompiled

desugared

Java 8 lambda expressions

class Foo { final String prefix; // constructor omitted void bar() { List<String> list = Arrays.asList("foo", "bar"); list.forEach(LambdaMetaFactory .INVOKEDYNAMIC(this::lambda$1) .make()); } private void lambda$1(String s) { System.out.println(this.prefix + ":" + s); } }

Page 18: A topology of memory leaks on the JVM

uncompiled

desugared

class Foo { final String prefix; // constructor omitted void bar() { List<String> list = Arrays.asList("foo", "bar"); String prefix = this.prefix; list.forEach(s -> { System.out.println( prefix + ":" + s)}); } }

class Foo { final String prefix; // constructor omitted void bar() { List<String> list = Arrays.asList("foo", "bar"); String prefix = this.prefix; list.forEach(LambdaMetafactory .INVOKEDYNAMIC(Foo::lambda$1) .make(prefix)); } private static void lambda$1(String prefix, String s) { System.out.println(prefix + ":" + s); } }

pay attention

to your lambda

expression‘s

scope

Java 8 lambda expressions

Page 19: A topology of memory leaks on the JVM

Other typical causes of heap leaks

• Functional expressions in e.g. Scala / Groovy• Serialization leaks (e.g. Apache Wicket)• Singletons / enums • Context frameworks (e.g. DI like Spring)

Page 20: A topology of memory leaks on the JVM

Stack leaks

HotSpot JVM process

heap perm gen

native

stack C sta. user

managed

string pool

method area

pc reg.

code cache

young gen

ten. gen

const. pool

eden

sr.1

sr.2

Page 21: A topology of memory leaks on the JVM

class StackLeak { int SIZE = (int) (0.5 * Runtime .getRuntime() .maxMemory());

void foo() { { byte[] array1 = new byte[SIZE]; } byte[] array2 = new byte[SIZE]; }

void bar() { { byte[] array1 = new byte[SIZE]; } byte[] array2 = new byte[10]; byte[] array3 = new byte[SIZE]; } }

Page 22: A topology of memory leaks on the JVM

this

void foo() { { byte[] array1 = new byte[SIZE]; } byte[] array2 = new byte[SIZE]; }

array1

SIZEarray1SIZEarray2

local variable array oper

and

stac

k

Page 23: A topology of memory leaks on the JVM

this

void bar() { { byte[] array1 = new byte[SIZE]; } byte[] array2 = new byte[1]; byte[] array3 = new byte[SIZE]; }

array1array2

SIZEarray11array2SIZEarray3

array3

write short

methods,

mistrust explicit

scopes local variable array oper

and

stac

k

Page 24: A topology of memory leaks on the JVM

“Perm gen“ leaks

HotSpot JVM process

heap perm gen

native

stack C sta. user

managed

string pool

method area

pc reg.

code cache

young gen

ten. gen

const. pool

eden

sr.1

sr.2

Page 25: A topology of memory leaks on the JVM

newFoo() Foo.class

getClass()ClassLoader()

getClassLoader()

Field field = ClassLoader.class.getDeclaredField("classes"); field.setAccessible(true); Vector<Class<?>> classes = (Vector<Class<?>>) field.get(ClassLoader.getSystemClassLoader());

classes

Bar.class

Classes and HotSpot

Foo.klass

MA

Bar.klass

Page 26: A topology of memory leaks on the JVM

ClassLoaders and HotSpot

null

ClassLoader()

ClassLoader()

ClassLoader() ClassLoader()

bootstrap CL(bootstrap directory)

extension CL(extension directory)

system CL(class path)

user CL(explicit)

parent

parent

parent

parent

Foo.classloadClass()

Page 27: A topology of memory leaks on the JVM

ClassLoader() Thread.class

Foo()Foo.class

Thread()

FooHolder.class

threadLocalMap

class loader leak avoid thread

context

ClassLoader()

class FooHolder { static ThreadLocal<Foo> tlFoo = new ThreadLocal<Foo>(); static { tlFoo.set(new Foo()); } }

Page 28: A topology of memory leaks on the JVM

Other typical causes of class loader leaks

• Shut down hooks• Use of thread context class loaders (e.g. OSGi)• Service provider interfaces (SPIs)• JDBC drivers (JDK DriverManager)• Security frameworks (e.g. JDK Policy)• Class loader magic (e.g. instrumentation)

avoid redeployment,

use one container

per app

Page 29: A topology of memory leaks on the JVM

Why do modern applications require so much perm gen memory?

class Foo { public Object bar(Object o) { return o; } }

Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Foo.class); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { return proxy.invokeSuper(obj, doSomethindWith(args)); }}); Foo enhancedFoo = (Foo) enhancer.create();

Created classes: 2 + #methods * 2[FastClass, FastMethod, MethodProxy]

avoid

instrumentation

Page 30: A topology of memory leaks on the JVM

Modern JDK‘s inflation works similarly

Method

MethodAccessor

DelegatingMethod-AccessorImpl

NativeMethod-AccessorImpl

invoked by

after inflation(byte code generation)

before inflation(JNI)

remember

inflation

Page 31: A topology of memory leaks on the JVM

Java 8 meta space

• Permanent generation rebranded (and probably approximation to JRockit)

• Meta space is allocated in native memory• No space limit by default (MaxMetaspaceSize)• Today‘s debugging tools will not be able to

read meta space

Page 32: A topology of memory leaks on the JVM

Tenuring leaks

HotSpot JVM process

heap perm gen

native

stack C sta. user

managed

string pool

method area

pc reg.

code cache

young gen

ten. gen

const. pool

eden

sr.1

sr.2

Page 33: A topology of memory leaks on the JVM

public void foo() { Set<Bar> bars = new HashSet<Bar>(); for(int i = 0; i < 100; i++) { for(int j = 0; j < 100; j++) { bars.add(makeBar(i, j)); } doSomethingWith(bars); bars.clear(); } }

public void foo() { for(int i = 0; i < 100; i++) { Set<Bar> bars = new HashSet<Bar>(); for(int j = 0; j < 100; j++) { bars.add(makeBar(i, j)); } doSomethingWith(bars); } }

young generation

Tenured generation

eden survivor 1 survivor 2

bars bars bars bars

collection of survivor 2collection of survivor 1

don‘t be scared of

“new“,

avoid object

pooling,

JIT knows best

Page 34: A topology of memory leaks on the JVM

Implicit memory leaks

• Leaked threads: Require fixed amount of memory for call stacks / pc register / general allocation

• Leaked handles (files, sockets, databases): Usually require JVM memory resources

Page 35: A topology of memory leaks on the JVM

debugging / profilingjmap -dump:live,format=b,file=<...> <pid>

java -XX:+HeapDumpOnOutOfMemoryError -XX:+HeapDumpPath=<...> <...>

jhat <hprof file>select a from [I a where a.length >= 256

GUI tools: MAT (Eclipse RCP) / JVisualVM / HeapWalker

tools that create heap

dumps by

instrumentation

require memory to

run

Page 36: A topology of memory leaks on the JVM

http://rafael.codes@rafaelcodes

http://documents4j.comhttps://github.com/documents4j/documents4j

http://bytebuddy.nethttps://github.com/raphw/byte-buddy