Pull to refresh

Comments 23

Эта реализация примерно аналогичная, когда на одной машине, но её нельзя расширить на кластер — из-за отсутствия интерфейсов для Listener'ов, а также из-за игнорирования особенностей использования Timestamp Region
На самом деле она изначально кластерная. Посмотрите внимательно ;) Отсутствие Listener'ов на высоком уровне не означает что их нет на более низком, там используется общая мапа для hazelcast cluster. В конструкторе класса HazelcastCache как раз и берется инстанс cache = Hazelcast.getMap(regionName); Из видео становится понятней что из себя представляет Hazelcast впринципе www.hazelcast.com/gettingstarted.htm.
Если там нет листенеров на высоком уровне, значит, кластер не учитывает особенности hibernate, и, единственное работающее решение — это кластерный кеш вроде старого jboss, где общее хранилище для всех нод.

Более того, судя по реализации Region у них будут проблемы с реализацией timestamp'ов — они у них разные на разных машинах :) И если часы рассинхронизированы (а у нас они часто в таком состоянии) кеш начнёт глючить.
А где же потолок этого кэша? Он что, кэширует сколько памяти хватит в надежде что в случае чего, SoftReference подсобит? Правильно я понимаю?
В этом и идея, что за памятью следит JVM, а не администратор, который не знает, сколько памяти в байтах на что можно отводить.
Это плохая идея. Куча будет всё время переплнена, будет постоянная сборка мусора и количество объёктов в куче будет очень велико. Результат: много частых сборок кучи, причём больших с Compact and Sweep. Кроме того, каждая такая сборка будет особенно дорогой, так как производительность сборки обратно пропорциональна количеству аллоцированных объектов, а у нас как раз объектов под завязку.
1) Давайте различать old & eden generation. Old generation на практике выше 80-85% у нас не поднимается.

2) Сборка мусора и так постоянна, она не зависит от того, soft или hard referenc'ы в памяти. То есть замена soft references на hard не уменьшит количества объектов в памяти (без уменьшения КПД), но увеличит вероятность OutOfMemory, если мы неправильно настроим пределы кеша.

То есть использование soft references вместо hard references не влияет на производительность сборки мусора.

3) Полная сборка мусора на 4 Гб занимает 0.3 секунды real time — в среднем один раз за 4 часа (old generation с ConcurrentMarkSweep). Хотя и много, но допустимо. Надеемся уменьшить это время после выхода 1.7.0 и использования G1 (сейчас не используем, так как нельзя мониторить через JMX)
P.S.: Постоянная сборка мусора это не плохо. Главное, чтобы это был не stop the world full gc.
Поскольку эти объекты будут жить в кеше долго, то они переедут в oldgen и займут много места. Постоянная сборка в eden не опасна. Постоянная сборка во всех зонах кучи — это уже гораздо хуже. Заполненность кучи не должна быть запредельной.

>> То есть использование soft references вместо hard references не влияет на производительность сборки мусора.
Разве VM'а не должна следить за такими ссылками? Вряд ли это полностью бесплатная фича. Думаю soft-линка всё же дороже обойдётся.

>> Полная сборка мусора на 4 Гб занимает 0.3 секунды real time
Всё это так до тех пор пока в куче на самом деле мусор и просто всё выбрасывается. Хуже когда он долго долго проверяет видимость и в итоге ничего не выбрасывается. Легко проверить, что в кучах реальных приложений сборка выполняется во много раз дольше. А уж если начать складывать на длительном промежутке времени, то суммарное время сборки может оказаться совсем не маленьким.

> Заполненность кучи не должна быть запредельной
а куда она денется-то в случае обычного кеша? Всё равно придётся собирать мусор и в old generation, от этого hard reference cache не спасёт.

> Разве VM'а не должна следить за такими ссылками? Вряд ли это полностью бесплатная фича. Думаю soft-линка всё же дороже обойдётся
Кто-то же должен следить за тем, какие объекты из кеша викидывать (хотя бы и по переполнению). Одно дело когда за ссылками (в том числе — last used time) следит JVM, и другое дело — когда за этим следит Java-код, реализующий кеш. Очевидно, первое дешевле.

> Легко проверить, что в кучах реальных приложений сборка выполняется во много раз дольше
А я Вам как раз про реальное приложение привёл пример :)
>> Одно дело когда за ссылками (в том числе — last used time) следит JVM, и другое дело — когда за этим следит Java-код, реализующий кеш. Очевидно, первое дешевле.

Нет, совсем не очевидно. Это создаёт лишнюю нагрузку на виртуальную машину.

>> а куда она денется-то в случае обычного кеша? Всё равно придётся собирать мусор и в old generation, от этого hard reference cache не спасёт.
В случае обычного кэша количество объектов не будет запредельным. Оно будет таким, какое будет указано — ни граммом больше.

Пожалуй я бы согласился скрестить ужа и ежа и сделать ограниченный Java-кодом кэш на мягких ссылках (на случай какого-то экстремального пика).
>> Это создаёт лишнюю нагрузку на виртуальную машину
<ирония>А Java-код, реализующий логику кеша выполняется на какой-то другой машине? Или он «бесплатный»?</ирония>
Для меня очевидно, что если какая-то логика реализуется в JVM и в Java-коде, первая реализация выходит дешевле. Пример с System.arraycopy() уж много раз это показывал.

>> Оно будет таким, какое будет указано — ни граммом больше
Для этого нужно:
а) Таки правильно указать каким оно должно быть — но при количестве кешей около 100 распредить память между ними сложно.
б) При складывании в кеш объектов их сериализовывать — чтобы получать массив байтов
в) При выборке из кеша их десериализовывать
И всем этим должен заниматься кеш. Да, это можно делать (так делает JBoss Cache). Но это дорого с точки зрения производительности и сложно в настройке.
System.arraycopy() не пример, т.к. это слишком мелкая деталь. Просто ко всему может быть общий подход, а может быть подход в зависимости от конкретной ситуации (индивидуальный). JVM действует обобщённо, она не будет лезть в конкретику. Чаще всего обобщённые методе менее эффективны. Например, обобщённый метод для Map — это просто хранить глупую линейную таблицу — это просто и всегда работает. Если же углубляться в конкретику и наложить кое какие ограничения, то оказывается, что можно и побыстрее сделать.
Мне кажется, спор становится малоэффективным.

Как я уже указал, SoftReference, по моему мнению, позволяет достичь следующего:
1) Объём кеша указывается в процентах к общей памяти JVM, а не в байтах, что упрощает настройку, особенно межсерверную (когда у серверов разные характеристики). Особенно когда не известно, сколько памяти займут остальные данные хранящиеся в Old Space — то есть неизвестно заранее, сколько вообще можно памяти отдать на кеш.
2) Объём указывается как часть настройки old space (де-факто -XX:CMSInitiatingOccupancyFraction), что позволяет нам забыть о вычислении того, сколько занимает кеш, а сколько — все остальные данные из old generation.
3) Отсутствует необходимость сериализации и десериализации, так как JVM и сама знает, сколько памяти занимает кеш
4) За evict следит JVM, что позволяет достичь преимущества в скорости по сравнению с другими кешами. Однако существует ограничение — поддерживается только NotUsedForNMinutes стратегия (где N, кажется, Xmx делённое на 40 Мб — по умолчанию), что только в приближении можно считать как LRU. Однако чтобы при SoftReference кеше добиться OutOfMemory — нужно очень сильно постараться.

Однако любой из существующих Java hard reference кешей не сможет без сериализации (либо без детальной интроспекции) данных удовлетворить первому требованию, так как не знает объём данных, что затруднит настройку серверов. Тем более он не знает, сколько свободной памяти у JVM ещё можно себе забрать. (требование два)

Для меня наличие сериализации в любом виде автоматически означает, что кеш будет медленнее, чем Soft Reference.

Если у вас есть сомнения, что это так, прошу вас привести пример реализации или идеи реализации кеша, который бы смог бы работать как минимум так же быстро, как SoftReferenceMap, а также поддерживать LRU (или хотя бы NotUsedForNMinutes ), и позволять указывать максимальный объём занимаемой памяти (в байтах или процентах от общей). И недопускать OOM за исключением каких-то критических случаев.
1. Сериализация совершенно не обязательная вещь — не хватает места — выкинуть к чёртовой матери и всё. Это кэш а не хранилище данных.

2. Это же кэш, опять-таки. Нам не нужно строгое следование правилу LRU или NotUsedForNMinutes. Нам достаточно примерного следования какому-то принципу. А если что-то не влезает, то надо жёстко выбрасывать. Да, я согласен что написать правило инвалидации кэша сложно так, чтобы он гарантированно очищался при каком-либо переполнении. Границу можно определить только исходя из конкретики приложения. Однако, следует понимать, что всё же если вся куча занята кэшом на SoftReferenceMap, то вместе с ним никакое нормальное приложение уже не сможет работать нормально, оно будет работать всё время в экстремальном режиме.
1. А откуда вы знаете, что места не хватает, если не считаете его? JVM знает, когда места мало — и именно тогда выкидывает SoftReference'ы. А обычный кеш может лишь предполагать, основываясь на своём размере.

2. > «вместе с ним никакое нормальное приложение»…
То самое приложение, которое кеш использует, прекрасно работает. Постоянные данные — в old generation, а временные — в eden. А кеш — в кеше. Что ещё нужно?

Речь, разумеется, о серверных приложения, где текущие данные в основном в eden.
И вот ещё про 0.3 сек. на сборку 4Гб кучи. Вот у меня работает нетбинс:
Current heap size: 
113 831 kbytes
Maximum heap size: 
466 048 kbytes
Committed memory: 
218 304 kbytes
Pending finalization: 
0 objects
Garbage collector: 
Name = 'PS Scavenge', Collections = 160, Total time spent = 20,397 seconds
Garbage collector: 
Name = 'PS MarkSweep', Collections = 10, Total time spent = 9,786 seconds

При такой крошечной неполной куче уже показатель сопоставимый. Что же будет если взять 4Гб под завязку набитых? будет жесть. Тот же нетбинс при переполненной куче порой уходит на пару секунд в сборку.

К сожалению у меня нет прямо сейчас под рукой настоящего EE-приложения, но как доберусь до него, посмотрю сколько в среднем на сборку выходит. Мне кажется, что 0.3 сек на 4Г это уж больно хорошо. Хотя, возможно у вас просто хорошая тачка ;)
Давайте-ка возьмём серверное приложение. К сожалению, только 13 часов аптайм, но:

Серверная «тачка» — 2 процессора Intel® Xeon(TM) CPU 3.60GHz

Current heap size:
2 874 277 kbytes
Maximum heap size:
4 827 840 kbytes
Committed memory:
4 827 840 kbytes
Pending finalization:
0 objects

Garbage collector:
Name = 'ParNew', Collections = 3085, Total time spent = 11 minutes
Garbage collector:
Name = 'ConcurrentMarkSweep', Collections = 3, Total time spent = 1,731 seconds

(хм… а в логах 0.3 было… наверно завершающая фаза).

Опять же,
JAVA_OPTS="-server -Xmx4800m -Xms4800m -Xmn1g -XX:MaxPermSize=128m -XX:+DisableExplicitGC"
JAVA_OPTS="$JAVA_OPTS -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseParNewGC -XX:SurvivorRatio=10"
JAVA_OPTS="$JAVA_OPTS -Djava.lang.Integer.IntegerCache.high=1048575"
Вот, например, наш продакшн-сервер:

Uptime: 
9 days 19 hours 58 minutes
Maximum heap size: 
760 256 kbytes
Garbage collector: 
Name = 'Copy', Collections = 271, Total time spent = 6,957 seconds
Garbage collector: 
Name = 'MarkSweepCompact', Collections = 252, Total time spent = 2 minutes

Показатели выходят даже хуже (~0.5s), хотя куча почти свободна.

Теория теорией, а факт есть факт. Видимо, ваша идея не так уж плоха ;)
Да, это именно stop the world full gc и будет получаться, когда куча переполнена.
Мы это не допускаем с помощью -XX:CMSInitiatingOccupancyFraction=80
Sign up to leave a comment.

Articles

Change theme settings