Memory usage is the system memory occupied while code and its data run. A smaller footprint matters when efficient code is part of green coding: using fewer resources can reduce energy consumption, hardware demand, operational cost, and environmental impact. Measure rather than guess: microbenchmarks isolate small snippets, while profilers expose allocation and heap behavior in running programs. section 8.5.2 section 10.2.2
Start with unnecessary allocation. Prefer lightweight primitives over boxed values where possible, and preallocate a collection when its required size is already known. In a people-analysis loop, replacing nullable Integer bounds with int sentinel values avoids temporary boxed objects; new ArrayList<>(persons.size()) avoids incremental resizing while names are collected. section 10.2.2
Listing 10.4 A smaller memory footprint in imperative coding
String youngestName = null;
String oldestName = null;
int minAge = Integer.MAX_VALUE; #1
int maxAge = Integer.MIN_VALUE; #1
List<String> names = new ArrayList<>(persons.size()); #2
for (Person p : persons) {
if (p.age() < minAge) { #3
minAge = p.age(); #3
youngestName = p.name(); #3
} #3
if (p.age() > maxAge) { #4
maxAge = p.age(); #4
oldestName = p.name(); #4
}
names.add(p.name()); #5
}
Collections.sort(names); #5
log.info("Youngest: {}, oldest: {}, count: {}, sorted names: {}",
youngestName, oldestName, persons.size(), names);
This version keeps the same youngest, oldest, and sorted-name result while reducing allocation through primitive age bounds and a preallocated names list. section 10.2.2
Allocation tradeoffs also affect stream design. Separate streams for minimum age, maximum age, and sorted names create short-lived objects at each stage and traverse the input repeatedly. When resource usage is the constraint, combine computations in one stream pipeline. A custom collector can do this before Java 12; Java 12 and later can use nested teeing() collectors to perform multiple aggregations during one traversal. section 10.2.2
Listing 10.5 Single-stream constraint using a custom collector
record Stats(Person youngest, Person oldest, List<String> names) {}
record OldestAndNames(Optional<Person> oldest, List<String> names) {}
Stats stats = persons.stream().collect(
Collectors.teeing(
Collectors.minBy( #1
Comparator.comparingInt(Person::age)), #1
Collectors.teeing( #2
Collectors.maxBy( #3
Comparator.comparingInt(Person::age)), #3
Collectors.mapping( #4
Person::name, #4
Collectors.collectingAndThen( #4
Collectors.toList(), #4
list -> { Collections.sort(list); #4
return list; } #4
) #4
), OldestAndNames::new #5
),
(minOpt, resultOldestAndNames) -> new Stats( #6
minOpt.orElse(null), #6
resultOldestAndNames.oldest().orElse(null), #6
resultOldestAndNames.names() #6
) #6
) #6
);
log.info("Youngest: {}, oldest: {}, sorted names: {}",
stats.youngest(),
stats.oldest(),
stats.names());
The collector-based approach combines youngest, oldest, and sorted-name computations in one stream traversal rather than launching separate pipelines. section 10.2.2
Use a practical decision rule:
- Profile CPU behavior and memory allocation when deeper evidence is needed; Java options include VisualVM, JDK Flight Recorder with Mission Control, Async-Profiler, and JOL for object layout. section 8.5.2
- Choose a lower-allocation imperative form when it stays clear; use a combined stream collector when the stream-based design must reduce repeated traversal and intermediate work. section 10.2.2






