├── .gitignore ├── labs-8 ├── gradle │ ├── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ └── jni.gradle ├── src │ └── main │ │ ├── java │ │ └── ru │ │ │ └── gvsmirnov │ │ │ └── perv │ │ │ └── labs │ │ │ ├── bugs │ │ │ ├── jdk8043188 │ │ │ │ ├── InterfaceWithField.java │ │ │ │ ├── InterfaceWithFieldAndDefaultMethod.java │ │ │ │ └── Main.java │ │ │ └── jdk8058847 │ │ │ │ └── Main.java │ │ │ ├── agent │ │ │ └── BloatedAgent.java │ │ │ ├── misc │ │ │ ├── LambdaOom.java │ │ │ ├── ScopingTest.java │ │ │ └── Jol.java │ │ │ ├── rekt │ │ │ └── ChecksumCalculator.java │ │ │ ├── gc │ │ │ ├── MaxMemory.java │ │ │ ├── NoGcTrigger.java │ │ │ ├── G1MemoryExhauster.java │ │ │ ├── ThreadLocals.java │ │ │ ├── Boxing.java │ │ │ ├── FixedBoxing.java │ │ │ ├── PrematurePromotion.java │ │ │ ├── WeakReferences.java │ │ │ ├── SoftReferences.java │ │ │ ├── PhantomReferences.java │ │ │ ├── G1Demo.java │ │ │ └── MetaspaceOom.java │ │ │ ├── safepoints │ │ │ ├── FullGc.java │ │ │ ├── BiasedLocks.java │ │ │ └── Deoptimization.java │ │ │ ├── util │ │ │ ├── OrderedWrapper.java │ │ │ └── Util.java │ │ │ ├── jit │ │ │ ├── DeadCode.java │ │ │ ├── AutoboxingElimination.java │ │ │ ├── EscapeAnalysis.java │ │ │ ├── LoopConditionals.java │ │ │ ├── EnumValues.java │ │ │ └── ComparingBenchmark.java │ │ │ ├── time │ │ │ ├── Savior.java │ │ │ ├── ObsessedTimeKiller.java │ │ │ ├── TimeKillerBenchmark.java │ │ │ ├── TimeKiller.java │ │ │ └── PrecisionTest.java │ │ │ └── concurrency │ │ │ ├── TestSubject.java │ │ │ └── VolatileBenchmark.java │ │ ├── headers │ │ └── jni_exports.h │ │ └── c │ │ └── ChecksumCalculator.c ├── build.gradle ├── gradlew.bat ├── results │ └── time.md └── gradlew ├── labs-7 ├── build.gradle └── src │ └── main │ └── java │ └── ru │ └── gvsmirnov │ └── perv │ └── labs │ └── gc │ └── NoGcTrigger.java ├── README.md └── LICENSE.md /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .gradle 3 | *.ipr 4 | *.iws 5 | *.iml 6 | .idea 7 | *.class 8 | -------------------------------------------------------------------------------- /labs-8/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gvsmirnov/java-perv/HEAD/labs-8/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/bugs/jdk8043188/InterfaceWithField.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.bugs.jdk8043188; 2 | 3 | public interface InterfaceWithField { 4 | 5 | long id = Main.register("InterfaceWithField"); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /labs-8/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Aug 30 18:10:56 EEST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.6-bin.zip 7 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/agent/BloatedAgent.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.agent; 2 | 3 | import java.lang.instrument.Instrumentation; 4 | 5 | public class BloatedAgent { 6 | public static void premain(String agentArgs, Instrumentation inst) { 7 | inst.addTransformer((loader, name, clazz, pd, originalBytes) -> originalBytes, true); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/bugs/jdk8043188/InterfaceWithFieldAndDefaultMethod.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.bugs.jdk8043188; 2 | 3 | public interface InterfaceWithFieldAndDefaultMethod { 4 | 5 | long id = Main.register("InterfaceWithFieldAndDefaultMethod"); 6 | 7 | default long getTime() { 8 | return System.currentTimeMillis(); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/misc/LambdaOom.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.misc; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | 6 | public class LambdaOom { 7 | public static void main(String[] args) { 8 | Collection leak = new ArrayList<>(); 9 | while(true) { 10 | leak.add(() -> {}); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /labs-7/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | sourceCompatibility = JavaVersion.VERSION_1_7 4 | 5 | repositories { 6 | jcenter() 7 | } 8 | 9 | dependencies { 10 | compile 'org.javassist:javassist:3.18.0-GA' 11 | } 12 | 13 | buildscript { 14 | repositories { jcenter() } 15 | dependencies { 16 | classpath 'com.github.jengelman.gradle.plugins:shadow:1.0.3' 17 | } 18 | } 19 | 20 | apply plugin: 'com.github.johnrengelman.shadow' 21 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/rekt/ChecksumCalculator.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.rekt; 2 | 3 | public class ChecksumCalculator { 4 | 5 | private static native long calculateChecksum(String filename); 6 | 7 | public static void main(String[] args) { 8 | 9 | System.loadLibrary("main"); 10 | long checksum = calculateChecksum(args[0]); 11 | System.out.println("Got checksum from native method: " + checksum); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/bugs/jdk8058847/Main.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.bugs.jdk8058847; 2 | 3 | public class Main { 4 | 5 | public static void main(String[] args) { 6 | crashJvm(); 7 | } 8 | 9 | private static final int[] values = new int[256]; 10 | 11 | private static void crashJvm() { 12 | byte[] bytes = new byte[] {-1}; 13 | while (true) { 14 | for (Byte b : bytes) { 15 | values[b & 0xff]++; 16 | } 17 | } 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /labs-7/src/main/java/ru/gvsmirnov/perv/labs/gc/NoGcTrigger.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.gc; 2 | 3 | import javassist.CannotCompileException; 4 | import javassist.ClassPool; 5 | 6 | public class NoGcTrigger { 7 | public static void main(String[] args) throws CannotCompileException { 8 | leakToPermGen(); 9 | } 10 | 11 | private static void leakToPermGen() throws CannotCompileException { 12 | ClassPool pool = ClassPool.getDefault(); 13 | for(long l = 0; l < Long.MAX_VALUE; l++) { 14 | pool.makeClass("com.example.Kitty" + l).toClass(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/gc/MaxMemory.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.gc; 2 | 3 | public class MaxMemory { 4 | 5 | // Run with -Xmx512m 6 | // Run with -Xmx512m -Xms512m 7 | // Run with -Xmx512m -Xms512m -XX:+PrintGCDetails, look at survivor space sizes 8 | // Run with -Xmx512m -XX:+UseG1GC 9 | // Run with -Xms512m -Xmx512m -XX:SurvivorRatio=100 10 | 11 | public static void main(final String[] args) { 12 | final long maxMemory = Runtime.getRuntime().maxMemory(); 13 | 14 | System.out.printf( 15 | "Max memory: %d MB", maxMemory / 1024 / 1024 16 | ); 17 | } 18 | } -------------------------------------------------------------------------------- /labs-8/src/main/headers/jni_exports.h: -------------------------------------------------------------------------------- 1 | /* DO NOT EDIT THIS FILE - it is machine generated */ 2 | #include 3 | /* Header for class ru_gvsmirnov_perv_labs_rekt_ChecksumCalculator */ 4 | 5 | #ifndef _Included_ru_gvsmirnov_perv_labs_rekt_ChecksumCalculator 6 | #define _Included_ru_gvsmirnov_perv_labs_rekt_ChecksumCalculator 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | /* 11 | * Class: ru_gvsmirnov_perv_labs_rekt_ChecksumCalculator 12 | * Method: calculateChecksum 13 | * Signature: (Ljava/lang/String;)J 14 | */ 15 | JNIEXPORT jlong JNICALL Java_ru_gvsmirnov_perv_labs_rekt_ChecksumCalculator_calculateChecksum 16 | (JNIEnv *, jclass, jstring); 17 | 18 | #ifdef __cplusplus 19 | } 20 | #endif 21 | #endif 22 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/safepoints/FullGc.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.safepoints; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | 6 | public class FullGc { 7 | 8 | private static final Collection leak = new ArrayList<>(); 9 | private static volatile Object sink; 10 | 11 | // Run with: -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDetails 12 | // Notice that all the stop the world pauses coincide with GC pauses 13 | 14 | public static void main(String[] args) { 15 | while(true) { 16 | try { 17 | leak.add(new byte[1024 * 1024]); 18 | 19 | sink = new byte[1024 * 1024]; 20 | } catch(OutOfMemoryError e) { 21 | leak.clear(); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/gc/NoGcTrigger.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.gc; 2 | 3 | import javassist.CannotCompileException; 4 | import javassist.ClassPool; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Collection; 8 | 9 | public class NoGcTrigger { 10 | 11 | public static void main(String[] args) throws CannotCompileException { 12 | // leakMetaSpace(); 13 | leakObjects(); 14 | } 15 | 16 | private static void leakMetaSpace() throws CannotCompileException { 17 | ClassPool pool = ClassPool.getDefault(); 18 | for(long l = 0; l < Long.MAX_VALUE; l++) { 19 | pool.makeClass("com.example.Kitty" + l).toClass(); 20 | } 21 | } 22 | 23 | public static void leakObjects() { 24 | Collection objects = new ArrayList<>(); 25 | while(true) { 26 | objects.add(new byte[2048]); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/gc/G1MemoryExhauster.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.gc; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class G1MemoryExhauster { 7 | 8 | public static final List sink = new ArrayList<>(); 9 | 10 | // Run with: -Xmx256m -XX:+UseG1GC -XX:+PrintGCDetails 11 | // Pay attention to the memory usage at the time of the OOME. 12 | // See http://hg.openjdk.java.net/jdk9/jdk9/hotspot/file/efe1782aad5c/src/share/vm/gc/g1/g1CollectedHeap.cpp#l475 13 | 14 | public static void main(String[] args) { 15 | try { 16 | while (true) { 17 | sink.add(new byte[1024]); 18 | } 19 | } catch (OutOfMemoryError err) { 20 | sink.clear(); 21 | err.printStackTrace(); 22 | } 23 | } 24 | 25 | // Bonus questions: what changes if we remove/add `sink.clear();` and why? 26 | 27 | } 28 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/gc/ThreadLocals.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.gc; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | public class ThreadLocals { 6 | 7 | private static final ThreadLocal locals = new ThreadLocal<>(); 8 | 9 | private static volatile Object sink; 10 | 11 | public static void main(String[] args) throws InterruptedException { 12 | for(long l = 0; l < Long.MAX_VALUE; l++) { 13 | System.out.println(l); 14 | Thread thread = new Thread(ThreadLocals::shitToThreadLocal); 15 | thread.start(); 16 | 17 | sink = new byte[1024 * 1024]; 18 | } 19 | } 20 | 21 | private static void shitToThreadLocal() { 22 | locals.set(new byte[16 * 1024 * 1024]); 23 | 24 | long endTime = System.nanoTime() + TimeUnit.MICROSECONDS.toNanos(100); 25 | 26 | while(System.nanoTime() < endTime); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/util/OrderedWrapper.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.util; 2 | 3 | public class OrderedWrapper implements Comparable> { 4 | public final long ordinal; 5 | public final T value; 6 | 7 | public OrderedWrapper(long ordinal, T value) { 8 | this.ordinal = ordinal; 9 | this.value = value; 10 | } 11 | 12 | @Override 13 | public int compareTo(OrderedWrapper o) { 14 | return Long.valueOf(ordinal).compareTo(o.ordinal); 15 | } 16 | 17 | @Override 18 | public boolean equals(Object o) { 19 | if (this == o) return true; 20 | if (o == null || getClass() != o.getClass()) return false; 21 | 22 | OrderedWrapper that = (OrderedWrapper) o; 23 | 24 | if (value != null ? !value.equals(that.value) : that.value != null) return false; 25 | 26 | return true; 27 | } 28 | 29 | @Override 30 | public int hashCode() { 31 | return value != null ? value.hashCode() : 0; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/misc/ScopingTest.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.misc; 2 | 3 | public class ScopingTest { 4 | 5 | public volatile int size = (int) (Runtime.getRuntime().maxMemory() * 0.6); 6 | public volatile int allocatedSize; 7 | 8 | public static void main(String[] args) throws Exception { 9 | 10 | ScopingTest test = new ScopingTest(); 11 | 12 | for (int i = 0; i < 1000; i++) { 13 | test.allocateMemory(i); 14 | } 15 | } 16 | 17 | private void allocateMemory(int i) { 18 | try { 19 | { 20 | byte[] bytes = new byte[size]; 21 | allocatedSize = bytes.length; 22 | } 23 | 24 | byte[] moreBytes = new byte[size]; 25 | allocatedSize = moreBytes.length; 26 | 27 | System.out.println("I allocated memory successfully " + i); 28 | 29 | } catch (OutOfMemoryError e) { 30 | System.out.println("I failed to allocate memory " + i); 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/jit/DeadCode.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.jit; 2 | 3 | import org.openjdk.jmh.annotations.*; 4 | import org.openjdk.jmh.runner.RunnerException; 5 | 6 | /** 7 | * TODO: link to the blog post 8 | */ 9 | @State(Scope.Benchmark) 10 | public class DeadCode extends ComparingBenchmark { 11 | 12 | private double value= Math.random() * Integer.MAX_VALUE; 13 | 14 | @Benchmark 15 | public double measureA() { 16 | double previous; 17 | double current = value / 2; 18 | 19 | do { 20 | previous = current; 21 | current = (previous + (value / previous)) / 2; 22 | } while ((previous - current) > 1e-15); 23 | 24 | return current; 25 | } 26 | 27 | @Benchmark 28 | public double measureB() { 29 | for (int i = 0; i < 1_000_000; i++); 30 | return Math.sqrt(value); 31 | } 32 | 33 | public static void main(String[] args) throws RunnerException { 34 | runBenchmarks_JIT_vs_Interpreter(DeadCode.class); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/gc/Boxing.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.gc; 2 | 3 | import java.util.concurrent.locks.LockSupport; 4 | 5 | public class Boxing { 6 | 7 | private static volatile Double sensorValue; 8 | 9 | private static void readSensor() { 10 | while(true) { 11 | sensorValue = Math.random(); 12 | } 13 | } 14 | 15 | private static void processSensorValue(Double value) { 16 | if(value != null) { 17 | // Be warned: may take more than one usec on some machines, especially Windows 18 | LockSupport.parkNanos(1000); 19 | } 20 | } 21 | 22 | public static void main(String[] args) { 23 | int iterations = args.length > 0 ? Integer.parseInt(args[0]) : 1_000_000; 24 | 25 | initSensor(); 26 | 27 | for(int i = 0; i < iterations; i ++) { 28 | processSensorValue(sensorValue); 29 | } 30 | } 31 | 32 | private static void initSensor() { 33 | Thread sensorReader = new Thread(Boxing::readSensor); 34 | 35 | sensorReader.setDaemon(true); 36 | sensorReader.start(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/gc/FixedBoxing.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.gc; 2 | 3 | import java.util.concurrent.locks.LockSupport; 4 | 5 | public class FixedBoxing { 6 | 7 | private static volatile double sensorValue = Double.NaN; 8 | 9 | private static void readSensor() { 10 | while(true) { 11 | sensorValue = Math.random(); 12 | } 13 | } 14 | 15 | private static void processSensorValue(double value) { 16 | if(!Double.isNaN(value)) { 17 | // Be warned: may take more than one usec on some machines, especially Windows 18 | LockSupport.parkNanos(1000); 19 | } 20 | } 21 | 22 | public static void main(String[] args) { 23 | int iterations = args.length > 0 ? Integer.parseInt(args[0]) : 1_000_000; 24 | 25 | initSensor(); 26 | 27 | for(int i = 0; i < iterations; i ++) { 28 | processSensorValue(sensorValue); 29 | } 30 | } 31 | 32 | private static void initSensor() { 33 | Thread sensorReader = new Thread(FixedBoxing::readSensor); 34 | 35 | sensorReader.setDaemon(true); 36 | sensorReader.start(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/time/Savior.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.time; 2 | 3 | import ru.gvsmirnov.perv.labs.util.OrderedWrapper; 4 | 5 | import java.util.concurrent.PriorityBlockingQueue; 6 | 7 | class Savior implements Runnable { 8 | 9 | private final PriorityBlockingQueue> schedule = new PriorityBlockingQueue<>(); 10 | 11 | public void schedule(long afterNanos, ObsessedTimeKiller whoToStop) { 12 | schedule.offer(new OrderedWrapper<>(afterNanos, whoToStop)); 13 | } 14 | 15 | @Override 16 | public void run() { 17 | while(true) { 18 | OrderedWrapper badGuy = null; 19 | try { 20 | badGuy = schedule.take(); 21 | } catch (InterruptedException e) { 22 | throw new RuntimeException(e); 23 | } 24 | 25 | long targetTime = System.nanoTime() + badGuy.ordinal; 26 | while(System.nanoTime() < targetTime); 27 | 28 | // There could be a race between invoking killingSpree() and enoughDeaths(), hence the loop 29 | while(!badGuy.value.isStopped()) { 30 | badGuy.value.enoughDeaths(); 31 | } 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/jit/AutoboxingElimination.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.jit; 2 | 3 | import org.openjdk.jmh.annotations.Benchmark; 4 | import org.openjdk.jmh.annotations.Scope; 5 | import org.openjdk.jmh.annotations.State; 6 | import org.openjdk.jmh.runner.RunnerException; 7 | 8 | @State(Scope.Benchmark) 9 | public class AutoboxingElimination extends ComparingBenchmark { 10 | public int anInt = (int) (Math.random() * Integer.MAX_VALUE); 11 | public Integer anInteger = new Integer(anInt); 12 | 13 | 14 | @Benchmark 15 | public Integer baseline_returnObject() { 16 | return anInteger; 17 | } 18 | 19 | @Benchmark 20 | public int baseline_returnInt() { 21 | return anInt; 22 | } 23 | 24 | @Benchmark 25 | public int measure_autoUnboxed() { 26 | return new Integer(anInt); 27 | } 28 | 29 | @Benchmark 30 | public int measure_manualUnboxed() { 31 | return new Integer(anInt).intValue(); 32 | } 33 | 34 | @Benchmark 35 | public Integer measure_Boxed() { 36 | return new Integer(anInt); 37 | } 38 | 39 | public static void main(String[] args) throws RunnerException { 40 | runBenchmarks_NoEscape(AutoboxingElimination.class); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/bugs/jdk8043188/Main.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.bugs.jdk8043188; 2 | 3 | import java.util.concurrent.atomic.AtomicLong; 4 | 5 | public class Main { 6 | 7 | private interface Child1 extends InterfaceWithField { 8 | long childId = register("Child1"); 9 | } 10 | 11 | private interface Child2 extends InterfaceWithFieldAndDefaultMethod { 12 | long childId = register("Child2"); 13 | } 14 | 15 | private static class ChildClass implements InterfaceWithFieldAndDefaultMethod { 16 | 17 | } 18 | 19 | public static void main(String[] args) { 20 | // println("Observed: Child1 -> %d", Child1.childId); 21 | // println("Observed: Child2 -> %d", Child2.childId); 22 | 23 | ChildClass c = new ChildClass(); 24 | c.getTime(); 25 | } 26 | 27 | private static final AtomicLong currentId = new AtomicLong(1); 28 | 29 | public static long register(String name) { 30 | final long id = currentId.getAndIncrement(); 31 | 32 | println("Registered: %s -> %d", name, id); 33 | 34 | return id; 35 | } 36 | 37 | private static void println(String format, Object... args) { 38 | System.out.println(String.format(format, args)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Java Perversions 2 | ================ 3 | 4 | Various java-related perversions. Do not try this in production. 5 | Unless you absolutely have to, or just feel like it, in which case totally do! 6 | 7 | labs-time 8 | --------- 9 | A tool that tries to empirically estimate the precision of various ways to 10 | bide your time in java. 11 | 12 | Usage: 13 | ``` 14 | cd labs-8/ 15 | gradle clean shadow 16 | java -XX:-UseBiasedLocking -cp build/libs/perverted-labs-0.1.jar ru.gvsmirnov.perv.labs.time.PrecisionTest 17 | ``` 18 | 19 | To make sure that you are not getting rubbish results due to internal VM activity, run it with 20 | ``` 21 | java -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -verbose:gc -XX:+PrintInlining -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 -cp build/libs/perverted-labs-0.1.jar ru.gvsmirnov.perv.labs.time.PrecisionTest -v 22 | ``` 23 | 24 | The precision may be affected by both resolution and by invocation cost. Use this command to estimate the invocation cost: 25 | ``` 26 | java -cp build/libs/perverted-labs-0.1.jar org.openjdk.jmh.Main "ru.gvsmirnov.perv.labs.time.*" -f -tu us -bm sample 27 | ``` 28 | 29 | labs-concurrency 30 | ---------------- 31 | Various investigations related to concurrency 32 | (e.g. http://gvsmirnov.ru/blog/tech/2014/02/10/jmm-under-the-hood.html) 33 | 34 | labs-jit 35 | -------- 36 | Messing with JIT, see here: -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/safepoints/BiasedLocks.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.safepoints; 2 | 3 | import java.util.concurrent.locks.LockSupport; 4 | import java.util.stream.Stream; 5 | 6 | public class BiasedLocks { 7 | 8 | private static synchronized void contend() { 9 | LockSupport.parkNanos(100_000); 10 | } 11 | 12 | // Run with: -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDetails 13 | // Notice that there are a lot of stop the world pauses, but no actual garbage collections 14 | // This is because PrintGCApplicationStoppedTime actually shows all the STW pauses 15 | 16 | // To see what's happening here, you may use the following arguments: 17 | // -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 18 | // It will reveal that all the safepoints are due to biased lock revocations. 19 | 20 | // Biased locks are on by default, but you can disable them by -XX:-UseBiasedLocking 21 | // It is quite possible that in the modern massively parallel world, they should be 22 | // turned back off by default 23 | 24 | public static void main(String[] args) throws InterruptedException { 25 | 26 | Thread.sleep(5_000); // Because of BiasedLockingStartupDelay 27 | 28 | Stream.generate(() -> new Thread(BiasedLocks::contend)) 29 | .limit(10) 30 | .forEach(Thread::start); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /labs-8/src/main/c/ChecksumCalculator.c: -------------------------------------------------------------------------------- 1 | #include "jni_exports.h" 2 | 3 | #ifndef MAX_FILE_NAME_LENGTH 4 | // 17 is strlen(123.txt.digested) + 1 for the terminating zero byte 5 | #define MAX_FILE_NAME_LENGTH 17 6 | #endif 7 | 8 | int digest(char *src_filename, char *dst_filename) { 9 | // For this demonstration, we do not actually need to do anything here 10 | // Assume that this function opens src file, calculates the checksum 11 | // and writes it to dst_filename. 12 | return rand(); 13 | } 14 | 15 | JNIEXPORT jlong JNICALL Java_ru_gvsmirnov_perv_labs_rekt_ChecksumCalculator_calculateChecksum 16 | (JNIEnv * jniEnv, jclass clazz, jstring filename) { 17 | 18 | int result; 19 | const char *src_filename; 20 | char dst_filename[MAX_FILE_NAME_LENGTH]; 21 | 22 | // Get the source file name 23 | src_filename = (*jniEnv)->GetStringUTFChars(jniEnv, filename, NULL); 24 | 25 | // Get the name of the file to write checksum to (and "accidentally" overflow the buffer) 26 | sprintf(dst_filename, "%s.digested", src_filename); 27 | 28 | // Write the checksum to the destination file and also get its value 29 | result = digest(src_filename, dst_filename); 30 | 31 | // Free the memory used for source file name 32 | (*jniEnv)->ReleaseStringUTFChars(jniEnv, filename, src_filename); 33 | 34 | printf("Exiting native method with checksum: %d\n", result); 35 | 36 | return result; 37 | } -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/jit/EscapeAnalysis.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.jit; 2 | 3 | import org.openjdk.jmh.annotations.Benchmark; 4 | import org.openjdk.jmh.annotations.Scope; 5 | import org.openjdk.jmh.annotations.State; 6 | import org.openjdk.jmh.runner.RunnerException; 7 | 8 | @State(Scope.Benchmark) 9 | public class EscapeAnalysis extends ComparingBenchmark { 10 | public int id = (int) (Math.random() * Integer.MAX_VALUE); 11 | 12 | public static class Convict { 13 | public final int id; 14 | 15 | public Convict(int id) { 16 | this.id = id; 17 | } 18 | } 19 | 20 | @Benchmark 21 | public Object baseline_returnObject() { 22 | return this; 23 | } 24 | 25 | @Benchmark 26 | public int baseline_returnInt() { 27 | // TODO: for some reason, this yields larger error 28 | // than measureNoEscape or measureDeadCode. 29 | return this.id; 30 | } 31 | 32 | @Benchmark 33 | public Object measureEscape() { 34 | return new Convict(id); 35 | } 36 | 37 | @Benchmark 38 | public int measureDeadCode() { 39 | Convict convict = new Convict(id); 40 | return this.id; 41 | } 42 | 43 | @Benchmark 44 | public int measureNoEscape() { 45 | return new Convict(id).id; 46 | } 47 | 48 | public static void main(String[] args) throws RunnerException { 49 | runBenchmarks_NoEscape(EscapeAnalysis.class); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/concurrency/TestSubject.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.concurrency; 2 | 3 | import java.util.concurrent.CountDownLatch; 4 | import java.util.concurrent.ExecutorService; 5 | import java.util.concurrent.Executors; 6 | 7 | public class TestSubject { 8 | 9 | private static final int DEFAULT_ITERATIONS = 10_000 * 2; 10 | private volatile boolean finished; 11 | private int value = 0; 12 | 13 | void executedOnCpu0() { 14 | value = 10; 15 | finished = true; 16 | } 17 | 18 | void executedOnCpu1() { 19 | while(!finished); 20 | assert value == 10; 21 | } 22 | 23 | public static void main(String[] args) throws InterruptedException { 24 | int iterations = args.length > 0 ? Integer.valueOf(args[0]) : DEFAULT_ITERATIONS; 25 | final ExecutorService executor = Executors.newFixedThreadPool(2); 26 | 27 | for(int i = 0; i < iterations; i ++) { 28 | final TestSubject poorGuy = new TestSubject(); 29 | final CountDownLatch latch = new CountDownLatch(2); 30 | 31 | executor.execute(() -> { 32 | poorGuy.executedOnCpu1(); 33 | latch.countDown(); 34 | }); 35 | 36 | executor.execute(() -> { 37 | poorGuy.executedOnCpu0(); 38 | latch.countDown(); 39 | }); 40 | 41 | latch.await(); 42 | } 43 | 44 | executor.shutdown(); 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/safepoints/Deoptimization.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.safepoints; 2 | 3 | public class Deoptimization { 4 | 5 | private interface Burner { 6 | void burn(long nanos); 7 | } 8 | 9 | // Run with: -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 -XX:+PrintCompilation 10 | // Notice that there was no apparent safepoint triggered by deoptimization. 11 | // This is because the JVM triggers safepoints every second by default with 12 | // "no vm operation" specified as the vmop. 13 | 14 | // If the same is run with -XX:+UnlockDiagnosticVMOptions -XX:GuaranteedSafepointInterval=0, 15 | // you will see the deoptimization-triggered safepoint 16 | 17 | public static void main(String[] args) throws InterruptedException { 18 | runManyTimes(new Burner1()); // JIT speculatively assumes a monomorphic callsite 19 | runManyTimes(new Burner2()); // Assumption fails 20 | } 21 | 22 | private static void runManyTimes(Burner burner) { 23 | for(int i = 0; i < 100_000; i++) { 24 | burner.burn(10); 25 | } 26 | } 27 | 28 | private static class Burner1 implements Burner { 29 | 30 | @Override 31 | public void burn(long nanos) { 32 | long endTime = System.nanoTime() + nanos; 33 | while(System.nanoTime() < endTime); 34 | } 35 | } 36 | 37 | private static class Burner2 implements Burner { 38 | @Override 39 | public void burn(long nanos) { 40 | 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/jit/LoopConditionals.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.jit; 2 | 3 | import org.openjdk.jmh.annotations.Benchmark; 4 | import org.openjdk.jmh.annotations.OperationsPerInvocation; 5 | import org.openjdk.jmh.annotations.Scope; 6 | import org.openjdk.jmh.annotations.State; 7 | import org.openjdk.jmh.infra.Blackhole; 8 | 9 | import java.util.ArrayList; 10 | import java.util.LinkedList; 11 | 12 | @State(Scope.Benchmark) 13 | public class LoopConditionals { 14 | public static final int ITERATIONS = 100_000; 15 | 16 | volatile boolean condition = true; 17 | 18 | // The two methods below have roughly the same performance 19 | 20 | @Benchmark 21 | @OperationsPerInvocation(ITERATIONS) 22 | public void measureInternal(Blackhole bh) { 23 | final boolean condition = this.condition; 24 | 25 | for(int i = 0; i < ITERATIONS; i ++) { 26 | if(condition) { 27 | bh.consume(new ArrayList<>()); 28 | } else { 29 | bh.consume(new LinkedList<>()); 30 | } 31 | } 32 | } 33 | 34 | @Benchmark 35 | @OperationsPerInvocation(ITERATIONS) 36 | public void measureExternal(Blackhole bh) { 37 | final boolean condition = this.condition; 38 | 39 | if(condition) { 40 | for (int i = 0; i < ITERATIONS; i++) { 41 | bh.consume(new ArrayList<>()); 42 | } 43 | } else { 44 | for (int i = 0; i < ITERATIONS; i++) { 45 | bh.consume(new LinkedList<>()); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /labs-8/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | sourceCompatibility = JavaVersion.VERSION_1_8 4 | 5 | repositories { 6 | mavenLocal() 7 | jcenter() 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | compile 'org.openjdk.jmh:jmh-core:1.6.1' 13 | compile 'org.openjdk.jmh:jmh-generator-annprocess:1.6.1' 14 | 15 | compile 'org.openjdk.jol:jol-core:0.3.1' 16 | 17 | compile 'args4j:args4j:2.0.26' 18 | 19 | compile 'com.fasterxml.jackson.core:jackson-core:2.3.2' 20 | compile 'com.fasterxml.jackson.core:jackson-databind:2.3.2' 21 | 22 | compile 'org.javassist:javassist:3.18.0-GA' 23 | } 24 | 25 | 26 | buildscript { 27 | repositories { jcenter() } 28 | dependencies { 29 | classpath 'com.github.jengelman.gradle.plugins:shadow:1.2.0' 30 | classpath 'com.github.rholder:gradle-one-jar:1.0.4' 31 | } 32 | } 33 | 34 | apply plugin: 'com.github.johnrengelman.shadow' 35 | apply from: "$projectDir/gradle/jni.gradle" 36 | 37 | wrapper { 38 | gradleVersion '2.6' 39 | } 40 | 41 | 42 | task bloatedAgentJar(type: Jar, dependsOn: ['jar']) { 43 | archiveName = 'bloated-agent.jar' 44 | 45 | 46 | manifest { 47 | attributes( 48 | "Premain-Class": "ru.gvsmirnov.perv.labs.agent.BloatedAgent", 49 | "Main-Class": "ru.gvsmirnov.perv.labs.gc.MetaspaceOom", 50 | "Can-Retransform-Classes": "true" 51 | ) 52 | } 53 | 54 | from { 55 | configurations.compile.collect { 56 | zipTree(it).matching { exclude 'META-INF/**' } 57 | } 58 | } 59 | 60 | from zipTree(jar.archivePath) 61 | } -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/misc/Jol.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.misc; 2 | 3 | import java.lang.ref.WeakReference; 4 | 5 | import static org.openjdk.jol.info.ClassLayout.parseClass; 6 | import static org.openjdk.jol.info.GraphLayout.parseInstance; 7 | import static org.openjdk.jol.util.VMSupport.vmDetails; 8 | 9 | public class Jol { 10 | 11 | public static void main(String[] args) { 12 | details(); 13 | 14 | weakRef(object()); 15 | weakRef(bytearray(1 << 16)); 16 | } 17 | 18 | private static Object object() { 19 | final Object object = new Object(); 20 | 21 | out(parseClass(Object.class).toPrintable()); 22 | out(parseInstance(object).toPrintable()); 23 | 24 | return object; 25 | } 26 | 27 | private static byte[] bytearray(int size) { 28 | final byte[] array = new byte[size]; 29 | 30 | out(parseClass(byte[].class).toPrintable()); 31 | out(parseInstance(array).toPrintable()); 32 | 33 | return array; 34 | } 35 | 36 | private static WeakReference weakRef(Object to) { 37 | WeakReference ref = new WeakReference<>(to); 38 | 39 | out(parseClass(WeakReference.class).toPrintable()); 40 | out(parseInstance(ref).toPrintable()); 41 | 42 | return ref; 43 | } 44 | 45 | private static void details() { 46 | out(vmDetails()); 47 | } 48 | 49 | private static void out(Object o) { 50 | System.out.println(o.toString()); 51 | } 52 | 53 | private static void out(String format, Object... args) { 54 | out(String.format(format, args)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/jit/EnumValues.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.jit; 2 | 3 | import org.openjdk.jmh.annotations.Benchmark; 4 | import org.openjdk.jmh.annotations.Scope; 5 | import org.openjdk.jmh.annotations.State; 6 | 7 | import java.util.Map; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | 10 | @State(Scope.Benchmark) 11 | public class EnumValues { 12 | 13 | volatile int target = 2; 14 | 15 | @Benchmark 16 | public Bool baseline() { 17 | return Bool.TRUE; 18 | } 19 | 20 | @Benchmark 21 | public Bool measureBuiltin() { 22 | return lookup(Bool.values(), target); 23 | } 24 | 25 | @Benchmark 26 | public Bool measureManualArray() { 27 | return lookup(Bool.manualValues(), target); 28 | } 29 | 30 | @Benchmark 31 | public Bool measureCached() { 32 | return lookup(Bool.valuesCache, target); 33 | } 34 | 35 | @Benchmark 36 | public Bool measureCachedMap() { 37 | return Bool.valuesMap.get(target); 38 | } 39 | 40 | private static Bool lookup(Bool[] array, int target) { 41 | for(Bool bool : array) { 42 | if(bool.ordinal() == target) { 43 | return bool; 44 | } 45 | } 46 | 47 | return null; 48 | } 49 | 50 | private static enum Bool { 51 | TRUE, FALSE, FILE_NOT_FOUND; 52 | 53 | private static final Bool[] valuesCache = values(); 54 | private static final Map valuesMap = new ConcurrentHashMap<>(); 55 | 56 | private static Bool[] manualValues() { 57 | return new Bool[] {TRUE, FALSE, FILE_NOT_FOUND}; 58 | } 59 | 60 | static { 61 | for(Bool bool : values()) { 62 | valuesMap.put(bool.ordinal(), bool); 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/util/Util.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.util; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | public class Util { 6 | 7 | public static String shortName(TimeUnit unit) { 8 | switch (unit) { 9 | case NANOSECONDS: return "ns"; 10 | case MICROSECONDS: return "us"; 11 | case MILLISECONDS: return "ms"; 12 | case SECONDS: return "s"; 13 | case MINUTES: return "m"; 14 | case HOURS: return "h"; 15 | case DAYS: return "days"; 16 | default:return unit.toString(); 17 | } 18 | } 19 | 20 | 21 | public static TimeUnit getUnit(long nanos) { 22 | 23 | for(TimeUnit unit : TimeUnit.values()) { 24 | long current = unit.convert(nanos, TimeUnit.NANOSECONDS); 25 | if(current < 1000) { 26 | return unit; 27 | } 28 | } 29 | 30 | return TimeUnit.NANOSECONDS; 31 | } 32 | 33 | public static String annotate(long nanos) { 34 | final boolean negative = nanos < 0; 35 | 36 | nanos = Math.abs(nanos); 37 | TimeUnit unit = getUnit(nanos); 38 | 39 | long fullPart = unit.convert(nanos, TimeUnit.NANOSECONDS); 40 | 41 | long remainder = nanos - unit.toNanos(fullPart); 42 | long one = unit.toNanos(1); 43 | 44 | int fractionalPart = (int) (1000.0 * remainder / one); 45 | 46 | return (negative ? "-" : "") + fullPart + "." + fractionalPart + " " + shortName(unit); 47 | } 48 | 49 | public static void out() { 50 | out(""); 51 | } 52 | 53 | public static void out(String format, Object... args) { 54 | outClear(format, args); 55 | System.out.println(); 56 | } 57 | 58 | public static void outClear(String format, Object... args) { 59 | System.out.print(String.format(format, args)); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/concurrency/VolatileBenchmark.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.concurrency; 2 | 3 | import org.openjdk.jmh.annotations.Benchmark; 4 | import org.openjdk.jmh.annotations.Group; 5 | import org.openjdk.jmh.annotations.Scope; 6 | import org.openjdk.jmh.annotations.State; 7 | 8 | public class VolatileBenchmark { 9 | 10 | @Benchmark 11 | @Group("plainLocal") 12 | public int plainLocalRead(PlainLocal value) { 13 | return value.value; 14 | } 15 | 16 | @Benchmark 17 | @Group("plainLocal") 18 | public int plainLocalIncrement(PlainLocal value) { 19 | return value.value++; 20 | } 21 | 22 | @Benchmark 23 | @Group("plainShared") 24 | public int plainSharedRead(PlainShared value) { 25 | return value.value; 26 | } 27 | 28 | @Benchmark 29 | @Group("plainShared") 30 | public int plainSharedIncrement(PlainShared value) { 31 | return value.value++; 32 | } 33 | 34 | @Benchmark 35 | @Group("volatileLocal") 36 | public int plainVolatileRead(VolatileLocal value) { 37 | return value.value; 38 | } 39 | 40 | @Benchmark 41 | @Group("volatileLocal") 42 | public int plainVolatileIncrement(VolatileLocal value) { 43 | return value.value++; 44 | } 45 | 46 | @Benchmark 47 | @Group("volatileShared") 48 | public int plainVolatileRead(VolatileShared value) { 49 | return value.value; 50 | } 51 | 52 | @Benchmark 53 | @Group("volatileShared") 54 | public int plainVolatileIncrement(VolatileShared value) { 55 | return value.value++; 56 | } 57 | 58 | 59 | @State(Scope.Benchmark) 60 | public static class PlainShared { 61 | private int value; 62 | } 63 | 64 | @State(Scope.Thread) 65 | public static class PlainLocal { 66 | private int value; 67 | } 68 | 69 | @State(Scope.Benchmark) 70 | public static class VolatileShared { 71 | private volatile int value; 72 | } 73 | 74 | @State(Scope.Thread) 75 | public static class VolatileLocal { 76 | private volatile int value; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/time/ObsessedTimeKiller.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.time; 2 | 3 | import java.util.concurrent.locks.LockSupport; 4 | 5 | public abstract class ObsessedTimeKiller extends TimeKiller { 6 | private final Savior savior; 7 | private volatile boolean stopped; 8 | 9 | 10 | protected ObsessedTimeKiller() { 11 | savior = new Savior(); 12 | Thread thread = new Thread(savior); 13 | thread.setDaemon(true); 14 | thread.start(); 15 | } 16 | 17 | @Override 18 | public void tryKill(long nanosToKill) { 19 | stopped = false; 20 | 21 | savior.schedule(nanosToKill, this); 22 | killingSpree(); 23 | 24 | stopped = true; 25 | } 26 | 27 | public final boolean isStopped() { 28 | return stopped; 29 | } 30 | 31 | protected abstract void killingSpree(); 32 | protected abstract void enoughDeaths(); 33 | 34 | public static class UntimedParker extends ObsessedTimeKiller { 35 | 36 | private volatile Thread parkedThread; 37 | 38 | @Override 39 | protected void killingSpree() { 40 | this.parkedThread = Thread.currentThread(); 41 | LockSupport.park(); 42 | } 43 | 44 | @Override 45 | protected void enoughDeaths() { 46 | LockSupport.unpark(parkedThread); 47 | } 48 | } 49 | 50 | public static class UntimedWaiter extends ObsessedTimeKiller { 51 | 52 | private final Object object = new Object(); 53 | 54 | 55 | @Override 56 | protected void killingSpree() { 57 | if(!isStopped()) { 58 | try { 59 | synchronized (object) { 60 | object.wait(); 61 | } 62 | } catch (InterruptedException ignored) {} 63 | } 64 | } 65 | 66 | @Override 67 | protected void enoughDeaths() { 68 | if(!isStopped()) { 69 | synchronized (object) { 70 | object.notifyAll(); 71 | } 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/time/TimeKillerBenchmark.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.time; 2 | 3 | import org.openjdk.jmh.annotations.Benchmark; 4 | import org.openjdk.jmh.annotations.OutputTimeUnit; 5 | import org.openjdk.jmh.annotations.Scope; 6 | import org.openjdk.jmh.annotations.State; 7 | 8 | import java.util.concurrent.TimeUnit; 9 | 10 | /** 11 | * Used to determine how much overhead invocation of various time-killing methods give 12 | * Helps differentiate between overhead and resolution 13 | */ 14 | 15 | @State(Scope.Benchmark) 16 | public class TimeKillerBenchmark { 17 | 18 | private final TimeKiller.Sleeper sleeper = new TimeKiller.Sleeper(); 19 | private final TimeKiller.TimedParker timedParker = new TimeKiller.TimedParker(); 20 | private final TimeKiller.Burner burner = new TimeKiller.Burner(); 21 | //TODO: add black hole 22 | 23 | @Benchmark 24 | public void sleepOneMs() { 25 | sleeper.kill(TimeUnit.MILLISECONDS.toNanos(1)); 26 | } 27 | 28 | @Benchmark 29 | public void sleepTwoMs() { 30 | sleeper.kill(TimeUnit.MILLISECONDS.toNanos(2)); 31 | } 32 | 33 | @Benchmark 34 | public void sleepOneNs() { 35 | sleeper.kill(TimeUnit.NANOSECONDS.toNanos(1)); 36 | } 37 | 38 | @Benchmark 39 | public void parkOneMs() { 40 | timedParker.kill(TimeUnit.MILLISECONDS.toNanos(1)); 41 | } 42 | 43 | @Benchmark 44 | public void parkTwoMs() { 45 | timedParker.kill(TimeUnit.MILLISECONDS.toNanos(2)); 46 | } 47 | 48 | @Benchmark 49 | public void parkOneNs() { 50 | timedParker.kill(TimeUnit.NANOSECONDS.toNanos(1)); 51 | } 52 | 53 | @Benchmark 54 | public void burnOneMs() { 55 | burner.kill(TimeUnit.MILLISECONDS.toNanos(1)); 56 | } 57 | 58 | @Benchmark 59 | public void burnTwoMs() { 60 | burner.kill(TimeUnit.MILLISECONDS.toNanos(2)); 61 | } 62 | 63 | @Benchmark 64 | public void burnOneNs() { 65 | burner.kill(TimeUnit.NANOSECONDS.toNanos(1)); 66 | } 67 | 68 | @Benchmark 69 | @OutputTimeUnit(TimeUnit.NANOSECONDS) 70 | public void getNanoTime() { 71 | System.nanoTime(); // being a syscall, spared by DCE 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/gc/PrematurePromotion.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.gc; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | 6 | public class PrematurePromotion { 7 | 8 | // This example shows how objects lingering in the heap for too long 9 | // may result in many more Full GC pauses than there could be. 10 | 11 | // 1. Run with: -verbose:gc -Xmx24m -XX:NewSize=16m 12 | // -XX:MaxTenuringThreshold=1 -XX:-UseAdaptiveSizePolicy 13 | // 14 | // Observe that there are many Full GCs 15 | // 16 | // 2. Run with: -verbose:gc -Xmx64m -XX:NewSize=32m 17 | // -XX:MaxTenuringThreshold=1 -XX:-UseAdaptiveSizePolicy 18 | // 19 | // Observe that most of GCs are minor 20 | // 21 | // 3. Run with: -Dmax.chunks=1000 -verbose:gc -Xmx24m -XX:NewSize=16m 22 | // -XX:MaxTenuringThreshold=1 -XX:-UseAdaptiveSizePolicy 23 | // 24 | // Observe that most of GCs are minor 25 | 26 | private static final int MAX_CHUNKS = Integer.getInteger("max.chunks", 10_000); 27 | 28 | private static final Collection accumulatedChunks = new ArrayList<>(); 29 | 30 | private static void onNewChunk(byte[] bytes) { 31 | accumulatedChunks.add(bytes); 32 | 33 | if(accumulatedChunks.size() > MAX_CHUNKS) { 34 | processBatch(accumulatedChunks); 35 | accumulatedChunks.clear(); 36 | } 37 | } 38 | 39 | public static void main(String[] args) { 40 | while(true) { 41 | onNewChunk(produceChunk()); 42 | } 43 | } 44 | 45 | private static byte[] produceChunk() { 46 | byte[] bytes = new byte[1024]; 47 | 48 | for(int i = 0; i < bytes.length; i ++) { 49 | bytes[i] = (byte) (Math.random() * Byte.MAX_VALUE); 50 | } 51 | 52 | return bytes; 53 | } 54 | 55 | public static volatile byte sink; 56 | 57 | public static void processBatch(Collection bytes) { 58 | byte result = 0; 59 | 60 | for(byte[] chunk : bytes) { 61 | for(byte b : chunk) { 62 | result ^= b; 63 | } 64 | } 65 | 66 | sink = result; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /labs-8/gradle/jni.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'c' 2 | apply plugin: 'gradle-one-jar' 3 | 4 | model { 5 | components { 6 | main(NativeLibrarySpec) { 7 | binaries.all { 8 | 9 | def javaHome =org.gradle.internal.jvm.Jvm.current().javaHome 10 | 11 | cCompiler.args '-I', "$javaHome/include" 12 | 13 | if (targetPlatform.operatingSystem.linux) { 14 | cCompiler.args '-I', "$javaHome/include/linux" 15 | 16 | // Clever-ass compilers will try to protect us from 17 | // Getting ourselves rekt and overflowing buffers 18 | // But we want that to happen so that the JVM has 19 | // to step in. See ChecksumCalculator.c 20 | cCompiler.args '-fno-sanitize=address' 21 | } 22 | 23 | if(targetPlatform.operatingSystem.macOsX) { 24 | cCompiler.args '-I', "$javaHome/include/darwin" 25 | cCompiler.args '-D_FORTIFY_SOURCE=0' 26 | } 27 | 28 | if(targetPlatform.operatingSystem.windows) { 29 | cCompiler.args '-I', "$javaHome/include/win32" 30 | // TODO: prevent sanitization by compiler 31 | } 32 | 33 | if(project.hasProperty('CHECKSUM_MAX_FILE_NAME_LENGTH')) { 34 | cCompiler.define 'MAX_FILE_NAME_LENGTH', project['CHECKSUM_MAX_FILE_NAME_LENGTH'] 35 | } 36 | } 37 | } 38 | } 39 | } 40 | 41 | task jniHeaders { 42 | def headerFile = new File('src/main/headers/jni_exports.h') 43 | 44 | inputs.files sourceSets.main.output 45 | outputs.file headerFile 46 | doLast { 47 | headerFile.parentFile.mkdirs() 48 | exec { 49 | executable org.gradle.internal.jvm.Jvm.current().getExecutable('javah') 50 | args '-o', headerFile 51 | args '-classpath', sourceSets.main.output.classesDir 52 | // TODO: do for each file 53 | args 'ru.gvsmirnov.perv.labs.rekt.ChecksumCalculator' 54 | } 55 | } 56 | } 57 | 58 | task jarWithNatives(type: OneJar, dependsOn: [jniHeaders, 'mainSharedLibrary']) { 59 | mainClass = 'ru.gvsmirnov.perv.labs.rekt.ChecksumCalculator' 60 | archiveName = 'checksum.jar' 61 | //FIXME: de-hardcode 62 | binLib = files('build/binaries/mainSharedLibrary/libmain.so') 63 | } -------------------------------------------------------------------------------- /labs-8/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/gc/WeakReferences.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.gc; 2 | 3 | import java.lang.ref.WeakReference; 4 | import java.util.Arrays; 5 | 6 | public class WeakReferences { 7 | 8 | // This example shows how having weak references pointing to objects 9 | // May result in more frequent Full GC pauses 10 | // 11 | // There are two modes (controlled by weak.refs) 12 | // 13 | // 1. A lot of objects are created 14 | // 2. A lot of objects are created, and weak references are created 15 | // for them. These references are held in a buffer until it's full 16 | // 17 | // The allocations made in both cases need to be exactly the same, 18 | // so in (1) weak references will be also created, but all of them 19 | // will be pointing to the same object 20 | 21 | 22 | // 1. Run with: -verbose:gc -Xmx24m -XX:NewSize=16m 23 | // -XX:MaxTenuringThreshold=1 -XX:-UseAdaptiveSizePolicy 24 | // 25 | // Observe that there are mostly young GCs 26 | // 27 | // 2. Run with: -Dweak.refs=true -verbose:gc -Xmx24m -XX:NewSize=16m 28 | // -XX:MaxTenuringThreshold=1 -XX:-UseAdaptiveSizePolicy 29 | // 30 | // Observe that there are mostly full GCs 31 | // 32 | // 3. Run with: -Dweak.refs=true -verbose:gc -Xmx64m -XX:NewSize=32m 33 | // -XX:MaxTenuringThreshold=1 -XX:-UseAdaptiveSizePolicy 34 | // 35 | // Observe that there are mostly young GCs 36 | 37 | private static final int OBJECT_SIZE = Integer.getInteger("obj.size", 192); 38 | private static final int BUFFER_SIZE = Integer.getInteger("buf.size", 64 * 1024); 39 | private static final boolean WEAK_REFS_FOR_ALL = Boolean.getBoolean("weak.refs"); 40 | 41 | private static Object makeObject() { 42 | return new byte[OBJECT_SIZE]; 43 | } 44 | 45 | public static volatile Object sink; 46 | 47 | public static void main(String[] args) throws InterruptedException { 48 | 49 | System.out.printf("Buffer size: %d; Object size: %d; Weak refs for all: %s%n", BUFFER_SIZE, OBJECT_SIZE, WEAK_REFS_FOR_ALL); 50 | 51 | final Object substitute = makeObject(); // We want to create it in both scenarios so the footprint matches 52 | final Object[] refs = new Object[BUFFER_SIZE]; 53 | 54 | System.gc(); // Clean up young gen 55 | 56 | for (int index = 0;;) { 57 | Object object = makeObject(); 58 | sink = object; // Prevent Escape Analysis from optimizing the allocation away 59 | 60 | if (!WEAK_REFS_FOR_ALL) { 61 | object = substitute; 62 | } 63 | 64 | refs[index++] = new WeakReference<>(object); 65 | 66 | if (index == BUFFER_SIZE) { 67 | Arrays.fill(refs, null); 68 | index = 0; 69 | } 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/gc/SoftReferences.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.gc; 2 | 3 | import java.lang.ref.SoftReference; 4 | import java.util.Arrays; 5 | 6 | public class SoftReferences { 7 | 8 | // This example shows how having soft references pointing to objects 9 | // May result in more frequent Full GC pauses 10 | // 11 | // There are two modes (controlled by soft.refs) 12 | // 13 | // 1. A lot of objects are created 14 | // 2. A lot of objects are created, and soft references are created 15 | // for them. These references are held in a buffer until it's full 16 | // 17 | // The allocations made in both cases need to be exactly the same, 18 | // so in (1) soft references will be also created, but all of them 19 | // will be pointing to the same object 20 | 21 | 22 | // 1. Run with: -verbose:gc -Xmx24m -XX:NewSize=16m 23 | // -XX:MaxTenuringThreshold=1 -XX:-UseAdaptiveSizePolicy 24 | // 25 | // Observe that there are mostly young GCs 26 | // 27 | // 2. Run with: -Dsoft.refs=true -verbose:gc -Xmx24m -XX:NewSize=16m 28 | // -XX:MaxTenuringThreshold=1 -XX:-UseAdaptiveSizePolicy 29 | // 30 | // Observe that there are lots of full GCs 31 | // 32 | // 3. Run with: -Dsoft.refs=true -verbose:gc -Xmx64m -XX:NewSize=32m 33 | // -XX:MaxTenuringThreshold=1 -XX:-UseAdaptiveSizePolicy 34 | // 35 | // Observe that there are still many full GCs 36 | 37 | private static final int OBJECT_SIZE = Integer.getInteger("obj.size", 192); 38 | private static final int BUFFER_SIZE = Integer.getInteger("buf.size", 64 * 1024); 39 | private static final boolean SOFT_REFS_FOR_ALL = Boolean.getBoolean("soft.refs"); 40 | 41 | private static Object makeObject() { 42 | return new byte[OBJECT_SIZE]; 43 | } 44 | 45 | public static volatile Object sink; 46 | 47 | public static void main(String[] args) throws InterruptedException { 48 | 49 | System.out.printf("Buffer size: %d; Object size: %d; Soft refs for all: %s%n", BUFFER_SIZE, OBJECT_SIZE, SOFT_REFS_FOR_ALL); 50 | 51 | final Object substitute = makeObject(); // We want to create it in both scenarios so the footprint matches 52 | final Object[] refs = new Object[BUFFER_SIZE]; 53 | 54 | System.gc(); // Clean up young gen 55 | 56 | for (int index = 0;;) { 57 | Object object = makeObject(); 58 | sink = object; // Prevent Escape Analysis from optimizing the allocation away 59 | 60 | if (!SOFT_REFS_FOR_ALL) { 61 | object = substitute; 62 | } 63 | 64 | refs[index++] = new SoftReference<>(object); 65 | 66 | if (index == BUFFER_SIZE) { 67 | Arrays.fill(refs, null); 68 | index = 0; 69 | } 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/jit/ComparingBenchmark.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.jit; 2 | 3 | import org.openjdk.jmh.annotations.Param; 4 | import org.openjdk.jmh.annotations.Scope; 5 | import org.openjdk.jmh.annotations.State; 6 | import org.openjdk.jmh.results.RunResult; 7 | import org.openjdk.jmh.results.format.ResultFormat; 8 | import org.openjdk.jmh.results.format.ResultFormatFactory; 9 | import org.openjdk.jmh.results.format.ResultFormatType; 10 | import org.openjdk.jmh.runner.Runner; 11 | import org.openjdk.jmh.runner.RunnerException; 12 | import org.openjdk.jmh.runner.options.ChainedOptionsBuilder; 13 | import org.openjdk.jmh.runner.options.OptionsBuilder; 14 | 15 | import java.util.ArrayList; 16 | import java.util.Arrays; 17 | import java.util.Collection; 18 | 19 | @State(Scope.Benchmark) 20 | public class ComparingBenchmark { 21 | 22 | // Too lazy to write my own formatter 23 | // Will use that of JMH 24 | // Also will pervert for that 25 | // Don't you judge me! 26 | 27 | @Param("default") 28 | public String mode; 29 | 30 | public static void runBenchmarks_JIT_vs_Interpreter(Class clazz) throws RunnerException { 31 | runBenchmarks(clazz, new Mode("jit"), new Mode("int", "-Xint")); 32 | } 33 | 34 | public static void runBenchmarks_NoEscape(Class clazz) throws RunnerException { 35 | runBenchmarks(clazz, new Mode("default"), new Mode("no-opt", "-XX:-DoEscapeAnalysis")); 36 | } 37 | 38 | public static void runBenchmarks(Class clazz, Mode... modes) throws RunnerException { 39 | Collection allResults = new ArrayList<>(); 40 | 41 | for(Mode mode : modes) { 42 | ChainedOptionsBuilder builder = makeOptionBuilder(clazz).param("mode", mode.name); 43 | 44 | for(String arg : mode.jvmArgs) { 45 | builder.jvmArgsAppend(arg); 46 | } 47 | 48 | allResults.addAll(new Runner(builder.build()).run()); 49 | } 50 | 51 | printResult(allResults); 52 | } 53 | 54 | private static ChainedOptionsBuilder makeOptionBuilder(Class clazz) { 55 | return new OptionsBuilder() 56 | .include(clazz.getSimpleName()) 57 | .warmupIterations(5) 58 | .measurementIterations(5) 59 | .forks(1); 60 | } 61 | 62 | private static void printResult(Collection runResults) { 63 | System.out.println(); 64 | 65 | ResultFormat resultFormat = ResultFormatFactory.getInstance(ResultFormatType.TEXT, System.out); 66 | resultFormat.writeOut(runResults); 67 | } 68 | 69 | public static class Mode { 70 | private final String name; 71 | private final Collection jvmArgs; 72 | 73 | public Mode(String name, Collection jvmArgs) { 74 | this.name = name; 75 | this.jvmArgs = jvmArgs; 76 | } 77 | 78 | public Mode(String name, String... jvmArgs) { 79 | this(name, Arrays.asList(jvmArgs)); 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/time/TimeKiller.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.time; 2 | 3 | import org.openjdk.jmh.infra.Blackhole; 4 | 5 | import java.util.concurrent.TimeUnit; 6 | import java.util.concurrent.locks.LockSupport; 7 | 8 | public abstract class TimeKiller { 9 | 10 | public final void kill(long nanos) { 11 | 12 | long endTime = System.nanoTime() + nanos; 13 | long remainingTime; 14 | 15 | while((remainingTime = endTime - System.nanoTime()) > 0) { 16 | tryKill(remainingTime); 17 | } 18 | } 19 | 20 | protected abstract void tryKill(long nanosToKill); 21 | 22 | public static class TimedParker extends TimeKiller { 23 | @Override 24 | public void tryKill(long nanosToKill) { 25 | LockSupport.parkNanos(nanosToKill); 26 | } 27 | } 28 | 29 | public static class Sleeper extends TimeKiller { 30 | @Override 31 | public void tryKill(long nanosToKill) { 32 | try { 33 | TimeUnit.NANOSECONDS.sleep(nanosToKill); 34 | } catch (InterruptedException ignored) {} 35 | } 36 | } 37 | 38 | public static class Burner extends TimeKiller { 39 | @Override 40 | public void tryKill(long l) {} 41 | } 42 | 43 | public static class Yielder extends TimeKiller { 44 | @Override 45 | public void tryKill(long l) { 46 | Thread.yield(); 47 | } 48 | } 49 | 50 | public static class TimedWaiter extends TimeKiller { 51 | 52 | private final Object object = new Object(); 53 | 54 | @Override 55 | public void tryKill(long nanosToKill) { 56 | long millis = TimeUnit.NANOSECONDS.toMillis(nanosToKill); 57 | long nanos = nanosToKill % TimeUnit.MILLISECONDS.toNanos(1); 58 | 59 | try { 60 | synchronized (object) { 61 | object.wait(millis, (int) nanos); 62 | } 63 | } catch (InterruptedException ignored) {} 64 | } 65 | } 66 | 67 | // TODO: BlackHole in fact does not perform linearly all the time 68 | // Nor, for that matter, is the time spent consuming the same number 69 | // of tokens the same between runs 70 | public static class BlackHole extends TimeKiller { 71 | 72 | private final double tokensPerNano; 73 | 74 | public BlackHole(double tokensPerNano) { 75 | this.tokensPerNano = tokensPerNano; 76 | } 77 | 78 | @Override 79 | public void tryKill(long nanosToKill) { 80 | Blackhole.consumeCPU((long) (tokensPerNano * nanosToKill)); 81 | } 82 | 83 | 84 | private static final long A_LOT_OF_TOKENS = TimeUnit.SECONDS.toNanos(20); 85 | 86 | public static double estimateTokensPerNano() { 87 | long start = System.nanoTime(); 88 | Blackhole.consumeCPU(A_LOT_OF_TOKENS); 89 | long end = System.nanoTime(); 90 | 91 | return ((double) A_LOT_OF_TOKENS) / (end - start) ; 92 | } 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/gc/PhantomReferences.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.gc; 2 | 3 | import java.lang.ref.PhantomReference; 4 | import java.lang.ref.Reference; 5 | import java.lang.ref.ReferenceQueue; 6 | import java.util.Arrays; 7 | 8 | public class PhantomReferences { 9 | 10 | // This example shows how having phantom references pointing to objects 11 | // May result in an OutOfMemoryError 12 | // 13 | // There are three modes (controlled by phantom.refs and no.ref.clearing) 14 | // 15 | // 1. A lot of objects are created 16 | // 2. A lot of objects are created, and phantom references are created 17 | // for them. These references are held in a buffer until it's full. 18 | // The reference queue is traversed and cleared on each iteration 19 | // 3. A lot of objects are created, and phantom references are created 20 | // for them. These references are held in a buffer until it's full. 21 | // The reference queue is not cleared. 22 | // 23 | // The allocations made in both cases need to be exactly the same, 24 | // so in (1) phantom references will be also created, but all of them 25 | // will be pointing to the same object 26 | 27 | 28 | // 1. Run with: -verbose:gc -Xmx24m -XX:NewSize=16m 29 | // -XX:MaxTenuringThreshold=1 -XX:-UseAdaptiveSizePolicy 30 | // 31 | // Observe that there are mostly young GCs 32 | // 33 | // 2. Run with: -Dphantom.refs=true -verbose:gc -Xmx24m -XX:NewSize=16m 34 | // -XX:MaxTenuringThreshold=1 -XX:-UseAdaptiveSizePolicy 35 | // 36 | // Observe that there are lots of full GCs 37 | // 38 | // 3. Run with: -Dphantom.refs=true -verbose:gc -Xmx64m -XX:NewSize=32m 39 | // -XX:MaxTenuringThreshold=1 -XX:-UseAdaptiveSizePolicy 40 | // 41 | // Observe that there are few full GCs 42 | // 43 | // 4. Run with: -Dphantom.refs=true -Dno.ref.clearing=true -verbose:gc -Xmx64m -XX:NewSize=32m 44 | // -XX:MaxTenuringThreshold=1 -XX:-UseAdaptiveSizePolicy 45 | // 46 | // Observe lots of Full GCs and an OutOfMemoryError 47 | 48 | private static final int OBJECT_SIZE = Integer.getInteger("obj.size", 192); 49 | private static final int BUFFER_SIZE = Integer.getInteger("buf.size", 24 * 1024); 50 | private static final boolean PHANTOM_REFS_FOR_ALL = Boolean.getBoolean("phantom.refs"); 51 | private static final boolean CLEAR_REFS = !Boolean.getBoolean("no.ref.clearing"); 52 | 53 | private static Object makeObject() { 54 | return new byte[OBJECT_SIZE]; 55 | } 56 | 57 | public static volatile Object sink; 58 | private static final ReferenceQueue queue = new ReferenceQueue<>(); 59 | 60 | public static void main(String[] args) throws InterruptedException { 61 | 62 | System.out.printf("Buffer size: %d; Object size: %d; Phantom refs for all: %s; Clearing refs: %s%n", BUFFER_SIZE, OBJECT_SIZE, PHANTOM_REFS_FOR_ALL, CLEAR_REFS); 63 | 64 | final Object substitute = makeObject(); // We want to create it in both scenarios so the footprint matches 65 | final Object[] refs = new Object[BUFFER_SIZE]; 66 | 67 | System.gc(); // Clean up young gen 68 | 69 | for (int index = 0;;) { 70 | Object object = makeObject(); 71 | sink = object; // Prevent Escape Analysis from optimizing the allocation away 72 | 73 | if (!PHANTOM_REFS_FOR_ALL) { 74 | object = substitute; 75 | } 76 | 77 | refs[index++] = new PhantomReference<>(object, queue); 78 | 79 | if (index == BUFFER_SIZE) { 80 | Arrays.fill(refs, null); 81 | index = 0; 82 | } 83 | 84 | if(CLEAR_REFS) { 85 | Reference ref; 86 | while((ref = queue.poll()) != null) { 87 | ref.clear(); 88 | } 89 | } 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /labs-8/results/time.md: -------------------------------------------------------------------------------- 1 | Warning: Work In Progress 2 | ------------------------- 3 | These are preliminary results, which are very likely to to contain errors and to be incomplete. 4 | Use them with extreme caution. Better yet, measure things on your own. Don't forget to make sure that 5 | you are really measuring what you think you're measuring. 6 | 7 | 8 | Linux Laptop 9 | ------------ 10 | 3816 MB RAM; Intel(R) Core(TM) i3 CPU M 380 @ 2.53GHz 11 | 12 | ``` 13 | Benchmark Mode Thr Count Sec Mean Mean error Units 14 | r.g.p.l.t.TimeKillerBenchmark.burnOneMs sample 1 20003 1 999.652 0.217 us/op 15 | r.g.p.l.t.TimeKillerBenchmark.burnOneNs sample 1 24245077 1 0.110 0.001 us/op 16 | r.g.p.l.t.TimeKillerBenchmark.burnTwoMs sample 1 10005 1 1998.941 0.046 us/op 17 | r.g.p.l.t.TimeKillerBenchmark.getNanoTime sample 1 21232069 1 0.074 0.000 us/op 18 | r.g.p.l.t.TimeKillerBenchmark.parkOneMs sample 1 18210 1 1097.345 3.912 us/op 19 | r.g.p.l.t.TimeKillerBenchmark.parkOneNs sample 1 322256 1 61.871 0.135 us/op 20 | r.g.p.l.t.TimeKillerBenchmark.parkTwoMs sample 1 9491 1 2105.804 5.576 us/op 21 | r.g.p.l.t.TimeKillerBenchmark.sleepOneMs sample 1 18135 1 1101.718 1.175 us/op 22 | r.g.p.l.t.TimeKillerBenchmark.sleepOneNs sample 1 18178 1 1099.514 4.258 us/op 23 | r.g.p.l.t.TimeKillerBenchmark.sleepTwoMs sample 1 9497 1 2104.680 6.870 us/op 24 | ``` 25 | 26 | ``` 27 | Estimating the precision of Thread.sleep()... 28 | Precision of Thread.sleep() is 1.126 ms 29 | Estimating the precision of LockSupport.parkNanos()... 30 | Precision of LockSupport.parkNanos() is 210.480 us 31 | Estimating the precision of System.nanoTime()... 32 | Precision of System.nanoTime() is 316.0 ns 33 | Estimating the precision of Thread.yield()... 34 | Precision of Thread.yield() is 436.0 ns 35 | Estimating the precision of Object.wait(millis, nanos)... 36 | Precision of Object.wait(millis, nanos) is 1.128 ms 37 | Estimating the precision of LockSupport.park()... 38 | Precision of LockSupport.park() is 15.625 ms 39 | Estimating the precision of Object.wait()... 40 | Precision of Object.wait() is 250.0 ms 41 | ``` 42 | 43 | Macbook Pro 44 | ----------- 45 | 16 GB RAM; 2.6 GHz Intel Core i7 (Haswell, model unknown) 46 | 47 | ``` 48 | Estimating the precision of Thread.sleep()... 49 | Precision of Thread.sleep() is 1.282 ms 50 | Estimating the precision of LockSupport.parkNanos()... 51 | Precision of LockSupport.parkNanos() is 31.70 us 52 | Estimating the precision of System.nanoTime()... 53 | Precision of System.nanoTime() is 2.222 us 54 | Estimating the precision of Thread.yield()... 55 | Precision of Thread.yield() is 1.30 us 56 | Estimating the precision of Object.wait(millis, nanos)... 57 | Precision of Object.wait(millis, nanos) is 1.275 ms 58 | Estimating the precision of LockSupport.park()... 59 | Precision of LockSupport.park() is 62.500 ms 60 | Estimating the precision of Object.wait()... 61 | Precision of Object.wait() is 30.714 us 62 | ``` 63 | 64 | Windows Box 65 | ----------- 66 | 4079 MB RAM; Intel(R) Xeon(R) CPU E5606 @ 2.13GHz 67 | 68 | ``` 69 | Benchmark Mode Thr Count Sec Mean Mean error Units 70 | r.g.p.l.t.TimeKillerBenchmark.burnOneMs sample 1 19989 1 999.672 0.108 us/op 71 | r.g.p.l.t.TimeKillerBenchmark.burnOneNs sample 1 20232856 1 0.495 0.001 us/op 72 | r.g.p.l.t.TimeKillerBenchmark.burnTwoMs sample 1 10000 1 1999.037 0.114 us/op 73 | r.g.p.l.t.TimeKillerBenchmark.getNanoTime sample 1 23745894 1 0.066 0.000 us/op 74 | r.g.p.l.t.TimeKillerBenchmark.parkOneMs sample 1 20004 1 998.957 1.811 us/op 75 | r.g.p.l.t.TimeKillerBenchmark.parkOneNs sample 1 20012 1 998.565 1.389 us/op 76 | r.g.p.l.t.TimeKillerBenchmark.parkTwoMs sample 1 10013 1 1998.168 2.678 us/op 77 | r.g.p.l.t.TimeKillerBenchmark.sleepOneMs sample 1 20011 1 998.441 1.462 us/op 78 | r.g.p.l.t.TimeKillerBenchmark.sleepOneNs sample 1 20009 1 998.668 1.640 us/op 79 | r.g.p.l.t.TimeKillerBenchmark.sleepTwoMs sample 1 10014 1 1997.672 1.998 us/op 80 | ``` 81 | 82 | ``` 83 | Estimating the precision of Thread.sleep()... 84 | Precision of Thread.sleep() is 1.38 ms 85 | Estimating the precision of LockSupport.parkNanos()... 86 | Precision of LockSupport.parkNanos() is 1.104 ms 87 | Estimating the precision of System.nanoTime()... 88 | Precision of System.nanoTime() is 554.0 ns 89 | ``` 90 | -------------------------------------------------------------------------------- /labs-8/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/time/PrecisionTest.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.time; 2 | 3 | import org.kohsuke.args4j.CmdLineException; 4 | import org.kohsuke.args4j.CmdLineParser; 5 | import org.kohsuke.args4j.Option; 6 | 7 | import java.util.Arrays; 8 | import java.util.Collection; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | import static ru.gvsmirnov.perv.labs.util.Util.*; 12 | 13 | public class PrecisionTest { 14 | 15 | private static final long DEFAULT_MAX_RESOLUTION = TimeUnit.SECONDS.toNanos(2); 16 | private static final long DEFAULT_MIN_RESOLUTION = TimeUnit.NANOSECONDS.toNanos(100); 17 | 18 | @Option(name = "--max", usage = "Max resolution to be tested") 19 | private long maxResolution = DEFAULT_MAX_RESOLUTION; 20 | 21 | @Option(name = "--min", usage = "Min resolution to be tested") 22 | private long minResolution = DEFAULT_MIN_RESOLUTION; 23 | 24 | @Option(name = "-v", usage = "Verbose") 25 | private boolean verbose = false; 26 | 27 | @Option(name = "-q", usage = "Quiet") 28 | private boolean quiet = false; 29 | 30 | private long estimatePrecision(TimeKiller killer, String name) { 31 | if(!quiet) out("Estimating the precision of %s...", name); 32 | 33 | long left = minResolution, right = maxResolution; 34 | long resultingResolution = -1; 35 | 36 | 37 | while(right - left >= minResolution) { 38 | if(verbose) { 39 | out("Current interval: (%d, %d)", left, right); 40 | } 41 | 42 | long currentResolution = (left + right) / 2; 43 | boolean precise = isPrecise(killer, currentResolution, (int) (maxResolution / currentResolution)); 44 | 45 | if(precise) { 46 | right = currentResolution - 1; 47 | 48 | resultingResolution = currentResolution * 2; // TODO: explain clearly why the multiplication 49 | } else { 50 | left = currentResolution + 1; 51 | } 52 | 53 | } 54 | 55 | if(!quiet) out("Precision of %s is %s", name, annotate(resultingResolution)); 56 | 57 | return resultingResolution; 58 | } 59 | 60 | private boolean isPrecise(TimeKiller timeKiller, long resolution, int iterations) { 61 | 62 | if(verbose) { 63 | out("Testing resolution: %s in %d iterations", annotate(resolution), iterations); 64 | } 65 | 66 | final long timeToKill = resolution * iterations; 67 | 68 | long elapsedNanoTime = 0; 69 | 70 | for(long killed = 0; killed < timeToKill; killed += resolution) { 71 | 72 | long startNanos = System.nanoTime(); 73 | timeKiller.kill(resolution); 74 | long endNanos = System.nanoTime(); 75 | 76 | elapsedNanoTime += (endNanos - startNanos); 77 | 78 | long shouldHaveElapsed = killed + resolution; 79 | 80 | long totalError = Math.abs(elapsedNanoTime - shouldHaveElapsed); 81 | long errorPerInvocation = (totalError) / (shouldHaveElapsed / resolution); 82 | 83 | if(errorPerInvocation > resolution) { 84 | if(verbose) { 85 | out("Cumulative error has exceeded allowed threshold, aborting." + 86 | "\n\tElapsed time: %s" + 87 | "\n\tExpected time: %s" + 88 | "\n\tError per invocation: %s\n", 89 | annotate(elapsedNanoTime), annotate(shouldHaveElapsed), annotate(errorPerInvocation) 90 | ); 91 | } 92 | return false; 93 | } 94 | } 95 | 96 | return true; 97 | } 98 | 99 | private static final int WARMUP_ITERATIONS = 10_000 * 2; //just to be sure 100 | 101 | private static void warmup() { 102 | out("Warming up"); 103 | 104 | 105 | PrecisionTest precisionTest = new PrecisionTest(); 106 | precisionTest.quiet = true; 107 | precisionTest.verbose = false; 108 | precisionTest.minResolution = 1; 109 | precisionTest.maxResolution = 3; 110 | 111 | 112 | Collection timeKillers = Arrays.asList( 113 | new TimeKiller.Sleeper(), 114 | new TimeKiller.TimedParker(), 115 | new TimeKiller.Burner(), 116 | new TimeKiller.Yielder(), 117 | new TimeKiller.TimedWaiter(), 118 | 119 | new ObsessedTimeKiller.UntimedParker(), 120 | new ObsessedTimeKiller.UntimedWaiter() 121 | //new TimeKiller.BlackHole(1) 122 | ); 123 | 124 | 125 | final int reportEvery = WARMUP_ITERATIONS / 400; 126 | final int newLineEvery = reportEvery * 80; 127 | 128 | 129 | for(int iteration = 1; iteration <= WARMUP_ITERATIONS; iteration++) { 130 | timeKillers.forEach((timeKiller) -> { 131 | precisionTest.estimatePrecision(timeKiller, timeKiller.getClass().getSimpleName()); 132 | }); 133 | 134 | if(iteration % reportEvery == 0) 135 | outClear("."); 136 | 137 | if(iteration % newLineEvery == 0) 138 | out(); 139 | } 140 | 141 | } 142 | 143 | public static void main(String[] args) throws CmdLineException { 144 | 145 | warmup(); 146 | 147 | PrecisionTest precisionTest = new PrecisionTest(); 148 | new CmdLineParser(precisionTest).parseArgument(args); 149 | 150 | precisionTest.estimatePrecision(new TimeKiller.Sleeper(), "Thread.sleep()"); 151 | precisionTest.estimatePrecision(new TimeKiller.TimedParker(), "LockSupport.parkNanos()"); 152 | 153 | // In effect, we measure how accurate System.nanoTime() is 154 | precisionTest.estimatePrecision(new TimeKiller.Burner(), "System.nanoTime()"); 155 | 156 | precisionTest.estimatePrecision(new TimeKiller.Yielder(), "Thread.yield()"); 157 | precisionTest.estimatePrecision(new TimeKiller.TimedWaiter(), "Object.wait(millis, nanos)"); 158 | 159 | precisionTest.estimatePrecision(new ObsessedTimeKiller.UntimedParker(), "LockSupport.park()"); 160 | 161 | precisionTest.estimatePrecision(new ObsessedTimeKiller.UntimedWaiter(), "Object.wait()"); 162 | 163 | 164 | /* 165 | out("Estimating the number of BlackHole tokens per nano..."); 166 | double tokensPerNano = TimeKiller.BlackHole.estimateTokensPerNano(); 167 | out("BlackHole tokens per nano: %.4f", tokensPerNano); 168 | 169 | precisionTest.estimatePrecision(new TimeKiller.BlackHole(tokensPerNano), "BlackHole.consumeCPU()"); 170 | */ 171 | 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | 117 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/gc/G1Demo.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.gc; 2 | 3 | import com.sun.management.GarbageCollectionNotificationInfo; 4 | import com.sun.management.GcInfo; 5 | 6 | import javax.management.NotificationEmitter; 7 | import javax.management.openmbean.CompositeData; 8 | import java.lang.management.ManagementFactory; 9 | import java.util.Objects; 10 | import java.util.concurrent.ThreadLocalRandom; 11 | import java.util.concurrent.locks.LockSupport; 12 | import java.util.stream.Stream; 13 | 14 | import static com.sun.management.GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION; 15 | import static com.sun.management.GarbageCollectionNotificationInfo.from; 16 | 17 | public class G1Demo { 18 | 19 | private static final int PAYLOAD_SIZE = Integer.getInteger("payload.size", 1024); 20 | private static final int INITIAL_OBJECT_COUNT = Integer.getInteger("initial.obj.count", 1_000_000); 21 | private static final int MAX_BRANCH_COUNT = Integer.getInteger("max.branch.count", 3); 22 | 23 | private static final long ITERATIONS = Long.getLong("iterations", 1_000_000); 24 | 25 | private static final double DISCARDING_RATE = Double.parseDouble(System.getProperty("discarding.rate", "0.1")); 26 | private static final double DISCARDING_RATIO = Double.parseDouble(System.getProperty("discarding.ratio", "0.5")); 27 | 28 | private static final int REVIVED_OBJECT_COUNT = Integer.getInteger("revived.obj.count", 10_000); 29 | 30 | 31 | public static volatile Object sink; 32 | 33 | public static void main(String[] args) { 34 | 35 | subscribeForGc(); 36 | 37 | Data root = makeData(INITIAL_OBJECT_COUNT); 38 | 39 | 40 | for(long l = 0; l < ITERATIONS; l ++) { 41 | root = maybeDiscardSome(root); 42 | 43 | // Force some garbage unto the heap 44 | sink = new Object(); 45 | LockSupport.parkNanos(1_000_000); 46 | } 47 | } 48 | 49 | private static Data maybeDiscardSome(Data root) { 50 | boolean shouldDiscard = ThreadLocalRandom.current().nextDouble() < DISCARDING_RATE; 51 | 52 | if(shouldDiscard) { 53 | long toDiscard = (long) (root.subTreeSize * DISCARDING_RATIO); 54 | 55 | return discard(root, toDiscard); 56 | } else { 57 | return append(root, REVIVED_OBJECT_COUNT); 58 | } 59 | } 60 | 61 | private static Data discard(Data root, long howManyToDiscard) { 62 | while(root.subTreeSize > 0 && howManyToDiscard > 0) { 63 | // System.err.print("Will prune " + howManyToDiscard + " leaves from a tree of " + root.subTreeSize); 64 | howManyToDiscard -= pruneLeaves(root, howManyToDiscard); 65 | // System.err.println("-> " + root.subTreeSize); 66 | } 67 | 68 | return root; 69 | } 70 | 71 | private static Data append(Data root, long howManyToAppend) { 72 | while(howManyToAppend > 0) { 73 | // System.err.print("Will append " + howManyToAppend + " leaves to a tree of " + root.subTreeSize); 74 | howManyToAppend -= appendLeaves(root, howManyToAppend); 75 | // System.err.println("-> " + root.subTreeSize); 76 | } 77 | 78 | return root; 79 | } 80 | 81 | private static long pruneLeaves(Data root, long maxLeavesToPrune) { 82 | if(root.subTreeSize == 0) { 83 | throw new AssertionError(); 84 | } 85 | 86 | long pruned = 0; 87 | 88 | for(int i = 0; i < root.children.length && pruned < maxLeavesToPrune; i ++) { 89 | Data child = root.children[i]; 90 | 91 | if(child == null) { 92 | continue; 93 | } 94 | 95 | if(child.subTreeSize == 0) { 96 | root.children[i] = null; 97 | pruned++; 98 | } else { 99 | pruned += pruneLeaves(child, maxLeavesToPrune - pruned); 100 | } 101 | } 102 | 103 | root.calculateSubTreeSize(); 104 | 105 | return pruned; 106 | } 107 | 108 | private static long appendLeaves(Data root, long maxLeavesToAppend) { 109 | long appended = 0; 110 | 111 | for(int i = 0; i < root.children.length && appended < maxLeavesToAppend; i ++) { 112 | Data child = root.children[i]; 113 | 114 | if(child == null) { 115 | int instanceCount = (int) Math.min(maxLeavesToAppend - 1, MAX_BRANCH_COUNT); 116 | root.children[i] = makeData(instanceCount); 117 | appended += instanceCount + 1; 118 | } else { 119 | appended += appendLeaves(child, maxLeavesToAppend - appended); 120 | } 121 | } 122 | 123 | root.calculateSubTreeSize(); 124 | 125 | return appended; 126 | } 127 | 128 | private static Data makeData(int instanceCount) { 129 | if(instanceCount <= 1) { 130 | return new Data(); 131 | } 132 | 133 | if(instanceCount <= MAX_BRANCH_COUNT) { 134 | Data[] children = new Data[MAX_BRANCH_COUNT]; 135 | for(int i = 0; i < instanceCount - 1; i ++) { 136 | children[i] = new Data(); 137 | } 138 | 139 | return new Data(children); 140 | } 141 | 142 | int childCount = (int) (Math.random() * MAX_BRANCH_COUNT) + 1; 143 | 144 | Data[] children = new Data[MAX_BRANCH_COUNT]; 145 | 146 | for(int i = 0; i < childCount; i ++) { 147 | if(i < childCount - 1) { 148 | int childSize = (int) (Math.random() * instanceCount); 149 | instanceCount -= childSize; 150 | children[i] = makeData(childSize); 151 | } else { 152 | // -1 for the governing parent Data that we return 153 | children[i] = makeData(instanceCount - 1); 154 | } 155 | } 156 | 157 | return new Data(children); 158 | } 159 | 160 | private static class Data { 161 | private final byte[] payload = new byte[PAYLOAD_SIZE]; 162 | private long subTreeSize; 163 | private Data[] children; 164 | 165 | public Data(Data... children) { 166 | this.children = children; 167 | calculateSubTreeSize(); 168 | } 169 | 170 | private void calculateSubTreeSize() { 171 | this.subTreeSize = calculateSize(children); 172 | } 173 | 174 | private static long calculateSize(Data... subTree) { 175 | return Stream.of(subTree).filter(Objects::nonNull).mapToLong(data -> data.subTreeSize + 1).sum(); 176 | } 177 | } 178 | 179 | private static void subscribeForGc() { 180 | ManagementFactory.getGarbageCollectorMXBeans().stream().filter(bean -> bean instanceof NotificationEmitter).forEach(bean -> { 181 | ((NotificationEmitter) bean).addNotificationListener((notification, handback) -> { 182 | // System.err.println("Notified"); 183 | if (GARBAGE_COLLECTION_NOTIFICATION.equals(notification.getType())) { 184 | GarbageCollectionNotificationInfo info = from((CompositeData) notification.getUserData()); 185 | com.sun.management.GarbageCollectorMXBean mxBean = (com.sun.management.GarbageCollectorMXBean) handback; 186 | 187 | // System.err.println("GC notification"); 188 | 189 | GcInfo gcInfo = info.getGcInfo(); 190 | if (gcInfo != null) { 191 | 192 | System.out.println(info.getGcName() + ", " + info.getGcAction() + ", " + mxBean.getName() + ", " + info.getGcCause() + ", " + gcInfo.getMemoryUsageBeforeGc() + ", " + gcInfo.getMemoryUsageAfterGc()); 193 | } 194 | } 195 | }, null, bean); 196 | }); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /labs-8/src/main/java/ru/gvsmirnov/perv/labs/gc/MetaspaceOom.java: -------------------------------------------------------------------------------- 1 | package ru.gvsmirnov.perv.labs.gc; 2 | 3 | import java.io.*; 4 | import java.nio.charset.Charset; 5 | import java.security.SecureClassLoader; 6 | import java.util.ArrayList; 7 | 8 | public class MetaspaceOom { 9 | 10 | public static volatile Object sink; 11 | 12 | public static void main(String[] args) throws InterruptedException { 13 | final int pid = getPid(); 14 | final long startMemory = getMemoryUsage(pid); // To load the required classes 15 | final long startTime = System.currentTimeMillis(); 16 | 17 | final BottomlessClassLoader loader = new BottomlessClassLoader(); 18 | 19 | try { 20 | while (true) { 21 | loader.loadAnotherClass(); 22 | } 23 | } catch (OutOfMemoryError e) { 24 | long elapsed = System.currentTimeMillis() - startTime; 25 | long memoryUsage = getMemoryUsage(pid); 26 | System.err.println( 27 | "Got an OOM: " + e.getMessage() + " after loading " + loader.classesLoaded + 28 | " classes in " + elapsed + " ms, memory: " + (memoryUsage / 1024 / 1024) + "M"); 29 | } catch (ClassNotFoundException e) { 30 | System.err.println("Class generation failed."); 31 | } 32 | } 33 | 34 | public static class BottomlessClassLoader extends SecureClassLoader { 35 | 36 | public static volatile Object sink; 37 | 38 | private byte[] classBytes; 39 | private final String className; 40 | private final byte[] classNameBytes; 41 | private final ArrayList replaceLocations; 42 | private final Class sourceClass = CombinatorialExplosion_XXXXXX.class; 43 | private final String baseName = sourceClass.getName().substring(0, sourceClass.getName().length() - 6); 44 | 45 | private final Charset UTF8_CHARSET = Charset.forName("UTF-8"); 46 | 47 | private int classesLoaded = 0; 48 | 49 | public BottomlessClassLoader() { 50 | super(BottomlessClassLoader.class.getClassLoader()); 51 | 52 | this.classBytes = null; 53 | this.className = sourceClass.getName().replace('.', '/'); 54 | this.classNameBytes = this.className.getBytes(UTF8_CHARSET); 55 | this.replaceLocations = new ArrayList<>(); 56 | 57 | if (loadClassBuffer(className)) { 58 | findNameLocations(); 59 | } 60 | } 61 | 62 | public void loadAnotherClass() throws ClassNotFoundException { 63 | String className = String.format("%s%06d", baseName, classesLoaded++); 64 | sink = loadClass(className); 65 | } 66 | 67 | protected byte[] streamToByteArray(InputStream input) throws IOException { 68 | ByteArrayOutputStream output = new ByteArrayOutputStream(); 69 | int n; 70 | byte[] buffer = new byte[4096]; 71 | while (-1 != (n = input.read(buffer))) { 72 | output.write(buffer, 0, n); 73 | } 74 | return output.toByteArray(); 75 | } 76 | 77 | protected boolean loadClassBuffer(String className) { 78 | InputStream stream = getResourceAsStream(className.replace('.', '/') + ".class"); 79 | try { 80 | classBytes = streamToByteArray(stream); 81 | return true; 82 | } catch (IOException e) { 83 | } finally { 84 | try { 85 | stream.close(); 86 | } catch (IOException e) { 87 | } 88 | } 89 | 90 | return false; 91 | } 92 | 93 | protected void findNameLocations() { 94 | int matchCount = 0; 95 | 96 | if (classNameBytes.length > 3) { 97 | for (int position = 0; position < classBytes.length; position++) { 98 | if (classNameBytes[matchCount] == classBytes[position]) { 99 | matchCount++; 100 | 101 | if (matchCount == classNameBytes.length) { 102 | replaceLocations.add(position - matchCount + 1); 103 | matchCount = 0; 104 | } 105 | } else { 106 | matchCount = 0; 107 | } 108 | } 109 | } 110 | } 111 | 112 | protected boolean renameClassBufferTo(final String requestedName) { 113 | if (classBytes == null || requestedName.length() != className.length()) { 114 | return false; 115 | } 116 | 117 | byte[] requestedNameBytes = requestedName.replace('.', '/').getBytes(UTF8_CHARSET); 118 | 119 | if (requestedNameBytes.length != classNameBytes.length) { 120 | return false; 121 | } 122 | 123 | for (int replaceStart : replaceLocations) { 124 | System.arraycopy(requestedNameBytes, 0, classBytes, replaceStart, classNameBytes.length); 125 | } 126 | 127 | return true; 128 | } 129 | 130 | @Override 131 | protected Class findClass(final String name) throws ClassNotFoundException { 132 | Class result; 133 | 134 | try { 135 | result = super.findClass(name); 136 | } catch (ClassNotFoundException e) { 137 | synchronized (this) { 138 | if (renameClassBufferTo(name)) { 139 | result = defineClass(name, classBytes, 0, classBytes.length); 140 | } else { 141 | throw e; 142 | } 143 | } 144 | } 145 | 146 | return result; 147 | } 148 | } 149 | 150 | // Kudos to @tagir_valeev for this approach: https://habrahabr.ru/post/245333/ 151 | public static class CombinatorialExplosion_XXXXXX {{ 152 | int a; 153 | try {a=0;} finally { 154 | try {a=0;} finally { 155 | try {a=0;} finally { 156 | try {a=0;} finally { 157 | try {a=0;} finally { 158 | try {a=0;} finally { 159 | try {a=0;} finally { 160 | try {a=0;} finally { 161 | try {a=0;} finally { 162 | try {a=0;} finally { 163 | a=0; 164 | }}}}}}}}}} 165 | }} 166 | 167 | public static int getPid() { 168 | try { 169 | java.lang.management.RuntimeMXBean runtime = 170 | java.lang.management.ManagementFactory.getRuntimeMXBean(); 171 | java.lang.reflect.Field jvm = runtime.getClass().getDeclaredField("jvm"); 172 | jvm.setAccessible(true); 173 | sun.management.VMManagement mgmt = 174 | (sun.management.VMManagement) jvm.get(runtime); 175 | java.lang.reflect.Method pid_method = 176 | mgmt.getClass().getDeclaredMethod("getProcessId"); 177 | pid_method.setAccessible(true); 178 | 179 | return (Integer) pid_method.invoke(mgmt); 180 | } catch(Exception e) { 181 | throw new AssertionError(e); 182 | } 183 | } 184 | 185 | // OSX-specific. Sorry, folks! 186 | public static long getMemoryUsage(int pid) { 187 | try { 188 | Process p = new ProcessBuilder() 189 | .command("ps", "-o", "rss=", "-p", Integer.toString(pid)) 190 | .start(); 191 | 192 | BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); 193 | String usage = reader.readLine().trim(); 194 | 195 | p.destroy(); 196 | 197 | // It's returned in kilobytes 198 | return Long.parseLong(usage) * 1024; 199 | } catch(Exception e) { 200 | throw new AssertionError(e); 201 | } 202 | } 203 | } 204 | 205 | --------------------------------------------------------------------------------