??xml version="1.0" encoding="utf-8" standalone="yes"?>久久久免费精品视频,国产人久久人人人人爽,亚洲欧美日韩天堂http://www.aygfsteel.com/DLevin/category/54909.htmlIn general the OO style is to use a lot of little objects with a lot of little methods that give us a lot of plug points for overriding and variation. To do is to be -Nietzsche, To bei is to do -Kant, Do be do be do -Sinatrazh-cnFri, 04 Sep 2015 15:05:46 GMTFri, 04 Sep 2015 15:05:46 GMT60Java Cache-EHCachepd之Store实现http://www.aygfsteel.com/DLevin/archive/2013/11/03/405887.htmlDLevinDLevinSun, 03 Nov 2013 14:05:00 GMThttp://www.aygfsteel.com/DLevin/archive/2013/11/03/405887.htmlhttp://www.aygfsteel.com/DLevin/comments/405887.htmlhttp://www.aygfsteel.com/DLevin/archive/2013/11/03/405887.html#Feedback1http://www.aygfsteel.com/DLevin/comments/commentRss/405887.htmlhttp://www.aygfsteel.com/DLevin/services/trackbacks/405887.html写了那么多,l于到Store了。Store是EHCache中Element理的核心,所有的Element都存攑֜Store中,也就是说Store用于所有和Element相关的处理?br />
EHCache中的Element
在EHCache中,它将所有的键值对抽象成一个ElementQ作为面向对象的设计原则Q把数据和操作放在一PElement除了包含key、value属性以外,它还加入了其他和一个Element相关的统计、配|信息以及操作:
public class Element implements Serializable, Cloneable {
    //the cache key. ?.2以后不再强制要求SerializableQ因为如果只是作为内存缓存,则不需要对它做序列化。IgnoreSizeOf注解表示在做SizeOf计算时key会被忽略?/span>
    @IgnoreSizeOf
    private final Object key;
    //the value. ?.2以后不再强制要求SerializableQ因为如果只是作为内存缓存,则不需要对它做序列化?/span>
    private final Object value;
    //version of the element. q个属性只是作为纪录信息,EHCache实际代码中ƈ没有用到Q用户代码可以通过它来实现不同版本的处理问题。默认值是1?br />     //如果net.sf.ehcache.element.version.autopȝ属性设|ؓtrueQ则当Element加入到Cache中时会被更新为当前系l时间。此Ӟ用户讄的g丢失?/span>
    private volatile long version;
    //The number of times the element was hit.命中ơ数Q在每次查找C个Element会加1?/span>
    private volatile long hitCount;
    //The amount of time for the element to live, in seconds. 0 indicates unlimited. 即一个Element自创建(CreationTimeQ以后可以存zȝ旉?/span>
    private volatile int timeToLive = Integer.MIN_VALUE;
    //The amount of time for the element to idle, in seconds. 0 indicates unlimited. 即一个Element自最后一ơ被使用Qmin(CreationTimeQLastAccessTime)Q以后可以存zȝ旉?/span>
    private volatile int timeToIdle = Integer.MIN_VALUE;
    //Pluggable element eviction data instanceQ它存储q个Element的CreationTime、LastAccessTime{信息,H以个抽取成一个单独的cL什么理由,而且q个cȝ名字也不好?/span>
    private transient volatile ElementEvictionData elementEvictionData;
    //If there is an Element in the Cache and it is replaced with a new Element for the same key, 
    
//then both the version number and lastUpdateTime should be updated to reflect that. The creation time
    
//will be the creation time of the new Element, not the original one, so that TTL concepts still work. 在put和replace操作中该属性会被更新?/span>
    private volatile long lastUpdateTime;
    //如果timeToLive和timeToIdle没有手动讄Q该gؓtrueQ此时在计算expired时用CacheConfiguration中的timeTiLive、timeToIdle的|否则使用Element自n的倹{?/span>
    private volatile boolean cacheDefaultLifespan = true;
    //q个ID值用于EHCache内部Q但是暂时不知道怎么用?/span>
    private volatile long id = NOT_SET_ID;
    //判断是否expiredQ这里如果timeToLive、timeToIdle都是Integer.MIN_VALUE时返回falseQ当他们都是0ӞisEternalq回true
    public boolean isExpired() {
        if (!isLifespanSet() || isEternal()) {
            return false;
        }

        long now = System.currentTimeMillis();
        long expirationTime = getExpirationTime();

        return now > expirationTime;
    }
    //expirationTime法Q如果timeToIdle没有讄Q或讄了,但是该Elementq没有用过Q取timeToLive计算出的|如果timeToLive没有讄Q则取timeToIdle计算出的|
    
//否则Q取他们的最倹{?/span>
    public long getExpirationTime() {
        if (!isLifespanSet() || isEternal()) {
            return Long.MAX_VALUE;
        }

        long expirationTime = 0;
        long ttlExpiry = elementEvictionData.getCreationTime() + TimeUtil.toMillis(getTimeToLive());

        long mostRecentTime = Math.max(elementEvictionData.getCreationTime(), elementEvictionData.getLastAccessTime());
        long ttiExpiry = mostRecentTime + TimeUtil.toMillis(getTimeToIdle());

        if (getTimeToLive() != 0 && (getTimeToIdle() == 0 || elementEvictionData.getLastAccessTime() == 0)) {
            expirationTime = ttlExpiry;
        } else if (getTimeToLive() == 0) {
            expirationTime = ttiExpiry;
        } else {
            expirationTime = Math.min(ttlExpiry, ttiExpiry);
        }
        return expirationTime;
    }
    //在将Element加入到Cache中ƈ且它的timeToLive和timeToIdle都没有设|时Q它的timeToLive和timeToIdle会根据CacheConfiguration的D用这个方法更新?/span>
    protected void setLifespanDefaults(int tti, int ttl, boolean eternal) {
        if (eternal) {
            this.timeToIdle = 0;
            this.timeToLive = 0;
        } else if (isEternal()) {
            this.timeToIdle = Integer.MIN_VALUE;
            this.timeToLive = Integer.MIN_VALUE;
        } else {
            timeToIdle = tti;
            timeToLive = ttl;
        }
    }
}
public class DefaultElementEvictionData implements ElementEvictionData {
    private long creationTime;
    private long lastAccessTime;
}
public class Cache implements InternalEhcache, StoreListener {
    private void applyDefaultsToElementWithoutLifespanSet(Element element) {
        if (!element.isLifespanSet()) {
            element.setLifespanDefaults(TimeUtil.convertTimeToInt(configuration.getTimeToIdleSeconds()),
                    TimeUtil.convertTimeToInt(configuration.getTimeToLiveSeconds()),
                    configuration.isEternal());
        }
    }
}

EHCache中的Store设计
Store是EHCache中用于存储、管理所有Element的仓库,它抽象出了所有对Element在内存中以及盘中的操作。基本的它可以向一个Store中添加ElementQput、putAll、putWithWriter、putIfAbsentQ、从一个Store获取一个或一些ElementQget、getQuiet、getAll、getAllQuietQ、获取一个Store中所有keyQgetKeysQ、从一个Store中移除一个或一些ElementQremove、removeElement、removeAll、removeWithWriterQ、替换一个Store中已存储的ElementQreplaceQ、pin或unpin一个ElementQunpinAll、isPinned、setPinnedQ、添加或删除StoreListenerQaddStoreListener、removeStoreListenerQ、获取一个Store的Element数量QgetSize、getInMemorySize、getOffHeapSize、getOnDiskSize、getTerracottaClusteredSizeQ、获取一个Store的Element以Byte为单位的大小QgetInMemorySizeInBytes、getOffHeapSizeInBytes、getOnDiskSizeInBytesQ、判断一个key的存在性(containsKey、containsKeyOnDisk、containsKeyOffHeap、containsKeyInMemoryQ、query操作QsetAttributeExtractors、executeQuery、getSearchAttributeQ、cluster相关操作QisCacheCoherent、isClusterCoherent、isNodeCoherent、setNodeCoherent、waitUtilClusterCoherentQ、其他操作(dispose、getStatus、getMBean、hasAbortedSizeOf、expireElements、flush、bufferFull、getInMemoryEvictionPolicy、setInMemoryEvictionPolicy、getInternalContext、calculateSizeQ?br />
所谓CacheQ就是将部分常用数据~存在内存中Q从而提升程序的效率Q然而内存大毕竟有限,因而有时候也需要有盘加以辅助Q因而在EHCache中真正的Store实现׃U(不考虑分布式缓存的情况下)Q存储在内存中的MemoryStore和存储在盘中的DiskStoreQ而所有其他Store都是l予q两个Store的基上来扩展Store的功能,如因为内存大的限制Q有些时候需要将内存中的暂时不用的Element写入到磁盘中Q以腑ևI间l其他更常用的ElementQ此时就需要MemoryStore和DiskStore共同来完成,q就是FrontCacheTier做的事情Q所有可以结合FrontEndCacheTier一起用的Store都要实现TierableStore接口QDiskStore、MemoryStore、NullStoreQ;对于可控制Store占用I间大小做限制的Storeq可以实现PoolableStoreQDiskStore、MemoryStoreQ;对于hTerracottaҎ的Storeq实CTerracottaStore接口QTransactionStore{)?br />
EHCache中Store的设计类l构囑֦下:

AbstractStore
几乎所有的Store实现都承自AbstractStoreQ它实现了Query、Cluster{相关的接口Q但没有涉及Element的管理,而且q部分现在也不了解,不详q?br />
MemoryStore和NotifyingMemoryStore
MemoryStore是EHCache中存储在内存中的Element的仓库。它使用SelectableConcurrentHashMap作ؓ内部的存储结构,该类实现参考ConcurrentHashMapQ只是它加入了pinned、evict{逻辑Q不详述Q注Q它的setPinnedҎ中,对不存在的keyQ会使用一个DUMMY_PINNED_ELEMENT来创Z个节点,q将它添加到HashEntry的链中,q时Z么?H以个应该是Z在以后这个keydq来后,当前的pinned讄可以对它有媄响,因ؓMemoryStore中ƈ没有包含所有的ElementQ还有一部分Element是在DiskStore中)。而MemoryStore中的基本实现都代理给SelectableConcurrentHashMapQ里面的其他l节在之前的文章中也有说明,不再赘述?

而NotifyingMemoryStorel承自MemoryStoreQ它在Element evict和exipre时会调用注册的CacheEventListener?br />
DiskStore
DiskStore依然采用ConcurrentHashMap的实现思想Q因而这部分逻辑不赘q。对DiskStoreQ当一个Elementdq来后,需要将其写入到盘中,q是接下来关注的重点。在DiskStore中,一个Element不再以Element本n而存在,而是以DiskSubstitute的实例而存在,DiskSubstitute有两个子c:PlaceHolder和DiskMarkerQ当一个Element初始被添加到DiskStore中时Q它是以PlaceHolder的Ş式存在,当这个PlaceHolder被写入到盘中时Q它会{换成DiskMarker?br />
    public abstract static class DiskSubstitute {
        protected transient volatile long onHeapSize;
        @IgnoreSizeOf
        private transient volatile DiskStorageFactory factory;
        DiskSubstitute(DiskStorageFactory factory) {
            this.factory = factory;
        }
        abstract Object getKey();
        abstract long getHitCount();
        abstract long getExpirationTime();
        abstract void installed();
        public final DiskStorageFactory getFactory() {
            return factory;
        }
    }
    final class Placeholder extends DiskSubstitute {
        @IgnoreSizeOf
        private final Object key;
        private final Element element;
        private volatile boolean failedToFlush;
        Placeholder(Element element) {
            super(DiskStorageFactory.this);
            this.key = element.getObjectKey();
            this.element = element;
        }
        @Override
        public void installed() {
            DiskStorageFactory.this.schedule(new PersistentDiskWriteTask(this));
        }
    }
    public static class DiskMarker extends DiskSubstitute implements Serializable {
        @IgnoreSizeOf
        private final Object key;
        private final long position;
        private final int size;
        private volatile long hitCount;
        private volatile long expiry;
        DiskMarker(DiskStorageFactory factory, long position, int size, Element element) {
            super(factory);
            this.position = position;
            this.size = size;

            this.key = element.getObjectKey();
            this.hitCount = element.getHitCount();
            this.expiry = element.getExpirationTime();
        }
        @Override
        public void installed() {
            //no-op
        }
        void hit(Element e) {
            hitCount++;
            expiry = e.getExpirationTime();
        }
    }
当向DiskStored一个ElementӞ它会先创Z个PlaceHolderQƈ该PlaceHolderd到DiskStore中,q在d完成后调用PlaceHolder的installed()ҎQ该Ҏ会用DiskStorageFactory schedule一个PersistentDiskWriteTaskQ将该PlaceHolder写入到磁盘(在DiskStorageFactory有一个DiskWriterU程会在一定的时候执行该TaskQ生成一个DiskMarkerQ释放PlaceHolder占用的内存。在从DiskStoreU除一个ElementӞ它会先读取磁盘中的数据,其解析成ElementQ然后释放这个Element占用的磁盘空_q返回这个被U除的Element。在从DiskStored一个ElementӞ它需要找到DiskStore中的DiskSubstituteQ对DiskMarkerd盘中的数据Q解析成ElementQ然后返回?

FrontEndCacheTier
上述的MemoryStore和DiskStoreQ他们是各自独立的,然而Cache的一个重要特Ҏ可以部分内存中的数据evict出到盘Q因为内存毕竟是有限的,所以需要有另一个Store可以MemoryStore和DiskStore联系hQ这是FrontEndCacheTier做的事情。FrontEndCacheTier有两个子c:DiskBackedMemoryStore和MemoryOnlyStoreQ这两个cȝ名字已经能很好的说明他们的用途了QDiskBackedMemoryStore可以部分Element先evict出到盘Q它也支持把盘文g作ؓpersistent介质Q在下一ơ读取时可以直接从磁盘中的文件直接读取ƈ重新构徏原来的缓存;而MemoryOnlyStore则只支持Element存储在内存中。FrontEndCacheTier有两个Store属性:cache和authorityQ它基本上所有的操作都直接同时代理给q两个StoreQ其中把authority作ؓȝ存储StoreQ而将cache作ؓ~存的Store。在DiskBackedMemoryStore中,authority是DiskStoreQ而cache是MemoryStoreQ即DiskBackedMemoryStoreDiskStore作ؓȝ存储StoreQ这刚开始让我很惊讶Q不q仔l想想也是合理的Q因为毕竟这里的Disk是作为persistent介质的;在MemoryOnlyStore中,authority是MemoryStoreQ而cache是NullStore?br /> FrontEndCacheTier在实现getҎӞd了faults属性的ConcurrentHashMapQ它是用于多个线E在同时d同一key的Element旉免多ơ读取,每次之前key和一个Fault新实例添加到faults中,q样W二个线E发现已l有另一个线E在读这个Element了,它就可以{待W一个线E读完直接拿W一个线E读取的l果卛_Q以提升性能?br /> 所有作为FrontEndCacheTier的内部Store都必d现TierableStore接口Q其中fill、removeIfNotPinned、isTierPinned、getPresentPinnedKeys为cache store准备Q而removeNoReturn、isPersistent为authority store准备?br />
public interface TierableStore extends Store {
    void fill(Element e);
    boolean removeIfNotPinned(Object key);
    void removeNoReturn(Object key);
    boolean isTierPinned();
    Set getPresentPinnedKeys();
    boolean isPersistent();
}

LruMemoryStore和LegacyStoreWrapper
q两个Store只是Z兼容而存在,其中LruMemoryStore使用LinkedHashMap作ؓ其存储结构,他只支持一UEvict法QLRUQ这个Store的名字也因此而来Q其他功能它cMMemoryStoreQ而LegacyStoreWrapper则类似FrontEndCacheTier。这两个Store的代码比较简单,而且他们也不应该再被使用Q因而不l究?

TerraccottaStore
Ҏ有实现这个接口的Store都还不了解,看以后有没有旉回来了。。。?img src ="http://www.aygfsteel.com/DLevin/aggbug/405887.html" width = "1" height = "1" />

DLevin 2013-11-03 22:05 发表评论
]]>
Java Cache-EHCachepd之用Pool和PoolAccessor抽象实现内存和磁盘中数据字节数的控制和Evicthttp://www.aygfsteel.com/DLevin/archive/2013/11/03/405886.htmlDLevinDLevinSat, 02 Nov 2013 16:49:00 GMThttp://www.aygfsteel.com/DLevin/archive/2013/11/03/405886.htmlhttp://www.aygfsteel.com/DLevin/comments/405886.htmlhttp://www.aygfsteel.com/DLevin/archive/2013/11/03/405886.html#Feedback1http://www.aygfsteel.com/DLevin/comments/commentRss/405886.htmlhttp://www.aygfsteel.com/DLevin/services/trackbacks/405886.html在上一?a href="">Java Cache-EHCachepd之计实例占用的内存大小QSizeOf引擎Q?/a>》中有说刎ͼ在EHCache中,可以讄maxBytesLocalHeap、maxBytesLocalOffHeap、maxBytesLocalDisk|以控制Cache占用的内存、磁盘的大小Q注Q这里Off Heap是指Element中的值已被序列化Q但是还没写入磁盘的状态,貌似只有企业版的EHCache支持q种配置Q而这里maxBytesLocalDisk是指在最大在盘中的数据大小Q而不是磁盘文件大,因ؓ盘文中有一些数据是I闲区)。那么如何实现这个需求呢Q?br />
常规设计思\
对这个需求,我们已经有SizeOfEngine用于计算内存中以及磁盘中的实例大了Q按我的常规思\Q实现v来会比较单,直接在Store中添加maxBytes以及currentBytes字段Q用于表辑ֽ前Store可以存放的最大实例大以及当前Store已存攄实例d。对MemoryStoreQ在d新Element之前使用DefaultSizeOfEngine计算要添加实例占用内存的大小Q然后判断要d的数据大和currentBytes相加的值是否会过maxBytesQ如果是Q先扑և已经expired的ElementQ将q些ElementU除Q如果此时还没能在maxBytes大小的限制中d新的ElementQ则对当前Store做evict操作Qevict超qmaxBytes大小的的数据大小Q如果是更新原来已存在的ElementQ一U方法先查找出已存在的ElementQ然后计新d的Element和原来Element占用内存大小的Delta|如果该值大?q且和currentBytes相加会大于maxBytes|则如上做expire和evict操作Q另一U方法是先移除已存在的ElementQ然后对当前Element做新dElement操作。对DiskStoreQ用DiskSizeOfEngine计算要添加的实例大小Q其他和MemoryStorecM?br />
在EHCache中,MemoryStore和DiskStore都是使用了Segment的设计思想Q不知道是否因ؓConcurrentHashMap的媄响,甚至Guava Cache也采用了q种Segment的设计思想Q,因而我们可以将maxBytes、currentBytes抽象Z个MaxBytesGuarderc,q且该实例赋值给每个Segment实例Q作为面向对象设计的基本思\Q将数据和操作放在一起它已经有了maxBytes和currentBytes数据了,我们可以尝试这和它相关的操作d到这个类中,比如实例占用内存、磁盘大的计算Q因而可以将SizeOfEngine引h该类中,另外它还可以包含一个ElementEvictor实例Q它可用于evict操作Q同时因为MaxBytesGuarder在每个Segment׃nQ因而它必须是线E安全的。对更新已存在的Element可以采用d之前先移除的ҎQ这样MaxBytesGuarder可以设计成包含如下ҎQ?br />
public interface MaxBytesGuarder {
    public int getCurrentSize();
    public void setMaxSize(int maxSize);
    public int getMaxSize();
    
    public int addWithEvict(Element element);
    public boolean canAddWithoutEvict(Element element);
    public void delete(int size);
    public ElementEvictor getEvictor();
    public Store getStore();
}

常规的Cache设计思\是用MemoryStore存储内存中的ElementQ而将DiskStore作ؓMemoryStore的从属,只有在Evict发生才将Element通过DiskStore写入盘Q读取时Q只有在MemoryStore中找不到对应的ElementӞ才从DiskStore中查找,q且找到的ElementdMemoryStoreQ对q种设计MaxBytesGuarder只需要在各自的Store中存在即可。但是在EHCache当前Store的设计中QDiskStoreq没有作为MemoryStore的从属,它们是单独可用的StoreQEHCache使用DiskBackedMemoryStore把它们联pv来,q个在接下来的文章中会详l分析。在DiskBackedMemoryStore的实CQDiskStore作ؓauthorityQ对每次新添加ElementQ会首先d到DiskStore中,然后才是到MemoryStoreQ从而对MemoryStore来说Q它在做evictӞ直接删除卛_Q因为DiskStore已经存储了所有的Element信息。既然DiskStore可以存储内存中的Element以及盘中的ElementQƈ且对盘中的ElementQEHCache也会在内存中有一些纪录信息,因而它需要同时控制内存中的Element占用内存最大g及磁盘中能保留的Element的最大倹{对q个需求,以上的MaxBytesGuarder很隑֊CQ因而EHCache做了q一步的抽象Q把MaxBytesGuarder拆分成两个接口:Pool和PoolAccessorQ其中Pool用于对maxSize的抽象,它包含PoolEvictor引用Q已处理需要Evict时的操作Q通过Pool创徏PoolAccessorQPoolAccessor和一个Store相关联,用于处理和某个Store相关的put、remove{操作,一个Pool当前Size是通过其创建的所有PoolAccessor的sized?br />
Pool和PoolAccessor设计
Pool和PoolAccessor的类l构囑֦下:

从类l构图中可以知道QPool包含maxSize、PoolEvictor、SizeOfEngine以及PoolAccessor的ListQƈ且Pool内部所有PoolAccessor的size和是当前Pool实例当前使用的size。Poolq可用于创徏相应的PoolAccessor实例Q通过Pool创徏的PoolAccessor会被自动的添加到Pool中。在Cache实例初始化时Q它会根据配|创建相应的onHeapPool和onDiskPoolQ它们可以是UnboundedPoolQ它的createPoolAccessorҎ创徏UnboundedPoolAccessor实例Q可以是BoundedPoolQ用于创建AtomicPoolAccessorQ可以是StrictlyBoundedPoolQ用于创建LockedPoolAccessor。其中AtomicPoolAccessor和LockedPoolAccessor的区别在于对size的锁机制QAtomicPoolAccessor使用AtomicLongU录size的D不是通过锁,q样会引起maxSize的限制有保证的情况,比如两个U程同时在添加ElementQ但是在一个线E还没添加完成时另一个线E已l执行了判断语句说当前还有够多的空_然而事实上在第一个线E执行完成后Q当前的I间可能已经不够了,q种方式的好处是没有锁,效率很高Q在那些对maxSize不是很敏感的目中可以用(感觉一般对q个值都不会很敏感)Q而LockedPoolAccessor则是采用锁的机制来保证size值的一致性,它一般用于那些对maxSize值特别敏感的目中。PoolAccessor由Pool创徏Q除了从Pool中承Pool自n实例、SizeOfEngine实例以外Q它q和一个PoolableStore相绑定,q个l定的PoolableStore可以通过PoolAccessor向Pool中消费一个Element大小的空_addҎQ、判断是否可以在没有Evict的情况下消费一个Element大小的空_canAddWithoutEvictingҎQ、移除指定大的I间、以另一个Element大小的空间替换已存在的另一个ElementI间大小QreplaceQ等。这里的addҎQ当可以成功的向q个PoolAccessor消费一个Element大小的空间时Q它q回新添加的Element大小Q否则返回-1Q表C添加失败;另外对addҎ的container表示存放该Element的实例,比如HashEntryQ只用于计算大小Q因而一般都用一个空的HashEntry来替代)Q对onDiskPoolAccessor来说Q它用于表示一个DiskMarker?

PoolEvictor
PoolableStore在调用PoolAccessor的addҎ用于消费Pool中一个Element大小的空间时Q会调用PoolEvictor的freeSpace该Pool相关联的所有PoolableStore释放掉不够的I间大小Q?br />
    protected long add(long sizeOf, boolean force) {
        long newSize = getPool().getSize() + sizeOf;

        if (newSize <= getPool().getMaxSize()) {
            // there is enough room => add & approve
            size.addAndGet(sizeOf);
            return sizeOf;
        } else {
            // check that the element isn't too big
            if (!force && sizeOf > getPool().getMaxSize()) {
                // this is too big to fit in the pool
                return -1;
            }

            // if there is not enough room => evict
            long missingSize = newSize - getPool().getMaxSize();

            if (getPool().getEvictor().freeSpace(getPool().getPoolableStores(), missingSize) || force) {
                size.addAndGet(sizeOf);
                return sizeOf;
            } else {
                // cannot free enough bytes
                return -1;
            }
        }
    }
PoolEvictor接口则是对如何从多个PoolableStore中evict出指定空间大的法的抽象,在EHCache中默认实C两中法QBalanced和FromLargest。FromLargest法比较单,它只需要遍历所有的PoolableStoreQ每ơ选择一个没有evictq的使用I间最大的PoolableStoreQ尝试从它evict出指定大的I间直到指定大小的空间被腑և来;Balanced法有点复杂Q它先打乱PoolableStoreQ然后遍历ؕ序的PoolableStoreQ每ơ选取其后部分PoolableStoreQ对齐按一下算法排序,遍历排序后的子PoolableStore集,Ҏ个PoolableStore试evict指定大小的空_直到evict执行成功?br />
/*
 * The code below is a simplified version of this:
 *
 * float meanEntrySize = byteSize / countSize;
 * float accessRate = hitRate + missRate;
 * float fillLevel = hitRate / accessRate;
 * float deltaFillLevel = fillLevel / byteSize;
 *
 * return meanEntrySize * accessRate * deltaFillLevel * hitDistributionFunction(fillLevel);
*/
因ؓ在PoolableStore中将从磁盘evict和从内存evict定义成两个不同的ҎQ因而对每种法的PoolEvictor都由两个子类实现QBalancedAccessOnDiskPoolEvictor、BalancedAccessOnHeapPoolEvictor和FromLargestCacheOnDiskPoolEvictor、FromLargestCacheOnHeapPoolEvictor。这里PoolableStore接口的抽象用于在提供Evict操作时的信息Q如PoolEvictor中evictҎ的实现、Balanced法中的一些统计信息的获得{:
public interface PoolableStore extends Store {
    boolean evictFromOnHeap(int count, long size);
    boolean evictFromOnDisk(int count, long size);
    float getApproximateDiskHitRate();
    float getApproximateDiskMissRate();
    long getApproximateDiskCountSize();
    long getApproximateDiskByteSize();
    float getApproximateHeapHitRate();
    float getApproximateHeapMissRate();
    long getApproximateHeapCountSize();
    long getApproximateHeapByteSize();
}

PoolableStroe中evict逻辑的实?/strong>
所谓evict是使用配置的evict法选出部分Element实例Q将它们从Store中移除。对MemoryStoreQ它只实现evictFromOnHeapҎQ而对DiskStore只需实现evictFromOnDiskҎ?br />
对MemoryStoreQevict操作的主要流E是Ҏ配置的EvictPolicy选取下一个expired或要被evict的ElementQ将q个ElementU除Qƈ出发expired或evict事gQ在做evict之前先判断该Element或当前Store处于pinned状态,如果是,则不做evictQ返回false。因而这里最主要的是要如何用EvictPolicy选取下一个要被Evict的Element。EHCache实现了四U算法:最q最用算法(LRUQ、先q先出算法(FIFOQ、最用算法(LFUQ、钟法QCLOCKQ?br /> 钟算法实现比较简单,它随即的选择一个SegmentQ每个Segment内部保存一个evictionIteratorQ每一ơevict调用是从这个Iterator中获取下一个expired Element或unpinned ElementQ如果该Iterator遍历到最后一个ElementQ则重新开始,卛_钟一样不同的循环Q,找到的Element从该Segment中移除?br /> 对其他的法Q都要先从MemoryStore中选取一个Element的样本数l,然后使用不同的Policy实现获取h中的候选evict Element。样本Element数组的最大容量是30Q其选取法是:如果当前evict是因为新d一个Element引vQ则从新d的Element所在的Segment中选取hQ否则随机的选取一个SegmentQ在选取的Segment中随机的选取一个HashEntry链,这个链中所有unpinned Element加入的样本数据中Q如果一条链不够Q则循环的查找下一条链直到h量达到指定的要求或整个Segment所有unpinned Element都已l添加到h中。所有的法都是Zq些h选择下一个候选的evict Element?br /> FifoPolicyQ样本中UpdateQCreateQ时间最早的Element?br /> LfuPolicyQ样本中最被使用的ElementQHit Count最)?br /> LruPolicyQ样本中最q最被使用的ElementQLastAccessTime最)?br />

对DiskStoreQevict操作cMMemoryStoreQ先扑ֈ一个DiskSubstituteQ必LDiskMarkercdQ样本数l(法和MemoryStore中找Elementh数组cMQ最大样本容量也?0Q,Ҏ到的h数组采用最用算法(Hit CountQ或Ҏ传入要被evict的key作ؓ下一个evict的候选,q尝试将该DiskSubstituteQDiskMarkerQ从盘中移除,d盘中的数据q反序列化成ElementQ返回凡序列化后的Element实例。移除的步骤包括Q将他从MemoryStore中移除;DiskSubstitute对应的节点从HashEntry中删除;释放该Element原本在磁盘中占用的空_释放Disk Pool中占用的I间Q释放Heap Pool中占用的I间?img src ="http://www.aygfsteel.com/DLevin/aggbug/405886.html" width = "1" height = "1" />

DLevin 2013-11-03 00:49 发表评论
]]>
Java Cache-EHCachepd之计实例占用的内存大小QSizeOf引擎Q?/title><link>http://www.aygfsteel.com/DLevin/archive/2013/11/01/405822.html</link><dc:creator>DLevin</dc:creator><author>DLevin</author><pubDate>Fri, 01 Nov 2013 03:03:00 GMT</pubDate><guid>http://www.aygfsteel.com/DLevin/archive/2013/11/01/405822.html</guid><wfw:comment>http://www.aygfsteel.com/DLevin/comments/405822.html</wfw:comment><comments>http://www.aygfsteel.com/DLevin/archive/2013/11/01/405822.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.aygfsteel.com/DLevin/comments/commentRss/405822.html</wfw:commentRss><trackback:ping>http://www.aygfsteel.com/DLevin/services/trackbacks/405822.html</trackback:ping><description><![CDATA[在EHCache中,可以讄maxBytesLocalHeap、maxBytesLocalOffHeap、maxBytesLocalDisk|以控制Cache占用的内存、磁盘的大小Q注Q这里Off Heap是指Element中的值已被序列化Q但是还没写入磁盘的状态,貌似只有企业版的EHCache支持q种配置Q而这里maxBytesLocalDisk是指在最大在盘中的数据大小Q而不是磁盘文件大,因ؓ盘文中有一些数据是I闲区)Q因而EHCache需要有一U机制计一个类在内存、磁盘中占用的字节数Q其中在盘中占用的字节大小计算比较ҎQ只需要知道序列化后字节数l的大小Qƈ且加上一些统计信息,如过期时间、磁盘位|、命中次数等信息卛_Q而要计算一个对象实例在内存中占用的大小则要复杂一些?br /> <br /> <strong style="color: orange;">计算一个实例内存占用大思\</strong><br /> 在Java中,除了基本cdQ其他所有通过字段包含其他实例的关p都是引用关p,因而我们不能直接计该实例占用的内存大,而是要递归的计其所有字D占用的内存大小的和。在Java中,我们可以所有这些通过字段引用单的看成一U树状结构,q样可以遍历这|Q计每个节点占用的内存大小Q所有这些节点占用的内存大小的d当前实例占用的内存大小Q遍历的法有:先序遍历、中序遍历、后序遍历、层U遍历等。但是在实际情况中很Ҏ出现环状引用Q最单的是两个实例之间的直接引用Q还有是多个实例构成的一个引用圈Q,而破坏这U树状结构,而让引用变成囄l构。然而图的遍历相Ҏ较复杂(臛_Ҏ来说Q,因而我更愿意把它l看成一颗树状图Q采用层U遍历,通过一个IdentitySetU录已经计算q的节点Q实例)Qƈ且用一个Queue来纪录剩余需要计的节点。算法步骤如下:<br /> 1. 先将当前实例加入Queue中?br /> 2. 循环取出Queue中的头节点,计算它占用的内存大小Q加到d存大中Qƈ该节点d到IdentitySet中?br /> 3. 扑ֈ该节Ҏ有非基本cd的子节点Q对每个子节点,如果在IdentityMap中没有这个子节点的实例,则将该实例加入的Queue?br /> 4. 回到2l箋计算直到Queue为空?br /> 剩下的问题就是如何计一个实例本w占用的内存大小了。这个以我目前的l验Q我只能惛_遍历一个实例的所有实例字D,Ҏ每个字段的类型来判断每个字段占用的内存大,然后它们的和是该实例占用的d存的大小。对于字D늚cdQ首先是基本cd字段Qbyte、boolean占一个字节,short、char?个字节,int、float?个字节,double?个字节等Q然后是引用cdQ对cdQ印象中虚拟范中没有定义其大,但是一般来说对32位系l占4个字节,?4位系l占8个字节;再就是对数组Q基本类型的数组Qbyte每个元素?个字节,short、char每个元素?个字节,int每个元素?个字节,double每个元素?个字节,引用cd的数l,先计每个引用元素占用的字节敎ͼ然后是引用本省占用的字节数?br /> 以上是我对EHCache中计一个实例逻辑不了解的时候的个h看法Q那么接下来我们看看EHCache怎么来计?br /> <br /> <strong style="color: orange;">Java对象内存l构Q以Sun JVMZQ?/strong><br /> 参考:http://www.importnew.com/1305.htmlQ之所以把参考链接放在开头是因ؓ下面基本上是寚w接所在文章的整理Q之所以要整理一遍,一是怕原链接文章消失Q二则是Z加深自己的理解?br /> 在Sun JVM中,除数l以外的对象都有8个字节的头部Q数l还有额外的4个字节头部用于存N度信息)Q前?个字节包含这个对象的标识哈希码以及其他一些flagQ如锁状态、年龄等标识信息Q后4个字节包含一个指向对象的cd例(Class实例Q的引用。在q头?个字节之后的内存l构遵@一?个规则:<br /> <em style="color: purple;">规则1: M对象都是?个字节ؓ_度q行寚w的?/em><br /> 比如对一个Objectc,因ؓ它没有Q何实例,因而它只有8个头部直接,则它?个字节大。而对一个只包含一个byte字段的实例,它需要填上(paddingQ?个字节的大小Q因而它?6个字节,典型的如一个Boolean实例要占?6个字节的内存Q?br /> <div style="background-color: #eeeeee; font-size: 13px; border: 1px solid #cccccc; padding: 4px 5px 4px 4px; width: 98%; word-break: break-all;"><!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --><span style="color: #0000ff;">class</span> MyClass {<br />     <span style="color: #0000FF; ">byte</span> a;<br /> }<br /> [HEADER:    8 bytes] 8<br /> [a:             1 <span style="color: purple;">byte</span> ] 9<br /> [padding:    7 bytes] 16</div> <em style="color: purple;">规则2: cd性按照如下优先q行排列Q长整型和双_ֺcdQ整型和点型;字符和短整型Q字节类型和布尔cdQ最后是引用cd。这些属性都按照各自的单位对齐?/em><br /> 在Java对象内存l构中,对象以上q的8个字节的头部开始,然后对象属性紧随其后。ؓ了节省内存,Sun VMq没有按照属性声明时序来进行内存布局Q而是使用如下序排列Q?br /> 1. 双精度型QdoubleQ和长整型(longQ,8字节?br /> 2. 整型QintQ和点型(floatQ,4字节?br /> 3. 短整型(shortQ和字符型(charQ,2字节?br /> 4. 布尔型(booleanQ和字节型(byteQ,2字节?br /> 5. 引用cd?br /> q且对象属性L以它们的单位寚wQ对于不?字节的数据类型,会填充未?字节的部分。之所以要填充是出于性能考虑Q因Z内存中读?字节数据?字节寄存器的动作Q如果数据以4字节寚w的情况小Q效率要高的多?br /> <div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --><span style="color: #0000FF; ">class</span> MyClass {<br />     <span style="color: #0000FF; ">byte</span> a;<br />     <span style="color: #0000FF; ">int</span> c;<br />     <span style="color: #0000FF; ">boolean</span> d;<br />     <span style="color: #0000FF; ">long</span> e;<br />     Object f;<br /> }<br /> <span style="color: #008000; ">//</span><span style="color: #008000; ">如果JVM不对光排序Q它要占40个字?/span><span style="color: #008000; "><br /> </span>[HEADER:    8 bytes] 8<br /> [a:             1 <span style="color: #0000FF; ">byte</span> ] 9<br /> [padding:    3 bytes] 12<br /> [c:             4 bytes] 16<br /> [d:             1 <span style="color: #0000FF; ">byte</span> ] 17<br /> [padding:    7 bytes] 24<br /> [e:             8 bytes] 32<br /> [f:              4 bytes] 36<br /> [padding:     4 bytes] 40<br /> <span style="color: #008000; ">//</span><span style="color: #008000; ">lJVM重排序后Q只需要占32个字?/span><span style="color: #008000; "><br /> </span>[HEADER:       8 bytes] 8<br /> [e:                8 bytes] 16<br /> [c:                4 bytes] 20<br /> [a:                1 <span style="color: #0000FF; ">byte</span> ] 21<br /> [d:                1 <span style="color: #0000FF; ">byte</span> ] 22<br /> [padding:       2 bytes] 24<br /> [f:                4 bytes] 28<br /> [padding:       4 bytes] 32</div> <span style="color: black;"><em style="color: purple;">规则3: 不同cȝ承关pM的成员不能؜合排列。首先按照规?处理父类中的成员Q接着才是子类的成员?/em><br /> </span> <div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --><span style="color: #0000FF; ">class</span> A {<br />     <span style="color: #0000FF; ">long</span> a;<br />     <span style="color: #0000FF; ">int</span> b;<br />     <span style="color: #0000FF; ">int</span> c;<br /> }<br /> <span style="color: #0000FF; ">class</span> B <span style="color: #0000FF; ">extends</span> A {<br />     <span style="color: #0000FF; ">long</span> d;<br /> }<br /> [HEADER:      8 bytes] 8<br /> [a:               8 bytes] 16<br /> [b:               4 bytes] 20<br /> [c:               8 bytes] 32</div> <span style="color: black;"><em style="color: purple;">规则4: 当父cL后一个属性和子类W一个属性之间间隔不?字节Ӟ必须扩展?个字节的基本单位?/em><br /> </span> <div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --><span style="color: #0000FF; ">class</span> A {<br />     <span style="color: #0000FF; ">byte</span> a;<br /> }<br /> <span style="color: #0000FF; ">class</span> B <span style="color: #0000FF; ">extends</span> A {<br />     <span style="color: #0000FF; ">byte</span> b;<br /> }<br /> [HEADER:    8 bytes] 8<br /> [a:             1 <span style="color: #0000FF; ">byte</span> ] 9<br /> [padding:    3 bytes] 12<br /> [b:             1 <span style="color: #0000FF; ">byte</span> ] 13<br /> [padding:    3 bytes] 16</div> <span style="color: black;"><em style="color: purple;">规则5: 如果子类W一个成员时一个双_ֺ或长整型Qƈ且父cL有用?个字节,JVM会破坏规?Q按整型QintQ、短整型QshortQ、字节型QbyteQ、引用类型(referenceQ的序向未填满的空间填充?/em><br /> </span> <div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --><span style="color: #0000FF; ">class</span> A {<br />     <span style="color: #0000FF; ">byte</span> a;<br /> }<br /> <span style="color: #0000FF; ">class</span> B <span style="color: #0000FF; ">extends</span> A {<br />     <span style="color: #0000FF; ">long</span> b;<br />     <span style="color: #0000FF; ">short</span> c;<br />     <span style="color: #0000FF; ">byte</span> d;<br /> }<br /> [HEADER:    8 bytes] 8<br /> [a:             1 <span style="color: #0000FF; ">byte</span> ] 9<br /> [padding:    3 bytes] 12<br /> [c:             2 bytes] 14<br /> [d:             1 <span style="color: #0000FF; ">byte</span> ] 15<br /> [padding:    8 bytes] 24</div> <span style="color: black;"><em style="color: purple;">数组内存布局</em><br /> 数组对象除了作ؓ对象而存在的头以外,q存在一个额外的头部成员用来存放数组的长度,它占4个字节?br /> </span> <div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --><span style="color: #008000; ">//</span><span style="color: #008000; ">三个元素的字节数l?/span><span style="color: #008000; "><br /> </span>[HEADER:    12 bytes] 12<br /> [[0]:             1  <span style="color: #0000FF; ">byte</span> ] 13<br /> [[1]:              1 <span style="color: #0000FF; ">byte</span> ] 14<br /> [[2]:              1 <span style="color: #0000FF; ">byte</span> ] 15<br /> [padding:      1 <span style="color: #0000FF; ">byte</span> ] 16<br /> <span style="color: #008000; ">//</span><span style="color: #008000; ">三个元素的长整型数组</span><span style="color: #008000; "><br /> </span>[HEADER:     12 bytes] 12<br /> [padding:     4 bytes ] 16<br /> [[0]:               8 bytes] 24<br /> [[1]:               8 bytes] 32<br /> [[2]:               8 bytes] 40</div> <span style="color: black;"><em style="color: purple;">非静态内部类</em><br /> 非静态内不篏它又一个额外的“隐藏”成员Q这个成员时一个指向外部类的引用变量。这个成员是一个普通引用,因此遵@引用内存布局的规则。因此内部类?个字节的额外开销?br /> <br /> <strong style="color: orange;">EHCache计算一个实例占用的内存大小</strong></span><br /> EHCache中计一个实例占用内存大的基本思\和以上类|遍历实例C的所有节点,Ҏ个节点计其占用的内存大。不q它l构设计的更好,而且它有三种用于计算一个实例占用内存大的实现。我们先来看q三U用于计一个实例占用内存大的逻辑Q?br /> <ol style="margin-top: 1px;"> <li>ReflectionSizeOf<br /> 使用反射的方式计计一个实例占用的内存大小是我上面想到的q种Ҏ?br /> <br /> 因ؓ使用反射计算一个实例占用内存大的Ҏ不同虚拟机的Ҏ是来判断一个实例的各个字段占用的大以及该实例存储额外信息占用的大,因而EHCache中采用JvmInformation枚Dcd来抽象这U对不同虚拟机实现的不同Q?br /> <table style="border-collapse: collapse; width: 996pt;" border="0" cellpadding="0" cellspacing="0" width="1327"> <colgroup><col style="width:254pt" width="339"> <col style="width:88pt" width="117"> <col style="width:96pt" width="128"> <col style="width:100pt" width="133"> <col style="width:87pt" width="116"> <col style="width:96pt" width="128"> <col style="width:135pt" width="180"> <col style="width:140pt" width="186"> </colgroup> <tbody> <tr style="height: 21pt; color: green;" height="28"> <td style="height:21.0pt;width:254pt" height="28" width="339"><strong>JVM Desc</strong></td> <td style="width:88pt" width="117"><strong>PointerSize</strong></td> <td style="width:96pt" width="128"><strong>JavaPointerSize</strong></td> <td style="width:100pt" width="133"><strong>MinimumObjectSize</strong></td> <td style="width:87pt" width="116"><strong>ObjectAlignment</strong></td> <td style="width:96pt" width="128"><strong>ObjectHeaderSize</strong></td> <td style="width:135pt" width="180"><strong>FieldOffsetAdjustment</strong></td> <td style="width:140pt" width="186"><strong>AgentSizeOfAdjustment</strong></td> </tr> <tr style="height:16.5pt;" height="22"> <td style="height:16.5pt;width:254pt" height="22" width="339"><strong>HotSpot 32-Bit</strong></td> <td style="width:88pt" align="right" width="117">4</td> <td style="width:96pt" align="right" width="128">4</td> <td style="width:100pt" align="right" width="133">8</td> <td style="width:87pt" align="right" width="116">8</td> <td style="width:96pt" align="right" width="128">8</td> <td style="width:135pt" align="right" width="180">0</td> <td style="width:140pt" align="right" width="186">0</td> </tr> <tr style="height:16.5pt;" height="22"> <td style="height:16.5pt;width:254pt" height="22" width="339"><strong>HotSpot 32-Bit with Concurrent Mark-and-Sweep GC</strong></td> <td style="width:88pt;padding-bottom:0cm; padding-top:0cm" align="right" width="117">4</td> <td style="width:96pt;padding-bottom:0cm; padding-top:0cm" align="right" width="128">4</td> <td style="width:100pt;padding-bottom:0cm; padding-top:0cm" align="right" width="133">16</td> <td style="width:87pt;padding-bottom:0cm; padding-top:0cm" align="right" width="116">8</td> <td style="width:96pt;padding-bottom:0cm; padding-top:0cm" align="right" width="128">8</td> <td style="width:135pt;padding-bottom:0cm; padding-top:0cm" align="right" width="180">0</td> <td style="width:140pt" align="right" width="186">0</td> </tr> <tr style="height:16.5pt;" height="22"> <td style="height:16.5pt;width:254pt" height="22" width="339"><strong>HotSpot 64-Bit</strong></td> <td style="width:88pt" align="right" width="117">8</td> <td style="width:96pt" align="right" width="128">8</td> <td style="width:100pt" align="right" width="133">8</td> <td style="width:87pt" align="right" width="116">8</td> <td style="width:96pt" align="right" width="128">16</td> <td style="width:135pt" align="right" width="180">0</td> <td style="width:140pt" align="right" width="186">0</td> </tr> <tr style="height:16.5pt;" height="22"> <td style="height:16.5pt;width:254pt" height="22" width="339"><strong>HotSpot 64-Bit With Concurrent Mark-and-Sweep GC</strong></td> <td style="width:88pt;padding-bottom:0cm; padding-top:0cm" align="right" width="117">8</td> <td style="width:96pt;padding-bottom:0cm; padding-top:0cm" align="right" width="128">8</td> <td style="width:100pt;padding-bottom:0cm; padding-top:0cm" align="right" width="133">24</td> <td style="width:87pt;padding-bottom:0cm; padding-top:0cm" align="right" width="116">8</td> <td style="width:96pt;padding-bottom:0cm; padding-top:0cm" align="right" width="128">16</td> <td style="width:135pt;padding-bottom:0cm; padding-top:0cm" align="right" width="180">0</td> <td style="width:140pt" align="right" width="186">0</td> </tr> <tr style="height:16.5pt;" height="22"> <td style="height:16.5pt;width:254pt" height="22" width="339"><strong>HotSpot 64-Bit with Compressed OOPs</strong></td> <td style="width:88pt" align="right" width="117">8</td> <td style="width:96pt" align="right" width="128">4</td> <td style="width:100pt" align="right" width="133">8</td> <td style="width:87pt" align="right" width="116">8</td> <td style="width:96pt" align="right" width="128">12</td> <td style="width:135pt" align="right" width="180">0</td> <td style="width:140pt" align="right" width="186">0</td> </tr> <tr style="height:30.75pt;" height="41"> <td style="height:30.75pt;width:254pt" height="41" width="339"><strong>HotSpot 64-Bit with Compressed OOPs and Concurrent Mark-and-Sweep GC</strong></td> <td style="width:88pt;padding-bottom:0cm; padding-top:0cm" align="right" width="117">8</td> <td style="width:96pt;padding-bottom:0cm; padding-top:0cm" align="right" width="128">4</td> <td style="width:100pt;padding-bottom:0cm; padding-top:0cm" align="right" width="133">24</td> <td style="width:87pt;padding-bottom:0cm; padding-top:0cm" align="right" width="116">8</td> <td style="width:96pt;padding-bottom:0cm; padding-top:0cm" align="right" width="128">12</td> <td style="width:135pt;padding-bottom:0cm; padding-top:0cm" align="right" width="180">0</td> <td style="width:140pt" align="right" width="186">0</td> </tr> <tr style="height:16.5pt;" height="22"> <td style="height:16.5pt;width:254pt" height="22" width="339"><strong>JRockit 32-Bit</strong></td> <td style="width:88pt" align="right" width="117">4</td> <td style="width:96pt" align="right" width="128">4</td> <td style="width:100pt" align="right" width="133">8</td> <td style="width:87pt" align="right" width="116">8</td> <td style="width:96pt" align="right" width="128">16</td> <td style="width:135pt" align="right" width="180">8</td> <td style="width:140pt" align="right" width="186">8</td> </tr> <tr style="height:16.5pt;" height="22"> <td style="height:16.5pt;width:254pt" height="22" width="339"><strong>JRockit 64-Bit(with no reference compression)</strong></td> <td style="width:88pt;padding-bottom:0cm; padding-top:0cm" align="right" width="117">4</td> <td style="width:96pt;padding-bottom:0cm; padding-top:0cm" align="right" width="128">4</td> <td style="width:100pt;padding-bottom:0cm; padding-top:0cm" align="right" width="133">8</td> <td style="width:87pt;padding-bottom:0cm; padding-top:0cm" align="right" width="116">8</td> <td style="width:96pt;padding-bottom:0cm; padding-top:0cm" align="right" width="128">16</td> <td style="width:135pt;padding-bottom:0cm; padding-top:0cm" align="right" width="180">8</td> <td style="width:140pt" align="right" width="186">8</td> </tr> <tr style="height:16.5pt;" height="22"> <td style="height:16.5pt;width:254pt" height="22" width="339"><strong>JRockit 64-Bit with 4GB compressed References</strong></td> <td style="width:88pt" align="right" width="117">4</td> <td style="width:96pt" align="right" width="128">4</td> <td style="width:100pt" align="right" width="133">8</td> <td style="width:87pt" align="right" width="116">8</td> <td style="width:96pt" align="right" width="128">16</td> <td style="width:135pt" align="right" width="180">8</td> <td style="width:140pt" align="right" width="186">8</td> </tr> <tr style="height:16.5pt;" height="22"> <td style="height:16.5pt;width:254pt" height="22" width="339"><strong>JRockit 64-Bit with 32GB Compressed References</strong></td> <td style="width:88pt;padding-bottom:0cm; padding-top:0cm" align="right" width="117">4</td> <td style="width:96pt;padding-bottom:0cm; padding-top:0cm" align="right" width="128">4</td> <td style="width:100pt;padding-bottom:0cm; padding-top:0cm" align="right" width="133">8</td> <td style="width:87pt;padding-bottom:0cm; padding-top:0cm" align="right" width="116">8</td> <td style="width:96pt;padding-bottom:0cm; padding-top:0cm" align="right" width="128">16</td> <td style="width:135pt;padding-bottom:0cm; padding-top:0cm" align="right" width="180">8</td> <td style="width:140pt" align="right" width="186">8</td> </tr> <tr style="height:16.5pt;" height="22"> <td style="height:16.5pt;width:254pt" height="22" width="339"><strong>JRockit 64-Bit with 64GB Compressed References</strong></td> <td style="width:88pt" align="right" width="117">4</td> <td style="width:96pt" align="right" width="128">4</td> <td style="width:100pt" align="right" width="133">16</td> <td style="width:87pt" align="right" width="116">16</td> <td style="width:96pt" align="right" width="128">24</td> <td style="width:135pt" align="right" width="180">16</td> <td style="width:140pt" align="right" width="186">16</td> </tr> <tr style="height:16.5pt;" height="22"> <td style="height:16.5pt;width:254pt" height="22" width="339"><strong>IBM 64-Bit with Compressed References</strong></td> <td style="width:88pt;padding-bottom:0cm; padding-top:0cm" align="right" width="117">4</td> <td style="width:96pt;padding-bottom:0cm; padding-top:0cm" align="right" width="128">4</td> <td style="width:100pt;padding-bottom:0cm; padding-top:0cm" align="right" width="133">8</td> <td style="width:87pt;padding-bottom:0cm; padding-top:0cm" align="right" width="116">8</td> <td style="width:96pt;padding-bottom:0cm; padding-top:0cm" align="right" width="128">16</td> <td style="width:135pt;padding-bottom:0cm; padding-top:0cm" align="right" width="180">0</td> <td style="width:140pt" align="right" width="186">0</td> </tr> <tr style="height:16.5pt;" height="22"> <td style="height:16.5pt;width:254pt" height="22" width="339"><strong>IBM 64-Bit with no reference compression</strong></td> <td style="width:88pt" align="right" width="117">8</td> <td style="width:96pt" align="right" width="128">8</td> <td style="width:100pt" align="right" width="133">8</td> <td style="width:87pt" align="right" width="116">8</td> <td style="width:96pt" align="right" width="128">24</td> <td style="width:135pt" align="right" width="180">0</td> <td style="width:140pt" align="right" width="186">0</td> </tr> <tr style="height:16.5pt;" height="22"> <td style="height:16.5pt;width:254pt" height="22" width="339"><strong>IBM 32-Bit</strong></td> <td style="width:88pt;padding-bottom:0cm; padding-top:0cm" align="right" width="117">4</td> <td style="width:96pt;padding-bottom:0cm; padding-top:0cm" align="right" width="128">4</td> <td style="width:100pt;padding-bottom:0cm; padding-top:0cm" align="right" width="133">8</td> <td style="width:87pt;padding-bottom:0cm; padding-top:0cm" align="right" width="116">8</td> <td style="width:96pt;padding-bottom:0cm; padding-top:0cm" align="right" width="128">16</td> <td style="width:135pt;padding-bottom:0cm; padding-top:0cm" align="right" width="180">0</td> <td style="width:140pt" align="right" width="186">0</td> </tr> <tr style="height:16.5pt;" height="22"> <td style="height:16.5pt;width:254pt" height="22" width="339"><strong>UNKNOWN 32-Bit</strong></td> <td style="width:88pt" align="right" width="117">4</td> <td style="width:96pt" align="right" width="128">4</td> <td style="width:100pt" align="right" width="133">8</td> <td style="width:87pt" align="right" width="116">8</td> <td style="width:96pt" align="right" width="128">8</td> <td style="width:135pt" align="right" width="180">0</td> <td style="width:140pt" align="right" width="186">0</td> </tr> <tr style="height:16.5pt;" height="22"> <td style="height:16.5pt;width:254pt" height="22" width="339"><strong>UNKNOWN 64-Bit</strong></td> <td style="width:88pt" align="right" width="117">8</td> <td style="width:96pt" align="right" width="128">8</td> <td style="width:100pt" align="right" width="133">8</td> <td style="width:87pt" align="right" width="116">8</td> <td style="width:96pt" align="right" width="128">16</td> <td style="width:135pt" align="right" width="180">0</td> <td style="width:140pt" align="right" width="186">0</td> </tr> </tbody> </table> <p>ObjectAligment default: 8<br /> MinimumObjectSize default equals ObjectAligment<br /> ObjectHeaderSize default: PointerSize + JavaPointerSize<br /> FIeldOffsetAdjustment default: 0<br /> AgentSizeOfAdjustment default: 0<br /> ReferenceSize equals JavaPointerSize<br /> ArrayHeaderSize: ObjectHeaderSize + 4(INT Size)<br /> JRockit and IBM JVM do not support ReflectionSizeOf</p> <br /> 而对基本cdQ则因ؓ虚拟机的规范Q它们都是相同的QEHCache中采用PrimitiveType枚Dcd来定义不同基本类型的长度Q?br /> <div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --><span style="color: #0000FF; ">enum</span> PrimitiveType {<br />     BOOLEAN(<span style="color: #0000FF; ">boolean</span>.<span style="color: #0000FF; ">class</span>, 1),<br />     BYTE(<span style="color: #0000FF; ">byte</span>.<span style="color: #0000FF; ">class</span>, 1),<br />     CHAR(<span style="color: #0000FF; ">char</span>.<span style="color: #0000FF; ">class</span>, 2),<br />     SHORT(<span style="color: #0000FF; ">short</span>.<span style="color: #0000FF; ">class</span>, 2),<br />     INT(<span style="color: #0000FF; ">int</span>.<span style="color: #0000FF; ">class</span>, 4),<br />     FLOAT(<span style="color: #0000FF; ">float</span>.<span style="color: #0000FF; ">class</span>, 4),<br />     DOUBLE(<span style="color: #0000FF; ">double</span>.<span style="color: #0000FF; ">class</span>, 8),<br />     LONG(<span style="color: #0000FF; ">long</span>.<span style="color: #0000FF; ">class</span>, 8);<br /> <br />     <span style="color: #0000FF; ">private</span> Class<?> type;<br />     <span style="color: #0000FF; ">private</span> <span style="color: #0000FF; ">int</span> size;<br /> <br />     <span style="color: #0000FF; ">public</span> <span style="color: #0000FF; ">static</span> <span style="color: #0000FF; ">int</span> getReferenceSize() {<br />         <span style="color: #0000FF; ">return</span> CURRENT_JVM_INFORMATION.getJavaPointerSize();<br />     }<br />     <span style="color: #0000FF; ">public</span> <span style="color: #0000FF; ">static</span> <span style="color: #0000FF; ">long</span> getArraySize() {<br />         <span style="color: #0000FF; ">return</span> CURRENT_JVM_INFORMATION.getObjectHeaderSize() + INT.getSize();<br />     }<br /> }</div> <br /> 反射计算一个实例(instanceQ占用内存大(sizeQ步骤如下:<br /> a. 如果instance为nullQsize?Q直接返回?br /> b. 如果instance是数l类型,size为数l头部大+每个数组元素占用大小Q数l长度+填充到对象对齐最单位,最后保证如果size要比对象最大大q相{?br /> c. 如果instance是普通实例,size初始gؓ对象头部大小Q然后找到对象对应类的所有承类Q从最层cd始遍历所有类Q规?Q,Ҏ个类Q纪录长整型和双_ֺ型、整型和点型、短整型和字W型、布型和字节型以及引用cd的非静态字D늚个数。如果整型和双精度型字段个数不ؓ0Q且当前size没有按长整型的大对齐(规则5Q,选择部分其他cd字段排在长整型和双精度型之前Q直到填充到以长整型大小寚wQ然后按照先规则2的顺序排列个字计不同类型字D늚大小。在每个cM间如果没有按规定大小寚wQ则填充~少的字节(规则4Q。在所有类计算完成后,如果没有按照cȝ寚w方式Q则按类寚w规则寚wQ规?Q。最后保证一个对象实例的大小要一个对象最大要大或相等?br /> <br /> </li> <li>UnsafeSizeOf?br /> UnsafeSizeOf的实现比反射的实现要单的多,它用Sun内部库的UnsafecL获取字段的offset值来计算一个类占用的内存大(个h理解Q这个应该只支持Sun JVMQ但是怎么JRockit中有对FieldOffsetAdjustment的配|,而该Ҏ只在q个cM被用。。。)。对数组Q它使用Unsafe.arrayBaseOffset()Ҏq回数组头大,使用Unsafe.arrayIndexScale()Ҏq回一个数l元素占用的内存大小Q其他计和反射机制cM。这里在最后计填充前有对FieldOffsetAdjustment的调_貌似在JRockit JVM中用到了,不了解ؓ什么它需要这个调整。对实例大小的计也比较单,它首先遍历当前类和父cȝ所有非静态字D,通过Unsafe.objectFieldOffset()扑ֈ最后一个字D늚offsetQ根据之前Java实例内存l构Q要扑ֈ最后一个字D,只需从当前类到最层父类遍历W一个有非静态字D늚cȝ所有非静态字D即可。在扑ֈ最后一个字D늚offset以后也需要做FieldOffsetAdjustment调整Q之后还需要加1Q因为有对象寚w大小寚wQ因而通过?而避免考虑最后一个字D늱型的问题Q很巧妙的代码!Q。最后根据规则以对对象以对象寚w大小寚w?br /> <br /> </li> <li>AgentSizeOf<br /> 在Java 1.5以后Q提供了Instrumentation接口Q可以调用该接口的getObjectSizeҎ获取一个对象实例占用的内存大小。对Instrumentation的机制不熟,但是从EHCache代码的实现角度上Q它首先需要有一个sizeof-agent.jar的包(包含在net.sf.ehcache.pool.sizeof?Q在该jar包的MANIFEST.MF文g中指定Premain-Classc,q个cdC个静态的premain、agentmainҎ。在实际q行ӞEHCache会将sizeof-agent.jar拯C时文件夹中,然后调用Sun工具包中的VirtualMachine的静态attachҎQ获取一个VirtualMachine实例Q然后调用其实例ҎloadAgentҎQ传入sizeof-agent.jar文g全\径,卛_一个SizeOfAgentc附着到当前实例中Q而我们就可以通过SizeOfAgentcL获取它的Instrumentation实例来计一个实例的大小?br /> </li> </ol> 我们可以使用一下一个简单的例子来测试一下各U不同计方法得出的l果Q? <br /> <div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --><span style="color: #0000FF; ">public</span> <span style="color: #0000FF; ">class</span> EhcacheSizeOfTest {<br />     <span style="color: #0000FF; ">public</span> <span style="color: #0000FF; ">static</span> <span style="color: #0000FF; ">void</span> main(String[] args) {<br />         MyClass ins = <span style="color: #0000FF; ">new</span> MyClass();<br />         <br />         System.out.println("ReflectionSizeOf: " + calculate(<span style="color: #0000FF; ">new</span> ReflectionSizeOf(), ins));<br />         System.out.println("UnsafeSizeOf: " + calculate(<span style="color: #0000FF; ">new</span> UnsafeSizeOf(), ins));<br />         System.out.println("AgentSizeOf: " + calculate(<span style="color: #0000FF; ">new</span> AgentSizeOf(), ins));<br />     }<br />     <br />     <span style="color: #0000FF; ">private</span> <span style="color: #0000FF; ">static</span> <span style="color: #0000FF; ">long</span> calculate(SizeOf sizeOf, Object instance) {<br />         <span style="color: #0000FF; ">return</span> sizeOf.sizeOf(instance);<br />     }<br />     <br />     <span style="color: #0000FF; ">public</span> <span style="color: #0000FF; ">static</span> <span style="color: #0000FF; ">class</span> MyClass {<br />         <span style="color: #0000FF; ">byte</span> a;<br />         <span style="color: #0000FF; ">int</span> c;<br />         <span style="color: #0000FF; ">boolean</span> d;<br />         <span style="color: #0000FF; ">long</span> e;<br />         Object f;<br />     }<br /> }<br /> <span style="color: #008000; ">//</span><span style="color: #008000; ">输出l果如下(问题Q这里的JVM?4-Bit HotSpot JVM with Compressed OOPsQ它的实例头部占用了12个字节大,但是它占用内存的大小q是?2位的大小一P</span><span style="color: red;">q是Z么?</span><span style="color: #008000; ">)Q?/span><span style="color: #008000; "><br /> </span>[31 23:21:19,598 INFO ] [main] sizeof.JvmInformation - Detected JVM data model settings of: 64-Bit HotSpot JVM with Compressed OOPs<br /> ReflectionSizeOf: 32<br /> UnsafeSizeOf: 32<br /> [31 23:26:52,479 INFO ] [main] sizeof.AgentLoader - Located valid 'tools.jar' at 'C:\Program Files\Java\jdk1.7.0_25\jre\..\lib\tools.jar'<br /> [31 23:26:52,729 INFO ] [main] sizeof.AgentLoader - Extracted agent jar to temporary file C:\Users\DINGLE~1\AppData\Local\Temp\ehcache-sizeof-agent6171098352070763093.jar<br /> [31 23:26:52,729 INFO ] [main] sizeof.AgentLoader - Trying to load agent @ C:\Users\DINGLE~1\AppData\Local\Temp\ehcache-sizeof-agent6171098352070763093.jar<br /> AgentSizeOf: 32</div> <br /> <strong style="color: orange;">Deep SizeOf计算</strong><br /> EHCache中的SizeOfcMq提供了deepSize计算Q它的步骤是Q用ObjectGraphWalker遍历一个实例的所有对象引用,在遍历中通过使用传入的SizeOfFilterqo掉那些不需要的字段Q然后调用传入的VisitorҎ个需要计的实例做计?br /> ObjectGraphWalker的实现算法和我之前所描述的类|E微不同的是它用了StackQ我更們֐于用QueueQ只是这个也只是影响遍历的顺序,q里有点深度优先q是q度优先的味道。另外,它抽象了SizeOfFilter接口Q可以用于过滤掉一些不想用于计内存大的字段Q如Element中的key字段。SizeOfFilter提供了对cd字段的过滤:<br /> <div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --><span style="color: #0000FF; ">public</span> <span style="color: #0000FF; ">interface</span> SizeOfFilter {<br />     <span style="color: #008000; ">//</span><span style="color: #008000; "> Returns the fields to walk and measure for a type</span><span style="color: #008000; "><br /> </span>    Collection<Field> filterFields(Class<?> klazz, Collection<Field> fields);<br />     <span style="color: #008000; ">//</span><span style="color: #008000; "> Checks whether the type needs to be filtered</span><span style="color: #008000; "><br /> </span>    <span style="color: #0000FF; ">boolean</span> filterClass(Class<?> klazz);<br /> }</div> SizeOfFilter的实现类可以用于qoqo掉@IgnoreSizeOf注解的字D和c,以及通过net.sf.ehcache.sizeof.filterpȝ变量定义的文Ӟd其中的每一行ؓ包名或字D名作ؓqo条g。最后,Z性能考虑Q它对一些计结果做了缓存?br /> <br /> ObjectGraphWalker中,它还会忽略一些系l原本就存在的一些静态变量以及类实例Q所有这些信息都定义在FlyweightTypecM?br /> <br /> <strong style="color: orange;">SizeOfEnginec?/strong><br /> SizeOfEngine是EHCache中对使用不同方式做SizeOf计算的抽象,如在计算内存中对象的大小需要用SizeOfcL实现Q而计磁盘中数据占用的大直接用其size值即可,因而在EHCache中对SizeOfEngine有两个实玎ͼDefaultSizeOfEngine和DiskSizeOfEngine。对DiskSizeOfEngine比较单,其container参数必须是DiskMarkercdQƈ且直接返回其size字段卛_Q对DefaultSizeOfEngineQ则需要配|SizeOfFilter和SizeOf子类实现问题Q对SizeOfFilterQ它会默认加入AnnotationSizeOfFilter、用builtin-sizeof.filter文g中定义的cR字D配|的ResourceSizeOfFilter、用户通过net.sf.ehcache.sizeof.filter配置的filter文g的ResourceSizeOfFilterQ对SizeOf的子cd现问题,它优先选择AgentSizeOfQ如果不支持则用UnsafeSizeOfQ最后才使用ReflectionSizeOf?br /> <div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --><span style="color: #0000FF; ">public</span> <span style="color: #0000FF; ">interface</span> SizeOfEngine {<br />     Size sizeOf(Object key, Object value, Object container);<br />     SizeOfEngine copyWith(<span style="color: #0000FF; ">int</span> maxDepth, <span style="color: #0000FF; ">boolean</span> abortWhenMaxDepthExceeded);<br /> }</div><h2> q可以参?/h2>http://www.javamex.com/tutorials/memory/<br /><img src ="http://www.aygfsteel.com/DLevin/aggbug/405822.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.aygfsteel.com/DLevin/" target="_blank">DLevin</a> 2013-11-01 11:03 <a href="http://www.aygfsteel.com/DLevin/archive/2013/11/01/405822.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java Cache-EHCachepd之AA-Tree实现溢出到磁盘的数据理(2)http://www.aygfsteel.com/DLevin/archive/2013/10/29/405728.htmlDLevinDLevinMon, 28 Oct 2013 17:59:00 GMThttp://www.aygfsteel.com/DLevin/archive/2013/10/29/405728.htmlhttp://www.aygfsteel.com/DLevin/comments/405728.htmlhttp://www.aygfsteel.com/DLevin/archive/2013/10/29/405728.html#Feedback2http://www.aygfsteel.com/DLevin/comments/commentRss/405728.htmlhttp://www.aygfsteel.com/DLevin/services/trackbacks/405728.htmlJava Cache-EHCachepd之AA-Tree实现溢出到磁盘的数据理(1)》已l详l讲解了EHCache中在AATreeSet中对AA Tree法的实玎ͼq且指出EHCache是采用它作ؓI闲盘理数据l构Q本文主要关注于EHCache是如何采用AATreeSetcL理I闲盘的(q里的磁盘管理是只EHCache data文g中的I闲盘Q?br />
I闲盘理数据l构和算?/strong>
在上一?a href="http://www.aygfsteel.com/DLevin/archive/2013/10/27/405683.html">Java Cache-EHCachepd之AA-Tree实现溢出到磁盘的数据理》有提到cM内存分配理Q空闲磁盘管理可以采用多U数据结构,也有多种法实现QEHCache采用AA Tree作ؓI闲盘的数据结构,以及首次适应法和最坏适应法相结合的法?br /> 最坏适应法QWorst FitQ,在《计机操作pȝQ汤子瀛版Q》中对该法描述如下Q最坏适应分配法要扫瞄整个空闲分或链表,L挑选一个最大的I闲分区分分割给作业使用Q其优点是可使剩下的I闲Z至于太小Q生碎片的几率最,对中作业有利,同时最坏适应法查找效率很高。该法要求所有的I闲分区按其定w以从大到的序形成一I闲分区链,查找时只要看W一个分否满作业要求。但该算法的~点也时明显的,它会使存储器中缺乏大的空闲分区。最坏适应法与前面所q的首次适应法、@环首ơ适应法、最佳适应法一赯成ؓ序搜烦法?
首次适应法在《计机操作pȝQ汤子瀛版Q》描q如下:我们以空闲分区链Z来说明采用FF法时的分配情况。FF法要求I闲分区链以地址递增ơ序链接。在分配内存Ӟ从链首开始顺序查找,直到扑ֈ一个大能满要求的空闲分Zؓ止;然后再按照作业的大小Q从该分Z划出一块内存空间分配给h者,余下的空闲分Z留在I闲链中。若从链首直至链N不能扑ֈ一个满求的分区Q则此次内存分配p|Q返回。该法們֐于有限利用内存中低地址部分的空闲分区,从而保留了高地址部分的大I闲区。这l尾以后到达的大作业分配大的内存I间创造了条g。其~点时低地址部分不断被划分,会留下许多难以利用的、很的I闲分区Q而每ơ查扑֏都时从低地址部分开始,q无疑会增加查找可用I闲分区时的开销?br />
作ؓCache的溢出数据文件作为Cache的交换区Q显然我们希望数据文件越越好,此时一般的选择是用首ơ适应法QFirst FitQ。然而虽然FF法能尽可能多的利用数据文g低地址部分的磁盘空间以减少盘文g的大,但是它的~点也是明显的,而WF法虽然效率很高Q但是它很容易数据文g膨胀D盘利用率很低。因而EHCache的空闲磁盘去理旉用了两种l合的方法:即空闲链QAA Tree链)的以盘地址序排列Q树的每个节点包含一个字D는于纪录当前节点以及其所有子节点中的最大sizeQ在查找Ӟ以层U顺序遍历,q且优先选择左子树(盘地址低的RegionQ?br />
EHCache一个空闲磁盘区抽象成一个Regionc,它包含start、end字段Q用于纪录在当前该区域在盘文g中的其实位置Qƈ且每个Region实例是AA Tree中的一个节点,因而它l承自AATreeSet.AbstractTreeNodec,即承了该类的left、right、level字段Q根据Region的比较算法,它大致上以Region所在磁盘文件的位置排序Q而不是以Region的大来排序Q,因而ؓ了提升查找性能Q它q包含了一个longcd的contiguous字段Q该单词字面意思是“临近的、连l的”Q用于表C当前Region临近节点的区域的最大Region大小Q即该字D表C当前Region以及其所有子节点的最大Region的大,从而当在查找时Q只有如果要查找的size比当前Region的contiguous字段要大的话Q就可以不用l箋查找其子节点了,q且通过该字D也实现了最坏适应法。在每次更新左子节点和右子节Ҏ都会调整contiguous的大,在新创徏一个Region节点时也会更新contiguous字段大小Q从而保证当前Region中的contiguous始终是其所有子节点的最大sizeQ对于叶子节点,其contiguous的值是当前Region的size。在计算sizeӞstartQend值是闭合的?br />
public abstract static class AbstractTreeNode<E> implements Node<E> {
    private Node<E> left;
    private Node<E> right;
    private int     level;
    .....
}
public class Region extends AATreeSet.AbstractTreeNode<Comparable> implements Comparable<Comparable> {
    private long start;
    private long end;
    private long contiguous;

    public Region(long start, long end) {
        super();
        this.start = start;
        this.end = end;
        updateContiguous();
    }

    public long contiguous() {
        if (getLeft().getPayload() == null && getRight().getPayload() == null) {
            return size();
        } else {
            return contiguous;
        }
    }

    private void updateContiguous() {
        Region left = (Region) getLeft().getPayload();
        Region right = (Region) getRight().getPayload();
        long leftContiguous = left == null ? 0 : left.contiguous();
        long rightContiguous = right == null ? 0 : right.contiguous();
        contiguous = Math.max(size(), Math.max(leftContiguous, rightContiguous));
    }

    public void setLeft(AATreeSet.Node<Comparable> l) {
        super.setLeft(l);
        updateContiguous();
    }

    public void setRight(AATreeSet.Node<Comparable> r) {
        super.setRight(r);
        updateContiguous();
    }

    public long size() {
        // since it is all inclusive
        return (isNull() ? 0 : this.end - this.start + 1);
    }

    //Region的比较算法,使Region在这AA Tree中大致保持其在数据文件中的排序顺?/span>
    public int compareTo(Comparable other) {
        if (other instanceof Region) {
            return compareTo((Region) other);
        } else if (other instanceof Long) {
            return compareTo((Long) other);
        } else {
            throw new AssertionError("Unusual Type " + other.getClass());
        }
    }
    private int compareTo(Region r) {
        if (this.start > r.start || this.end > r.end) {
            return 1;
        } else if (this.start < r.start || this.end < r.end) {
            return -1;
        } else {
            return 0;
        }
    }
    private int compareTo(Long l) {
        if (l.longValue() > end) {
            return -1;
        } else if (l.longValue() < start) {
            return 1;
        } else {
            return 0;
        }
    }
}

EHCache中对I闲盘的分配与回收

在C语言中用malloc和free来对内存的分配与回收Q在CQ+中用new和deleteQ在Java中只有newQ在EHCache中则磁盘空间的分配与回收抽象成FileAllocationTreec,它提供alloc、free、mark{接口用于管理磁盘区的分配与回收。另外EHCacheq增加了RegionSetc,它承子AATreeSetc,用于表达它专门用于存储Region节点。这里吐槽一下,FileAllocationTree竟然设计成承自RegionSet而不是组合。。。。。所有这些类的结构图如下Q?


盘的分?/strong>
盘的分配分成一下几个步骤(逻辑比较单,׃贴代码了Q:
  1. Ҏ传入的sizeQ在AA Tree中查扑ֈ一个可以容U传入size大小的Region节点Qƈ找到的Region的前size部分分配Z个新的Regionq返回?br /> 查找逻辑在RegionSetcM实现(findҎ)Q它从root节点向下查找Q因为root节点的contiguous字段保存了整|的最大sizeQ因而先查root节点的contiguousQ如果size比root的Contiguous要大Q则抛异常,因ؓ整棵树中已经没有比传入的size要大的Region。然后层U遍历AA树,如果当前节点的size要比传入的size大或相等Q则扑ֈ以容纳传入size大小的Region节点Q以当前节点的size大小的前部分新创Z个Regionq回Q否则如果它的左子树的contiguous字段要比传入size大,则向左子树查找;否则如果它的叛_树的contiguous字段要比传入的size大,则向叛_树查找;否则Q抛出异常,因ؓ左右子树都找不到可以容纳size大小的Region?/li>
  2. 新创徏的Region实例mark成已l用(q个新创建的Region的start和AA树中某个Region节点的startgP而end大小则不一定一P?br /> 因ؓq个新创建的Region实例是要从AA树中的某个节点分出部分空_因而首先要AA树中的那个节点从树中U除Q然后如果树中移除的节点的end值和新创建的Region的endgP则直接移除就可以了,否则Q要树U移除的节点剩余部分的重新创Z个Region插回树中。从代码的角度,首先以新创徏的Region的start值找到树中对应的RegionQRegion中接收Long作ؓ参数的compareҎ的存在以实现q种方式的查找)Q将其移除ƈq回U除后的RegionQremoveAndReturnҎQ在RegionSet中重新拷贝一个Region实例是ؓ了防止通过Rq回的Region改变树内部的状态,因ؓRegion即作Z个Node也作为payload存在Q同时也可以l接下来的插入提供新的Region节点Q,然后把将新创建的Region从原树中的Region中移除,q里的移除逻辑假设新创建的Region可以是原树中的Region的前部分、中间部分以及末位部分,作ؓ前部分和末位部分Q因为Region是新创徏的节点,因而直接更新当前节点即可,而如果是中间部分Q则前部分作为当前节点,而后部分作ؓ新节点返回。最后,如果原树中节点还剩余部分数据作ؓ新的I闲盘区添回到I闲盘树中。最后,查是否需要增加文件大,q里只需要更新文件大的字段卛_而不需要实际增加文件的大小Q因为文件在写入时会自动增加大小。Region中移除其中的部分Region代码如下Q?br />
        protected Region remove(Region r) throws IllegalArgumentException {
            if (r.start < this.start || r.end > this.end) {
                throw new IllegalArgumentException("Ranges : Illegal value passed to remove : " + this + " remove called for : " + r);
            }
            if (this.start == r.start) {
                this.start = r.end + 1;
                updateContiguous();
                return null;
            } else if (this.end == r.end) {
                this.end = r.start - 1;
                updateContiguous();
                return null;
            } else {
                Region newRegion = new Region(r.end + 1, this.end);
                this.end = r.start - 1;
                updateContiguous();
                return newRegion;
            }
        }
  3. 最后将分配出来的Region实例q回QƈU录在DiskMarker中,在以后需要将盘中的数据重新d到内存中时用于定位该数据在磁盘中的位|,q可以将该Regin回收?/li>
盘的回?br /> 盘的回收也分几个步骤来完成Q?br />
  1. 对要回收的RegionQ查扑֜当前树中是否有一个Region节点其start和要回收的Region?end - 1)值相{,如果有,则删除树中的原节点ƈq回Q合q这两个节点Q将合ƈ后的节点重新插入到树中。Region中合q的代码逻辑如下Q?br />
        protected void merge(Region r) throws IllegalArgumentException {
            if (this.start == r.end + 1) {
                this.start = r.start;
            } else if (this.end == r.start - 1) {
                this.end = r.end;
            } else {
                throw new IllegalArgumentException("Ranges : Merge called on non contiguous values : [this]:" + this + " and " + r);
            }
            updateContiguous();
        }
  2. 对要回收的RegionQ或合ƈ后的RegionQ,l箋查找当前树是否有一个Region节点Q其end和要回收的(或已合ƈ的)Region?start - 1)的值相{,如果有,则删除树中的原节点ƈq回Q合q这两个节点Q将合ƈ后的节点l箋插入树中?/li>
  3. 如果在树中找不到可以和回收的Region合ƈ的Region节点Q则只是要合ƈ的Regiond到树中?/li>
  4. 最后如果回收后数据文g可以减小Q更新数据文件大的字段Qƈ数据文件的~小Q从而保持数据文件处于尽量小的状态?br />
最后我写了一个简单的试E序Q对盘分配与释攑ց了一些随机的模拟Q以增加一些直观的感受(cMDiskStorageFactoryQ在创徏FileAllocationTreeӞl了Long.MAX_VALUE的初始|我这也给q个g为初始|从而保证基本上在所有情况下Q都能找C个合适的Region节点Q也是说FileAllocationTree不用来控制数据文件的大小Q数据文件的大小由其他逻辑来控Ӟq在后面会详l讲?Q?nbsp;
public class FileAllocationTreeTest {
    public static void main(String[] args) {
        final int count = 5;
        Random random = new Random();
        
        FileAllocationTree alloc = new FileAllocationTree(Long.MAX_VALUE, null);
        List<Region> allocated = Lists.newArrayList();
        for(int i = 0; i < count; i++) {
            int size = random.nextInt(1000);
            Region region = alloc.alloc(size);
            System.out.println("new size: " + size + ", " + toString(region) + ", filesize: " + alloc.getFileSize() + ", allocator: " + toString(alloc));
            allocated.add(region);
        }
        
        for(int i = 0; i < count; i++) {
            int size = random.nextInt(1000);
            Region region = alloc.alloc(size);
            System.out.println("new size: " + size + ", " + toString(region) + ", filesize: " + alloc.getFileSize() + ", allocator: " + toString(alloc));
            allocated.add(region);
            region = allocated.get(random.nextInt(allocated.size()));
            alloc.free(region);
            allocated.remove(region);
            System.out.println("Freed region: " + toString(region) + ", after file size: " + alloc.getFileSize() + ", allocator: " + toString(alloc));
        }
    }
    
    private static String toString(FileAllocationTree alloc) {
        StringBuilder builder = new StringBuilder("[");
        for(Region region : alloc) {
            builder.append(toString(region)).append(", ");
        }
        builder.replace(builder.length() - 2, builder.length() - 1, "]");
        return builder.toString();
    }
    
    private static String toString(Region region) {
        return "Regin(" + region.start() + ", " + region.end() + ")";
    }
}
输出的随机结果如下: 
new size: 397, Regin(0, 396), filesize: 397, allocator: [Regin(397, 9223372036854775806)] 
new size: 175, Regin(397, 571), filesize: 572, allocator: [Regin(572, 9223372036854775806)] 
new size: 210, Regin(572, 781), filesize: 782, allocator: [Regin(782, 9223372036854775806)] 
new size: 11, Regin(782, 792), filesize: 793, allocator: [Regin(793, 9223372036854775806)] 
new size: 432, Regin(793, 1224), filesize: 1225, allocator: [Regin(1225, 9223372036854775806)] 
new size: 226, Regin(1225, 1450), filesize: 1451, allocator: [Regin(1451, 9223372036854775806)] 
Freed region: Regin(572, 781), after file size: 1451, allocator: [Regin(572, 781), Regin(1451, 9223372036854775806)] 
new size: 500, Regin(1451, 1950), filesize: 1951, allocator: [Regin(572, 781), Regin(1951, 9223372036854775806)] 
Freed region: Regin(793, 1224), after file size: 1951, allocator: [Regin(572, 781), Regin(793, 1224), Regin(1951, 9223372036854775806)] 
new size: 681, Regin(1951, 2631), filesize: 2632, allocator: [Regin(572, 781), Regin(793, 1224), Regin(2632, 9223372036854775806)] 
Freed region: Regin(1951, 2631), after file size: 1951, allocator: [Regin(572, 781), Regin(793, 1224), Regin(1951, 9223372036854775806)] 
new size: 23, Regin(793, 815), filesize: 1951, allocator: [Regin(572, 781), Regin(816, 1224), Regin(1951, 9223372036854775806)] 
Freed region: Regin(0, 396), after file size: 1951, allocator: [Regin(0, 396), Regin(572, 781), Regin(816, 1224), Regin(1951, 9223372036854775806)] 
new size: 109, Regin(816, 924), filesize: 1951, allocator: [Regin(0, 396), Regin(572, 781), Regin(925, 1224), Regin(1951, 9223372036854775806)] 
Freed region: Regin(1225, 1450), after file size: 1951, allocator: [Regin(0, 396), Regin(572, 781), Regin(925, 1450), Regin(1951, 9223372036854775806)] 


DLevin 2013-10-29 01:59 发表评论
]]>
Java Cache-EHCachepd之AA-Tree实现溢出到磁盘的数据理(1)http://www.aygfsteel.com/DLevin/archive/2013/10/27/405683.htmlDLevinDLevinSun, 27 Oct 2013 11:13:00 GMThttp://www.aygfsteel.com/DLevin/archive/2013/10/27/405683.htmlhttp://www.aygfsteel.com/DLevin/comments/405683.htmlhttp://www.aygfsteel.com/DLevin/archive/2013/10/27/405683.html#Feedback0http://www.aygfsteel.com/DLevin/comments/commentRss/405683.htmlhttp://www.aygfsteel.com/DLevin/services/trackbacks/405683.html
需求思?br /> 当Cachepȝ发现需要溢出某些实例到盘Ӟ它需要把实例序列化后Q将序列化后的二q制数据写入盘Qƈ释放内存中的数据Q当用户hd已经溢出到磁盘中的实例时Q需要读取磁盘中的二q制数据Q反序列化成内存实例Q返回给用户Q由于该实例最q一ơ被讉KQ因而根据程序的局部性(或许q里是数据的局部性)原理QCache会将新读取的实例保存在内存中Qƈ释放是磁盘中占用的空_如果此时或者在来的某个时候内存依然或变的非常紧张QCachepȝ会根据溢出算法溢出根据算法得到的实例到磁盘,因而这里磁盘的d是一个动态的、周而复始的行ؓ。在把序列化的实例写入磁盘时Q我们需要分配相同大的盘I间QƈU录该实例的key、在盘文g中的起始位置、数据大用于在后来的读回数据ƈ反序列化成内存实例;当一个实例数据被d内存Ӟ需要释攑֮在磁盘中占用的内存,从而可以将它原本占用磁盘分配给其他溢出实例。这有点cM内存理的味道:对一台机器来_内存大小是有限的Q而进E不断的产生、销毁,在启动一个进E时Q需要ؓ该进E分配一定的初始I间Qƈ且随着q程的运行,需要不断的为其分配q行期间增长的内存空_当一个进E销毁时Q操作系l需要回收分配给它的I间Q以实现循环利用。在操作pȝ中,内存理方式有:
1. 固定分区分配Q操作系l把内存分成固定大小的相{的或不{的内存分区Q每个进E只能分配给q几个固定大的分区。这是一U最单的内存理法Q它需要提前直C个进E最大会使用的内存大,q且需要合理分配内存分区的大小Q以防止有些q程因ؓ使用内存q多而找不到对应大小的空闲分无法蝲入,虽然此时可能内存q有很多其他比较的I闲分区Q它也要防止内存分区q大Q从而在载入q小q程时浪费内存。对q种内存理方式Q只需要一张分卛_Q每个表纪录了分区受v始位|、大、分配状态等。如果表不多,可以直接用数l表C,实现单,查找效率影响也不大,如果表项很多Q则可以考虑在其上层建立一颗空闲分区红黑树Q已提升查找、插入、删除效率?br /> 2. 相对于固定分区分配,自然会存在动态内存分配,已处理不同内存需要不同大的内存的需求,x作系l根据不同进E所需的不同内存大分配内存。可以用三U数据结构来理分区Q空闲分、空闲分区链以及I闲分区U黑树,对于I闲分区表和I闲分区U黑树,每一个节炚wU录了空闲分区号、v始位|、大等Q不同的是分l成一个数l,而空闲分区红黑树l成一颗树已提升查找、插入、删除等性能Q对I闲分区链,它不象空闲分和空闲分区红黑树Q用额外的内存中间存储信息Q它直接使用在空闲内存的头和֭储额外的分区链信息。而对分区分配法Q可以用:首次适应法、@环首ơ适应法、最佳适应法、最坏适应法{。在分配内存Ӟ首先Ҏ一定的法扑ֈ可以装蝲该进E的I闲分区Q然后根据当前分区的大小和进E所需要的内存大小以及最可存在的内存分区的大小Q防止过多的q小的内存碎片生)选择当前空闲分区的部分分区划分l该新进E或当前整个分区分配给新进E,如果只是当前空闲分区的部分分区划分l新q程Q只需调整当前I闲分区的基本信息卛_Q否则需要将该空闲进E节点从I闲分区表、链、树中移除。内存回首时Q根据不同的法该内存区创建出一个空闲分点,它插入到原有空闲分、链、树中,在插入前Q如果出现相ȝI闲分区表,则需要将所有的盔RI闲分区合ƈ?br /> 3. 伙伴pȝ?br /> 4. 可重定位分区分配?br /> 貌似有点题了,作ؓCache的溢出数据磁盘文件管理ؓ了简单和效率的考虑Q不会也没必要象内存理一样实现的那么复杂Q因而伙伴系l和可重定ؓ分区分配不细讲了Q需要进一步了解的可以参考Q意一本的操作pȝ教材。其实Cache的溢出数据磁盘文件管理更cM于内存溢出到盘的交换区理Q即操作pȝ会将暂时不用的进E或数据调换到外存中Q已腑և内存供其他需要进E用,已提升内存用率。一般进E或内存交换出的数据在交换区中的停留旉比较D,而交换操作又比较频繁Q因而交换区中的I间理主要Z提高q程或数据换入和换出的速度Qؓ此,一般采用的是连l分配方式,较少考虑外存片的问题,而对其管理的数据l构和上q的数据l构cMQ不再赘q?

EHCache使用AA Tree数据l构来管理磁盘空?/strong>
在EHCache中,采用AA Tree数据l构来纪录空闲磁盘块Q因而首先了解一下AA Tree的实玎ͼAATreeSetc)。AA TreeArne Andersson?993q的论文“Balanced Search Trees Made Simple”中提出的对红黑树的改q,因而它是红黑树的变U,但是比红黑树实现要简单。不同于U黑树采用颜ԌU色或黑Ԍ来纪录一个节点的状态以调整节点的位|来保持其^衡性,AA Tree采用level值来U录当前节点的状态以保持它的q性,level值相当于U黑树中的黑节点高度。AA Tree的定义如下:
1. AA Tree是红黑树的变U,因而它也是一颗二叉排序树?br /> 2. 每个叶子节点的level??br /> 3. 每个左孩子的level是其父节点level值减1?br /> 4. 每个叛_子的level和其父节点的level相等或是其父节点的level??br /> 5. 每个叛_子的level一定比其祖父节点的level要小?br /> 6. 每个level大于1的节Ҏ两个孩子?br /> cMU黑树,AA Tree通过对level值的判断来调整节点位|以保证当前Treel持以上定义。由于AA Tree采用level来纪录每个节点的状态,q且每个叛_子的level和其父节点的level值相{或是其父节点的level?Q因而一般AA Tree采用如下方式表达Q图片来自:http://blog.csdn.net/zhaojinjia/article/details/8121156Q:

Ҏ以上AA Tree的定义,AA Tree止以下两种情况的出玎ͼ
1. 止出现q箋两个水^方向链(horizontal linkQ,所谓horizontal link是指一个结点跟它的叛_子结点的level相同Q左孩子l点永远比它的父l点level?Q。这个规定其实相当于RB树中不能出现两个q箋的红色结炏V即需要满_??br /> 2. 止出现向左的水qx向链Qleft horizontal linkQ,也就是说一个结Ҏ多只能出Cơ向右的水^方向链。在AA Tree中水q链相当于红黑树中的U孩子,Ҏ定义3QAA Tree不允许左孩子出现“U节?#8221;Q即AA Tree的左孩子的level值L要比父节点的level值小1?br /> 对不允许的情况如下图所C(囄同样来自Qhttp://blog.csdn.net/zhaojinjia/article/details/8121156Q:


AA Tree的节点调?/strong>
在AA Tree中,只需要当出现以上止的两U情况下才需要调_因而它比红黑树的逻辑要简单的多。同U黑树,AA Tree也通过旋{节点来调整节点以使当前树始终保持AA Tree的特点,然而AA Tree创徏了自q术语Qskew和split。skew用于处理出现向左的水qx向链的情况;split用于处理q箋两个水^方向铄情况?br /> skew相当于一个右旋操作:

  private static <T> Node<T> skew(Node<T> top) {
    if (top.getLeft().getLevel() == top.getLevel() && top.getLevel() != 0) {
      Node<T> save = top.getLeft();
      top.setLeft(save.getRight());
      save.setRight(top);
      top = save;
    }

    return top;
  }
split相当于一个左旋操作,q且它还会父节点的level?Q?br />
  private static <T> Node<T> split(Node<T> top) {
    if (top.getRight().getRight().getLevel() == top.getLevel() && top.getLevel() != 0) {
      Node<T> save = top.getRight();
      top.setRight(save.getLeft());
      save.setLeft(top);
      top = save;
      top.incrementLevel();
    }

    return top;
  }
当skew之前已经有一个右孩子的level跟当前结点的level相同Qskew 操作之后可能引v两个q箋水^方向链,q时需要配合用split操作。split操作会新的子树的根节点level增加1Q原节点的右孩子Q,当原节点作ؓ其父l点的右孩子而且其父l点跟祖父结点的level相同Ӟ在split操作后会引v两个q箋水^方向链,此时需要进一步的split操作Q而当原节点作为其父节点的左孩子时Q在split操作后会引v向左方向的水q链Q此旉要配合用skew操作Q因而AA Tree在做调整Ӟ需要skew和split一起配合操作?

AA Tree节点定义
AA Tree节点定义和红黑树节点定义cMQ只是它没有了color字段Q而用level字段替代。在EHCache中采用Node接口表达Q在该接口中它还定义了compare()Ҏ以比较两个节点之间的大小关系Q?br />
  public static interface Node<E> {
    public int compareTo(E data);
    public void setLeft(Node<E> node);
    public void setRight(Node<E> node);
    public Node<E> getLeft();
    public Node<E> getRight();
    public int getLevel();
    public void setLevel(int value);
    public int decrementLevel();
    public int incrementLevel();
    public void swapPayload(Node<E> with);
    public E getPayload();
  }
对该接口的实C比较单:
  public abstract static class AbstractTreeNode<E> implements Node<E> {
    private Node<E> left;
    private Node<E> right;
    private int     level;
    .....
    }
  private static final class TreeNode<E extends Comparable> extends AbstractTreeNode<E> {
    private E payload;
    public void swapPayload(Node<E> node) {
      if (node instanceof TreeNode<?>) {
        TreeNode<E> treeNode = (TreeNode<E>) node;
        E temp = treeNode.payload;
        treeNode.payload = this.payload;
        this.payload = temp;
      } else {
        throw new IllegalArgumentException();
      }
    }
    .....
  }

AA Tree的插入操?/strong>
同红黑树Q作Z颗二叉排序树QAA Tree先以二叉排序树的法插入一个新节点Q然后对插入的节点做skew和split调整Q以l持AA Tree的定义,它是一个递归操作Q因而可以保证在当前一ơ调整完成后q需要再调整Ӟ在上一个递归栈中可以l箋调整Q这里比较用当前节点和传入数据做比较,因而当direction大于0时表C当前节点要比传入数据大Q因而应该插入它的左子树Q我刚开始没仔细看,q以是一颗倒序树,囧。。。。)Q?br />
  private Node<T> insert(Node<T> top, T data) {
    if (top == terminal()) {
      mutated = true;
      return createNode(data);
    } else {
      int direction = top.compareTo(data);
      if (direction > 0) {
        top.setLeft(insert(top.getLeft(), data));
      } else if (direction < 0) {
        top.setRight(insert(top.getRight(), data));
      } else {
        return top;
      }
      top = skew(top);
      top = split(top);
      return top;
    }
  }

AA Tree的删除操?/strong>
AA Tree节点的删除也只需考虑两种情况Q?br />1. 如果删除节点的后l节点不是叶子节点,后l节点的Dl当前节点,后节点叛_子的Dl后l节点,删除后节点?br />如下图,要删?0Q其后节点?5Q删除时Q将30?5节点交换Q将35?0节点交换Q后删除40节点Q以下图片都Qhttp://blog.csdn.net/zhaojinjia/article/details/8121156Q:

2. 如果删除节点的后l节Ҏ叶子节点Q后l节点的Dl当前节点,q将后节点的父节点level值减1Q如果出现向左的水^链或向右的连l水q链Q则做skew和split调整?br />如下面两囑ֈ别ؓ删除50节点和删?5节点的事例图Q?br />

EHCache AATreeSet中删除的代码Q这里的比较也是使用当前节点和传入数据比较,因而当direction大于0Ӟ应该向左赎ͼ要特别留意)Q?br />
  private Node<T> remove(Node<T> top, T data) {
    if (top != terminal()) {
      int direction = top.compareTo(data);

      heir = top;
      if (direction > 0) {
        top.setLeft(remove(top.getLeft(), data));
      } else {
        item = top;
        top.setRight(remove(top.getRight(), data));
      }

      if (top == heir) {
        if (item != terminal() && item.compareTo(data) == 0) {
          mutated = true;
          item.swapPayload(top);
          removed = top.getPayload();
          top = top.getRight();
        }
      } else {
        if (top.getLeft().getLevel() < top.getLevel() - 1 || top.getRight().getLevel() < top.getLevel() - 1) {
          if (top.getRight().getLevel() > top.decrementLevel()) {
            top.getRight().setLevel(top.getLevel());
          }

          top = skew(top);
          top.setRight(skew(top.getRight()));
          top.getRight().setRight(skew(top.getRight().getRight()));
          top = split(top);
          top.setRight(split(top.getRight()));
        }
      }
    }
    return top;
  }

参考文章:
http://blog.csdn.net/zhaojinjia/article/details/8121156
http://www.cnblogs.com/ljsspace/archive/2011/06/16/2082240.html
http://baike.baidu.com/view/10841337.htm


DLevin 2013-10-27 19:13 发表评论
]]>
Java Cachepd之Cache概述和Simple Cachehttp://www.aygfsteel.com/DLevin/archive/2013/10/15/404770.htmlDLevinDLevinTue, 15 Oct 2013 15:46:00 GMThttp://www.aygfsteel.com/DLevin/archive/2013/10/15/404770.htmlhttp://www.aygfsteel.com/DLevin/comments/404770.htmlhttp://www.aygfsteel.com/DLevin/archive/2013/10/15/404770.html#Feedback1http://www.aygfsteel.com/DLevin/comments/commentRss/404770.htmlhttp://www.aygfsteel.com/DLevin/services/trackbacks/404770.html
记得我最早接触Cache是在大学学计机l成原理的时候,׃CPU的速度要远大于内存的读取速度Qؓ了提高CPU的效率,CPU会在内部提供~存区,该缓存区的读取速度和CPU的处理速度cMQCPU可以直接从缓存区中读取数据,从而解决CPU的处理速度和内存读取速度不匹配的问题。缓存之所以能解决q个问题是基于程序的局部性原理,?#8221;E序在执行时呈现出局部性规律,卛_一D|间内Q整个程序的执行仅限于程序中的某一部分。相应地Q执行所讉K的存储空间也局限于某个内存区域。局部性原理又表现为:旉局部性和I间局部性。时间局部性是指如果程序中的某条指令一旦执行,则不久之后该指o可能再次被执行;如果某数据被讉KQ则不久之后该数据可能再ơ被讉K。空间局部性是指一旦程序访问了某个存储单元Q则不久之后。其附近的存储单元也被讉K?#8221;在实际工作中QCPU先向~存取数据,如果~存区已存在Q则d~存中的数据Q命中)Q否则(失效Q,内存中相应数据块蝲入缓存中Q以提高接下来的讉K速度。由于成本和CPU大小的限ӞCPU只能提供有限的缓存区Q因而缓存区的大是衡量CPU性能的重要指标之一?br />
使用~存Q在CPU向内存更新数据时需要处理一个问题(写回{略问题Q,即CPU在更新数据时只更新缓存的数据Qwrite backQ写回,当缓存需要被替换时才缓存中更新的值写回内存)Q还是CPU在更新数据时同时更新~存中和内存中的数据Qwrite throughQ写通)。在写回{略中,Z减少内存写操作,~存块通常q设有一个脏位(dirty bitQ,用以标识该块在被载入之后是否发生q更新。如果一个缓存块在被|换回内存之前从未被写入q,则可以免d写操作;写回的优Ҏ节省了大量的写操作。这主要是因为,对一个数据块内不同单元的更新仅需一ơ写操作卛_完成。这U内存带宽上的节省进一步降低了能耗,因此颇适用于嵌入式pȝ。写通策略由于要l常和内存交互(有些CPU设计会在中间提供写缓冲器以缓解性能Q,因而性能较差Q但是它实现单,而且能简单的l持数据一致性?br />
在Y件的~存pȝ中,一般是Z解决内存的访问速率和磁盘、网l、数据库Q属于磁盘或|络讉KQ单独列出来因ؓ它的应用比较q泛Q等讉K速率不匹配的问题Q对于内存缓存系l来_。但是由于内存大和成本的限Ӟ我们又不能把所有的数据先加载进内存来。因而如CPU中的~存Q我们只能先一部分数据保存在缓存中。此Ӟ对于~存Q我们一般要解决如下需求:
  1. 使用l定Key从Cache中读取Value倹{CPU是通过内存地址来定位内存已获取相应内存中的|cM的在软gCache中,需要通过某个Key值来标识相关的倹{因而可以简单的认ؓ软g中的Cache是一个存储键值对的MapQ比如Gemfire中的Regionq承自MapQ只是Cache的实现更加复杂?/li>
  2. 当给定的Key在当前Cache不存在时Q程序员可以通过指定相应的逻辑从其他源Q如数据库、网l等源)中加载该Key对应的Value|同时该D回。在CPU中,ZE序局部性原理,一般是默认的加载接下来的一D内存块Q然而在软g中,不同的需求有不同的加载逻辑Q因而需要用戯己指定对应的加蝲逻辑Q而且一般来说也很难预知接下来要d的数据,所以只能一ơ只加蝲一条纪录(对可预知的场景下当然可以扚w加蝲数据Q只是此旉要权衡当前操作的响应旉问题Q?/li>
  3. 可以向Cache中写入KeyQValue键值对Q新增的U录或对原有的键值对的更斎ͼ。就像CPU的写回策略中有写回和写通策略,有些Cachepȝ提供了写通接口。如果没有提供写通接口,E序员需要额外的逻辑处理写通策略。也可以如CPU中的Cache一P只当相应的键值对UdCache以后Q再值写回到数据源,可以提供一个标C以决定要不要写回Q不q感觉这U实现比较复杂,代码的的耦合度也比较高,如果为提升写的速度Q采用异步写回即可,为防止数据丢失,可以使用Queue来存储)?/li>
  4. 给定Key的键值对UdCacheQ或l定多个Key以批量移除,甚至清除整个CacheQ?/li>
  5. 配置Cache的最大用率Q当Cache过该用率Ӟ可配|溢出策?
    1. 直接U除溢出的键值对。在U除时决定是否要写回已更新的数据到数据源?/li>
    2. 溢出的溢出的键值对写到盘中。在写磁盘时需要解军_何序列化键值对Q如何存储序列化后的数据到磁盘中Q如何布局盘存储Q如何解决磁盘碎片问题,如何从磁盘中扑֛相应的键值对Q如何读取磁盘中的数据ƈ方序列化Q如何处理磁盘溢出等问题?/li>
    3. 在溢出策略中Q除了如何处理溢出的键值对问题Q还需要处理如何选择溢出的键值对问题。这有点cM内存的页面置换算法(其实内存也可以看作是对磁盘的CacheQ,一般用的法有:先进先出QFIFOQ、最q最用(LRUQ、最用(LFUQ、Clock|换Q类LRUQ、工作集{算法?/li>
  6. 对Cache中的键值对Q可以配|其生存旉Q以处理某些键值对在长旉不被使用Q但是又没能溢出的问题(因ؓ溢出{略的选择或者Cache没有到溢出阶D)Q以提前释放内存?/li>
  7. Ҏ些特定的键值对Q我们希望它能一直留在内存中不被溢出Q有些Cachepȝ提供PIN配置Q动态或静态)Q以保该键值对不会被溢出?/li>
  8. 提供Cache状态、命中率{统计信息,如磁盘大、Cache大小、^均查询时间、每U查询次数、内存命中次数、磁盘命中次数等信息?/li>
  9. 提供注册Cache相关的事件处理器Q如Cache的创建、Cache的销毁、一条键值对的添加、一条键值对的更新、键值对溢出{事件?/li>
  10. ׃引入Cache的目的就是ؓ了提升程序的d性能Q而且一般Cache都需要在多线E环境下工作Q因而在实现时一般需要保证线E安全,以及提供高效的读写性能?/li>
在Java中,Map是最单的CacheQؓ了高效的在多U程环境中用,可以使用ConcurrentHashMapQ这也正是我之前参与的一个项目中最开始的实现Q后来引入EHCacheQ。ؓ了语意更加清晰、保持接口的单,下面我实C一个基于Map的最单的CachepȝQ用以演CCache的基本用方式。用户可以向它提供数据、查询数据、判断给定Key的存在性、移除给定的Key(s)、清除整个Cache{操作。以下是Cache的接口定义?br />
public interface Cache<K, V> {
    public String getName();
    public V get(K key);
    public Map<? extends K, ? extends V> getAll(Iterator<? extends K> keys);
    public boolean isPresent(K key);
    public void put(K key, V value);
    public void putAll(Map<? extends K, ? extends V> entries);
    public void invalidate(K key);
    public void invalidateAll(Iterator<? extends K> keys);
    public void invalidateAll();
    public boolean isEmpty();
    public int size();
    public void clear();
    public Map<? extends K, ? extends V> asMap();
}
q个单的Cache实现只是对HashMap的封装,之所以选择HashMap而不是ConcurrentHashMap是因为在ConcurrentHashMap无法实现getAll()ҎQƈ且这里所有的操作我都加锁了,因而也不需要ConcurrentHashMap来保证线E安全问题;Z提升性能Q我使用了读写锁Q以提升q发查询性能。因Z码比较简单,所以把所有代码都贴上了(懒得整理了。。。。)?br />
public class CacheImpl<K, V> implements Cache<K, V> {
    private final String name;
    private final HashMap<K, V> cache;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock readLock = lock.readLock();
    private final Lock writeLock = lock.writeLock();
    
    public CacheImpl(String name) {
        this.name = name;
        cache = new HashMap<K, V>();
    }
    
    public CacheImpl(String name, int initialCapacity) {
        this.name = name;
        cache = new HashMap<K, V>(initialCapacity);
    }
    
    public String getName() {
        return name;
    }

    public V get(K key) {
        readLock.lock();
        try {
            return cache.get(key);
        } finally {
            readLock.unlock();
        }
    }

    public Map<? extends K, ? extends V> getAll(Iterator<? extends K> keys) {
        readLock.lock();
        try {
            Map<K, V> map = new HashMap<K, V>();
            List<K> noEntryKeys = new ArrayList<K>();
            while(keys.hasNext()) {
                K key = keys.next();
                if(isPresent(key)) {
                    map.put(key, cache.get(key));
                } else {
                    noEntryKeys.add(key);
                }
            }
            
            if(!noEntryKeys.isEmpty()) {
                throw new CacheEntriesNotExistException(this, noEntryKeys);
            }
            
            return map;
        } finally {
            readLock.unlock();
        }
    }

    public boolean isPresent(K key) {
        readLock.lock();
        try {
            return cache.containsKey(key);
        } finally {
            readLock.unlock();
        }
    }

    public void put(K key, V value) {
        writeLock.lock();
        try {
            cache.put(key, value);
        } finally {
            writeLock.unlock();
        }
    }

    public void putAll(Map<? extends K, ? extends V> entries) {
        writeLock.lock();
        try {
            cache.putAll(entries);
        } finally {
            writeLock.unlock();
        }
    }

    public void invalidate(K key) {
        writeLock.lock();
        try {
            if(!isPresent(key)) {
                throw new CacheEntryNotExistsException(this, key);
            }
            cache.remove(key);
        } finally {
            writeLock.unlock();
        }
    }

    public void invalidateAll(Iterator<? extends K> keys) {
        writeLock.lock();
        try {
            List<K> noEntryKeys = new ArrayList<K>();
            while(keys.hasNext()) {
                K key = keys.next();
                if(!isPresent(key)) {
                    noEntryKeys.add(key);
                }
            }
            if(!noEntryKeys.isEmpty()) {
                throw new CacheEntriesNotExistException(this, noEntryKeys);
            }
            
            while(keys.hasNext()) {
                K key = keys.next();
                invalidate(key);
            }
        } finally {
            writeLock.unlock();
        }
    }

    public void invalidateAll() {
        writeLock.lock();
        try {
            cache.clear();
        } finally {
            writeLock.unlock();
        }
    }

    public int size() {
        readLock.lock();
        try {
            return cache.size();
        } finally {
            readLock.unlock();
        }
    }

    public void clear() {
        writeLock.lock();
        try {
            cache.clear();
        } finally {
            writeLock.unlock();
        }
    }

    public Map<? extends K, ? extends V> asMap() {
        readLock.lock();
        try {
            return new ConcurrentHashMap<K, V>(cache);
        } finally {
            readLock.unlock();
        }
    }

    public boolean isEmpty() {
        readLock.lock();
        try {
            return cache.isEmpty();
        } finally {
            readLock.unlock();
        }
    }

}
其简单的使用用例如下Q?
    @Test
    public void testCacheSimpleUsage() {
        Book uml = bookFactory.createUMLDistilled();
        Book derivatives = bookFactory.createDerivatives();
        
        String umlBookISBN = uml.getIsbn();
        String derivativesBookISBN = derivatives.getIsbn();
        
        Cache<String, Book> cache = cacheFactory.create("book-cache");
        cache.put(umlBookISBN, uml);
        cache.put(derivativesBookISBN, derivatives);
        
        Book fetchedBackUml = cache.get(umlBookISBN);
        System.out.println(fetchedBackUml);
        
        Book fetchedBackDerivatives = cache.get(derivativesBookISBN);
        System.out.println(fetchedBackDerivatives);
    }


DLevin 2013-10-15 23:46 发表评论
]]>
վ֩ģ壺 | | | ̨| | | ٲ| ֿ| | | ԫ| ̨| | | | ٲ| ǭ| | | ͩ| | | | ³ɽ| | ƽɽ| | | | | ½| ƽ| | | Ž| Ӣɽ| | | | | |