# HG changeset patch # User kbarrett # Date 1426097187 14400 # Wed Mar 11 14:06:27 2015 -0400 # Node ID 2835aaf6438ce2161264ca2ce9154d5be3aa8b0f # Parent 2e4f8342fed050bfb12c43548995397f8e9230d0 imported patch inc1 diff --git a/src/share/vm/gc_implementation/g1/concurrentMark.cpp b/src/share/vm/gc_implementation/g1/concurrentMark.cpp --- a/src/share/vm/gc_implementation/g1/concurrentMark.cpp +++ b/src/share/vm/gc_implementation/g1/concurrentMark.cpp @@ -2978,6 +2978,7 @@ class VerifyNoCSetOopsClosure : public OopClosure, public ObjectClosure { private: G1CollectedHeap* _g1h; + ConcurrentMark* _cm; VerifyNoCSetOopsPhase _phase; int _info; @@ -2993,13 +2994,22 @@ } void do_object_work(oop obj) { - guarantee(!_g1h->obj_in_cs(obj), - err_msg("obj: "PTR_FORMAT" in CSet, phase: %s, info: %d", - p2i((void*) obj), phase_str(), _info)); + switch (_phase) { + case VerifyNoCSetOopsStack: + case VerifyNoCSetOopsQueues: + // Ignore reclaimed humongous object entries in mark stack and + // thread queues. + if (_cm->is_stale_humongous_marked_entry(obj)) break; + default: + guarantee(!_g1h->obj_in_cs(obj), + err_msg("obj: "PTR_FORMAT" in CSet, phase: %s, info: %d", + p2i((void*) obj), phase_str(), _info)); + } } public: - VerifyNoCSetOopsClosure() : _g1h(G1CollectedHeap::heap()) { } + VerifyNoCSetOopsClosure(G1CollectedHeap* g1h, ConcurrentMark* cm) + : _g1h(g1h), _cm(cm) { } void set_phase(VerifyNoCSetOopsPhase phase, int info = -1) { _phase = phase; @@ -3031,7 +3041,7 @@ return; } - VerifyNoCSetOopsClosure cl; + VerifyNoCSetOopsClosure cl(_g1h, this); if (verify_stacks) { // Verify entries on the global mark stack @@ -3406,6 +3416,7 @@ void CMTask::scan_object(oop obj) { assert(_nextMarkBitMap->isMarked((HeapWord*) obj), "invariant"); + assert(!_g1h->is_on_master_free_list(_g1h->heap_region_containing(obj)), "invariant"); if (_cm->verbose_high()) { gclog_or_tty->print_cr("[%u] we're scanning object "PTR_FORMAT, @@ -3818,15 +3829,7 @@ p2i((void*) obj)); } - assert(_g1h->is_in_g1_reserved((HeapWord*) obj), "invariant" ); - - if (is_stale_humongous_queue_entry(obj)) { - statsOnly( ++stale_humongous_queue_entries ); - } else { - assert(!_g1h->is_on_master_free_list( - _g1h->heap_region_containing(obj)), "invariant"); - scan_object(obj); - } + process_queue_entry(obj); if (_task_queue->size() <= target_size || has_aborted()) { ret = false; @@ -4330,14 +4333,7 @@ } statsOnly( ++_steals ); - - if (is_stale_humongous_queue_entry(obj)) { - statsOnly( ++stale_humongous_queue_entries ); - } else { - assert(_nextMarkBitMap->isMarked((HeapWord*) obj), - "any stolen object should be marked"); - scan_object(obj); - } + process_queue_entry(obj); // And since we're towards the end, let's totally drain the // local queue and global stack. diff --git a/src/share/vm/gc_implementation/g1/concurrentMark.hpp b/src/share/vm/gc_implementation/g1/concurrentMark.hpp --- a/src/share/vm/gc_implementation/g1/concurrentMark.hpp +++ b/src/share/vm/gc_implementation/g1/concurrentMark.hpp @@ -646,6 +646,10 @@ bool mark_stack_overflow() { return _markStack.overflow(); } bool mark_stack_empty() { return _markStack.isEmpty(); } + // Test whether an "object" obtained from a (local or global) mark + // stack is really a stale reference to a reclaimed humongous object. + bool is_stale_humongous_marked_entry(oop entry) const; + CMRootRegions* root_regions() { return &_root_regions; } bool concurrent_marking_in_progress() { @@ -1154,9 +1158,10 @@ // It scans an object and visits its children. void scan_object(oop obj); - // Test whether the "object" obtained from a queue is really a stale - // reference to a reclaimed humongous object. - bool is_stale_humongous_queue_entry(oop obj) const; + // Scan the "object" obtained from a queue (using scan_object) + // unless it should be ignored, e.g. because it is for a reclaimed + // humongous object. + void process_queue_entry(oop entry); // It pushes an object on the local queue. inline void push(oop obj); diff --git a/src/share/vm/gc_implementation/g1/concurrentMark.inline.hpp b/src/share/vm/gc_implementation/g1/concurrentMark.inline.hpp --- a/src/share/vm/gc_implementation/g1/concurrentMark.inline.hpp +++ b/src/share/vm/gc_implementation/g1/concurrentMark.inline.hpp @@ -222,27 +222,47 @@ #undef check_mark -inline bool CMTask::is_stale_humongous_queue_entry(oop obj) const { +inline bool ConcurrentMark::is_stale_humongous_marked_entry(oop entry) const { // When a humongous object is eagerly reclaimed, we don't remove - // entries for it from queues. Instead, we filter out such entries - // on the processing side. + // entries for it from mark stacks (either local or global). + // Instead, we filter out such entries on the processing side. // // Recently allocated objects are filtered out when queuing, to // minimize queue size and processing time. Therefore, if we find // what appears to be a recently allocated object in the queue, it // must be for a reclaimed humongous object. - HeapRegion* hr = _g1h->heap_region_containing_raw(obj); - bool result = hr->obj_allocated_since_next_marking(obj); + HeapRegion* hr = _g1h->heap_region_containing_raw(entry); + bool result = hr->obj_allocated_since_next_marking(entry); #ifdef ASSERT if (result) { - HeapWord* hp = (HeapWord*)obj; - assert(hp == hr->bottom(), "stale humongous should be at region bottom"); - assert(!_nextMarkBitMap->isMarked(hp), "stale humongous should not be marked"); + // If entry appears to be a stale humongous object, perform some + // additional tests to increase our confidence in that belief. + // We're limited in what tests we can perform, because a stale + // entry is for an object that has already been reclaimed. + HeapWord* hp = (HeapWord*)entry; + assert(hp == hr->bottom(), + err_msg("Stale humongous object should be at region bottom: object = " + PTR_FORMAT ", region id %u, bottom = " PTR_FORMAT, + p2i(hp), hr->hrm_index(), p2i(hr->bottom()))); + assert(!_nextMarkBitMap->isMarked(hp), + err_msg("Stale humongous object should not be marked: object = " + PTR_FORMAT ", region id %u, bottom = " PTR_FORMAT, + p2i(hp), hr->hrm_index(), p2i(hr->bottom()))); } #endif return result; } +inline void CMTask::process_queue_entry(oop entry) { + assert(_g1h->is_in_g1_reserved((HeapWord*)entry), "invariant" ); + + if (_cm->is_stale_humongous_marked_entry(entry)) { + statsOnly( ++stale_humongous_queue_entries ); + } else { + scan_object(entry); + } +} + inline void CMTask::push(oop obj) { HeapWord* objAddr = (HeapWord*) obj; assert(_g1h->is_in_g1_reserved(objAddr), "invariant"); diff --git a/test/TEST.groups b/test/TEST.groups --- a/test/TEST.groups +++ b/test/TEST.groups @@ -394,6 +394,7 @@ hotspot_gc = \ sanity/ExecuteInternalVMTests.java \ gc/ \ + -gc/g1/TestGreyReclaimedHumongousObjects.java \ -gc/metaspace/CompressedClassSpaceSizeInJmapHeap.java hotspot_gc_closed = \ diff --git a/test/gc/g1/TestGreyReclaimedHumongousObjects.java b/test/gc/g1/TestGreyReclaimedHumongousObjects.java new file mode 100644 --- /dev/null +++ b/test/gc/g1/TestGreyReclaimedHumongousObjects.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2015, 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. + */ + +/* + * @test TestGreyReclaimedHumongousObjects.java + * @bug 8069367 + * @requires vm.gc == "G1" | vm.gc == "null" + * @summary Test handling of marked but unscanned reclaimed humongous objects. + * @key gc + * @run main/othervm -XX:+UseG1GC -Xss32m -Xmx128m -XX:G1HeapRegionSize=1m + * -XX:+UnlockExperimentalVMOptions + * -XX:+G1EagerReclaimHumongousObjects + * -XX:+G1EagerReclaimHumongousObjectsWithStaleRefs + * TestGreyReclaimedHumongousObjects 1048576 + */ + +// This test spawns a bunch of threads, each of them rapidly +// allocating large objects and storing them into a circular buffer +// associated with the thread. The circular buffer results in these +// objects becoming dead in fairly short order. +// +// The situation we're trying to provoke is +// +// (1) A humongous object H is marked and added to the mark stack. +// +// (2) An evacuation pause determines H is no longer live, and +// reclaims it. This occurs before concurrent marking has gotten +// around to processing the mark stack entry for H. +// +// (3) Concurrent marking processes the mark stack entry for H. The +// bug is that it would attempt to scan the now dead object. +// +// Unfortunately, this test is *very* sensitive to configuration. +// Among the parameters that affect whether / how often we'll get into +// the desired situation within a reasonable amount of time are: +// +// - THREAD_COUNT: The number of allocating threads. +// +// - OLD_COUNT: The number of objects each thread keeps. +// +// - MAX_MEMORY: The maximum heap size. +// +// - G1HeapRegionSize +// +// - The size of the objects being allocated. +// +// The parameter values specified here: +// +// - THREAD_COUNT = 12 +// - OLD_COUNT == 4 +// - MAX_MEMORY == 128m +// - G1HeapRegionSize = 1m +// - Object size = 1048576 (2 regions after header overhead and roundup) +// +// seems to work well at provoking the desired state fairly quickly. +// Even relatively small perturbations may change that. The key +// factors seem to be keeping the heap mostly full of live objects but +// having them become dead fairly quickly. + +import java.util.Date; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import sun.management.ManagementFactoryHelper; +import com.sun.management.HotSpotDiagnosticMXBean; +import com.sun.management.VMOption; + +public class TestGreyReclaimedHumongousObjects { + + static class NamedThreadFactory implements ThreadFactory { + private int threadNum = 0; + + @Override + public Thread newThread(Runnable r) { + return new Thread(r, THREAD_NAME + (threadNum++)); + } + } + + static class Runner extends Thread { + private final Date startDate = new Date(); + private final int obj_size; + private final Object[] old_garbage; + private int old_index = 0; + + public Runner(int obj_size) { + this.obj_size = obj_size; + old_garbage = new Object[OLD_COUNT]; + } + + private void allocate_garbage() { + byte[] garbage = new byte[obj_size]; + old_garbage[Math.abs(++old_index % OLD_COUNT)] = garbage; + } + + @Override + public void run() { + try { + while (!isInterrupted()) { + allocate_garbage(); + Thread.sleep(0); // Yield, to ensure interruptable. + } + } catch (InterruptedException e) { + System.out.println("Aborted after " + + (new Date().getTime() - startDate.getTime()) + + " ms"); + interrupt(); + } + } + } + + public static void main(String[] args) throws Exception { + HotSpotDiagnosticMXBean diagnostic = ManagementFactoryHelper.getDiagnosticMXBean(); + + System.out.println("Max memory= " + MAX_MEMORY + " bytes"); + + int obj_size = 0; + if (args.length != 1) { + throw new RuntimeException("Object size argument must be supplied"); + } else { + obj_size = Integer.parseInt(args[0]); + } + System.out.println("Objects size= " + obj_size + " bytes"); + + int region_size = + Integer.parseInt(diagnostic.getVMOption("G1HeapRegionSize").getValue()); + if (obj_size < (region_size / 2)) { + throw new RuntimeException("Object size " + obj_size + + " is not humongous with region size " + region_size); + } + + ExecutorService executor = + Executors.newFixedThreadPool(THREAD_COUNT, new NamedThreadFactory()); + System.out.println("Starting " + THREAD_COUNT + " threads"); + + for (int i = 0; i < THREAD_COUNT; i++) { + executor.execute(new Runner(obj_size)); + } + + Thread.sleep(SECONDS_TO_RUN * 1000); + executor.shutdownNow(); + + if (!executor.awaitTermination(10, TimeUnit.SECONDS)) { + System.err.println("Thread pool did not terminate after 10 seconds after shutdown"); + } + } + + private static final long MAX_MEMORY = Runtime.getRuntime().maxMemory(); + private static final int OLD_COUNT = 4; + private static final int THREAD_COUNT = 12; + private static final long SECONDS_TO_RUN = 90; + private static final String THREAD_NAME = "TestGreyRH-"; +} +