我有一张桌子" class"这与表格#34;学生"和#34;老师"。 A"班级"通过foriegn关键关系链接到多个学生和教师。
当我使用hibernate关联并获取大量实体(尝试5000)时,我发现它占用的内存比使用外键占位符的内存多4倍。 在休眠关联中有什么问题吗?
我可以使用任何内存分析器来找出使用过多内存的内容吗?
这是架构的方式:
class(id,className)
student(id,studentName,class_id)
teacher(id,teacherName,class_id)
class_id is foreign key..
案例#1 - Hibernate协会
1)在Class Entity中,将学生和教师映射为:
@Entity
@Table(name="class")
public class Class {
private Integer id;
private String className;
private Set<Student> students = new HashSet<Student>();
private Set<Teacher> teachers = new HashSet<Teacher>();
@OneToMany(fetch = FetchType.EAGER, mappedBy = "classRef")
@Cascade({ CascadeType.ALL })
@Fetch(FetchMode.SELECT)
@BatchSize(size=500)
public Set<Student> getStudents() {
return students;
}
2)在学生和老师中,将班级映射为:
@Entity
@Table(name="student")
public class Student {
private Integer id;
private String studentName;
private Class classRef;
@ManyToOne
@JoinColumn(name = "class_id")
public Class getClassRef() {
return classRef;
}
使用的查询:
sessionFactory.openSession().createQuery("from Class where id<5000");
然而,这需要大量的记忆。
案例#2-删除关联并单独获取
1)类实体中没有映射
@Entity
@Table(name="class")
public class Class {
private Integer id;
private String className;
2)只有学生,教师的外键占位符
@Entity
@Table(name="student")
public class Student {
private Integer id;
private String studentName;
private Integer class_id;
使用的查询:
sessionFactory.openSession().createQuery("from Class where id<5000");
sessionFactory.openSession().createQuery("from Student where class_id = :classId");
sessionFactory.openSession().createQuery("from Teacher where class_id = :classId");
注意 - 仅显示imp。部分代码。我正在通过JAMM库测量获取实体的内存使用情况。
我还尝试在下面的情况#1中将查询标记为readOnly,这不会极大地提高内存使用率;只是一点点。所以这不是解决方案。
Query query = sessionFactory.openSession().
createQuery("from Class where id<5000");
query.setReadOnly(true);
List<Class> classList = query.list();
sessionFactory.getCurrentSession().close();
以下是按大小排序的heapdump快照。看起来像hibernate维护的实体正在创建问题..
答案 0 :(得分:7)
您正在使用以下注释进行EAGER提取。即使您没有访问getStudents(),也可以获取所有学生。让它变得懒惰,只在需要时才会获取。
这
@OneToMany(fetch = FetchType.EAGER, mappedBy = "classRef")
要
@OneToMany(fetch = FetchType.LAZY, mappedBy = "classRef")
答案 1 :(得分:3)
当Hibernate加载包含Class
关系的OneToMany
实体时,它会用自己的自定义版本替换这些集合。对于Set
,它使用PersistentSet
。从grepcode可以看出,这个PersistentSet
对象包含很多东西,其中大部分都是从AbstractPersistentCollection
继承的,以帮助Hibernate管理和跟踪事物,特别是脏检查。
除其他外,PersistentSet
包含对会话的引用,用于跟踪其是否已初始化的布尔值,排队操作列表,对拥有的Class
对象的引用它,一个描述其角色的字符串(不知道究竟是什么,只是通过这里的变量名称),会话工厂的字符串uuid等等。该批次中最大的内存占用可能是该组未修改状态的快照,我希望它本身可以大约加倍内存消耗。
这里没有任何问题,Hibernate只是做得比你意识到的更多,并且以更复杂的方式。除非你内存严重不足,否则它不应该成为一个问题。
顺便提一下,当你保存一个Hibernate以前不知道的新Class
对象时,Hibernate将用新的HashSet
对象替换你创建的简单PersistentSet
对象,存储原始HashSet
包含在其PersistentSet
字段中的set
内。所有Set
操作都将转发到包装HashSet
,同时还会触发PersistentSet
脏跟踪和排队逻辑等。考虑到这一点,您不应该保留并使用任何外部引用来自保存之前的Set
,而应该获取对Hibernate的PersistentSet
实例的新引用,并在需要进行任何更改时使用它(对于集合,而不是对学生或教师在初始保存之后。)
答案 2 :(得分:2)
关于你注意到的巨大内存消耗,一个潜在的原因是Hibernate Session
必须保持每个entity
的状态,它已经加载了EntityEntry
对象的形式,即一个额外的对象,EntityEntry,用于每个加载的entity
。这是在刷新阶段hibernate自动脏检查机制所需要的,以比较实体的当前状态与其原始状态(存储为EntityEntry
的状态)。
请注意,当我们调用EntityEntry
时,此session.load/get/createQuery/createCriteria
与我们在应用程序代码中访问的对象不同。这是休眠内部并存储在第一级缓存中。
引用EntityEntry的javadocs:
我们需要一个条目来告诉我们关于对象当前状态的所有信息 关于其持久状态实现警告:Hibernate 需要实例化此类的大量实例, 因此,我们需要注意它对内存消耗的影响。
一个选项,假设目的只是读取和遍历数据而不对这些实体执行任何更改,您可以考虑使用StatelessSession
而不是Session
。
无状态会话Javadocs引用的优势:
无状态会话也不实现第一级缓存 与任何二级缓存交互,也不实现 事务性后写或自动脏检查
没有自动脏检查,Hibernate不需要为加载EntityEntry
的每个实体创建entity
,就像之前使用Session
的情况一样。这可以减少内存利用率的压力。
说,它确实有自己的一组限制,如StatelessSession javadoc文档中所述。
值得强调的一个限制是,它不会延迟加载集合。如果我们使用StatelessSession
并希望加载关联的collections
,我们应join fetch
使用HQL
或EAGER
使用Criteria
获取。
另一个与second level cache
相关,它与任何二级缓存(如果有)都不会互动。
因此,考虑到它没有任何第一级缓存的开销,您可能需要尝试使用Stateless Session
,看看它是否符合您的要求,并有助于减少内存消耗。 / p>
答案 3 :(得分:0)
是的,您可以使用内存分析器(如visualvm或yourkit)来查看占用大量内存的内存。一种方法是获取堆转储,然后将其加载到其中一个工具中。
但是,您还需要确保将苹果与苹果进行比较。您的问题是#2 sessionFactory.openSession().createQuery("from Student where class_id = :classId");
sessionFactory.openSession().createQuery("from Teacher where class_id = :classId");
仅为一个班级选择学生和老师,而在#1的情况下,您选择的方式更多。您需要改为使用<= :classId
。
此外,每个班级需要一名学生和一名教师记录,这有点奇怪。教师可以教授多个班级,学生可以在多个班级教授。我不知道你要解决的确切问题,但如果学生确实可以参加很多课程而且老师可以教授多个课程,你可能需要以不同的方式设计你的表格。
答案 4 :(得分:0)
尝试@Fetch(FetchMode.JOIN)
,这只生成一个查询而不是多个选择查询。还要查看生成的查询。我更喜欢使用Criteria
而不是HQL
(只是一个想法)。
要进行性能分析,请使用 visualvm 或 jconsole 等免费软件。 yourkit 适用于高级分析,但它不是免费的。我猜它有一个跟踪版本。
您可以使用应用程序的 heapdump 并使用任何内存分析器工具对其进行分析,以检查是否存在任何内存泄漏。
顺便说一句,我不确定当前场景的内存使用情况。
答案 5 :(得分:0)
可能原因是从学生到班级和班级到学生的双向链接。当你获取A类(id 4500)时,Class对象必须是水合的,反过来这必须去拉动所有与这个类相关的Student对象(和教师)。发生这种情况时,每个学生对象都必须保湿。这导致学生参与的每个班级的获取。所以,虽然你只想要A级,但你最终会得到:
获取A类(id 4900) 参考3名学生A,B,C返回A班。 学生A已参考A,B(身份证5500) B级需要保湿 B班参考学生C,D 学生C需要补水 学生C仅参考A类和B类 学生C保湿完成。 学生D需要补水 学生D仅参考B类 学生B补水完成 B级水合作用完成 学生B需要补水(从原班级负荷A级)
等......随着热切的提取,这一直持续到所有链接都水合为止。关键是你可能最终得到了你并不真正想要的内存类。或者其身份不低于5000.
这可能会变得更快。
此外,您应该确保覆盖hashcode和equals方法。否则,您可能会在内存和设备中获得冗余对象。
改进的一种方法是改变LAZY加载,就像其他人提到的那样或打破双向链接。如果您知道每个班级只会访问学生,那么请不要让学生回到课堂。对于学生/班级的例子,有双向链接是有意义的,但也许可以避免。
答案 6 :(得分:0)
正如你所说“我想要”所有“收藏品”。所以懒惰加载无济于事。 你需要每个实体的每个领域吗?在这种情况下,使用投影来获得您想要的位。见when to use Hibernate Projections。 或者考虑使用全脂版本扩展的极简主义的Teacher-Lite和Student-Lite实体。