Есть ли деструктор для Java?


594

Есть ли деструктор для Java? Кажется, я не могу найти никакой документации по этому вопросу. Если нет, как я могу достичь того же эффекта?

Чтобы сделать мой вопрос более конкретным, я пишу приложение, которое работает с данными, и в спецификации говорится, что должна быть кнопка «сброс», которая возвращает приложение в исходное только что запущенное состояние. Однако все данные должны быть «живыми», если приложение не закрыто или не нажата кнопка сброса.

Будучи обычно программистом на C / C ++, я думал, что это будет тривиально реализовать. (И, следовательно, я планировал реализовать его в последний раз.) Я структурировал свою программу так, чтобы все объекты с возможностью сброса находились в одном классе, чтобы я мог просто уничтожить все «живые» объекты при нажатии кнопки сброса.

Я думал, что если бы все, что я делал, это просто разыменовывал данные и ждал, когда сборщик мусора соберет их, не будет ли утечка памяти, если мой пользователь неоднократно вводит данные и нажимает кнопку сброса? Я также думал, что поскольку Java достаточно зрел как язык, должен быть способ предотвратить это или изящно справиться с этим.


7
Утечка памяти происходит только в том случае, если вы храните ссылки на ненужные объекты. т.е. есть ошибка в вашей программе. GC будет работать так, как нужно (иногда раньше)
Питер Лори

17
Виртуальная машина не запустит GC достаточно быстро, если вы быстро обрабатываете данные с помощью объектов. Идея, что GC всегда может не отставать или принимать правильные решения, ошибочна.
Кьевели

1
@ Kieveli Разве JVM не запускает GC до того, как выдаст ошибку?
WVrock

4
Да, было бы неплохо, если бы был деструктор для Java, который уничтожил бы его раз и навсегда.
Томаш Зато - Восстановить Монику

Ответы:


526

Поскольку Java является языком сборки мусора, вы не можете предсказать, когда (или даже если) объект будет уничтожен. Следовательно, нет прямого эквивалента деструктора.

Существует наследуемый метод finalize, который вызывается, но он вызывается исключительно по усмотрению сборщика мусора. Таким образом, для классов, которые должны явно привести в порядок, соглашение состоит в том, чтобы определить метод close и использовать finalize только для проверки работоспособности (то есть, если close не был вызван, сделайте это сейчас и зарегистрируйте ошибку).

Был вопрос, который недавно породил всестороннее обсуждение финализации , так что это должно обеспечить большую глубину при необходимости ...


5
Относится ли "close ()" в этом контексте к методу в java.lang.Autocloseable?
Шридхар Сарнобат

21
Нет, AutoCloseable был введен в Java 7, но соглашение о close () существовало намного дольше.
Джон Онстотт

почему вы не можете предсказать, когда (или даже если) объект будет уничтожен. какой приблизительный другой способ предсказать это?
dctremblay

Уничтожение объекта @dctremblay выполняется сборщиком мусора, и сборщик мусора может никогда не запуститься при жизни приложения.
Пиро говорит восстановить Монику

4
Обратите внимание, что этот finalizeметод устарел в Java 9.
Lii

124

Посмотрите на инструкцию " попробуй с ресурсами" . Например:

try (BufferedReader br = new BufferedReader(new FileReader(path))) {
  System.out.println(br.readLine());
} catch (Exception e) {
  ...
} finally {
  ...
}

Здесь ресурс, который больше не нужен, освобождается в BufferedReader.close()методе. Вы можете создать свой собственный класс, который реализует AutoCloseableи использовать его аналогичным образом.

Это утверждение более ограничено, чем finalizeс точки зрения структурирования кода, но в то же время делает код более простым для понимания и сопровождения. Кроме того, нет никакой гарантии, что finalizeметод вызывается вообще во время работы приложения.


12
Я удивлен, что так мало голосов. Это фактический ответ.
nurettin

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

14
Действительно, это не фактический ответ. Невозможно использовать эту структуру для управления уничтожением объекта, если конструкция и использование объекта полностью не инкапсулированы tryи finallyиспользуются для принудительного вызова obj.finalize(). И даже эта настройка не решает проблему, поставленную OP: уничтожение объекта в середине программы, вызванное кнопкой «перезагрузки».
7yl4r

1
Другие пользователи показали, что это делается в точке входа вашего приложения. Определите вашу переменную глобально. Инициализируйте его в функции ввода с помощью try. Деинициализировать в конце (когда ваше приложение закрывается). Это вполне возможно.
TamusJRoyce

1
@nurettin Java 7 отсутствовал только в течение 3 месяцев, когда задавался вопрос, если это поможет сделать его более понятным.
corsiKa

110

Нет, здесь нет деструкторов. Причина в том, что все объекты Java выделены в куче и мусор. Без явного освобождения (т. Е. Оператора удаления C ++) не существует разумного способа реализации реальных деструкторов.

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

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


4
Спасибо за упоминание нативных ресурсов - это та область, где полезен метод, похожий на деструктор.
Натан Осман

да, я сталкиваюсь с той же проблемой прямо сейчас с высвобождением ресурсов / дескрипторов, выделенных через собственные вызовы C ++.
Никк

@ddimitrov, теоретически java может реализовать явное освобождение? Или это логическое противоречие?
мила

1
@mils, наивно реализующий явное освобождение, нарушит предположение Java о том, что любая ссылка указывает на живой объект. Вы можете перебирать все указатели и обнулять псевдонимы, но это дороже, чем GC. Или вы можете попробовать использовать систему линейных типов (см. «Владение» в Rust), но это серьезное изменение языка. Есть и другие варианты (см. Объем памяти JavaRT и т. Д.), Но в целом явное освобождение не подходит для языка Java.
ддимитров

31

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

Если вам требуется вызов освобождения в вашем объекте, скажем, для освобождения ресурсов, используйте явный вызов метода. Это соглашение можно увидеть в существующих API (например, Closeable , Graphics.dispose () , Widget.dispose () ) и обычно вызывается через try / finally.

Resource r = new Resource();
try {
    //work
} finally {
    r.dispose();
}

Попытки использовать удаленный объект должны вызвать исключение времени выполнения (см. IllegalStateException ).


РЕДАКТИРОВАТЬ:

Я думал, что если бы я просто разыскивал данные и ждал, когда сборщик мусора соберет их, не будет ли утечка памяти, если мой пользователь неоднократно вводит данные и нажимает кнопку сброса?

Как правило, все, что вам нужно сделать, это разыменовать объекты - по крайней мере, так оно и должно работать. Если вы беспокоитесь о сборке мусора, ознакомьтесь с настройкой сборки мусора виртуальной машины Java SE 6 HotSpot [tm] (или эквивалентным документом для вашей версии JVM).


1
Это не то, что означает разыменование. Это не «установка последней ссылки объекта на ноль», а получение (чтение) значения из ссылки, чтобы вы могли использовать его для последующих операций.

1
Является ли try..finally все еще действительным и рекомендуемым подходом? Предположим, я ранее вызывал собственный метод в finalize (), могу ли я переместить вызов в предложение finally? class Resource { finalize() { destroy(); } protected native void destroy(); } class Alt_Resource { try (Resource r = new Resource()) { // use r } finalize { r.destroy(); }
Аасима

r не будет ограничен до блока finally. Следовательно, вы не можете вызвать уничтожение в этой точке. Теперь, если вы исправите область для создания объекта до блока try, вы получите уродливый случай «перед попыткой с ресурсами».
userAsh

21

С выпуском Java 1.7 у вас теперь есть дополнительная опция использования try-with-resourcesблока. Например,

public class Closeable implements AutoCloseable {
    @Override
    public void close() {
        System.out.println("closing..."); 
    }
    public static void main(String[] args) {
        try (Closeable c = new Closeable()) {
            System.out.println("trying..."); 
            throw new Exception("throwing..."); 
        }
        catch (Exception e) {
            System.out.println("catching..."); 
        }
        finally {
            System.out.println("finalizing..."); 
        } 
    }
}

Если выполнить этот класс, c.close()будет выполняться , когда tryблок остается, и до того , как catchи finallyблоки выполняются. В отличие от случая, finalize()метод close()гарантированно будет выполнен. Однако нет необходимости выполнять это явно в finallyпредложении.


что если мы не использовали блок try-with-resources? Я думаю, что мы можем вызвать close в finalize () только для того, чтобы убедиться, что close был вызван.
shintoZ 12.12.14

3
@shintoZ, как я прочитал в ответах выше, гарантии finalize()исполнения нет
Асиф Муштак

14

Я полностью согласен с другими ответами, говоря не полагаться на выполнение финализации.

В дополнение к блокам try-catch-finally вы можете использовать Runtime # addShutdownHook (представленный в Java 1.3) для выполнения окончательных очисток в вашей программе.

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

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


addShutdownHook был явно представлен в Java 1.3. Во всяком случае, он доступен для меня в 1.5. :) Смотрите это: stackoverflow.com/questions/727151/…
skiphoppy

1
К вашему сведению, по моему опыту перехватчики отключения не будут вызываться, если вы используете красную кнопку «завершить» в Eclipse - вся JVM уничтожается немедленно, перехватчики отключения не изящно вызываются. Это означает, что вы можете увидеть различное поведение во время разработки и производства, если вы разрабатываете с использованием затмения
Hamy

11

Нет, java.lang.Object#finalizeэто самое близкое, что вы можете получить.

Однако когда (и если) он вызывается, это не гарантируется.
Видеть:java.lang.Runtime#runFinalizersOnExit(boolean)


5
Метод, который может или не может быть вызван, по сути бесполезен в моей книге. Было бы лучше не загрязнять язык бесполезным специальным методом, который в лучшем случае дает ложное чувство безопасности. Я никогда не пойму, почему разработчики языка Java думали, что финализация была хорошей идеей.
antred

@antred Разработчики языка Java согласны . Наверное, тогда для некоторых из них это был первый раз, когда они разработали язык программирования и среду выполнения с сборкой мусора. Менее понятно, почему этот другой управляемый язык копировал эту концепцию в то время, когда уже было понято, что эта концепция плохая идея.
Хольгер

7

Во-первых, обратите внимание, что, поскольку Java является сборщиком мусора, редко нужно что-либо делать с уничтожением объектов. Во-первых, потому что у вас обычно нет никаких управляемых ресурсов для освобождения, а во-вторых, потому что вы не можете предсказать, когда или если это произойдет, поэтому это неуместно для вещей, которые должны произойти, «как только никто больше не использует мой объект ».

Вы можете быть уведомлены после того, как объект был уничтожен, используя java.lang.ref.PhantomReference (фактически, сказать, что он был уничтожен, может быть немного неточным, но если фантомная ссылка на него помещена в очередь, то он больше не подлежит восстановлению, что обычно составляет тоже самое). Общее использование:

  • Разделите ресурсы в вашем классе, которые нужно разрушить, на другой вспомогательный объект (обратите внимание, что если все, что вы делаете, это закрытие соединения, что является обычным случаем, вам не нужно писать новый класс: соединение, которое будет закрыто, будет в этом случае "вспомогательным объектом").
  • Когда вы создаете свой основной объект, создайте для него также PhantomReference. Либо обратитесь к новому вспомогательному объекту, либо настройте карту из объектов PhantomReference к соответствующим вспомогательным объектам.
  • После того, как основной объект собран, PhantomReference ставится в очередь (или, скорее, он может быть поставлен в очередь - как у финализаторов, нет никаких гарантий, что он когда-либо будет, например, если виртуальная машина выйдет, то она не будет ждать). Убедитесь, что вы обрабатываете его очередь (либо в специальном потоке, либо время от времени). Из-за жесткой ссылки на вспомогательный объект вспомогательный объект еще не был собран. Поэтому сделайте все, что вам угодно, для объекта-помощника, затем отбросьте PhantomReference, и помощник в конечном итоге также будет собран.

Есть также finalize (), который выглядит как деструктор, но не ведет себя как один. Обычно это не очень хороший вариант.


Почему PhantomReference вместо WeakReference?
Uckelman

2
@uckelman: если все, что вам нужно, это уведомление, то PhantomReference справится со своей задачей, это в значительной степени то, для чего он предназначен. Дополнительная семантика WeakReference здесь не нужна, и в тот момент, когда ваш ReferenceQueue уведомлен, вы больше не можете восстанавливать объект через WeakReference, поэтому единственная причина его использования - это избавление от необходимости помнить, что PhantomReference существует. Любая дополнительная работа, которую выполняет WeakReference, вероятно, незначительна, но зачем беспокоиться об этом?
Стив Джессоп

Спасибо за намеки на PhantomReference. Это не идеально, но все же лучше, чем ничего.
Foo

@SteveJessop Какая «дополнительная работа», по вашему мнению, имеет слабую ссылку по сравнению с фантомной ссылкой?
Хольгер

6

finalize()Функция деструктор.

Тем не менее, он не должен обычно использоваться, потому что он вызывается после GC, и вы не можете сказать, когда это произойдет (если когда-либо).

Кроме того, требуется более одного GC для освобождения объектов, которые имеют finalize().

Вы должны попытаться очистить логические места в вашем коде, используя try{...} finally{...}операторы!


5

Я согласен с большинством ответов.

Вы не должны зависеть полностью от либо finalizeилиShutdownHook

финализации

  1. JVM не гарантирует, когда этот finalize()метод будет вызван.

  2. finalize()вызывается только один раз потоком GC. Если объект восстанавливается из метода финализации, он finalizeбольше не будет вызываться.

  3. В вашем приложении вы можете иметь несколько живых объектов, для которых сборка мусора никогда не вызывается.

  4. Все, Exceptionчто выброшено методом финализации, игнорируется потоком GC

  5. System.runFinalization(true)и Runtime.getRuntime().runFinalization(true)методы увеличивают вероятность вызова finalize()метода, но теперь эти два метода устарели. Эти методы очень опасны из-за недостаточной безопасности потока и возможного создания тупика.

shutdownHooks

public void addShutdownHook(Thread hook)

Регистрирует новый хук отключения виртуальной машины.

Виртуальная машина Java отключается в ответ на два вида событий:

  1. Программа завершается нормально, когда завершается последний поток, не являющийся демоном, или когда System.exitвызывается метод exit (эквивалентно ), или
  2. Виртуальная машина прерывается в ответ на пользовательское прерывание, такое как ввод ^ C, или общесистемное событие, такое как выход пользователя из системы или завершение работы системы.
  3. Хук отключения - это просто инициализированный, но не запущенный поток. Когда виртуальная машина начинает свою последовательность выключения, она запускает все зарегистрированные обработчики завершения работы в неустановленном порядке и позволяет им запускаться одновременно. Когда все перехватчики завершены, он будет запускать все непроверенные финализаторы, если финализация при выходе была включена.
  4. Наконец, виртуальная машина остановится. Обратите внимание, что потоки демона будут продолжать работать во время последовательности завершения работы, как и потоки, не являющиеся демонами, если завершение работы было инициировано путем вызова метода выхода.
  5. Отключающие крюки также должны быстро закончить свою работу. Когда программа вызывает выход, ожидается, что виртуальная машина будет быстро закрыта и завершит работу.

    Но даже в документации Oracle указано, что

В редких случаях виртуальная машина может прерваться, то есть прекратить работу без выключения

Это происходит, когда виртуальная машина завершается извне, например, при SIGKILLсигнале в Unix или при TerminateProcessвызове в Microsoft Windows. Виртуальная машина также может прервать работу, если собственный метод не работает, например, из-за повреждения внутренних структур данных или попытки доступа к несуществующей памяти. Если виртуальная машина прерывает работу, то нельзя гарантировать, будут ли запущены какие-либо перехватчики завершения работы.

Вывод : используйте try{} catch{} finally{}блоки соответствующим образом и высвобождайте критические ресурсы в finally(}блоке. Во время освобождения ресурсов в finally{}блоке catch Exceptionи Throwable.


4

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


3

Возможно, вы можете использовать блок try ... finally, чтобы завершить объект в потоке управления, в котором вы используете объект. Конечно, это не происходит автоматически, но и уничтожение в C ++ не происходит. Вы часто видите закрытие ресурсов в блоке finally.


1
Это правильный ответ, когда рассматриваемый ресурс имеет одного владельца и никогда не ссылается на него, «украденный» другим кодом.
Стив Джессоп

3

В Ломбоке есть аннотация @Cleanup, которая в основном напоминает деструкторы C ++:

@Cleanup
ResourceClass resource = new ResourceClass();

При обработке (во время компиляции) Lombok вставляет соответствующий try-finallyблок, который resource.close()вызывается, когда выполнение выходит из области видимости переменной. Вы также можете явно указать другой метод освобождения ресурса, например resource.dispose():

@Cleanup("dispose")
ResourceClass resource = new ResourceClass();

2
Преимущество, которое я вижу, состоит в том, что будет меньше вложенности (что может быть значительным, если у вас много объектов, которые требуют «уничтожения»).
Алексей

Блок «попробуй с ресурсом» может иметь несколько ресурсов за один выстрел
Александр - Восстановить Монику

1
Но между ними не может быть инструкций.
Алексей

Справедливая. Я полагаю, что в таком случае рекомендуется отдавать предпочтение try-with-resource даже для нескольких ресурсов, если между ними не требуется инструкция, которая вынуждает вас создавать новые блоки try-with-resource (увеличивая вложение), а затем используйте@Cleanup
Alexander - Reinstate Моника

2

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


2

Здесь много хороших ответов, но есть дополнительная информация о том, почему вы должны избегать использования finalize () .

Если JVM завершает работу из-за System.exit()или Runtime.getRuntime().exit(), финализаторы не будут запускаться по умолчанию. Из Javadoc для Runtime.exit () :

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

Вы можете позвонить, System.runFinalization()но это только делает все возможное, чтобы завершить все незавершенные финализации - не гарантия.

Есть System.runFinalizersOnExit()метод, но не используйте его - он небезопасен, давно устарел.


1

Если вы пишете Java-апплет, вы можете переопределить метод Applet "destroy ()". Это...

 * Called by the browser or applet viewer to inform
 * this applet that it is being reclaimed and that it should destroy
 * any resources that it has allocated. The stop() method
 * will always be called before destroy().

Очевидно, не то, что вы хотите, но может быть то, что ищут другие люди.


1

Просто подумав об исходном вопросе ... который, я думаю, мы можем сделать вывод из всех других изученных ответов, а также из существенной Блоха Эффективной Java , пункт 7, «Избегать финализаторов», ищет решение законного вопроса таким образом, чтобы не подходит для языка Java ...:

... не было бы довольно очевидным решением сделать то, что на самом деле хочет ОП - сохранить все ваши объекты, которые должны быть сброшены, в виде «игрового автомата», на который все другие не сбрасываемые объекты имеют ссылки только через какой-то вид объекта доступа ...

И затем, когда вам нужно «сбросить», вы отключаете существующий манеж и создаете новый: вся паутина объектов в манеже отбрасывается по течению, никогда не возвращается и однажды будет собрана GC.

Если какой-либо из этих объектов Closeable(или нет, но у него есть closeметод), вы можете поместить их Bagв манеж в момент их создания (и, возможно, открытия), и последним действием аксессора перед тем, как отрезать манеж, будет через все Closeablesзакрывающие их ...?

Код, вероятно, будет выглядеть примерно так:

accessor.getPlaypen().closeCloseables();
accessor.setPlaypen( new Playpen() );

closeCloseablesВероятно, это будет метод блокировки, возможно, включающий защелку (например CountdownLatch), для обработки (и ожидания в случае необходимости) любых Runnables/ Callablesв любых потоках, специфичных Playpenдля завершения, в зависимости от ситуации, в частности в потоке JavaFX.


0

Хотя в технологии Java GC были достигнуты значительные успехи, вы все равно должны помнить о своих ссылках. Вспоминаются многочисленные случаи, казалось бы, тривиальных эталонных образцов, которые на самом деле являются гнездами крыс под капотом.

Из вашего поста не похоже, что вы пытаетесь реализовать метод сброса для повторного использования объекта (верно?). Содержат ли ваши объекты какие-либо другие типы ресурсов, которые необходимо очистить (т. Е. Потоки, которые должны быть закрыты, любые объекты пула или заимствованные объекты, которые должны быть возвращены)? Если единственное, что вас беспокоит, это освобождение памяти, то я бы пересмотрел структуру своего объекта и попытался бы убедиться, что мои объекты являются автономными структурами, которые будут очищены во время GC.


0

В Java нет точно класса деструктора, класс уничтожается в Java автоматически сборщиком мусора. но вы можете сделать это, используя ниже один, но это не совсем то же самое:

финализации ()

Был вопрос, который породил углубленное обсуждение финализации , так что вы должны получить больше глубины, если требуется ...


0

Ни в Java нет деструкторов. Основной причиной этого в Java являются сборщики мусора, которые всегда пассивно работают в фоновом режиме, и все объекты создаются в динамической памяти, где и работает GC. В c ++ мы Явно нужно вызвать функцию удаления, так как нет такой вещи, как сборщик мусора.


-3

Раньше я в основном имел дело с C ++, и это также привело меня к поиску деструктора. Я сейчас использую JAVA. То, что я сделал, и это, возможно, не лучший случай для всех, но я реализовал свой собственный деструктор, сбросив все значения до 0 или там по умолчанию через функцию.

Пример:

public myDestructor() {

variableA = 0; //INT
variableB = 0.0; //DOUBLE & FLOAT
variableC = "NO NAME ENTERED"; //TEXT & STRING
variableD = false; //BOOL

}

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

Я знаю, что я не лучший Java-программист, но, похоже, он мне подходит.


Постарайтесь больше использовать неизменяемые объекты, после того как вы «получите это», все это будет иметь больше смысла :)
Р. ван Твиск

5
Это не столько неправильно, сколько бессмысленно - то есть ничего не добивается. Если ваша программа требует сброса необработанных типов для правильной работы, то экземпляры ваших классов имеют неверную область, что означает, что вы, вероятно, переназначаете свойства существующего объекта на свойства нового объекта, не создавая новый Object ().
simo.3792

1
За исключением того, что существует множество потребностей для сброса переменных. Я выбрал имя деструктор, потому что оно соответствует тому, что я делаю. Он чего-то достиг, просто ничего не понимаешь
ChristianGLong
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.