我有一個使用 Hibernate 的應用程式,它在中等容量資料集(約 300 萬條記錄)中記憶體不足。使用 Eclipse 的 Memory Analyzer 分析記憶體轉儲時,我可以看到StatefulPersistenceContext除了物件本身之外,它似乎還在記憶體中保存了記錄的副本,從而使記憶體使用量增加了一倍。
我能夠使用定義的作業流以稍微小的規模重現它,但無法將其簡化到我可以將完整應用程式放在這里的水平。作業流程是:
- 將~400,000 條記錄 (
Fruit) 從檔案插入資料庫 - 從資料庫中獲取所有的
Fruits,并查找是否有任何互補專案創建~150,000Baskets(包含兩個Fruits) - 檢索所有資料 -
Fruits&Baskets- 并保存到檔案
它在最后階段記憶體不足,堆轉儲顯示記憶體StatefulPersistenceContext中有數十萬個Fruits,此外還有Fruit我們檢索到的保存到檔案中的 s。
我在網上四處看看,建議似乎是QueryHints.READ_ONLY在查詢中使用(我把它放在 上getAll),或者Transaction用readOnly屬性集將它包裝起來——但這些似乎都沒有阻止大量的StatefulPersistenceContext.
還有什么我應該看的嗎?
我正在使用的類/查詢示例:
public interface ShoppingService {
public void createBaskets();
public void loadFromFile(ObjectInput input);
public void saveToFile(ObjectOutput output);
}
@Service
public class ShoppingServiceImpl implements ShoppingService {
@Autowired
private FruitDAO fDAO;
@Autowired
private BasketDAO bDAO;
@Override
public void createBaskets() {
bDAO.add(Basket.generate(fDAO.getAll()));
}
@Override
public void loadFromFile(ObjectInput input) {
SavedState state = ((SavedState) input.readObject());
fDAO.add(state.getFruits());
bDAO.add(state.getBaskets());
}
@Override
public void saveToFile(ObjectOutput output) {
output.writeObject(new SavedState(fDAO.getAll(), bDAO.getAll()));
}
public static void main(String[] args) throws Throwable {
ShoppingService service = null;
try (ObjectInput input = new ObjectInputStream(new FileInputStream("path\\to\\input\\file"))) {
service.loadFromFile(input);
}
service.createBaskets();
try (ObjectOutput output = new ObjectOutputStream(new FileOutputStream("path\\to\\output\\file"))) {
service.saveToFile(output);
}
}
}
@Entity
public class Fruit {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String name;
// ~ 200 string fields
}
public interface FruitDAO {
public void add(Collection<Fruit> elements);
public List<Fruit> getAll();
}
@Repository
public class JPAFruitDAO implements FruitDAO {
@PersistenceContext
private EntityManager em;
@Override
@Transactional()
public void add(Collection<Fruit> elements) {
elements.forEach(em::persist);
}
@Override
public List<Fruit> getAll() {
return em.createQuery("FROM Fruit", Fruit.class).getResultList();
}
}
@Entity
public class Basket {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@OneToOne
@JoinColumn(name = "arow")
private Fruit aRow;
@OneToOne
@JoinColumn(name = "brow")
private Fruit bRow;
public static Collection<Basket> generate(List<Fruit> fruits) {
// Some complicated business logic that does things
return null;
}
}
public interface BasketDAO {
public void add(Collection<Basket> elements);
public List<Basket> getAll();
}
@Repository
public class JPABasketDAO implements BasketDAO {
@PersistenceContext
private EntityManager em;
@Override
@Transactional()
public void add(Collection<Basket> elements) {
elements.forEach(em::persist);
}
@Override
public List<Basket> getAll() {
return em.createQuery("FROM Basket", Basket.class).getResultList();
}
}
public class SavedState {
private Collection<Fruit> fruits;
private Collection<Basket> baskets;
}
uj5u.com熱心網友回復:
在這里看看這個答案...... Hibernate 如何檢測物體物件的臟狀態?
在無法訪問堆轉儲或您的完整代碼的情況下,我相信您所看到的正是您所看到的。只要 hibernate 認為物體有可能發生變化,它就會在記憶體中保留一份完整的副本,以便將物件的當前狀態與最初從資料庫加載時的狀態進行比較。然后在事務結束時(事務代碼塊),它會自動將更改寫入資料庫。為了做到這一點,它需要知道物件過去的狀態,以避免大量(可能代價高昂的)寫操作。
我相信將事務塊設定為只讀是正確的一步。不完全確定,但我希望這里的資訊至少能幫助您理解為什么會看到大量記憶體消耗。
uj5u.com熱心網友回復:
1:一次從DB中獲取所有的Fruits,或者Persisting large set of bucket once會影響DB性能和應用程式性能,因為Heap記憶體中有巨大的物件(young gen Old gen基于Object在堆中存活)。使用批處理而不是一次處理所有資料。使用 spring batch 或實作或自定義邏輯來處理一組塊中的資料。
2:持久化背景關系將新創建和修改的物體存盤在記憶體中。Hibernate 在同步事務時將這些更改發送到資料庫。這通常發生在交易結束時。但是,呼叫 EntityManager.flush() 也會觸發事務同步。其次,持久化背景關系充當物體快取,也稱為一級快取。要清除持久化背景關系中的物體,我們可以呼叫 EntityManager.clear()。
可以從這里獲取 ref 進行批處理。
3.如果您不打算修改 Fruit,您可以只以只讀模式獲取條目:Hibernate 不會保留它通常用于臟檢查機制的脫水狀態。所以,你得到一半的記憶體占用。
uj5u.com熱心網友回復:
快速解決方案:如果您只執行一次此方法以db create增加 jvm-Xmx值。
真正的解決方案:當您嘗試保留所有內容時,它會將所有資料保留在memoryuntilcommit中,并且記憶體很容易消耗,所以與其這樣,不如嘗試像這種轉儲模式那樣保存資料的一部分。例如:
EntityManager em = ...;
for (Fruid fruid : fruids) {
try {
em.getTransaction().begin();
em.persist(fruid);
em.getTransaction().commit();
} finally {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
if (em.isOpen())
em.close();
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/537615.html
標籤:爪哇春天冬眠jpa
下一篇:如何從請求陣列中獲取資料?
