JVM初探-使用堆外内存减少FullGC_第1页
JVM初探-使用堆外内存减少FullGC_第2页
JVM初探-使用堆外内存减少FullGC_第3页
JVM初探-使用堆外内存减少FullGC_第4页
JVM初探-使用堆外内存减少FullGC_第5页
已阅读5页,还剩12页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、JVM初探- 使用堆外内存减少Full GC引入这个idea最初来源于TaobaoJVM对OpenJDK定制开发的GCIH部分(详见撒迦的分享-JVM定制改进淘宝), 其中GCIH就是将CMS Old Heap区的一部分划分出来, 这部分内存虽然还在堆内, 但已不被GC所管理.将长生命周期Java对象放在Java堆外, GC不能管理GCIH内Java对象(GC Invisible Heap):这样做有两方面的好处: 减少GC管理内存: 由于GCIH会从Old区“切出”一块, 因此导致GC管理区域变小, 可以明显降低GC工作量, 提高GC效率, 降低Full GC STW时间(且由于这部分内存仍

2、属于堆, 因此其访问方式/速度不变- 不必付出序列化/反序列化的开销).GCIH内容进程间共享: 由于这部分区域不再是JVM运行时数据的一部分, 因此GCIH内的对象可供对个JVM实例所共享(如一台Server跑多个MR-Job可共享同一份Cache数据), 这样一台Server也就可以跑更多的VM实例.(实际测试数据/图示可下载撒迦分享PPT).但是大部分的互联公司不能像阿里这样可以有专门的工程师针对自己的业务特点定制JVM, 因此我们只能”眼馋”GCIH带来的性能提升却无法”享用”. 但通用的JVM开放了接口可直接向操作系统申请堆外内存(ByteBuffer or Unsafe), 而这部

3、分内存也是GC所顾及不到的, 因此我们可用JVM堆外内存来模拟GCIH的功能(但相比GCIH不足的是需要付出serialize/deserialize的开销).JVM堆外内存在JVM初探 -JVM内存模型一文中介绍的Java运行时数据区域中是找不到堆外内存区域的:因为它并不是JVM运行时数据区的一部分, 也不是Java虚拟机规范中定义的内存区域, 这部分内存区域直接被操作系统管理. 在JDK 1.4以前, 对这部分内存访问没有光明正大的做法: 只能通过反射拿到Unsafe类, 然后调用allocateMemory()/freeMemory()来申请/释放这块内存. 1.4开始新加入了NIO,

4、它引入了一种基于Channel与Buffer的I/O方式, 可以使用Native函数库直接分配堆外内存, 然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作, ByteBuffer提供了如下常用方法来跟堆外内存打交道:下面我们就用通用的JDK API来使用堆外内存来实现一个local cache.示例1.: 使用JDK API实现堆外Cache注: 主要逻辑都集中在方法invoke()内, 而AbstractAppInvoker是一个自定义的性能测试框架, 在后面会有详细的介绍./* * author jifang * since 2016/12/

5、31 下午6:05. */public class DirectByteBufferApp extends AbstractAppInvoker Test Override public void invoke(Object. param) Map<String, FeedDO> map = createInHeapMap(SIZE); / move in off-heap byte bytes = serializer.serialize(map); ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length); buff

6、er.put(bytes); buffer.flip(); / for gc map = null; bytes = null; System.out.println("write down"); / move out from off-heap byte offHeapBytes = new bytebuffer.limit(); buffer.get(offHeapBytes); Map<String, FeedDO> deserMap = serializer.deserialize(offHeapBytes); for (int i = 0; i <

7、; SIZE; +i) String key = "key-" + i; FeedDO feedDO = deserMap.get(key); checkValid(feedDO); if (i % 10000 = 0) System.out.println("read " + i); free(buffer); private Map<String, FeedDO> createInHeapMap(int size) long createTime = System.currentTimeMillis(); Map<String, F

8、eedDO> map = new ConcurrentHashMap<>(size); for (int i = 0; i < size; +i) String key = "key-" + i; FeedDO value = createFeed(i, key, createTime); map.put(key, value); return map; 由JDK提供的堆外内存访问API只能申请到一个类似一维数组的ByteBuffer, JDK并未提供基于堆外内存的实用数据结构实现(如堆外的Map、Set), 因此想要实现Cache的功能只能在wri

9、te()时先将数据put()到一个堆内的HashMap, 然后再将整个Map序列化后MoveIn到DirectMemory, 取缓存则反之. 由于需要在堆内申请HashMap, 因此可能会导致多次Full GC. 这种方式虽然可以使用堆外内存, 但性能不高、无法发挥堆外内存的优势. 幸运的是开源界的前辈开发了诸如Ehcache、MapDB、Chronicle Map等一系列优秀的堆外内存框架, 使我们可以在使用简洁API访问堆外内存的同时又不损耗额外的性能.其中又以Ehcache最为强大, 其提供了in-heap、off-heap、on-disk、cluster四级缓存, 且Ehcache企业

10、级产品(BigMemory Max / BigMemory Go)实现的BigMemory也是Java堆外内存领域的先驱.示例2: MapDB API实现堆外Cachepublic class MapDBApp extends AbstractAppInvoker private static HTreeMap<String, FeedDO> mapDBCache; static mapDBCache = DBMaker.hashMapSegmentedMemoryDirect() .expireMaxSize(SIZE) .make(); Test Override public

11、 void invoke(Object. param) for (int i = 0; i < SIZE; +i) String key = "key-" + i; FeedDO feed = createFeed(i, key, System.currentTimeMillis(); mapDBCache.put(key, feed); System.out.println("write down"); for (int i = 0; i < SIZE; +i) String key = "key-" + i; Feed

12、DO feedDO = mapDBCache.get(key); checkValid(feedDO); if (i % 10000 = 0) System.out.println("read " + i); 结果 & 分析DirectByteBufferApp S0 S1 E O P YGC YGCT FGC FGCT GCT0.00 0.00 5.22 78.57 59.85 19 2.902 13 7.251 10.153the last one jstat of MapDBApp S0 S1 E O P YGC YGCT FGC FGCT GCT0.00 0

13、.03 8.02 0.38 44.46 171 0.238 0 0.000 0.238运行DirectByteBufferApp.invoke()会发现有看到很多Full GC的产生, 这是因为HashMap需要一个很大的连续数组, Old区很快就会被占满, 因此也就导致频繁Full GC的产生. 而运行MapDBApp.invoke()可以看到有一个DirectMemory持续增长的过程, 但FullGC却一次都没有了.实验: 使用堆外内存减少Full GC实验环境java -versionjava version "1.7.0_79"Java(TM) SE Runtim

14、e Environment (build 1.7.0_79-b15)Java HotSpot(TM) 64-Bit Server VM (build 24.79-b02, mixed mode)VM Options-Xmx512M-XX:MaxDirectMemorySize=512M-XX:+PrintGC-XX:+UseConcMarkSweepGC-XX:+CMSClassUnloadingEnabled-XX:CMSInitiatingOccupancyFraction=80-XX:+UseCMSInitiatingOccupancyOnly实验数据 170W条动态(FeedDO).实

15、验代码第1组: in-heap、affect by GC、no serializeConcurrentHashMapApppublic class ConcurrentHashMapApp extends AbstractAppInvoker private static final Map<String, FeedDO> cache = new ConcurrentHashMap<>(); Test Override public void invoke(Object. param) / write for (int i = 0; i < SIZE; +i) S

16、tring key = String.format("key_%s", i); FeedDO feedDO = createFeed(i, key, System.currentTimeMillis(); cache.put(key, feedDO); System.out.println("write down"); / read for (int i = 0; i < SIZE; +i) String key = String.format("key_%s", i); FeedDO feedDO = cache.get(ke

17、y); checkValid(feedDO); if (i % 10000 = 0) System.out.println("read " + i); GuavaCacheApp类似, 详细代码可参考完整项目.第2组: off-heap、not affect by GC、need serializeEhcacheApppublic class EhcacheApp extends AbstractAppInvoker private static Cache<String, FeedDO> cache; static ResourcePools resource

18、Pools = ResourcePoolsBuilder.newResourcePoolsBuilder() .heap(1000, EntryUnit.ENTRIES) .offheap(480, MemoryUnit.MB) .build(); CacheConfiguration<String, FeedDO> configuration = CacheConfigurationBuilder .newCacheConfigurationBuilder(String.class, FeedDO.class, resourcePools) .build(); cache = C

19、acheManagerBuilder.newCacheManagerBuilder() .withCache("cacher", configuration) .build(true) .getCache("cacher", String.class, FeedDO.class); Test Override public void invoke(Object. param) for (int i = 0; i < SIZE; +i) String key = String.format("key_%s", i); FeedDO

20、 feedDO = createFeed(i, key, System.currentTimeMillis(); cache.put(key, feedDO); System.out.println("write down"); / read for (int i = 0; i < SIZE; +i) String key = String.format("key_%s", i); Object o = cache.get(key); checkValid(o); if (i % 10000 = 0) System.out.println(&quo

21、t;read " + i); MapDBApp与前同.第3组: off-process、not affect by GC、serialize、affect by process communicationLocalRedisApppublic class LocalRedisApp extends AbstractAppInvoker private static final Jedis cache = new Jedis("localhost", 6379); private static final IObjectSerializer serializer =

22、 new Hessian2Serializer(); Test Override public void invoke(Object. param) / write for (int i = 0; i < SIZE; +i) String key = String.format("key_%s", i); FeedDO feedDO = createFeed(i, key, System.currentTimeMillis(); byte value = serializer.serialize(feedDO); cache.set(key.getBytes(), v

23、alue); if (i % 10000 = 0) System.out.println("write " + i); System.out.println("write down"); / read for (int i = 0; i < SIZE; +i) String key = String.format("key_%s", i); byte value = cache.get(key.getBytes(); FeedDO feedDO = serializer.deserialize(value); checkVali

24、d(feedDO); if (i % 10000 = 0) System.out.println("read " + i); 结果分析对比前面几组数据, 可以有如下总结:将长生命周期的大对象(如cache)移出heap可大幅度降低Full GC次数与耗时;使用off-heap存储对象需要付出serialize/deserialize成本;将cache放入分布式缓存需要付出进程间通信/网络通信的成本(UNIX Domain/TCP IP)附: off-heap的Ehcache能够跑出比in-heap的HashMap/Guava更好的成绩确实是我始料未及的O(_)O, 但确实这

25、些数据和堆内存的搭配导致in-heap的Full GC太多了, 当heap堆开大之后就肯定不是这个结果了. 因此在使用堆外内存降低Full GC前, 可以先考虑是否可以将heap开的更大.附: 性能测试框架在main函数启动时, 扫描com.vdian.se.apps包下的所有继承了AbstractAppInvoker的类, 然后使用Javassist为每个类生成一个代理对象: 当invoke()方法执行时首先检查他是否标注了Test注解(在此, 我们借用junit定义好了的注解), 并在执行的前后记录方法执行耗时, 并最终对比每个实现类耗时统计.依赖<dependency> &l

26、t;groupId>mons</groupId> <artifactId>commons-proxy</artifactId> <version>$xy.version</version></dependency><dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>$javassist.versio

27、n</version></dependency><dependency> <groupId>com.caucho</groupId> <artifactId>hessian</artifactId> <version>$hessian.version</version></dependency><dependency> <groupId>com.google.guava</groupId> <artifactId>guava&l

28、t;/artifactId> <version>$guava.version</version></dependency><dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>$junit.version</version></dependency>启动类: OffHeapStarter/* * author jifang * since 2017/1/1

29、上午10:47. */public class OffHeapStarter private static final Map<String, Long> STATISTICS_MAP = new HashMap<>(); public static void main(String args) throws IOException, IllegalAccessException, InstantiationException Set<Class<?>> classes = PackageScanUtil.scanPackage("co

30、m.vdian.se.apps"); for (Class<?> clazz : classes) AbstractAppInvoker invoker = createProxyInvoker(clazz.newInstance(); invoker.invoke(); /System.gc(); System.out.println("* statistics *"); for (Map.Entry<String, Long> entry : STATISTICS_MAP.entrySet() System.out.println(&q

31、uot;method " + entry.getKey() + " total cost " + entry.getValue() + "ms"); private static AbstractAppInvoker createProxyInvoker(Object invoker) ProxyFactory factory = w JavassistProxyFactory(); Class<?> superclass = invoker.getClass().getSuperclass(); Object proxy = fa

32、ctory .createInterceptorProxy(invoker, new ProfileInterceptor(), new Classsuperclass); return (AbstractAppInvoker) proxy; private static class ProfileInterceptor implements Interceptor Override public Object intercept(Invocation invocation) throws Throwable Class<?> clazz = invocation.getProxy

33、().getClass(); Method method = clazz.getMethod(invocation.getMethod().getName(), Object.class); Object result = null; if (method.isAnnotationPresent(Test.class) && method.getName().equals("invoke") String methodName = String.format("%s.%s", clazz.getSimpleName(), method.g

34、etName(); System.out.println("method " + methodName + " start invoke"); long start = System.currentTimeMillis(); result = ceed(); long cost = System.currentTimeMillis() - start; System.out.println("method " + methodName + " total cost " + cost +

35、"ms"); STATISTICS_MAP.put(methodName, cost); return result; 包扫描工具: PackageScanUtilpublic class PackageScanUtil private static final String CLASS_SUFFIX = ".class" private static final String FILE_PROTOCOL = "file" public static Set<Class<?>> scanPackage(Stri

36、ng packageName) throws IOException Set<Class<?>> classes = new HashSet<>(); String packageDir = packageName.replace('.', '/'); Enumeration<URL> packageResources = Thread.currentThread().getContextClassLoader().getResources(packageDir); while (packageResources.

37、hasMoreElements() URL packageResource = packageResources.nextElement(); String protocol = packageResource.getProtocol(); / 只扫描项目内class if (FILE_PROTOCOL.equals(protocol) String packageDirPath = URLDecoder.decode(packageResource.getPath(), "UTF-8"); scanProjectPackage(packageName, packageDi

38、rPath, classes); return classes; private static void scanProjectPackage(String packageName, String packageDirPath, Set<Class<?>> classes) File packageDirFile = new File(packageDirPath); if (packageDirFile.exists() && packageDirFile.isDirectory() File subFiles = packageDirFile.lis

39、tFiles(new FileFilter() Override public boolean accept(File pathname) urn pathname.isDirectory() | pathname.getName().endsWith(CLASS_SUFFIX); ); for (File subFile : subFiles) if (!subFile.isDirectory() String className = trimClassSuffix(subFile.getName(); String classNameWithPackage = packageName +

40、"." + className; Class<?> clazz = null; try clazz = Class.forName(classNameWithPackage); catch (ClassNotFoundException e) / ignore assert clazz != null; Class<?> superclass = clazz.getSuperclass(); if (superclass = AbstractAppInvoker.class) classes.add(clazz); / trim .class suf

41、fix private static String trimClassSuffix(String classNameWithSuffix) int endIndex = classNameWithSuffix.length() - CLASS_SUFFIX.length(); return classNameWithSuffix.substring(0, endIndex); 注: 在此仅扫描项目目录下的单层目录的class文件, 功能更强大的包扫描工具可参考spring源代码或Touch源代码中的PackageScanUtil类.AppInvoker基类: AbstractAppInvoke

42、r提供通用测试参数 & 工具函数.public abstract class AbstractAppInvoker protected static final int SIZE = 170_0000; protected static final IObjectSerializer serializer = new Hessian2Serializer(); protected static FeedDO createFeed(long id, String userId, long createTime) return new FeedDO(id, userId, (int) id

43、, userId + "_" + id, createTime); protected static void free(ByteBuffer byteBuffer) if (byteBuffer.isDirect() (DirectBuffer) byteBuffer).cleaner().clean(); protected static void checkValid(Object obj) if (obj = null) throw new RuntimeException("cache invalid"); protected static void sleep(int time, String beforeMsg) if (!Strings.isNullOrEmpty(beforeMsg) System.out.println(beforeMsg); try

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论