Java 8的全垃圾回收和OutOfMemory Java堆空间问题

4
使用VisualVM并检查Tomcat 8.5的catalina.out日志,我发现几乎每次(大约11次中有7次)进行full GC时,日志都会显示OutOfMemory(在完全相同的时间点)。
使用与内存管理相关的Tomcat参数:-Xms3G -Xmx=6G -XX:+UseG1GC -XX:+UseStringDeduplication -XX:MaxHeapFreeRatio=100
起初,我认为这是由于默认的-XX:MaxHeapFreeRatio值(为70),因为我看到最大堆大小(和已使用的堆当然)在full GC期间会显著下降 - 至10-20%左右。但是添加XX:MaxHeapFreeRatio=100并没有解决它。
尽管这是一张使用不同的JVM参数(目前无法获得旧JVM参数的图表)的内存使用情况图表,但在进行完全GC后,内存使用情况迅速增长,最大堆大小相同且该最大堆大小不会下降。

enter image description here

任何想法为什么会发生这种情况?
更新:我忘了提到之前当堆大小没有达到满时,就会发生完整的GC和OutOfMemory-大约5GB。在那时,我从未见过堆达到6GB。

你应该调整那些JVM GC参数:http://blog.sokolenko.me/2014/11/javavm-options-production.html - duffymo
在提出这样的问题之前,请进行一些分析,例如您可以使用YourKit。在您的情况下,可能是由于GC无法跟上在短时间内创建和释放太多对象而导致的。 - tsolakp
3个回答

1
显然,有些创建的对象无法被垃圾回收。您可以尝试使用VisualVM的采样器函数来跟踪创建的实例数量。

你说得对,这就是OOM的意思,但当还剩下约1GB的空闲空间(请参见问题更新)并且同时发生完整GC时,OOM如何发生呢? - user435421
这可能有几个原因。例如,如果permgen空间已满,或者没有可用的连续内存块用于大型数组或对象创建,或者GC所花费的时间太长,数组大小超过了允许的jvm限制等。 - Alex

0

尝试使用MapDB缓存IO操作。

您可以像这样将其缓存到基于磁盘的文件数据库中:

import java.io.File;
import java.io.IOException;
import java.util.Map;
import org.mapdb.DB;
import org.mapdb.DBMaker;

/**
 * Singleton class.
 */
public class DBManager 
{
        /**
     * Variables.
     */
    private static File dbFile = new File("path/to/file");
    private DB db;
    private static final String password = "yourPassword";
    private Map<Integer, String> ctDB;
    private static DBManager manager;

   /**
    * Singleton operations.
    */

  /**
   * Static initializer.
   */
  static
  {
     manager = null;
  }

/**
 * Singleton method @see DBManager.getInstance(File dbFile);
 * @return          ->  An object / instance of this class.
 */
public static DBManager getInstance()
{       
    if(isFileDatabaseOK())
    {
        /**
         * Check if an object/instance from this class exists already. 
         */
        if(manager == null)
        {
            manager = new DBManager();
        }

        /**
         * Return an object/instance of this class.
         */
        return manager;
    }
    else
    {
        return null;
    }
}

/**
 * Constructors.
 */

/**
 * Empty default Constructor starts the MapDB instance.
 */
private DBManager() 
{       
    /**
     * Load the database file from the given path
     * and initialize the database.
     */
    initMapDB();
}

/**
 * MapDB initializer.
 */

/**
 * Initialize a MapDB database.
 */
private void initMapDB() 
{
    /**
     * Persistence: Make MapDB able to load the same database from the 
     * file after JVM-Shutdown. Initialize database without @see     org.mapdb.DBMaker.deleteFilesAfterClose()
     * @see <link>https://groups.google.com/forum/#!topic/mapdb/AW8Ax49TLUc</link>
     */
    db = DBMaker.newFileDB(dbFile)
            .closeOnJvmShutdown()       
            .asyncWriteDisable()
            .encryptionEnable(password.getBytes())
            .make();

    /**
     * Create a Map / Get the existing map.
     */
    ctDB = db.getTreeMap("database");
}

/**
 * File existence check.
 * If file doesn't exists -> Create a new db file and inform the user.
 */
private static boolean isFileDatabaseOK() 
{       
    /**
     * If the file doesn't exists (First run) create a new file and 
     * inform the user.
     */
    if(!dbFile.exists())
    {
        try 
        {
            dbFile.getParentFile().mkdirs();
            dbFile.createNewFile();

            /**
             * TODO 
             * System.out.println("Database not found. Creating a new one.");
             */

            return true;
        }
        catch (IOException e)
        {
            /**
             * TODO Error handling
             */
            e.printStackTrace();
            return false;
        }
    }
    else
    {           
        return true;
    }
}

/**
 * Database methods / operations.
 */

/**
 * Get objects by id.
 * @param id    ->  Search parameter.
 * @return      ->  The object that belongs to the id.
 */
public String get(int id) 
{
    return ctDB.get(id);
}

/**
 * Adding objects to the database.
 * @param id -> The key reference to the object as 'id'.
 * @param object -> The object to cache.
 */
public void put(int id, String object)
{
    ctDB.put(id, object);

    db.commit();
}
}

然后执行:

 DBManager manager = DBManager.getInstance();
 manager.put(1, "test");
 Sytem.out.println(manger.get(1));

0

如果您为大多数参数设置默认值,则G1GC效果很好。只设置关键参数即可。

-XX:MaxGCPauseMillis
-XX:G1HeapRegionSize
-XX:ParallelGCThreads
-XX:ConcGCThreads

把一切都留给Java。

您可以在以下帖子中找到更多详细信息:

Java 7(JDK 7)垃圾收集和G1文档

为什么当堆的20%仍然空闲时我会得到OutOfMemory错误?

使用像mat这样的内存分析工具来了解根本原因。

在您的情况下,很明显oldgen正在增长。检查可能的内存泄漏。如果您没有发现内存泄漏,请进一步增加堆内存。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接