/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library 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 Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.vm.memmgr.def; import java.io.PrintWriter; import org.jnode.annotation.Inline; import org.jnode.annotation.MagicPermission; import org.jnode.vm.BaseVmArchitecture; import org.jnode.vm.MemoryBlockManager; import org.jnode.vm.VmMagic; import org.jnode.vm.classmgr.ObjectFlags; import org.jnode.vm.classmgr.ObjectLayout; import org.jnode.vm.classmgr.VmClassLoader; import org.jnode.vm.classmgr.VmClassType; import org.jnode.vm.classmgr.VmNormalClass; import org.jnode.vm.classmgr.VmType; import org.jnode.vm.facade.GCStatistics; import org.jnode.vm.facade.HeapStatistics; import org.jnode.vm.facade.ObjectFilter; import org.jnode.vm.facade.ObjectVisitor; import org.jnode.vm.facade.VmProcessor; import org.jnode.vm.memmgr.HeapHelper; import org.jnode.vm.memmgr.VmHeapManager; import org.jnode.vm.scheduler.Monitor; import org.vmmagic.unboxed.Address; import org.vmmagic.unboxed.Extent; import org.vmmagic.unboxed.Word; @MagicPermission public final class DefaultHeapManager extends VmHeapManager { /** * Default size in bytes of a new heap */ public static final int DEFAULT_HEAP_SIZE = 16 * 1024 * 1024; /** * When this percentage of the free memory has been allocated, a GC is * triggered (0..1.0) */ public static final float GC_TRIGGER_PERCENTAGE = 0.75f; /** * The boot heap */ private final VmBootHeap bootHeap; /** * The GC thread */ private GCThread gcThread; /** * The finalizer thread */ private FinalizerThread finalizerThread; /** * Monitor to synchronize heap access */ private Monitor heapMonitor; /** * Are we low on memory */ private boolean lowOnMemory; /** * Linked list of all heaps. */ private VmDefaultHeap heapList; /** * The first heap. */ private final VmDefaultHeap firstNormalHeap; /** * The heap currently used for allocation */ private VmDefaultHeap currentHeap; /** * The heap used for allocations during a GC */ private VmDefaultHeap gcHeap; /** * The class of the default heap type. Set by initialize */ private final VmNormalClass<VmDefaultHeap> defaultHeapClass; /** * The number of allocated bytes since the last GC trigger */ private int allocatedSinceGcTrigger; private int triggerSize = Integer.MAX_VALUE; private boolean gcActive; private GCManager gcManager; /** * Make this private, so we cannot be instantiated */ @SuppressWarnings("unchecked") public DefaultHeapManager(VmClassLoader loader, HeapHelper helper) throws ClassNotFoundException { super(helper); this.bootHeap = new VmBootHeap(helper); // this.writeBarrier = new DefaultWriteBarrier(helper); setWriteBarrier(null); this.firstNormalHeap = new VmDefaultHeap(this); this.currentHeap = firstNormalHeap; this.heapList = firstNormalHeap; this.defaultHeapClass = (VmNormalClass<VmDefaultHeap>) loader.loadClass( VmDefaultHeap.class.getName(), true); } /** * Is the given address the address of an allocated object on this heap? * * @param ptr The address to examine. * @return True if the given address if a valid starting address of an * object, false otherwise. */ @Inline public final boolean isObject(Address ptr) { if (!ptr.toWord().and(Word.fromIntZeroExtend(ObjectLayout.OBJECT_ALIGN - 1)).isZero()) { // The object is not at an object aligned boundary return false; } if (bootHeap.isObject(ptr)) { return true; } VmDefaultHeap heap = heapList; while (heap != null) { if (heap.isObject(ptr)) { return true; } heap = heap.getNext(); } return false; } /** * Is the system low on memory? * * @return boolean */ public boolean isLowOnMemory() { return lowOnMemory; } /** * Start a garbage collection process */ public final void gc() { gcThread.trigger(false); } /** * Gets the size of free memory in bytes. * * @return long */ public long getFreeMemory() { Extent size = Extent.zero(); VmDefaultHeap h = firstNormalHeap; while (h != null) { size = size.add(h.getFreeSize()); h = h.getNext(); } // size += (Unsafe.addressToLong(heapEnd) - // Unsafe.addressToLong(nextHeapPtr)); size = size.add(Extent.fromLong(MemoryBlockManager.getFreeMemory())); return size.toLong(); } /** * Gets the size of all memory in bytes. * * @return the size of all memory in bytes */ public long getTotalMemory() { long size = bootHeap.getSize(); VmDefaultHeap h = firstNormalHeap; while (h != null) { size += h.getSize(); h = h.getNext(); } // size += (Unsafe.addressToLong(heapEnd) - // Unsafe.addressToLong(nextHeapPtr)); size += MemoryBlockManager.getFreeMemory(); return size; } /** * Gets the first heap. All other heaps can be iterated through the * <code>getNext()</code> method. * * @return the first heap */ public final VmDefaultHeap getHeapList() { return heapList; } // ------------------------------------------ // Private natives // ------------------------------------------ protected void initialize() { // Set the basic fields helper.bootArchitecture(false); final BaseVmArchitecture arch = getCurrentProcessor().getArchitecture(); final int slotSize = arch.getReferenceSize(); // Initialize the boot heap. bootHeap.initialize(helper.getBootHeapStart(), helper.getBootHeapEnd(), slotSize); // Initialize the first normal heap final Address ptr = helper.allocateBlock(Extent .fromIntZeroExtend(DEFAULT_HEAP_SIZE)); firstNormalHeap.initialize(ptr, ptr.add(DEFAULT_HEAP_SIZE), slotSize); // Initialize the GC heap gcHeap = allocHeap(Extent.fromIntZeroExtend(DEFAULT_HEAP_SIZE), false); gcHeap.append(firstNormalHeap); // Initialize the total heap list. heapList = gcHeap; } public void start() { // Create a Heap monitor heapMonitor = new Monitor(); final BaseVmArchitecture arch = getCurrentProcessor().getArchitecture(); this.gcManager = new GCManager(this, arch); this.gcThread = new GCThread(gcManager, heapMonitor); this.finalizerThread = new FinalizerThread(this); gcThread.start(); finalizerThread.start(); // Calculate the trigger size triggerSize = (int) Math.min(Integer.MAX_VALUE, getFreeMemory() * GC_TRIGGER_PERCENTAGE); } /** * Allocate a new instance for the given class. Not that this method cannot * be synchronized, since obtaining a monitor might require creating one, * which in turn needs this method. * * @param vmClass * @param size * @return Object */ protected Object allocObject(VmClassType<?> vmClass, int size) { // Make sure the class is initialized vmClass.initialize(); final int alignedSize = ObjectLayout.objectAlign(size); // final Monitor mon = heapMonitor; VmDefaultHeap heap = currentHeap; Object result = null; int oomCount = 0; final Monitor m = heapMonitor; // final Monitor m = null; if (m != null) { m.enter(); } try { if (gcActive) { if ((heapFlags & TRACE_ALLOC) != 0) { debug("Using GC Heap type "); debug(vmClass.getName()); } result = gcHeap.alloc(vmClass, alignedSize); if (result == null) { helper.die("Out of GC heap memory."); } } else { while (result == null) { // The current heap is full if (heap == null) { // Unsafe.debug("allocHeap in allocObject("); // Unsafe.debug(alignedSize); // Unsafe.debug(") "); int newHeapSize = DEFAULT_HEAP_SIZE; if (size > newHeapSize) { // this is a BIG object, try to allocate a new // heap // only for it newHeapSize = size; } if ((heap = allocHeap(Extent .fromIntZeroExtend(newHeapSize), true)) == null) { lowOnMemory = true; // It was not possible to allocate another heap. // First try to GC, if we've done that before // in this allocation, then we're in real panic. if (oomCount == 0) { oomCount++; if ((heapFlags & TRACE_OOM) != 0) { debug("<oom/>"); } gcThread.trigger(true); heap = firstNormalHeap; currentHeap = firstNormalHeap; } else { if ((heapFlags & TRACE_OOM) != 0) { debug("Out of memory in allocObject("); debug(size); debug(")"); } throw OOME; // Unsafe.die(); } } else { // Unsafe.debug("AO.G"); // We successfully allocated a new heap, set it // to current, so we'll use it for the following // allocations. currentHeap = heap; } } result = heap.alloc(vmClass, alignedSize); if (result == null) { heap = (VmDefaultHeap) heap.getNext(); } } lowOnMemory = false; allocatedSinceGcTrigger += alignedSize; if ((allocatedSinceGcTrigger > triggerSize) && (gcThread != null)) { if ((heapFlags & TRACE_TRIGGER) != 0) { debug("<alloc:GC trigger/>"); } allocatedSinceGcTrigger = 0; gcThread.trigger(false); } } vmClass.incInstanceCount(); // Allocated objects are initially black. VmMagic.setObjectFlags(result, Word .fromIntZeroExtend(ObjectFlags.GC_DEFAULT_COLOR)); } finally { if (m != null) { m.exit(); } } return result; } /** * Allocate a new heap with a given size. The heap object itself is * allocated on the new heap, so this method can be called even if all other * heaps are full. * * @param size * @return The heap */ private VmDefaultHeap allocHeap(Extent size, boolean addToHeapList) { // Unsafe.debug("allocHeap"); final Address start = helper.allocateBlock(size); // final Address start = MemoryBlockManager.allocateBlock(size); if (start == null) { return null; } final Address end = start.add(size); final int slotSize = getCurrentProcessor().getArchitecture() .getReferenceSize(); final VmDefaultHeap heap = VmDefaultHeap.setupHeap(helper, start, defaultHeapClass, slotSize); heap.initialize(start, end, slotSize); if (addToHeapList) { heapList.append(heap); } return heap; } /** * Print the statics on this object on out. */ public void dumpStatistics(PrintWriter out) { out.println("WriteBarrier: " + getWriteBarrier()); } /** * @return Returns the bootHeap. */ final VmBootHeap getBootHeap() { return this.bootHeap; } /** * @param gcActive The gcActive to set. */ final void setGcActive(boolean gcActive) { this.gcActive = gcActive; } /** * Sets the currentHeap to the first heap. */ final void resetCurrentHeap() { this.currentHeap = this.firstNormalHeap; // Recalculate the trigger size triggerSize = (int) Math.min(Integer.MAX_VALUE, getFreeMemory() * GC_TRIGGER_PERCENTAGE); } /** * Sets the currentHeap to the first heap. */ final void triggerFinalization() { finalizerThread.trigger(false); } public GCStatistics getStatistics() { return gcManager.getStatistics(); } /** * {@inheritDoc} */ public HeapStatistics getHeapStatistics(ObjectFilter objectFilter) { final DefHeapStatistics heapStatistics = new DefHeapStatistics(); heapStatistics.setObjectFilter(objectFilter); final HeapStatisticsVisitor heapStatisticsVisitor = new HeapStatisticsVisitor( heapStatistics); accept(heapStatisticsVisitor, false); return heapStatistics; } private void accept(ObjectVisitor visitor, boolean locking) { VmDefaultHeap heap = firstNormalHeap; final Word zero = Word.zero(); while (heap != null) { heap.walk(visitor, locking, zero, zero); heap = heap.getNext(); } } /** * @see org.jnode.vm.memmgr.VmHeapManager#createProcessorHeapData(org.jnode.vm.facade.VmProcessor) */ public Object createProcessorHeapData(VmProcessor cpu) { // No need here, so return null. return null; } /** * @see org.jnode.vm.memmgr.VmHeapManager#notifyClassResolved(org.jnode.vm.classmgr.VmType) */ public void notifyClassResolved(VmType<?> vmType) { // Do nothing } /** * @see org.jnode.vm.memmgr.VmHeapManager#loadClasses(org.jnode.vm.classmgr.VmClassLoader) */ public void loadClasses(VmClassLoader loader) throws ClassNotFoundException { loader.loadClass("org.jnode.vm.memmgr.def.VmBootHeap", true); loader.loadClass("org.jnode.vm.memmgr.def.VmDefaultHeap", true); } }