All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.zeno.fastblob.state.WeakObjectOrdinalMap Maven / Gradle / Ivy

The newest version!
/*
 *
 *  Copyright 2014 Netflix, Inc.
 *
 *     Licensed under the Apache License, Version 2.0 (the "License");
 *     you may not use this file except in compliance with the License.
 *     You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *     Unless required by applicable law or agreed to in writing, software
 *     distributed under the License is distributed on an "AS IS" BASIS,
 *     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *     See the License for the specific language governing permissions and
 *     limitations under the License.
 *
 */
package com.netflix.zeno.fastblob.state;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Arrays;

/**
 * Weak hash lookup map associate object references to already seen ordinals.
 * The fundamental assumption made here is that objects are immutable, so that
 * once the ordinal is assigned to an object, the ordinal stays the same
 * throughout the life of the object.
 *
 * @author timurua
 *
 */
public class WeakObjectOrdinalMap {

    /**
     * Hashmap entry
     */
    public static final class Entry extends WeakReference {
        // identity hashcode
        private int hash;
        // ordinal
        private int ordinal;
        // membership flags
        private long imageMembershipsFlags;
        // linked list pointer
        private Entry next;

        /**
         * Creates new entry.
         */
        Entry(Object key, ReferenceQueue queue, int hash, int ordinal, long imageMembershipsFlags, Entry next) {
            super(key, queue);
            this.hash  = hash;
            this.ordinal = ordinal;
            this.imageMembershipsFlags = imageMembershipsFlags;
            this.next  = next;
        }

        public int getOrdinal() {
            return ordinal;
        }

        public long getImageMembershipsFlags() {
            return imageMembershipsFlags;
        }

        public boolean hasImageMembershipsFlags(long newImageMembershipsFlags) {
            return (imageMembershipsFlags | newImageMembershipsFlags) == imageMembershipsFlags;
        }

        @Override
        public String toString() {
            Object v = get();
            return v == null ? "null" : v.toString();
        }
    }

    /**
     * The map is divided into segments to increase concurrency
     */
    private class Segment {

        // The same concept as in HashMap. If the entry array is becoming too
        // dense, it should be increased
        private static final int LOAD_FACTOR_PERCENT = 75;
        private static final int MINIMUM_CAPACITY = 256;
        private static final int MAXIMUM_CAPACITY = (1<<30);

        private int count = 0;
        private int maxThreshold = 0;
        private int minThreshold = 0;
        private Entry[] entries;

        private final ReferenceQueue queue = new ReferenceQueue();

        public Segment(){
            resize(MINIMUM_CAPACITY);
        }

        public synchronized void put(Object object, int hashCode, int ordinal, long imageMembershipsFlags) {
            removeGarbageCollectedEntities();
            int index = index(hashCode, entries.length);
            Entry current = entries[index];
            Entry prev = null;
            while (current != null) {
                if (current.hash == hashCode) {
                    Object currentObject = current.get();
                    if( currentObject == null){
                        deleteEntry(index, current, prev);
                        current = current.next;
                        continue;
                    } else if (currentObject == object) {
                        current.imageMembershipsFlags = (current.imageMembershipsFlags | imageMembershipsFlags);
                        return;
                    }
                }
                prev = current;
                current = current.next;
            }
            count++;
            Entry first = entries[index];
            Entry entry = new Entry(object, queue, hashCode, ordinal, imageMembershipsFlags, first);
            entries[index] = entry;
            entry.next = first;
            checkSize();
            return;
        }

        public synchronized Entry get(Object object, int hashCode) {
            removeGarbageCollectedEntities();
            int index = index(hashCode, entries.length);
            Entry current = entries[index];
            Entry prev = null;
            while (current != null) {
                if (current.hash == hashCode) {
                    Object currentObject = current.get();
                    if( currentObject == null){
                        deleteEntry(index, current, prev);
                        current = current.next;
                        continue;
                    } else if (currentObject == object) {
                        return current;
                    }
                }
                prev = current;
                current = current.next;
            }
            return null;
        }

        private void checkSize() {
            if( count >= minThreshold && count <= maxThreshold ){
                return;
            }
            int newCapacity;
            if( count < minThreshold ) {
                newCapacity = Math.max(MINIMUM_CAPACITY, entries.length >> 1);
            } else {
                newCapacity = Math.min(MAXIMUM_CAPACITY, entries.length << 1);
            }

            // nothing should be done, since capacity is not changed
            if (newCapacity == entries.length) {
                return;
            }

            resize(newCapacity);
        }

        private void resize(int newCapacity) {
            Entry[] newEntries = new Entry[newCapacity];
            if( entries != null){
                for(Entry entry : entries){
                    Entry current = entry;
                    while(current != null){
                        Entry newEntry = current;
                        current = current.next;
                        int index = index(newEntry.hash, newEntries.length);
                        newEntry.next = newEntries[index];
                        newEntries[index] = newEntry;
                    }
                }
            }
            minThreshold = (newEntries.length == MINIMUM_CAPACITY) ? 0 : (newEntries.length * LOAD_FACTOR_PERCENT / 200);
            maxThreshold = (newEntries.length == MAXIMUM_CAPACITY) ? Integer.MAX_VALUE : newEntries.length * LOAD_FACTOR_PERCENT / 100;
            entries = newEntries;
        }

        private void removeGarbageCollectedEntities() {
            for (Object x; (x = queue.poll()) != null; ) {
                Entry entry = (Entry) x;
                int index = index(entry.hash, entries.length);
                Entry current = entries[index];
                Entry prev = null;
                while (current != null) {
                    if (current == entry) {
                        deleteEntry(index, current, prev);
                        break;
                    }
                    prev = current;
                    current = current.next;
                }
            }
            checkSize();
        }

        private void deleteEntry(int index, Entry current, Entry prev) {
            count--;
            if (prev != null) {
                prev.next = current.next;
            } else {
                entries[index] = current.next;
            }
        }

        private final int index(int hashCode, int capacity) {
            return (hashCode >>> WeakObjectOrdinalMap.this.logOfSegmentNumber) % capacity;
        }

        public synchronized void clear() {

            while (queue.poll() != null)
                ;

            Arrays.fill(entries, null);
            count = 0;

            resize(MINIMUM_CAPACITY);

            while (queue.poll() != null)
                ;

        }

        public synchronized int size() {
            removeGarbageCollectedEntities();
            return count;
        }
    }

    private final Segment[] segments;
    private final int mask;
    private final int logOfSegmentNumber;

    public WeakObjectOrdinalMap(int logOfSegmentNumber) {
        if (logOfSegmentNumber < 1 && logOfSegmentNumber > 32) {
            throw new RuntimeException("Invalid power level");
        }
        segments = new Segment[2 << logOfSegmentNumber];
        for(int i=0; i