У меня приложение Spring Boot, работающее на Wildfly 18.0.1. Основная цель приложения: каждые 5 минут запускать какую-то работу. Поэтому я делаю:
TaskScheduler: инициализировать планировщик
@Autowired
ThreadPoolTaskScheduler taskScheduler;
taskScheduler.scheduleWithFixedDelay(new ScheduledVehicleDataUpdate(), 300000);
ScheduledVehicleDataUpdate: планировщик, который запускает программу обновления
public class ScheduledVehicleDataUpdate implements Runnable {
@Autowired
TaskExecutor taskExecutor;
@Override
public void run() {
try {
CountDownLatch countDownLatch;
List<VehicleEntity> vehicleList = VehicleService.getInstance().getList();
if (vehicleList.size() > 0) {
countDownLatch = new CountDownLatch(vehiclesList.size());
vehicleList.forEach(vehicle -> taskExecutor.execute(new VehicleDataUpdater(vehicle, countDownLatch)));
countDownLatch.await();
}
}
catch (InterruptedException | RuntimeException e) {
System.out.println(e.getMessage())
}
}
}
TaskExecutor:
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(23);
executor.setMaxPoolSize(23);
executor.setQueueCapacity(5000);
executor.setThreadNamePrefix("VehicleService_updater_thread");
executor.initialize();
return executor;
}
VehicleDataUpdater: основной класс обновления
public class VehicleDataUpdater implements Runnable {
private final VehicleEntity vehicle;
private final CountDownLatch countDownLatch;
public VehicleDataUpdater(VehicleEntity vehicle, CountDownLatch countDownLatch) {
this.vehicle = vehicle;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
this.updateVehicleData();
}
catch (Exception e) {
System.out.println(e.getMessage());
}
finally {
countDownLatch.countDown();
}
}
public void updateVehicleData() {
// DO UPDATE ACTIONS;
}
}
Проблема в том, что после fini sh ScheduledVehicleDataUpdate память НЕ очищается. Это выглядит так:
Каждый шаг памяти растет, растет, растет, и в непредсказуемый момент вся память освобождается. И объекты из первой итерации, и объекты из последней итерации. В самом плохом случае это занимает всю доступную память (120 Гб) и происходит сбой Wildfly.
У меня около 3200 записей VehicleEntity (допустим, ровно 3200). Итак, я искал VehicleDataUpdater - сколько объектов в памяти. После первой итерации (когда я только запускал приложение) оно меньше 3200, но не ноль - может быть, около 3000-3100. И каждый шаг растет, но не точно на 3200 записей. Это означает, что некоторые объекты удаляются из памяти, но большинство из них остается там.
Далее: обычная продолжительность итерации составляет около 30 с c - 1 мин. Когда память не очищается и продолжает расти, каждая итерация получает все больше и больше времени: самое длинное, что я видел, было 30 минут. И потоки из пула в основном находятся в состоянии «мониторинга», то есть существуют некоторые блокировки, ожидающие освобождения. Возможно, блокирует от предыдущих итераций, которые не были освобождены - и снова вопрос - почему не была освобождена вся память на предыдущем шаге?
Если я выполняю обновление в одном потоке (без taskExecutor, просто vehicleList.foreach ( транспортное средство -> VehicleDataUpdater (vehicle)); ), чем я не видел увеличения памяти. После обновления очищается каждая память автомобиля.
Я не обнаружил никаких проблем с утечками памяти для ThreadPoolTaskExecutor или ThreadPoolTaskScheduler, поэтому я понятия не имею, как это исправить.
Какие возможные способы не очистка памяти после финишного sh задания планировщика? Как я могу посмотреть, кто блокирует объект после fini sh? Я использую VisualVM 2.0.1 и не нашел там таких возможностей.
РЕДАКТИРОВАТЬ 1:
VehicleService:
public class VehicleService {
private static VehicleService instance = null;
private VehicleDao dao;
public static VehicleService getInstance(){
if (instance == null) {
instance = new VehicleService();
}
return instance;
}
private VehicleService(){}
public void setDao(VehicleDao vehicleDao) { this.dao = vehicleDao; }
public List<VehicleEntity> list() {
return new ArrayList<>(this.dao.list(LocalDateTime.now()));
}
}
VehicleDao:
@Repository
public class VehicleDao {
@PersistenceContext(unitName = "entityManager")
private EntityManager entityManager;
@Transactional("transactionManager")
public List<VehicleRegisterEntity> list(LocalDateTime dtPeriod) {
return this.entityManager.createQuery("SOME_QUERY", VehicleEntity.class).getResultList();
}
}
InitService:
@Service
public class InitHibernateService {
private final VehicleDao vehicleDao;
@Autowired
public InitHibernateService(VehicleDao vehicleDao){
this.vehicleDao = vehicleDao;
}
@PostConstruct
private void setDao() {
VehicleService.getInstance().setDao(this.vehicleDao);
}
}
EntityManager:
@Bean(name = "entityManager")
@DependsOn("dataSource")
public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws NamingException {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setPersistenceProviderClass(HibernatePersistenceProvider.class);
em.setDataSource(dataSource());
em.setPackagesToScan("MY_PACKAGE");
em.setJpaVendorAdapter(vendorAdapter());
em.setJpaProperties(hibernateProperties());
em.setPersistenceUnitName("customEntityManager");
em.setJpaDialect(new CustomHibernateJpaDialect());
return em;
}