JVM初探使用堆外内存减少FullGCWord文档格式.docx
- 文档编号:21723735
- 上传时间:2023-01-31
- 格式:DOCX
- 页数:18
- 大小:216.83KB
JVM初探使用堆外内存减少FullGCWord文档格式.docx
《JVM初探使用堆外内存减少FullGCWord文档格式.docx》由会员分享,可在线阅读,更多相关《JVM初探使用堆外内存减少FullGCWord文档格式.docx(18页珍藏版)》请在冰豆网上搜索。
05.
*/
publicclassDirectByteBufferAppextendsAbstractAppInvoker{
@Test
@Override
publicvoidinvoke(Object...param){
Map<
String,FeedDO>
map=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);
deserMap=serializer.deserialize(offHeapBytes);
for(inti=0;
i<
SIZE;
++i){
Stringkey="
key-"
+i;
FeedDOfeedDO=deserMap.get(key);
checkValid(feedDO);
if(i%10000==0){
read"
+i);
}
free(buffer);
privateMap<
createInHeapMap(intsize){
longcreateTime=System.currentTimeMillis();
map=newConcurrentHashMap<
>
(size);
size;
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{
privatestaticHTreeMap<
mapDBCache;
static{
mapDBCache=DBMaker.hashMapSegmentedMemoryDirect()
.expireMaxSize(SIZE)
.make();
FeedDOfeed=createFeed(i,key,System.currentTimeMillis());
mapDBCache.put(key,feed);
FeedDOfeedDO=mapDBCache.get(key);
结果&
分析
DirectByteBufferApp
S0S1EOPYGCYGCTFGCFGCTGCT
0.000.005.2278.5759.85192.902137.25110.153
thelastonejstatofMapDBApp
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
+PrintGC
+UseConcMarkSweepGC
+CMSClassUnloadingEnabled
CMSInitiatingOccupancyFraction=80
+UseCMSInitiatingOccupancyOnly
实验数据
170W条动态(FeedDO).
实验代码
第1组:
in-heap、affectbyGC、noserialize
ConcurrentHashMapApp
publicclassConcurrentHashMapAppextendsAbstractAppInvoker{
privatestaticfinalMap<
cache=newConcurrentHashMap<
();
//write
Stringkey=String.format("
key_%s"
i);
FeedDOfeedDO=createFeed(i,key,System.currentTimeMillis());
cache.put(key,feedDO);
//read
FeedDOfeedDO=cache.get(key);
GuavaCacheApp类似,详细代码可参考完整项目.
第2组:
off-heap、notaffectbyGC、needserialize
EhcacheApp
publicclassEhcacheAppextendsAbstractAppInvoker{
privatestaticCache<
cache;
ResourcePoolsresourcePools=ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(1000,EntryUnit.ENTRIES)
.offheap(480,MemoryUnit.MB)
.build();
CacheConfiguration<
configuration=CacheConfigurationBuilder
.newCacheConfigurationBuilder(String.class,FeedDO.class,resourcePools)
cache=CacheManagerBuilder.newCacheManagerBuilder()
.withCache("
cacher"
configuration)
.build(true)
.getCache("
String.class,FeedDO.class);
Objecto=cache.get(key);
checkValid(o);
MapDBApp与前同.
第3组:
off-process、notaffectbyGC、serialize、affectbyprocesscommunication
LocalRedisApp
publicclassLocalRedisAppextendsAbstractAppInvoker{
privatestaticfinalJediscache=newJedis("
localhost"
6379);
privatestaticfinalIObjectSerializerserializer=newHessian2Serializer();
byte[]value=serializer.serialize(feedDO);
cache.set(key.getBytes(),value);
write"
byte[]value=cache.get(key.getBytes());
FeedDOfeedDO=serializer.deserialize(value);
结果分析
对比前面几组数据,可以有如下总结:
将长生命周期的大对象(如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定义好了的注解),并在执行的前后记录方法执行耗时,并最终对比每个实现类耗时统计.
依赖
<
dependency>
<
groupId>
mons<
/groupId>
artifactId>
commons-proxy<
/artifactId>
version>
${commons.proxy.version}<
/version>
/dependency>
org.javassist<
javassist<
${javassist.version}<
com.caucho<
hessian<
${hessian.version}<
com.google.guava<
guava<
${guava.version}<
junit<
${junit.version}<
启动类:
OffHeapStarter
*@since2017/1/1上午10:
47.
publicclassOffHeapStarter{
String,Long>
STATISTICS_MAP=newHashMap<
publicstaticvoidmain(String[]args)throwsIOException,IllegalAccessException,InstantiationException{
Set<
Class<
?
classes=PackageScanUtil.scanPackage("
com.vdian.se.apps"
for(Class<
clazz:
classes){
AbstractAppInvokerinvoker=createProxyInvoker(clazz.newInstance());
invoker.invoke();
//System.gc();
*********************statistics**********************"
for(Map.Entry<
entry:
STATISTICS_MAP.entrySet()){
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{
publicObjectintercept(Invocationinvocation)throwsThrowable{
clazz=invocation.getProxy().getClass();
Methodmethod=clazz.getMethod(invocation.getMethod().getName(),Object[].class);
Objectresult=null;
if(method.isAnnotationPresent(Test.class)
&
&
method.getName().equals("
invoke"
))
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- JVM 初探 使用 内存 减少 FullGC