使用 profiling 工具查找 java 代码中 outOfMemoryError 的确切原因

Posted

技术标签:

【中文标题】使用 profiling 工具查找 java 代码中 outOfMemoryError 的确切原因【英文标题】:Using profiling tool to find the exact reason for outOfMemoryError in the java code 【发布时间】:2013-12-25 04:59:46 【问题描述】:

您好,我有一个使用 JDBC 同时访问远程和本地数据库的 java 程序。

但我得到了这个例外: ->线程“Thread-1964”java.lang.OutOfMemoryError 中的异常:Java 堆空间

现在我想使用任何分析工具,通过它我可以在我的代码中得到这个异常的确切原因。

这是我的java程序

public class DBTestCases

    Connection localConnection; 
    Connection remoteConnection;
    Connection localCon;
    Connection remoteCon;
    List<Connection> connectionsList;
    String driver = "com.mysql.jdbc.Driver";
    String user = "root";
    String password = "root";
    String dbName = "myDB";
    String connectionUrl1= "jdbc:mysql://11.232.33:3306/"+dbName+"?user="+user+"&password="+password+"&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false&maxReconnects=10";
    String connectionUrl2= "jdbc:mysql://localhost:3306/"+dbName+"?user="+user+"&password="+password+"&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false&maxReconnects=10";

    public List<Connection> createConnection() 

        try 
                    Class.forName(driver);
                    localCon = DriverManager.getConnection(connectionUrl2);
                    if(localCon != null)
                        System.out.println("connected to remote database at : "+new Date());
                    remoteCon = DriverManager.getConnection(connectionUrl1);
                    if(remoteCon != null)
                        System.out.println("connected to local database at : "+new Date());
                    connectionsList = new ArrayList<Connection>( 2 );
                    connectionsList.add( 0 , localCon );
                    connectionsList.add( 1 , remoteCon );
                 catch(ClassNotFoundException cnfe) 
                    cnfe.printStackTrace();
                     catch(SQLException sqle) 
                        sqle.printStackTrace();
                        
        return connectionsList;
    

    public void insert()

        Runnable runnable = new Runnable()
        public void run() 
        PreparedStatement ps1 = null;
        PreparedStatement ps2 = null;
        String sql = "insert into user1(name, address, created_date)" +
                                " values('johnsan', 'usa', '2013-08-04')";
        if(remoteConnection != null&&localConnection != null) 
            System.out.println("Database Connection Is Established");
            try 
                        ps1 = remoteConnection.prepareStatement(sql);
                        ps2 = localConnection.prepareStatement(sql);
                        int i = ps1.executeUpdate();
                        int k = ps2.executeUpdate();
                        if(i > 0) 
                            System.out.println("Data Inserted into remote database table Successfully");
                        
                        if(k > 0) 
                            System.out.println("Data Inserted into local database table Successfully");
                        
                     catch (SQLException s) 
                            System.out.println("SQL code does not execute.");
                            s.printStackTrace();
                        catch (Exception e) 
                            e.printStackTrace();
                        
            
            System.out.println("Inserting values in db");
        
        ;
        Thread thread = new Thread(runnable);
        thread.start();
    

    public void retrieve()

        Runnable runnable = new Runnable()
        public void run() 
        try 
                    Statement st1 = localConnection.createStatement();
                    Statement st2 = remoteConnection.createStatement();
                    ResultSet res1 = st1.executeQuery("SELECT * FROM  user1");
                    ResultSet res2 = st2.executeQuery("SELECT * FROM  user1");
                    System.out.println("---------------------------Local Database------------------------");
                    while (res1.next()) 
                        Long i = res1.getLong("userId");
                        String s1 = res1.getString("name");
                        String s2 = res1.getString("address");
                        java.sql.Date d = res1.getDate("created_date");
                        System.out.println(i + "\t\t" + s1 + "\t\t" + s2 + "\t\t"+ d);
                    
                    System.out.println("------------------------Remote Database---------------------");
                    while (res2.next()) 
                        Long i = res2.getLong("userId");
                        String s1 = res2.getString("name");
                        String s2 = res2.getString("address");
                        java.sql.Date d = res2.getDate("created_date");
                        System.out.println(i + "\t\t" + s1 + "\t\t" + s2 + "\t\t"+ d);
                    
         catch (SQLException s) 
                System.out.println("SQL code does not execute.");
                s.printStackTrace();
        catch (Exception e) 
                e.printStackTrace();
        
        
        ;
        Thread thread = new Thread(runnable);
        thread.start();
    

    public static void main(String[] args) 
        DBTestCases dbTestCases = new DBTestCases();
        List l = dbTestCases.createConnection();
        dbTestCases.localConnection = (Connection)l.get(0);
        dbTestCases.remoteConnection = (Connection)l.get(1);
        for(;;) 
            dbTestCases.insert();
            dbTestCases.countRows();
            dbTestCases.retrieve();
        
    

请任何人帮助我,这是最好的工具,我必须如何使用它,以及任何链接。我使用的是 linux 操作系统。

我想我正在为每个方法调用启动一个新线程,任何人都可以建议如何在再次启动之前关闭线程或使用线程池..

提前谢谢你。

【问题讨论】:

您的 USER1 表中有多少数据? 【参考方案1】:

同样的问题曾经发生在我身上。问题是你的java保留堆内存。此堆内存可通过其中一个 java 配置文件进行配置;它是最小和最大数量。

【讨论】:

你能告诉我你是怎么解决的吗,我也想用任何分析工具,你可以随便看看。 -Xms 设置初始 Java 堆大小 -Xmx 设置最大 Java 堆大小 -Xss 设置 Java 线程堆栈大小 java -Xms16m -Xmx64m ClassName 您应该在运行应用程序时放置所有这些配置。就像例子一样。【参考方案2】:

有很多分析器。 我使用 Visual VM 已经有一段时间了,至少这对我很有用。它易于使用且功能强大。 这是链接: http://visualvm.java.net

不确定您使用的是哪个 IDE。但是与 OutOfMemoryError 无关,这意味着它是一个 RuntimeException。

【讨论】:

我没有使用任何 IDE 我在 Linux 操作系统的 bluefish 编辑器中编写代码【参考方案3】:

有点离题(因为我不会建议你使用分析器),但我认为问题来自这些行

ps1 = remoteConnection.prepareStatement(sql);
ps2 = localConnection.prepareStatement(sql);
int i = ps1.executeUpdate();
int k = ps2.executeUpdate();

Statement st1 = localConnection.createStatement();
Statement st2 = remoteConnection.createStatement();
ResultSet res1 = st1.executeQuery("SELECT * FROM  user1");
ResultSet res2 = st2.executeQuery("SELECT * FROM  user1");

创建 PreparedStatement 并使用它后,应将其关闭。当您不再需要它时,您应该使用 close() 方法,或者使用 try-with-resources (它自动使用 close() )。 Java Tutorial on statements 和 try-with-resources。还可以考虑阅读this 和this 主题。

A 会像这样重写您的代码(实际上并没有尝试过,但它可以编译并且应该可以消除内存泄漏的问题):

public class DBTestCases 

    Connection localConnection;
    Connection remoteConnection;
    Connection localCon;
    Connection remoteCon;
    List<Connection> connectionsList;
    String driver = "com.mysql.jdbc.Driver";
    String user = "root";
    String password = "root";
    String dbName = "myDB";
    String connectionUrl1= "jdbc:mysql://11.232.33:3306/"+dbName+"?user="+user+"&password="+password+"&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false&maxReconnects=10";
    String connectionUrl2= "jdbc:mysql://localhost:3306/"+dbName+"?user="+user+"&password="+password+"&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false&maxReconnects=10";

    public List<Connection> createConnection() 

        try 
            Class.forName(driver);
            localCon = DriverManager.getConnection(connectionUrl2);
            if(localCon != null)
                System.out.println("connected to remote database at : "+new Date());
            remoteCon = DriverManager.getConnection(connectionUrl1);
            if(remoteCon != null)
                System.out.println("connected to local database at : "+new Date());
            connectionsList = new ArrayList<Connection>( 2 );
            connectionsList.add( 0 , localCon );
            connectionsList.add( 1 , remoteCon );
         catch(ClassNotFoundException cnfe) 
            cnfe.printStackTrace();
         catch(SQLException sqle) 
            sqle.printStackTrace();
        
        return connectionsList;
    

    public void insert()

        Runnable runnable = new Runnable()
            public void run() 
                PreparedStatement ps1 = null;
                PreparedStatement ps2 = null;
                String sql = "insert into user1(name, address, created_date)" +
                        " values('johnsan', 'usa', '2013-08-04')";
                if(remoteConnection != null&&localConnection != null) 
                    System.out.println("Database Connection Is Established");
                    try 
                        ps1 = remoteConnection.prepareStatement(sql);
                        ps2 = localConnection.prepareStatement(sql);
                        int i = ps1.executeUpdate();
                        int k = ps2.executeUpdate();
                        if(i > 0) 
                            System.out.println("Data Inserted into remote database table Successfully");
                        
                        if(k > 0) 
                            System.out.println("Data Inserted into local database table Successfully");
                        
                     catch (SQLException s) 
                        System.out.println("SQL code does not execute.");
                        s.printStackTrace();
                    catch (Exception e) 
                        e.printStackTrace();
                     finally 
                        if (ps1 != null) 
                            try 
                                ps1.close();
                             catch (SQLException ex) 
                                System.out.println("Cannot close ps1 statement.");
                            
                        

                        if (ps2 != null) 
                            try 
                                ps2.close();
                             catch (SQLException ex) 
                                System.out.println("Cannot close ps2 statement.");
                            
                        
                    
                
                System.out.println("Inserting values in db");
            
        ;
        Thread thread = new Thread(runnable);
        thread.start();
    

    public void retrieve()

        Runnable runnable = new Runnable()
            public void run() 
                Statement st1 = null;
                Statement st2 = null;
                ResultSet res1 = null;
                ResultSet res2 = null;
                try 
                    st1 = localConnection.createStatement();
                    st2 = remoteConnection.createStatement();
                    res1 = st1.executeQuery("SELECT * FROM  user1");
                    res2 = st2.executeQuery("SELECT * FROM  user1");
                    System.out.println("---------------------------Local Database------------------------");
                    while (res1.next()) 
                        Long i = res1.getLong("userId");
                        String s1 = res1.getString("name");
                        String s2 = res1.getString("address");
                        java.sql.Date d = res1.getDate("created_date");
                        System.out.println(i + "\t\t" + s1 + "\t\t" + s2 + "\t\t"+ d);
                    
                    System.out.println("------------------------Remote Database---------------------");
                    while (res2.next()) 
                        Long i = res2.getLong("userId");
                        String s1 = res2.getString("name");
                        String s2 = res2.getString("address");
                        java.sql.Date d = res2.getDate("created_date");
                        System.out.println(i + "\t\t" + s1 + "\t\t" + s2 + "\t\t"+ d);
                    
                 catch (SQLException s) 
                    System.out.println("SQL code does not execute.");
                    s.printStackTrace();
                catch (Exception e) 
                    e.printStackTrace();
                 finally 
                    if (res1 != null) 
                        try 
                            res1.close();
                         catch (SQLException ex) 
                            System.out.println("Cannot close res1 result set.");
                        
                    

                    if (st1 != null) 
                        try 
                            st1.close();
                         catch (SQLException ex) 
                            System.out.println("Cannot close st1 statement.");
                            ex.printStackTrace();
                        
                    

                    if (res2 != null) 
                        try 
                            res2.close();
                         catch (SQLException ex) 
                            System.out.println("Cannot close res2 result set.");
                        
                    

                    if (st2 != null) 
                        try 
                            st2.close();
                         catch (SQLException ex) 
                            System.out.println("Cannot close st2 statement.");
                            ex.printStackTrace();
                        
                    
                
            
        ;
        Thread thread = new Thread(runnable);
        thread.start();
    

    public static void main(String[] args) 
        DBTestCases dbTestCases = new DBTestCases();
        List l = dbTestCases.createConnection();
        dbTestCases.localConnection = (Connection)l.get(0);
        dbTestCases.remoteConnection = (Connection)l.get(1);
        for(;;) 
            dbTestCases.insert();
            dbTestCases.countRows();
            dbTestCases.retrieve();
        
    

这些陈述还有另一个问题:

ResultSet res1 = st1.executeQuery("SELECT * FROM  user1");
ResultSet res2 = st2.executeQuery("SELECT * FROM  user1");

如果不使用WHERE 中的条件,我绝不应该(除了极少数例外)读取整个表格。此外,如果您只需要一列created_date,那么您必须像这样重写它:

ResultSet res1 = st1.executeQuery("SELECT created_date FROM  user1");
ResultSet res2 = st2.executeQuery("SELECT created_date FROM  user1");

但我实际上并没有在完​​整列表中替换它,因为它可能只是“快速而肮脏”的代码。 :)

【讨论】:

但我尝试关闭连接,因为我无限地调用它的显示在关闭连接后无法使用。 如果我最终关闭,则表明异常已经关闭,关闭连接后无法进行任何操作.. 但是您每次运行方法insert()retrieve() 时都会创建这些对象(PreparedStatement、Statement 和ResultSet)。我不会关闭您代码中的连接,仅关闭使用您的连接的语句和结果集。 你能告诉我如果我们不关闭你列出的这些对象和连接会发生什么。 垃圾收集器不会收集这些对象。因此,一段时间后,Java 虚拟机的内存将满,您将收到java.lang.OutOfMemoryError 错误。这不会立即发生,Java Heap 需要一些时间才能填满。但是,如果您继续创建这些对象而不关闭它们,就会发生这种情况。据我所知,它发生在您的 PC 上创建第 1964 个线程时(数量可能会不时变化)。我不建议关闭连接。在您的情况下,最好重用它们(我什至会重用 PreparedStatements,但您必须明智地这样做)。

以上是关于使用 profiling 工具查找 java 代码中 outOfMemoryError 的确切原因的主要内容,如果未能解决你的问题,请参考以下文章

React Profiler-React性能审查工具

如何使用gprof对软件做profiling

Java 的 Profiler [几乎] 不会减慢分析代码的速度

怎么查找linux上jdk安装目录

AndroidStudio通过Profiler查找内存泄漏

AndroidStudio通过Profiler查找内存泄漏