JVM初探 使用堆外内存减少Full GC.docx

上传人:b****5 文档编号:3876593 上传时间:2022-11-26 格式:DOCX 页数:18 大小:216.83KB
下载 相关 举报
JVM初探 使用堆外内存减少Full GC.docx_第1页
第1页 / 共18页
JVM初探 使用堆外内存减少Full GC.docx_第2页
第2页 / 共18页
JVM初探 使用堆外内存减少Full GC.docx_第3页
第3页 / 共18页
JVM初探 使用堆外内存减少Full GC.docx_第4页
第4页 / 共18页
JVM初探 使用堆外内存减少Full GC.docx_第5页
第5页 / 共18页
点击查看更多>>
下载资源
资源描述

JVM初探 使用堆外内存减少Full GC.docx

《JVM初探 使用堆外内存减少Full GC.docx》由会员分享,可在线阅读,更多相关《JVM初探 使用堆外内存减少Full GC.docx(18页珍藏版)》请在冰豆网上搜索。

JVM初探 使用堆外内存减少Full GC.docx

JVM初探使用堆外内存减少FullGC

JVM初探-使用堆外内存减少FullGC

引入

这个idea最初来源于TaobaoJVM对OpenJDK定制开发的GCIH部分(详见撒迦的分享-JVM定制改进@淘宝),其中GCIH就是将CMSOldHeap区的一部分划分出来,这部分内存虽然还在堆内,但已不被GC所管理.将长生命周期Java对象放在Java堆外,GC不能管理GCIH内Java对象(GCInvisibleHeap):

这样做有两方面的好处:

减少GC管理内存:

由于GCIH会从Old区“切出”一块,因此导致GC管理区域变小,可以明显降低GC工作量,提高GC效率,降低FullGCSTW时间(且由于这部分内存仍属于堆,因此其访问方式/速度不变-不必付出序列化/反序列化的开销).

GCIH内容进程间共享:

由于这部分区域不再是JVM运行时数据的一部分,因此GCIH内的对象可供对个JVM实例所共享(如一台Server跑多个MR-Job可共享同一份Cache数据),这样一台Server也就可以跑更多的VM实例.

(实际测试数据/图示可下载撒迦分享PPT).

但是大部分的互联公司不能像阿里这样可以有专门的工程师针对自己的业务特点定制JVM,因此我们只能”眼馋”GCIH带来的性能提升却无法”享用”.但通用的JVM开放了接口可直接向操作系统申请堆外内存(ByteBufferorUnsafe),而这部分内存也是GC所顾及不到的,因此我们可用JVM堆外内存来模拟GCIH的功能(但相比GCIH不足的是需要付出serialize/deserialize的开销).

JVM堆外内存

在JVM初探-JVM内存模型一文中介绍的Java运行时数据区域中是找不到堆外内存区域的:

因为它并不是JVM运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,这部分内存区域直接被操作系统管理.

在JDK1.4以前,对这部分内存访问没有光明正大的做法:

只能通过反射拿到Unsafe类,然后调用allocateMemory()/freeMemory()来申请/释放这块内存.1.4开始新加入了NIO,它引入了一种基于Channel与Buffer的I/O方式,可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作,ByteBuffer提供了如下常用方法来跟堆外内存打交道:

下面我们就用通用的JDKAPI来使用堆外内存来实现一个localcache.

示例1.:

使用JDKAPI实现堆外Cache

注:

主要逻辑都集中在方法invoke()内,而AbstractAppInvoker是一个自定义的性能测试框架,在后面会有详细的介绍.

/**

*@authorjifang

*@since2016/12/31下午6:

05.

*/

publicclassDirectByteBufferAppextendsAbstractAppInvoker{

@Test

@Override

publicvoidinvoke(Object...param){

Mapmap=createInHeapMap(SIZE);

//moveinoff-heap

byte[]bytes=serializer.serialize(map);

ByteBufferbuffer=ByteBuffer.allocateDirect(bytes.length);

buffer.put(bytes);

buffer.flip();

//forgc

map=null;

bytes=null;

System.out.println("writedown");

//moveoutfromoff-heap

byte[]offHeapBytes=newbyte[buffer.limit()];

buffer.get(offHeapBytes);

MapdeserMap=serializer.deserialize(offHeapBytes);

for(inti=0;i

Stringkey="key-"+i;

FeedDOfeedDO=deserMap.get(key);

checkValid(feedDO);

if(i%10000==0){

System.out.println("read"+i);

}

}

free(buffer);

}

privateMapcreateInHeapMap(intsize){

longcreateTime=System.currentTimeMillis();

Mapmap=newConcurrentHashMap<>(size);

for(inti=0;i

Stringkey="key-"+i;

FeedDOvalue=createFeed(i,key,createTime);

map.put(key,value);

}

returnmap;

}

}

由JDK提供的堆外内存访问API只能申请到一个类似一维数组的ByteBuffer,JDK并未提供基于堆外内存的实用数据结构实现(如堆外的Map、Set),因此想要实现Cache的功能只能在write()时先将数据put()到一个堆内的HashMap,然后再将整个Map序列化后MoveIn到DirectMemory,取缓存则反之.由于需要在堆内申请HashMap,因此可能会导致多次FullGC.这种方式虽然可以使用堆外内存,但性能不高、无法发挥堆外内存的优势.

幸运的是开源界的前辈开发了诸如Ehcache、MapDB、ChronicleMap等一系列优秀的堆外内存框架,使我们可以在使用简洁API访问堆外内存的同时又不损耗额外的性能.

其中又以Ehcache最为强大,其提供了in-heap、off-heap、on-disk、cluster四级缓存,且Ehcache企业级产品(BigMemoryMax/BigMemoryGo)实现的BigMemory也是Java堆外内存领域的先驱.

示例2:

MapDBAPI实现堆外Cache

publicclassMapDBAppextendsAbstractAppInvoker{

privatestaticHTreeMapmapDBCache;

static{

mapDBCache=DBMaker.hashMapSegmentedMemoryDirect()

.expireMaxSize(SIZE)

.make();

}

@Test

@Override

publicvoidinvoke(Object...param){

for(inti=0;i

Stringkey="key-"+i;

FeedDOfeed=createFeed(i,key,System.currentTimeMillis());

mapDBCache.put(key,feed);

}

System.out.println("writedown");

for(inti=0;i

Stringkey="key-"+i;

FeedDOfeedDO=mapDBCache.get(key);

checkValid(feedDO);

if(i%10000==0){

System.out.println("read"+i);

}

}

}

}

结果&分析

DirectByteBufferApp

S0S1EOPYGCYGCTFGCFGCTGCT

0.000.005.2278.5759.85192.902137.25110.153

thelastonejstatofMapDBApp

S0S1EOPYGCYGCTFGCFGCTGCT

0.000.038.020.3844.461710.23800.0000.238

运行DirectByteBufferApp.invoke()会发现有看到很多FullGC的产生,这是因为HashMap需要一个很大的连续数组,Old区很快就会被占满,因此也就导致频繁FullGC的产生.

而运行MapDBApp.invoke()可以看到有一个DirectMemory持续增长的过程,但FullGC却一次都没有了.

实验:

使用堆外内存减少FullGC

实验环境

java-version

javaversion"1.7.0_79"

Java(TM)SERuntimeEnvironment(build1.7.0_79-b15)

JavaHotSpot(TM)64-BitServerVM(build24.79-b02,mixedmode)

VMOptions

-Xmx512M

-XX:

MaxDirectMemorySize=512M

-XX:

+PrintGC

-XX:

+UseConcMarkSweepGC

-XX:

+CMSClassUnloadingEnabled

-XX:

CMSInitiatingOccupancyFraction=80

-XX:

+UseCMSInitiatingOccupancyOnly

实验数据

170W条动态(FeedDO).

实验代码

第1组:

in-heap、affectbyGC、noserialize

ConcurrentHashMapApp

publicclassConcurrentHashMapAppextendsAbstractAppInvoker{

privatestaticfinalMapcache=newConcurrentHashMap<>();

@Test

@Override

publicvoidinvoke(Object...param){

//write

for(inti=0;i

Stringkey=String.format("key_%s",i);

FeedDOfeedDO=createFeed(i,key,System.currentTimeMillis());

cache.put(key,feedDO);

}

System.out.println("writedown");

//read

for(inti=0;i

Stringkey=String.format("key_%s",i);

FeedDOfeedDO=cache.get(key);

checkValid(feedDO);

if(i%10000==0){

System.out.println("read"+i);

}

}

}

}

GuavaCacheApp类似,详细代码可参考完整项目.

第2组:

off-heap、notaffectbyGC、needserialize

EhcacheApp

publicclassEhcacheAppextendsAbstractAppInvoker{

privatestaticCachecache;

static{

ResourcePoolsresourcePools=ResourcePoolsBuilder.newResourcePoolsBuilder()

.heap(1000,EntryUnit.ENTRIES)

.offheap(480,MemoryUnit.MB)

.build();

CacheConfigurationconfiguration=CacheConfigurationBuilder

.newCacheConfigurationBuilder(String.class,FeedDO.class,resourcePools)

.build();

cache=CacheManagerBuilder.newCacheManagerBuilder()

.withCache("cacher",configuration)

.build(true)

.getCache("cacher",String.class,FeedDO.class);

}

@Test

@Override

publicvoidinvoke(Object...param){

for(inti=0;i

Stringkey=String.format("key_%s",i);

FeedDOfeedDO=createFeed(i,key,System.currentTimeMillis());

cache.put(key,feedDO);

}

System.out.println("writedown");

//read

for(inti=0;i

Stringkey=String.format("key_%s",i);

Objecto=cache.get(key);

checkValid(o);

if(i%10000==0){

System.out.println("read"+i);

}

}

}

}

MapDBApp与前同.

第3组:

off-process、notaffectbyGC、serialize、affectbyprocesscommunication

LocalRedisApp

publicclassLocalRedisAppextendsAbstractAppInvoker{

privatestaticfinalJediscache=newJedis("localhost",6379);

privatestaticfinalIObjectSerializerserializer=newHessian2Serializer();

@Test

@Override

publicvoidinvoke(Object...param){

//write

for(inti=0;i

Stringkey=String.format("key_%s",i);

FeedDOfeedDO=createFeed(i,key,System.currentTimeMillis());

byte[]value=serializer.serialize(feedDO);

cache.set(key.getBytes(),value);

if(i%10000==0){

System.out.println("write"+i);

}

}

System.out.println("writedown");

//read

for(inti=0;i

Stringkey=String.format("key_%s",i);

byte[]value=cache.get(key.getBytes());

FeedDOfeedDO=serializer.deserialize(value);

checkValid(feedDO);

if(i%10000==0){

System.out.println("read"+i);

}

}

}

}

结果分析

对比前面几组数据,可以有如下总结:

将长生命周期的大对象(如cache)移出heap可大幅度降低FullGC次数与耗时;

使用off-heap存储对象需要付出serialize/deserialize成本;

将cache放入分布式缓存需要付出进程间通信/网络通信的成本(UNIXDomain/TCPIP)

附:

off-heap的Ehcache能够跑出比in-heap的HashMap/Guava更好的成绩确实是我始料未及的O(∩_∩)O~,但确实这些数据和堆内存的搭配导致in-heap的FullGC太多了,当heap堆开大之后就肯定不是这个结果了.因此在使用堆外内存降低FullGC前,可以先考虑是否可以将heap开的更大.

附:

性能测试框架

在main函数启动时,扫描com.vdian.se.apps包下的所有继承了AbstractAppInvoker的类,然后使用Javassist为每个类生成一个代理对象:

当invoke()方法执行时首先检查他是否标注了@Test注解(在此,我们借用junit定义好了的注解),并在执行的前后记录方法执行耗时,并最终对比每个实现类耗时统计.

依赖

mons

commons-proxy

${commons.proxy.version}

org.javassist

javassist

${javassist.version}

com.caucho

hessian

${hessian.version}

com.google.guava

guava

${guava.version}

junit

junit

${junit.version}

启动类:

OffHeapStarter

/**

*@authorjifang

*@since2017/1/1上午10:

47.

*/

publicclassOffHeapStarter{

privatestaticfinalMapSTATISTICS_MAP=newHashMap<>();

publicstaticvoidmain(String[]args)throwsIOException,IllegalAccessException,InstantiationException{

Set

>>classes=PackageScanUtil.scanPackage("com.vdian.se.apps");

for(Class

>clazz:

classes){

AbstractAppInvokerinvoker=createProxyInvoker(clazz.newInstance());

invoker.invoke();

//System.gc();

}

System.out.println("*********************statistics**********************");

for(Map.Entryentry:

STATISTICS_MAP.entrySet()){

System.out.println("method["+entry.getKey()+"]totalcost["+entry.getValue()+"]ms");

}

}

privatestaticAbstractAppInvokercreateProxyInvoker(Objectinvoker){

ProxyFactoryfactory=wJavassistProxyFactory();

Class

>superclass=invoker.getClass().getSuperclass();

Objectproxy=factory

.createInterceptorProxy(invoker,newProfileInterceptor(),newClass[]{superclass});

return(AbstractAppInvoker)proxy;

}

privatestaticclassProfileInterceptorimplementsInterceptor{

@Override

publicObjectintercept(Invocationinvocation)throwsThrowable{

Class

>clazz=invocation.getProxy().getClass();

Methodmethod=clazz.getMethod(invocation.getMethod().getName(),Object[].class);

Objectresult=null;

if(method.isAnnotationPresent(Test.class)

&&method.getName().equals("invoke")

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 小学教育 > 数学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1