diff --git a/src/java.base/share/classes/java/util/TreeMap.java b/src/java.base/share/classes/java/util/TreeMap.java
index 8e8711ddfd7..5aa95bc6482 100644
--- a/src/java.base/share/classes/java/util/TreeMap.java
+++ b/src/java.base/share/classes/java/util/TreeMap.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2020, 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
@@ -29,6 +29,7 @@ import java.io.Serializable;
 import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
+import java.util.function.Function;
 
 /**
  * A Red-Black tree based {@link NavigableMap} implementation.
@@ -341,8 +342,7 @@ public class TreeMap<K,V>
         // Offload comparator-based version for sake of performance
         if (comparator != null)
             return getEntryUsingComparator(key);
-        if (key == null)
-            throw new NullPointerException();
+        Objects.requireNonNull(key);
         @SuppressWarnings("unchecked")
             Comparable<? super K> k = (Comparable<? super K>) key;
         Entry<K,V> p = root;
@@ -531,14 +531,37 @@ public class TreeMap<K,V>
      *         does not permit null keys
      */
     public V put(K key, V value) {
+        return put(key, value, true);
+    }
+
+    @Override
+    public V putIfAbsent(K key, V value) {
+        return put(key, value, false);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This method will, on a best-effort basis, throw a
+     * {@link ConcurrentModificationException} if it is detected that the
+     * mapping function modifies this map during computation.
+     *
+     * @throws ConcurrentModificationException if it is detected that the
+     * mapping function modified this map
+     */
+    @Override
+    public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
+        Objects.requireNonNull(mappingFunction);
+        V newValue;
         Entry<K,V> t = root;
         if (t == null) {
-            compare(key, key); // type (and possibly null) check
-
-            root = new Entry<>(key, value, null);
-            size = 1;
-            modCount++;
-            return null;
+            newValue = callMappingFunctionWithCheck(key, mappingFunction);
+            if (newValue != null) {
+                addEntryToEmptyMap(key, newValue);
+                return newValue;
+            } else {
+                return null;
+            }
         }
         int cmp;
         Entry<K,V> parent;
@@ -553,14 +576,12 @@ public class TreeMap<K,V>
                 else if (cmp > 0)
                     t = t.right;
                 else
-                    return t.setValue(value);
+                    return t.value;
             } while (t != null);
-        }
-        else {
-            if (key == null)
-                throw new NullPointerException();
+        } else {
+            Objects.requireNonNull(key);
             @SuppressWarnings("unchecked")
-                Comparable<? super K> k = (Comparable<? super K>) key;
+            Comparable<? super K> k = (Comparable<? super K>) key;
             do {
                 parent = t;
                 cmp = k.compareTo(t.key);
@@ -569,20 +590,271 @@ public class TreeMap<K,V>
                 else if (cmp > 0)
                     t = t.right;
                 else
-                    return t.setValue(value);
+                    return t.value;
             } while (t != null);
         }
+        newValue = callMappingFunctionWithCheck(key, mappingFunction);
+        if (newValue != null) {
+            addEntry(key, newValue, parent, cmp < 0);
+            return newValue;
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This method will, on a best-effort basis, throw a
+     * {@link ConcurrentModificationException} if it is detected that the
+     * remapping function modifies this map during computation.
+     *
+     * @throws ConcurrentModificationException if it is detected that the
+     * remapping function modified this map
+     */
+    @Override
+    public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+        Objects.requireNonNull(remappingFunction);
+        Entry<K,V> oldEntry = getEntry(key);
+        if (oldEntry != null && oldEntry.value != null) {
+            return remapValue(oldEntry, key, remappingFunction);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This method will, on a best-effort basis, throw a
+     * {@link ConcurrentModificationException} if it is detected that the
+     * remapping function modifies this map during computation.
+     *
+     * @throws ConcurrentModificationException if it is detected that the
+     * remapping function modified this map
+     */
+    @Override
+    public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+        Objects.requireNonNull(remappingFunction);
+        V newValue;
+        Entry<K,V> t = root;
+        if (t == null) {
+            newValue = callRemappingFunctionWithCheck(key, null, remappingFunction);
+            if (newValue != null) {
+                addEntryToEmptyMap(key, newValue);
+                return newValue;
+            } else {
+                return null;
+            }
+        }
+        int cmp;
+        Entry<K,V> parent;
+        // split comparator and comparable paths
+        Comparator<? super K> cpr = comparator;
+        if (cpr != null) {
+            do {
+                parent = t;
+                cmp = cpr.compare(key, t.key);
+                if (cmp < 0)
+                    t = t.left;
+                else if (cmp > 0)
+                    t = t.right;
+                else
+                    return remapValue(t, key, remappingFunction);
+            } while (t != null);
+        } else {
+            Objects.requireNonNull(key);
+            @SuppressWarnings("unchecked")
+            Comparable<? super K> k = (Comparable<? super K>) key;
+            do {
+                parent = t;
+                cmp = k.compareTo(t.key);
+                if (cmp < 0)
+                    t = t.left;
+                else if (cmp > 0)
+                    t = t.right;
+                else
+                    return remapValue(t, key, remappingFunction);
+            } while (t != null);
+        }
+        newValue = callRemappingFunctionWithCheck(key, null, remappingFunction);
+        if (newValue != null) {
+            addEntry(key, newValue, parent, cmp < 0);
+            return newValue;
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This method will, on a best-effort basis, throw a
+     * {@link ConcurrentModificationException} if it is detected that the
+     * remapping function modifies this map during computation.
+     *
+     * @throws ConcurrentModificationException if it is detected that the
+     * remapping function modified this map
+     */
+    @Override
+    public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
+        Objects.requireNonNull(remappingFunction);
+        Objects.requireNonNull(value);
+        Entry<K,V> t = root;
+        if (t == null) {
+            addEntryToEmptyMap(key, value);
+            return value;
+        }
+        int cmp;
+        Entry<K,V> parent;
+        // split comparator and comparable paths
+        Comparator<? super K> cpr = comparator;
+        if (cpr != null) {
+            do {
+                parent = t;
+                cmp = cpr.compare(key, t.key);
+                if (cmp < 0)
+                    t = t.left;
+                else if (cmp > 0)
+                    t = t.right;
+                else return mergeValue(t, value, remappingFunction);
+            } while (t != null);
+        } else {
+            Objects.requireNonNull(key);
+            @SuppressWarnings("unchecked")
+            Comparable<? super K> k = (Comparable<? super K>) key;
+            do {
+                parent = t;
+                cmp = k.compareTo(t.key);
+                if (cmp < 0)
+                    t = t.left;
+                else if (cmp > 0)
+                    t = t.right;
+                else return mergeValue(t, value, remappingFunction);
+            } while (t != null);
+        }
+        addEntry(key, value, parent, cmp < 0);
+        return value;
+    }
+
+    private V callMappingFunctionWithCheck(K key, Function<? super K, ? extends V> mappingFunction) {
+        int mc = modCount;
+        V newValue = mappingFunction.apply(key);
+        if (mc != modCount) {
+            throw new ConcurrentModificationException();
+        }
+        return newValue;
+    }
+
+    private V callRemappingFunctionWithCheck(K key, V oldValue, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+        int mc = modCount;
+        V newValue = remappingFunction.apply(key, oldValue);
+        if (mc != modCount) {
+            throw new ConcurrentModificationException();
+        }
+        return newValue;
+    }
+
+    private void addEntry(K key, V value, Entry<K, V> parent, boolean addToLeft) {
         Entry<K,V> e = new Entry<>(key, value, parent);
-        if (cmp < 0)
+        if (addToLeft)
             parent.left = e;
         else
             parent.right = e;
         fixAfterInsertion(e);
         size++;
         modCount++;
+    }
+
+    private void addEntryToEmptyMap(K key, V value) {
+        compare(key, key); // type (and possibly null) check
+        root = new Entry<>(key, value, null);
+        size = 1;
+        modCount++;
+    }
+
+    private V put(K key, V value, boolean replaceOld) {
+        Entry<K,V> t = root;
+        if (t == null) {
+            addEntryToEmptyMap(key, value);
+            return null;
+        }
+        int cmp;
+        Entry<K,V> parent;
+        // split comparator and comparable paths
+        Comparator<? super K> cpr = comparator;
+        if (cpr != null) {
+            do {
+                parent = t;
+                cmp = cpr.compare(key, t.key);
+                if (cmp < 0)
+                    t = t.left;
+                else if (cmp > 0)
+                    t = t.right;
+                else {
+                    V oldValue = t.value;
+                    if (replaceOld || oldValue == null) {
+                        t.value = value;
+                    }
+                    return oldValue;
+                }
+            } while (t != null);
+        } else {
+            Objects.requireNonNull(key);
+            @SuppressWarnings("unchecked")
+            Comparable<? super K> k = (Comparable<? super K>) key;
+            do {
+                parent = t;
+                cmp = k.compareTo(t.key);
+                if (cmp < 0)
+                    t = t.left;
+                else if (cmp > 0)
+                    t = t.right;
+                else {
+                    V oldValue = t.value;
+                    if (replaceOld || oldValue == null) {
+                        t.value = value;
+                    }
+                    return oldValue;
+                }
+            } while (t != null);
+        }
+        addEntry(key, value, parent, cmp < 0);
         return null;
     }
 
+    private V remapValue(Entry<K,V> t, K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+        V newValue = callRemappingFunctionWithCheck(key, t.value, remappingFunction);
+        if (newValue == null) {
+            deleteEntry(t);
+            return null;
+        } else {
+            // replace old mapping
+            t.value = newValue;
+            return newValue;
+        }
+    }
+
+    private V mergeValue(Entry<K,V> t, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
+        V oldValue = t.value;
+        V newValue;
+        if (t.value == null) {
+            newValue = value;
+        } else {
+            int mc = modCount;
+            newValue = remappingFunction.apply(oldValue, value);
+            if (mc != modCount) {
+                throw new ConcurrentModificationException();
+            }
+        }
+        if (newValue == null) {
+            deleteEntry(t);
+            return null;
+        } else {
+            // replace old mapping
+            t.value = newValue;
+            return newValue;
+        }
+    }
+
     /**
      * Removes the mapping for this key from this TreeMap if present.
      *
diff --git a/test/jdk/java/util/Map/FunctionalCMEs.java b/test/jdk/java/util/Map/FunctionalCMEs.java
index 4d83d241c94..51cc85efe80 100644
--- a/test/jdk/java/util/Map/FunctionalCMEs.java
+++ b/test/jdk/java/util/Map/FunctionalCMEs.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2020, 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
@@ -30,6 +30,7 @@ import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.TreeMap;
 import java.util.function.BiFunction;
 
 import org.testng.annotations.Test;
@@ -53,6 +54,7 @@ public class FunctionalCMEs {
                 new Object[]{new HashMap<>(), true},
                 new Object[]{new Hashtable<>(), true},
                 new Object[]{new LinkedHashMap<>(), true},
+                new Object[]{new TreeMap<>(), true},
                 // Test default Map methods - no CME
                 new Object[]{new Defaults.ExtendsAbstractMap<>(), false}
         ).iterator();
diff --git a/test/jdk/java/util/Map/InPlaceOpsCollisions.java b/test/jdk/java/util/Map/InPlaceOpsCollisions.java
index 22ac0c24c09..ce624990f16 100644
--- a/test/jdk/java/util/Map/InPlaceOpsCollisions.java
+++ b/test/jdk/java/util/Map/InPlaceOpsCollisions.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2020, 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
@@ -27,11 +27,19 @@
  * @run testng/othervm -Dtest.map.collisions.shortrun=true InPlaceOpsCollisions
  * @summary Ensure overrides of in-place operations in Maps behave well with lots of collisions.
  */
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.TreeMap;
 import java.util.function.BiFunction;
 import java.util.function.Function;
 import java.util.function.Supplier;
 
+import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.assertFalse;
@@ -71,6 +79,19 @@ public class InPlaceOpsCollisions extends MapWithCollisionsProviders {
                 String.format("map expected size m%d != k%d", map.size(), keys.length));
     }
 
+    @Test(dataProvider = "nullValueFriendlyMaps")
+    void testPutIfAbsentOverwriteNull(String desc, Supplier<Map<Object, Object>> ms) {
+        Map<Object, Object> map = ms.get();
+        map.put("key", null);
+        assertEquals(map.size(), 1, desc + ": size != 1");
+        assertTrue(map.containsKey("key"), desc + ": does not have key");
+        assertNull(map.get("key"), desc + ": value is not null");
+        map.putIfAbsent("key", "value"); // must rewrite
+        assertEquals(map.size(), 1, desc + ": size != 1");
+        assertTrue(map.containsKey("key"), desc + ": does not have key");
+        assertEquals(map.get("key"), "value", desc + ": value is not 'value'");
+    }
+
     @Test(dataProvider = "mapsWithObjectsAndStrings")
     void testRemoveMapping(String desc, Supplier<Map<Object, Object>> ms, Object val) {
         Map<Object, Object> map = ms.get();
@@ -496,4 +517,13 @@ public class InPlaceOpsCollisions extends MapWithCollisionsProviders {
         }
     }
 
+    @DataProvider
+    public Iterator<Object[]> nullValueFriendlyMaps() {
+        return Arrays.asList(
+                new Object[]{"HashMap", (Supplier<Map<?, ?>>) HashMap::new},
+                new Object[]{"LinkedHashMap", (Supplier<Map<?, ?>>) LinkedHashMap::new},
+                new Object[]{"TreeMap", (Supplier<Map<?, ?>>) TreeMap::new},
+                new Object[]{"TreeMap(cmp)", (Supplier<Map<?, ?>>) () -> new TreeMap<>(Comparator.reverseOrder())}
+        ).iterator();
+    }
 }
diff --git a/test/micro/org/openjdk/bench/java/util/TreeMapUpdate.java b/test/micro/org/openjdk/bench/java/util/TreeMapUpdate.java
new file mode 100644
index 00000000000..d43a070cc40
--- /dev/null
+++ b/test/micro/org/openjdk/bench/java/util/TreeMapUpdate.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2020, 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.
+ *
+ * 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.java.util;
+
+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.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.Random;
+import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
+@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
+@Fork(3)
+@State(Scope.Thread)
+public class TreeMapUpdate {
+    @Param({"10", "1000", "100000"})
+    public int size;
+
+    @Param({"true", "false"})
+    public boolean comparator;
+
+    @Param({"true", "false"})
+    public boolean preFill;
+
+    @Param({"0"})
+    public long seed;
+
+    private Supplier<TreeMap<Integer, Integer>> supplier;
+
+    private Integer[] keys;
+
+    @Setup
+    public void setUp() {
+        supplier = comparator ? () -> new TreeMap<>(Comparator.reverseOrder()) : TreeMap::new;
+        keys = IntStream.range(0, size).boxed().toArray(Integer[]::new);
+        Random rnd = seed == 0 ? new Random() : new Random(seed);
+        Collections.shuffle(Arrays.asList(keys, rnd));
+        if (preFill) {
+            TreeMap<Integer, Integer> template = Arrays.stream(keys)
+                .collect(Collectors.toMap(Function.identity(), Function.identity(), (a, b) -> a, supplier));
+            supplier = () -> new TreeMap<>(template);
+        }
+    }
+
+    @Benchmark
+    public Map<Integer, Integer> baseline() {
+        // Just create map (empty or pre-filled)
+        return supplier.get();
+    }
+
+    @Benchmark
+    public Map<Integer, Integer> put(Blackhole bh) {
+        Map<Integer, Integer> map = supplier.get();
+        Integer[] keys = this.keys;
+        for (Integer key : keys) {
+            bh.consume(map.put(key, key));
+        }
+        return map;
+    }
+
+    @Benchmark
+    public Map<Integer, Integer> putIfAbsent(Blackhole bh) {
+        Map<Integer, Integer> map = supplier.get();
+        Integer[] keys = this.keys;
+        for (Integer key : keys) {
+            bh.consume(map.putIfAbsent(key, key));
+        }
+        return map;
+    }
+
+    @Benchmark
+    public Map<Integer, Integer> computeIfAbsent(Blackhole bh) {
+        Map<Integer, Integer> map = supplier.get();
+        Integer[] keys = this.keys;
+        for (Integer key : keys) {
+            bh.consume(map.computeIfAbsent(key, k -> k));
+        }
+        return map;
+    }
+
+    @Benchmark
+    public Map<Integer, Integer> compute(Blackhole bh) {
+        Map<Integer, Integer> map = supplier.get();
+        Integer[] keys = this.keys;
+        for (Integer key : keys) {
+            bh.consume(map.compute(key, (k, old) -> k));
+        }
+        return map;
+    }
+
+    @Benchmark
+    public Map<Integer, Integer> computeIfPresent(Blackhole bh) {
+        Map<Integer, Integer> map = supplier.get();
+        Integer[] keys = this.keys;
+        for (Integer key : keys) {
+            bh.consume(map.computeIfPresent(key, (k, old) -> k));
+        }
+        return map;
+    }
+
+    @Benchmark
+    public Map<Integer, Integer> merge(Blackhole bh) {
+        Map<Integer, Integer> map = supplier.get();
+        Integer[] keys = this.keys;
+        for (Integer key : keys) {
+            bh.consume(map.merge(key, key, (k1, k2) -> k1));
+        }
+        return map;
+    }
+}