我试图通过一些调查了解Neo4j对象缓存。我对Object cache的第一印象来自此链接中的幻灯片: http://www.slideshare.net/thobe/an-overview-of-neo4j-internals
具体来说,缓存中的节点/关系对象应该看起来像幻灯片9或15/42。为了验证这一点,我使用现有的图形数据库内容编写了一个简单的服我这样做的方法是尝试使用sun.misc.Unsafe查看节点/关系对象的起始虚拟地址。获取虚拟地址的程序来自以下链接: How can I get the memory location of a object in java?
public static long addressOf(Object o) throws Exception {
Object[] array = new Object[] { o };
long baseOffset = unsafe.arrayBaseOffset(Object[].class);
int addressSize = unsafe.addressSize();
long objectAddress;
switch (addressSize) {
case 4:
objectAddress = unsafe.getInt(array, baseOffset);
break;
case 8:
objectAddress = unsafe.getLong(array, baseOffset);
break;
default:
throw new Error("unsupported address size: " + addressSize);
}
return (objectAddress);
}
在neo4j服务器脚本(My main()类)中,我按id获取节点地址,并按以下方式打印出地址:
void checkAddr(){
nodeAddr(0);
nodeAddr(1);
nodeAddr(2);
}
void nodeAddr(int n){
Node oneNode = graphDb.getNodeById(n);
Node[] array1 = {oneNode};
try {
long address = UnsafeUtil.addressOf(array1);
System.out.println("Addess: " + address);
} catch (Exception e) {
e.printStackTrace();
}
}
首先,我尝试使用软缓存提供程序,这是默认情况。为节点对象0,1和2打印的地址是:
地址:4168500044 地址:4168502383 地址:4168502753
因此,使用第二个地址 - 第一个地址和第三个地址 - 第二个地址,我可以准确地知道节点占用了多少空间。在这种情况下,第一个节点对象占用2339B,第二个占用370B。
然后,要查看禁用对象缓存的影响,我使用NoCacheProvider进行设置:
调用setConfig(GraphDatabaseSettings.cache_type,NoCacheProvider.NAME)
打印出来的地址是:
地址:4168488391 地址:4168490708 地址:4168491056
与第一种情况类似地计算的偏移量为:第一个节点对象占用2317B,第二个占用348B。
我的问题出现了:
由于我使用相同的图表并进行只读查询,为什么同一节点对象的大小会发生变化?
当我禁用对象缓存时,为什么地址偏移看起来与存在对象缓存一样?例如,在节点存储文件中,单个节点占用9个字节,而在我的实验中并非如此。如果我获取节点对象的方式有问题,我怎样才能以正确的方式获取虚拟地址?有什么方法我可以具体知道mmap节点文件在内存中的位置吗?
我怎么能准确知道节点对象中存储的内容。当我在这个链接上查看Node.class时: https://github.com/neo4j/neo4j/blob/1.9.8/community/kernel/src/main/java/org/neo4j/graphdb/Node.java 看起来节点对象的外观与演示幻灯片中的相同。而只是节点对象使用的一组函数。还有一个节点对象在无对象缓存和对象缓存场合中一次性整合到内存中?
答案 0 :(得分:3)
Node
对象不是Neo4j存储在“对象缓存”中的对象,因此您不会通过查看这些实例来深入了解Neo4j的缓存。 Neo4j为您提供的Node
实现是一个名为NodeProxy
的类的实例,并且尽可能小(两个字段:内部标识和对数据库的引用)。这些只是用作执行数据库中该节点周围操作的节点的句柄。存储在“对象缓存”中的对象是名为NodeImpl
的类的实例(尽管名称它们没有实现Node
接口)。 NodeImpl
个对象具有在该演示文稿中的第15张幻灯片(幻灯片中的页码9)上勾勒出的形状。好吧,它大致有这种形状,自从我制作这些幻灯片后,Neo4j已经发展了。
Neo4j的发展也改变了节点记录在磁盘上占用的字节数。 Neo4j 2.0及更高版本的节点记录略大于那些幻灯片。如果您有兴趣查看这些记录的布局,您应该查看NodeRecord
类,然后从NodeStore
类和“向下”开始到其依赖项中以查找内存映射。
除了查看错误的对象以查看Neo4j中不同缓存方法之间的差异外,您的测量方法也存在缺陷。比较对象的地址并不能告诉您有关这些对象大小的任何信息。 JVM不保证一个接一个地(按时间)分配的两个对象将相邻地驻留在内存中,即使JVM确实使用了这样的分配策略,Neo4j也可能在两个对象的分配之间分配了多个对象正在比较。然后是垃圾收集器,它可能在您获取一个对象的地址和获取下一个对象的地址之间移动了对象。因此,查看Java中对象的地址几乎从来没有用过任何东西。有关在Java中测量对象大小的更好方法,请查看Java Object Layout utility,或使用Java代理中的Instrumentation.getObjectSize(...)
method。
如上所述回答你的问题:
节点对象的大小没有变化,在运行之间不能保证它们的地址相同。根据我上面的描述,你不能依赖对象地址来计算对象大小。
由于您正在查看NodeProxy
个对象,因此无论Neo4j使用何种缓存策略,它们都会看起来相同。为了查看NodeImpl
对象,你需要深入挖掘Neo4j的内部结构。由于看起来您正在使用Neo4j 1.9,因此您将GraphDatabaseService
实例转换为GraphDatabaseAPI
(实现内部的接口),然后在该对象上调用getNodeManager()
方法。从NodeManager
您可以致电getNodeIfCached( node.getId() )
获取NodeImpl
个对象。请注意,此API在Neo4j版本之间不兼容,并且使用它是“保修无效,如果密封破坏”的情况之一......
请查看NodeImpl
的源代码。至于数据何时以及如何进入缓存,Neo4j试图对此懒惰,只加载您使用的数据。如果要获取节点的关系,那么它们将被加载到缓存中,如果要获取属性,则会将这些属性加载到缓存中。如果只获得关系,则永远不会加载属性,反之亦然。