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

转载自  JVM初探- 使用堆外内存减少Full GC

问题: 大部分主流互联网企业线上Server JVM选用了CMS收集器(如Taobao、LinkedIn、Vdian), 虽然CMS可与用户线程并发GC以降低STW时间, 但它也并非十分完美, 尤其是当出现Concurrent Mode Failure由并行GC转入串行时, 将导致非常长时间的Stop The World(详细可参考JVM初探- 内存分配、GC原理与垃圾收集器).

解决: 由GCIH可以联想到: 将长期存活的对象(如Local Cache)移入堆外内存(off-heap, 又名直接内存/direct-memory), 从而减少CMS管理的对象数量, 以降低Full GC的次数和频率, 达到提高系统响应速度的目的.


引入

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

(图片来源: JVM@Taobao PPT)

  • 这样做有两方面的好处: 
    1. 减少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), 而这部分内存也是GC所顾及不到的, 因此我们可用JVM堆外内存来模拟GCIH的功能(但相比GCIH不足的是需要付出serialize/deserialize的开销).


JVM堆外内存

在JVM初探 -JVM内存模型一文中介绍的Java运行时数据区域中是找不到堆外内存区域的: 
 
因为它并不是JVM运行时数据区的一部分, 也不是Java虚拟机规范中定义的内存区域, 这部分内存区域直接被操作系统管理. 
在JDK 1.4以前, 对这部分内存访问没有光明正大的做法: 只能通过反射拿到Unsafe类, 然后调用allocateMemory()/freeMemory()来申请/释放这块内存. 1.4开始新加入了NIO, 它引入了一种基于Channel与Buffer的I/O方式, 可以使用Native函数库直接分配堆外内存, 然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作, ByteBuffer提供了如下常用方法来跟堆外内存打交道:

API描述
static ByteBuffer allocateDirect(int capacity)Allocates a new direct byte buffer.
ByteBuffer put(byte b)Relative put method (optional operation).
ByteBuffer put(byte[] src)Relative bulk put method (optional operation).
ByteBuffer putXxx(Xxx value)Relative put method for writing a Char/Double/Float/Int/Long/Short value (optional operation).
ByteBuffer get(byte[] dst)Relative bulk get method.
Xxx getXxx()Relative get method for reading a Char/Double/Float/Int/Long/Short value.
XxxBuffer asXxxBuffer()Creates a view of this byte buffer as a Char/Double/Float/Int/Long/Short buffer.
ByteBuffer asReadOnlyBuffer()Creates a new, read-only byte buffer that shares this buffer’s content.
boolean isDirect()Tells whether or not this byte buffer is direct.
ByteBuffer duplicate()Creates a new byte buffer that shares this buffer’s content.

下面我们就用通用的JDK API来使用堆外内存来实现一个local cache.


示例1.: 使用JDK API实现堆外Cache

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

/*** @author jifang* @since 2016/12/31 下午6:05.*/
public class DirectByteBufferApp extends AbstractAppInvoker {@Test@Overridepublic void invoke(Object... param) {Map<String, FeedDO> map = createInHeapMap(SIZE);// move in off-heapbyte[] bytes = serializer.serialize(map);ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length);buffer.put(bytes);buffer.flip();// for gcmap = null;bytes = null;System.out.println("write down");// move out from off-heapbyte[] offHeapBytes = new byte[buffer.limit()];buffer.get(offHeapBytes);Map<String, FeedDO> deserMap = serializer.deserialize(offHeapBytes);for (int i = 0; i < 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, FeedDO> 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并未提供基于堆外内存的实用数据结构实现(如堆外的MapSet), 因此想要实现Cache的功能只能在write()时先将数据put()到一个堆内的HashMap, 然后再将整个Map序列化后MoveInDirectMemory, 取缓存则反之. 由于需要在堆内申请HashMap, 因此可能会导致多次Full GC. 这种方式虽然可以使用堆外内存, 但性能不高、无法发挥堆外内存的优势. 
幸运的是开源界的前辈开发了诸如EhcacheMapDBChronicle Map等一系列优秀的堆外内存框架, 使我们可以在使用简洁API访问堆外内存的同时又不损耗额外的性能.

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


示例2: MapDB API实现堆外Cache

public class MapDBApp extends AbstractAppInvoker {private static HTreeMap<String, FeedDO> mapDBCache;static {mapDBCache = DBMaker.hashMapSegmentedMemoryDirect().expireMaxSize(SIZE).make();}@Test@Overridepublic 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;FeedDO 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     GCT
0.00   0.00   5.22  78.57  59.85     19    2.902    13    7.251   10.153
  • the last one jstat of MapDBApp
 S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT
0.00   0.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 -version
java version "1.7.0_79"
Java(TM) SE Runtime 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).

实验代码

第1组: in-heap、affect by GC、no serialize

  • ConcurrentHashMapApp
public class ConcurrentHashMapApp extends AbstractAppInvoker {private static final Map<String, FeedDO> cache = new ConcurrentHashMap<>();@Test@Overridepublic void invoke(Object... param) {// writefor (int i = 0; i < SIZE; ++i) {String key = String.format("key_%s", i);FeedDO feedDO = createFeed(i, key, System.currentTimeMillis());cache.put(key, feedDO);}System.out.println("write down");// readfor (int i = 0; i < SIZE; ++i) {String key = String.format("key_%s", i);FeedDO feedDO = cache.get(key);checkValid(feedDO);if (i % 10000 == 0) {System.out.println("read " + i);}}}
}

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


第2组: off-heap、not affect by GC、need serialize

  • EhcacheApp
public class EhcacheApp extends AbstractAppInvoker {private static Cache<String, FeedDO> cache;static {ResourcePools resourcePools = ResourcePoolsBuilder.newResourcePoolsBuilder().heap(1000, EntryUnit.ENTRIES).offheap(480, MemoryUnit.MB).build();CacheConfiguration<String, FeedDO> configuration = CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, FeedDO.class, resourcePools).build();cache = CacheManagerBuilder.newCacheManagerBuilder().withCache("cacher", configuration).build(true).getCache("cacher", String.class, FeedDO.class);}@Test@Overridepublic void invoke(Object... param) {for (int i = 0; i < SIZE; ++i) {String key = String.format("key_%s", i);FeedDO feedDO = createFeed(i, key, System.currentTimeMillis());cache.put(key, feedDO);}System.out.println("write down");// readfor (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("read " + i);}}}
}

MapDBApp与前同.


第3组: off-process、not affect by GC、serialize、affect by process communication

  • LocalRedisApp
public class LocalRedisApp extends AbstractAppInvoker {private static final Jedis cache = new Jedis("localhost", 6379);private static final IObjectSerializer serializer = new Hessian2Serializer();@Test@Overridepublic void invoke(Object... param) {// writefor (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(), value);if (i % 10000 == 0) {System.out.println("write " + i);}}System.out.println("write down");// readfor (int i = 0; i < SIZE; ++i) {String key = String.format("key_%s", i);byte[] value = cache.get(key.getBytes());FeedDO feedDO = serializer.deserialize(value);checkValid(feedDO);if (i % 10000 == 0) {System.out.println("read " + i);}}}
}

RemoteRedisApp类似, 详细代码可参考下面完整项目.


实验结果

*ConcurrentMapGuava
TTC32166ms/32s47520ms/47s
Minor C/T31/1.52229/1.312
Full C/T24/23.21236/41.751
   
 MapDBEhcache
TTC40272ms/40s30814ms/31s
Minor C/T511/0.557297/0.430
Full C/T0/0.0000/0.000
   
 LocalRedisNetworkRedis
TTC176382ms/176s1h+
Minor C/T421/0.415-
Full C/T0/0.000-

备注: 
- TTC: Total Time Cost 总共耗时 
- C/T: Count/Time 次数/耗时(seconds)


结果分析

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

  • 将长生命周期的大对象(如cache)移出heap可大幅度降低Full GC次数与耗时;
  • 使用off-heap存储对象需要付出serialize/deserialize成本;
  • 将cache放入分布式缓存需要付出进程间通信/网络通信的成本(UNIX Domain/TCP IP)

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


附: 性能测试框架

在main函数启动时, 扫描com.vdian.se.apps包下的所有继承了AbstractAppInvoker的类, 然后使用Javassist为每个类生成一个代理对象: 当invoke()方法执行时首先检查他是否标注了@Test注解(在此, 我们借用junit定义好了的注解), 并在执行的前后记录方法执行耗时, 并最终对比每个实现类耗时统计.

  • 依赖
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-proxy</artifactId><version>${commons.proxy.version}</version>
</dependency>
<dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>${javassist.version}</version>
</dependency>
<dependency><groupId>com.caucho</groupId><artifactId>hessian</artifactId><version>${hessian.version}</version>
</dependency>
<dependency><groupId>com.google.guava</groupId><artifactId>guava</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 上午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("com.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("method [" + entry.getKey() + "] total cost [" + entry.getValue() + "]ms");}}private static AbstractAppInvoker createProxyInvoker(Object invoker) {ProxyFactory factory = new JavassistProxyFactory();Class<?> superclass = invoker.getClass().getSuperclass();Object proxy = factory.createInterceptorProxy(invoker, new ProfileInterceptor(), new Class[]{superclass});return (AbstractAppInvoker) proxy;}private static class ProfileInterceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {Class<?> clazz = invocation.getProxy().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.getName());System.out.println("method [" + methodName + "] start invoke");long start = System.currentTimeMillis();result = invocation.proceed();long cost = System.currentTimeMillis() - start;System.out.println("method [" + methodName + "] total cost [" + cost + "]ms");STATISTICS_MAP.put(methodName, cost);}return result;}}
}
  • 包扫描工具: PackageScanUtil
public class PackageScanUtil {private static final String CLASS_SUFFIX = ".class";private static final String FILE_PROTOCOL = "file";public static Set<Class<?>> scanPackage(String packageName) throws IOException {Set<Class<?>> classes = new HashSet<>();String packageDir = packageName.replace('.', '/');Enumeration<URL> packageResources = Thread.currentThread().getContextClassLoader().getResources(packageDir);while (packageResources.hasMoreElements()) {URL packageResource = packageResources.nextElement();String protocol = packageResource.getProtocol();// 只扫描项目内classif (FILE_PROTOCOL.equals(protocol)) {String packageDirPath = URLDecoder.decode(packageResource.getPath(), "UTF-8");scanProjectPackage(packageName, packageDirPath, 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.listFiles(new FileFilter() {@Overridepublic boolean accept(File pathname) {return pathname.isDirectory() || pathname.getName().endsWith(CLASS_SUFFIX);}});for (File subFile : subFiles) {if (!subFile.isDirectory()) {String className = trimClassSuffix(subFile.getName());String classNameWithPackage = packageName + "." + 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 suffixprivate static String trimClassSuffix(String classNameWithSuffix) {int endIndex = classNameWithSuffix.length() - CLASS_SUFFIX.length();return classNameWithSuffix.substring(0, endIndex);}
}

注: 在此仅扫描项目目录下的单层目录的class文件, 功能更强大的包扫描工具可参考Spring源代码或Touch源代码中的PackageScanUtil类.


AppInvoker基类: AbstractAppInvoker

提供通用测试参数 & 工具函数.

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, 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 {Thread.sleep(time);} catch (InterruptedException ignored) {// no op}}/*** 供子类继承 & 外界调用** @param param*/public abstract void invoke(Object... param);
}

序列化/反序列化接口与实现

public interface IObjectSerializer {<T> byte[] serialize(T obj);<T> T deserialize(byte[] bytes);
}
public class Hessian2Serializer implements IObjectSerializer {private static final Logger LOGGER = LoggerFactory.getLogger(Hessian2Serializer.class);@Overridepublic <T> byte[] serialize(T obj) {if (obj != null) {try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {Hessian2Output out = new Hessian2Output(os);out.writeObject(obj);out.close();return os.toByteArray();} catch (IOException e) {LOGGER.error("Hessian serialize error ", e);throw new CacherException(e);}}return null;}@SuppressWarnings("unchecked")@Overridepublic <T> T deserialize(byte[] bytes) {if (bytes != null) {try (ByteArrayInputStream is = new ByteArrayInputStream(bytes)) {Hessian2Input in = new Hessian2Input(is);T obj = (T) in.readObject();in.close();return obj;} catch (IOException e) {LOGGER.error("Hessian deserialize error ", e);throw new CacherException(e);}}return null;}
}

完整项目地址: https://github.com/feiqing/off-heap-tester.git.


GC统计工具

#!/bin/bash

pid=`jps | grep $1 | awk '{print $1}'`
jstat -gcutil ${pid} 400 10000
  • 使用 
    sh jstat-uti.sh ${u-main-class}

附加: 为什么在实验中in-heap cache的Minor GC那么少? 
现在我还不能给出一个确切地分析答案, 有的同学说是因为CMS Full GC会连带一次Minor GC, 而用jstat会直接计入Full GC, 但查看详细的GC日志也并未发现什么端倪. 希望有了解的同学可以在下面评论区可以给我留言, 再次先感谢了O(∩_∩)O~.


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/327991.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

快捷生成---QQ点击联系我的方法

第一步 第二步 第三步 把uin2764954910 p2:2764954910:53 换成自己的QQ号 运行 完结撒花

.NET Core中间件的注册和管道的构建(1)---- 注册和构建原理

0x00 问题的产生 管道是.NET Core中非常关键的一个概念&#xff0c;很多重要的组件都以中间件的形式存在&#xff0c;包括权限管理、会话管理、路由等。所以搞明白中间件是如何注册并最终构建成管道的很重要。园子里很多先驱早已经开始了这方面的研究学习&#xff0c;也写了很多…

语言 高速公路超速处罚_重磅!全国高速将统一限速,这4种超速不再扣分罚款!【饮茶论道】...

在高速开车&#xff0c;经常会经历“断崖式降速”和“忽高忽低式限速”。相信不少吃了罚单的司机感受都是&#xff1a;哑巴吃黄连——有苦说不出……现在&#xff0c;重磅消息来啦&#xff01;在高速公路上行驶前方没有任何障碍导航却突然提醒你“当前道路限速60km/h&#xff0…

SpringBoot开发常用技术整合 代码上传至github上面去

简介&#xff1a;本课程通过详细的对springboot的各个技能点逐一介绍与演示&#xff0c;可以很迅速的熟悉整个springboot框架体系&#xff0c;并且与springmvc有效的进行对比&#xff0c;理解异同&#xff0c;这样对于后续的springboot开发会非常迅速。同时课程中会针对不同的技…

Java中对象的三种状态

转载自 Java中对象的三种状态Java中的对象的三种状态是和垃圾回收紧密相关的&#xff0c;因此有必要深究。 状态一&#xff1a;可触及态&#xff1a;从根节点开始&#xff0c;可以搜索到这个对象&#xff0c;也就是可以访问到这个对象&#xff0c;也有人将其称为可达状态。 状…

HTML5(笔记)

什么是HTML Hyper Text Markup Language(超文本标记语言) 超文本包括&#xff1a;文字&#xff0c;图片&#xff0c;音频&#xff0c;视频&#xff0c;动画等 w3c标准 WOrld Wide Web Consortium(万维网联盟) 成立于1994年&#xff0c;Web技术领域最权威和具影响力的国际中…

训练测试数据大小不一致_三步学会训练狗狗不随地大小便

训练狗狗在规定的地点大小便是非常重要的训练&#xff0c;它决定了你的屋子和院子能否干净整洁。如果是室内训练&#xff0c;我要先告诉你一些相关的训练禁忌。首先&#xff0c;当狗狗在家里排便之后才对狗狗做出惩罚&#xff0c;是最普遍的一个训练错误&#xff0c;这只会使问…

使用实体框架、Dapper和Chain的仓储模式实现策略

关键要点&#xff1a; Dapper这类微ORM&#xff08;Micro-ORM&#xff09;虽然提供了最好的性能&#xff0c;但也需要去做最多的工作。在无需复杂对象图时&#xff0c;Chain这类Fluent ORM更易于使用。对实体框架&#xff08;Entity Framework&#xff09;做大量的工作后&#…

JVM-对象的存活与死亡

转载自 JVM-对象的存活与死亡 当Java虚拟机进行垃圾收集的时候&#xff0c;那么它必须要先判断对象&#xff0c;是否还存活&#xff0c;如果存活就不能对它进行回收。所以判断一个对象是否存活是Java虚拟机必须要实现的。1.对象是否存活  1&#xff09;引用计数器&#xff1…

SpringBoot+MyBatis搭建迷你小程序

简介&#xff1a;用Spring Boot框架大大简化了新Spring应用的初始搭建以及开发过程&#xff0c;在开发人员中越来越受到欢迎。微信小程序作为目前炙手可热的应用&#xff0c;很有可能在未来占据轻应用的市场。本门课程的主要目的是将两者结合起来&#xff0c;同时希望作为入门翔…

蓝桥杯JAVA省赛2013-----B------3(振兴中华)

【解析】&#xff1a;将格子中的字存放到一个二维数组中&#xff0c;使用回溯法依次进行遍历&#xff0c; 符合条件的1&#xff0c;最后求出总和 【答案】&#xff1a;35 从我做起振 (0, 0) (0, 1) (0, 2) (0, 3) (0, 4) 我做起振兴 (1, 0) (1, 1) (1, 2) (1, 3) (1, 4) 做起…

python变量后面加星号_Python基础找茬系列20--python函数的秘密

一、小试牛刀二、函数的定义def 函数名(参数列表): 函数体【1】函数的关键词&#xff1a;是def&#xff0c;不是del&#xff0c;也不是function【2】函数的名称&#xff1a;不能使用关键词作为函数的名称&#xff0c;允许使用内置函数名作为函数名称&#xff0c;这会覆盖内置函…

基于Quartz.net 的开源任务管理平台

最近&#xff0c;又重新整理&#xff0c;开发出了一套基于Quartz.net 的任务管理平台。将Quartz.net 的任务调度&#xff0c;管理等功能统一整合&#xff0c;形成了一套比较完整的任务调度平台。主要是&#xff1a;任务调度服务&#xff0c;后台任务管理 等功能。 github地址&a…

Java中的垃圾回收与对象生命周期

转载自 Java中的垃圾回收与对象生命周期1. 垃圾回收 垃圾回收是Java程序设计中内存管理的核心概念&#xff0c;JVM的内存管理机制被称为垃圾回收机制。 一个对象创建后被放置在JVM的堆内存中&#xff0c;当永远不再引用这个对象时&#xff0c;它将被JVM在堆内存中回收。…

页面复杂对象传递参数 开发中遇到的问题

左边是我发送的数据 转换成右边的就可以接受到数据了 我发送的数据接收回来 这样发送 服务器就可以接收到数据了

hibernate正向生成数据库表以及配置——hibernate.cfg.xml

<?xml version1.0 encodingUTF-8?> <!DOCTYPE hibernate-configuration PUBLIC"-//Hibernate/Hibernate Configuration DTD 3.0//EN""http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"><!-- Generated by MyEclipse H…

蓝桥杯JAVA省赛2013-----B------4(黄金连分数)

【答案】&#xff1a;0.6180339887498948482045868343656381177203091798057628621354486227052604628189024497072072041893911375 识别问题 --> 斐波那契的第n项与第n1项的比值n要多少才够 --> 精度处理大整数、大浮点数 &#xff1a;add()-&#xff1a;subtract()*&…

.NET仓储模式高级用例

主要结论 如果需要执行基本CURD之外的其他操作&#xff0c;此时就有必要使用仓储&#xff08;Repository&#xff09;。为了促进测试工作并改善可靠性&#xff0c;应将仓储视作可重复使用的库&#xff08;Library&#xff09;。将安全和审计功能放入仓储中可减少Bug并简化应用程…

可视化大屏设计尺寸_可视化大屏设计_酷炫不是最高效的大屏展示的唯一标准...

目前市面上有众多做大屏的可视化BI工具&#xff0c;有的部分企业为了要实现其功能效果而令人感到枯燥乏味&#xff0c;或者是为了看上去绚丽多彩而显得极端复杂&#xff0c;从而实现对于相当复杂而又冗余数据的深入分析&#xff0c;让企业决策者难以理解数据的价值。这也导致了…

Java的GC机制及算法

转载自 Java的GC机制及算法 GC的阶段 对每个对象而言&#xff0c;垃圾回收分为两个阶段&#xff1a;finalization和reclamation。 finalization: 指运行这个对象的finalize的方法。reclamation: 回收被这个对象使用的内存。 GC的过程的基本步骤首先确认对象是不可达的&#…