From 82790b03da58d812e6067e12d66424fa32252dc4 Mon Sep 17 00:00:00 2001 From: Martin Doerr Date: Sat, 31 Dec 2022 11:49:31 +0000 Subject: [PATCH] 8295724: VirtualMachineError: Out of space in CodeCache for method handle intrinsic 8298947: compiler/codecache/MHIntrinsicAllocFailureTest.java fails intermittently Reviewed-by: kvn, mbaesken Backport-of: cd2182a9967917e733e486d918e9aeba3bd35ee8 --- src/hotspot/share/code/nmethod.cpp | 58 ++++++++++++- src/hotspot/share/code/nmethod.hpp | 4 + src/hotspot/share/oops/method.hpp | 2 + .../MHIntrinsicAllocFailureTest.java | 86 +++++++++++++++++++ 4 files changed, 147 insertions(+), 3 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/codecache/MHIntrinsicAllocFailureTest.java diff --git a/src/hotspot/share/code/nmethod.cpp b/src/hotspot/share/code/nmethod.cpp index e2b27e5f4f0..7fe1d05ff1c 100644 --- a/src/hotspot/share/code/nmethod.cpp +++ b/src/hotspot/share/code/nmethod.cpp @@ -451,6 +451,42 @@ void nmethod::init_defaults() { #endif } +#ifdef ASSERT +class CheckForOopsClosure : public OopClosure { + bool _found_oop = false; + public: + virtual void do_oop(oop* o) { _found_oop = true; } + virtual void do_oop(narrowOop* o) { _found_oop = true; } + bool found_oop() { return _found_oop; } +}; +class CheckForMetadataClosure : public MetadataClosure { + bool _found_metadata = false; + Metadata* _ignore = nullptr; + public: + CheckForMetadataClosure(Metadata* ignore) : _ignore(ignore) {} + virtual void do_metadata(Metadata* md) { if (md != _ignore) _found_metadata = true; } + bool found_metadata() { return _found_metadata; } +}; + +static void assert_no_oops_or_metadata(nmethod* nm) { + if (nm == nullptr) return; + assert(nm->oop_maps() == nullptr, "expectation"); + + CheckForOopsClosure cfo; + nm->oops_do(&cfo); + assert(!cfo.found_oop(), "no oops allowed"); + + // We allow an exception for the own Method, but require its class to be permanent. + Method* own_method = nm->method(); + CheckForMetadataClosure cfm(/* ignore reference to own Method */ own_method); + nm->metadata_do(&cfm); + assert(!cfm.found_metadata(), "no metadata allowed"); + + assert(own_method->method_holder()->class_loader_data()->is_permanent_class_loader_data(), + "Method's class needs to be permanent"); +} +#endif + nmethod* nmethod::new_native_nmethod(const methodHandle& method, int compile_id, CodeBuffer *code_buffer, @@ -470,14 +506,19 @@ nmethod* nmethod::new_native_nmethod(const methodHandle& method, CodeOffsets offsets; offsets.set_value(CodeOffsets::Verified_Entry, vep_offset); offsets.set_value(CodeOffsets::Frame_Complete, frame_complete); - nm = new (native_nmethod_size, CompLevel_none) + + // MH intrinsics are dispatch stubs which are compatible with NonNMethod space. + // IsUnloadingBehaviour::is_unloading needs to handle them separately. + bool allow_NonNMethod_space = method->can_be_allocated_in_NonNMethod_space(); + nm = new (native_nmethod_size, allow_NonNMethod_space) nmethod(method(), compiler_none, native_nmethod_size, compile_id, &offsets, code_buffer, frame_size, basic_lock_owner_sp_offset, basic_lock_sp_offset, oop_maps); - NOT_PRODUCT(if (nm != NULL) native_nmethod_stats.note_native_nmethod(nm)); + DEBUG_ONLY( if (allow_NonNMethod_space) assert_no_oops_or_metadata(nm); ) + NOT_PRODUCT(if (nm != NULL) native_nmethod_stats.note_native_nmethod(nm)); } if (nm != NULL) { @@ -710,6 +751,14 @@ void* nmethod::operator new(size_t size, int nmethod_size, int comp_level) throw return CodeCache::allocate(nmethod_size, CodeCache::get_code_blob_type(comp_level)); } +void* nmethod::operator new(size_t size, int nmethod_size, bool allow_NonNMethod_space) throw () { + // Try MethodNonProfiled and MethodProfiled. + void* return_value = CodeCache::allocate(nmethod_size, CodeBlobType::MethodNonProfiled); + if (return_value != nullptr || !allow_NonNMethod_space) return return_value; + // Try NonNMethod or give up. + return CodeCache::allocate(nmethod_size, CodeBlobType::NonNMethod); +} + nmethod::nmethod( Method* method, CompilerType type, @@ -1780,7 +1829,10 @@ bool nmethod::is_unloading() { // oops in the CompiledMethod, by calling oops_do on it. state_unloading_cycle = current_cycle; - if (is_zombie()) { + if (is_zombie() || method()->can_be_allocated_in_NonNMethod_space()) { + // When the nmethod is in NonNMethod space, we may reach here without IsUnloadingBehaviour. + // However, we only allow this for special methods which never get unloaded. + // Zombies without calculated unloading epoch are never unloading due to GC. // There are no races where a previously observed is_unloading() nmethod diff --git a/src/hotspot/share/code/nmethod.hpp b/src/hotspot/share/code/nmethod.hpp index 893f28863a6..ea5e92845ff 100644 --- a/src/hotspot/share/code/nmethod.hpp +++ b/src/hotspot/share/code/nmethod.hpp @@ -324,6 +324,10 @@ class nmethod : public CompiledMethod { // helper methods void* operator new(size_t size, int nmethod_size, int comp_level) throw(); + // For method handle intrinsics: Try MethodNonProfiled, MethodProfiled and NonNMethod. + // Attention: Only allow NonNMethod space for special nmethods which don't need to be + // findable by nmethod iterators! In particular, they must not contain oops! + void* operator new(size_t size, int nmethod_size, bool allow_NonNMethod_space) throw(); const char* reloc_string_for(u_char* begin, u_char* end); diff --git a/src/hotspot/share/oops/method.hpp b/src/hotspot/share/oops/method.hpp index 33ac08e5fe4..5794ba5e0bb 100644 --- a/src/hotspot/share/oops/method.hpp +++ b/src/hotspot/share/oops/method.hpp @@ -720,6 +720,8 @@ class Method : public Metadata { static methodHandle make_method_handle_intrinsic(vmIntrinsicID iid, // _invokeBasic, _linkToVirtual Symbol* signature, //anything at all TRAPS); + // Some special methods don't need to be findable by nmethod iterators and are permanent. + bool can_be_allocated_in_NonNMethod_space() const { return is_method_handle_intrinsic(); } static Klass* check_non_bcp_klass(Klass* klass); enum { diff --git a/test/hotspot/jtreg/compiler/codecache/MHIntrinsicAllocFailureTest.java b/test/hotspot/jtreg/compiler/codecache/MHIntrinsicAllocFailureTest.java new file mode 100644 index 00000000000..1cbaaf0f52d --- /dev/null +++ b/test/hotspot/jtreg/compiler/codecache/MHIntrinsicAllocFailureTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022 SAP SE. All rights reserved.ights 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. + */ + +/* + * @test MHIntrinsicAllocFailureTest + * @bug 8295724 + * @requires vm.compMode == "Xmixed" + * @requires vm.opt.TieredCompilation == null | vm.opt.TieredCompilation == true + * @requires vm.opt.TieredStopAtLevel == null | vm.opt.TieredStopAtLevel == 4 + * @summary test allocation failure of method handle intrinsic in profiled/non-profiled space + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.management + * + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -XX:CompileCommand=compileonly,null::* + * -XX:ReservedCodeCacheSize=16m -XX:+SegmentedCodeCache + * compiler.codecache.MHIntrinsicAllocFailureTest + */ + +package compiler.codecache; + +import jdk.test.lib.Asserts; +import jdk.test.whitebox.WhiteBox; +import jdk.test.whitebox.code.BlobType; + +import java.lang.management.MemoryPoolMXBean; + +public class MHIntrinsicAllocFailureTest { + private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + + private interface TestInterface { + int testMethod(int a, int b, Object c); + } + + private static void fillCodeCacheSegment(BlobType type) { + // Fill with large blobs. + MemoryPoolMXBean bean = type.getMemoryPool(); + int size = (int) (bean.getUsage().getMax() >> 7); + while (WHITE_BOX.allocateCodeBlob(size, type.id) != 0) {} + // Fill rest with minimal blobs. + while (WHITE_BOX.allocateCodeBlob(1, type.id) != 0) {} + } + + public static void main(String[] args) { + // Lock compilation to be able to better control code cache space + WHITE_BOX.lockCompilation(); + fillCodeCacheSegment(BlobType.MethodNonProfiled); + fillCodeCacheSegment(BlobType.MethodProfiled); + // JIT compilers should be off, now. + Asserts.assertNotEquals(WHITE_BOX.getCompilationActivityMode(), 1); + System.out.println("Code cache segments for non-profiled and profiled nmethods are full."); + // Generate and use a MH itrinsic. Should not trigger one of the following: + // - VirtualMachineError: Out of space in CodeCache for method handle intrinsic + // - InternalError: java.lang.NoSuchMethodException: no such method: + // java.lang.invoke.MethodHandle.linkToStatic(int,int,Object,MemberName)int/invokeStatic + TestInterface add2ints = (a, b, c) -> a + b; + System.out.println("Result of lambda expression: " + add2ints.testMethod(1, 2, null)); + // Let GC check the code cache. + WHITE_BOX.unlockCompilation(); + WHITE_BOX.fullGC(); + } +}