-
Notifications
You must be signed in to change notification settings - Fork 0
lessons
Miguel Gamboa edited this page May 22, 2023
·
28 revisions
Lessons:
- 01-03-2023 - Lesson 01 - Introduction and Modern VMs, e.g. JVM, Node
- 03-03-2023 - Lesson 02 - Java Type System, Components and Metadata
- 08-03-2023 - Lesson 03 - Kotlin to JVM
- 08-03-2023 - Lesson 04 - Unmanaged versus Managed
- 10-03-2023 - Lesson 05 - Programming with Metadata
- 15-03-2023 - Lesson 06 - Programming with Metadata
- 15-03-2023 - Lesson 07 - Logger via reflection
- 17-03-2023 - Lesson 08 - Logger and interface Getter and Annotations
- 22-03-2023 - Lesson 09 - Lab 1 - Assignment 1
- 22-03-2023 - Lesson 10 - Lab 2 - Assignment 1
- 24-03-2023 - Lesson 11 - Logger and Annotations with Class parameter
- 27-03-2023 - Lesson 12 - Lab 3 - Assignment 1
- 27-03-2023 - Lesson 13 - Lab 4 - Assignment 1
- 05-04-2023 - Lesson 14 - Reflection Exercises
- 12-04-2023 - Lesson 14 - Bytecode
- 12-04-2023 - Lesson 15 - Benchmarking
- 14-04-2023 - Lesson 16 - Meta-programming and Cojen Maker
- 19-04-2023 - Lesson 17 - LoggerDynamic
- 19-04-2023 - Lesson 18 - Benchmarking
- 21-04-2023 - Lesson 19 - Lab 5 - Assignment 2
- 26-04-2023 - Lesson 20 - Lab 6 - Assignment 2
- 26-04-2023 - Lesson 21 - Lab 7 - Assignment 2
- 28-04-2023 - Lesson 22 - Boxing Unboxing
- 26-04-2023 - Lesson 23 - Lab 8 - Assignment 2
- 26-04-2023 - Lesson 24 - Lab 9 - Assignment 2
- 05-05-2023 - Lesson 25 - Garbage Collector
- 10-05-2023 - Lesson 26 - Sequences
- 10-05-2023 - Lesson 27 - yield
- 12-05-2023 - Lesson 28 - Closeable
- 17-05-2023 - Lesson 29 - Exercises with lazy sequences
- 17-05-2023 - Lesson 30 - Generics and Type Erasure
- 19-05-2023 - Lesson 31 - Lab 10 - Assignment 3
Bibliography - The Well-Grounded Java Developer, Second Edition:
- 1.1 - The language and the platform
- 1.2 - The new Java release model
- 9,6 - Java interoperability
- 4.1 - Class loading and class objects
- 4.2 - Class loaders
- 4.3 - Examining class files
- 4.5 - Reflection
- 4.5 - Bytecode
- 7.1 - Performance terminology: Some basic definitions
- 7.2 - A pragmatic approach to performance analysis
- 7.3 - What went wrong? Why do we have to care?
- 7.4 - Why is Java performance tuning hard?
- 7.5 - Garbage collection
- 15.3.7 - Sequences
- 16.3 - Under the hood with Kotlin coroutines
- Bibliography and Lecturing methodology: github and slack.
- Tools:
javac
,javap
,kotlinc
, JDK 17 and gradle. - Program outline in 3 parts:
- Java Type System and Reflection;
- Metaprogramming and Performance;
- Iterators versus Sequences (
yield
). Generics and Reified type parameters.
- Project in 3 parts according to program outline.
- Managed Runtime or Execution Environment.
- Informally virtual machine (VM) or runtime
- Historic evolution since Java to nowadays
- Examples of languages targeting JVM: Java, kotlin, Scala, Clojure.
- Examples of languages targeting Node: JavaScript, TypeScript, Kotlin.
- Java syntax similar to C and C++.
- Distinguish between Programming Language
<versus>
VM
- Type System - Set of rules and principles that specify how types are defined and behave.
- Module
.class
for eachclass
definition -
CLASSPATH
- e.g.
-cp .
- local folder - e.g.
-cp .:JetBrains/IntelliJIdea2022.2/plugins/Kotlin/lib/*
- (for windows use
;
rather than:
)
- e.g.
- Class have Members
- Members may be: Fields, Constructors or Methods.
- Constructor is a function with name
"<init>"
returningvoid
. - Using
javap -p Student.class
to inspect metadata - Using
javap -c -p AppKt.class
to inspect metadata and bytecode definition of data classPoint
- Analyzing kotin properties in Java.
- There are NO properties in Java Type System.
- A Kotlin property may generate:
- Backing field -- accessed with
getfield
bytecode. - Getter, i.e. function
get...
-- called withinvoke...
bytecode. - Setter, i.e. function
set...
(if defined withvar
).
- Backing field -- accessed with
- Kotlin
val
<=>
Javafinal
. - FIELDS => MEM
- bytecode
invokevirtual
to call e.g.getNr()
andprint()
-
Component - Reusable software unit, with:
- IR - code in intermediate representation (e.g. bytecode, IL, other)
- Metadata - auto description
- Ready to use
=>
does not require static compilation. - API
=>
conforms to a public interface. - Indivisible
=>
1 module (file)
-
Software development by Components:
- Reduce Complexity;
- Promote Reuse.
- Developer roles:
- Provider - provides a component with a well-known API (e.g.
Point
) - Client - uses the component from a provider to build an Application, or another component (e.g.
App
).
- Provider - provides a component with a well-known API (e.g.
- Managed Runtime:
- Software components = Metadata + IR (intermediate representation) (e.g. bytecode)
- Interoperability supported at bytecode and metadata level e.g. Java <> Kotlin
- Portability - Compile once and run everywhere with VM, e.g. JRE.
- Jitter - Just-in-time compiler
- Safety - NO invalid memory accesses
- GC - Garbage Collector.
- ClassLoader and CLASSPATH - dynamic and lazy load.
- Homework 01 resolution
- Java Type descriptors
- Java access control
- Immutability through
final
fields equivalent to kotlinval
-
Virtual and non-virtual methods (Kotlin
open
versus Javafinal
) - Optional
@Override
annotation in Java equivalent to kotlin keywordoverride
- Java
static
members - Translating kotlin companion objects to Java
- Translating kotlin extensions to Java
-
Unmanaged
<versus>
Managed - Static
<versus>
Dynamic link
- Building unmanaged components with static link:
- NOT a unit, but two parts instead: header + obj
- Need of a header file that describes the component content
- Different builds for different architectures
-
Structural modifications (new header)
=>
compilation + link -
Behavioral modifications
=>
link
- Demo with an unmanaged
App
using aPoint
.-
Static compilation (
gcc -c
) and static link (gcc
)
-
Static compilation (
- Demo with a managed
App
using aPoint
.- Jitter (just-in-time compiler) - compiles IR to native code (e.g. x86, amd64, ppc) at runtime
-
Dynamic link -
Point.class
onApp
compilation + link at runtime. -
Lazy Load -
Point.class
is loaded only when needed
- Homework 02 resolution
- Translating kotlin companion objects to Java
- Java static nested classes e.g.
class Person { static class Companion { .. }}
=>Person$Companion
- Java static constructor
- Singleton design pattern
- Private instance constructor to avoid new objects.
- Translating kotlin extensions to Java.
- Reflection object oriented API for metadata
-
KClass
is representing a type in Kotlin, whereasClass
is representing a type in JVM. -
KClass
--- .java --->
Class
-
Class
---- .kotlin --->
KClass
-
TypeName.class
- returns theClass
corresponding to the typeTypeName
. -
refObject.getClass()
- returns theClass
of the object referenced byrefObject
.
- Java Reflection API (
java.lang.reflect
):Class
,Member
,Field
,Executable
,Constructor
,Method
andParameter
. - Inspecting functions trough Reflection API
-
Method
=>Class<?> getReturnType()
;Parameter[] getParameters()
; -
Parameter
=>Class<?> getType()
- the declared type for the parameter represented by thisParameter
. -
Field
=>Class<?> getType()
- the declared type for the field represented by thisField
. -
NOTICE do not mislead
getType()
withgetClass()
- Invoking methods and creating instances:
-
Method
=>Object invoke(Object obj, Object... args)
- Invokes the underlying method represented by this
Method
object, on the specified objectobj
with the specified parametersargs
.
- Invokes the underlying method represented by this
-
Check if a method is static -
Modifier.isStatic(m.getModifiers())
-
Constructor
=>T newInstance(Object... initargs)
- Uses the constructor represented by this
Constructor
object to create and initialize a new instance of the constructor's declaring class, with the specified parametersinitargs
.
- Uses the constructor represented by this
-
ClassLoader
- class-loading capability exposed to the user programmer.-
BootstrapClassLoader
- used to get the absolute basic system loaded—essentiallyjava.base
. -
AppClassLoader
- loads the application classes in CLASSPATH. -
UrlClassLoader
- loads classes and resources from a search path of URLs.
-
- Example
static Stream<Class<?>>listClassesInClasspath() throws IOException {
ClassLoader cl = ClassLoader.getSystemClassLoader(); // An AppClassLoader instance
return list(cl.getResources("")).stream()
.filter(url -> url.getProtocol().equals("file")) // Exclude jar files
.map(url -> new File(toURI(url)))
.flatMap(f -> f.isDirectory() ? stream(f.listFiles()) : Stream.of(f))
.filter(f -> f.getName().contains(".class"))
.map(f -> loadClass(cl, qualifiedName(f.getName())));
}
- Homework 03 resolution
- Distinguishing between streams of
java.io
andjava.util.stream
. -
java.io
streams are related with implementations ofInputStream
andOutputStream
. -
java.util.stream
provides abstractions on sequences and its operations likefilter
,map
,reduce
, etc.
- Type check, e.g. if
m
is compatible withMethod
:-
m instanceof Method
=>true
ifm
is an instance of the classMethod
or any inherited class. -
m.getClass() == Method
=>true
ifm
has exactly the typeMethod
.
-
-
get<member>
versusgetDeclared<member>
to get inaccessible members (e.g.private
). -
setAccessible(boolean)
=> to invoke inaccessible methods or constructors (e.g.private
)
- Distinguishing the role of
App
domain--->
Logger
utility. - Unit tests with sample domain
Student
andPoint
- Simple
Logger
--->
Printer
- Decouple
Logger
output into aPrinter
interface. - Implement
PrinterConsole
andPrinterBuffer
. - Make
PrinterConsole
singleton. logFields(Object target)
-
logProperties(Object target)
- for parameterless methods withget
prefix and non void return type.
- Maintain getters on
Map<Class<?>, List<Getter>>
interface Getter { void getAndPrint(target: Object) }
- Distinct implementations:
loadFieldGetters()
,loadPropertyGetters()
, ... - Annotations
isAnnotationPresent(Class<? extends Annotation> annotationClass)
getAnnotation(Class<T> annotationClass)
Retention
-
@NonLog
andAltName(name="<alternate name>"
- Customizing the value printed by Logger.
- How to pass a function to annotations?
- Use
Class
as annotation parameter.
- Develop the utility Java function
Comparator<Object> comparerOrder(Class<?> klass)
that returns an instance ofComparator
that is able to compare objects of the type represented byklass
, according to the properties which are both:Comparable
and annotated withComparison
. Notice that you should compare respecting the order specified in annotation. Example:
class Student (
@Comparison(2) val nr:Int,
val name: String,
@Comparison(1) val nationality: String,
) |
val s1 = Student(12000, "Ana", "pt")
val s2 = Student(14000, "Ana", "pt")
val s3 = Student(11000, "Ana", "en")
val cmp = comparerOrder(Student::class)
assertTrue { cmp.compare(s1, s2) < 0 } // same nationality and 12000 is < 14000
assertTrue { cmp.compare(s2, s3) > 0 } // “pt” is > “en” |
- Develop the utility Java function
Comparator<Object> comparer(Class<?> klass)
that returns a new intance ofComparator
and it is able to compare instances of type represented byklass
, according to the properties which are:Comparable
OR annotated with aComparison
that specifies aComparator
for that property, according to the following example:
class Person(
val id: Int,
val name: String,
@Comparison(cmp = AddressByRoad::class) val address: Address,
@Comparison(cmp = AccountByBalance::class) val account: Account) {
}
class AccountByBalance : Comparator<Account>{
override fun compare(o1: Account, o2: Account): Int {
return o1.balance.compareTo(o2.balance);
}
}
class AddressByRoad : Comparator<Address> {
override fun compare(o1: Address, o2: Address): Int {
return o1.road.compareTo(o2.road)
}
} |
val p1 = Person(11000, "Ana", Address("Rua Amarela", 24), Account("FD3R", 9900))
val p2 = Person(11000, "Ana", Address("Rua Rosa", 24), Account("8YH5", 9900))
val p3 = Person(11000, "Ana", Address("Rua Rosa", 24), Account("JK2E", 100))
val p4 = Person(11000, "Ana", Address("Rua Rosa", 97), Account("BFR5", 100))
val p5 = Person(17000, "Ana", Address("Rua Rosa", 97), Account("BFR5", 100))
val cmp = Comparer<Person>(Person::class)
assertTrue { cmp.compare(p1, p2) < 0 } // Rua Amarela is < Rua Rosa
assertTrue { cmp.compare(p2, p3) > 0 } // 9900 is > 100
assertEquals(0, cmp.compare(p3, p4)) // All properties are equal
assertTrue { cmp.compare(p4, p5) < 0 } // 11000 is < 17000 |
-
Reference Types are instantiated in bytecode with:
-
new
- Allocates storage on Heap, initializes space and the object's header, and returns the reference to newbie object. -
invokespecial
- Call to class<init>
method (corresponding to constructor).
-
- Instantiating a refence type, e.g.
Student(765134, "Ze Manel")
may produce in bytecode:
new #8 // class Student
dup // duplicates the value on top of the stack
... // One load (push) for each parameter of <init> (constructor)
invokespecial #14 // Method Student."<init>"
-
invokestatic
- nothis
required -
invokespecial
- static dispatch (i.e. non polymorphic) -
invokevirtual
- dynamic dispatch (i.e. polymorphic) -
invokeinterface
- dynamic dispatch (i.e. polymorphic) for interface methods - Constant Pool
- 16-bit index into the constant pool
- Load and store opcodes
- Shortcut opcode forms
- Arithmetic
- Execution Flow
- Benchmark - assess the relative performance
- Benchmark != Unit Tests
- A naif approach - direct measurement, e.g.
measureTimeMillis { logger.log(Student(...)) }.also { println("logger.log() took $it millis"); }
- Some Problems:
- Mixing domain instantiation (i.e.
Student
) with operation executionlogger.log()
. - First execution includes Jitter overhead and misses optimizations.
- Milliseconds could not be accurate enough.
- IO may be orders of magnitude slower than
log
operation itself - IDE (e.g. InteliJ) may induce other overheads.
- Absolute results in milliseconds may vary on different hardware environments.
-
System.currentTimeMillis()
includes a System call with implicit overhead. - Garbage Collector may degrade performance
- Mixing domain instantiation (i.e.
- Minimize side effects:
- Remove domain instantiation from operation measurement
- Include warm-up => Optimizations may improve performance
- Measure the total execution of several iterations rather than several measurements of single executions.
- Avoid IO => Mocking IO
- Avoid extra tools such as IDE (e.g. InteliJ), gradle, or other => run directly on VM (e.g.
java
) - Baseline => How much can we improve performance?
- same as 3.
- GC => Run several iterations and discard most divergent results.
- JMH - Java Microbenchmark Harness.
- Benchmark tests annotated with
@Benchmark
. - JMH Gradle Plugin
gradlew jmhJar
-
java -jar <path to JAR> -f 1 -wi 4 -i 4 -w 2 -r 2 -tu ms
:-
-f
- forks -
-wi
- warm-up iterations -
-i
- iterations -
-w
- each warm-up iteration duration -
-r
- each iteration duration -
-tu
- time unit
-
- Introduction to Metaprograming and dynamic code generation;
- Cojen Maker API:
ClassMaker
,MethodMaker
,FieldMaker
-
finish(): Class
,finishTo(OutputStream)
- Auxiliary function
printBytecodes(bytes: ByteArray)
ofautorouter
test project.
-
Logger
refactoring with a base typeAbstractLogger
extended by every logger approach: baseline, reflect or dynamic. - Implementing a dynamic builder for a logger using Cojen Maker:
ClassMaker buildLoggerDynamicForProperties(Class<?> domain)
- Saving from the dead-code elimination.
- JMH
Blackhole
- "consumes" the values, conceiving no information to JIT whether the value is actually used afterwards. - JMH API:
@Setup
,@Param
,@State
- Comparing performance between: baseline, reflect or dynamic.
-
Casting between reference types:
- upcasting in bytecodes it is only copying references, e.g.
aload_0
andastore_1
- downcasting includes
checkcast
bytecode
- upcasting in bytecodes it is only copying references, e.g.
-
checkcast
checks for compatibility (i.e. same type or subtype). -
checkcast
throwsClassCastException
on unsuccess, otherwise leaves the object's reference on top of the stack.
- Primitive
<->
Reference: boxing or unboxing:- There is no specific JVM bytecode for these conversions.
- Supported in JVM through auxiliary functions (i.e.
valueOf()
and<type>Value()
) of Wrapper classes.
- Primitive Type
->
Reference: boxing through<Wrapper Type>.valueOf(primitive)
- e.g.
fun boxing(nr: Int) : Any { return nr }
- e.g.
- Reference Type
->
Primitive: unboxing through<Wrapper>.<primitive>Value()
- e.g.
fun unboxing(nr: Int?) : Int { return nr ?: throw Exception("Null not supported for primitive Int!") }
- e.g.
Types | Java | Kotlin |
---|---|---|
Value |
int , long , double , ... |
Int , Long , Double , ... |
Reference |
Integer , Long , Double , ... |
Int? , Long? , Double? , ... |
- Local values in Stack
<versus>
Objects in Heap. - Local variables managed on stack <versus> Objects managed on Heap by Garbage Collector
- Stack frame on function's execution => cleaned on function completion, including local variables.
- Unreachable objects are candidates for Garbage Collector
- Object's layout and header (containing type information):
-
header is useful for operations such as
obj::class
(<=>
obj.getClass()
in Java)
-
header is useful for operations such as
- Garbage Collection - "process of looking at heap memory, identifying which objects are in use and which are not, and deleting the unused objects."
- Unused object, or unreferenced object.
- In use object, or a referenced object:
- "some part of your program still maintains a pointer to that object."
- Referenced by a root or belongs to a graph with a root reference.
-
root reference:
- Local variables - stored in the stack of a thread.
- Static variables - belong to the class type stored in permanent generation.
- GC basic process: 1. Marking and 2. Deletion.
-
Deletion approaches:
- Normal Deletion => holds a list to free spaces. !! memory allocation slower !!
- Deletion with Compacting => move referenced objects together ++ makes new memory allocation faster !!! longer garbage collection time !!!
-
Generational Garbage Collection:
- Most objects are short lived => GC is more effective in younger generations.
- Enhance the performance
-
JVM Generations:
- Young Generation is where all new objects are allocated and aged.
- Old Generation is used to store long surviving objects.
- Permanent generation contains metadata, classes and methods.
- "Stop the World" events:
- minor garbage collection - collects the young generation.
- major garbage collection - collects the old generation.
- Young Generation process: Eden, Survivor Space 0 and Survivor Space 1.
- Distinguishing between:
- General-purpose language, e.g. Kotlin, Java C#,
- Domain-specific languages, e.g. SQL, Jetpack Compose, Linq,
- E.g. SQL for relational data, e.g.
SELECT COUNT (DISTINCT ...) FROM ... WHERE ...
- E.g. Jetpack Compose
application { Window { MaterialTheme { ... } Column {... } } }
- E.g. Linq
from s in studentList group s by s.StandardID into sg select new { sg.Key, sg };
-
Collection Pipeline -
"organize some computation as a sequence of operations which compose by taking
a collection as output of one operation and feeding it into the next."
- e.g. method chaining:
students.filter(...).map(...).distinct(...).count()
- e.g. nested function:
count(distinct( map (... filter (...))))
- e.g. method chaining:
- Collection pipeline:
-
Data Source
-->
Intermediate Operation*
-->
Terminal Operation
- May have many intermediate operations.
- After the terminal operation we cannot chain any intermediate operation.
-
-
Query Example:
From a students listing select the first surname starting with letter A of a student with number greater than 47000
. - Divide and reorder requirements:
-
From a students listing...
- data source -
... of a student with number greater than 47000
- - intermediate operation -
... surname ...
- intermediate operation -
... starting with letter A...
- - intermediate operation -
... select the first...
- terminal operation
-
- Kotlin 2 distinct APIs for collections pipeline: Iterable versus Sequences
- Collections are eager with horizontal processing
- Sequences are lazy with vertical processing and can be infinite.
- Marble diagrams.
- Implementing
convert
andwhere
equivalent to the eager version ofmap
andfilter
.
- Implementing
convert
andwhere
equivalent to the lazy version ofmap
andfilter
. - Implementing lazy iterators
SequenceConvert
andSequenceFilter
-
sequence
- Builds a Sequence lazily yielding values one by one. -
suspend fun yield(value: T)
- Yields a value to theIterator
being built and suspends until the next value is requested.
- The try-with-resources Statement
- A resource is an object that must be closed after use.
- Kotlin
use{}
extension -
Cleaner
- manage cleaning actions.-
public Cleanable register(Object obj, Runnable action)
- Registers an object and a cleaning action, i.e.Runnable
-
Cleanable
has a single methodclean()
that runs the cleaning action and unregisters the cleanable.
-
- Explicitly invoke the
clean()
method when the object is closed or no longer needed. - If the
close()
method is not called, the cleaning action is called by theCleaner
.
fun <T> Sequence<T>.concat(other: Sequence<T>) = Sequence<T>
fun <T : Any?> Sequence<T>.collapse() = Sequence<T>
fun <T> Sequence<T>.window(size: Int) = Sequence<Sequence<T>>
-
Type Erasure:
- Replace all type parameters in generic types with their bounds or
Object
- Insert type casts (i.e. bytecode
checkcast
) if necessary to preserve type safety.
- Replace all type parameters in generic types with their bounds or
-
Type Erasure and the need of parameter
destination: KClass<T>
- Inline functions and Reified type parameters
to allow the use of
T::class