diff a/.github/actions/upload-bundles/action.yml b/.github/actions/upload-bundles/action.yml
--- a/.github/actions/upload-bundles/action.yml
+++ b/.github/actions/upload-bundles/action.yml
@@ -71,7 +71,6 @@
     - name: 'Upload bundles artifact'
       uses: actions/upload-artifact@v3
       with:
         name: bundles-${{ inputs.platform }}${{ inputs.debug-suffix }}
         path: bundles
-        retention-days: 1
       if: steps.bundles.outputs.bundles-found == 'true'
diff a/.github/workflows/pea.yml b/.github/workflows/pea.yml
--- /dev/null
+++ b/.github/workflows/pea.yml
@@ -0,0 +1,231 @@
+name:  "C2 PEA Test"
+on:
+  workflow_dispatch:
+    inputs:
+      configure-arguments:
+        description: 'Additional configure arguments'
+        required: false
+      make-arguments:
+        description: 'Additional make arguments'
+        required: false
+  push:
+    branches:
+      - PEA_beta
+  pull_request:
+    branches:
+      - PEA_beta
+
+jobs:
+  ###
+  ### Build jobs
+  ###
+  build-linux-x64:
+    name: linux-x64
+    uses: ./.github/workflows/build-linux.yml
+    with:
+      platform: linux-x64
+      debug-levels: '[ "debug" ]'
+      gcc-major-version: '10'
+      configure-arguments: ${{ github.event.inputs.configure-arguments }}
+      make-arguments: ${{ github.event.inputs.make-arguments }}
+
+  smoke-test:
+    needs: build-linux-x64
+    runs-on: ubuntu-22.04
+    steps:
+      - name: 'Checkout the JDK source'
+        uses: actions/checkout@v3
+
+      - name: 'Get bundles'
+        id: bundles
+        uses: ./.github/actions/get-bundles
+        with:
+          platform: linux-x64
+          debug-suffix: -debug
+
+      - name: 'Run smoke tests'
+        id: smoke-test
+        run:
+          cd ./PEA && make JAVA_HOME=${{ steps.bundles.outputs.jdk-path }}
+
+        # This is the best way I found to abort the job with an error message
+      - name: 'Notify about test failures'
+        uses: actions/github-script@v6
+        with:
+          script: core.setFailed('${{ steps.smoke-test.outputs.error-message }}')
+        if: steps.smoke-test.outputs.failure == 'true'
+
+  ctw-test:
+    needs: smoke-test
+    runs-on: ubuntu-22.04
+    steps:
+      - name: 'Checkout the JDK source'
+        uses: actions/checkout@v3
+
+      - name: 'Get bundles'
+        id: bundles
+        uses: ./.github/actions/get-bundles
+        with:
+          platform: linux-x64
+          debug-suffix: -debug
+
+      - name: 'Run CTW java.base'
+        id: ctw-java-base
+        run: |
+          cd PEA/
+          make run-ctw JAVA_HOME=${{ steps.bundles.outputs.jdk-path }}
+          # run without inliner
+          make run-ctw-no-inline JAVA_HOME=${{ steps.bundles.outputs.jdk-path }}
+
+      # This is the best way I found to abort the job with an error message
+      - name: 'Notify about test failures'
+        uses: actions/github-script@v6
+        with:
+          script: core.setFailed('${{ steps.ctw-java-base.outputs.error-message }}')
+        if: steps.ctw-java-base.outputs.failure == 'true'
+
+  jtreg-tests:
+    needs: ctw-test
+    runs-on: ubuntu-22.04
+    strategy:
+      fail-fast: false
+      matrix:
+        test-name:
+          - 'jdk/tier1 part 1'
+          - 'jdk/tier1 part 2'
+          - 'jdk/tier1 part 3'
+          - 'langtools/tier1'
+          - 'hs/tier1 common'
+          - 'hs/tier1 compiler'
+          - 'hs/tier1 gc'
+          - 'hs/tier1 runtime'
+          - 'hs/tier1 serviceability'
+          - 'lib-test/tier1'
+          - 'hs/tier2'
+          - 'jdk/tier2 part 1'
+          - 'jdk/tier2 part 2'
+          - 'jdk/tier2 part 3'
+
+
+        include:
+          - test-name: 'jdk/tier1 part 1'
+            test-suite: 'test/jdk/:tier1_part1'
+
+          - test-name: 'jdk/tier1 part 2'
+            test-suite: 'test/jdk/:tier1_part2'
+
+          - test-name: 'jdk/tier1 part 3'
+            test-suite: 'test/jdk/:tier1_part3'
+
+          - test-name: 'langtools/tier1'
+            test-suite: 'test/langtools/:tier1'
+
+          - test-name: 'hs/tier1 common'
+            test-suite: 'test/hotspot/jtreg/:tier1_common'
+
+          - test-name: 'hs/tier1 compiler'
+            test-suite: 'test/hotspot/jtreg/:tier1_compiler'
+
+          - test-name: 'hs/tier1 gc'
+            test-suite: 'test/hotspot/jtreg/:tier1_gc'
+
+          - test-name: 'hs/tier1 runtime'
+            test-suite: 'test/hotspot/jtreg/:tier1_runtime'
+
+          - test-name: 'hs/tier1 serviceability'
+            test-suite: 'test/hotspot/jtreg/:tier1_serviceability'
+
+          - test-name: 'lib-test/tier1'
+            test-suite: 'test/lib-test/:tier1'
+
+          - test-name: 'hs/tier2'
+            test-suite: 'hotspot:tier2'
+
+          - test-name: 'jdk/tier2 part 1'
+            test-suite: 'test/jdk/:tier2_part1'
+
+          - test-name: 'jdk/tier2 part 2'
+            test-suite: 'test/jdk/:tier2_part2'
+
+          - test-name: 'jdk/tier2 part 3'
+            test-suite: 'test/jdk/:tier2_part3'
+
+    steps:
+      - name: 'Checkout the JDK source'
+        uses: actions/checkout@v3
+
+      - name: 'Get bundles'
+        id: bundles
+        uses: ./.github/actions/get-bundles
+        with:
+          platform: linux-x64
+          debug-suffix: -debug
+
+      - name: 'Get the BootJDK'
+        id: bootjdk
+        uses: ./.github/actions/get-bootjdk
+        with:
+          platform: linux-x64
+
+      - name: 'Get JTReg'
+        id: jtreg
+        uses: ./.github/actions/get-jtreg
+
+      - name: 'Run tests'
+        id: run-tests
+        run: >
+          make test-prebuilt
+          TEST='${{ matrix.test-suite }}'
+          BOOT_JDK=${{ steps.bootjdk.outputs.path }}
+          JT_HOME=${{ steps.jtreg.outputs.path }}
+          JDK_IMAGE_DIR=${{ steps.bundles.outputs.jdk-path }}
+          SYMBOLS_IMAGE_DIR=${{ steps.bundles.outputs.symbols-path }}
+          TEST_IMAGE_DIR=${{ steps.bundles.outputs.tests-path }}
+          JTREG='JAVA_OPTIONS=-XX:-CreateCoredumpOnCrash -XX:+UnlockExperimentalVMOptions -XX:+DoPartialEscapeAnalysis;VERBOSE=fail,error,time;KEYWORDS=!headful'
+          && bash ./.github/scripts/gen-test-summary.sh "$GITHUB_STEP_SUMMARY" "$GITHUB_OUTPUT"
+
+        # This is a separate step, since if the markdown from a step gets bigger than
+        # 1024 kB it is skipped, but then the short summary above is still generated
+      - name: 'Generate test report'
+        run: bash ./.github/scripts/gen-test-results.sh "$GITHUB_STEP_SUMMARY"
+        if: always()
+
+      - name: 'Package test results'
+        id: package
+        run: |
+          # Package test-results and relevant parts of test-support
+          mkdir results
+
+          if [[ -d build/run-test-prebuilt/test-results ]]; then
+            cd build/run-test-prebuilt/test-results/
+            zip -r -9 "$GITHUB_WORKSPACE/results/test-results.zip" .
+            cd $GITHUB_WORKSPACE
+          else
+            echo '::warning ::Missing test-results directory'
+          fi
+
+          if [[ -d build/run-test-prebuilt/test-support ]]; then
+            cd build/run-test-prebuilt/test-support/
+            zip -r -9 "$GITHUB_WORKSPACE/results/test-support.zip" . -i *.jtr -i */hs_err*.log -i */replay*.log
+            cd $GITHUB_WORKSPACE
+          else
+            echo '::warning ::Missing test-support directory'
+          fi
+
+          artifact_name="results-linux-x64-$(echo ${{ matrix.test-name }} | tr '/ ' '__')"
+          echo "artifact-name=$artifact_name" >> $GITHUB_OUTPUT
+        if: always()
+
+      - name: 'Upload test results'
+        uses: actions/upload-artifact@v3
+        with:
+          path: results
+          name: ${{ steps.package.outputs.artifact-name }}
+        if: always()
+
+        # This is the best way I found to abort the job with an error message
+      - name: 'Notify about test failures'
+        uses: actions/github-script@v6
+        with:
+          script: core.setFailed('${{ steps.run-tests.outputs.error-message }}')
+        if: steps.run-tests.outputs.failure == 'true'
diff a/PEA/.gitignore b/PEA/.gitignore
--- /dev/null
+++ b/PEA/.gitignore
@@ -0,0 +1,2 @@
+*.class
+*.log
diff a/PEA/BadGraphVolatile.java b/PEA/BadGraphVolatile.java
--- /dev/null
+++ b/PEA/BadGraphVolatile.java
@@ -0,0 +1,24 @@
+class BadGraphVolatile {
+    // use volatile to force c2 to load it again
+    volatile int value;
+
+    BadGraphVolatile(int value) {
+        this.value = value;
+    }
+
+    void blackhole(int field) {
+        assert field  == 42 :" wrong answer";
+    }
+
+    public static void foo(int value) {
+        var x = new BadGraphVolatile(value);
+        x.blackhole(x.value);
+        return;
+    }
+
+    public static void main(String[] args)  {
+        for (int i = 0; i < 30_000; ++i) {
+            foo(42);
+        }
+    }
+}
diff a/PEA/CompositeObjects.java b/PEA/CompositeObjects.java
--- /dev/null
+++ b/PEA/CompositeObjects.java
@@ -0,0 +1,24 @@
+class CompositeObjects {
+    static class Node {
+       public Node _next;
+       public Node() {}
+    }
+
+    static Object _global;
+
+    public static void test() {
+        var a = new Node();
+        var b = new Node();
+
+        a._next = b; b._next = a;
+        blackhole(a); // materialize a here.
+    }
+    public static void blackhole(Object o) {
+        _global = o;
+    }
+    public static void main(String[] args ) {
+        for (int i=0; i < 100_000; ++i) {
+            test();
+        }
+    }
+}
diff a/PEA/CountUppercase.java b/PEA/CountUppercase.java
--- /dev/null
+++ b/PEA/CountUppercase.java
@@ -0,0 +1,20 @@
+// https://www.graalvm.org/22.1/examples/java-performance-examples/#streams-api-example
+public class CountUppercase {
+    static final int ITERATIONS = Math.max(Integer.getInteger("iterations", 1), 1);
+    public static void main(String[] args) {
+        String sentence = String.join(" ", args);
+        for (int iter = 0; iter < ITERATIONS; iter++) {
+            if (ITERATIONS != 1) System.out.println("-- iteration " + (iter + 1) + " --");
+            long total = 0, start = System.currentTimeMillis(), last = start;
+            for (int i = 1; i < 10_000_000; i++) {
+                total += sentence.chars().filter(Character::isUpperCase).count();
+                if (i % 1_000_000 == 0) {
+                    long now = System.currentTimeMillis();
+                    System.out.printf("%d (%d ms)%n", i / 1_000_000, now - last);
+                    last = now;
+                }
+            }
+            System.out.printf("total: %d (%d ms)%n", total, System.currentTimeMillis() - start);
+        }
+    }
+}
diff a/PEA/CrazyException.java b/PEA/CrazyException.java
--- /dev/null
+++ b/PEA/CrazyException.java
@@ -0,0 +1,23 @@
+class CrazyException {
+    void blackhole() {}
+    public static Object foo(boolean cond) {
+        var x = new CrazyException();
+        for (int i=0; i< 100; ++i) {
+          if (cond)  {
+              try {
+                  x.blackhole(); // not inline. escape here.
+              } finally {        // hidden catch(Throwable) is here!
+                  close();
+              }
+          }
+        }
+        return x;
+    }
+
+    public static void close() {}
+    public static void main(String[] args)  {
+        for (int i = 0; i < 20_000; ++i) {
+            CrazyException.foo(0 == ( i& 0xf));
+        }
+    }
+}
diff a/PEA/EscapeInInitializer.java b/PEA/EscapeInInitializer.java
--- /dev/null
+++ b/PEA/EscapeInInitializer.java
@@ -0,0 +1,64 @@
+class EscapeInInitializer {
+    private final int foo;
+    private final int bar;
+    private static EscapeInInitializer cache;
+
+    EscapeInInitializer() {
+        this.foo = 1;   // put a final field, set_alloc_with_final(obj)
+        cache = this;   // materialize this here.
+        this.bar = 2;   // put a final field again, set_alloc_with_final(obj)
+    }
+
+    EscapeInInitializer(int i) {
+        this.foo = 1;   // put a final field, set_alloc_with_final(obj)
+        this.bar = the_answer(); // invocation, either inline or not line may trigger materialization.
+    }
+
+    EscapeInInitializer(boolean cond) {
+        this.foo = 1;   // put a final field, set_alloc_with_final(obj)
+        this.bar = 2;
+        if  (cond) {
+            cache = this;   // materialize this here.
+        }
+        // we need to merge 'this' here, and then emit the trailing barrier for the phi.
+    }
+
+    int the_answer() { // of life, the universe and everything...
+        cache = this;  // materialize 'this' here
+        return 42;
+    }
+
+    static class Scope {
+      public int kind;
+      final Scope parent;
+      final Scope compilationUnitScope;
+
+      // https://github.com/testforstephen/eclipse.jdt.core/blob/c74a21fabed6931812aac1d1cc3b5b09690e8c96/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/Scope.java#L159C86-L159C106
+      public Scope(int kind, Scope parent) {
+        this.kind = kind;
+        this.parent = parent;
+        this.compilationUnitScope = (parent == null ? this : parent.compilationUnitScope());
+      }
+
+      public Scope compilationUnitScope() {
+        return new Scope(0, null);
+      }
+    }
+
+    static void test(int i) {
+        var kase = new EscapeInInitializer();
+        var kase2 = new EscapeInInitializer(i);
+        var kase3 = new EscapeInInitializer(0 == (i % 100));
+    }
+
+    static void test2(int i) {
+        Scope parent = new Scope(1, null);
+        var kase = new Scope(i, 0 == (i % 100) ? null : parent);
+    }
+    public static void main(String[] args) {
+        for (int i = 0; i < 30_000; ++i) {
+            test(i);
+            test2(i);
+        }
+    }
+}
diff a/PEA/Example1.java b/PEA/Example1.java
--- /dev/null
+++ b/PEA/Example1.java
@@ -0,0 +1,29 @@
+class Example1 {
+    private Object _cache;
+    public void test(boolean cond) {
+        Object x = new Object();
+
+        if (cond) {
+            _cache = x;
+        }
+    }
+
+    public Object test2(boolean cond) {
+        Object x = new Object();
+
+        if (cond) {
+            _cache = x;
+        }
+
+        return cond ? null: x;
+    }
+
+    public static void main(String[] args)  {
+        Example1 kase = new Example1();
+
+        for (int i = 0; i < 1_000_000; ++i) {
+            kase.test(0 == (i& 0xf));
+            kase.test2(0 == (i& 0xf));
+        }
+    }
+}
diff a/PEA/Example1_ivanov.java b/PEA/Example1_ivanov.java
--- /dev/null
+++ b/PEA/Example1_ivanov.java
@@ -0,0 +1,36 @@
+// -Xcomp -Xms16M -Xmx16M -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:-UseOnStackReplacement -XX:CompileOnly='Example1.ivanov' -XX:CompileCommand=dontinline,Example1.blackhole
+class Example1_ivanov {
+    // Ivanov suggest to make this happen first.
+    // we don't need to create JVMState for the cloning Allocate.
+    public void ivanov(boolean cond) {
+        Object x = new Object();
+
+        if (cond) {
+            blackhole(x);
+        }
+    }
+
+    static void blackhole(Object x) {}
+
+    public void test1(boolean cond) {
+        ivanov(cond);
+    }
+
+    public static void main(String[] args)  {
+        Example1_ivanov kase = new Example1_ivanov();
+        // Epsilon Test:
+        // By setting the maximal heap and use EpsilonGC, let's see how long and how many iterations the program can sustain.
+        // if PEA manages to reduce allocation rate, we expect the program to stay longer.
+        // Roman commented it with a resonable doubt: "or your code slow down the program..."
+        // That's why I suggest to observe iterations. It turns out not trivial because inner OOME will implode hotspot. We don't have a chance to execute the final statement...
+        long iterations = 0;
+        try {
+            while (true) {
+                kase.test1(0 == (iterations & 0xf));
+                iterations++;
+            }
+        } finally {
+            System.err.println("Epsilon Test: " + iterations);
+        }
+    }
+}
diff a/PEA/Example2.java b/PEA/Example2.java
--- /dev/null
+++ b/PEA/Example2.java
@@ -0,0 +1,34 @@
+// -Xcomp -Xms16M -Xmx16M -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:-UseOnStackReplacement -XX:CompileOnly='Example2.foo' -XX:CompileCommand=dontinline,Example2.blackhole
+class Example2 {
+    private Object _cache;
+    public Object foo(boolean cond) {
+        Object x = new Object();
+        
+        blackhole();
+
+        if (cond) {
+            _cache = x;
+        }
+        return x;
+    }
+
+    public static void blackhole() {}
+    
+    public static void main(String[] args)  {
+        Example2 kase = new Example2();
+        // Epsilon Test:
+        // By setting the maximal heap and use EpsilonGC, let's see how long and how many iterations the program can sustain.
+        // if PEA manages to reduce allocation rate, we expect the program to stay longer.
+        // Roman commented it with a resonable doubt: "or your code slow down the program..."
+        // That's why I suggest to observe iterations. It turns out not trivial because inner OOME will implode hotspot. We don't have a chance to execute the final statement...
+        long iterations = 0;
+        try {
+            while (true) {
+                kase.foo(0 == (iterations & 0xf));
+                iterations++;
+            }
+        } finally {
+            System.err.println("Epsilon Test: " + iterations);
+        }
+    }
+}
diff a/PEA/Example2_1.java b/PEA/Example2_1.java
--- /dev/null
+++ b/PEA/Example2_1.java
@@ -0,0 +1,50 @@
+// -Xcomp -Xms16M -Xmx16M -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:-UseOnStackReplacement -XX:CompileOnly='Example2.foo' -XX:CompileCommand=dontinline,Example2.blackhole
+class Example2_1 {
+    int value  = 0;
+
+    public static Example2_1 foo(boolean cond) {
+        var x = new Example2_1();
+        
+        if (cond) {
+            x.value++;
+        } else {
+            x.value += 2;
+        }
+        return x;
+    }
+
+    public static int foo2(boolean cond) {
+        var x = new Example2_1();
+        
+        if (cond) {
+            x.value++;
+        } else {
+            x.value += 2;
+        }
+        return x.value;
+    }
+
+
+    public static void blackhole() {}
+    
+    public static void main(String[] args)  {
+        long iterations = 0;
+        try {
+            while (true) {
+                boolean cond = 0 == (iterations & 0xf);
+                Example2_1 obj = foo(cond);
+                int expected = cond ? 1 : 2; 
+                if (obj.value != expected) {
+                    throw new RuntimeException("wrong answer");
+                }
+                int val = foo2(cond);
+                if (val != expected) {
+                    throw new RuntimeException("wrong answer");
+                }
+                iterations++;
+            }
+        } finally {
+            System.err.println("Epsilon Test: " + iterations);
+        }
+    }
+}
diff a/PEA/Example2_merykitty.java b/PEA/Example2_merykitty.java
--- /dev/null
+++ b/PEA/Example2_merykitty.java
@@ -0,0 +1,38 @@
+// -Xcomp -Xms16M -Xmx16M -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:-UseOnStackReplacement -XX:CompileOnly='Example2_merykitty.foo' -XX:CompileCommand=dontinline,Example2_merykitty.blackhole
+class Example2_merykitty {
+    private static Object _cache1;
+    private static Object _cache2;
+
+    public void foo(boolean cond1, boolean cond2) {
+        Object x = new Object();
+    
+        blackhole();
+    
+        if (cond1) {
+            _cache1 = x;
+        }
+    
+        blackhole();
+    
+        if (cond2) {
+            _cache2 = x;
+        }
+    }
+
+    public static void blackhole() {}
+    
+    public static void main(String[] args)  {
+        Example2_merykitty kase = new Example2_merykitty();
+        long iterations = 0;
+        try {
+            while (true) {
+                boolean cond = 0 == (iterations & 0xf);
+                kase.foo(cond, cond);
+                assert Example2_merykitty._cache1 == Example2_merykitty._cache2 :"check";
+                iterations++;
+            }
+        } finally {
+            System.err.println("Epsilon Test: " + iterations);
+        }
+    }
+}
diff a/PEA/Example2b.java b/PEA/Example2b.java
--- /dev/null
+++ b/PEA/Example2b.java
@@ -0,0 +1,66 @@
+// java -Xms32M -Xmx32M -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:-UseOnStackReplacement -XX:+UseTLAB -XX:CompileCommand='compileonly,Example2b::foo*' -Xlog:gc -XX:+DoPartialEscapeAnalysis -XX:+PrintEscapeAnalysis -XX:+PrintEliminateAllocations -XX:-PrintCompilation -XX:CompileCommand=quiet  Example2b
+class Example2b {
+    public String _cache;
+
+    public String foo(boolean cond) {
+        String x = null;
+
+        if (cond) {
+            x = new String("hello");
+        }
+
+        return x;
+    }
+    public String foo2(boolean cond) {
+        String x = null;
+
+        if (cond) {
+            x = new String("hello");
+            _cache = x;       // object 'x' escapes
+        }
+
+        return x;
+    }
+
+    public String foo3(String str, boolean cond) {
+        if (str == null) {
+            str = new String("hello");
+        }
+        if (cond) {
+            _cache = str;
+        }
+        return str;
+    }
+    public static void main(String[] args)  {
+        Example2b kase = new Example2b();
+        int iterations = 0;
+        try {
+            while (true) {
+                boolean cond = 0 == (iterations & 0xf);
+                String s = kase.foo(cond);
+                if (s != null && !s.equals("hello")) {
+                    throw new RuntimeException("e");
+                }
+
+                kase.foo2(cond);
+                if (kase._cache != null && !kase._cache.equals("hello")) {
+                    throw new RuntimeException("e2");
+                }
+
+                if (0 == (iterations % 2)) {
+                    s = kase.foo3(null, cond);
+                } else {
+                    s= kase.foo3(kase._cache, cond);
+                }
+
+                if (s != null && !s.equals("hello")) {
+                    throw new RuntimeException("e3");
+                }
+
+                iterations++;
+            }
+        } finally {
+            System.err.println("Epsilon Test: " + iterations);
+        }
+    }
+}
diff a/PEA/Example3.java b/PEA/Example3.java
--- /dev/null
+++ b/PEA/Example3.java
@@ -0,0 +1,63 @@
+//  from ResourceScopeCloseMin.java
+//  https://bugs.openjdk.org/browse/JDK-8267532
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+public class Example3 {
+
+    Runnable dummy = () -> {};
+
+    static class ConfinedScope {
+        final Thread owner;
+        boolean closed;
+        final List<Runnable> resources = new ArrayList<>();
+        //int [] weight = new int[1024];
+        //final Runnable[] resources = new Runnable[1];
+
+        private void checkState() {
+            if (closed) {
+                throw new AssertionError("Closed");
+            } else if (owner != Thread.currentThread()) {
+                throw new AssertionError("Wrong thread");
+            }
+        }
+
+        ConfinedScope() {
+            this.owner = Thread.currentThread();
+        }
+
+        void addCloseAction(Runnable runnable) {
+            checkState();
+            resources.add(runnable);
+            //resources[0] = runnable;
+        }
+
+        public void close() {
+            checkState();
+            closed = true;
+            for (Runnable r : resources) {
+                r.run();
+            }
+        }
+    }
+
+    public void confined_close() {
+        ConfinedScope scope = new ConfinedScope();
+        try { // simulate TWR
+            scope.addCloseAction(dummy);
+            scope.close();
+        } catch (RuntimeException ex) {
+            scope.close();
+            throw ex;
+        }
+    }
+
+    public static void main(String[] args) {
+	    var kase = new Example3();
+	
+        while (true) {
+            kase.confined_close();
+        }
+    }
+}
diff a/PEA/Example3_1.java b/PEA/Example3_1.java
--- /dev/null
+++ b/PEA/Example3_1.java
@@ -0,0 +1,38 @@
+// java -Xms16M -Xmx16M -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:-UseOnStackReplacement -XX:-UseTLAB -XX:CompileOnly='Example3_1.foo' -XX:+DoPartialEscapeAnalysis Example3_1
+import java.util.ArrayList; 
+
+class Example3_1 {
+    public ArrayList<Integer> _cache;
+    public void foo(boolean cond) {
+        ArrayList<Integer> x = new ArrayList<Integer>();
+
+        if (cond) {
+            _cache = x;
+        }
+    }
+
+    public void test1(boolean cond) {
+        foo(cond);
+    }
+
+    public static void main(String[] args)  {
+        Example3_1 kase = new Example3_1();
+        // Epsilon Test:
+        // By setting the maximal heap and use EpsilonGC, let's see how long and how many iterations the program can sustain.
+        // if PEA manages to reduce allocation rate, we expect the program to stay longer.
+        // Roman commented it with a resonable doubt: "or your code slow down the program..."
+        // That's why I suggest to observe iterations. It turns out not trivial because inner OOME will implode hotspot. We don't have a chance to execute the final statement...
+        long iterations = 0;
+        try {
+            while (true) {
+                kase.test1(0 == (iterations & 0xf));
+                if (kase._cache != null) {
+                    kase._cache.add(1);
+                }
+                iterations++;
+            }
+        } finally {
+            System.err.println("Epsilon Test: " + iterations);
+        }
+    }
+}
diff a/PEA/Example3_2.java b/PEA/Example3_2.java
--- /dev/null
+++ b/PEA/Example3_2.java
@@ -0,0 +1,35 @@
+import java.util.*;
+
+class Example3_2 {
+    //public static Integer zero = Integer.valueOf(0);
+
+    void bar(boolean cond, ArrayList<Integer> L) {
+        if(cond) {
+            L.add(0);
+        }
+    }
+
+    public void foo(boolean cond) {
+        var x = new ArrayList<Integer>();
+        bar(cond, x);
+        return;
+    }
+
+    public static void main(String[] args)  {
+        var kase = new Example3_2();
+        // Epsilon Test:
+        // By setting the maximal heap and use EpsilonGC, let's see how long and how many iterations the program can sustain.
+        // if PEA manages to reduce allocation rate, we expect the program to stay longer.
+        // Roman commented it with a resonable doubt: "or your code slow down the program..."
+        // That's why I suggest to observe iterations. It turns out not trivial because inner OOME will implode hotspot. We don't have a chance to execute the final statement...
+        long iterations = 0;
+        try {
+            while (true) {
+                kase.foo(0 == (iterations & 0xf));
+                iterations++;
+            }
+        } finally {
+            System.err.println("Epsilon Test: " + iterations);
+        }
+    }
+}
diff a/PEA/GetFieldIsAlias.java b/PEA/GetFieldIsAlias.java
--- /dev/null
+++ b/PEA/GetFieldIsAlias.java
@@ -0,0 +1,31 @@
+// inspired by this code snippet:
+// https://github.com/openjdk/jdk/blob/42758cb889a5cf1d7f4c4b468a383b218baa1b27/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java#L1318
+class GetFieldIsAlias {
+    static class Type {
+        private int value;
+        public GetFieldIsAlias parent;
+
+        public Type(int value, GetFieldIsAlias parent) {
+            this.value = value;
+            this.parent = parent;
+        }
+    }
+
+    public Type type;
+    public GetFieldIsAlias() {
+        this.type = new Type(42, null);
+        // 'this.type' is supposed to be the fix pattern: AddP-LoadN-DecodeN-CastPP.
+        // We need to ensure this.type is alias with the object just created, or PEA assumes that this.type is 'a global variable'
+        // and we have to materialize 'this'.
+        this.type.parent = this;
+    }
+
+    public static void main(String[] args) {
+        for (int i = 0; i< 200_000; ++i) {
+            var obj = new GetFieldIsAlias();
+            if (obj.type.parent != obj) {
+                throw new RuntimeException("wrong answer");
+            }
+        }
+    }
+}
diff a/PEA/LoadSelfIntoField.java b/PEA/LoadSelfIntoField.java
--- /dev/null
+++ b/PEA/LoadSelfIntoField.java
@@ -0,0 +1,37 @@
+class LoadSelfIntoField {
+    // Test that PEA handles loading self into a field correctly.
+    // This test is a smaller reproducer of Test6843752.
+    static class Item {
+        public Item    next;
+        public Item    prev;
+    }
+    Item list = null;
+
+    private Item test(boolean cond) {
+        Item item = new Item();
+        Item head = list;
+        if (cond) {
+            item.next = item;
+            item.prev = item;
+            list = item;
+            // Confirm we correctly materialize setting object's field to self.
+            assert item == item.next;
+            assert item == item.prev;
+        } else {
+            item.next = head;
+            item.prev = head.prev;
+            head.prev.next = item;
+            head.prev = item;
+        }
+        return item;
+    }
+
+    public static void main(String[] args)  {
+        LoadSelfIntoField obj = new LoadSelfIntoField();
+        long iterations = 0;
+        while (iterations <= 20000) {
+            obj.test(0 == (iterations & 0xf));
+            iterations++;
+        }
+    }
+}
diff a/PEA/Makefile b/PEA/Makefile
--- /dev/null
+++ b/PEA/Makefile
@@ -0,0 +1,61 @@
+.DEFAULT_GOAL := all
+
+JAVA_HOME ?= $(abspath ../build/linux-x86_64-server-fastdebug/jdk)
+export PATH := ${JAVA_HOME}/bin:$(PATH)
+
+CTW_DIR := $(abspath ../test/hotspot/jtreg/testlibrary/ctw)
+CTW_DIST= ${CTW_DIR}/dist
+CTW_JAVA_OPTIONS := -XX:+CITime -XX:-TieredCompilation -XX:+UseTLAB -XX:+PrintCompilation -XX:+PrintOptoStatistics -XX:+UnlockExperimentalVMOptions -XX:+DoPartialEscapeAnalysis
+comma := ,
+empty :=
+space := $(empty) $(empty)
+# CTWRunner accepts arguments with commas instead of spaces
+CTW_RUNNER_JAVA_OPTIONS := $(subst $(space),$(comma),$(CTW_JAVA_OPTIONS))
+CONF ?= linux-x86_64-server-fastdebug
+JTREG_COMMON = JAVA_OPTIONS=-XX:+UnlockExperimentalVMOptions -XX:+DoPartialEscapeAnalysis
+JTREG_PARANOID=VM_OPTIONS=-ea -esa -XX:CompileThreshold=100
+
+set_jdk:
+	if [ -e ${JAVA_HOME} ]; then \
+		echo "choose ${JAVA_HOME}"; \
+		java -version; \
+	else \
+		echo "can't find valid jdk!"; \
+		exit 1; \
+	fi
+
+classes := $(patsubst %.java, %.class, $(wildcard *.java))
+
+%.class: %.java
+	javac --add-exports java.base/jdk.internal.misc=ALL-UNNAMED $<
+
+smoketest: set_jdk $(classes)
+	./auto.rb
+
+$(CTW_DIST):
+	${MAKE} -C ${CTW_DIR} all
+
+run-ctw: set_jdk ${CTW_DIST}
+	cd ${CTW_DIST}; \
+	JAVA_OPTIONS="-Dsun.hotspot.tools.ctwrunner.ctw_extra_args=${CTW_RUNNER_JAVA_OPTIONS}" JAVA_HOME=${JAVA_HOME} ./ctwrunner.sh modules:java.base
+
+run-ctw-no-inline: set_jdk ${CTW_DIST}
+	cd ${CTW_DIST}; \
+	JAVA_OPTIONS="${CTW_JAVA_OPTIONS} -XX:-Inline" JAVA_HOME=${JAVA_HOME} ./ctw.sh modules:java.base
+
+bootstrap-version: set_jdk
+	java -Xcomp -XX:-TieredCompilation -XX:CICompilerCount=1 -Xbatch -XX:+UnlockExperimentalVMOptions -XX:+DoPartialEscapeAnalysis -version
+
+hotspot-tier1:
+	cd ..; make test TEST="hotspot:tier1" CONF=${CONF} JREG="${JTREG_COMMON}"
+
+hotspot-tier1-paranoid:
+	cd ..; make test TEST="hotspot:tier1" CONF=${CONF} JTREG="${JTREG_COMMON};${JTREG_PARANOID}"
+
+hotspot-tier2:
+	cd ..; make test TEST="hotspot:tier2" CONF=${CONF} JREG="${JTREG_COMMON}"
+
+all: smoketest bootstrap-version
+
+clean:
+	rm *.class *.log
diff a/PEA/MatInMonitor.java b/PEA/MatInMonitor.java
--- /dev/null
+++ b/PEA/MatInMonitor.java
@@ -0,0 +1,68 @@
+class MatInMonitor {
+    private Object _cache;
+    private boolean _odd;
+
+    public void test() {
+        Object x = new Object();
+        synchronized (x) {
+            _cache = x;
+        }
+    }
+
+    public void test2() {
+        Object x = new Object();
+        Object y = new Object();
+        synchronized (x) {
+        synchronized (y) {
+            _cache = x; // steal the monitor of x, which is not the top of stack.
+        }}
+    }
+
+    public void test3() {
+        Object x = new Object();
+        synchronized (x) {
+            if (_odd) {
+                _cache = x;
+            }
+        }
+    }
+    // c2 doesn't support this nesting form. GeneratePairingInfo regards it as 'unbalanced'.
+    public void testNested() {
+        Object x = new Object();
+        synchronized (x) {
+        synchronized (x) {
+        synchronized (x) {
+            _cache = x;
+        }}}
+    }
+
+    synchronized void bar() {
+        if (_odd) {
+            _cache = this; // escaping
+        }
+    }
+
+    synchronized void foo() { bar(); }
+
+    // Perfectly fine in C2. it is also a nesting lock after inlining.
+    // it passes GeneratePairingInfo becasue it is intra-procedural.
+    public void testNested2() {
+        synchronized(this) {
+            foo();
+        }
+    }
+
+    public static void main(String[] args)  {
+        MatInMonitor kase = new MatInMonitor();
+
+        for (int i = 0; i < 10_000; ++i) {
+            kase._odd = 0 == (i& 0xf);
+            kase.test();
+            kase.test2();
+            kase.test3();
+
+            kase.testNested();
+            kase.testNested2();
+        }
+    }
+}
diff a/PEA/MaterializeMember.java b/PEA/MaterializeMember.java
--- /dev/null
+++ b/PEA/MaterializeMember.java
@@ -0,0 +1,42 @@
+import java.util.*;
+
+class MaterializeMember {
+    // inspired by TestNG-7.3.0
+    // https://github.com/testng-team/testng/blob/9ae7d3d9d19e5aa51e9610549c72e883718e82e5/src/main/java/org/testng/internal/reflect/ReflectionRecipes.java#L369C56-L369C80
+    private static class ListBackedImmutableQueue<T> {
+        private final List<T> backingList;
+
+        ListBackedImmutableQueue(final T[] elements) {
+            backingList = new ArrayList<>(elements.length);
+            Collections.addAll(backingList, elements);
+        }
+
+        T poll() {
+            if (!backingList.isEmpty()) {
+                return backingList.remove(0);
+            }
+            throw new RuntimeException("Queue exhausted");
+        }
+    }
+
+    public void test() {
+        Integer[] parameters = new Integer[3];
+        for (int i = 0; i < parameters.length; ++i) {
+            parameters[i] = Integer.valueOf(i);
+        }
+        var queue = new ListBackedImmutableQueue<Integer>(parameters);
+        for (Integer p : parameters) {
+            Integer t = queue.poll();
+            if (!t.equals(p)) {
+                throw new RuntimeException("wrong result!");
+            }
+        }
+    }
+
+    public static void main(String[] args) {
+        MaterializeMember kase = new MaterializeMember();
+        for (int i = 0; i < 300_000; ++i) {
+            kase.test();
+        }
+    }
+}
diff a/PEA/MergeAllVirts.java b/PEA/MergeAllVirts.java
--- /dev/null
+++ b/PEA/MergeAllVirts.java
@@ -0,0 +1,230 @@
+import java.io.*;
+
+class MergeAllVirts {
+    int a;
+    int b;
+    int c = 100;
+
+    public MergeAllVirts() {}
+    public MergeAllVirts(boolean cond) {
+        if (cond) {
+            this.a = 1;
+            this.b = 20;
+            this.c = 300;
+            blackhole(); // kill locals, so LV0 = this is dead here.
+        } else {
+            this.a = 100;
+            this.b = 50;
+            this.c = 7;
+            blackhole(); // kill locals.
+        }
+        // merge 2 predecessors, but LV0 is not live here.
+        // we need to merge allocation states, or wrong current allocation state is wrong.
+    }
+
+    public int sum() {
+        return a + b + c;
+    }
+
+    public static int add(boolean cond) {
+        MergeAllVirts obj = new MergeAllVirts();
+        if (cond) {
+            obj.a = 1;
+        } else {
+            obj.b = 2;
+        }
+        //obj3 = phi(obj1, obj2);
+        return obj.sum();
+    }
+
+    public static MergeAllVirts cached;
+    public static void escaped(boolean cond) {
+        MergeAllVirts obj = new MergeAllVirts();
+
+        if (cond) {
+            obj.a = 1;
+        } else {
+            obj.b = 2;
+        }
+        // return obj;  we don't materialize at exit. if it's virtual, keep it virtual.
+        cached = obj; // materialize here.
+    }
+
+    static void check_result(boolean cond, int sum) {
+        if ((cond && sum != 101) || (!cond && sum != 102)) {
+            throw new RuntimeException("wrong answer: " + sum);
+        }
+    }
+    static void blackhole() {} // not inline this.
+    static void blackhole(MergeAllVirts obj) {} // not inline this.
+    static void blackhole(File file) throws IOException {}
+    public static MergeAllVirts escaped2(boolean cond1, boolean cond2) {
+        MergeAllVirts obj = new MergeAllVirts();
+
+        if (cond1) {
+            obj.a = 20;
+        } else {
+            obj.b = 30;
+        }
+        if (cond2) {
+            blackhole(obj); // materialize obj here.
+        }
+        return obj;
+    }
+
+    static void check_result2(boolean cond1, int sum) {
+        boolean okay = true;
+
+        if (cond1) {
+            okay = sum == 120;
+        } else {
+            okay = sum == 130;
+        }
+
+        if (!okay) {
+            throw new RuntimeException("wrong answer: " + cond1 + " " + sum);
+        }
+    }
+
+    public static MergeAllVirts escaped3(boolean cond1, boolean cond2) {
+        MergeAllVirts obj = new MergeAllVirts();
+
+        if (cond1) {
+            obj.a = 20;
+        } else {
+            obj.b = 30;
+        }
+        if (cond2) {
+            blackhole(obj); // materialize obj here.
+            obj.a = 300;
+        }
+        return obj;
+    }
+
+    static void check_result3(boolean cond1, boolean cond2, int sum) {
+        boolean okay = true;
+
+        if (cond1) {
+            if (cond2)
+                okay = sum == 400; // 300 + 0 + 100
+            else
+                okay = sum == 120;
+        } else {
+            if (cond2)
+                okay = sum == 430; // 300 + 30 + 100
+            else
+                okay = sum == 130;
+        }
+
+        if (!okay) {
+            throw new RuntimeException("wrong answer: " + cond1 + " "  + cond2 + " " + sum);
+        }
+    }
+    public static void escaped4(boolean cond) {
+        MergeAllVirts obj = new MergeAllVirts(cond);
+
+        // return obj;  we don't materialize at exit. if it's virtual, keep it virtual.
+        cached = obj; // materialize here.
+    }
+
+    static void check_result4(boolean cond, int sum) {
+        boolean okay;
+
+        if (cond) {
+            okay = sum == 321;
+        } else {
+            okay = sum == 157;
+        }
+
+        if (!okay) {
+            throw new RuntimeException("wrong answer: " + cond + " "  + sum);
+        }
+    }
+
+    private static void fail(String msg, Object... args) {
+        throw new RuntimeException(msg);
+    }
+
+    // JVM-1880: merge the same object but they have different aliases in its predecessors.
+    // one common reason is exception handler. C2 parse forces to generate a phi for them.
+    // inspired by sun.net.sdp.SdpProvider::<init>
+    public static Object merge_2virts(File file) {
+        MergeAllVirts obj = new MergeAllVirts();
+
+        try {
+            blackhole(file);
+        } catch(IOException e) {
+            // create a phi node : n = phi(region, _, obj)
+            fail("Error reading %s: %s", file, e.getMessage());
+        }
+        // merge here: phi(r, m, n) and n is phi(region, _, obj)
+        // same object but different aliases!
+        return obj;
+    }
+
+    public static MergeAllVirts merge_virts(boolean cond1, boolean cond2) {
+        MergeAllVirts obj = new MergeAllVirts();
+        if (cond1) {
+            if (cond2) {
+                obj.a = 1;
+            } else {
+                obj.a = 2;
+            }
+            blackhole(); // merge obj.a here.
+        } else {
+            obj.b = 20;  // merge both obj.a and obj.b
+        }
+        return obj;
+    }
+
+    static void check_result_virts(boolean cond1, boolean cond2, int sum) {
+        boolean okay;
+        if (cond1) {
+            if (cond2) {
+                okay = sum == 101;
+            } else {
+                okay = sum == 102;
+            }
+        } else {
+            okay = sum == 120;
+        }
+
+        if (!okay) {
+            throw new RuntimeException("wrong answer: " + cond1 + " " + cond2 + " "  + sum);
+        }
+    }
+
+    public static void main(String[] args) {
+        long iterations = 0;
+        File file = new File("hello");
+        boolean cond2 = true;
+        try {
+            while (true) {
+                boolean cond = 0 == (iterations & 0xf);
+                int sum = MergeAllVirts.add(cond);
+                check_result(cond, sum);
+
+                MergeAllVirts.escaped(cond);
+                check_result(cond, cached.sum());
+
+                cond2 = cond2 ^ cond;
+                sum = MergeAllVirts.escaped2(cond, cond2).sum();
+                check_result2(cond, sum);
+
+                sum = MergeAllVirts.escaped3(cond, cond2).sum();
+                check_result3(cond, cond2, sum);
+
+                MergeAllVirts.escaped4(cond);
+                check_result4(cond, cached.sum());
+
+                MergeAllVirts.merge_2virts(file);
+
+                sum = MergeAllVirts.merge_virts(cond, cond2).sum();
+                check_result_virts(cond, cond2, sum);
+                iterations++;
+            }
+        } finally {
+            System.err.println("Epsilon Test: " + iterations);
+        }
+    }
+}
diff a/PEA/MergeEscaped.java b/PEA/MergeEscaped.java
--- /dev/null
+++ b/PEA/MergeEscaped.java
@@ -0,0 +1,158 @@
+class MergeEscaped {
+    int a;
+    int b;
+    int c = 100;
+
+    public MergeEscaped() {}
+    public MergeEscaped(boolean cond) {
+        if (cond) {
+            this.a = 1;
+            this.b = 20;
+            this.c = 300;
+            blackhole(); // kill locals, so LV0 = this is dead here.
+        } else {
+            this.a = 100;
+            this.b = 50;
+            this.c = 7;
+            blackhole(); // kill locals.
+        }
+        // merge 2 predecessors, but LV0 is not live here.
+        // we need to merge allocation states, or wrong current allocation state is wrong.
+    }
+
+    public int sum() {
+        return a + b + c;
+    }
+
+    public static MergeEscaped cached;
+
+    void blackhole() {} // not inline this.
+    static void blackhole(Object obj) {} // not inline this.
+
+    public static MergeEscaped escaped(boolean cond1) {
+        MergeEscaped obj = new MergeEscaped();
+
+        if (cond1) {
+            blackhole(obj);
+            obj.a = 20;
+        } else {
+            obj.b = 30;
+            blackhole(obj);
+        }
+        obj.c = 100;
+        return obj;
+    }
+
+    static void check_result(boolean cond1, int sum) {
+        boolean okay = true;
+
+        if (cond1) {
+            okay = sum == 120;
+        } else {
+            okay = sum == 130;
+        }
+
+        if (!okay) {
+            throw new RuntimeException("wrong answer: " + cond1 + " " + sum);
+        }
+    }
+    public static MergeEscaped escaped2(boolean cond1) {
+        MergeEscaped obj = new MergeEscaped();
+
+        for (int i = 0; i < 100; ++i) {
+            if (cond1) {
+                blackhole(obj);
+                obj.a = 20;
+            } else {
+                obj.b = 30;
+            }
+        }
+        return obj;
+    }
+
+    public static MergeEscaped escaped3(boolean cond1, boolean cond2) {
+        MergeEscaped obj = new MergeEscaped();
+        if (cond1) {
+            obj.a = 10; // V
+        } else {
+            if (cond2) {
+                blackhole(obj); // E1
+                obj.a = 20;
+            } else {
+                blackhole(obj); // E2
+                obj.b = 30;
+            }
+            obj.c = 300;        // PHI(E1, E2),
+        }                       // PHI'(V, PHI)
+        return obj;
+    }
+
+    static void check_result3(boolean cond1, boolean cond2, int sum) {
+        boolean okay = true;
+
+        if (cond1) {
+            okay = sum == 110;
+        } else {
+            if (cond2) {
+                okay = sum == 320;
+            } else {
+                okay = sum == 330;
+            }
+        }
+
+        if (!okay) {
+            throw new RuntimeException("wrong answer: " + cond1 + " " +  cond2 + " " + sum);
+        }
+    }
+
+    private static void test4(MergeEscaped obj, int cond) {
+        if (cond == 0) {
+            obj.a = 1;
+        } else if (cond == 1) {
+            obj.a = 2;
+        } else {
+            blackhole(obj);
+        }
+    }
+
+    public static MergeEscaped escaped4(boolean cond1, boolean cond2) {
+        MergeEscaped obj = new MergeEscaped();
+
+        int cond = 0;
+        if (cond1) {
+            cond = 2;  // cond1 << 1
+        }
+        if (cond2) {
+            cond = cond + 1;
+        }
+
+        test4(obj,  cond);
+        blackhole(obj);
+        return obj;
+    }
+
+    public static void main(String[] args) {
+        long iterations = 0;
+
+        try {
+            while (true) {
+                boolean cond = 0 == (iterations & 0xf);
+                boolean cond2 = 7 == (iterations& 0xf);
+
+                int sum = MergeEscaped.escaped(cond).sum();
+                check_result(cond, sum);
+
+                sum = MergeEscaped.escaped2(cond).sum();
+                check_result(cond, sum);
+
+                sum = MergeEscaped.escaped3(cond, cond2).sum();
+                check_result3(cond, cond2, sum);
+
+                escaped4(cond, cond2);
+                iterations++;
+            }
+        } finally {
+            System.err.println("Epsilon Test: " + iterations);
+        }
+    }
+}
diff a/PEA/MergeIfElseParanoid.java b/PEA/MergeIfElseParanoid.java
--- /dev/null
+++ b/PEA/MergeIfElseParanoid.java
@@ -0,0 +1,29 @@
+class MergeIfElseParanoid {
+    static class Node {
+       public Node _next;
+       public Node() {}
+    }
+
+    static Object _global;
+    static Object _global2;
+
+    public static void test(boolean cond) {
+        Object foo = new Object();
+        if (cond) {
+            _global = foo;
+        }
+        // When `cond == true`, we materialize foo a second time. We cannot
+        // eliminate the original allocation because we need for when
+        // `cond == false`.
+        //
+        // If we have passive materialization, we would materialize foo in the
+        // predecessor, and the original object would be dead.
+        _global2 = foo;
+    }
+
+    public static void main(String[] args ) {
+        for (int i=0; i < 100_000; ++i) {
+            test(0 == (i& 0xf));
+        }
+    }
+}
diff a/PEA/MergeLoop.java b/PEA/MergeLoop.java
--- /dev/null
+++ b/PEA/MergeLoop.java
@@ -0,0 +1,29 @@
+class MergeLoop {
+    static void blackhole(Object obj) {}
+    static void test(int val) {
+        Object obj1 = new Object();
+        Object obj2 = new Object();
+
+        for (int i = 0; i < val; ++i)  {
+            // to prevent phi from being elided, we need to ensure obj is not loop invariant!
+            // the following shuffle just confuses ciTypeFlow
+
+            // swap
+            Object t = obj1;
+            obj1 = obj2;
+            obj2 = t;
+            // swap again
+            t = obj1;
+            obj1 = obj2;
+            obj2 = t;
+
+            blackhole(obj1); // escape here.
+        }
+    }
+
+    public static void main(String[] args) {
+        for (int i = 0; i < 200_000; ++i) {
+            test(24);
+        }
+    }
+}
diff a/PEA/SimpleLoop.java b/PEA/SimpleLoop.java
--- /dev/null
+++ b/PEA/SimpleLoop.java
@@ -0,0 +1,43 @@
+class SimpleLoop {
+    Object[] elements;
+
+    class Iterator {
+        int idx = 0;
+
+        Object next() {
+            return elements[idx++];
+        }
+    }
+
+    Iterator iterator() {
+        return new Iterator();
+    }
+
+    // based on java.util.ImmutableCollections$SetN::toArray
+    Object[] toArray(int size) {
+        Object[] array = new Object[size];
+        Iterator it = iterator();
+        for (int i = 0; i < size; i++) { array[i] = it.next(); }
+        return array;
+    }
+
+    public static void main(String[] args) {
+        SimpleLoop kase = new SimpleLoop();
+
+        int sz = 1024;
+        kase.elements = new Object[sz];
+        for (int i=0; i< sz; ++i ) kase.elements[i] = new Object();
+
+        Object[] results = null;
+        for (int i = 0; i < 1_000_000; ++i) {
+            results = kase.toArray(24);
+        }
+
+        for (int i =0; i< 24; ++i) {
+            if (results[i] != kase.elements[i]) {
+                throw new RuntimeException("wong answser at " + i);
+            }
+        }
+    }
+
+}
diff a/PEA/Str.java b/PEA/Str.java
--- /dev/null
+++ b/PEA/Str.java
@@ -0,0 +1,14 @@
+class Str {
+    public String strBuilderRepro() {
+        return new StringBuilder(10).toString();
+    }
+
+    public static void main(String[] args)  {
+        Str s = new Str();
+        String foo = "";
+        for (int i = 0; i < 20000; ++i) {
+            foo = s.strBuilderRepro();
+        }
+        System.out.println(foo);
+    }
+}
diff a/PEA/UnsafeStoreReference.java b/PEA/UnsafeStoreReference.java
--- /dev/null
+++ b/PEA/UnsafeStoreReference.java
@@ -0,0 +1,31 @@
+import jdk.internal.misc.Unsafe;
+
+class UnsafeStoreReference {
+  private static final Unsafe U = Unsafe.getUnsafe();
+  private transient volatile Node head;
+  private static final long HEAD
+              = U.objectFieldOffset(UnsafeStoreReference.class, "head");
+  private transient volatile Node tail;
+
+  static class Node {}
+
+  // inspired from java.util.concurrent.locks.AbstractQueuedSynchronizer::tryInitializeHead
+  void test() {
+    Node h = new Node();
+
+    if (U.compareAndSetReference(this, HEAD, null, h))
+        tail = h;
+  }
+
+  public static void main(String[] args) {
+    UnsafeStoreReference kase = new UnsafeStoreReference();
+
+    for (int i = 0; i < 100_000; i++) {
+        kase.test();
+        if (kase.head != kase.tail) {
+            throw new RuntimeException("wrong result");
+        }
+        kase.head = kase.tail = null;
+    }
+  }
+}
diff a/PEA/WrongBCIAfterMotion.java b/PEA/WrongBCIAfterMotion.java
--- /dev/null
+++ b/PEA/WrongBCIAfterMotion.java
@@ -0,0 +1,39 @@
+class WrongBCIAfterMotion {
+    static int counter;
+
+    private int id;
+
+    public WrongBCIAfterMotion(int id) {
+        this.id = id;
+    }
+
+    static void blackhole(Object obj) {
+    }
+
+    public static void main(String[] args) {
+        final int n = 300_000;
+
+        for (int i =0; i<n;++i) {
+        /*
+         * PEA moves AllocateNode from 8 to 24. however, there's memory side-effect in between.
+         * PEA is using the JVMState including bci=8.
+         * This is wrong under -XX:-UseTLAB and -XX:OptimizeALot.
+         *
+       8: new           #8                  // class WrongBCIAfterMotion
+      11: dup
+      12: getstatic     #14                 // Field counter:I
+      15: dup
+      16: iconst_1
+      17: iadd
+      18: putstatic     #14                 // Field counter:I
+      21: invokespecial #17                 // Method "<init>":(I)V
+      24: invokestatic  #20                 // Method blackhole:(Ljava/lang/Object;)V
+         */
+            blackhole(new WrongBCIAfterMotion(counter++));
+        }
+
+        if (counter != n) {
+            throw new RuntimeException("Wrong result");
+        }
+    }
+}
diff a/PEA/auto.rb b/PEA/auto.rb
--- /dev/null
+++ b/PEA/auto.rb
@@ -0,0 +1,41 @@
+#!/usr/bin/env ruby
+
+PROBLEM_LIST = ["run_composite.sh", "run_merge_if_else_paranoid.sh", "run_wrong_bci_after_motion.sh"]
+
+if $0 == __FILE__
+  puts  "using #{%x|which java|}"
+  java_version = `java --version`
+
+  jvm_options ="-Xlog:gc -XX:+DoPartialEscapeAnalysis"
+  if java_version =~ /\(.*debug build/
+    jvm_options << " -XX:+PrintEscapeAnalysis -XX:+PrintEliminateAllocations -XX:+PrintOptoStatistics"
+  end
+  puts jvm_options
+
+  failed = []
+  Dir.glob("run*.sh").each do |t|
+    print "[#{t}]"
+
+    ret = system("sh ./#{t} #{jvm_options} > #{t}.log")
+    if ret == nil then
+      puts "\t fail to execute."
+    else
+      # exitcode == 3 is out of memory. acceptable.
+      if ret or $?.exitstatus == 3 then
+        puts "\tpassed."
+      elsif  PROBLEM_LIST.include? t then
+        puts "\tis a known issue."
+      else
+        puts "\tfailed!"
+        failed.push("[#{t}]")
+      end
+    end
+  end
+
+  if failed.length() > 0
+    puts "failed #{failed.length} tests:"
+    failed.each { |test| puts "\t#{test}" }
+    exit(1)
+  end
+  puts "passed all tests!"
+end
diff a/PEA/run1.sh b/PEA/run1.sh
--- /dev/null
+++ b/PEA/run1.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/bash
+java -Xms32M -Xmx32M -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:-UseOnStackReplacement -XX:+UseTLAB -XX:CompileCommand='compileOnly,Example1.test*' $* Example1
diff a/PEA/run1_ivanov.sh b/PEA/run1_ivanov.sh
--- /dev/null
+++ b/PEA/run1_ivanov.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/bash
+java -Xcomp -Xms32M -Xmx32M -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:-UseOnStackReplacement -XX:+UseTLAB -XX:CompileOnly='Example1_ivanov.ivanov' -XX:CompileCommand=dontinline,Example1_ivanov.blackhole $* Example1_ivanov
diff a/PEA/run2.sh b/PEA/run2.sh
--- /dev/null
+++ b/PEA/run2.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/bash
+java -Xcomp -Xms32M -Xmx32M -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:-UseOnStackReplacement -XX:-UseTLAB -XX:CompileOnly='Example2.foo' -XX:CompileCommand=dontinline,Example2.blackhole $* Example2
diff a/PEA/run2_1.sh b/PEA/run2_1.sh
--- /dev/null
+++ b/PEA/run2_1.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/bash
+java -Xms32M -Xmx32M -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:-UseOnStackReplacement -XX:+UseTLAB -XX:CompileCommand='compileonly,Example2_1::foo*' -XX:CompileCommand=dontinline,Example2.blackhole $* Example2_1
+java -Xcomp -Xms32M -Xmx32M -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:-UseOnStackReplacement -XX:+UseTLAB -XX:CompileCommand='compileonly,Example2_1::foo*' -XX:CompileCommand=dontinline,Example2.blackhole $* Example2_1
diff a/PEA/run2_merykitty.sh b/PEA/run2_merykitty.sh
--- /dev/null
+++ b/PEA/run2_merykitty.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/bash
+java -ea -Xcomp -Xms32M -Xmx32M -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:-UseOnStackReplacement -XX:-UseTLAB -XX:CompileOnly='Example2_merykitty.foo' -XX:CompileCommand=dontinline,Example2_merykitty.blackhole $* Example2_merykitty
diff a/PEA/run2b.sh b/PEA/run2b.sh
--- /dev/null
+++ b/PEA/run2b.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/bash
+java -Xms32M -Xmx32M -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:-UseOnStackReplacement -XX:+UseTLAB -XX:CompileCommand='compileonly,Example2b::foo*' $* Example2b
diff a/PEA/run3.sh b/PEA/run3.sh
--- /dev/null
+++ b/PEA/run3.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/bash
+java -XX:+PrintCompilation -XX:CompileCommand='CompileOnly,Example3::confined_close' -XX:CompileCommand=quiet -XX:-UseOnStackReplacement -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -Xms32M -Xmx32M -XX:-TieredCompilation  $* Example3
diff a/PEA/run3_1.sh b/PEA/run3_1.sh
--- /dev/null
+++ b/PEA/run3_1.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/bash
+java -Xms32M -Xmx32M -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:-UseOnStackReplacement -XX:+UseTLAB -XX:CompileOnly='Example3_1.foo' -XX:CompileCommand=dontinline,Example1.blackhole $* Example3_1
diff a/PEA/run3_2.sh b/PEA/run3_2.sh
--- /dev/null
+++ b/PEA/run3_2.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/bash
+java -Xms8M -Xmx8M -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:-UseOnStackReplacement -XX:+UseTLAB -XX:-TieredCompilation -XX:CompileOnly='Example3_2.foo' $* Example3_2
diff a/PEA/run_EscapeInInitializer.sh b/PEA/run_EscapeInInitializer.sh
--- /dev/null
+++ b/PEA/run_EscapeInInitializer.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+java -XX:+UnlockExperimentalVMOptions  -XX:-TieredCompilation -XX:CompileCommand='compileonly,EscapeInInitializer::*' -XX:CompileCommand='inline,EscapeInInitializer::the_answer' -XX:-UseTLAB -XX:-UseOnStackReplacement -Xbatch $* EscapeInInitializer
diff a/PEA/run_badgraph_volatile.sh b/PEA/run_badgraph_volatile.sh
--- /dev/null
+++ b/PEA/run_badgraph_volatile.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/bash
+java -Xms8M -Xmx8M -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:-UseOnStackReplacement -XX:-UseTLAB -XX:-TieredCompilation -XX:CompileOnly='BadGraphVolatile.foo' -XX:CompileCommand='dontinline,BadGraphVolatile::blackhole' -Xbatch $* BadGraphVolatile
diff a/PEA/run_composite.sh b/PEA/run_composite.sh
--- /dev/null
+++ b/PEA/run_composite.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+java -XX:CompileCommand=compileonly,CompositeObjects::main -XX:CompileCommand='dontinline,*::blackhole' -XX:-TieredCompilation -Xbatch -XX:+UnlockExperimentalVMOptions -XX:+PEAParanoid $* CompositeObjects
diff a/PEA/run_crazy_exception.sh b/PEA/run_crazy_exception.sh
--- /dev/null
+++ b/PEA/run_crazy_exception.sh
@@ -0,0 +1,1 @@
+java -Xms16M -Xmx16M -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:-TieredCompilation -XX:+UseEpsilonGC -XX:-UseOnStackReplacement -XX:-UseTLAB -XX:CompileCommand=dontinline,CrazyException.blackhole -XX:CompileCommand=compileonly,CrazyException.foo  -XX:CompileCommand=quiet $* CrazyException 
diff a/PEA/run_exception.sh b/PEA/run_exception.sh
--- /dev/null
+++ b/PEA/run_exception.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/bash
+java -Xms16M -Xmx16M -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:-TieredCompilation -XX:+UseEpsilonGC -XX:-UseOnStackReplacement -XX:CompileCommand=dontinline,CrazyException.blackhole -XX:CompileCommand=compileonly,CrazyException.foo -XX:CompileCommand=dontinline,CrazyException.close $* CrazyException
diff a/PEA/run_get_field_is_alias.sh b/PEA/run_get_field_is_alias.sh
--- /dev/null
+++ b/PEA/run_get_field_is_alias.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/bash
+java -XX:+UnlockExperimentalVMOptions -Xbatch -XX:CompileCommand=compileOnly,GetFieldIsAlias::main -XX:CompileCommand=quiet $* GetFieldIsAlias
diff a/PEA/run_load_self_into_field.sh b/PEA/run_load_self_into_field.sh
--- /dev/null
+++ b/PEA/run_load_self_into_field.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/bash
+java -ea -Xcomp -Xms32M -Xmx32M -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:-UseOnStackReplacement -XX:+UseTLAB -XX:CompileCommand='compileOnly,LoadSelfIntoField.test*' $* LoadSelfIntoField
diff a/PEA/run_mat_in_monitor.sh b/PEA/run_mat_in_monitor.sh
--- /dev/null
+++ b/PEA/run_mat_in_monitor.sh
@@ -0,0 +1,4 @@
+#!/usr/bin/bash
+set -x
+java -Xms32M -Xmx32M -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:-UseOnStackReplacement -XX:-TieredCompilation -XX:-UseTLAB -XX:CompileCommand='compileOnly,MatInMonitor.test*' $* MatInMonitor
+java -Xms32M -Xmx32M -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:-UseOnStackReplacement -XX:-TieredCompilation -XX:-UseTLAB -XX:CompileCommand='compileOnly,MatInMonitor.test*' -XX:-DoEscapeAnalysis $* MatInMonitor
diff a/PEA/run_materialize_member.sh b/PEA/run_materialize_member.sh
--- /dev/null
+++ b/PEA/run_materialize_member.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+java  -XX:+UnlockExperimentalVMOptions -XX:CompileCommand=dontinline,java.util.Collections::addAll -XX:-UseOnStackReplacement -XX:CompileCommand=compileonly,MaterializeMember::test -Xbatch $* MaterializeMember
diff a/PEA/run_merge_all_virts.sh b/PEA/run_merge_all_virts.sh
--- /dev/null
+++ b/PEA/run_merge_all_virts.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/bash
+java -Xms32M -Xmx32M -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:-TieredCompilation -XX:-UseOnStackReplacement -XX:-UseTLAB -XX:CompileCommand=dontinline,MergeAllVirts::blackhole -XX:CompileCommand='compileonly,MergeAllVirts::*' -Xbatch $* MergeAllVirts
diff a/PEA/run_merge_escaped.sh b/PEA/run_merge_escaped.sh
--- /dev/null
+++ b/PEA/run_merge_escaped.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/bash
+java -Xms32M -Xmx32M -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:-TieredCompilation -XX:-UseOnStackReplacement -XX:-UseTLAB -XX:CompileCommand=dontinline,MergeEscaped::blackhole -XX:CompileCommand='compileonly,MergeEscaped::*' $* MergeEscaped
diff a/PEA/run_merge_if_else_paranoid.sh b/PEA/run_merge_if_else_paranoid.sh
--- /dev/null
+++ b/PEA/run_merge_if_else_paranoid.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+java -XX:CompileCommand=compileonly,MergeIfElseParanoid::test -XX:-TieredCompilation -Xbatch -XX:+UnlockExperimentalVMOptions -XX:+PEAParanoid $* MergeIfElseParanoid
diff a/PEA/run_merge_loops.sh b/PEA/run_merge_loops.sh
--- /dev/null
+++ b/PEA/run_merge_loops.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/bash
+java -XX:+UnlockExperimentalVMOptions  -XX:-TieredCompilation -XX:-UseOnStackReplacement -XX:+UseTLAB -XX:CompileCommand=dontinline,MergeLoop::blackhole -XX:CompileCommand='compileonly,MergeLoop::test' -Xbatch $* MergeLoop
diff a/PEA/run_simple_loop.sh b/PEA/run_simple_loop.sh
--- /dev/null
+++ b/PEA/run_simple_loop.sh
@@ -0,0 +1,1 @@
+java -XX:-TieredCompilation -XX:-UseOnStackReplacement -XX:CompileCommand=compileonly,SimpleLoop::toArray -XX:+UnlockExperimentalVMOptions -XX:CompileCommand='dontinline,SimpleLoop$Iterator::next' $* SimpleLoop
diff a/PEA/run_str.sh b/PEA/run_str.sh
--- /dev/null
+++ b/PEA/run_str.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/bash
+java -Xcomp -Xms16M -Xmx16M -XX:+UnlockExperimentalVMOptions -XX:+AlwaysPreTouch -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:-UseOnStackReplacement -XX:CompileOnly='Str.strBuilderRepro' -XX:-Inline $* Str
diff a/PEA/run_streams.sh b/PEA/run_streams.sh
--- /dev/null
+++ b/PEA/run_streams.sh
@@ -0,0 +1,1 @@
+java -Xbatch -XX:-TieredCompilation -XX:+UnlockExperimentalVMOptions $* CountUppercase This year I would like to run ALL languages in one VM.
diff a/PEA/run_unsafe_stores.sh b/PEA/run_unsafe_stores.sh
--- /dev/null
+++ b/PEA/run_unsafe_stores.sh
@@ -0,0 +1,1 @@
+java --add-exports java.base/jdk.internal.misc=ALL-UNNAMED -XX:+UnlockExperimentalVMOptions -XX:CompileCommand=compileonly,UnsafeStoreReference::test $*  UnsafeStoreReference
diff a/PEA/run_wrong_bci_after_motion.sh b/PEA/run_wrong_bci_after_motion.sh
--- /dev/null
+++ b/PEA/run_wrong_bci_after_motion.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+java -XX:+UnlockExperimentalVMOptions -XX:-TieredCompilation -XX:+DeoptimizeALot -XX:CompileCommand=dontinline,WrongBCIAfterMotion::blackhole -XX:-UseTLAB $* WrongBCIAfterMotion
diff a/src/hotspot/share/compiler/methodLiveness.hpp b/src/hotspot/share/compiler/methodLiveness.hpp
--- a/src/hotspot/share/compiler/methodLiveness.hpp
+++ b/src/hotspot/share/compiler/methodLiveness.hpp
@@ -45,10 +45,17 @@
     , _is_valid(false)
   {}
 
   void set_is_valid() { _is_valid = true; }
   bool is_valid() const { return _is_valid; }
+
+#ifdef ASSERT
+  bool at(idx_t index) const {
+    assert(is_valid(), "reading invalid");
+    return ResourceBitMap::at(index);
+  }
+#endif
 };
 
 class MethodLiveness : public ArenaObj {
  public:
   // The BasicBlock class is used to represent a basic block in the
diff a/src/hotspot/share/libadt/vectset.cpp b/src/hotspot/share/libadt/vectset.cpp
--- a/src/hotspot/share/libadt/vectset.cpp
+++ b/src/hotspot/share/libadt/vectset.cpp
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.
@@ -38,26 +38,24 @@
 }
 
 void VectorSet::init(Arena* arena) {
   _size = 2;
   _data = NEW_ARENA_ARRAY(arena, uint32_t, 2);
-  _data_size = 2;
   _set_arena = arena;
   _data[0] = 0;
   _data[1] = 0;
 }
 
 // Expand the existing set to a bigger size
 void VectorSet::grow(uint new_word_capacity) {
   assert(new_word_capacity < (1U << 30), "");
   uint x = next_power_of_2(new_word_capacity);
-  if (x > _data_size) {
+  if (x > _size) {
     _data = REALLOC_ARENA_ARRAY(_set_arena, uint32_t, _data, _size, x);
-    _data_size = x;
+    Copy::zero_to_bytes(_data + _size, (x - _size) * sizeof(uint32_t));
+    _size = x;
   }
-  Copy::zero_to_bytes(_data + _size, (x - _size) * sizeof(uint32_t));
-  _size = x;
 }
 
 // Insert a member into an existing Set.
 void VectorSet::insert(uint elem) {
   uint32_t word = elem >> word_bits;
@@ -75,5 +73,44 @@
       return false;
     }
   }
   return true;
 }
+
+#ifndef PRODUCT
+void VectorSet::print_on(outputStream* st) const {
+  st->print("VectorSet(" PTR_FORMAT ")", p2i(this));
+  if (is_empty()) {
+    st->print_cr(" empty");
+    return;
+  }
+
+  bool first = false;
+  for (uint i = 0; i < (_size << word_bits); ++i) {
+    if (test(i)) {
+      if (!first) {
+        st->print(" [%u", i);
+        first = true;
+      } else {
+        st->print(",%u", i);
+      }
+    }
+  }
+  st->print_cr("]");
+}
+#endif
+
+VectorSet intersect(const VectorSet& lhs, const VectorSet& rhs) {
+  VectorSet result;
+  uint min;
+  if (lhs._size > rhs._size) {
+    result.grow(lhs._size);
+    min = rhs._size;
+  } else {
+    result.grow(rhs._size);
+    min = lhs._size;
+  }
+  for (uint i = 0; i < min; ++i) {
+    result._data[i] = lhs._data[i] & rhs._data[i];
+  }
+  return result;
+}
diff a/src/hotspot/share/libadt/vectset.hpp b/src/hotspot/share/libadt/vectset.hpp
--- a/src/hotspot/share/libadt/vectset.hpp
+++ b/src/hotspot/share/libadt/vectset.hpp
@@ -33,20 +33,21 @@
 // These sets can grow or shrink, based on the initial size and the largest
 // element currently in them.
 
 //------------------------------VectorSet--------------------------------------
 class VectorSet : public AnyObj {
+  friend VectorSet intersect(const VectorSet&, const VectorSet&);
+
 private:
 
   static const uint word_bits = 5;
   static const uint bit_mask  = 31;
 
   // Used 32-bit words
   uint       _size;
-  uint32_t*  _data;
   // Allocated words
-  uint       _data_size;
+  uint32_t*  _data;
   Arena*     _set_arena;
 
   void init(Arena* arena);
   // Grow vector to required word capacity
   void grow(uint new_word_capacity);
@@ -113,8 +114,13 @@
       grow(word);
     }
     uint32_t mask = 1U << (elem & bit_mask);
     _data[word] |= mask;
   }
+
+#ifndef PRODUCT
+  void print_on(outputStream* st) const override;
+#endif // PRODUCT
 };
 
+VectorSet intersect(const VectorSet& lhs, const VectorSet& rhs);
 #endif // SHARE_LIBADT_VECTSET_HPP
diff a/src/hotspot/share/opto/c2_globals.hpp b/src/hotspot/share/opto/c2_globals.hpp
--- a/src/hotspot/share/opto/c2_globals.hpp
+++ b/src/hotspot/share/opto/c2_globals.hpp
@@ -491,10 +491,33 @@
           "Exit or throw assert in EA when it reaches time limit")          \
                                                                             \
   notproduct(bool, PrintEscapeAnalysis, false,                              \
           "Print the results of escape analysis")                           \
                                                                             \
+  product(bool, DoPartialEscapeAnalysis, true, EXPERIMENTAL,                \
+          "Perform partial escape analysis")                                \
+                                                                            \
+  product(ccstr, PEAMethodOnly, nullptr, EXPERIMENTAL,                      \
+          "perform PEA for only matched methods")                           \
+                                                                            \
+  product(bool, PEAParanoid, false, EXPERIMENTAL,                           \
+          "Abort when C2 expands an object which has been materialized."    \
+          "Duplicated allocation is detected caused by PEA.")               \
+                                                                            \
+  notproduct(bool, PEAVerbose, false,                                       \
+          "trace PEA in verbose")                                           \
+                                                                            \
+  notproduct(intx, PEA_debug_idx, 0,                                        \
+          "only select ObjID in PEA when debug_idx > 0;"                    \
+          "skip ObjID if debug_idx < 0")                                    \
+                                                                            \
+  notproduct(intx, PEA_debug_start, 0,                                      \
+          "only start performing PEA after _debug_start")                   \
+                                                                            \
+  notproduct(intx, PEA_debug_stop, INT_MAX,                                 \
+          "stop performing PEA after (excluded) _debug_stop")               \
+                                                                            \
   product(bool, EliminateAllocations, true,                                 \
           "Use escape analysis to eliminate allocations")                   \
                                                                             \
   notproduct(bool, PrintEliminateAllocations, false,                        \
           "Print out when allocations are eliminated")                      \
diff a/src/hotspot/share/opto/callGenerator.cpp b/src/hotspot/share/opto/callGenerator.cpp
--- a/src/hotspot/share/opto/callGenerator.cpp
+++ b/src/hotspot/share/opto/callGenerator.cpp
@@ -642,10 +642,14 @@
     kit.replace_call(call, C->top(), true);
   } else {
     // Make a clone of the JVMState that appropriate to use for driving a parse
     JVMState* old_jvms = call->jvms();
     JVMState* jvms = old_jvms->clone_shallow(C);
+
+    // Clear the allocation state. We assume all inputs are materialized.
+    jvms->alloc_state().clear();
+
     uint size = call->req();
     SafePointNode* map = new SafePointNode(size, jvms);
     for (uint i1 = 0; i1 < size; i1++) {
       map->init_req(i1, call->in(i1));
     }
@@ -809,11 +813,11 @@
   return new LateInlineVectorReboxingCallGenerator(method, inline_cg);
 }
 
 //------------------------PredictedCallGenerator------------------------------
 // Internal class which handles all out-of-line calls checking receiver type.
-class PredictedCallGenerator : public CallGenerator {
+class PredictedCallGenerator : public InlineCallGenerator {
   ciKlass*       _predicted_receiver;
   CallGenerator* _if_missed;
   CallGenerator* _if_hit;
   float          _hit_prob;
   bool           _exact_check;
@@ -821,11 +825,11 @@
 public:
   PredictedCallGenerator(ciKlass* predicted_receiver,
                          CallGenerator* if_missed,
                          CallGenerator* if_hit, bool exact_check,
                          float hit_prob)
-    : CallGenerator(if_missed->method())
+    : InlineCallGenerator(if_missed->method())
   {
     // The call profile data may predict the hit_prob as extreme as 0 or 1.
     // Remove the extremes values from the range.
     if (hit_prob > PROB_MAX)   hit_prob = PROB_MAX;
     if (hit_prob < PROB_MIN)   hit_prob = PROB_MIN;
@@ -914,11 +918,10 @@
     return kit.transfer_exceptions_into_jvms();
   }
 
   // Fall through if the instance matches the desired type.
   kit.replace_in_map(receiver, casted_receiver);
-
   // Make the hot call:
   JVMState* new_jvms = _if_hit->generate(kit.sync_jvms());
   if (new_jvms == nullptr) {
     // Inline failed, so make a direct call.
     assert(_if_hit->is_inline(), "must have been a failed inline");
@@ -943,10 +946,13 @@
   // There are 2 branches and the replaced nodes are only valid on
   // one: restore the replaced nodes to what they were before the
   // branch.
   kit.map()->set_replaced_nodes(replaced_nodes);
 
+  PEAState& slow_as = slow_jvms->alloc_state();
+  PEAState& as = new_jvms->alloc_state();
+  AllocationStateMerger as_merger(as);
   // Finish the diamond.
   kit.C->set_has_split_ifs(true); // Has chance for split-if optimization
   RegionNode* region = new RegionNode(3);
   region->init_req(1, kit.control());
   region->init_req(2, slow_map->control());
@@ -976,12 +982,19 @@
     if (m != n) {
       const Type* t = gvn.type(m)->meet_speculative(gvn.type(n));
       Node* phi = PhiNode::make(region, m, t);
       phi->set_req(2, n);
       kit.map()->set_req(i, gvn.transform(phi));
+      if (DoPartialEscapeAnalysis) {
+        as_merger.merge_at_phi_creation(kit.PEA(), slow_as, phi->as_Phi(), m, n);
+      }
     }
   }
+
+  if (DoPartialEscapeAnalysis) {
+    as_merger.merge(slow_as, &kit, region, 2);
+  }
   return kit.transfer_exceptions_into_jvms();
 }
 
 
 CallGenerator* CallGenerator::for_method_handle_call(JVMState* jvms, ciMethod* caller, ciMethod* callee, bool allow_inline) {
@@ -1154,25 +1167,24 @@
   return nullptr;
 }
 
 //------------------------PredicatedIntrinsicGenerator------------------------------
 // Internal class which handles all predicated Intrinsic calls.
-class PredicatedIntrinsicGenerator : public CallGenerator {
+class PredicatedIntrinsicGenerator : public InlineCallGenerator {
   CallGenerator* _intrinsic;
   CallGenerator* _cg;
 
 public:
   PredicatedIntrinsicGenerator(CallGenerator* intrinsic,
                                CallGenerator* cg)
-    : CallGenerator(cg->method())
+    : InlineCallGenerator(cg->method())
   {
     _intrinsic = intrinsic;
     _cg        = cg;
   }
 
   virtual bool      is_virtual()   const    { return true; }
-  virtual bool      is_inline()    const    { return true; }
   virtual bool      is_intrinsic() const    { return true; }
 
   virtual JVMState* generate(JVMState* jvms);
 };
 
@@ -1241,10 +1253,11 @@
     assert(old_exc == new_map->next_exception(), "generate_predicate should not add exceptions");
 #endif
     if (!kit.stopped()) {
       PreserveJVMState pjvms(&kit);
       // Generate intrinsic code:
+      assert(_intrinsic->is_inline(), "LibraryIntrinsic");
       JVMState* new_jvms = _intrinsic->generate(kit.sync_jvms());
       if (new_jvms == nullptr) {
         // Intrinsic failed, use normal compilation path for this predicate.
         slow_region->add_req(kit.control());
       } else {
diff a/src/hotspot/share/opto/callGenerator.hpp b/src/hotspot/share/opto/callGenerator.hpp
--- a/src/hotspot/share/opto/callGenerator.hpp
+++ b/src/hotspot/share/opto/callGenerator.hpp
@@ -26,10 +26,11 @@
 #define SHARE_OPTO_CALLGENERATOR_HPP
 
 #include "compiler/compileBroker.hpp"
 #include "opto/callnode.hpp"
 #include "opto/compile.hpp"
+#include "opto/partialEscape.hpp"
 #include "opto/type.hpp"
 #include "runtime/deoptimization.hpp"
 
 //---------------------------CallGenerator-------------------------------------
 // The subclasses of this class handle generation of ideal nodes for
diff a/src/hotspot/share/opto/callnode.cpp b/src/hotspot/share/opto/callnode.cpp
--- a/src/hotspot/share/opto/callnode.cpp
+++ b/src/hotspot/share/opto/callnode.cpp
@@ -276,10 +276,11 @@
   _monoff = _stkoff + _method->max_stack();
   _scloff = _monoff;
   _endoff = _monoff;
   _sp = 0;
 }
+
 JVMState::JVMState(int stack_size) :
   _method(nullptr) {
   _bci = InvocationEntryBci;
   _reexecute = Reexecute_Undefined;
   debug_only(_map = (SafePointNode*)-1);
@@ -612,10 +613,11 @@
   n->set_monoff(_monoff);
   n->set_scloff(_scloff);
   n->set_endoff(_endoff);
   n->set_sp(_sp);
   n->set_map(_map);
+  n->_alloc_state = _alloc_state;
   return n;
 }
 
 //---------------------------clone_deep----------------------------------------
 JVMState* JVMState::clone_deep(Compile* C) const {
@@ -1574,11 +1576,11 @@
 uint AllocateNode::size_of() const { return sizeof(*this); }
 
 AllocateNode::AllocateNode(Compile* C, const TypeFunc *atype,
                            Node *ctrl, Node *mem, Node *abio,
                            Node *size, Node *klass_node, Node *initial_test)
-  : CallNode(atype, nullptr, TypeRawPtr::BOTTOM)
+  : CallNode(atype, nullptr, TypeRawPtr::BOTTOM), _materialized(0)
 {
   init_class_id(Class_Allocate);
   init_flags(Flag_is_macro);
   _is_scalar_replaceable = false;
   _is_non_escaping = false;
@@ -1619,10 +1621,20 @@
   // For now only enable fast locking for non-array types
   mark_node = phase->MakeConX(markWord::prototype().value());
   return mark_node;
 }
 
+// This is a precise notnull oop of the klass.
+// (Actually, it need not be precise if this is a reflective allocation.)
+// It's what we cast the result to.
+const TypeOopPtr* AllocateNode::oop_type(const PhaseValues& phase) const {
+  Node* klass_node = in(KlassNode);
+  const TypeKlassPtr* tklass = phase.type(klass_node)->isa_klassptr();
+  if (!tklass) tklass = TypeInstKlassPtr::OBJECT;
+  return tklass->as_instance_type();
+}
+
 // Retrieve the length from the AllocateArrayNode. Narrow the type with a
 // CastII, if appropriate.  If we are not allowed to create new nodes, and
 // a CastII is appropriate, return null.
 Node *AllocateArrayNode::make_ideal_length(const TypeOopPtr* oop_type, PhaseValues* phase, bool allow_new_nodes) {
   Node *length = in(AllocateNode::ALength);
diff a/src/hotspot/share/opto/callnode.hpp b/src/hotspot/share/opto/callnode.hpp
--- a/src/hotspot/share/opto/callnode.hpp
+++ b/src/hotspot/share/opto/callnode.hpp
@@ -27,10 +27,11 @@
 
 #include "opto/connode.hpp"
 #include "opto/mulnode.hpp"
 #include "opto/multnode.hpp"
 #include "opto/opcodes.hpp"
+#include "opto/partialEscape.hpp"
 #include "opto/phaseX.hpp"
 #include "opto/replacednodes.hpp"
 #include "opto/type.hpp"
 #include "utilities/growableArray.hpp"
 
@@ -206,10 +207,12 @@
   uint              _sp;        // Java Expression Stack Pointer for this state
   int               _bci;       // Byte Code Index of this JVM point
   ReexecuteState    _reexecute; // Whether this bytecode need to be re-executed
   ciMethod*         _method;    // Method Pointer
   SafePointNode*    _map;       // Map node associated with this scope
+  PEAState          _alloc_state;
+
 public:
   friend class Compile;
   friend class PreserveReexecuteState;
 
   // Because JVMState objects live over the entire lifetime of the
@@ -299,10 +302,11 @@
   JVMState* clone_deep(Compile* C) const;    // recursively clones caller chain
   JVMState* clone_shallow(Compile* C) const; // retains uncloned caller
   void      set_map_deep(SafePointNode *map);// reset map for all callers
   void      adapt_position(int delta);       // Adapt offsets in in-array after adding an edge.
   int       interpreter_frame_size() const;
+  PEAState& alloc_state() { return _alloc_state; }
 
 #ifndef PRODUCT
   void      print_method_with_lineno(outputStream* st, bool show_name) const;
   void      format(PhaseRegAlloc *regalloc, const Node *n, outputStream* st) const;
   void      dump_spec(outputStream *st) const;
@@ -947,10 +951,13 @@
 //  the _is_io_use flag in the projection.)  This is needed when expanding the node in
 //  order to differentiate the uses of the projection on the normal control path from
 //  those on the exception return path.
 //
 class AllocateNode : public CallNode {
+private:
+  int _materialized; // materializaiton counts by PEA
+
 public:
   enum {
     // Output:
     RawAddress  = TypeFunc::Parms,    // the newly-allocated raw address
     // Inputs:
@@ -1050,13 +1057,23 @@
   // If object doesn't escape in <.init> method and there is memory barrier
   // inserted at exit of its <.init>, memory barrier for new is not necessary.
   // Inovke this method when MemBar at exit of initializer and post-dominate
   // allocation node.
   void compute_MemBar_redundancy(ciMethod* initializer);
-  bool is_allocation_MemBar_redundant() { return _is_allocation_MemBar_redundant; }
+  bool is_allocation_MemBar_redundant() const { return _is_allocation_MemBar_redundant; }
 
   Node* make_ideal_mark(PhaseGVN *phase, Node* obj, Node* control, Node* mem);
+
+  const TypeOopPtr* oop_type(const PhaseValues& phase) const;
+
+  void inc_materialized() {
+    _materialized++;
+  }
+
+  int materialized_cnt() const {
+    return _materialized;
+  }
 };
 
 //------------------------------AllocateArray---------------------------------
 //
 // High-level array allocation
diff a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp
--- a/src/hotspot/share/opto/compile.cpp
+++ b/src/hotspot/share/opto/compile.cpp
@@ -646,10 +646,11 @@
                   _template_assertion_predicate_opaqs (comp_arena(), 8, 0, nullptr),
                   _expensive_nodes   (comp_arena(), 8, 0, nullptr),
                   _for_post_loop_igvn(comp_arena(), 8, 0, nullptr),
                   _unstable_if_traps (comp_arena(), 8, 0, nullptr),
                   _coarsened_locks   (comp_arena(), 8, 0, nullptr),
+                  _pea(nullptr),
                   _congraph(nullptr),
                   NOT_PRODUCT(_igv_printer(nullptr) COMMA)
                   _unique(0),
                   _dead_node_count(0),
                   _dead_node_list(comp_arena()),
@@ -736,10 +737,14 @@
   _types = new (comp_arena()) Type_Array(comp_arena());
   _node_hash = new (comp_arena()) NodeHash(comp_arena(), estimated_size);
   PhaseGVN gvn;
   set_initial_gvn(&gvn);
 
+  if (DoPartialEscapeAnalysis) {
+    _pea = new (mtCompiler) PartialEscapeAnalysis(comp_arena());
+  }
+
   print_inlining_init();
   { // Scope for timing the parser
     TracePhase tp("parse", &timers[_t_parser]);
 
     // Put top into the hash table ASAP.
diff a/src/hotspot/share/opto/compile.hpp b/src/hotspot/share/opto/compile.hpp
--- a/src/hotspot/share/opto/compile.hpp
+++ b/src/hotspot/share/opto/compile.hpp
@@ -46,10 +46,11 @@
 #include "runtime/vmThread.hpp"
 #include "utilities/ticks.hpp"
 
 class AbstractLockNode;
 class AddPNode;
+class AllocateNode;
 class Block;
 class Bundle;
 class CallGenerator;
 class CallStaticJavaNode;
 class CloneMap;
@@ -69,10 +70,11 @@
 class Node_Notes;
 class NodeHash;
 class NodeCloneInfo;
 class OptoReg;
 class ParsePredicateNode;
+class PartialEscapeAnalysis;
 class PhaseCFG;
 class PhaseGVN;
 class PhaseIterGVN;
 class PhaseRegAlloc;
 class PhaseCCP;
@@ -96,10 +98,12 @@
 class nmethod;
 class Node_Stack;
 struct Final_Reshape_Counts;
 class VerifyMeetResult;
 
+typedef AllocateNode* ObjID;
+
 enum LoopOptsMode {
   LoopOptsDefault,
   LoopOptsNone,
   LoopOptsMaxUnroll,
   LoopOptsShenandoahExpand,
@@ -357,10 +361,11 @@
   int                   _loop_opts_cnt;         // loop opts round
   uint                  _stress_seed;           // Seed for stress testing
 
   // Compilation environment.
   Arena                 _comp_arena;            // Arena with lifetime equivalent to Compile
+  Arena*                _parser_arena;          // Arena with lifttime equivalent to the top-level parser.
   void*                 _barrier_set_state;     // Potential GC barrier state for Compile
   ciEnv*                _env;                   // CI interface
   DirectiveSet*         _directive;             // Compiler directive
   CompileLog*           _log;                   // from CompilerThread
   const char*           _failure_reason;        // for record_failure/failing pattern
@@ -371,10 +376,11 @@
   GrowableArray<Node*>  _template_assertion_predicate_opaqs; // List of Opaque4 nodes for Template Assertion Predicates.
   GrowableArray<Node*>  _expensive_nodes;       // List of nodes that are expensive to compute and that we'd better not let the GVN freely common
   GrowableArray<Node*>  _for_post_loop_igvn;    // List of nodes for IGVN after loop opts are over
   GrowableArray<UnstableIfTrap*> _unstable_if_traps;        // List of ifnodes after IGVN
   GrowableArray<Node_List*> _coarsened_locks;   // List of coarsened Lock and Unlock nodes
+  PartialEscapeAnalysis* _pea;
   ConnectionGraph*      _congraph;
 #ifndef PRODUCT
   IdealGraphPrinter*    _igv_printer;
   static IdealGraphPrinter* _debug_file_printer;
   static IdealGraphPrinter* _debug_network_printer;
@@ -779,10 +785,15 @@
   }
   void add_coarsened_locks(GrowableArray<AbstractLockNode*>& locks);
   void remove_coarsened_lock(Node* n);
   bool coarsened_locks_consistent();
 
+  PartialEscapeAnalysis* PEA() const {
+    assert(!DoPartialEscapeAnalysis || _pea != nullptr, "sanity check");
+    return _pea;
+  }
+
   bool       post_loop_opts_phase() { return _post_loop_opts_phase;  }
   void   set_post_loop_opts_phase() { _post_loop_opts_phase = true;  }
   void reset_post_loop_opts_phase() { _post_loop_opts_phase = false; }
 
   void record_for_post_loop_opts_igvn(Node* n);
@@ -805,10 +816,11 @@
   // Sort expensive nodes to locate similar expensive nodes
   void sort_expensive_nodes();
 
   // Compilation environment.
   Arena*      comp_arena()           { return &_comp_arena; }
+  Arena*      parser_arena()         { return comp_arena(); } // for the time being, just return comp_arena.
   ciEnv*      env() const            { return _env; }
   CompileLog* log() const            { return _log; }
   bool        failing() const        { return _env->failing() || _failure_reason != nullptr; }
   const char* failure_reason() const { return (_env->failing()) ? _env->failure_reason() : _failure_reason; }
   const CompilationFailureInfo* first_failure_details() const { return _first_failure_details; }
diff a/src/hotspot/share/opto/doCall.cpp b/src/hotspot/share/opto/doCall.cpp
--- a/src/hotspot/share/opto/doCall.cpp
+++ b/src/hotspot/share/opto/doCall.cpp
@@ -660,10 +660,38 @@
 #endif // not PRODUCT
 
   assert(jvms == this->jvms(), "still operating on the right JVMS");
   assert(jvms_in_sync(),       "jvms must carry full info into CG");
 
+  PEAState* caller_state = nullptr;
+  if (DoPartialEscapeAnalysis) {
+    // Besides outlined methods, we scan all intrinsics because C2 skips parsing bytecodes of them.
+    // Some intrinsics have side-effect of Java heap such as Unsafe.compareAndSetReference().
+    // We treat PredicatedIntrinsicGenerator as an ordinary intrinsic as well.
+    if (!cg->is_inline() || cg->is_intrinsic()) {
+      PEAState& state = jvms->alloc_state();
+      // Materialize all inputs to non-inline and late inline calls.
+      // TODO: we can support late inline calls if we can pass the right JVM
+      // state to the late inline call.
+      //
+      // TODO: Should we query BCEA to make an enger inlining decision here?
+      // Because callee require a concrete object pointer as an argument,
+      // we have nothing to do but matertialize it.
+      //
+      // BCEscapeAnalyzer* bcea = cg->method()->get_bcea();
+      uint nargs = cg->method()->arg_size();
+      for (uint i=0; i < nargs; ++i) {
+        Node* arg = argument(i);
+
+        if (state.as_virtual(PEA(), arg)) {
+          Node* objx = state.materialize(this, arg);
+          set_argument(i, objx);
+        }
+      }
+    }
+  }
+
   // save across call, for a subsequent cast_not_null.
   Node* receiver = has_receiver ? argument(0) : nullptr;
 
   // The extra CheckCastPPs for speculative types mess with PhaseStringOpts
   if (receiver != nullptr && !call_does_dispatch && !cg->is_string_late_inline()) {
diff a/src/hotspot/share/opto/graphKit.cpp b/src/hotspot/share/opto/graphKit.cpp
--- a/src/hotspot/share/opto/graphKit.cpp
+++ b/src/hotspot/share/opto/graphKit.cpp
@@ -1026,10 +1026,12 @@
     // Update the two tail pointers in parallel.
     out_jvms = out_jvms->caller();
     in_jvms  = in_jvms->caller();
   }
 
+  PEAState& as = youngest_jvms->alloc_state();
+  backfill_materialized(call, TypeFunc::Parms, call->req(), as);
   assert(debug_ptr == non_debug_edges, "debug info must fit exactly");
 
   // Test the correctness of JVMState::debug_xxx accessors:
   assert(call->jvms()->debug_start() == non_debug_edges, "");
   assert(call->jvms()->debug_end()   == call->req(), "");
@@ -3513,23 +3515,65 @@
   }
 #endif
 
   return flock;
 }
+// Clone LockNode for PEA materialization.
+// LockNode is a safepoint, so it's location sensitive. We can't just clone it.
+// We create it using the current JVMState and mark bytecode non-reexecute.
+//
+// It is worthy noting that we reuse BoxNode. It represents the slot on stack.
+// PhaseMacroExpand::mark_eliminated_box() can't eliminate BoxLockNode and its
+// associated AbstractLockNodes until share prove that the 'eliminated' BoxLockNode
+// is exclusive.
+void GraphKit::clone_shared_lock(Node* box, Node* obj) {
+  kill_dead_locals();
+
+  Node* mem = reset_memory();
+
+  FastLockNode * flock = _gvn.transform(new FastLockNode(0, obj, box) )->as_FastLock();
+  const TypeFunc *tf = LockNode::lock_type();
+  LockNode *lock = new LockNode(C, tf);
+
+  lock->set_req(TypeFunc::Control, control());
+  lock->set_req(TypeFunc::Memory , mem);
+  lock->set_req(TypeFunc::I_O    , top())     ;   // does no i/o
+  lock->set_req(TypeFunc::FramePtr, frameptr());
+  lock->set_req(TypeFunc::ReturnAdr, top());
 
+  lock->set_req(TypeFunc::Parms + 0, obj);
+  lock->set_req(TypeFunc::Parms + 1, box);
+  lock->set_req(TypeFunc::Parms + 2, flock);
+
+  // we can't reexecute current bc. it's not monitorenter.
+  jvms()->set_should_reexecute(false);
+  add_safepoint_edges(lock);
+
+  lock = _gvn.transform( lock )->as_Lock();
+
+  // lock has no side-effects, sets few values
+  set_predefined_output_for_runtime_call(lock, mem, TypeRawPtr::BOTTOM);
+
+  insert_mem_bar(Op_MemBarAcquireLock);
+
+  // Add this to the worklist so that the lock can be eliminated
+  record_for_igvn(lock);
+}
 
 //------------------------------shared_unlock----------------------------------
 // Emit unlocking code.
-void GraphKit::shared_unlock(Node* box, Node* obj) {
+void GraphKit::shared_unlock(Node* box, Node* obj, bool preserve_monitor) {
   // bci is either a monitorenter bc or InvocationEntryBci
   // %%% SynchronizationEntryBCI is redundant; use InvocationEntryBci in interfaces
   assert(SynchronizationEntryBCI == InvocationEntryBci, "");
 
   if( !GenerateSynchronizationCode )
     return;
   if (stopped()) {               // Dead monitor?
-    map()->pop_monitor();        // Kill monitor from debug info
+    if (!preserve_monitor) {
+      map()->pop_monitor();        // Kill monitor from debug info
+    }
     return;
   }
 
   // Memory barrier to avoid floating things down past the locked region
   insert_mem_bar(Op_MemBarReleaseLock);
@@ -3552,13 +3596,14 @@
 
   Node* mem = reset_memory();
 
   // unlock has no side-effects, sets few values
   set_predefined_output_for_runtime_call(unlock, mem, TypeRawPtr::BOTTOM);
-
-  // Kill monitor from debug info
-  map()->pop_monitor( );
+  if (!preserve_monitor) {
+    // Kill monitor from debug info
+    map()->pop_monitor();
+  }
 }
 
 //-------------------------------get_layout_helper-----------------------------
 // If the given klass is a constant or known to be an array,
 // fetch the constant layout helper value into constant_value
@@ -3602,18 +3647,14 @@
 
   Node* prevmem = kit.memory(alias_idx);
   init_in_merge->set_memory_at(alias_idx, prevmem);
   kit.set_memory(init_out_raw, alias_idx);
 }
-
-//---------------------------set_output_for_allocation-------------------------
-Node* GraphKit::set_output_for_allocation(AllocateNode* alloc,
+Node* GraphKit::set_output_for_allocation_common(AllocateNode* alloc,
                                           const TypeOopPtr* oop_type,
                                           bool deoptimize_on_exception) {
   int rawidx = Compile::AliasIdxRaw;
-  alloc->set_req( TypeFunc::FramePtr, frameptr() );
-  add_safepoint_edges(alloc);
   Node* allocx = _gvn.transform(alloc);
   set_control( _gvn.transform(new ProjNode(allocx, TypeFunc::Control) ) );
   // create memory projection for i_o
   set_memory ( _gvn.transform( new ProjNode(allocx, TypeFunc::Memory, true) ), rawidx );
   make_slow_call_ex(allocx, env()->Throwable_klass(), true, deoptimize_on_exception);
@@ -3688,10 +3729,66 @@
 #endif //ASSERT
 
   return javaoop;
 }
 
+//---------------------------set_output_for_allocation-------------------------
+Node* GraphKit::set_output_for_allocation(AllocateNode* alloc,
+                                          const TypeOopPtr* oop_type,
+                                          bool deoptimize_on_exception) {
+  alloc->set_req( TypeFunc::FramePtr, frameptr() );
+  add_safepoint_edges(alloc);
+  return set_output_for_allocation_common(alloc, oop_type, deoptimize_on_exception);
+}
+
+//
+// Position-Agnostic Materialization
+// -------------------------------------
+// When PEA materializes a virtual object, it emits a cluster of nodes in the current position.
+// Unlike ordinary "floating" nodes, an AllocateNode is a subclass of SafePointNode so it is
+// dependent on JVMState. The JVMState of current position may not fit for an AllocateNode.
+//
+// To ensure we can safely embed nodes into the curren position, we have the following
+// measures:
+//
+// 1. Debug edges and JVMState of the cloned AllocateNode are not from current GraphKit.
+// We copy them from the original AllocateNode instead.
+//
+// 2. Choose deoptimization on exception. A real exception may be mistakenly dispatched to
+// the exception handler in current context.
+//
+Node* GraphKit::materialize_object(AllocateNode* alloc, const TypeOopPtr* oop_type) {
+  Node *mem = reset_memory();
+  AllocateNode* allocx  = new AllocateNode(C, alloc->tf(), control(), mem, i_o(),
+                                           alloc->in(AllocateNode::AllocSize),
+                                           alloc->in(AllocateNode::KlassNode),
+                                           alloc->in(AllocateNode::InitialTest));
+  allocx->set_req(TypeFunc::FramePtr, frameptr());
+
+  JVMState* out_jvms = alloc->jvms()->clone_shallow(C);
+  out_jvms->bind_map(allocx);
+  // copy all debuginfo edges from the original AllocateNode
+  for (uint i=allocx->req(); i < alloc->req(); ++i) {
+    allocx->add_req(alloc->in(i));
+  }
+
+  JVMState* jvms = sync_jvms();
+  // We can not use PreserveJVMState here because 'this' is a Parse. We would fail in jvms_in_sync().
+  GraphKit kit(allocx->jvms());
+  kit.set_map_clone(allocx);
+
+  Node* objx = kit.set_output_for_allocation_common(allocx, oop_type, true /*deoptimize_on_ex*/);
+
+  // copy back compile-time state to 'this'.
+  set_jvms(jvms);
+  set_control(kit.control());
+  set_i_o(kit.i_o());
+  set_all_memory(kit.merged_memory());
+
+  return objx;
+}
+
 //---------------------------new_instance--------------------------------------
 // This routine takes a klass_node which may be constant (for a static type)
 // or may be non-constant (for reflective code).  It will work equally well
 // for either, and the graph will fold nicely if the optimizer later reduces
 // the type to a constant.
@@ -4248,5 +4345,33 @@
   if (con_type != nullptr) {
     return makecon(con_type);
   }
   return nullptr;
 }
+
+void GraphKit::backfill_materialized(SafePointNode* map, uint begin, uint end, PEAState& as){
+  bool printed = false;
+
+  for (uint i = begin; i < end; ++i) {
+    Node* t = map->in(i);
+
+    if (t != nullptr && t->is_CheckCastPP()) {
+      AllocateNode* alloc = AllocateNode::Ideal_allocation(t);
+
+      if (as.contains(alloc)) {
+        Node* neww = as.get_materialized_value(alloc);
+        if (neww != nullptr && neww != t) {
+#ifndef PRODUCT
+          if (PEAVerbose) {
+            if (!printed) {
+              map->dump();
+              printed = true;
+            }
+            tty->print_cr("[PEA] replace %d with node %d", i, neww->_idx);
+          }
+#endif
+          map->set_req(i, neww);
+        }
+      }
+    }
+  }
+}
diff a/src/hotspot/share/opto/graphKit.hpp b/src/hotspot/share/opto/graphKit.hpp
--- a/src/hotspot/share/opto/graphKit.hpp
+++ b/src/hotspot/share/opto/graphKit.hpp
@@ -138,10 +138,11 @@
   int                sp()       const { return _sp; }
   int                bci()      const { return _bci; }
   Bytecodes::Code    java_bc()  const;
   ciMethod*          method()   const { return _method; }
 
+  PartialEscapeAnalysis* PEA() const { return C->PEA(); }
   void set_jvms(JVMState* jvms)       { set_map(jvms->map());
                                         assert(jvms == this->jvms(), "sanity");
                                         _sp = jvms->sp();
                                         _bci = jvms->bci();
                                         _method = jvms->has_method() ? jvms->method() : nullptr; }
@@ -829,11 +830,12 @@
   int next_monitor();
   Node* insert_mem_bar(int opcode, Node* precedent = nullptr);
   Node* insert_mem_bar_volatile(int opcode, int alias_idx, Node* precedent = nullptr);
   // Optional 'precedent' is appended as an extra edge, to force ordering.
   FastLockNode* shared_lock(Node* obj);
-  void shared_unlock(Node* box, Node* obj);
+  void shared_unlock(Node* box, Node* obj, bool preserve_monitor = false);
+  void clone_shared_lock(Node* box, Node* obj);
 
   // helper functions for the fast path/slow path idioms
   Node* fast_and_slow(Node* in, const Type *result_type, Node* null_result, IfNode* fast_test, Node* fast_result, address slow_call, const TypeFunc *slow_call_type, Node* slow_arg, Klass* ex_klass, Node* slow_result);
 
   // Generate an instance-of idiom.  Used by both the instance-of bytecode
@@ -855,14 +857,21 @@
 
   // Inexact type check used for predicted calls.
   Node* subtype_check_receiver(Node* receiver, ciKlass* klass,
                                Node** casted_receiver);
 
+  Node* set_output_for_allocation_common(AllocateNode* alloc,
+                                  const TypeOopPtr* oop_type,
+                                  bool deoptimize_on_exception);
+
   // implementation of object creation
   Node* set_output_for_allocation(AllocateNode* alloc,
                                   const TypeOopPtr* oop_type,
                                   bool deoptimize_on_exception=false);
+
+  Node* materialize_object(AllocateNode* alloc, const TypeOopPtr* oop_type);
+
   Node* get_layout_helper(Node* klass_node, jint& constant_value);
   Node* new_instance(Node* klass_node,
                      Node* slow_test = nullptr,
                      Node* *return_size_val = nullptr,
                      bool deoptimize_on_exception = false);
@@ -905,10 +914,11 @@
 
   // Vector API support (implemented in vectorIntrinsics.cpp)
   Node* box_vector(Node* in, const TypeInstPtr* vbox_type, BasicType elem_bt, int num_elem, bool deoptimize_on_exception = false);
   Node* unbox_vector(Node* in, const TypeInstPtr* vbox_type, BasicType elem_bt, int num_elem, bool shuffle_to_vector = false);
   Node* vector_shift_count(Node* cnt, int shift_op, BasicType bt, int num_elem);
+  void backfill_materialized(SafePointNode* map, uint begin, uint end, PEAState& as);
 };
 
 // Helper class to support building of control flow branches. Upon
 // creation the map and sp at bci are cloned and restored upon de-
 // struction. Typical use:
diff a/src/hotspot/share/opto/locknode.cpp b/src/hotspot/share/opto/locknode.cpp
--- a/src/hotspot/share/opto/locknode.cpp
+++ b/src/hotspot/share/opto/locknode.cpp
@@ -185,20 +185,175 @@
   if (stopped()) return;
 
   // the monitor object is not part of debug info expression stack
   pop();
 
+  if (DoPartialEscapeAnalysis) {
+    PEAState& state = jvms()->alloc_state();
+    VirtualState* vs = state.as_virtual(PEA(), obj);
+
+    if (vs != nullptr) {
+      vs->lock_inc();
+    }
+  }
+
   // Insert a FastLockNode which takes as arguments the current thread pointer,
   // the obj pointer & the address of the stack slot pair used for the lock.
   shared_lock(obj);
 }
 
 //------------------------------do_monitor_exit--------------------------------
 void Parse::do_monitor_exit() {
   kill_dead_locals();
 
+  // need to set it for monitor exit as well.
+  // OSR compiled methods can start with lock taken
+  C->set_has_monitors(true);
+  Node* obj = map()->peek_monitor_obj();
+
+  if (DoPartialEscapeAnalysis) {
+    PEAState& state = jvms()->alloc_state();
+    ObjID id = PEA()->is_alias(obj); //
+    if (id != nullptr && state.contains(id)) {
+      ObjectState* os = state.get_object_state(id);
+      if (os->is_virtual()) {
+        static_cast<VirtualState*>(os)->lock_dec();
+      } else {
+        auto materialized = state.get_materialized_value(id);
+        if (materialized != nullptr) {
+          obj = materialized;
+
+          if (obj->is_Phi()) {
+            pop();
+
+            // We need to split Phi + Unlock like this:
+            // +------+   +-----+ +------+
+            // |Region|   | obj | | obj' | (PEA materialized object)
+            // +------+   +-----+ +------+
+            //        \     |    /
+            //        +------------+
+            //        |phi|        |
+            //        +------------+
+            //               |P0
+            //           +--------------+
+            //           | UnlockNode   |
+            //           +--------------+
+            //
+            // split this phi because it helps EA and ME eliminate Unlock nodes.
+            // bytecode monitor_exit post-dominates the object, so PhiNode has been finalized.
+            // for GraphKit::shared_unlock(), the only side-effect is ctrl + abio + memory.
+            //
+            //
+            // +------+          +-----+                      +------+
+            // |Region|          | obj |                      | obj' |
+            // +------+          +-----+                      +------+
+            //                      |                         /
+            //                      |P0                      |P0
+            //                  +--------------+         +--------------+
+            //                  | UnlockNode   |         | UnlockNode   |
+            //                  +--------------+         +--------------+
+            //                    |   |abio |memory        |  |abio |memory
+            //                    |                 /-----/
+            //                    | ctrl           / ctrl
+            //                 +--------------------+
+            //                 | new_rgn            | ( new_rgn merges abio and memory as well)
+            //                 +--------------------+
+            //                    |ctrl |abio |memory
+            //                   +--------------------+
+            //                   | sfpt (map)         |
+            //                   +--------------------+
+            //
+            RegionNode* region = obj->in(0)->as_Region();
+            BoxLockNode* box = map()->peek_monitor_box()->as_BoxLock();
+
+            Node* new_rgn = new RegionNode(region->req());
+            gvn().set_type(new_rgn, Type::CONTROL);
+
+            bool merged = false;
+            GraphKit saved_ctx = {clone_map()->jvms()};
+
+            // reverse i to simulate merging normal paths.
+            // merge_memory_edges() will do GVN when i == 1
+            for (uint i = region->req()-1; i > 0; --i) {
+              Node* ctrl  = region->in(i);
+              Node* abio = nullptr;
+              MergeMemNode* mem = nullptr;
+
+              if (ctrl != nullptr && ctrl != C->top()) {
+                SafePointNode* curr = saved_ctx.clone_map();
+                GraphKit kit = { curr->jvms() };
+
+                kit.set_control(ctrl);
+                // We need to resume SafePointNode to the state as if it was the predecessor controlled by region->in(i).
+                for (uint j = 1; j < curr->req(); ++j) {
+                  Node* m = curr->in(j);
+
+                  if (j == TypeFunc::Memory) {
+                    if (m->is_Phi() && m->in(0) == region) {
+                      m = m->in(i);
+                    } else if (m->is_MergeMem()) {
+                      // a blank memory
+                      MergeMemNode* new_all_mem = MergeMemNode::make(MergeMemNode::make_empty_memory());
+                      new_all_mem->grow_to_match(m->as_MergeMem());
+                      for (MergeMemStream mms(m->as_MergeMem()); mms.next_non_empty(); ) {
+                        Node* p = mms.memory();
+                        if (p->is_Phi() && p->in(0) == region) {
+                          new_all_mem->set_req(mms.alias_idx(), p->in(i));
+                        } else {
+                          new_all_mem->set_req(mms.alias_idx(), p);
+                        }
+                      }
+                      m = new_all_mem;
+                    }
+                    curr->set_memory(m);
+                  } else if (m != nullptr && m->is_Phi() && m->in(0) == region) {
+                    curr->set_req(j, m->in(i));
+                  }
+                }
+
+                kit.shared_unlock(box, obj->in(i), true);
+
+                ctrl = kit.control();
+                mem = kit.merged_memory();
+                abio = kit.i_o();
+              } else {
+                assert(false, "impossible! monitorExit must post-dominate the PhiNode.");
+              }
+
+              new_rgn->init_req(i, ctrl);
+
+              if (!merged) {
+                merged = true;
+                set_control(new_rgn); // merge_memory_edges() requires that ctrl() is a RegionNode.
+                set_all_memory(mem);
+                set_i_o(abio);
+              } else {
+                merge_memory_edges(mem, i, false);
+                Node* phi = i_o();
+                if (!(phi->is_Phi() && phi->in(0) != new_rgn)) {
+                  phi = PhiNode::make(new_rgn, phi);
+                  gvn().set_type(phi, Type::ABIO);
+                  record_for_igvn(phi);
+                }
+                phi->set_req(i, abio);
+                set_i_o(phi);
+              }
+            }
+
+            new_rgn = _gvn.transform(new_rgn);
+            set_control(new_rgn);
+            record_for_igvn(new_rgn);
+
+            map()->pop_monitor();
+            return ;
+          }
+        }
+      }
+    }
+  }
+
   pop();                        // Pop oop to unlock
   // Because monitors are guaranteed paired (else we bail out), we know
   // the matching Lock for this Unlock.  Hence we know there is no need
   // for a null check on Unlock.
-  shared_unlock(map()->peek_monitor_box(), map()->peek_monitor_obj());
+  shared_unlock(map()->peek_monitor_box(), obj);
 }
diff a/src/hotspot/share/opto/macro.cpp b/src/hotspot/share/opto/macro.cpp
--- a/src/hotspot/share/opto/macro.cpp
+++ b/src/hotspot/share/opto/macro.cpp
@@ -1295,10 +1295,14 @@
 
   enum { too_big_or_final_path = 1, need_gc_path = 2 };
   Node *slow_region = nullptr;
   Node *toobig_false = ctrl;
 
+  if (PEAParanoid && alloc->materialized_cnt() > 0) {
+    fatal("[PEA] Expanding obj#%d which has been materialized.", alloc->_idx);
+  }
+
   // generate the initial test if necessary
   if (initial_slow_test != nullptr ) {
     assert (expand_fast_path, "Only need test if there is a fast path");
     slow_region = new RegionNode(3);
 
@@ -1948,28 +1952,36 @@
   // eliminated even if different objects are referenced in one locked region
   // (for example, OSR compilation of nested loop inside locked scope).
   if (EliminateNestedLocks ||
       oldbox->as_BoxLock()->is_simple_lock_region(nullptr, obj, nullptr)) {
     // Box is used only in one lock region. Mark this box as eliminated.
-    _igvn.hash_delete(oldbox);
-    oldbox->as_BoxLock()->set_eliminated(); // This changes box's hash value
-     _igvn.hash_insert(oldbox);
-
+    int locks = 0;
     for (uint i = 0; i < oldbox->outcnt(); i++) {
       Node* u = oldbox->raw_out(i);
       if (u->is_AbstractLock() && !u->as_AbstractLock()->is_non_esc_obj()) {
         AbstractLockNode* alock = u->as_AbstractLock();
         // Check lock's box since box could be referenced by Lock's debug info.
         if (alock->box_node() == oldbox) {
-          // Mark eliminated all related locks and unlocks.
+          locks++;
+
+          if (alock->obj_node() == obj) {
+            // Mark eliminated all related locks and unlocks.
 #ifdef ASSERT
-          alock->log_lock_optimization(C, "eliminate_lock_set_non_esc4");
+            alock->log_lock_optimization(C, "eliminate_lock_set_non_esc4");
 #endif
-          alock->set_non_esc_obj();
+            alock->set_non_esc_obj();
+            locks--;
+          }
         }
       }
     }
+    if (locks == 0) {
+      _igvn.hash_delete(oldbox);
+      oldbox->as_BoxLock()->set_eliminated(); // This changes box's hash value
+      _igvn.hash_insert(oldbox);
+    }
+
     return;
   }
 
   // Create new "eliminated" BoxLock node and use it in monitor debug info
   // instead of oldbox for the same object.
@@ -2110,11 +2122,11 @@
   }
 #ifdef ASSERT
   if (!alock->is_coarsened()) {
     // Check that new "eliminated" BoxLock node is created.
     BoxLockNode* oldbox = alock->box_node()->as_BoxLock();
-    assert(oldbox->is_eliminated(), "should be done already");
+    assert(oldbox->is_eliminated() || DoPartialEscapeAnalysis, "should be done already");
   }
 #endif
 
   alock->log_lock_optimization(C, "eliminate_lock");
 
diff a/src/hotspot/share/opto/parse.hpp b/src/hotspot/share/opto/parse.hpp
--- a/src/hotspot/share/opto/parse.hpp
+++ b/src/hotspot/share/opto/parse.hpp
@@ -29,10 +29,11 @@
 #include "ci/ciTypeFlow.hpp"
 #include "compiler/methodLiveness.hpp"
 #include "libadt/vectset.hpp"
 #include "oops/generateOopMap.hpp"
 #include "opto/graphKit.hpp"
+#include "opto/partialEscape.hpp"
 #include "opto/subnode.hpp"
 
 class BytecodeParseHistogram;
 class InlineTree;
 class Parse;
@@ -159,17 +160,27 @@
     uint               _count;          // how many times executed?  Currently only set by _goto's
     bool               _is_parsed;      // has this block been parsed yet?
     bool               _is_handler;     // is this block an exception handler?
     bool               _has_merged_backedge; // does this block have merged backedge?
     SafePointNode*     _start_map;      // all values flowing into this block
-    MethodLivenessResult _live_locals;  // lazily initialized liveness bitmap
+    mutable MethodLivenessResult _live_locals;  // lazily initialized liveness bitmap
     bool               _has_predicates; // Were predicates added before parsing of the loop head?
 
     int                _num_successors; // Includes only normal control flow.
     int                _all_successors; // Include exception paths also.
     Block**            _successors;
+    Block**            _predecessors;
+    Block*             _from_block;
+    int                _init_pnum;      // the pnum of Block where _state is copied from.
 
+    const MethodLivenessResult& liveness() const {
+      if (!_live_locals.is_valid()) {
+        _live_locals = flow()->outer()->method()->liveness_at_bci(start());
+      }
+      assert(_live_locals.is_valid(), "sanity check");
+      return _live_locals;
+    }
    public:
 
     // Set up the block data structure itself.
     Block(Parse* outer, int rpo);
 
@@ -187,10 +198,16 @@
     SafePointNode* start_map() const       { assert(is_merged(),"");   return _start_map; }
     void set_start_map(SafePointNode* m)   { assert(!is_merged(), ""); _start_map = m; }
 
     // True after any predecessor flows control into this block
     bool is_merged() const                 { return _start_map != nullptr; }
+    Block* from_block() const              { return _from_block; }
+    int init_pnum() const                  { return _init_pnum; }
+    PEAState& state()                      {
+      assert(is_merged(), "sanity check");
+      return _start_map->jvms()->alloc_state();
+    }
 
 #ifdef ASSERT
     // True after backedge predecessor flows control into this block
     bool has_merged_backedge() const       { return _has_merged_backedge; }
     void mark_merged_backedge(Block* pred) {
@@ -214,10 +231,16 @@
       assert((uint)i < (uint)all_successors(), "");
       return _successors[i];
     }
     Block* successor_for_bci(int bci);
 
+    Block* predecessor_at(int i) const {
+      assert(DoPartialEscapeAnalysis, "_predecessors is only available when DoPartialEscapeAnalysis is ON");
+      assert(i < _pred_count, "");
+      return _predecessors[i];
+    }
+
     int start() const                      { return flow()->start(); }
     int limit() const                      { return flow()->limit(); }
     int rpo() const                        { return flow()->rpo(); }
     int start_sp() const                   { return flow()->stack_size(); }
 
@@ -284,11 +307,11 @@
     // and all its phi nodes (if any).  The value returned is a
     // path number ("pnum").
     int add_new_path();
 
     // Initialize me by recording the parser's map.  My own map must be null.
-    void record_state(Parse* outer);
+    void record_state(Parse* outer, int pnum);
   };
 
 #ifndef PRODUCT
   // BytecodeParseHistogram collects number of bytecodes parsed, nodes constructed, and transformations.
   class BytecodeParseHistogram : public ArenaObj {
@@ -341,10 +364,11 @@
   friend class Block;
 
   // Variables which characterize this compilation as a whole:
 
   JVMState*     _caller;        // JVMS which carries incoming args & state.
+  PEAState*     _caller_state;  // current PEA state of caller
   float         _expected_uses; // expected number of calls to this code
   float         _prof_factor;   // discount applied to my profile counts
   int           _depth;         // Inline tree depth, for debug printouts
   const TypeFunc*_tf;           // My kind of function type
   int           _entry_bci;     // the osr bci or InvocationEntryBci
@@ -370,18 +394,21 @@
 #ifndef PRODUCT
   int _max_switch_depth;        // Debugging SwitchRanges.
   int _est_switch_depth;        // Debugging SwitchRanges.
 #endif
 
-  bool         _first_return;                  // true if return is the first to be parsed
+  int          _first_return;                  // true if return is the first to be parsed
   bool         _replaced_nodes_for_exceptions; // needs processing of replaced nodes in exception paths?
   uint         _new_idx;                       // any node with _idx above were new during this parsing. Used to trim the replaced nodes list.
 
  public:
   // Constructor
-  Parse(JVMState* caller, ciMethod* parse_method, float expected_uses);
+  Parse(JVMState* caller, ciMethod* parse_method, float expected_uses, PEAState* caller_state = nullptr);
 
+#ifndef PRODUCT
+  ~Parse();
+#endif
   virtual Parse* is_Parse() const { return (Parse*)this; }
 
   // Accessors.
   JVMState*     caller()        const { return _caller; }
   float         expected_uses() const { return _expected_uses; }
@@ -403,12 +430,18 @@
   void      set_wrote_stable(bool z)  { _wrote_stable = z; }
   bool         wrote_fields() const   { return _wrote_fields; }
   void     set_wrote_fields(bool z)   { _wrote_fields = z; }
   Node*    alloc_with_final() const   { return _alloc_with_final; }
   void set_alloc_with_final(Node* n)  {
-    assert((_alloc_with_final == nullptr) || (_alloc_with_final == n), "different init objects?");
-    _alloc_with_final = n;
+    if (DoPartialEscapeAnalysis) {
+      assert((_alloc_with_final == nullptr) || (_alloc_with_final == PEA()->is_alias(n)), 
+             "different init objects?");
+      _alloc_with_final = PEA()->is_alias(n);
+    } else {
+      assert((_alloc_with_final == nullptr) || (_alloc_with_final == n), "different init objects?");
+      _alloc_with_final = n;
+    }
   }
 
   Block*             block()    const { return _block; }
   ciBytecodeStream&  iter()           { return _iter; }
   Bytecodes::Code    bc()       const { return _iter.cur_bc(); }
@@ -450,11 +483,11 @@
   void  load_interpreter_state(Node* osr_buf);
 
   // Functions for managing basic blocks:
   void init_blocks();
   void load_state_from(Block* b);
-  void store_state_to(Block* b) { b->record_state(this); }
+  void store_state_to(Block* b, int pnum) { b->record_state(this, pnum); }
 
   // Parse all the basic blocks.
   void do_all_blocks();
 
   // Parse the current basic block
@@ -482,10 +515,11 @@
   // Helper: Merge the current mapping into the given basic block
   void merge_common(Block* target, int pnum);
   // Helper functions for merging individual cells.
   PhiNode *ensure_phi(       int idx, bool nocreate = false);
   PhiNode *ensure_memory_phi(int idx, bool nocreate = false);
+
   // Helper to merge the current memory state into the given basic block
   void merge_memory_edges(MergeMemNode* n, int pnum, bool nophi);
 
   // Parse this bytecode, and alter the Parsers JVM->Node mapping
   void do_one_bytecode();
diff a/src/hotspot/share/opto/parse1.cpp b/src/hotspot/share/opto/parse1.cpp
--- a/src/hotspot/share/opto/parse1.cpp
+++ b/src/hotspot/share/opto/parse1.cpp
@@ -93,10 +93,14 @@
   }
 
   if (PrintParseStatistics && BytecodeParseHistogram::initialized()) {
     BytecodeParseHistogram::print();
   }
+
+  if (DoPartialEscapeAnalysis) {
+    printPeaStatistics();
+  }
 }
 #endif
 
 //------------------------------ON STACK REPLACEMENT---------------------------
 
@@ -386,25 +390,26 @@
   }
 }
 
 //------------------------------Parse------------------------------------------
 // Main parser constructor.
-Parse::Parse(JVMState* caller, ciMethod* parse_method, float expected_uses)
+Parse::Parse(JVMState* caller, ciMethod* parse_method, float expected_uses, PEAState* caller_state)
   : _exits(caller)
 {
   // Init some variables
   _caller = caller;
+  _caller_state = caller_state;
   _method = parse_method;
   _expected_uses = expected_uses;
   _depth = 1 + (caller->has_method() ? caller->depth() : 0);
   _wrote_final = false;
   _wrote_volatile = false;
   _wrote_stable = false;
   _wrote_fields = false;
   _alloc_with_final = nullptr;
   _block = nullptr;
-  _first_return = true;
+  _first_return = 0;
   _replaced_nodes_for_exceptions = false;
   _new_idx = C->unique();
   DEBUG_ONLY(_entry_bci = UnknownBci);
   DEBUG_ONLY(_block_count = -1);
   DEBUG_ONLY(_blocks = (Block*)-1);
@@ -413,10 +418,16 @@
     // Make sure I have an inline tree, so I can print messages about it.
     InlineTree::find_subtree_from_root(C->ilt(), caller, parse_method);
   }
   _max_switch_depth = 0;
   _est_switch_depth = 0;
+
+  if (TraceOptoParse) {
+    tty->print_raw("Parsing method ");
+    parse_method->print_name(tty);
+    tty->print_cr(" {");
+  }
 #endif
 
   if (parse_method->has_reserved_stack_access()) {
     C->set_has_reserved_stack_access(true);
   }
@@ -528,10 +539,15 @@
     assert(!this->is_osr_parse(), "no recursive OSR");
   }
 #endif
 
 #ifndef PRODUCT
+  // Dump CFG in RPO order before Parsing.
+  if (Verbose && !CITraceTypeFlow) {
+    _flow->rpo_print_on(tty);
+  }
+
   if (_flow->has_irreducible_entry()) {
     C->set_parsed_irreducible_loop(true);
   }
 
   methods_parsed++;
@@ -603,12 +619,12 @@
   // We begin parsing as if we have just encountered a jump to the
   // method entry.
   Block* entry_block = start_block();
   assert(entry_block->start() == (is_osr_parse() ? osr_bci() : 0), "");
   set_map_clone(entry_map);
-  merge_common(entry_block, entry_block->next_path_num());
 
+  merge_common(entry_block, entry_block->next_path_num());
 #ifndef PRODUCT
   BytecodeParseHistogram *parse_histogram_obj = new (C->env()->arena()) BytecodeParseHistogram(this, C);
   set_parse_histogram( parse_histogram_obj );
 #endif
 
@@ -631,10 +647,38 @@
 
   if (log)  log->done("parse nodes='%d' live='%d' memory='" SIZE_FORMAT "'",
                       C->unique(), C->live_nodes(), C->node_arena()->used());
 }
 
+#ifndef PRODUCT
+Parse::~Parse() {
+  if (TraceOptoParse) {
+    tty->print("} // ");
+    method()->print_short_name(tty);
+    tty->cr();
+  }
+
+  if (DoPartialEscapeAnalysis && PEAVerbose) {
+    PEAState& as = _exits.jvms()->alloc_state();
+    auto objs = PEA()->all_objects();
+    for (int i = 0; i < objs.length(); ++i) {
+      ObjID obj = objs.at(i);
+
+      if (as.contains(obj)) {
+        ObjectState* os = as.get_object_state(obj);
+        tty->print("%4d | Obj%d\t", i, obj->_idx);
+
+        if (os->is_virtual()) {
+          tty->print_cr("V");
+        } else {
+          tty->print_cr("M");
+        }
+      }
+    }
+  }
+}
+#endif
 //---------------------------do_all_blocks-------------------------------------
 void Parse::do_all_blocks() {
   bool has_irreducible = flow()->has_irreducible_entry();
 
   // Walk over all blocks in Reverse Post-Order.
@@ -660,10 +704,15 @@
 
       NOT_PRODUCT(blocks_parsed++);
 
       progress = true;
       if (block->is_loop_head() || block->is_handler() || (has_irreducible && !block->is_ready())) {
+        // mark live objects 'Escaped' in map before mounting phi nodes.
+        if (DoPartialEscapeAnalysis && block->is_loop_head()) {
+          PEAState& as = jvms()->alloc_state();
+          as.mark_all_live_objects_escaped(PEA(), map());
+        }
         // Not all preds have been parsed.  We must build phis everywhere.
         // (Note that dead locals do not get phis built, ever.)
         ensure_phis_everywhere();
 
         if (block->is_SEL_head()) {
@@ -1002,19 +1051,29 @@
   //
   if (method()->is_initializer() &&
        (wrote_final() ||
          (AlwaysSafeConstructors && wrote_fields()) ||
          (support_IRIW_for_not_multiple_copy_atomic_cpu && wrote_volatile()))) {
-    _exits.insert_mem_bar(Op_MemBarRelease, alloc_with_final());
-
-    // If Memory barrier is created for final fields write
-    // and allocation node does not escape the initialize method,
-    // then barrier introduced by allocation node can be removed.
-    if (DoEscapeAnalysis && alloc_with_final()) {
-      AllocateNode* alloc = AllocateNode::Ideal_allocation(alloc_with_final());
-      alloc->compute_MemBar_redundancy(method());
+    if (!DoPartialEscapeAnalysis) {
+      // If Memory barrier is created for final fields write
+      // and allocation node does not escape the initialize method,
+      // then barrier introduced by allocation node can be removed.
+      if (DoEscapeAnalysis && alloc_with_final()) {
+        AllocateNode *alloc = AllocateNode::Ideal_allocation(alloc_with_final());
+        alloc->compute_MemBar_redundancy(method());
+      }
+    } else {
+      // in PEA, alloc_with_final stores ObjID
+      AllocateNode* alloc = (ObjID)alloc_with_final();
+
+      if (DoEscapeAnalysis && alloc != nullptr) {
+        Node* obj = _exits.jvms()->alloc_state().get_java_oop(alloc);
+        _exits.insert_mem_bar(Op_MemBarRelease, obj);
+        alloc->compute_MemBar_redundancy(method());
+      }
     }
+
     if (PrintOpto && (Verbose || WizardMode)) {
       method()->print_name();
       tty->print_cr(" writes finals and needs a memory barrier");
     }
   }
@@ -1038,11 +1097,22 @@
   // Clean up input MergeMems created by transforming the slices
   _gvn.transform(_exits.merged_memory());
 
   if (tf()->range()->cnt() > TypeFunc::Parms) {
     const Type* ret_type = tf()->range()->field_at(TypeFunc::Parms);
-    Node*       ret_phi  = _gvn.transform( _exits.argument(0) );
+    Node* const ret_phi_old = _exits.argument(0);
+    Node*       ret_phi  = _gvn.transform(ret_phi_old);
+    if (DoPartialEscapeAnalysis && ret_phi_old != ret_phi) {
+      PEAState& as = _exits.jvms()->alloc_state();
+      EscapedState* es = as.as_escaped(PEA(), ret_phi_old);
+
+      if (es != nullptr && es->merged_value() == ret_phi_old) {
+        es->update(ret_phi);
+        ObjID obj = PEA()->is_alias(ret_phi_old);
+        PEA()->add_alias(obj, ret_phi);
+      }
+    }
     if (!_exits.control()->is_top() && _gvn.type(ret_phi)->empty()) {
       // If the type we set for the ret_phi in build_exits() is too optimistic and
       // the ret_phi is top now, there's an extremely small chance that it may be due to class
       // loading.  It could also be due to an error, so mark this method as not compilable because
       // otherwise this could lead to an infinite compile loop.
@@ -1119,11 +1189,19 @@
     SafePointNode* ex_map;
     while ((ex_map = caller.pop_exception_state()) != nullptr) {
       _exits.add_exception_state(ex_map);
     }
   }
+
   _exits.map()->apply_replaced_nodes(_new_idx);
+  // don't trust replace list. return_current() may mess it up.
+  // use AllocationState to update it.
+  if (DoPartialEscapeAnalysis) {
+    PEAState& as = _exits.jvms()->alloc_state();
+    SafePointNode* map = _exits.map();
+    backfill_materialized(map, TypeFunc::Parms, map->req(), as);
+  }
 }
 
 //-----------------------------create_entry_map-------------------------------
 // Initialize our parser map to contain the types at method entry.
 // For OSR, the map contains a single RawPtr parameter.
@@ -1154,10 +1232,14 @@
 
   assert(method() != nullptr, "parser must have a method");
 
   // Create an initial safepoint to hold JVM state during parsing
   JVMState* jvms = new (C) JVMState(method(), _caller->has_method() ? _caller : nullptr);
+  if (_caller != nullptr && DoPartialEscapeAnalysis) {
+    jvms->alloc_state() = _caller->alloc_state();
+  }
+
   set_map(new SafePointNode(len, jvms));
   jvms->set_map(map());
   record_for_igvn(map());
   assert(jvms->endoff() == len, "correct jvms sizing");
 
@@ -1301,11 +1383,11 @@
   _num_successors = 0;
   _all_successors = 0;
   _successors = nullptr;
   assert(pred_count() == 0 && preds_parsed() == 0, "sanity");
   assert(!(is_merged() || is_parsed() || is_handler() || has_merged_backedge()), "sanity");
-  assert(_live_locals.size() == 0, "sanity");
+  assert(!_live_locals.is_valid(), "sanity");
 
   // entry point has additional predecessor
   if (flow()->is_start())  _pred_count++;
   assert(flow()->is_start() == (this == outer->start_block()), "");
 }
@@ -1317,12 +1399,11 @@
   GrowableArray<ciTypeFlow::Block*>* tfe = flow()->exceptions();
   int ns = tfs->length();
   int ne = tfe->length();
   _num_successors = ns;
   _all_successors = ns+ne;
-  _successors = (ns+ne == 0) ? nullptr : NEW_RESOURCE_ARRAY(Block*, ns+ne);
-  int p = 0;
+  _successors = (ns+ne == 0) ? nullptr: NEW_RESOURCE_ARRAY(Block*, ns+ne);
   for (int i = 0; i < ns+ne; i++) {
     ciTypeFlow::Block* tf2 = (i < ns) ? tfs->at(i) : tfe->at(i-ns);
     Block* block2 = outer->rpo_at(tf2->rpo());
     _successors[i] = block2;
 
@@ -1344,10 +1425,21 @@
       if (block1 == block2)  continue;  // duplicates are OK
       assert(block1->start() != block2->start(), "successors have unique bcis");
     }
     #endif
   }
+
+  if (DoPartialEscapeAnalysis) {
+    GrowableArray<ciTypeFlow::Block*>* tfp = flow()->predecessors();
+    int np = tfp->length();
+    _predecessors = np > 0 ? NEW_RESOURCE_ARRAY(Block*, np) : nullptr;
+    for (int i = 0; i < np; ++i) {
+      ciTypeFlow::Block* tf2 = tfp->at(i);
+      Block* block2 = outer->rpo_at(tf2->rpo());
+      _predecessors[i] = block2;
+    }
+  }
 }
 
 //---------------------------successor_for_bci---------------------------------
 Parse::Block* Parse::Block::successor_for_bci(int bci) {
   for (int i = 0; i < all_successors(); i++) {
@@ -1372,18 +1464,14 @@
 }
 
 
 //-----------------------------local_type_at-----------------------------------
 const Type* Parse::Block::local_type_at(int i) const {
-  // Make dead locals fall to bottom.
-  if (_live_locals.size() == 0) {
-    MethodLivenessResult live_locals = flow()->outer()->method()->liveness_at_bci(start());
-    // This bitmap can be zero length if we saw a breakpoint.
-    // In such cases, pretend they are all live.
-    ((Block*)this)->_live_locals = live_locals;
-  }
-  if (_live_locals.size() > 0 && !_live_locals.at(i))
+  // This bitmap can be zero length if we saw a breakpoint.
+  // In such cases, pretend they are all live.
+  auto live_locals = liveness();
+  if (live_locals.size() > 0 && !live_locals.at(i))
     return Type::BOTTOM;
 
   return get_type(flow()->local_type_at(i));
 }
 
@@ -1495,14 +1583,17 @@
   set_sp( block->start_sp());
 }
 
 
 //-----------------------------record_state------------------------------------
-void Parse::Block::record_state(Parse* p) {
+void Parse::Block::record_state(Parse* p, int pnum) {
   assert(!is_merged(), "can only record state once, on 1st inflow");
   assert(start_sp() == p->sp(), "stack pointer must agree with ciTypeFlow");
   set_start_map(p->stop());
+
+  _from_block = p->block();
+  _init_pnum = pnum;
 }
 
 
 //------------------------------do_one_block-----------------------------------
 void Parse::do_one_block() {
@@ -1523,10 +1614,16 @@
       tty->print("  irreducible");
     }
     tty->cr();
   }
 
+#ifndef PRODUCT
+  if (PEAVerbose) {
+    PEAState& as = jvms()->alloc_state();
+    as.print_on(tty);
+  }
+#endif
   assert(block()->is_merged(), "must be merged before being parsed");
   block()->mark_parsed();
 
   // Set iterator to start of block.
   iter().reset_to_bci(block()->start());
@@ -1680,19 +1777,23 @@
 
 //--------------------------merge_common---------------------------------------
 void Parse::merge_common(Parse::Block* target, int pnum) {
   if (TraceOptoParse) {
     tty->print("Merging state at block #%d bci:%d", target->rpo(), target->start());
+    if (!target->is_merged()) {
+      tty->print(" with empty state");
+    } else {
+      tty->print(" with previous state");
+    }
+    tty->print_cr(" on path %d", pnum);
   }
 
   // Zap extra stack slots to top
   assert(sp() == target->start_sp(), "");
   clean_stack(sp());
 
   if (!target->is_merged()) {   // No prior mapping at this bci
-    if (TraceOptoParse) { tty->print(" with empty state");  }
-
     // If this path is dead, do not bother capturing it as a merge.
     // It is "as if" we had 1 fewer predecessors from the beginning.
     if (stopped()) {
       if (TraceOptoParse)  tty->print_cr(", but path is dead and doesn't count");
       return;
@@ -1730,15 +1831,17 @@
       target->copy_irreducible_status_to(r, jvms());
       set_parse_bci(current_bci); // Restore bci
     }
 
     // Convert the existing Parser mapping into a mapping at this bci.
-    store_state_to(target);
+    store_state_to(target, pnum);
     assert(target->is_merged(), "do not come here twice");
-
+#ifdef ASSERT
+    target->state().validate();
+#endif
   } else {                      // Prior mapping at this bci
-    if (TraceOptoParse) {  tty->print(" with previous state"); }
+
 #ifdef ASSERT
     if (target->is_SEL_head()) {
       target->mark_merged_backedge(block());
     }
 #endif
@@ -1775,11 +1878,15 @@
     }
 
     // Update all the non-control inputs to map:
     assert(TypeFunc::Parms == newin->jvms()->locoff(), "parser map should contain only youngest jvms");
     bool check_elide_phi = target->is_SEL_backedge(save_block);
-    for (uint j = 1; j < newin->req(); j++) {
+    PEAState& pred_as = newin->jvms()->alloc_state();
+    PEAState& as = block()->state();
+    AllocationStateMerger as_merger(as);
+
+    for (uint j = 1; j < newin->req(); ++j) {
       Node* m = map()->in(j);   // Current state of target.
       Node* n = newin->in(j);   // Incoming change to target state.
       PhiNode* phi;
       if (m->is_Phi() && m->as_Phi()->region() == r)
         phi = m->as_Phi();
@@ -1806,10 +1913,18 @@
               // This assert also tests that nodes are BoxLock.
               assert(BoxLockNode::same_slot(n, m), "sanity");
               C->gvn_replace_by(n, m);
             } else if (!check_elide_phi || !target->can_elide_SEL_phi(j)) {
               phi = ensure_phi(j, nophi);
+
+              // We merges allocation state according to 5.2.4 of the dissertation
+              // Becase C2 Parse is merging basic blocks, we have to intercept some phi creation or
+              // PEA MergeProcessor creates duplicated phi nodes.
+              if (DoPartialEscapeAnalysis && phi != nullptr) {
+                PartialEscapeAnalysis* pea = PEA();
+                as_merger.merge_at_phi_creation(pea, pred_as, phi, m, n);
+              } // DoPartialEscapeAnalysis
             }
           }
           break;
         }
       }
@@ -1819,10 +1934,11 @@
       // It is a bug if we create a phi which sees a garbage value on a live path.
 
       if (phi != nullptr) {
         assert(n != top() || r->in(pnum) == top(), "live value must not be garbage");
         assert(phi->region() == r, "");
+
         phi->set_req(pnum, n);  // Then add 'n' to the merge
         if (pnum == PhiNode::Input) {
           // Last merge for this Phi.
           // So far, Phis have had a reasonable type from ciTypeFlow.
           // Now _gvn will join that with the meet of current inputs.
@@ -1836,28 +1952,32 @@
           record_for_igvn(phi);
         }
       }
     } // End of for all values to be merged
 
+
+    if (DoPartialEscapeAnalysis) {
+      as_merger.merge(pred_as, this, r, pnum);
+    }
+
     if (pnum == PhiNode::Input &&
         !r->in(0)) {         // The occasional useless Region
       assert(control() == r, "");
       set_control(r->nonnull_req());
     }
 
     map()->merge_replaced_nodes_with(newin);
 
+#ifdef ASSERT
+    block()->state().validate();
+#endif
     // newin has been subsumed into the lazy merge, and is now dead.
     set_block(save_block);
 
     stop();                     // done with this guy, for now
   }
 
-  if (TraceOptoParse) {
-    tty->print_cr(" on path %d", pnum);
-  }
-
   // Done with this parser state.
   assert(stopped(), "");
 }
 
 
@@ -1971,29 +2091,28 @@
   if (!map->control()->is_Region())
     return pred_count()+1;  // there may be a region some day
   RegionNode* r = map->control()->as_Region();
 
   // Add new path to the region.
-  uint pnum = r->req();
+  const uint pnum = r->req();
   r->add_req(nullptr);
 
-  for (uint i = 1; i < map->req(); i++) {
-    Node* n = map->in(i);
-    if (i == TypeFunc::Memory) {
+  for (DUIterator_Fast imax, i = r->fast_outs(imax); i < imax; i++) {
+    Node* n = r->fast_out(i);
+
+    if (n->is_MergeMem()) {
       // Ensure a phi on all currently known memories.
       for (MergeMemStream mms(n->as_MergeMem()); mms.next_non_empty(); ) {
         Node* phi = mms.memory();
-        if (phi->is_Phi() && phi->as_Phi()->region() == r) {
+        if (phi->is_Phi() && phi->as_Phi()->region() == r && phi->req() <= pnum) {
           assert(phi->req() == pnum, "must be same size as region");
           phi->add_req(nullptr);
         }
       }
-    } else {
-      if (n->is_Phi() && n->as_Phi()->region() == r) {
-        assert(n->req() == pnum, "must be same size as region");
-        n->add_req(nullptr);
-      }
+    } else if (n->is_Phi() && n->as_Phi()->region() == r && n->req() <= pnum) {
+      assert(n->req() == pnum, "must be same size as region");
+      n->add_req(nullptr);
     }
   }
 
   return pnum;
 }
@@ -2048,10 +2167,11 @@
 
   PhiNode* phi = PhiNode::make(region, o, t);
   gvn().set_type(phi, t);
   if (C->do_escape_analysis()) record_for_igvn(phi);
   map->set_req(idx, phi);
+
   return phi;
 }
 
 //--------------------------ensure_memory_phi----------------------------------
 // Turn the idx'th slice of the current memory into a Phi
@@ -2249,15 +2369,30 @@
     // here.
     Node* phi = _exits.argument(0);
     phi->add_req(value);
   }
 
-  if (_first_return) {
+  if (_first_return++ == 0) {
     _exits.map()->transfer_replaced_nodes_from(map(), _new_idx);
-    _first_return = false;
+    // copy assignment
+    _exits.jvms()->alloc_state() = jvms()->alloc_state();
   } else {
     _exits.map()->merge_replaced_nodes_with(map());
+
+    if (DoPartialEscapeAnalysis) {
+      PEAState& as =_exits.jvms()->alloc_state();
+      PEAState& newin = jvms()->alloc_state();
+      AllocationStateMerger mp(as);
+      // if value is a tracking object and PEA needs to create a phi node to merge it,
+      // we need to use _exits.argument(0)
+      ObjID obj = PEA()->is_alias(value);
+      if (as.contains(obj) && newin.contains(obj)) {
+        Node* phi = _exits.argument(0);
+        mp.merge_at_phi_creation(PEA(), newin, phi->as_Phi(), phi->in(_first_return-1), value);
+      }
+      mp.merge(newin, &_exits, _exits.control()->as_Region(), _first_return);
+    }
   }
 
   stop_and_kill_map();          // This CFG path dies here
 }
 
diff a/src/hotspot/share/opto/parse2.cpp b/src/hotspot/share/opto/parse2.cpp
--- a/src/hotspot/share/opto/parse2.cpp
+++ b/src/hotspot/share/opto/parse2.cpp
@@ -97,10 +97,21 @@
   Node* array = pop();        // the array itself
 
   if (elemtype == TypeInt::BOOL) {
     bt = T_BOOLEAN;
   }
+
+  if (DoPartialEscapeAnalysis && bt == T_OBJECT) {
+    PartialEscapeAnalysis* pea = PEA();
+    PEAState& as = jvms()->alloc_state();
+    VirtualState* obj_vs = as.as_virtual(pea, val);    // obj is tracked and Virtual
+    VirtualState* ary_vs = as.as_virtual(pea, array);  // array is not-tracked or Escaped
+
+    if (ary_vs == nullptr && obj_vs != nullptr) {
+      val = as.materialize(this, val);
+    }
+  }
   const TypeAryPtr* adr_type = TypeAryPtr::get_array_body_type(bt);
 
   access_store_at(array, adr, adr_type, val, elemtype, bt, MO_UNORDERED | IN_HEAP | IS_ARRAY);
 }
 
diff a/src/hotspot/share/opto/parse3.cpp b/src/hotspot/share/opto/parse3.cpp
--- a/src/hotspot/share/opto/parse3.cpp
+++ b/src/hotspot/share/opto/parse3.cpp
@@ -162,12 +162,29 @@
     }
   } else {
     type = Type::get_const_basic_type(bt);
   }
 
-  Node* ld = access_load_at(obj, adr, adr_type, type, bt, decorators);
+  Node* ld = nullptr;
 
+  if (DoPartialEscapeAnalysis && is_field) { // non-static field a global
+    PEAState& as = jvms()->alloc_state();
+    VirtualState* vs = as.as_virtual(PEA(), obj);
+    if (vs != nullptr) { // obj is a virtual object
+      Node* val = vs->get_field(field);
+      // val is nullptr because the field is not explicitly initialized. It is 'null'.
+      // We only replace an instance pointer here. for array pointer, we need to cast 'val' from oop-ptr to aryptr.
+      if (is_obj && val != nullptr && type->isa_instptr()) {
+        ld = val;
+      }
+      // theoretically, we can replace ld with val if val is scalar or even nullptr.
+      // Graal has a feature called 'ReadElimination to do so.
+    }
+  }
+  if (ld == nullptr) {
+    ld = access_load_at(obj, adr, adr_type, type, bt, decorators);
+  }
   // Adjust Java stack
   if (type2size[bt] == 1)
     push(ld);
   else
     push_pair(ld);
@@ -222,10 +239,29 @@
       field_type = TypeOopPtr::make_from_klass(field->type()->as_klass());
     } else {
       field_type = Type::BOTTOM;
     }
   }
+
+  // if val is a valid object and the current path isn't dead
+  if (DoPartialEscapeAnalysis && !stopped()) {
+    PartialEscapeAnalysis* pea = PEA();
+    PEAState& state = jvms()->alloc_state();
+
+    // val is escaped if obj is escaped or is not trackable.
+    if (is_obj && !val->is_top()) {
+      // put_static_field or unknown dst or dst is escaped.
+      if (state.as_virtual(pea, val) != nullptr && (!is_field || state.as_virtual(pea, obj) == nullptr)) {
+        val = state.materialize(this, val);
+      }
+    }
+    VirtualState* vs = nullptr;
+    if (is_field && (vs = state.as_virtual(pea, obj)) != nullptr) {
+      vs->set_field(field, val);
+    }
+  }
+
   access_store_at(obj, adr, adr_type, val, field_type, bt, decorators);
 
   if (is_field) {
     // Remember we wrote a volatile field.
     // For not multiple copy atomic cpu (ppc64) a barrier should be issued
diff a/src/hotspot/share/opto/parseHelper.cpp b/src/hotspot/share/opto/parseHelper.cpp
--- a/src/hotspot/share/opto/parseHelper.cpp
+++ b/src/hotspot/share/opto/parseHelper.cpp
@@ -32,10 +32,21 @@
 #include "opto/parse.hpp"
 #include "opto/rootnode.hpp"
 #include "opto/runtime.hpp"
 #include "runtime/sharedRuntime.hpp"
 
+#ifndef PRODUCT
+unsigned peaNumAllocsTracked = 0;
+unsigned peaNumMaterializations = 0;
+
+void printPeaStatistics() {
+  tty->print("PEA: ");
+  tty->print("num allocations tracked = %u, ", peaNumAllocsTracked);
+  tty->print_cr("num materializations = %u", peaNumMaterializations);
+}
+#endif
+
 //------------------------------make_dtrace_method_entry_exit ----------------
 // Dtrace -- record entry or exit of a method if compiled with dtrace support
 void GraphKit::make_dtrace_method_entry_exit(ciMethod* method, bool is_entry) {
   const TypeFunc *call_type    = OptoRuntime::dtrace_method_entry_exit_Type();
   address         call_address = is_entry ? CAST_FROM_FN_PTR(address, SharedRuntime::dtrace_method_entry) :
@@ -278,10 +289,15 @@
 
   // Keep track of boxed values for EliminateAutoBox optimizations.
   if (C->eliminate_boxing() && klass->is_box_klass()) {
     C->set_has_boxed_value(true);
   }
+
+  if (DoPartialEscapeAnalysis) {
+    // obj is a CheckCastPP Node, aka. cooked oop.
+    jvms()->alloc_state().add_new_allocation(this, obj);
+  }
 }
 
 #ifndef PRODUCT
 //------------------------------dump_map_adr_mem-------------------------------
 // Debug dump of the mapping from address types to MergeMemNode indices.
@@ -300,5 +316,828 @@
     }
   }
 }
 
 #endif
+
+#include "ci/ciUtilities.inline.hpp"
+#include "compiler/methodMatcher.hpp"
+
+class PEAContext {
+private:
+  BasicMatcher* _matcher;
+
+  PEAContext() {
+    if (PEAMethodOnly != nullptr) {
+      const char* error_msg = nullptr;
+      _matcher = BasicMatcher::parse_method_pattern((char*)PEAMethodOnly, error_msg, false);
+      if (error_msg != nullptr) {
+        tty->print_cr("Invalid PEAMethodOnly: %s", error_msg);
+      }
+    }
+  }
+
+  NONCOPYABLE(PEAContext);
+public:
+  bool match(ciMethod* method) const;
+  // mayer's singleton.
+  static PEAContext& instance() {
+    static PEAContext s;
+    return s;
+  }
+};
+
+//
+// Partial Escape Analysis
+// Stadler, Lukas, Thomas Würthinger, and Hanspeter Mössenböck. "Partial escape analysis and scalar replacement for Java."
+//
+// Our adaption to C2.
+// https://gist.github.com/navyxliu/62a510a5c6b0245164569745d758935b
+//
+
+VirtualState::VirtualState(const TypeOopPtr* oop_type): _oop_type(oop_type), _lockcnt(0) {
+  Compile* C = Compile::current();
+  int nof = nfields();
+  _entries = NEW_ARENA_ARRAY(C->parser_arena(), Node*, nof);
+  // only track explicit stores.
+  // see IntializeNode semantics in memnode.cpp
+  for (int i = 0; i < nof; ++i) {
+    _entries[i] = nullptr;
+  }
+}
+
+// do NOT call base's copy constructor. we would like to reset refcnt!
+VirtualState::VirtualState(const VirtualState& other) : _oop_type(other._oop_type), _lockcnt(other._lockcnt) {
+  int nof = nfields();
+  _entries = NEW_ARENA_ARRAY(Compile::current()->parser_arena(), Node*, nof);
+
+  // Using arraycopy stub is more efficient?
+  Node** dst = _entries;
+  Node** src = other._entries;
+  while (nof-- > 0) {
+    *dst++ = *src++;
+  }
+}
+
+int VirtualState::nfields() const {
+  ciInstanceKlass* holder = _oop_type->is_instptr()->instance_klass();
+  return holder->nof_nonstatic_fields();
+}
+
+void VirtualState::set_field(ciField* field, Node* val) {
+  // We can't trust field->holder() here. It may reference to the super class.
+  // field layouter may flip order in jdk15+, refer to:
+  // https://shipilev.net/jvm/objects-inside-out/#_superhierarchy_gaps_in_java_15
+  //
+  // _oop_type is the exact type when we registered ObjID in allocation state.
+  //
+  ciInstanceKlass* holder = _oop_type->is_instptr()->instance_klass();
+
+  for (int i = 0; i < holder->nof_nonstatic_fields(); ++i) {
+    if (field->offset_in_bytes() == holder->nonstatic_field_at(i)->offset_in_bytes()) {
+      _entries[i] = val;
+      return;
+    }
+  }
+
+  ShouldNotReachHere();
+}
+
+Node* VirtualState::get_field(ciField* field) const {
+  ciInstanceKlass* holder = _oop_type->is_instptr()->instance_klass();
+
+  for (int i = 0; i < holder->nof_nonstatic_fields(); ++i) {
+    if (field->offset_in_bytes() == holder->nonstatic_field_at(i)->offset_in_bytes()) {
+      return _entries[i];
+    }
+  }
+
+  ShouldNotReachHere();
+  return nullptr;
+}
+
+static void ensure_phi(PhiNode* phi, uint pnum) {
+    while (phi->req() <= pnum) {
+      phi->add_req(nullptr);
+    }
+}
+
+static const Type* initialize_null_field(GraphKit* kit, ciField* field, Node*& val) {
+  assert(val == nullptr, "must been a null field");
+  const Type* type;
+  BasicType bt = field->layout_type();
+
+  if (bt == T_OBJECT && field->type()->is_instance_klass()) {
+    val = kit->gvn().makecon(TypePtr::NULL_PTR);
+    type = TypeInstPtr::make(TypePtr::BotPTR, field->type()->as_instance_klass());
+  } else {
+    val = kit->zerocon(bt);
+    type = Type::get_const_basic_type(bt);
+  }
+
+  return type;
+}
+
+ObjectState& VirtualState::merge(ObjectState* newin, GraphKit* kit, RegionNode* r, int pnum) {
+  assert(newin->is_virtual(), "only support VirtualState");
+
+  if (this != newin) {
+    VirtualState* vs = static_cast<VirtualState*>(newin);
+    ciInstanceKlass* ik = _oop_type->is_instptr()->instance_klass();
+    assert(nfields() == ik->nof_nonstatic_fields(), "_nfields should be consistent with instanceKlass");
+
+    for (int i = 0; i < nfields(); ++i) {
+      Node* m = _entries[i];
+
+      if (m != vs->_entries[i]) {
+        ciField* field = ik->nonstatic_field_at(i);
+        Node* n = vs->_entries[i];
+        const Type* tn;
+        if (n == nullptr) {
+          tn = initialize_null_field(kit, field, n);
+        } else {
+          tn = kit->gvn().type(n);
+        }
+
+        if (m == nullptr || !m->is_Phi() || m->in(0) != r) {
+          const Type* type;
+
+          if (m == nullptr) {
+            type = initialize_null_field(kit, field, m);
+          } else {
+            type = kit->gvn().type(m);
+          }
+          type = type->meet(tn);
+
+          m = PhiNode::make(r, m, type);
+          kit->gvn().set_type(m, type);
+          _entries[i] = m;
+        }
+
+        ensure_phi(m->as_Phi(), pnum);
+        m->set_req(pnum, n);
+        if (pnum == 1) {
+          _entries[i] = kit->gvn().transform(m);
+        }
+      }
+    }
+  }
+
+  return *this;
+}
+
+#ifndef PRODUCT
+void VirtualState::print_on(outputStream* os) const {
+  os->print_cr("Virt = %p", this);
+
+  for (int i = 0; i < nfields(); ++i) {
+    Node* val = _entries[i];
+    os->print("#%d: ", i);
+    if (val != nullptr) {
+      val->dump();
+    } else {
+      os->print_cr("_");
+    }
+  }
+}
+
+void EscapedState::print_on(outputStream* os) const {
+  os->print_cr("Escaped = %p %d", this, _materialized);
+  if (_merged_value == nullptr) {
+    os->print_cr(" null");
+  } else {
+    _merged_value->dump();
+  }
+}
+
+#endif
+
+void PEAState::add_new_allocation(GraphKit* kit, Node* obj) {
+  PartialEscapeAnalysis* pea = kit->PEA();
+  int nfields;
+  const TypeOopPtr* oop_type = obj->as_Type()->type()->is_oopptr();
+
+  if (oop_type->isa_aryptr()) {
+    const TypeAryPtr* ary_type = oop_type->is_aryptr();
+    const TypeInt* size = ary_type->size();
+    if (size->is_con() && size->get_con() <= EliminateAllocationArraySizeLimit) {
+      nfields = size->get_con();
+    } else {
+      // length of array is too long or unknown
+      return;
+    }
+  } else {
+    const TypeInstPtr* inst_type = oop_type->is_instptr();
+    nfields = inst_type->instance_klass()->nof_nonstatic_fields();
+  }
+
+  if (nfields >= 0) {
+    AllocateNode* alloc = obj->in(1)->in(0)->as_Allocate();
+    int idx = pea->add_object(alloc);
+#ifndef PRODUCT
+    // node_idx_t is unsigned. Use static_cast<> here to avoid comparison between signed and unsigned.
+    if (PEA_debug_idx > 0 && alloc->_idx != static_cast<node_idx_t>(PEA_debug_idx)) {         // only allow PEA_debug_idx
+      return;
+    } else if (PEA_debug_idx < 0 && alloc->_idx == static_cast<node_idx_t>(-PEA_debug_idx)) { // block PEA_debug_idx
+      return;
+    }
+    Atomic::inc(&peaNumAllocsTracked);
+#endif
+    // Opt out all subclasses of Throwable because C2 will not inline all methods of them including <init>.
+    // PEA needs to materialize it at <init>.
+    ciInstanceKlass* ik = oop_type->is_instptr()->instance_klass();
+    ciEnv* env = ciEnv::current();
+    if (ik->is_subclass_of(env->Throwable_klass())) {
+      return;
+    }
+    // Opt out of all subclasses that non-partial escape analysis opts out of. Opt out of StringBuffer/Builder and
+    // defer those objects to StringOpts.
+    if (ik->is_subclass_of(env->Thread_klass()) ||
+        ik->is_subclass_of(env->Reference_klass()) ||
+        ik->is_subclass_of(env->StringBuffer_klass()) ||
+        ik->is_subclass_of(env->StringBuilder_klass()) ||
+        !ik->can_be_instantiated() || ik->has_finalizer()) {
+      return;
+    }
+    if (idx < PEA_debug_start || idx >= PEA_debug_stop) {
+      return;
+    }
+
+    ciMethod* method = kit->jvms()->method();
+    if (PEAContext::instance().match(method)) {
+#ifndef PRODUCT
+      if (PEAVerbose) {
+        if (method != nullptr) {
+          method->dump_name_as_ascii(tty);
+        }
+        tty->print_cr(" start tracking %d | obj#%d", idx, alloc->_idx);
+        alloc->dump();
+      }
+#endif
+      bool result = _state.put(alloc, new VirtualState(oop_type));
+      assert(result, "the key existed in _state");
+      pea->add_alias(alloc, obj);
+    }
+  }
+}
+
+PEAState& PEAState::operator=(const PEAState& init) {
+  if (this != &init) {
+    clear();
+
+    init._state.iterate([&](ObjID key, ObjectState* value) {
+      _state.put(key, value->clone());
+      return true;
+    });
+  }
+
+#ifdef ASSERT
+    validate();
+#endif
+  return *this;
+}
+
+void PEAState::put_field(GraphKit* kit, ciField* field, Node* objx, Node* val) {
+  Compile* C = kit->C;
+  PartialEscapeAnalysis* pea = C->PEA();
+  int offset = field->offset_in_bytes();
+  Node* adr = kit->basic_plus_adr(objx, objx, offset);
+  const TypePtr* adr_type = C->alias_type(field)->adr_type();
+  DecoratorSet decorators = IN_HEAP;
+
+  BasicType bt = field->layout_type();
+  const Type* type = Type::get_const_basic_type(bt);
+  bool is_obj = is_reference_type(bt);
+
+  if (is_obj && pea->is_alias(val)) {
+    // recurse if val is a virtual object.
+    if (as_virtual(pea, val)) {
+      materialize(kit, val);
+    }
+    EscapedState* es = as_escaped(pea, val);
+    assert(es != nullptr, "the object of val is not Escaped");
+    val = es->merged_value();
+  }
+  // Store the value.
+  const Type* field_type;
+  if (!field->type()->is_loaded()) {
+    field_type = TypeInstPtr::BOTTOM;
+  } else {
+    if (is_obj) {
+      field_type = TypeOopPtr::make_from_klass(field->type()->as_klass());
+    } else {
+      field_type = Type::BOTTOM;
+    }
+  }
+  decorators |= field->is_volatile() ? MO_SEQ_CST : MO_UNORDERED;
+
+#ifndef PRODUCT
+  if (PEAVerbose) {
+    val->dump();
+  }
+#endif
+  kit->access_store_at(objx, adr, adr_type, val, field_type, bt, decorators);
+}
+
+// Because relevant objects may form a directed cyclic graph, materialization is a DFS process.
+// PEA clones the object and marks escaped in allocation state. PEA then iterates all fields
+// and recursively materializes the references which are still aliasing with virtual objects in
+// allocation state.
+Node* PEAState::materialize(GraphKit* kit, Node* var) {
+  Compile* C = kit->C;
+  PartialEscapeAnalysis* pea = C->PEA();
+  ObjID alloc = pea->is_alias(var);
+  VirtualState* virt = static_cast<VirtualState*>(get_object_state(alloc));
+
+  assert(alloc != nullptr && get_object_state(alloc)->is_virtual(), "sanity check");
+#ifndef PRODUCT
+  if (PEAVerbose) {
+    tty->print_cr("PEA materializes a virtual %d obj%d ", pea->object_idx(alloc), alloc->_idx);
+  }
+  Atomic::inc(&peaNumMaterializations);
+#endif
+
+  const TypeOopPtr* oop_type = var->as_Type()->type()->is_oopptr();
+  Node* objx = kit->materialize_object(alloc, oop_type);
+
+  // we save VirtualState beforehand.
+  escape(alloc, objx, true);
+  pea->add_alias(alloc, objx);
+
+  if (virt->lockcnt() > 0 && GenerateSynchronizationCode) {
+    if (PEAVerbose) {
+      tty->print_cr("materializing an object with unbalanced monitor");
+    }
+
+    int mon_id = 0;
+    JVMState* jvms = kit->jvms();
+    int cnt = 0;
+    // It's possible that the locked moninitor is not in the youngest JVMState,
+    // so we have to follow the stacktrace to discover them all.
+    //
+    // PEA Materialization steals those monitors from the original object. Here is the scheme:
+    // 1. unlock the original object.
+    // 2. lock the materialized object.
+    // 3. backfill the obj for Monitor 'obj|box' pair when Parse translates 'monitor-exit'.
+    // 4. split Phi-Unlock in the upcoming monitor_exit (Parse::do_monitor_exit).
+    //
+    while (jvms != nullptr) {
+      for (mon_id = 0;  mon_id < jvms->nof_monitors() && jvms->map()->monitor_obj(jvms, mon_id) != var; ++mon_id);
+
+      if (mon_id < jvms->nof_monitors()) {
+        cnt++;
+        Node* box = jvms->map()->monitor_box(jvms, mon_id);
+        kit->shared_unlock(box, var, true/*preserve_monitor*/); // PEA pops the monitor in Parse::monitor_exit().
+        kit->clone_shared_lock(box, objx);
+      }
+      jvms = jvms->caller();
+    }
+    assert(cnt == virt->lockcnt(), "steal all locks from var");
+  }
+
+  kit->replace_in_map(var, objx);
+
+#ifndef PRODUCT
+  if (PEAVerbose) {
+    tty->print("new object: ");
+    objx->dump();
+  }
+#endif
+
+  if (oop_type->isa_instptr()) {
+    // virt->_oop_type is an exact non-null pointer. oop_type may not be exact, or BOT
+    // We check that they both refer to the same java type.
+    assert(virt->_oop_type->is_instptr()->is_same_java_type_as(oop_type), "type of oopptr is inconsistent!");
+#ifndef PRODUCT
+    if (PEAVerbose) {
+      ciInstanceKlass* ik = oop_type->is_instptr()->instance_klass();
+      tty->print("ciInstanceKlass: ");
+      ik->print_name_on(tty);
+      tty->cr();
+    }
+#endif
+
+    for (auto&& it = virt->field_iterator(); it.has_next(); ++it) {
+      ciField* field = it.field();
+      Node* val = it.value();
+
+#ifndef PRODUCT
+      if (PEAVerbose) {
+        tty->print("field: ");
+        field->print_name_on(tty);
+        tty->cr();
+      }
+#endif
+      // no initial value or is captured by InitializeNode
+      if (val == nullptr) continue;
+
+      put_field(kit, field, objx, val);
+    }
+
+    // back from DFS, we still need to check again for all virtual states.
+    // they may have a field 'var' which has committed to memory via prior putfield. We emit a store with updated objx.
+    // Hopefully, two consecutive stores coalesce.
+    _state.iterate([&](ObjID obj, ObjectState* os) {
+      if (os->is_virtual()) {
+        VirtualState* vs = static_cast<VirtualState*>(os);
+
+        for (auto&& i = vs->field_iterator(); i.has_next(); ++i) {
+          if (i.value() == var) {
+            vs->set_field(i.field(), objx);
+            put_field(kit, i.field(), get_java_oop(obj), objx);
+          }
+        }
+      }
+      return true;
+    });
+
+    // if var is associated with MemBarRelease, copy it for objx
+    for (DUIterator_Fast kmax, k = var->fast_outs(kmax); k < kmax; k++) {
+      Node* use = var->fast_out(k);
+
+      if (use->Opcode() == Op_MemBarRelease) {
+        kit->insert_mem_bar(Op_MemBarRelease, objx);
+        break;
+      }
+    }
+  } else {
+    assert(false, "array not support yet!");
+  }
+
+#ifdef ASSERT
+  validate();
+#endif
+  return objx;
+}
+
+#ifndef PRODUCT
+void PEAState::print_on(outputStream* os) const {
+  if (size() > 0) {
+    os->print_cr("PEAState:");
+  }
+
+  _state.iterate([&](ObjID obj, ObjectState* state) {
+    bool is_virt = state->is_virtual();
+    os->print("Obj#%d(%s) ref = %d\n", obj->_idx, is_virt ? "Virt" : "Mat", state->ref_cnt());
+
+    if (is_virt) {
+      VirtualState* vs = static_cast<VirtualState*>(state);
+      vs->print_on(os);
+    } else {
+      EscapedState* es = static_cast<EscapedState*>(state);
+      es->print_on(tty);
+    }
+    return true;
+  });
+}
+
+#endif
+
+#ifdef ASSERT
+void PEAState::validate() const {
+}
+#endif
+
+bool safepointContains(SafePointNode* sfpt, Node *oop) {
+  for (uint i = TypeFunc::Parms; i < sfpt->req(); ++i) {
+    if (oop == sfpt->in(i)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void PEAState::mark_all_escaped(PartialEscapeAnalysis* pea, ObjID id, Node* obj) {
+  VirtualState* virt = as_virtual(pea, obj);
+  escape(id, obj, false);
+
+  for (auto&& it = virt->field_iterator(); it.has_next(); ++it) {
+    ciField* field = it.field();
+    Node* val = it.value();
+
+    BasicType bt = field->layout_type();
+    bool is_obj = is_reference_type(bt);
+
+    ObjID alias = pea->is_alias(val);
+    if (is_obj && alias != nullptr) {
+      // recurse if val is a virtual object.
+      if (get_object_state(alias)->is_virtual()) {
+        mark_all_escaped(pea, alias, val);
+      }
+      assert(as_escaped(pea, val) != nullptr, "the object of val is not Escaped");
+    }
+  }
+}
+
+void PEAState::mark_all_live_objects_escaped(PartialEscapeAnalysis *pea, SafePointNode* sfpt) {
+  Unique_Node_List objs;
+  int sz = objects(objs);
+
+  for (int i = 0; i < sz; ++i) {
+    ObjID id = static_cast<ObjID>(objs.at(i));
+    ObjectState* os = get_object_state(id);
+
+    if (os->is_virtual()) {
+      Node *oop = get_java_oop(id);
+      // We only need to mark objects that are live as escaped.
+      if (safepointContains(sfpt, oop)) {
+        mark_all_escaped(pea, id, oop);
+      }
+    }
+  }
+}
+
+// get the key set from _state. we stop maintaining aliases for the materialized objects.
+int PEAState::objects(Unique_Node_List& nodes) const {
+  _state.iterate([&](ObjID obj, ObjectState* state) {
+                   nodes.push(obj); return true;
+                 });
+  return nodes.size();
+}
+
+// We track '_merged_value' along with control-flow but only return it if _materialized = true;
+// GraphKit::backfill_materialized() replaces the original CheckCastPP with it at do_exits() or at safepoints.
+// If materialization doesn't take place, replacement shouldn't happen either.
+//
+// @return: nullptr if id has not been materialized, or the SSA java_oop that denotes the original object.
+Node* PEAState::get_materialized_value(ObjID id) const {
+  assert(contains(id), "must exists in allocation");
+  ObjectState* os = get_object_state(id);
+
+  if (os->is_virtual()) {
+    return nullptr;
+  } else {
+    return static_cast<EscapedState*>(os)->materialized_value();
+  }
+}
+
+Node* PEAState::get_java_oop(ObjID id) const {
+  if (!contains(id)) return nullptr;
+
+  Node* obj = get_materialized_value(id);
+  if (obj != nullptr) {
+    return obj;
+  }
+
+  ProjNode* resproj = id->proj_out_or_null(TypeFunc::Parms);
+  if (resproj != nullptr) {
+    for (DUIterator_Fast imax, i = resproj->fast_outs(imax); i < imax; i++) {
+      Node* p = resproj->fast_out(i);
+      if (p->is_CheckCastPP()) {
+        assert(obj == nullptr, "multiple CheckCastPP?");
+        obj = p;
+      }
+    }
+  }
+  assert(obj == nullptr || AllocateNode::Ideal_allocation(obj) == id, "sanity check");
+  return obj;
+}
+
+AllocationStateMerger::AllocationStateMerger(PEAState& target) : _state(target) {}
+
+void AllocationStateMerger::merge(PEAState& newin, GraphKit* kit, RegionNode* region, int pnum) {
+  PartialEscapeAnalysis* pea = kit->PEA();
+  Unique_Node_List set1, set2;
+
+  _state.objects(set1);
+  newin.objects(set2);
+
+  VectorSet intersection = intersect(set1.member_set(), set2.member_set());
+  set1.remove_useless_nodes(intersection);
+
+  for (uint i = 0; i < set1.size(); ++i) {
+    ObjID obj = static_cast<ObjID>(set1.at(i));
+    ObjectState* os1 = _state.get_object_state(obj);
+    ObjectState* os2 = newin.get_object_state(obj);
+    if (os1->is_virtual() && os2->is_virtual()) {
+      os1->merge(os2, kit, region, pnum);
+    } else {
+      assert(os1 != nullptr && os2 != nullptr, "sanity check");
+      Node* m;
+      Node* n;
+      bool materialized;
+      EscapedState* es;
+
+      if (os1->is_virtual()) {
+        // If obj is virtual in current state,  it must be escaped in newin.
+        // Mark it escaped in current state.
+        EscapedState* es2 = static_cast<EscapedState*>(os2);
+        materialized = es2->has_materialized();
+        m = _state.get_java_oop(obj);
+        n = es2->merged_value();
+        es = _state.escape(obj, m, materialized);
+      } else if (os2->is_virtual()) {
+        // If obj is virtual in newin,  it must be escaped in current state.
+        // Mark it escaped  in newin
+        es = static_cast<EscapedState*>(os1);
+        materialized = es->has_materialized();
+        m = es->merged_value();
+        n = newin.get_java_oop(obj);
+        os2 = newin.escape(obj, n, false);
+      } else {
+        // obj is escaped in both newin and current state.
+        es = static_cast<EscapedState*>(os1);
+        EscapedState* es2 = static_cast<EscapedState*>(os2);
+        m = es->merged_value();
+        n = es2->merged_value();
+        materialized = es->has_materialized() || es2->has_materialized();
+      }
+
+      if (m->is_Phi() && m->in(0) == region) {
+        ensure_phi(m->as_Phi(), pnum);
+        // only update the pnum if we have never seen it before.
+        if (m->in(pnum) == nullptr) {
+          m->set_req(pnum, n);
+        }
+      } else if (m != n) {
+        const Type* type = obj->oop_type(kit->gvn());
+        Node* phi = PhiNode::make(region, m, type);
+        phi->set_req(pnum, n);
+        kit->gvn().set_type(phi, type);
+        es->update(materialized, phi);
+      }
+    }
+  }
+
+  // process individual phi
+  SafePointNode* map = kit->map();
+  for (uint i = 0; i < map->req(); ++i) {
+    Node* node = map->in(i);
+
+    if (node != nullptr && node->is_Phi() && node->as_Phi()->region() == region) {
+      process_phi(node->as_Phi(), kit, region, pnum);
+    }
+  }
+
+#ifdef ASSERT
+  _state.validate();
+#endif
+}
+
+// Passive Materialization
+// ------------------------
+// Materialize an object at the phi node because at least one of its predecessors has materialized the object.
+// Since C2 PEA does not eliminate the original allocation, we skip passive materializaiton and keep using it.
+// The only problem is partial redudancy. JDK-8287061 should address this issue.
+//
+// PEA split a object based on its escapement. At the merging point, the original object is NonEscape, or it has already
+// been materialized before. the phi is 'reducible Object-Phi' in JDK-828706 and the original object is scalar replaceable!
+//
+// obj' = PHI(Region, OriginalObj, ClonedObj)
+// and OriginalObj is NonEscape but NSR; CloendObj is Global/ArgEscape
+//
+// JDK-8287061 transforms it to =>
+// obj' = PHI(Region, null, ClonedObj)
+// selector = PHI(Region, 0, 1)
+//
+// since OriginalObj is NonEscape, it is replaced by scalars.
+//
+static Node* ensure_object_materialized(Node* var, PEAState& state, SafePointNode* from_map, RegionNode* r, int pnum) {
+  // skip passive materialize for time being.
+  // if JDK-8287061 can guarantee to replace the orignial allocation, we don't need to worry about partial redundancy.
+  return var;
+}
+
+// Merge phi node incrementally.
+// we check all merged inputs in _state.
+// 1. all inputs refer to the same ObjID, then phi is created as alias of ObjID
+// 2. otherwise, any input is alias with a 'virtual' object needs to convert to 'Escaped'. replace input with merged_value.
+// 3. otherwise, if any input is aliased with an Escaped object. replace input with merged value.
+void AllocationStateMerger::process_phi(PhiNode* phi, GraphKit* kit, RegionNode* region, int pnum) {
+  ObjID unique = nullptr;
+  bool materialized = false;
+  bool same_obj = true;
+  PartialEscapeAnalysis* pea = kit->PEA();
+
+  if (pea == nullptr) return;
+
+  for (uint i = 1; i < phi->req(); ++i) {
+    if (region->in(i) == nullptr || region->in(i)->is_top())
+      continue;
+
+    Node* node = phi->in(i);
+    ObjID obj = pea->is_alias(node);
+    if (obj != nullptr) {
+      if (unique == nullptr) {
+        unique = obj;
+      } else if (unique != obj) {
+        same_obj = false;
+      }
+      EscapedState* es = _state.as_escaped(pea, node);
+      if (es != nullptr) {
+        materialized |= es->has_materialized();
+      }
+    } else {
+      same_obj = false;
+    }
+  }
+
+  if (same_obj) {
+    //xliu: should I also check pnum == 1?
+    // phi nodes for exception handler may have leave normal paths vacant.
+    pea->add_alias(unique, phi);
+  } else {
+    bool printed = false;
+
+    for (uint i = 1; i < phi->req(); ++i) {
+      if (region->in(i) == nullptr || region->in(i)->is_top())
+        continue;
+
+      Node* node = phi->in(i);
+      ObjID obj = pea->is_alias(node);
+      if (obj != nullptr && _state.contains(obj)) {
+        ObjectState* os = _state.get_object_state(obj);
+        if (os->is_virtual()) {
+          Node* n = ensure_object_materialized(node, _state, kit->map(), region, pnum);
+          os = _state.escape(obj, n, materialized);
+        }
+        EscapedState* es = static_cast<EscapedState*>(os);
+        Node* value = es->merged_value();
+        if (value->is_Phi() && value->in(0) == region) {
+          value = value->in(i);
+        }
+
+        if (node != value) {
+          assert(value != phi, "sanity");
+#ifndef PRODUCT
+          if (PEAVerbose) {
+            if (!printed) {
+              phi->dump();
+              printed = true;
+            }
+            tty->print_cr("[PEA] replace %dth input with node %d", i, value->_idx);
+          }
+#endif
+          phi->replace_edge(node, value);
+        }
+      }
+    }
+    ObjID obj = pea->is_alias(phi);
+    if (obj != nullptr) {
+      pea->remove_alias(obj, phi);
+    }
+  }
+}
+
+void AllocationStateMerger::merge_at_phi_creation(const PartialEscapeAnalysis* pea, PEAState& newin, PhiNode* phi, Node* m, Node* n) {
+  ObjID obj1 = pea->is_alias(m);
+  ObjID obj2 = pea->is_alias(n);
+
+  if (_state.contains(obj1)) { // m points to an object that as is tracking.
+    ObjectState* os1 = _state.get_object_state(obj1);
+    ObjectState* os2 = newin.contains(obj2) ? newin.get_object_state(obj2) : nullptr;
+
+    // obj1 != obj2 if n points to something else. It could be the other object, null or a ConP.
+    // we do nothing here because PEA doesn't create phi in this case.
+    if (obj1 == obj2 && os2 != nullptr) { // n points to the same object and pred_as is trakcing.
+      if (!os1->is_virtual() || !os2->is_virtual()) {
+        if (os2->is_virtual()) {
+          // passive materialize
+          os2 = newin.escape(obj2, n, false);
+        }
+
+        if (os1->is_virtual()) {
+          bool materialized = static_cast<EscapedState*>(os2)->has_materialized();
+          _state.escape(obj1, phi, materialized);
+        } else {
+          static_cast<EscapedState*>(os1)->update(phi);
+        }
+      }
+    }
+  }
+}
+
+AllocationStateMerger::~AllocationStateMerger() {
+}
+
+bool PEAContext::match(ciMethod* method) const {
+  if (_matcher != nullptr && method != nullptr) {
+    VM_ENTRY_MARK;
+    methodHandle mh(THREAD, method->get_Method());
+    return _matcher->match(mh);
+  }
+  return true;
+}
+
+EscapedState* PEAState::escape(ObjID id, Node* p, bool materialized) {
+  assert(p != nullptr, "the new alias must be non-null");
+  Node* old = nullptr;
+  EscapedState* es;
+
+  if (contains(id)) {
+    ObjectState* os = get_object_state(id);
+    // if os is EscapedState and its materialized_value is not-null,
+    if (!os->is_virtual()) {
+      materialized |= static_cast<EscapedState*>(os)->has_materialized();
+    }
+    es = new EscapedState(materialized, p);
+    es->ref_cnt(os->ref_cnt()); // copy the refcnt from the original ObjectState.
+  } else {
+    es = new EscapedState(materialized, p);
+  }
+  _state.put(id, es);
+  if (materialized) {
+    static_cast<AllocateNode*>(id)->inc_materialized();
+  }
+  assert(contains(id), "sanity check");
+  return es;
+}
diff a/src/hotspot/share/opto/partialEscape.hpp b/src/hotspot/share/opto/partialEscape.hpp
--- /dev/null
+++ b/src/hotspot/share/opto/partialEscape.hpp
@@ -0,0 +1,340 @@
+/*
+ * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#ifndef SHARE_OPTO_PARTIAL_ESCAPE_HPP
+#define SHARE_OPTO_PARTIAL_ESCAPE_HPP
+
+#include "opto/type.hpp"
+#include "utilities/resourceHash.hpp"
+
+// forward declaration
+class GraphKit;
+
+class ObjectState {
+  friend class PEAState;
+
+  //  ObjectState(const ObjectState&) = delete;
+ protected:
+  int _refcnt = 0;
+  void ref_inc() { _refcnt++; }
+  int ref_dec() { return --_refcnt; }
+  int ref_cnt(int cnt) {
+    int old = _refcnt;
+    _refcnt = cnt;
+    return old;
+  }
+
+ public:
+  inline void* operator new(size_t x) throw() {
+    Compile* C = Compile::current();
+    return C->parser_arena()->AmallocWords(x);
+  }
+
+  virtual bool is_virtual() const = 0;
+  // clone contents but not refcnt;
+  virtual ObjectState* clone() const = 0;
+
+  int ref_cnt() const { return _refcnt; }
+
+  virtual ObjectState& merge(ObjectState* newin, GraphKit* kit, RegionNode* region, int pnum) = 0;
+};
+
+class VirtualState: public ObjectState {
+  friend class PEAState;
+  const TypeOopPtr* const _oop_type;
+  int _lockcnt;
+  Node** _entries;
+
+ protected:
+  VirtualState(const VirtualState& other);
+
+ public:
+  VirtualState(const TypeOopPtr* oop_type);
+
+  bool is_virtual() const override { return true; }
+
+  ObjectState* clone() const override {
+    return new VirtualState(*this);
+  }
+
+  int nfields() const;
+  void set_field(ciField* field, Node* val);
+  Node* get_field(ciField* field) const;
+
+  ObjectState& merge(ObjectState* newin, GraphKit* kit, RegionNode* region, int pnum) override;
+
+  void lock_inc() { _lockcnt++; }
+  void lock_dec() {
+    assert(_lockcnt > 0, "sanity check");
+    --_lockcnt;
+  }
+  int lockcnt() const {
+    return _lockcnt;
+  }
+
+#ifndef PRODUCT
+  void print_on(outputStream* os) const;
+#endif
+
+  class iterator {
+   private:
+    const VirtualState* _state;
+    int _end;
+    int _idx;
+
+   public:
+    iterator(const VirtualState* state): _state(state), _idx(0) {
+      const TypeInstPtr* instptr = _state->_oop_type->isa_instptr();
+      if (instptr != nullptr) {
+        ciInstanceKlass* holder = instptr->instance_klass();
+        _end = holder->nof_nonstatic_fields();
+      } else {
+        _end = 0;
+      }
+    }
+
+    bool has_next() const {
+      return _idx < _end;
+    }
+
+    void operator++() {
+      assert(has_next(), "out of range.");
+      ++_idx;
+    }
+
+    ciField* field() const {
+      assert(_idx < _end, "sanity");
+      ciInstanceKlass* ik = _state->_oop_type->is_instptr()->instance_klass();
+      return ik->nonstatic_field_at(_idx);
+    }
+
+    Node* value() const {
+      assert(_idx < _end, "sanity check");
+      assert(_idx >= 0 && _idx < _state->nfields(), "field is out of range");
+
+      return _state->_entries[_idx];
+    }
+  };
+
+  iterator field_iterator() {
+    return {this};
+  }
+};
+
+class EscapedState: public ObjectState {
+  bool  _materialized; // we may skip passive materialization.
+  Node* _merged_value; // the latest java_oop
+
+ public:
+  EscapedState(bool materialized, Node* value) : _materialized(materialized), _merged_value(value) {}
+
+  bool is_virtual() const override { return false;}
+
+  Node* materialized_value() const {
+    return _materialized ? _merged_value : nullptr;
+  }
+
+  Node* merged_value() const {
+    return _merged_value;
+  }
+
+  bool has_materialized() const {
+    return _materialized;
+  }
+
+  void update(bool materialized, Node* node) {
+    assert(node != nullptr, "assign a nullptr as merged value");
+    assert(materialized || !_materialized, "reverting _materialized is wrong");
+
+    _materialized = materialized;
+    _merged_value = node;
+  }
+
+  void update(Node* node) {
+    update(_materialized, node);
+  }
+
+  ObjectState* clone() const override {
+    return new EscapedState(_materialized, _merged_value);
+  }
+
+  ObjectState& merge(ObjectState* newin, GraphKit* kit, RegionNode* region, int pnum) override {
+    assert(0, "not implemented");
+    return *this;
+  }
+
+#ifndef PRODUCT
+  void print_on(outputStream* os) const;
+#endif
+};
+
+template<typename K, typename V>
+using PEAMap = ResourceHashtable<K, V, 17, /*table_size*/
+                                 AnyObj::C_HEAP, mtCompiler>;
+
+class PartialEscapeAnalysis : public AnyObj {
+  // alias maps from node to ObjID.
+  PEAMap<Node*, ObjID>  _aliases;
+  GrowableArray<ObjID>  _objects;
+
+public:
+  PartialEscapeAnalysis(Arena* arena): _aliases(), _objects(arena, 2, 0, nullptr) {}
+
+  ObjID is_alias(Node* node) const {
+    assert(node != nullptr || !_aliases.contains(node), "_aliases.contain(nullptr) must return false");
+    if (_aliases.contains(node)) {
+      return *(_aliases.get(node));
+    } else {
+      return nullptr;
+    }
+  }
+
+  // refcount is the no. of aliases which refer to the object.
+  // we do garbage collection if refcnt drops to 0.
+  void add_alias(ObjID id, Node* var) {
+    assert(!_aliases.contains(var) || (*_aliases.get(var)) == id, "redefine an alias to another object");
+    if (_aliases.contains(var)) return;
+    _aliases.put(var, id);
+  }
+
+  void remove_alias(ObjID id, Node* var) {
+    assert(_aliases.contains(var) && (*_aliases.get(var)) == id, "sanity check");
+    _aliases.remove(var);
+  }
+
+  // PEA tracks all new instances in the current compilation unit
+  // so we could bisect for bugs.
+  int add_object(ObjID obj) {
+    _objects.push(obj);
+    return _objects.length() - 1;
+  }
+
+  int object_idx(ObjID obj) const {
+    return _objects.find(obj);
+  }
+
+  const GrowableArray<ObjID>& all_objects() const {
+    return _objects;
+  }
+};
+
+//TODO: rename it to allocation state.
+class PEAState {
+  friend class Parse;
+  PEAMap<ObjID, ObjectState*> _state;
+
+ public:
+  PEAState& operator=(const PEAState& init);
+
+  ObjectState* get_object_state(ObjID id) const {
+    assert(contains(id), "object doesn't exist in allocation state");
+    return *(_state.get(id));
+  }
+
+  bool contains(ObjID id) const {
+#ifdef ASSERT
+    if (id == nullptr) {
+      assert(!_state.contains(id), "PEAState must exclude nullptr");
+    }
+#endif
+    return _state.contains(id);
+  }
+
+  Node* get_materialized_value(ObjID id) const;
+
+  Node* get_java_oop(ObjID id) const;
+
+  // Convert the state of obj#id to Escaped.
+  // p is the new alias of obj#id. If materialized is true, the materiazation has taken place in code.
+  // PEA expects to replace all appearances of the object with its java_oop, or materilzied_value().
+  // refer to GraphKit::backfill_materialized.
+  EscapedState* escape(ObjID id, Node* p, bool materialized);
+
+  void add_new_allocation(GraphKit* kit, Node* obj);
+
+  Node* materialize(GraphKit* kit, Node* var);
+
+  void put_field(GraphKit* kit, ciField* field, Node* objx, Node* val);
+
+  int objects(Unique_Node_List& nodes) const;
+  int size() const {
+    return _state.number_of_entries();
+  }
+
+  VirtualState* as_virtual(const PartialEscapeAnalysis* pea, Node* var) const {
+    ObjID id = pea->is_alias(var);
+    if (id != nullptr && contains(id)) {
+      ObjectState* os = get_object_state(id);
+
+      if (os->is_virtual()) {
+        return static_cast<VirtualState*>(os);
+      }
+    }
+    return nullptr;
+  }
+
+  EscapedState* as_escaped(const PartialEscapeAnalysis* pea, Node* var) const {
+    ObjID id = pea->is_alias(var);
+    if (id != nullptr && contains(id)) {
+      ObjectState* os = get_object_state(id);
+
+      if (!os->is_virtual()) {
+        return static_cast<EscapedState*>(os);
+      }
+    }
+    return nullptr;
+  }
+
+  void clear() {
+    _state.unlink_all();
+  }
+
+  // we drop out all virtual objects when we encounter a loop header.
+  void mark_all_escaped(PartialEscapeAnalysis* pea, ObjID id, Node *obj);
+  void mark_all_live_objects_escaped(PartialEscapeAnalysis *pea, SafePointNode* map);
+
+#ifndef PRODUCT
+  void print_on(outputStream* os) const;
+#endif
+
+#ifdef ASSERT
+  void validate() const;
+#endif
+};
+
+class AllocationStateMerger {
+ private:
+  GrowableArray<ObjID> _live; // live objects
+  PEAState& _state;
+  void process_phi(PhiNode* phi, GraphKit* kit, RegionNode* region, int pnum);
+
+ public:
+  AllocationStateMerger(PEAState& target);
+  ~AllocationStateMerger(); // purge all objects which are not in live.
+  void merge(PEAState& newin, GraphKit* kit, RegionNode* region, int pnum);
+  void merge_at_phi_creation(const PartialEscapeAnalysis* pea, PEAState& newin, PhiNode* phi, Node* m, Node* n);
+};
+
+void printPeaStatistics();
+#endif // SHARE_OPTO_PARTIAL_ESCAPE_HPP
diff a/src/hotspot/share/utilities/resourceHash.hpp b/src/hotspot/share/utilities/resourceHash.hpp
--- a/src/hotspot/share/utilities/resourceHash.hpp
+++ b/src/hotspot/share/utilities/resourceHash.hpp
@@ -104,21 +104,11 @@
   ResourceHashtableBase(unsigned size) : STORAGE(size), _number_of_entries(0) {}
   NONCOPYABLE(ResourceHashtableBase);
 
   ~ResourceHashtableBase() {
     if (ALLOC_TYPE == AnyObj::C_HEAP) {
-      Node* const* bucket = table();
-      const unsigned sz = table_size();
-      while (bucket < bucket_at(sz)) {
-        Node* node = *bucket;
-        while (node != nullptr) {
-          Node* cur = node;
-          node = node->_next;
-          delete cur;
-        }
-        ++bucket;
-      }
+      unlink_all();
     }
   }
 
  public:
   unsigned table_size() const { return STORAGE::table_size(); }
@@ -293,12 +283,15 @@
   // called for each entry in the table.  If do_entry() returns true,
   // the entry is deleted.
   template<class ITER>
   void unlink(ITER* iter) {
     const unsigned sz = table_size();
-    for (unsigned index = 0; index < sz; index++) {
+    int cnt = _number_of_entries;
+
+    for (unsigned index = 0; cnt > 0 && index < sz; ++index) {
       Node** ptr = bucket_at(index);
+
       while (*ptr != nullptr) {
         Node* node = *ptr;
         // do_entry must clean up the key and value in Node.
         bool clean = iter->do_entry(node->_key, node->_value);
         if (clean) {
@@ -308,12 +301,42 @@
           }
           _number_of_entries --;
         } else {
           ptr = &(node->_next);
         }
+        if (--cnt <= 0) return;
+      }
+    }
+  }
+
+  // unlink_all() is a specialized version of unlink() when we decide to remove all elements.
+  // It can not replace unlink(ITER* iter) if user-provided iter releases key/value
+  void unlink_all() {
+    Node** bucket = table();
+    const unsigned sz = table_size();
+
+    while (_number_of_entries > 0 && bucket < bucket_at(sz)) {
+      Node* node = *bucket;
+      int n = 0;
+
+      while (node != NULL) {
+        Node* cur = node;
+        node = node->_next;
+        if (ALLOC_TYPE == AnyObj::C_HEAP) {
+          delete cur;
+        }
+        n++;
       }
+
+      if (n > 0) {
+        *bucket = nullptr;
+        _number_of_entries -= n;
+      }
+      bucket++;
     }
+
+    assert(_number_of_entries == 0, "sanity check");
   }
 
   template<typename Function>
   TableStatistics statistics_calculate(Function size_function) const {
     NumberSeq summary;
diff a/test/hotspot/gtest/utilities/test_resourceHash.cpp b/test/hotspot/gtest/utilities/test_resourceHash.cpp
--- a/test/hotspot/gtest/utilities/test_resourceHash.cpp
+++ b/test/hotspot/gtest/utilities/test_resourceHash.cpp
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.
@@ -150,11 +150,11 @@
 
       ASSERT_TRUE(rh.remove(as_K(step)));
       ASSERT_FALSE(rh.contains(as_K(step)));
       rh.iterate(&et);
 
-
+      rh.unlink_all();
     }
   };
 };
 
 TEST_VM_F(SmallResourceHashtableTest, default) {
@@ -257,10 +257,16 @@
         ASSERT_TRUE(rh.put(as_K(index), index));
       }
       DeleterTestIter dt(5);
       rh.unlink(&dt);
       ASSERT_FALSE(rh.get(as_K(5)));
+
+      rh.unlink_all();
+      for (uintptr_t i = 10; i > 0; --i) {
+        uintptr_t index = i - 1;
+        ASSERT_FALSE(rh.get(as_K(index)));
+      }
     }
   };
 };
 
 TEST_VM_F(GenericResourceHashtableTest, default) {
@@ -328,10 +334,22 @@
   SimpleDeleter deleter;
   _simple_test_table.unlink(&deleter);
   ASSERT_EQ(s->refcount(), s_orig_count) << "refcount should be same as start";
 }
 
+TEST_VM_F(SimpleResourceHashtableDeleteTest, simle_unlink_all) {
+  TempNewSymbol t = SymbolTable::new_symbol("abcdefg_simple");
+  Symbol* s = t;
+  int s_orig_count = s->refcount();
+  _simple_test_table.put(s, 66);
+  ASSERT_EQ(s->refcount(), s_orig_count + 1) << "refcount should be incremented in table";
+
+  // Use unlink_all to remove the matching (or all) values from the table.
+  _simple_test_table.unlink_all();
+  ASSERT_EQ(s->refcount(), s_orig_count) << "refcount should be same as start";
+}
+
 // More complicated ResourceHashtable with SymbolHandle in the key. Since the *same* Symbol is part
 // of the value, it's not necessary to manipulate the refcount of the key, but you must in the value.
 // Luckily SymbolHandle does this.
 class ResourceHashtableDeleteTest : public ::testing::Test {
  public:
@@ -409,10 +427,25 @@
   ASSERT_EQ(d->refcount(), d_orig_count + 1) << "refcount incremented in table";
   Deleter deleter;
   _test_table.unlink(&deleter);
   ASSERT_EQ(d->refcount(), d_orig_count) << "refcount should be as we started";
 }
+
+TEST_VM_F(ResourceHashtableDeleteTest, value_unlink_all) {
+  TempNewSymbol d = SymbolTable::new_symbol("defghijklmnop");
+  int d_orig_count = d->refcount();
+  {
+    TestValue tv(d);
+    // Same as above, but the do_entry does nothing because the value is deleted when the
+    // hashtable node is deleted.
+    _test_table.put(d, tv);
+    ASSERT_EQ(d->refcount(), d_orig_count + 2) << "refcount incremented by copy";
+  }
+  ASSERT_EQ(d->refcount(), d_orig_count + 1) << "refcount incremented in table";
+  _test_table.unlink_all();
+  ASSERT_EQ(d->refcount(), d_orig_count) << "refcount should be as we started";
+}
 
 TEST_VM_F(ResourceHashtableDeleteTest, check_delete_ptr) {
   TempNewSymbol s = SymbolTable::new_symbol("abcdefg_ptr");
   int s_orig_count = s->refcount();
   {
diff a/test/hotspot/jtreg/compiler/c2/irTests/blackhole/BlackholeSyncEATest.java b/test/hotspot/jtreg/compiler/c2/irTests/blackhole/BlackholeSyncEATest.java
--- a/test/hotspot/jtreg/compiler/c2/irTests/blackhole/BlackholeSyncEATest.java
+++ b/test/hotspot/jtreg/compiler/c2/irTests/blackhole/BlackholeSyncEATest.java
@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2022, Red Hat, Inc. All rights reserved.
+ * Copyright (c) 2022, 2024 Red Hat, Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.
@@ -36,12 +36,14 @@
 import jdk.test.lib.Asserts;
 
 public class BlackholeSyncEATest {
 
     public static void main(String[] args) {
+        // Turn off PEA or it will elide the lock of synchronized (o){} before escaping.
         TestFramework.runWithFlags(
             "-XX:+UnlockExperimentalVMOptions",
+            "-XX:-DoPartialEscapeAnalysis",
             "-XX:CompileCommand=blackhole,compiler.c2.irTests.blackhole.BlackholeSyncEATest::blackhole",
             "-XX:CompileCommand=dontinline,compiler.c2.irTests.blackhole.BlackholeSyncEATest::dontinline"
         );
     }
 
diff a/test/micro/org/openjdk/bench/vm/compiler/pea/HashMapBench.java b/test/micro/org/openjdk/bench/vm/compiler/pea/HashMapBench.java
--- /dev/null
+++ b/test/micro/org/openjdk/bench/vm/compiler/pea/HashMapBench.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package org.openjdk.bench.vm.compiler.pea;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Warmup;
+
+import java.util.concurrent.TimeUnit;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
+@State(Scope.Benchmark)
+@Fork(value = 3)
+public class HashMapBench {
+    class Key {
+        int x;
+        Key(int x) { this.x = x; }
+        public int hashCode() { return x; }
+        public boolean equals(Object other) { return this.hashCode() == other.hashCode(); }
+    }
+
+    static void blackhole(Object o) {}
+
+    @Param("1024")
+    private int size;
+
+    @Param({"1", "2", "4"})
+    private int fillInterval;
+
+    ArrayList<Key> keys;
+    HashMap<Key, Object> map;
+
+    @Setup
+    public void setUp() {
+        keys = new ArrayList<>();
+        map = new HashMap<>();
+        for (int i = 0; i < size; ++i) {
+            keys.add(new Key(i));
+            if (i % fillInterval == 0) {
+                map.put(new Key(i), new Object());
+            }
+        }
+    }
+
+    @Benchmark
+    public void replace() {
+        for (int i = 0; i < size; ++i) {
+            map.replace(keys.get(i), new Object());
+        }
+    }
+
+    @Benchmark
+    public void computeIfAbsent() {
+        for (int i = 0; i < size; ++i) {
+            map.computeIfAbsent(new Key(i), key -> new Object());
+        }
+    }
+
+    @Benchmark
+    public void cacheUpdate() {
+        for (int i = 0; i < size; ++i) {
+            Key key = new Key(i);
+            if (!map.containsKey(key)) {
+                map.put(key, new Object());
+            }
+        }
+    }
+}
+
diff a/test/micro/org/openjdk/bench/vm/compiler/pea/Simple.java b/test/micro/org/openjdk/bench/vm/compiler/pea/Simple.java
--- /dev/null
+++ b/test/micro/org/openjdk/bench/vm/compiler/pea/Simple.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package org.openjdk.bench.vm.compiler.pea;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.CompilerControl;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Warmup;
+
+import java.util.concurrent.TimeUnit;
+
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 20, time = 1, timeUnit = TimeUnit.SECONDS)
+@State(Scope.Benchmark)
+@Fork(value = 3)
+public class Simple {
+    @Param({"1", "2", "4", "8"})
+    int odd;
+    private Object _cache;
+
+    void example1(boolean cond) {
+        Object x = new Object();
+        if (cond) {
+            _cache = x;
+        }
+    }
+
+    @Benchmark
+    public void example1() {
+        for (int i = 0; i< 1024; ++i) {
+            boolean cond = (i % odd) == 0;
+            example1(cond);
+        }
+    }
+    @CompilerControl(CompilerControl.Mode.DONT_INLINE)
+    static void escaped_as_argument(Object x) {}
+
+    // Ivanov suggests making this happen first.
+    // we don't need to create JVMState for the cloning Allocate.
+    @Benchmark
+    public void example1_ArgEscape() {
+        for (int i = 0; i< 1024; ++i) {
+            boolean cond = (i % odd) == 0;
+            Object x = new Object();
+
+            if (cond) {
+                escaped_as_argument(x);
+            }
+        }
+    }
+}
