Heap liveness and its usage in
automatic memory management
Ran Shaham
Elliot Kolodner
Mooly Sagiv
•ISMM’02
•Unpublished
http://www.cs.tau.ac.il/~ransh/
TV
LA inside
Motivation• An object could be collected once it is no longer
needed– Yet, run-time garbage collectors (RTGCs) are
typically based on reachability• Profiling tools can detect when objects are
needed• The compiler can:
– Statically identify a subset of unneeded objects – Issue a free instruction (compile-time Garbage
Collection)– Issue a warning when a potentially needed object is
reclaimed– Inform run-time garbage collector that a reference to
an object is not further used
A Pathological C Program
a = malloc(…) ;
b = a;
free (a);
c = malloc (…);
if (b == c) printf(“unexpected equality”);
Inefficient Java Classpublic Class Stack { private Object stack[]; private int top; public Stack(int len) { stack = new Object[len]; top = 0; } public synchronized Object pop() { top= top-1; return stack[top]; } public synchronized void push(Object o) { stack[top]=o; top= top+1; } public synchronized void print() { for (int i=0; i<top; i++) { System.out.println(stack[i]); } }}
GC does not reclaim the memory stack[top+1]
Needed Location
pp’
l is neededa reference to l is used
l is allocated
Needed Reference Expression
pp’
e is needed a reference to l is used
e references l
e is not needed free(e) is valid
l is allocated
A Pathological C Program
a = malloc(…) ;
b = a;
free (a);
c = malloc (…);
if (b == c) printf(“unexpected equality”);
a is needed
Location Liveness
pp’
l is live
l is not assigned
l is used
Reference Expression Liveness
pp’
e is live
e denotes a location l
l is not assigned
l is used
Generalizes liveness of program variables when l is &x
Inefficient Java Classpublic Class Stack { private Object stack[]; private int top; public Stack(int len) { stack = new Object[len]; top = 0; } public synchronized Object pop() { top= top-1; return stack[top]; } public synchronized void push(Object o) { stack[top]=o; top= top+1; } public synchronized void print() { for (int i=0; i<top; i++) { System.out.println(stack[i]); } }}
stack[top+1] is not live
Typical GC Limits
class Node { Node left, right; int data;}
class C { void main(…) { Node root = createTree(); processTree(root.right); }}
root
Typical GC Limits
class Node { Node left, right; int data;}
class C { void main(…) { Node root = createTree(); processTree(root.right); }}
root
Typical GC Limits
class Node { Node left, right; int data;}
class C { void main(…) { Node root = createTree(); processTree(root.right); }}
root
Liveness Analysis Aids GC
class Node { Node left, right; int data;}
class C { void main(…) { Node root = createTree(); processTree(root.right); }}
root
root.right is live, root.left is dead
Liveness Analysis Aids GC
class Node { Node left, right; int data;}
class C { void main(…) { Node root = createTree(); processTree(root.right); }}
root
root.right is live, root.left is dead
Liveness Analysis Aids GC
class Node { Node left, right; int data;}
class C { void main(…) { Node root = createTree(); processTree(root.right); }}
root
root.right is live, root.left is dead
Liveness Analysis Aids GC
class Node { Node left, right; int data;}
class C { void main(…) { Node root = createTree(); processTree(root.right); }}
root
root.right is live, root.left is dead
Typical GC Limits
Program
Variables
a
b
c
d
e
f
Outline
• Dynamic liveness measurements– Complete location liveness– Assign-null interface
• Static analysis algorithms– (Exact) assign null (improve GC)– (Exact) free (CTGC)
Dynamic Liveness Measurements
• Estimating the potential of static analysis– Find upper bounds on expected savings
• Can be used in as an assistant tool
• Liveness information kinds– Stack reference liveness– Global reference liveness– Heap reference liveness
Main Results
• Dynamic measurements for 10 benchmarks
• Shallow information Small Potential– local variables 2%– global variables 5%– local + global 9%
• Deep information Larger Potential– heap liveness 39% complete location liveness
15% assign-null interface
Dynamic measurements
– Implemented via an instrumented JVM
• Complete location liveness measurements– Single-run algorithm
• Assign-null liveness interface – Determines the liveness of expressions– Assign null to dead reference expressions– Requires two runs of the program
Complete Liveness Measurements
• An Observation
The last use of the references to an object determines the time an object could be collected assuming liveness information
Heap Liveness Example IStack
y
z
gStatic
x
f
f2
f 1
f
fHeapL= t
…
use z.f
f
HeapL
= t
Heap Liveness Example IStack
y
z
gStatic
x
f
f2
f 1
f
fHeapL= t+2
…
use y.f2
f
HeapL= t
= t+2
Heap Liveness Example IIStack
y
z
gStatic
x
f
f2
f 1
f
f
StackR = t’’
f
StackR = Directly stack reachable
(computed during GC)
Collection time = max(t’, t’’)
Collection time(obj) = max(HeapL(obj), StackR(obj), StaticR(obj), OtherR(obj))
= t’’
HeapL= t’
Complete Liveness Summary
• Mutator– Tracks the last use of references to an object
• Collector– Propagation needed for stack/static liveness– Propagates reachability information– Propagates path liveness
• Object Collection/Program Termination– Maximum of liveness/reachability properties of an object– Depends on liveness scheme (heap liveness etc.)
Experimental Results
• Instrumented Sun’s classic JVM (1.2)
• 10 benchmarks (5 SPECjvm)
• Time is measured bytes allocated by the mutator so far in program
• Total space savings (integral)
• Maximum heap size savings (footprint)
Analyzer - Complete Liv eness
0
0.5
1
1.5
2
2.5
3
3.5
0 50 100 150 200 250 300
allocation time (MB)
siz
e (
MB
) heap liveness
stack+staticliveness
w /out liveness
Potential Savings – Total Space
0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
jess
raytr
ace
db
javac
jack
analy
zer
juru
eule
r
mc
tvla
average
heap+stack+static liveness heap liveness
stack+static liveness static liveness
stack liveness
Restricted GC Interface
• GC Interface– Should be simple/effective/efficient – Feasible heap liveness representation
• Assign null to dead heap references– Simple– Effective?
• Partially answered by our experiments
– Efficient?• Will be answered by static analysis
Null Assignable Program Points
• Normalized statements (Java Bytecode)– Manipulate at most one heap reference
• x = y.f is null assignable– Could be followed by y.f = null
• Dynamic algorithm – First run
• Determine null assignable program points– Assume all program points are null assignable– Detect non-null-assignable program points during the run
– Second run• Assign null in null assignable program points
Doubly-Linked List Example – First Run
x
n
pd d
n
pd d
// processing list elements in pairspt1: y = x.n; // x.n = null; x = null; while (y != null) { pt2: t = y.p; // y.p = null;pt3: d1 = t.d; // t.d = nullpt4: d2 = y.d; // y.d = null process(d1, d2); pt5: t = y.n; // y.n = null; y = t; }
Doubly-Linked List Example – First Run
x
n
pd d
n
pd d
[pt1]
// processing list elements in pairspt1: y = x.n; // x.n = null; x = null; while (y != null) { pt2: t = y.p; // y.p = null;pt3: d1 = t.d; // t.d = nullpt4: d2 = y.d; // y.d = null process(d1, d2); pt5: t = y.n; // y.n = null; y = t; }
y
Doubly-Linked List Example – First Run
n
pd d
n
pd d
yt
[pt2]
[pt1]
// processing list elements in pairspt1: y = x.n; // x.n = null; x = null; while (y != null) { pt2: t = y.p; // y.p = null;pt3: d1 = t.d; // t.d = nullpt4: d2 = y.d; // y.d = null process(d1, d2); pt5: t = y.n; // y.n = null; y = t; }
Doubly-Linked List Example – First Run
n
pd d
n
pd d
yt
[pt2]
[pt3]
d1
[pt1]
// processing list elements in pairspt1: y = x.n; // x.n = null; x = null; while (y != null) { pt2: t = y.p; // y.p = null;pt3: d1 = t.d; // t.d = nullpt4: d2 = y.d; // y.d = null process(d1, d2); pt5: t = y.n; // y.n = null; y = t; }
Doubly-Linked List Example – First Run
n
pd d
n
pd d
yt
[pt2]
[pt3]
d1
[pt4]
d2
[pt1]
// processing list elements in pairspt1: y = x.n; // x.n = null; x = null; while (y != null) { pt2: t = y.p; // y.p = null;pt3: d1 = t.d; // t.d = nullpt4: d2 = y.d; // y.d = null process(d1, d2); pt5: t = y.n; // y.n = null; y = t; }
Doubly-Linked List Example – First Run
n
pd d
n
pd d
y t
[pt2]
[pt3]
d1
[pt4]
d2
[pt5][pt1]
// processing list elements in pairspt1: y = x.n; // x.n = null; x = null; while (y != null) { pt2: t = y.p; // y.p = null;pt3: d1 = t.d; // t.d = nullpt4: d2 = y.d; // y.d = null process(d1, d2); pt5: t = y.n; // y.n = null; y = t; }
Doubly-Linked List Example – First Run
n
pd d
n
pd d
t y
[pt2]
[pt3]
d1
[pt4]
d2
[pt5]
[pt2]
[pt1]
// processing list elements in pairspt1: y = x.n; // x.n = null; x = null; while (y != null) { pt2: t = y.p; // y.p = null;pt3: d1 = t.d; // t.d = nullpt4: d2 = y.d; // y.d = null process(d1, d2); pt5: t = y.n; // y.n = null; y = t; }
[pt4]
Doubly-Linked List Example – First Run
n
pd d
n
pd d
t y
[pt2]
[pt3]
d1
[pt3]
d2
[pt5]
[pt1]
[pt1]
// processing list elements in pairspt1: y = x.n; // x.n = null; x = null; while (y != null) { pt2: t = y.p; // y.p = null;pt3: d1 = t.d; // t.d = nullpt4: d2 = y.d; // y.d = null process(d1, d2); pt5: t = y.n; // y.n = null; y = t; }
Doubly-Linked List Example – First Run
n
pd d
n
pd d
[pt2]
[pt3]
d2
[pt3]
d1
[pt5]
[pt3]
[pt1]
[pt3]
// processing list elements in pairspt1: y = x.n; // x.n = null; x = null; while (y != null) { pt2: t = y.p; // y.p = null;pt3: d1 = t.d; // t.d = nullpt4: d2 = y.d; process(d1, d2); pt5: t = y.n; // y.n = null; y = t; }
Doubly-Linked List Example - Second Run
x
n
pd d
n
pd d
// processing list elements in pairspt1: y = x.n; x.n = null; x = null; while (y != null) { pt2: t = y.p; y.p = null;pt3: d1 = t.d; t.d = nullpt4: d2 = y.d; process(d1, d2); pt5: t = y.n; y.n = null; y = t; }
n
pd d
n
pd d
ty
// processing list elements in pairspt1: y = x.n; x.n = null; x = null; while (y != null) { pt2: t = y.p; y.p = null;pt3: d1 = t.d; t.d = nullpt4: d2 = y.d; process(d1, d2); pt5: t = y.n; y.n = null; y = t; }
Doubly-Linked List Example - Second Run
d1 d2
// processing list elements in pairspt1: y = x.n; x.n = null; x = null; while (y != null) { pt2: t = y.p; y.p = null;pt3: d1 = t.d; t.d = nullpt4: d2 = y.d; process(d1, d2); pt5: t = y.n; y.n = null; y = t; }
Doubly-Linked List Example - Second Run
d
n
pd d
ty
d1 d2
Assign Null - Potential Savings
0%
5%
10%
15%
20%
25%
30%
35%
40%
jes
s
ray
tra
ce
db
jav
ac
jac
k
an
aly
ze
r
juru
eu
ler
mc
tvla
av
era
ge
context=0
context=1
context=2
context=3
• 15% average savings for context = 2– 11% assigning null to instance fields– 10% assigning null to array elements
• Results are valid across runs – Detecting null assignable program points on a second input– Running the program with the first input
– null assignable program points are those detected for both inputs
Related Work
• On the Usefulness of Liveness for Garbage Collection and Leak Detection [HDH01]– Does not handle heap liveness – Algorithm requires two runs
• First run: record uses and defs• Analyze log backwards for liveness information• Second run: use liveness results
• Garbage Collection and Local Variable Type-Precision and Liveness in Java Virtual Machines [ADM98]– Stack liveness static analysis– Actual trends match our upper bounds
• On the Effectiveness of GC in Java [SKS00]– Drag information
• Slightly larger potential than heap liveness information• Not clear how to automate space savings
• HUP tool (PLDI’01 + M. Pan)
Dynamic liveness measurements -Conclusion
• Liveness Information has large potential• Assign null savings “achievable” by static analysis• Stack liveness information
– Small potential• Stack+static liveness information
– Medium potential• Heap liveness information
– Is feasible• Recording history on heap is a powerful mechanism
– Larger potential• Depends on static analysis precision• Depends on GC interface
Static Analysis
• Combine history with shape analysis – a-La- Horwitz, Pfeiffer, and Reps 1989
• Assign null – Assign null to a dead reference expression– GC exploits information
• Free– free an unneeded object
Assign Null Analysis
– Insert “x.fld = null” after statements in which the expression x.fld becomes dead
– Limitations• Only one reference is assigned null• All the paths to the statement must agree
– Detects last-use– Technically
• llastu[pt,x.fld](v)– The last use of the location denoted by x.fld occurs at pt
• null[pt,x.fld]() – It is safe to insert “x.fld = null” after pt
Assign Null Example // traversing list elements pt1: y = x; pt2: while (y != null) { pt3: t = y.n; pt4: y = t; }
x
nn
null[pt3,y.n]
Assign Null Example // traversing list elements pt1: y = x; pt2: while (y != null) { pt3: t = y.n; pt4: y = t; }
x,y
nn
null[pt3,y.n]
Assign Null Example // traversing list elements pt1: y = x; pt2: while (y != null) { pt3: t = y.n; pt4: y = t; }
x,y
nn
null[pt3,y.n]
Assign Null Example // traversing list elements pt1: y = x; pt2: while (y != null) { pt3: t = y.n; pt4: y = t; }
x,y
n
llastu[pt3,y.n]
nn
t
null[pt3,y.n]
Assign Null Example // traversing list elements pt1: y = x; pt2: while (y != null) { pt3: t = y.n; pt4: y = t; }
x
n
llastu[pt3,y.n]
nn
y,t
null[pt3,y.n]
Assign Null Example // traversing list elements pt1: y = x; pt2: while (y != null) { pt3: t = y.n; pt4: y = t; }
x
n
llastu[pt3,y.n]
nn
y,t
null[pt3,y.n]
Assign Null Example // traversing list elements pt1: y = x; pt2: while (y != null) { pt3: t = y.n; pt4: y = t; }
x
n
llastu[pt3,y.n]
nn
y
llastu[pt3,y.n]
nt null[pt3,y.n]
Assign Null Example // traversing list elements pt1: y = x; pt2: while (y != null) { pt3: t = y.n; pt4: y = t; }
x
n
llastu[pt3,y.n]
nn
llastu[pt3,y.n]
n
y,t null[pt3,y.n]
Assign Null Example // traversing list elements pt1: y = x; pt2: while (y != null) { pt3: t = y.n; pt4: y = t; }
x
n
llastu[pt3,y.n]
nn
llastu[pt3,y.n]
n
y,t null[pt3,y.n]
Assign Null Example // traversing list elements pt1: y = x; pt2: while (y != null) { pt3: t = y.n; pt4: y = t; }
x
n
llastu[pt3,y.n]
nn
llastu[pt3,y.n]
n
llastu[pt3,y.n]
y
n
t
null[pt3,y.n]
Assign Null Example // traversing list elements pt1: y = x; pt2: while (y != null) { pt3: t = y.n; pt4: y = t; }
x
nn
llastu[pt3,y.n]
n
llastu[pt3,y.n]
n
y,tn
null[pt3,y.n]
Assign Null Example // traversing list elements pt1: y = x; pt2: while (y != null) { pt3: t = y.n; pt4: y = t; }
x
nn
llastu[pt3,y.n]
n
llastu[pt3,y.n]
n
y,tn
Assign Null Example // traversing list elements pt1: y = x; pt2: while (y != null) { pt3: t = y.n; pt4: y = t; }
x
n
llastu[pt3,y.n]
nn
llastu[pt3,y.n]
n
llastu[pt3,y.n]
y
n
tn
null[pt3,y.n]
Assign Null Example // traversing list elements pt1: y = x; pt2: while (y != null) { pt3: t = y.n; pt4: y = t; }
x
n
llastu[pt3,y.n]
nn
llastu[pt3,y.n]
n
y,tn
null[pt3,y.n]
Assign Null Example // traversing list elements pt1: y = x; pt2: while (y != null) { pt3: t = y.n; pt4: y = t; }
x
n
llastu[pt3,y.n]
nn
llastu[pt3,y.n]
n
y,tn
null[pt3,y.n]
Assign Null Example // traversing list elements pt1: y = x; pt2: while (y != null) { pt3: t = y.n; pt4: y = t; }
x
n
llastu[pt3,y.n]
n
llastu[pt3,y.n]
yn
llastu[pt3,y.n]
null[pt3,y.n]
Assign Null Example // traversing list elements pt1: y = x; pt2: while (y != null) { pt3: t = y.n; pt4: y = t; }
x
n
llastu[pt3,y.n] llastu[pt3,y.n]
n
null[pt3,y.n]
Assign Null Example // traversing list elements pt1: y = x; pt2: while (y != null) { pt3: t = y.n; pt4: y = t; }
x
n
llastu[pt3,y.n] llastu[pt3,y.n]
n
null[pt3,y.n]
• Static Reachability Information +Static Liveness Information CT garbage detection
• Issue “free(e)” for unneeded e
Compile-Time Garbage Collection
Exact Free Analysis
– Insert free(x) at program points when the x becomes unneeded
• Only one location is freed• All the paths to the statement must agree on the
garbage
– History predicates• lastu[pt,x](v)
– the last use of the location pointed-to by x occurs
• unneeded [pt,x]() – “free(x)” is safe after pt
Exact Free Example // traversing list elements pt1: y = x; pt2: while (y != null) { pt3: t = y.n; pt4: y = t; }
x
nn
unneeded[pt1,x]
unneeded[pt2,y]
unneeded[pt3,y]
unneeded[pt4,t]
Exact unneeded Example // traversing list elements pt1: y = x; pt2: while (y != null) { pt3: t = y.n; pt4: y = t; }
x,y
nn
unneeded[pt1,x]
unneeded[pt2,y]
unneeded[pt3,y]
unneeded[pt4,t]
lastu[pt1,x]
Exact Free Example // traversing list elements pt1: y = x; pt2: while (y != null) { pt3: t = y.n; pt4: y = t; }
x,y
nn
unneeded[pt2,y]
unneeded[pt3,y]
unneeded[pt4,t]
lastu[pt2,y]
Exact Free Example // traversing list elements pt1: y = x; pt2: while (y != null) { pt3: t = y.n; pt4: y = t; }
x
n
unneeded[pt3,y]
lastu[pt3,y] lastu[pt3,y]
n
Preliminary Conclusions
• Heap Liveness is useful
• Can be dynamically computed for large programs
• More data is needed to confirm scalability of static analysis
• Recording local history is a powerful tool– Refines the abstraction