Bukkit插件:破坏方块写入SQLite触发StackOverflowError问题排查
Bukkit插件BlockBreakEvent触发StackOverflowError的原因及解决方案
错误原因
核心问题是循环实例化与事件监听递归注册:
- 你的
SqliteDB类构造函数中大概率存在实例化EventBlock(事件监听类)或重复注册BlockBreakEvent监听的逻辑。 - 每次玩家破坏方块触发
BlockBreakEvent时,你在事件处理方法里新建SqliteDB实例,而SqliteDB初始化又会触发EventBlock的实例化/事件注册,后续事件触发时又会重复这个流程,形成无限递归调用,最终耗尽栈内存抛出java.lang.StackOverflowError。 - 每次事件都新建数据库连接,会快速消耗栈资源,进一步加剧溢出问题。
解决方案
1. 用单例模式复用SqliteDB实例
不要在事件方法内每次创建SqliteDB对象,而是在插件启动时初始化全局实例,事件中直接调用:
// 插件主类 public class YourPlugin extends JavaPlugin { private static SqliteDB dbInstance; @Override public void onEnable() { // 初始化数据库实例,仅执行一次 dbInstance = new SqliteDB(); // 注册事件监听 getServer().getPluginManager().registerEvents(new EventBlock(), this); } // 提供全局访问方法 public static SqliteDB getDbInstance() { return dbInstance; } @Override public void onDisable() { // 插件关闭时关闭数据库连接 if (dbInstance != null) { dbInstance.close(); } } } // 事件监听类 public class EventBlock implements Listener { @EventHandler public void onBlockBreak(BlockBreakEvent event) { Player player = event.getPlayer(); Location loc = event.getBlock().getLocation(); // 直接调用全局数据库实例执行插入 YourPlugin.getDbInstance().insert(loc, player); } }
2. 修复SqliteDB的循环依赖
检查SqliteDB的构造函数,移除所有实例化EventBlock或注册BlockBreakEvent的代码,彻底切断循环触发的源头。
3. 复用数据库连接
在SqliteDB内部维护持久化的数据库连接,避免每次插入都新建连接:
public class SqliteDB { private Connection conn; public SqliteDB() { try { // 初始化连接,仅执行一次 conn = DriverManager.getConnection("jdbc:sqlite:plugins/YourPlugin/break_log.db"); // 初始化表结构 createTable(); } catch (SQLException e) { e.printStackTrace(); } } private void createTable() { String createSql = "CREATE TABLE IF NOT EXISTS break_log (" + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "player_name TEXT NOT NULL," + "x INTEGER NOT NULL," + "y INTEGER NOT NULL," + "z INTEGER NOT NULL," + "world TEXT NOT NULL)"; try (Statement stmt = conn.createStatement()) { stmt.execute(createSql); } catch (SQLException e) { e.printStackTrace(); } } public void insert(Location loc, Player player) { if (conn == null) return; String insertSql = "INSERT INTO break_log (player_name, x, y, z, world) VALUES (?, ?, ?, ?, ?)"; try (PreparedStatement pstmt = conn.prepareStatement(insertSql)) { pstmt.setString(1, player.getName()); pstmt.setInt(2, loc.getBlockX()); pstmt.setInt(3, loc.getBlockY()); pstmt.setInt(4, loc.getBlockZ()); pstmt.setString(5, loc.getWorld().getName()); pstmt.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } } // 关闭连接方法 public void close() { try { if (conn != null && !conn.isClosed()) { conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } }
内容的提问来源于stack exchange,提问作者Dami 78
相关产品推荐
相关产品推荐

