Как избежать огромного результата запроса, вызывающего исключение OutOfMemoryException - PullRequest
3 голосов
/ 03 ноября 2010

Вот моя ситуация:

С моего контроллера Grails я вызываю службу, которая запрашивает базу данных только для чтения, преобразует результат в JSON и возвращает результат.

: JDK 1.6, Tomcat 5.5, Grails 1.3.4, DB через JNDI

Для Tomcats MaxPermSize установлено значение 256 м, а Xmx - 128 м. РЕДАКТИРОВАТЬ: Увеличение памяти должно быть последним средством

Метод обслуживания:

  String queryDB(String queryString) {
    StringWriter writer = new StringWriter()
    JSonBuilder json = new JSonBuilder(writer)
    def queryResult = SomeDomain.findAllBySomePropIlike("%${queryString}%")

    json.whatever {
      results {
        queryResult.eachWithIndex { qr, i ->
          // insert domain w/ properties
        }
      }
    }
    queryResult = null
    return writer.toString()
  }

Теперь, когда queryString == 'a' набор результатов огромен иВ итоге я получаю следующее:

[ERROR] 03/Nov/2010@09:46:39,604 [localhost].[/grails-app-0.1].[grails] - Servlet.service() for servlet grails threw exception
java.lang.OutOfMemoryError: GC overhead limit exceeded
    at org.codehaus.groovy.util.ComplexKeyHashMap.init(ComplexKeyHashMap.java:81)
    at org.codehaus.groovy.util.ComplexKeyHashMap.<init>(ComplexKeyHashMap.java:46)
    at org.codehaus.groovy.util.SingleKeyHashMap.<init>(SingleKeyHashMap.java:29)
    at groovy.lang.MetaClassImpl$Index.<init>(MetaClassImpl.java:3381)
    at groovy.lang.MetaClassImpl$MethodIndex.<init>(MetaClassImpl.java:3364)
    at groovy.lang.MetaClassImpl.<init>(MetaClassImpl.java:140)
    at groovy.lang.MetaClassImpl.<init>(MetaClassImpl.java:190)
    at groovy.lang.MetaClassImpl.<init>(MetaClassImpl.java:196)
    at groovy.lang.ExpandoMetaClass.<init>(ExpandoMetaClass.java:298)
    at groovy.lang.ExpandoMetaClass.<init>(ExpandoMetaClass.java:333)
    at groovy.lang.ExpandoMetaClassCreationHandle.createNormalMetaClass(ExpandoMetaClassCreationHandle.java:46)
    at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.createWithCustomLookup(MetaClassRegistry.java:139)
    at groovy.lang.MetaClassRegistry$MetaClassCreationHandle.create(MetaClassRegistry.java:122)
    at org.codehaus.groovy.reflection.ClassInfo.getMetaClassUnderLock(ClassInfo.java:165)
    at org.codehaus.groovy.reflection.ClassInfo.getMetaClass(ClassInfo.java:182)
    at org.codehaus.groovy.runtime.callsite.ClassMetaClassGetPropertySite.<init>(ClassMetaClassGetPropertySite.java:35)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.createClassMetaClassGetPropertySite(AbstractCallSite.java:308)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.createGetPropertySite(AbstractCallSite.java:258)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.acceptGetProperty(AbstractCallSite.java:245)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callGetProperty(AbstractCallSite.java:237)
    at org.codehaus.groovy.grails.plugins.web.filters.FilterToHandlerAdapter.accept(FilterToHandlerAdapter.groovy:196)
    at org.codehaus.groovy.grails.plugins.web.filters.FilterToHandlerAdapter$accept.callCurrent(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:44)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:143)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:159)
    at org.codehaus.groovy.grails.plugins.web.filters.FilterToHandlerAdapter.preHandle(FilterToHandlerAdapter.groovy:107)
    at org.springframework.web.servlet.HandlerInterceptor$preHandle.call(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:40)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:117)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:133)
    at org.codehaus.groovy.grails.plugins.web.filters.CompositeInterceptor.preHandle(CompositeInterceptor.groovy:42)
    at org.codehaus.groovy.grails.web.servlet.GrailsDispatcherServlet.doDispatch(GrailsDispatcherServlet.java:282)

Один из подходов, которые я обнаружил в Интернете, касается некоторых утечек в Hibernate и проверки домена, объяснил здесь и подробно здесь .Я только собираюсь проверить это, но я не знаю, действительно ли это решение моей проблемы и (если оно есть), в какой момент лучше очистить GORM.

Или есть другойутечка памяти в моем коде?Любые идеи?

РЕДАКТИРОВАТЬ: Насколько я сейчас, исключение происходит в точке, где вызывается метод поиска.Это означает, что GORM не может обрабатывать объем данных, возвращаемых базой данных, верно?Извините за вопрос, как зеленый рог, но я никогда не сталкивался с такой проблемой, даже с очень большими наборами результатов.

Ответы [ 5 ]

4 голосов
/ 03 ноября 2010

Вс (эта ссылка больше не действительна) задокументировал это OutOfMemoryError следующим образом:

Параллельный / параллельный коллектор сгенерирует OutOfMemoryError, если тоже много времени тратится на мусор сбор: если более 98% общее время проведено в мусоре сбор и менее 2% восстанавливается куча, ошибка OutOfMemoryError будет брошен. Эта функция предназначен для предотвращения приложений от работает в течение длительного периода времени делая мало или нет прогресса потому что куча слишком мала. Если необходимо, эта функция может быть отключить, добавив опцию -XX: -UseGCOverheadLimit в командной строке.

Другими словами, эта ошибка - это особенность, подсказка для увеличения доступной памяти (как вы упомянули, не является предпочтительным вариантом в вашем случае). Некоторые разработчики считают, что эта функция не будет полезна в каждом случае использования, поэтому не забудьте отключить ее.

3 голосов
/ 03 ноября 2010

Другим вариантом для тех, кто уже предлагал, будет работа с страницами результатов.Вместо использования динамического поиска, используйте критерии и просматривайте результаты самостоятельно.Вот наивный пример с псевдокодом:

def offset = 0
def max = 50
while(stillMoreResults) {
    def batch = SomeDomain.findAllBySomePropIlike("%${queryString}%", [max: max, offset: offset])
    appendBatchToJsonResult(batch)
    offset += max
}

Вы можете настроить размер пакета в соответствии с требованиями к памяти.Это позволит избежать необходимости настраивать память.

Редактировать

Я просто перечитал ответ Флетча и заметил, что он упомянул это как решение, а вы прокомментировали его.Я оставлю свой здесь, поскольку у него есть пример, но если Флетч добавит к своему примеру пример подкачки, я удалю этот ответ, поскольку он упомянул его раньше, чем я.

2 голосов
/ 03 ноября 2010

Если вы не хотите увеличивать память, возможно, вам следует искать только строки, превышающие определенную сумму.Я предполагаю, что это какая-то функция опережения типа / предложения;Может быть, вы можете начать поиск, когда есть три символа или около того.В противном случае, возможно, страничные результаты - это вариант?

Кстати, архитектурно, контроллер предназначен для взаимодействия с внешним миром и его форматами, то есть вы, вероятно, захотите, чтобы ваш сервис просто возвращал объекты и ваш контроллер всделать преобразование JSON.Но это не решит вашу текущую проблему.

1 голос
/ 03 ноября 2010

Я бы также предложил вам вернуть только те свойства, которые вам нужны, с этим запросом типа запроса вперед и получить полный объект домена с фактическими данными, которые требуются пользователю.

JSON Builder собирается создать много объектов и использовать память. Например, при опечатке для пользователей я бы возвращал только базовую информацию об имени и идентификатор вместо полного объекта

0 голосов
/ 10 декабря 2012

В приложении Grails, использующем базу данных MySQL (MySQL был установлен через Homebrew), у меня возникла та же проблема, как ни странно, только при запуске приложения без предварительного запуска сервера MySQL.Таким образом, простое выполнение

mysql.server start

решило проблему для меня.

...