Страница: 1 | 2 | 3 |
|
Вопрос: GC финализирует объект без причины
|
Добавлено: 01.09.12 20:36
|
|
Номер ответа: 16 Автор ответа: Artyom
Разработчик
Вопросов: 130 Ответов: 6602
|
Профиль | | #16
|
Добавлено: 02.09.12 00:13
|
EROS пишет:
Объект никем не используется и на него НЕТ ссылок
Я так полагаю, вы хотели сказать, что на него нет ЖИВЫХ ссылок??
EROS пишет:
Я рассчитываю, что, возможно, кто-то сможет предположить, каким образом может происходит вызов ~ObjectName(),
Собственно EROS все верно ответил. Я знаком с принципом работы менеджера памяти дотнета очень хорошо, и не могу придумать ситуацию, в которой бы у реально живого объекта мог бы по какой-либо причине быть вызван финализатор.
К финализатору нельзя получить доступ через Reflection. Через IL Emit, если не ошибаюсь, тоже не получится.
Разве что добраться до MethodTable, оттуда получить адрес метода финализации и вызвать его (впрочем если в вашем коде есть что-то подобное, то странно что глючит только финализация)
Ответить
|
Номер ответа: 17 Автор ответа: Programmer
Вопросов: 71 Ответов: 246
|
Профиль | | #17
|
Добавлено: 02.09.12 00:42
|
EROS, я знаком с этой информацией, но все-равно спасибо за интересные подробности
Я так понимаю, "оживление" может произойти только в том случае, если объект в финализаторе где-нибудь сохранит ссылку на себя. Но у меня точно такого не происходит. Возможно, это глюк Extension-методов (объект "исчезает", когда я передаю его в качестве аргумента extension-метода).
-
- // вызывается
- void a(BinaryWriter wr) {
- wr.Write(1); // успешно
- b(123);
- }
-
- ...
- public static void b(this BinaryWriter wr, float data){
- wr.Write(data); // перегруженный метод MemoryStream.Write(byte[],int,int) сообщает, что установлен флаг "вызван финализатор".
- }
Ответить
|
Номер ответа: 21 Автор ответа: Artyom
Разработчик
Вопросов: 130 Ответов: 6602
|
Профиль | | #21
|
Добавлено: 02.09.12 04:43
|
Programmer пишет:
EROS, я знаком с этой информацией, но все-равно спасибо за интересные подробности
Вообще-то это не EROS писал а я
Programmer пишет:
Возможно, это глюк Extension-методов (объект "исчезает", когда я передаю его в качестве аргумента extension-метода).
Programmer, глюк только у тебя в коде. В компиляторе csc этих глюков нет.
Programmer пишет:
Extension-метод может быть вызван на null-объекте. GC определяет, что код, который идет после первого Write прекрасно может обойтись без объекта BinaryWriter. Это наиболее вероятная версия. Может быть, это даже можно воспроизвести.
В твоем коде ты сможешь воспроизвести только NullReferenceException, так как до вызова Extension метода вызывается Instance метод класса BinaryWriter. Instance методы вызываются инструкцией IL callvirt, которая в первую очередь проверяет референс на null, и в случае чего кидает NullReferenceException (даже в том случае, если instance метод не использует ссылку на this).
В любом случае, скорее всего ты движешься не в том направлении, поскольку все локальные переменные хранятся в стеке и в регистрах процессора, а стек и регистры тоже являются корнями, с которыми работает GC. Поэтому ситуация при которой ссылка из локальной переменной вдруг станет указывать на мусор исключена.
Ответить
|
Номер ответа: 25 Автор ответа: Programmer
Вопросов: 71 Ответов: 246
|
Профиль | | #25
|
Добавлено: 02.09.12 21:49
|
Код стрима:
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Threading;
-
- namespace FlexibleGameServer
- {
- public class SimpleDataBuffer : MemoryStream, IDisposable
- {
- public override void Write(byte[] buffer, int offset, int count)
- {
- bool disposed = _disposeCalled;
- try
- {
- if (_disposeCalled || _asEmpty) throw new ObjectDisposedException("SimpleDataBuffer");
- base.Write(buffer, offset, count);
- }
- catch (ObjectDisposedException e) { throw new ObjectDisposedException("SimpleDataBuffer disposed? " + _disposeCalled + ", was " + disposed + ", disposed at " + _disposedAt, e); }
- }
-
- public override void WriteByte(byte value)
- {
- if (_disposeCalled || _asEmpty) throw new ObjectDisposedException("SimpleDataBuffer");
- base.WriteByte(value);
- }
-
- public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
- {
- if (_disposeCalled || _asEmpty) throw new ObjectDisposedException("SimpleDataBuffer");
- return base.BeginRead(buffer, offset, count, callback, state);
- }
-
- public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
- {
- if (_disposeCalled || _asEmpty) throw new ObjectDisposedException("SimpleDataBuffer");
- return base.BeginWrite(buffer, offset, count, callback, state);
- }
-
- public override long Seek(long offset, SeekOrigin loc)
- {
- if (_disposeCalled || _asEmpty) throw new ObjectDisposedException("SimpleDataBuffer");
- return base.Seek(offset, loc);
- }
-
- public override byte[] GetBuffer()
- {
- if (_disposeCalled || _asEmpty) throw new ObjectDisposedException("SimpleDataBuffer");
- return base.GetBuffer();
- }
-
- public override byte[] ToArray()
- {
- if (_disposeCalled || _asEmpty) throw new ObjectDisposedException("SimpleDataBuffer");
- return base.ToArray();
- }
-
- static readonly SimpleDataBuffer[] EmptyBuffer = new SimpleDataBuffer[5000];//, _emptyBuffer2, _emptyBuffer3, _emptyBuffer4, _emptyBuffer5;
- static int _emptyBufferUsed;
- static int _emptyBufferBegin;
- static readonly object EmptyBufferLock = new object();
-
- public static SimpleDataBuffer Create()
- {
- if (_emptyBufferUsed == 0) return new SimpleDataBuffer();
-
- lock (EmptyBufferLock)
- {
- if (_emptyBufferUsed != 0)
- {
- SimpleDataBuffer ret = EmptyBuffer[_emptyBufferBegin];
- _emptyBufferUsed--;
- _emptyBufferBegin++;
-
- Monitor.Enter(ret._dispLock);
- if (ret._disposeCalled || !ret._asEmpty || !ret.CanRead || ret.Writer._disposed || ret.Reader._disposed)
- {
- Monitor.Exit(ret._dispLock);
- return Create();
- }
- try
- {
- if (ret.Length != 0) ret.SetLength(0);
- ret._asEmpty = false;
- return ret;
- }
- catch { }
- finally
- {
- Monitor.Exit(ret._dispLock);
- }
- return Create();
- }
- return new SimpleDataBuffer();
- }
-
-
- }
-
- static bool SetEmptyBuffer(SimpleDataBuffer buffer)
- {
- if (_emptyBufferUsed == EmptyBuffer.Length) return false;
- //if (_emptyBuffer5 != null) return false;
- lock (EmptyBufferLock)
- {
- if (_emptyBufferUsed == EmptyBuffer.Length) return false;
-
- if (_emptyBufferBegin > 0)
- {
- _emptyBufferBegin--;
- EmptyBuffer[_emptyBufferBegin] = buffer;
- }
- else EmptyBuffer[_emptyBufferUsed] = buffer;
- buffer._asEmpty = true;
- _emptyBufferUsed++;
-
-
- return true;
- }
-
- }
-
- public BinaryWriter Writer { get; protected set; }
- public BinaryReader Reader { get; protected set; }
-
- static readonly HashSet<SimpleDataBuffer> Objects = new HashSet<SimpleDataBuffer>();
-
- protected SimpleDataBuffer()
- {
- this.Writer = new BinaryWriter(this);
- this.Reader = new BinaryReader(this);
- // ReSharper disable DoNotCallOverridableMethodsInConstructor
- Position = 0;
- // ReSharper restore DoNotCallOverridableMethodsInConstructor
- lock (Objects) Objects.Add(this);
- }
-
-
- public class BinaryWriter : System.IO.BinaryWriter
- {
- public BinaryWriter(Stream output) : base(output)
- {
- }
-
- internal bool _disposed;
-
- protected override void Dispose(bool disposing)
- {
- _disposed = true;
- base.Dispose(disposing);
- }
- }
-
- public class BinaryReader : System.IO.BinaryReader
- {
- public BinaryReader(Stream input) : base(input)
- {
- }
-
- internal bool _disposed;
-
- protected override void Dispose(bool disposing)
- {
- _disposed = true;
- base.Dispose(disposing);
- }
- }// BinaryReader
-
- public override void Close()
- {
- Dispose(true);
- }
-
- bool _disposeCalled;
- bool _asEmpty;
- readonly object _dispLock = new object();
-
- string _disposedAt;
-
- protected override void Dispose(bool disposing)
- {
- lock (this._dispLock)
- {
- if (this._disposeCalled || this._asEmpty) return;
- if (SetEmptyBuffer(this)) return;
- _disposedAt = Environment.StackTrace;
- this._disposeCalled = true;
- }
-
- try
- {
- Reader.Close();
-
-
- Writer.Close();
- //Writer = null;
- base.Close();
- base.Dispose(disposing);
- }
- finally
- {
- lock (Objects) Objects.Remove(this);
- }
- }
-
- void IDisposable.Dispose()
- {
- Dispose(true);
- }
-
- ~SimpleDataBuffer()
- {
- _disposedAt = "\n at ~SimpleDataBuffer: \n" + Environment.StackTrace;
- if (_disposeCalled) return;
- _disposeCalled = true;
- try
- {
- Reader.Close();
- Writer.Close();
- }
- catch { }
- try
- {
- base.Close();
- }
- catch { }
- try
- {
- base.Dispose(true);
- }
- catch { }
- }
- } // SimpleDataBuffer2
- }
Ответить
|
Номер ответа: 26 Автор ответа: Artyom
Разработчик
Вопросов: 130 Ответов: 6602
|
Профиль | | #26
|
Добавлено: 02.09.12 23:36
|
Так и хочется тут сказать over reliability.
Вобщем, налицо какая-то каша у вас в голове.
Путаете Dispose и финализацию.
В коде логика неверная. Смотри, у тебя в финализаторе есть вызов метода Dispose, при этом логика кода построена так, что этот метод не сможет отработать, так как в финализаторе ты уже поставил флаг _disposeCalled, Dispose его считывает и сразу выходит из метода.
Далее, в методе Dispose (bool disposing) аргумент disposing используется для того, чтоб метод мог понять кто инициировал его вызов - или Dispose, или финализатор. Если вызов делается из Dispose, то передается true. Если из финализатора - то False. У вас в обоих случаях передается true
По большому счету здесь нужно удалить абсолютно весь код, который начинается со строчки
- public override void Close()
и до конца класса. Он не делает абсолютно никакой полезной работы с точки зрения управления памяти.
Дальше убрать статический список Objects. Это уже настоящая утечка памяти. Причем пока на объект есть ссылка из статического HashSet, он не будет признан мусором. И, сответственно, ваш финализатор для него не вызовется.
По сути единственное, что стоит делать в финализаторе - это освобождать неуправляемые ресурсы, котоыре не были удалены штатно (через прямой вызов Dispose), поскольку, во-первых, кроме вас никто больше не знает как эти ресурсы освобождать, а во-вторых, это последний шанс вообще что-то удалить, после этого неуправляемые ресурсы повиснут и освободить их уже не представится возможности.
У вас в коде не используется ни одного неуправляемого ресурса, следовательно, в финализаторе здесь нет необходимости.
С Dispose сложнее. У вас очень неудачно выбрано отношение между вашим классом SimpleDataBuffer и MemoryStream. Здесь гораздо уместнее бы выглядела композиция а не наследования. Тем не менее вы сделали что сделали. В таком случае я бы оставил метод Dispose, в нем нужно делать только
- Writer.Flush();
- Base.Dispose(true);
Первое чтоб из буфера Writer контент сбросился в поток. Второе, я думаю, понятно зачем. Но тут нужно понимать что это результат не изменит, даже если и буфер будет сброшен, контент дальше никуда не попадет, так как сам поток тоже закрывается.
Дальше можно убрать все проверки флага _disposeCalled. MemoryStream имеет встроенный механизм, препятствующий чтению данных из него после закрытия потока.
- // ReSharper disable DoNotCallOverridableMethodsInConstructor
- Position = 0;
- // ReSharper restore DoNotCallOverridableMethodsInConstructor
Есть определенная причина, по которой Resharper показывает это сообщения. Она состоит в том, что вызов виртуального метода из конструктора может привести к ситуации, когда Instance метод класса вызывается ДО того как конструктор этого класса был вызван, что в свою очередь может причести к непредсказуемому поведению. Поэтому эту строчку нужно выпилить отсюда, тем более что MemoryStream и так инициализируется на нулевой позиции.
- Monitor.Enter(ret._dispLock);
- if (ret._disposeCalled || !ret._asEmpty || !ret.CanRead || ret.Writer._disposed || ret.Reader._disposed)
- {
- Monitor.Exit(ret._dispLock);
- return Create();
- }
При определенных обстоятельствах (например, асинхронное исключение) этот код залочит Monitor и не освободит его. И следующая попытка вызова этого метода навечно его "повесит". Нужно переписать с try или не выделываться и сделать обычным lock'ом
- static readonly SimpleDataBuffer[] EmptyBuffer = new SimpleDataBuffer[5000];//, _emptyBuffer2, _emptyBuffer3, _emptyBuffer4, _emptyBuffer5;
Это, я так полагаю, пул объектов? Если так, то безжалостно убрать. Пул можно применять в тех случаях, когда речь идет о ресурсах, инициализация которых занимает длительное время (например, как в случае с подключениями к БД). Этого нельзя сказать о MemoryStream - выделение памяти в куче выполняется очень быстро.
Ответить
|
Номер ответа: 27 Автор ответа: Artyom
Разработчик
Вопросов: 130 Ответов: 6602
|
Профиль | | #27
|
Добавлено: 02.09.12 23:46
|
Я в свое время писал игровой сервер, позволю привести несколько фактов.
Windows-служба.
Если не ошибаюсь, фактически запускается 3 или 4 потока.
В службе поднимается TCP-сервер.
Кол-во одновременных активных подключений не ограничено со стороны сервера (если не ошибаюсь, на тестах удавалось подключить 64+ тысячи клиентов, дальше кончились клиентские локальные порты, сервер же был готов и дальше принимать подключения)
Самый большой аптайм, который я фиксировал, составлял около 2 месяцев, больше не было из-за того что сервак постоянно перегружается ставя Windows Update.
В коде 15К+ строк кода и ни одного финализатора.
Когда нагрузка с сервера уходит, потребление памяти падает до 100 с лишним метров, из чего можно сделать определенные выводы о том, был ли предыдущий пункт правильным решением.
Ответить
|
Номер ответа: 30 Автор ответа: Programmer
Вопросов: 71 Ответов: 246
|
Профиль | | #30
|
Добавлено: 02.09.12 23:57
|
Смотри, у тебя в финализаторе есть вызов метода Dispose, при этом логика кода построена так, что этот метод не сможет отработать, так как в финализаторе ты уже поставил флаг _disposeCalled, Dispose его считывает и сразу выходит из метода.
Я вызываю base Dispose. Там мой флаг не проверяется.
Далее, в методе Dispose (bool disposing) аргумент disposing используется для того, чтоб метод мог понять кто инициировал его вызов - или Dispose, или финализатор. Если вызов делается из Dispose, то передается true. Если из финализатора - то False. У вас в обоих случаях передается true
Да, для меня это приемлемо.
Дальше убрать статический список Objects. Это уже настоящая утечка памяти. Причем пока на объект есть ссылка из статического HashSet, он не будет признан мусором. И, сответственно, ваш финализатор для него не вызовется.
Я как раз об этом поле и говорил в предыдущем посте. Без него объект финализируется когда на него есть живая ссылка.
По сути единственное, что стоит делать в финализаторе - это освобождать неуправляемые ресурсы, котоыре не были удалены штатно (через прямой вызов Dispose), поскольку, во-первых, кроме вас никто больше не знает как эти ресурсы освобождать, а во-вторых, это последний шанс вообще что-то удалить, после этого неуправляемые ресурсы повиснут и освободить их уже не представится возможности. В моем случае финализатор нужен для того, чтобы я мог не вызывать Dispose. Т.е. Dispose нужен, если требуется освободить ресурсы прямо сейчас. Мой финализатор освобождает эти ресурсы, если они не были освобождены из Dispose. Я не знаю, освободит ли MemoryStream все свои ресурсы в финализаторе или нет (если не вызывать Dispose), поэтому предпочитаю вручную вызвать base.Dispose в финализаторе.
Дальше можно убрать все проверки флага _disposeCalled. MemoryStream имеет встроенный механизм, препятствующий чтению данных из него после закрытия потока. Ты вообще читал код? У меня механизм кэширования, который помещает стрим в пул при вызове Dispose, при этом устанавливается флаг asEmpty. А если пул заполнен - только тогда делается настоящий Dispose. Настоящий смысл проверок не в _disposeCalled, а в _asEmpty. Create() не создает новый стрим, а берет его из пула, если это возможно.
Есть определенная причина, по которой Resharper показывает это сообщения. Она состоит в том, что вызов виртуального метода из конструктора может привести к ситуации, когда Instance метод класса вызывается ДО того как конструктор этого класса был вызван, что в свою очередь может причести к непредсказуемому поведению. Спасибо, я умею читать. Тем не менее, такое поведение бывает необходимо. Хотя не спорю, в данном случае это лишнее.
С Dispose сложнее. У вас очень неудачно выбрано отношение между вашим классом SimpleDataBuffer и MemoryStream. Здесь гораздо уместнее бы выглядела композиция а не наследования. Тем не менее вы сделали что сделали. В том виде, в котором я выложил, этот код вообще никому не может быть полезен. Но в полном коде выбранный паттерн себя оправдывает.
При определенных обстоятельствах (например, асинхронное исключение) этот код залочит Monitor и не освободит его. И следующая попытка вызова этого метода навечно его "повесит". Нужно переписать с try или не выделываться и сделать обычным lock'ом Откуда здесь взятся асинхронному исключению? Или срабатывает эта ветка или далее выполнение как раз и ведет в try-finally.
Это, я так полагаю, пул объектов? Если так, то безжалостно убрать. Пул можно применять в тех случаях, когда речь идет о ресурсах, инициализация которых занимает длительное время (например, как в случае с подключениями к БД). Этого нельзя сказать о MemoryStream - выделение памяти в куче выполняется очень быстро. Профайлер показывает значительный выигрышь. Не было бы тормозов - не было бы и пула.
Ответить
|
Страница: 1 | 2 | 3 |
Поиск по форуму