- How memory works in Java
- How to find source of problem and fix it
- Open source softwares
- For code optimization
- For avoiding code errors (difficult to trace)
- JVM is complicated
- Different aspects of how it is managed
- Not a precise representation
- As a developer how to account for it
- Stack: Every thread has its own stack
- Java knows when data can be destroyed
- Each time a function is called, local veriables are pushed into the stack
- copy of argument is made
- When we return from method, all variables in function are popped
- All local variables are stored in stack
- Stack helps define variable scopes
- Thread maintains its own stack
- Calling method variables are burried inside
- Stack helps define variable scopes
-
Heap: for data that has a longer lifetime than a code block or function
- Objects shared across methods
-
Heap is all memory except stack
- One heap is shared across threads
-
Easy sharing of variables
- Primitives are stored on stack
-
References are stored in Stack
-
Stack
age = 21; -
Heap
name -> Hello (name is in stack)
-
-
Rules:
- Objects are stored on heap
- Variables are a reference to the object
- Local variables are stored on the stack
-
Heap is a massive amount of storage space compared to the stack
- In Java we don't have access to the address stored in reference
-
Only variables in stack are directly reachable
-
Calling methods
public static void main(String[] args) { int localValue = 5; calculate(localValue); System.out.println(localValue); } public static void calculate(int calcValue) { // a copy of the value is passed calcValue = calcValue * 100; } -
Pass by value in Java (copies are made)
- References are copied
-
A variable can only be assigned once
-
Un initialized can be assigned
final Customer c; c = new Customer("John"); // allowed
-
-
We can't change which object is pointed to
-
But we can change the object in the heap
c.setName("Susan"); // allowed
-
-
In Java we cannot make an object fixed and un-changable
constis not implemented
-
PMD tool enforces final keyword
- Open MemoryTest
- Predict the outcome
-
Well defined class:
public class Book { private String title; // cannot be changed outside private String author; public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public Book(String title, String author) { this.title = title; this.author = author; } }-
We cannot accidentally change the data (access is controlled)
-
Example 2:
public class CustomerRecords { private Map<String, Customer> records; public CustomerRecords() { this.records = new HashMap<String, Customer>(); } public void addCustomer(Customer c) { this.records.put(c.getName(), c); } public Map<String, Customer> getCustomers() { return this.records; } }-
Client code:
for (Customer next: records.getCustomers().values()) { System.out.println(); }-
Problem: calling code gets reference and do anything to it
CustomerRecords records = new CustomerRecords(); Map<String, Customer> myCustomers = records.getCustomers(); myCustomers.clear();- Violation of encapsulation
- Difficult to debug
-
Work around: Don't pass pointers to objects
public class CustomerRecords implements Iterable<Customer> { ... @Override public Iterator<Customer> iterator() { return records.values().iterator(); } } ... for (Customer next: records) { System.out.println(next); } records.iterator().remove(); // problem -
Better solution is to return a new object
public Map<String, Customer> getCustomers() { return new HashMap<String, Customer>(this.records); }-
This solution builds a new HashMap of references to existing Customers
- If reference is changed to new Customer object, then original Customer does not change
- Problem: Any internal value of a Customer (property) can be changed
- Problem: If we change copy and assume that we are changing original collection, then it causes confusion
-
Example:
records.getCustomers().clear(); // references in new collection are removed completely but original collection is not changed
-
-
Elegant solution: Return immutable collection
-
Collections.unmodifyableMap -
Collections.unmodifyableList -
Example:
public Map<String, Customer> getCustomers() { return Collections.unmodifiableMap(this.records); }records.getCustomers().clear(): fails with error
-
-
-
-
-
Example:
public Customer getCustomerByName(String name) { return this.records.get(name); }- Gets reference to Customer and can do anything
- Elegant solution: Read only copy
-
Need a copy constructor
public class Customer { public Customer(Customer oldCustomer) { this.name = oldCustomer.name; } ... } public Customer getCustomerByName(String name) { return new Customer(this.records.get(name)); } ... Customer john = records.getCustomerByName("John"); System.out.println(john.getName()); john.setName("Derek"); ... for (Customer next: records.getCustomers().values()) { return new Customer(this.records.get(name)); } -
Elegant solution: read only version using interface
public class Customer { ... }-
Extract interface (include only getters):
CustomerReadOnlypublic class Customer implements CustomerReadOnly { ... public CustomerReadOnly getCustomerByName(String name) { return this.records.get(name); } } ... CustomerReadOnly john = records.getCustomerByName("John"); ... // customer can change this to Customer
-
-
- Returning string references:
-
String: They are immutable
- If the value is changed, new value is created
-
Example:
Customer cust = (new Customer("John")); String name = cust.getName(); name = "Simon"; // New literal is created and reference is changed to the new literal address
-
- Exercise: Open up the BookCatalog project - what's wrong with this code?
-
Stack is efficient (if code block is exited the variable is popped out)
-
Problem: We want objects to live outside the scope of where the object is created
public void main() { Customer c = new Customer("John"); processCustomer(c); System.out.println(c); } public void processCustomer (Customer aCustomer) { // shared here (heap) aCustomer.doSomethingReallyImportant(); }- Lifetimes vary in heap
-
C++: Choice of creation of objects in stack or heap
-
Java: No choice, all objects are in heap
- Simplification
- Clean way
- Now VM is clever. If object falls within the scope of a function, it is created in stack
- No control to the developer (optimized automatically)
-
Example:
public class Main { public static void main(String[] args) { String one = "hello"; String two = "hello"; // both point to single object in heap since string is immutable (internalized strings) if (one == two) // references are same System.out.println("They are the same object"); else System.out.println("They are difference object"); String three = new Integer(76).toString(); String four = "76"; if (three == four) // references are same System.out.println("They are the same object"); else System.out.println("They are difference object"); // Java cannot re-use } }-
Forced internalization:
new Integer(76).toString().itern(); // forced re-use- No duplicates
- Limits creation of objects
-
-
- Lifetimes vary in heap
-
Starting point for garbage collection: All objects are created in heap (assumption)
- When we are finished with objects, Java cleans up automatically
- C/C++: Explicitly delete the objects
- C:
free() - If we don't free memory, then we may have to re-start the computer (if it is web server, it must not be done)
- Fault: Memory leak (objects not freed continue to consume memory... this is a "memory leak"
- Hard to detect (it takes too long and it is catastrophic)
- It is also hard to decide when to delete objects
- C:
- Java avoids memory leaks by:
- Running on a VM
new: we are taking from VM (Not OS) - just C program- VM decides when to put
free()function
- VM decides when to put
- No leaks (no bug reports)
- Adopts a Garbage Collection strategy (invented in LISP)
- Automatic process analyzes the heap and works out to find out which objects are no longer needed
- Running on a VM
- C/C++: Explicitly delete the objects
- Rule for garbage collection:
- Any object on the heap which cannot be reached through a reference from the stack is "eligible for garbage collection"
- It can never be used again
- It can be removed anytime in the future
- It is possible for an object that is referenced be garbage
- List has no reference but has references to other objects (these are also garbage)
- Circular references
- None of them are reachable from stack
- Garbage collector works out different possibilities
- Any object on the heap which cannot be reached through a reference from the stack is "eligible for garbage collection"
- A programmer has very little control over garbage collection process.
- It must be treated as automatic process
gc(): suggests that the JVM to run the garbage collection process-
No guarantee that the VM will do
-
Example:
public class Main { public static void main(String[] args) { Runtime runtime = Runtime.getRuntime(); for (int i = 0; i < 100000000; i++) { Customer c; c = new Customer("John"); } availableBytes = runtime.freeMemory(); System.out.println("Available Memory: " + availableBytes / 1024 + "k); System.gc(); availableBytes = runtime.freeMemory(); System.out.println("Available memory: " + availableBytes / 1024 + "k"); } }- GC did run (accepted the request or it may have been running already and finished the job at the end)
- It may not be accepted and may get different numbers (lower, same or higher)
- Example usage:
- If two blocks need to be evaluated for performance, then we may run gc before we test that for each code block
- State need be same
- But may not work with this suggestion
- If two blocks need to be evaluated for performance, then we may run gc before we test that for each code block
- GC will temporarily stop all threads in the application running
- App is temporarily suspended until completed
- GC must be quick and should be reasonably in-frequent
- When garbage collection process physically removes objects from the heap, Java runs
finalize()methodfinalize(): called by the gc-
Example:
public class Customer { ... public void finalize() { System.out.println("This object is being gc'd"); } }- This is useless:
-
It may or may not run or may run at a different time
-
Do not close resources in this method - if not, we do not know when it may be closed
-
Even if gc runs, the object may not be garbage collected at that point in time
-
GC is needed to keep the heap nice and tidy while app is running
-
Finalize method is useful to check that all resources are being closed
public void closeFile() { file.close(); } public void finalize() { if (file.isOpen()) { // no guarantee that this will run logger.warn("It looks like this resource was not closed"); } }
-
- This is useless:
-
- GC did run (accepted the request or it may have been running already and finished the job at the end)
-
- Garbage Eligibility
- Java avoids memory leaks by:
- Running on a virtual machine
- Releases all of its memory to the OS automatically
- It must be properly implemented
- Releases all of its memory to the OS automatically
- Adopts a Garbage Collection strategy
- Running on a virtual machine
- Java avoids memory leaks by:
- Soft leak: An app or library keeps an object live even if we are not going to use it again
-
An object is referenced on the stack even though it will never be used again
- When the program ends, the VM will execute
free()- While program is running, the object will never be released
- Risk of running out of memory
- While program is running, the object will never be released
- When the program ends, the VM will execute
-
Example:
@Override public void run() { while (true) { try { } catch (InterruptedException e) { e.printStackTrace(); } String name = new UUID(1l, 10l).toString(); Customer c = new Customer(name); cm.addCustomer(c); totalCustomerGenerated++; cm.getNextCustomer(); } } public static void main(String[] args) { CustomerManager cm = new CustomerManager(); GenerateCustomerTask task = new GenerateCustomerTask(cm); for (int user = 0; user < 10; user++) { Thread t = new Thread(task); t.start(); } while (true) { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } cm.howManyCustomers(); System.out.println("Available memory: " + Runtime.getRuntime().freeMemory() / 1024 + "k"); } }- Arguments
-
VM arguments:
-Xmx10m- Max heap size
-
bin/jvisualvm.exe- start- Shows apps using vm
- Right click on Eclipse and choose open
- Shows apps using vm
- Arguments
-