Я написал приложение (Springboot + Data JPA + Data Rest), которое продолжает выдавать исключение OutOfMemoryException при загрузке приложения. Я могу пропустить этот код, который запускается при запуске приложения, но в дальнейшем может произойти исключение. Вероятно, лучше всего показать вам, что происходит при запуске приложения, потому что это на самом деле очень просто и не должно вызывать каких-либо проблем imho:
@SpringBootApplication
@EnableAsync
@EnableJpaAuditing
public class ScraperApplication {
public static void main(String[] args) {
SpringApplication.run(ScraperApplication.class, args);
}
}
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class DefaultDataLoader {
private final @NonNull LuceneService luceneService;
@Transactional
@EventListener(ApplicationReadyEvent.class)
public void load() {
luceneService.reindexData();
}
}
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class LuceneService {
private static final Log LOG = LogFactory.getLog(LuceneService.class);
private final @NonNull TrainingRepo trainingRepo;
private final @NonNull EntityManager entityManager;
public void reindexData() {
LOG.info("Reindexing triggered");
FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager);
fullTextEntityManager.purgeAll(Training.class);
LOG.info("Index purged");
int page = 0;
int size = 100;
boolean morePages = true;
Page<Training> pageData;
while (morePages) {
pageData = trainingRepo.findAll(PageRequest.of(page, size));
LOG.info("Loading page " + (page + 1) + "/" + pageData.getTotalPages());
pageData.getContent().stream().forEach(t -> fullTextEntityManager.index(t));
fullTextEntityManager.flushToIndexes(); // flush regularly to keep memory footprint low
morePages = pageData.getTotalPages() > ++page;
}
fullTextEntityManager.flushToIndexes();
LOG.info("Index flushed");
}
}
Вы можете видеть, что я делаю, очистить указатель, прочитать все тренинги из TrainingRepo в постраничном виде (100 за один раз) и записать их в индекс. Не так много на самом деле происходит. Через несколько минут после сообщения «Индекс очищен» я получаю следующее:
java.lang.OutOfMemoryError: Java heap space
В журналах я вижу сообщение «Индекс очищен», но никогда не вижу сообщения «Загрузка страницы ...». поэтому он должен застрять в вызове findAll ().
Я заставил JVM записать дамп кучи, загрузил его в Eclipse Memory Analyzer и получил полную трассировку стека: https://gist.github.com/mathias-ewald/2fddb9762427374bb04d332bd0b6b499
Я также немного просмотрел отчет, но мне нужна помощь в интерпретации этой информации, поэтому я приложил несколько скриншотов из Eclipse Memory Analyzer.
РЕДАКТИРОВАТЬ:
Я только что включил «show- sql» и увидел это до того, как все зависло:
Hibernate: select training0_.id as id1_9_, training0_.created_date as created_2_9_, training0_.description as descript3_9_, training0_.duration_days as duration4_9_, training0_.execution_id as executi14_9_, training0_.level as level5_9_, training0_.modified_date as modified6_9_, training0_.name as name7_9_, training0_.price as price8_9_, training0_.product as product9_9_, training0_.quality as quality10_9_, training0_.raw as raw11_9_, training0_.url as url12_9_, training0_.vendor as vendor13_9_ from training training0_ where not (exists (select 1 from training training1_ where training0_.url=training1_.url and training0_.created_date<training1_.created_date)) limit ?
Hibernate: select execution0_.id as id1_1_0_, execution0_.created_date as created_2_1_0_, execution0_.duration_millis as duration3_1_0_, execution0_.message as message4_1_0_, execution0_.modified_date as modified5_1_0_, execution0_.scraper as scraper6_1_0_, execution0_.stats_id as stats_id8_1_0_, execution0_.status as status7_1_0_, properties1_.execution_id as executio1_2_1_, properties1_.properties as properti2_2_1_, properties1_.properties_key as properti3_1_, stats2_.id as id1_5_2_, stats2_.avg_quality as avg_qual2_5_2_, stats2_.max_quality as max_qual3_5_2_, stats2_.min_quality as min_qual4_5_2_, stats2_.null_products as null_pro5_5_2_, stats2_.null_vendors as null_ven6_5_2_, stats2_.products as products7_5_2_, stats2_.tags as tags8_5_2_, stats2_.trainings as training9_5_2_, stats2_.vendors as vendors10_5_2_, producthis3_.stats_id as stats_id1_6_3_, producthis3_.product_histogram as product_2_6_3_, producthis3_.product_histogram_key as product_3_3_, taghistogr4_.stats_id as stats_id1_7_4_, taghistogr4_.tag_histogram as tag_hist2_7_4_, taghistogr4_.tag_histogram_key as tag_hist3_4_, vendorhist5_.stats_id as stats_id1_8_5_, vendorhist5_.vendor_histogram as vendor_h2_8_5_, vendorhist5_.vendor_histogram_key as vendor_h3_5_ from execution execution0_ left outer join execution_properties properties1_ on execution0_.id=properties1_.execution_id left outer join stats stats2_ on execution0_.stats_id=stats2_.id left outer join stats_product_histogram producthis3_ on stats2_.id=producthis3_.stats_id left outer join stats_tag_histogram taghistogr4_ on stats2_.id=taghistogr4_.stats_id left outer join stats_vendor_histogram vendorhist5_ on stats2_.id=vendorhist5_.stats_id where execution0_.id=?
По-видимому, он создает оператор для извлечения всех объектов Training, но оператор Execution является последним, который ему удается выполнить.
Я изменил отношение с Training на Выполнение от @ManyToOne
до @ManyToOne(fetch = FetchType.LAZY)
и вдруг я код смог загрузить данные в индекс снова. Поэтому я думаю, что что-то может быть не так с моим отображением сущности Execution. Позвольте мне поделиться с вами кодом:
@Entity
@Data
@EntityListeners(AuditingEntityListener.class)
public class Execution {
public enum Status { SCHEDULED, RUNNING, SUCCESS, FAILURE };
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@ToString.Include
private Long id;
@Column(updatable = false)
private String scraper;
@CreatedDate
private LocalDateTime createdDate;
@LastModifiedDate
private LocalDateTime modifiedDate;
@Min(0)
@JsonProperty(access = Access.READ_ONLY)
private Long durationMillis;
@ElementCollection(fetch = FetchType.EAGER)
private Map<String, String> properties;
@NotNull
@Enumerated(EnumType.STRING)
private Status status;
@Column(length = 9999999)
private String message;
@EqualsAndHashCode.Exclude
@OneToOne(cascade = CascadeType.ALL)
private Stats stats;
}
И поскольку это отношение выполнения, здесь также есть сущность Stats:
@Entity
@Data
public class Stats {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@ToString.Include
private Long id;
private Long trainings;
private Long vendors;
private Long products;
private Long tags;
private Long nullVendors;
private Long nullProducts;
private Double minQuality;
private Double avgQuality;
private Double maxQuality;
@ElementCollection(fetch = FetchType.EAGER)
private Map<String, Long> vendorHistogram;
@ElementCollection(fetch = FetchType.EAGER)
private Map<String, Long> productHistogram;
@ElementCollection(fetch = FetchType.EAGER)
private Map<String, Long> tagHistogram;
}