Java 大体积目标的优化

Java 大体积目标的优化,java,performance,optimization,Java,Performance,Optimization,我有一个从Oracle DB获取结果集的过程,然后使用返回的数据创建对象,然后将对象添加到映射,最后将映射加载到内存中的数据网格中。我的最大结果集可能有3000万条记录。我注意到,在处理了大约1400万条记录后,应用程序的速度明显减慢。我有-Xmx=12G和-Xms=512M。我的应用程序部署在WebLogic版本12.2.0.1和Java版本8_66上。我还注意到托管服务器产生的以下消息: JVM暂停可能太长: 我试图弄清楚如何优化代码或JVM配置。这是密码 MyObj myObj =

我有一个从Oracle DB获取结果集的过程,然后使用返回的数据创建对象,然后将对象添加到映射,最后将映射加载到内存中的数据网格中。我的最大结果集可能有3000万条记录。我注意到,在处理了大约1400万条记录后,应用程序的速度明显减慢。我有-Xmx=12G和-Xms=512M。我的应用程序部署在WebLogic版本12.2.0.1和Java版本8_66上。我还注意到托管服务器产生的以下消息:

  • JVM暂停可能太长:
  • 我试图弄清楚如何优化代码或JVM配置。这是密码

        MyObj myObj = null;
    
        while (rs.next()) {
            myObj = new Balance(rs.getString("Field1"), rs.getString("Field2"), rs.getString("Field3"),
                                       ....
            rs.getString("Field17"), rs.getString("Field18"), rs.getString("Field19"));
    
            Map<String, Account> myMap = new HashMap<>();
            myMap.put(rs.getString("FieldA"), new Account(rs.getString("FieldA"), rs.getDouble("FieldC"),
                    rs.getString("FieldD"), Boolean.FALSE, Boolean.FALSE));
            myObj.setAccounts(myMap);
    
            myKey = myObj.getKey();
    
            existingObject = cacheMap.get(myKey);
    
            if (existingObject != null) {
                myObj = myObj.merge(existingObject);
            }
    
            cacheMap.put(myKey, myObj);
            recCount++;
    
            if (recCount % 250000 == 0) {
                logger.info("Processed " + recCount + " records.");
            }
        }
    
    MyObj MyObj=null;
    while(rs.next()){
    myObj=新余额(rs.getString(“字段1”)、rs.getString(“字段2”)、rs.getString(“字段3”),
    ....
    rs.getString(“Field17”)、rs.getString(“Field18”)、rs.getString(“Field19”);
    Map myMap=newhashmap();
    myMap.put(rs.getString(“FieldA”)、新账户(rs.getString(“FieldA”)、rs.getDouble(“FieldC”),
    rs.getString(“FieldD”)、Boolean.FALSE、Boolean.FALSE);
    myObj.setAccounts(myMap);
    myKey=myObj.getKey();
    existingObject=cacheMap.get(myKey);
    if(existingObject!=null){
    myObj=myObj.merge(现有对象);
    }
    cacheMap.put(myKey,myObj);
    recCount++;
    如果(记录%250000==0){
    logger.info(“已处理”+recCount+“记录”);
    }
    }
    
    您可以使用分页并分块检索数据集,然后您的程序可以在检索下一个数据块时开始处理当前数据块等等。以这种方式检索结果集所需的时间将减少。这里有一篇关于mySql分页的好文章

    选项:1

    如果您的某些字段值将与
    字符串
    相同,则在创建
    余额
    帐户
    时,请执行
    字符串.intern()
    。如果存在冗余值,这将给内存足迹带来巨大的好处

    在我们的一个数据为2个LAC的应用程序中,我们看到
    String.intern()
    之后有50%的好处

    代码可能看起来有点难看。但是,通过一些实用的方法,你可以减少丑陋

    代码在这里

    String str(ResultSet rs, String fieldName)
    {
        String s = rs.getString(fieldName);
        return s == null ? null : s.intern();
    }
    
    void somemethod()
    {
    ...
    ...
    
        while (rs.next()) {
            myObj = new Balance(str(rs, "Field1"), str(rs, "Field2"), str(rs, "Field3"), str(rs, "Field17"), str(rs, "Field18"), str(rs, "Field19"));
    
            Map<String, Account> myMap = new HashMap<>();
            myMap.put(str(rs, "FieldA"), new Account(str(rs, "FieldA"), str(rs, "FieldC"), str(rs, "FieldD"), Boolean.FALSE, Boolean.FALSE));
            myObj.setAccounts(myMap);
    
    ...
    ...
        }
    ...
    }
    
    String str(结果集rs,字符串字段名)
    {
    字符串s=rs.getString(字段名);
    返回s==null?null:s.intern();
    }
    void方法()
    {
    ...
    ...
    while(rs.next()){
    myObj=新余额(str(rs,“字段1”)、str(rs,“字段2”)、str(rs,“字段3”)、str(rs,“字段17”)、str(rs,“字段18”)、str(rs,“字段19”);
    Map myMap=newhashmap();
    myMap.put(str(rs,“FieldA”)、新帐户(str(rs,“FieldA”)、str(rs,“FieldC”)、str(rs,“FieldD”)、Boolean.FALSE、Boolean.FALSE);
    myObj.setAccounts(myMap);
    ...
    ...
    }
    ...
    }
    
    选项:2

    这真是一个节省内存的选项

    如果使用Java 8 Update 20或更高版本,可以通过启用标志
    XX:+UseStringDuplication
    为整个JVM启用此行为。如果您使用
    G1
    GC,这将起作用

    一些裁判。您可以搜索
    XX:+UseStringDuplication
    以获取更多参考

    选项:3


    您真的可以考虑在
    merge()
    操作中执行的操作,以查看是否有任何功能更改来降低内存需求…

    是否有特定的原因需要在内存中使用如此大的映射?更好的策略是在需要的时候只取你需要的东西。这确实是一个设计问题。似乎12G对于您的程序来说是不够的,所以当垃圾收集器试图释放未使用的对象时,它会卡住,因为大多数对象仍在使用中。在任何情况下,如果重构太晚,您可以重新组织您的循环。我认为你可以改变你的
    merge
    策略。即,首先在缓存映射中检查该对象,然后,如果该对象存在,则在现有映射中添加其他实体,而不是构建ne映射并将其与现有映射合并。我建议使用探查器检查运行时发生的情况。仅通过查看代码,我猜您可能的痛点是:
    rs#next
    myMap#put
    myObjsetAccounts
    myObj#merge
    cacheMap#put
    。我已经在使用Statement.setFetchSize并找到了一个最佳点。这一定是另外一回事了第4节:因为这个原因而切换到Java9。