Week 5: Memory Model Continued (Monday, February 2, 2026)¶
Lecture Recording
Related Assignment
This lecture covers concepts for Assignment A2 and continues the memory model from Week 4 Day 2.
Lecture Preview [0:00]¶
Today's agenda:
- Quiz 1 feedback — Grading status, curve plans, test format reflection
- Assignment 2 Q&A — Vehicle behavior clarifications
- Test-driven development — Why write tests before implementation
- Technical debt — The cost of "good enough" code
- Memory model continuation — Complete the tracing exercise from Wednesday
Memory Model Continuation
The whiteboard demo from Wednesday ran out of time. Today we'll pick up where we left off and complete the full object instantiation trace.
Quiz 1 Feedback [0:39]¶
Grading Status:
- Tests are graded (raw scores posted)
- A few outstanding tests for emergency absences
- Question-by-question analysis still in progress
- Tests will be returned Wednesday with 10-20 minutes of in-class review
Test Length:
The test was too long. The goal is for the bulk of students to finish between 3/4 and 4/5 of the allotted time, with only 2-3 students remaining at the end. Instead, more than half the class was still working when time expired.
What Happened:
- Students got through the first part quickly (within 20 minutes, questions about the back half)
- Then hit a wall on the debugging/code-reading questions
- Many students left large portions blank
The Debugging Question Style:
The debugging-style questions were new. In hindsight, providing a practice question on Discord a few days before would have helped. (This was done for the TCSS 380 test with better results.)
Practice Recommendation
Spend time hand-tracing what code is doing. Be the compiler, but more importantly, be the runtime environment:
- "I get to this statement—what exactly is it doing?"
- "What is the output of this statement?"
- "What becomes the input for the next statement?"
Grades and Curve:
- No traditional curve, but class average will be adjusted
- Everyone gets 4 points for a flawed question
- A Checkstyle question was worth 6 points but graded out of 4 (slight built-in curve)
- Potential take-home opportunity for commonly missed questions
Future Tests:
- Similar debugging/code-reading questions will appear again (with advance notice of question style)
- May include unit test analysis: "Given this spec and these 4 tests, which pass/fail?"
- Clear directions for bug fixes: "Point to the one thing that needs fixing"
Assignment 2 Q&A [15:41]¶
Human and Crosswalk Behavior [16:18]¶
Student Question
Q: I saw the human get run over. When does that happen?
A: Humans wait at green crosswalk lights (when vehicles have the right of way), then cross when the light turns yellow. A taxi can legally proceed through a yellow light—so if a human is crossing on yellow, the taxi can hit them.
Human behavior at crosswalks:
- Light is green (for vehicles) → Human waits
- Light turns yellow → Human starts crossing immediately
- Taxi approaches yellow light → Taxi proceeds (can run over the human)
The only scenarios where a human gets disabled:
- ATV drives over them (ATVs ignore terrain rules)
- Human is in a crosswalk on a yellow light and gets hit
No street-driving vehicle goes through a red crosswalk light, so humans are safe once the light turns red.
Test-Driven Development Philosophy [19:01]¶
Student Question
Q: I finished the truck tests after writing the implementation. Writing the implementation first made more sense to me.
A: That makes complete sense—your brain is trained to write implementation code. That's what you've done since day one of 142.
The Training Problem:
Your programming education has trained you to:
- Read a problem
- Start writing implementation immediately
This works for simple problems. But as problems become more complex:
- "Just start coding" leads to more rework and refactoring
- The "throw spaghetti at the wall" approach becomes increasingly costly
The TDD Mindset Shift:
The goal isn't to completely reverse your thinking, but to introduce a new approach:
- Stop and think about the specification before implementation
- Write tests that verify documented behavior
- Design first, then implement
Student Question
Q: How are you supposed to think about methods before you write them?
A: You already do this—you read the document to understand the requirements. The real question is: how much should you think before writing? As problems get more complex, you need to invest more time in design. Once you've seen more patterns and implementations, you'll naturally spend more time thinking before coding.
Technical Debt [24:52]¶
The Scenario:
You write a method. It works. But the implementation makes you feel "icky inside." The tests pass. The deadline is tonight or tomorrow. You submit it.
For a class assignment that nobody touches again, this is fine (though it teaches bad habits).
In Professional Software:
- Sprint 1: You submit that "icky" method. It works. You move on.
- Sprint 2: New deadline. You build code on top of that method.
- Sprint 3: More code built on top.
- Sprint 4: You need to add new functionality, but the original method's design blocks you.
- Now: To add the feature, you must redesign that method from Sprint 1—and rebuild everything built on top of it.
This accumulated cost is technical debt. Every piece of software built on that original bad decision carries 10%, 20%, 30% overhead.
The Point Class Example
A student mentioned that Java's Point class has tech debt built in—the implementation is stuck and causes bugs everywhere it's used.
Build to Learn (Carefully):
It's okay to "build to learn." But be prepared:
- Build something to understand the problem
- Tear it down completely
- Rebuild it correctly from scratch
Don't just patch your learning prototype—you'll carry its design flaws forward.
Memory Model Continuation [30:36]¶
This section continues the memory model exercise from Week 4 Day 2, tracing through memoryModelEx7() with classes A and B.
Review: Load-Time Setup¶
Before runtime execution begins, the JVM performs load-time setup:
1. String Pool — The compiler identifies all string literals. At load time, these become String objects in a special heap region:
| Address | Value |
|---|---|
| #123A | "Charles" |
| #123B | "Steven" |
String objects in the pool are stored in contiguous memory addresses for efficient lookup.
2. Metaspace — static final constants are stored for fast access:
| Class | Type | Name | Value |
|---|---|---|---|
| A | int | MAX_ID | 1000 |
3. Class Objects — Every class used in the application has exactly one object representing it on the heap:
String.classMain.class(or whatever the main class is called)System.classObject.classA.class— containsidGenerator(static, non-final)B.class
Static Non-Final vs Static Final
static finalfields → Metaspace (constants)static(non-final) fields → Live in the class object
The idGenerator field is static but not final, so it lives in the A.class object, not in Metaspace.
Stack Frame Setup [32:24]¶
When memoryModelEx7() is called, a stack frame is created with space for local variables:
| Type | Name | Value |
|---|---|---|
| A | a | (uninitialized) |
| String | sString | (uninitialized) |
Stack frame sizing:
- The JVM knows exactly what local variables are needed before executing the method
- Primitive
int= 4 bytes (32 bits) - Object references = 8 bytes (64 bits on modern systems)—we're storing memory addresses, not objects
References, Not Objects
The String sString local variable doesn't store the string—it stores a memory address pointing to a String object. The size needed is always the same regardless of string length.
Tracing new A("Charles") [47:30]¶
Step 1: What gets passed to the constructor?
Not the string "Charles"—the memory address of the string literal in the pool: #123A.
Step 2: Object instantiation
Memory is allocated on the heap for a new A object (address: #619A):
| Field | Value |
|---|---|
| myName | (to be assigned) |
| myId | (to be assigned) |
Step 3: Constructor execution
super()→ Object constructor runs (implicit)myName = theName→myNamegets#123A(not "Charles"—the memory address)myId = idGenerator++→ Post-increment: read 1 fromA.classobject, assign tomyId, then incrementA.class.idGeneratorto 2
Step 4: Assignment
The variable a on the stack now holds #619A.
Tracing Method Calls [58:25]¶
a.toString():
String.format() creates a new String object on the heap:
| Address | Value |
|---|---|
| #791C | "id: 1\nname: Charles" |
sString is assigned #791C.
a.getName():
Returns myName, which is #123A—a reference to the pooled string. No new object created.
a.convertName():
toUpperCase() creates a new String object:
| Address | Value |
|---|---|
| #8111 | "CHARLES" |
What gets sent to println()? The memory address #8111.
System.out.println("\n***********\n"):
What gets sent? The memory address of the string literal in the pool: #123C (or wherever that literal lives).
Garbage Collection Interlude [1:03:03]¶
Student Question
Q: Is this like garbage collection?
A: No. Popping stack frames off the call stack is different from garbage collection.
Stack frames: When a method returns, its stack frame is "popped"—but nothing is actually erased. The stack pointer just moves. The data stays until overwritten by a new stack frame.
Garbage collection:
- Runs periodically (expensive operation)
- Looks for orphaned objects—heap objects with no references pointing to them
- Multi-step process: remove unreferenced objects, then remove objects only referenced by removed objects
Your Programs Probably Haven't Run Long Enough
Most student programs don't run long enough for the garbage collector to execute. It's designed for long-running applications.
After convertName() returns:
#791C("id: 1\nname: Charles") — still referenced bysString#8111("CHARLES") — orphaned (no references)
If the garbage collector runs now, #8111 gets cleaned up.
Tracing new B("Steven") [1:05:37]¶
A new B object is created. What are its instance fields?
| Field | Value | Notes |
|---|---|---|
| myName | (inherited from A) | Private in A—B has it but can't directly access it |
| myId | (inherited from A) | Private in A—same restriction |
| myValue | (B's own field) | Directly accessible |
Private Inheritance
Subclasses have the private fields of their parent—they're allocated in the object's memory. But they can't directly access them. That's why they're shown in red on the whiteboard.
Constructor chain:
new B("Steven")passes#123Bto B's constructor- B's constructor:
super(theName)passes#123Bto A's constructor - A's constructor:
myName = #123B,myId = 2(idGenerator was 2, now 3) - Back to B's constructor:
myValue = theName.length()→ 6
To Be Continued
The lecture ran out of time here. Wednesday's lecture will complete the B object trace and show how polymorphism affects method calls on a.
Whiteboard Photos¶
Memory Model Heap Diagram¶
The complete diagram shows the heap (center/right) with object instances, class objects, and metaspace, plus the string pool (left).

What's shown:
- String Pool (left): Map containing "Charles" at
#123A, "Steven" at#123B, and the separator string at#123C - Heap (center):
- Object
Aat#619AwithmyName→#123AandmyId=1 - String object at
#791Ccontaining "id: 1\nname: Charles" (fromtoString()) - Object
Bwith inherited fieldsmyName/myId(in red—private) andmyValue
- Object
- Class Objects (right):
Object.class,String.class,A.class(withidGenerator = 2),B.class,Main.class - Metaspace (bottom right):
A.MAX_ID = 1000
Call Stack Diagram¶
This diagram shows the call stack during constructor chaining when creating the B object.

What's shown:
- Stack (bottom to top):
main()— entry pointmemEx7— stack frame with local variables:String sString = #791C,A a = #619AAconstructor —theName = #123BB Const— B's constructor withtheName = #123BObject— implicitsuper()call at top of chain
Lecture Demo Code¶
The following classes demonstrate the memory model concepts discussed in this lecture (same as Week 4 Day 2).
import edu.uw.tcss.mem.A;
import edu.uw.tcss.mem.B;
public class MemoryModelDemo {
void main() {
memoryModelEx7();
}
public void memoryModelEx7() {
A a = new A("Charles");
String sString = a.toString();
System.out.println(sString);
System.out.println(a.getName());
System.out.println(a.convertName());
System.out.println("\n************\n");
a = new B("Steven");
sString = a.toString();
System.out.println(sString);
System.out.println(a.getName());
System.out.println(a.convertName());
// System.out.println(a.getValue()); // Won't compile - a is type A
}
}
package edu.uw.tcss.mem;
public class A {
/** Stored in Metaspace (static final constant). */
private static final int MAX_ID = 1000;
/** Stored in A.class object (static, shared by all instances). */
private static int idGenerator = 1;
/** Instance field - stored in each A object on heap. */
private int myId;
/** Instance field - reference to String (in pool or heap). */
private String myName;
public A(final String theName) {
super();
myName = theName;
myId = idGenerator++; // Post-increment: assign then increment
}
public String getName() {
return myName;
}
public String convertName() {
return myName.toUpperCase();
}
@Override
public String toString() {
return String.format("id: %d %nname: %s", myId, myName);
}
}
package edu.uw.tcss.mem;
public class B extends A {
private int myValue;
public B(final String theName) {
super(theName); // Chains to A's constructor
myValue = theName.length();
}
@Override
public String convertName() {
// Cannot access myName directly - it's private in A
return getName().toLowerCase();
}
public int getValue() {
return myValue;
}
}
This lecture outline is part of TCSS 305 Programming Practicum, School of Engineering and Technology, University of Washington Tacoma.