Spring Tool Suite中HikariPool线程未停止致内存泄漏告警求助
Hikari连接池线程未停止告警的解决方案
问题描述
在Spring Tool Suite 4中创建的小型Spring Boot应用运行时出现如下告警:
"Web应用[ROOT]启动了名为[HikariPool-1 housekeeper]的线程但未正确停止,这很可能导致内存泄漏。"
反复检查业务代码未发现错误,代码如下:
业务代码
Bootstrap初始化类
@Component public class BootStrapData implements CommandLineRunner { private final AuthorRepository authorRepository; private final BookRepository bookRepository; private final PublisherRepository publisherRepository; public BootStrapData(AuthorRepository authorRepository, BookRepository bookRepository, PublisherRepository publisherRepository) { this.authorRepository = authorRepository; this.bookRepository = bookRepository; this.publisherRepository = publisherRepository; } @Override public void run(String... args) throws Exception { System.out.println("Started in Bootstrap"); Publisher publisher = new Publisher(); publisher.setName("SFG Publishing"); publisher.setCity("St Petersburg"); publisher.setState("FL"); publisherRepository.save(publisher); System.out.println("Publisher Count: " + publisherRepository.count()); Author eric = new Author("Eric", "Evans"); Book ddd = new Book("Domain Driven Design", "123123"); eric.getBooks().add(ddd); ddd.getAuthors().add(eric); ddd.setPublisher(publisher); publisher.getBooks().add(ddd); authorRepository.save(eric); bookRepository.save(ddd); publisherRepository.save(publisher); Author rod = new Author("Rod", "Johnson"); Book noEJB = new Book("J2EE Development without EJB", "3939459459"); rod.getBooks().add(noEJB); noEJB.getAuthors().add(rod); noEJB.setPublisher(publisher); publisher.getBooks().add(noEJB); authorRepository.save(rod); bookRepository.save(noEJB); publisherRepository.save(publisher); System.out.println("Number of Books: " + bookRepository.count()); System.out.println("Publisher Number of Books: " + publisher.getBooks().size()); } }
实体类
Book实体
@Entity public class Book { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String title; private String isbn; @ManyToOne private Publisher publisher; @ManyToMany @JoinTable(name = "author_book", joinColumns = @JoinColumn(name = "book_id"), inverseJoinColumns = @JoinColumn(name = "author_id")) private Set<Author> authors = new HashSet<>(); public Book() { } public Book(String title, String isbn) { this.title = title; this.isbn = isbn; } public Publisher getPublisher() { return publisher; } public void setPublisher(Publisher publisher) { this.publisher = publisher; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getIsbn() { return isbn; } public void setIsbn(String isbn) { this.isbn = isbn; } public Set<Author> getAuthors() { return authors; } public void setAuthors(Set<Author> authors) { this.authors = authors; } @Override public String toString() { return "Book{" + "id=" + id + ", title='" + title + '\'' + ", isbn='" + isbn + '\'' + ", authors=" + authors + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Book book = (Book) o; return id != null ? id.equals(book.id) : book.id == null; } @Override public int hashCode() { return id != null ? id.hashCode() : 0; } }
Author实体
@Entity public class Author { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String firstName; private String lastName; @ManyToMany(mappedBy = "authors") private Set<Book> books = new HashSet<>(); public Author() { } public Author(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public Set<Book> getBooks() { return books; } public void setBooks(Set<Book> books) { this.books = books; } @Override public String toString() { return "Author{" + "id=" + id + ", firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", books=" + books + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Author author = (Author) o; return id != null ? id.equals(author.id) : author.id == null; } @Override public int hashCode() { return id != null ? id.hashCode() : 0; } }
Publisher实体
@Entity public class Publisher { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; private String addressLine1; private String city; private String state; private String zip; @OneToMany @JoinColumn(name = "publisher_id") private Set<Book> books = new HashSet<>(); public Publisher() { } public Set<Book> getBooks() { return books; } public void setBooks(Set<Book> books) { this.books = books; } @Override public String toString() { return "Publisher{" + "id=" + id + ", name='" + name + '\'' + ", addressLine1='" + addressLine1 + '\'' + ", city='" + city + '\'' + ", state='" + state + '\'' + ", zip='" + zip + '\'' + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Publisher publisher = (Publisher) o; return id != null ? id.equals(publisher.id) : publisher.id == null; } @Override public int hashCode() { return id != null ? id.hashCode() : 0; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAddressLine1() { return addressLine1; } public void setAddressLine1(String addressLine1) { this.addressLine1 = addressLine1; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getState() { return state; } public void setState(String state) { this.state = state; } public String getZip() { return zip; } public void setZip(String zip) { this.zip = zip; }
仓库接口
public interface AuthorRepository extends CrudRepository<Author, Long>{ } public interface BookRepository extends CrudRepository<Book, Long>{ } public interface PublisherRepository extends CrudRepository<Publisher, Long>{ }
解决方案
这个告警本质上是Tomcat在应用关闭时的误报,并非业务代码错误导致,可通过以下方式处理:
配置Hikari连接池优雅关闭
在application.properties中添加:spring.datasource.hikari.shutdown-timeout=5000强制连接池在应用关闭时等待5秒完成线程销毁。
关闭Tomcat后台线程检测
若确认是误报,可关闭Tomcat的后台处理器延迟检测:server.tomcat.background-processor-delay=-1升级Spring Boot版本
旧版本Spring Boot与HikariCP存在生命周期协调问题,升级到2.7.x及以上稳定版可解决该类告警。正常停止应用
在STS中停止应用时,使用标准"Stop"按钮而非强制终止,确保Spring容器触发Bean销毁流程,Hikari连接池会自动关闭后台线程。
内容的提问来源于stack exchange,提问作者Krati Vaishnao
相关产品推荐
相关产品推荐

