Когда метод finalize () вызывается в Java?


330

Мне нужно знать, когда finalize()метод вызывается в JVM. Я создал тестовый класс, который записывает в файл при finalize()вызове метода путем его переопределения. Это не выполнено. Кто-нибудь может сказать мне причину, почему это не выполняется?


34
Как примечание: финализация помечена как устаревшая в Java 9
Reg


если что-то имеет ссылку на ваш объект или даже класс. finalize()и сборка мусора не имеет никакого влияния.
ha9u63ar

Ответы:


273

В общем, лучше не полагаться на finalize()уборку и т. Д.

Согласно Javadoc (который стоило бы прочитать), это:

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

Как отметил Иоахим, это может никогда не произойти в жизни программы, если объект всегда доступен.

Кроме того, сборщик мусора не гарантированно работает в любое конкретное время. В общем, то, что я пытаюсь сказать, это finalize(), вероятно, не лучший метод для использования в целом, если нет необходимости в чем-то конкретном.


19
Другими словами (просто для разъяснения будущим читателям), он никогда не вызывается для основного класса, так как, когда основной класс закрывается, мусор собирать не нужно. Операционная система очищает все, что использовалось приложением.
Марк Джеронимус

113
«не лучший метод для использования ... если нет необходимости в чем-то конкретном» - это предложение применимо к 100% всего, поэтому оно не помогает. Ответ Иоахима Зауэра намного лучше
BT

1
@ Zom-B ваш пример полезен для пояснения, но просто для того, чтобы быть педантичным, предположительно его можно было бы вызвать в основном классе, если основной класс создает поток, не являющийся демоном, а затем возвращает?
Том Г

3
Так в каких ситуациях finalizeбыло бы полезно?
nbro

3
@MarkJeronimus - На самом деле, это не имеет значения. finalize()Метод на для основного класса вызывается , когда >> << экземпляр класса является сбор мусора, не тогда , когда основной метод завершается. И, кроме того, главный класс может быть собран мусором до завершения приложения; например, в многопоточном приложении, где «основной» поток создает другие потоки, а затем возвращает. (На практике потребуется нестандартный загрузчик классов ....)
Стивен С.

379

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

Обратите внимание, что вполне возможно, что объект никогда не получает мусор (и, следовательно finalize, никогда не вызывается). Это может произойти, когда объект никогда не становится подходящим для gc (потому что он доступен в течение всего времени жизни JVM) или когда фактически не выполняется сборка мусора между моментом, когда объект становится приемлемым, и временем, когда JVM прекращает работу (это часто происходит с простыми тестовые программы).

Есть способы заставить JVM работать finalizeс объектами, которые еще не были вызваны, но их использование тоже не очень хорошая идея (гарантии этого метода также не очень сильны).

Если вы полагаетесь на finalizeправильную работу вашего приложения, то вы делаете что-то не так. finalizeследует использовать только для очистки (обычно не Java) ресурсов. И это именно потому, что JVM не гарантирует, что finalizeкогда-либо вызывается на любом объекте.


2
@Rajesh. Нет. Это не проблема "продолжительности жизни". Вы можете поместить вашу программу в бесконечный цикл (на годы), и если сборщик мусора не нужен, он никогда не запустится.
Крис Кантрелл

5
@VikasVerma: идеальная замена - ничто: они вам не нужны. Единственный случай, когда это имеет смысл, - это если ваш класс управляет каким-то внешним ресурсом (например, TCP / IP-соединением, файлом ... всем, что не может обработать Java GC). В этих случаях Closableинтерфейс (и идея, лежащая в его основе), вероятно, то, что вам нужно: .close()закройте / отмените ресурс и потребуйте, чтобы пользователь вашего класса вызвал его в нужное время. Вы , возможно , хотите добавить finalizeметод «просто быть сохранить», но это будет скорее инструмент отладки , чем фактическое исправление (потому что это не достаточно надежным).
Йоахим Зауэр

4
@Dragonborn: на самом деле это совсем другой вопрос, и его следует задавать отдельно. Существуют перехватчики отключения, но они не гарантируются, если JVM неожиданно завершает работу (иначе происходит сбой). Но их гарантии значительно сильнее, чем гарантии для финализаторов (и они также безопаснее).
Йоахим Зауэр

4
В последнем абзаце говорится, что использовать его только для очистки ресурсов, хотя нет никаких гарантий, что он когда-либо будет вызван. Этот оптимизм говорит? Я бы предположил, что что-то ненадежное, так как это также не подходит для очистки ресурсов.
Йерун Ванневел

2
Финализаторы иногда могут сохранить день ... У меня был случай, когда сторонняя библиотека использовала FileInputStream, никогда не закрывая его. Мой код вызывал код библиотеки, а затем попытался переместить файл, но не удалось, потому что он все еще был открыт. Мне пришлось принудительно вызвать call, System.gc()чтобы вызвать FileInputStream :: finalize (), затем я мог переместить файл.
Elist

73
protected void finalize() throws Throwable {}
  • каждый класс наследует finalize()метод от java.lang.Object
  • метод вызывается сборщиком мусора, когда он определяет, что ссылки на объект больше не существуют
  • метод Object finalize не выполняет никаких действий, но может быть переопределен любым классом
  • обычно он должен быть переопределен для очистки не Java-ресурсов, то есть закрытия файла
  • при переопределении finalize()хорошей практикой программирования является использование оператора try-catch-finally и всегда вызывать super.finalize(). Это мера безопасности, гарантирующая, что вы случайно не пропустите закрытие ресурса, используемого объектами, вызывающими класс

    protected void finalize() throws Throwable {
         try {
             close();        // close open files
         } finally {
             super.finalize();
         }
     }
  • любое исключение, возникающее finalize()во время сборки мусора, останавливает завершение, но в противном случае игнорируется

  • finalize() никогда не запускается более одного раза на любом объекте

цитируется с: http://www.janeg.ca/scjp/gc/finalize.html

Вы также можете проверить эту статью:


25
Статья на JavaWorld, на которую вы ссылаетесь, была написана в 1998 году и содержит несколько интересных советов, в частности, предложение о вызове System.runFinalizersOnExit () для обеспечения работы финализаторов до выхода из JVM. Этот метод в настоящее время устарел, с комментарием «Этот метод небезопасен. Это может привести к вызову финализаторов для живых объектов, в то время как другие потоки одновременно манипулируют этими объектами, что приводит к ошибочному поведению или тупику ». Так что я не буду этого делать.
Грег Чабала

3
Так как runFinalizerOnExit () НЕ является потокобезопасным, то, что можно сделать, это Runtime.getRuntime (). AddShutdownHook (new Thread () {public void run () {destroyMyEnclosingClass ();}}); в конструкторе класса.
Устаман Сангат

2
@ Ustaman Sangat Это способ сделать это, но помните, что он устанавливает ссылку на ваш экземпляр из shutdownHook, который в значительной степени гарантирует, что ваш класс никогда не будет собирать мусор. Другими словами, это утечка памяти.
Pieroxy

1
@pieroxy, в то время как я согласен со всеми остальными здесь относительно того, чтобы не использовать finalize () ни для чего, я не понимаю, почему должна быть ссылка из ловушки завершения работы. Можно иметь мягкую ссылку.
Устаман Сангат

25

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

Вероятно, вам нужно сочетание finallyи метод очистки, как в:

MyClass myObj;

try {
    myObj = new MyClass();

    // ...
} finally {

    if (null != myObj) {
        myObj.cleanup();
    }
}

20

Проверьте Effective Java, 2-е издание, стр. 27. Пункт 7: Избегайте финализаторов

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

Чтобы завершить ресурс, используйте вместо этого try-finally:

// try-finally block guarantees execution of termination method
Foo foo = new Foo(...);
try {
    // Do what must be done with foo
    ...
} finally {
    foo.terminate(); // Explicit termination method
}

3
или используйте попытку с ресурсами
Габриэль Гарсия

3
Это предполагает, что время жизни объекта находится в области действия одной функции. Что, конечно, не тот случай, на который ссылается ФП, и не тот случай, который вообще нужен кому-то. Подумайте «подсчет возвращаемого значения механизма кэширования». Вы хотите освободить запись кэша при освобождении последней ссылки, но не знаете, когда освобождается последняя ссылка. finalize () может уменьшить счетчик ссылок, например ... но если вам требуется, чтобы ваши пользователи явно вызывали свободную функцию, вы просите утечки памяти. обычно я просто делаю и то, и другое (функция релиза + двойная проверка в финализации ...) ...
Эрик Аронести,

16

Когда finalize()метод вызывается в Java?

Метод finalize будет вызван после того, как GC обнаружит, что объект более недоступен, и до того, как он фактически освободит память, используемую объектом.

  • Если объект никогда не станет недоступным, finalize()его никогда не вызовут.

  • Если GC не работает, то finalize()никогда не может быть вызван. (Обычно GC запускается только тогда, когда JVM решает, что мусора, вероятно, достаточно, чтобы оно того стоило.)

  • Может потребоваться более одного цикла GC, прежде чем GC определит, что определенный объект недоступен. (Java GC, как правило, являются сборщиками поколений ...)

  • Как только GC обнаруживает, что объект недоступен и завершен, он помещается в очередь завершения. Завершение обычно происходит асинхронно с обычным GC.

(Спецификация JVM фактически позволяет JVM никогда не запускать финализаторы ... при условии, что она не освобождает пространство, используемое объектами. JVM, который был реализован таким образом, будет поврежден / бесполезен, но если это поведение "разрешено" .)

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

Единственное законное использование для финализации - очистить ресурсы, связанные с объектами, которые были потеряны кодом приложения. Даже тогда вы должны попытаться написать код приложения, чтобы он не терял объекты в первую очередь. (Например, используйте Java 7+ try-with-resources, чтобы close()всегда вызывать ...)


Я создал тестовый класс, который записывает в файл при вызове метода finalize (), переопределяя его. Это не выполнено. Кто-нибудь может сказать мне причину, почему это не выполняется?

Трудно сказать, но есть несколько возможностей:

  • Объект не является сборщиком мусора, потому что он все еще доступен.
  • Объект не является сборщиком мусора, потому что сборщик мусора не запускается до завершения теста.
  • Объект найден GC и помещен GC в очередь на финализацию, но финализация не завершена до завершения теста.

10

Поскольку существует неопределенность в вызове метода finalize () из JVM (не уверен, будет ли выполнен finalize (), который переопределяется, будет или нет), для целей изучения лучший способ наблюдать за тем, что происходит при вызове finalize (), состоит в том, чтобы заставить JVM вызывать сборщик мусора командой System.gc() .

В частности, finalize () вызывается, когда объект больше не используется. Но когда мы пытаемся вызвать его, создавая новые объекты, нет уверенности в его вызове. Поэтому для определенности мы создаем nullобъект, cкоторый, очевидно, не будет использоваться в будущем, поэтому мы видим cвызов объекта finalize.

пример

class Car {

    int maxspeed;

    Car() {
        maxspeed = 70;
    }

    protected void finalize() {

    // Originally finalize method does nothing, but here we override finalize() saying it to print some stmt
    // Calling of finalize is uncertain. Difficult to observe so we force JVM to call it by System.gc(); GarbageCollection

        System.out.println("Called finalize method in class Car...");
    }
}

class Bike {

    int maxspeed;

    Bike() {
        maxspeed = 50;
    }

    protected void finalize() {
        System.out.println("Called finalize method in class Bike...");
    }
}

class Example {

    public static void main(String args[]) {
        Car c = new Car();
        c = null;    // if c weren`t null JVM wouldn't be certain it's cleared or not, null means has no future use or no longer in use hence clears it
        Bike b = new Bike();
        System.gc();    // should clear c, but not b
        for (b.maxspeed = 1; b.maxspeed <= 70; b.maxspeed++) {
            System.out.print("\t" + b.maxspeed);
            if (b.maxspeed > 50) {
                System.out.println("Over Speed. Pls slow down.");
            }
        }
    }
}

Вывод

    Called finalize method in class Car...
            1       2       3       4       5       6       7       8       9
    10      11      12      13      14      15      16      17      18      19
    20      21      22      23      24      25      26      27      28      29
    30      31      32      33      34      35      36      37      38      39
    40      41      42      43      44      45      46      47      48      49
    50      51Over Speed. Pls slow down.
            52Over Speed. Pls slow down.
            53Over Speed. Pls slow down.
            54Over Speed. Pls slow down.
            55Over Speed. Pls slow down.
            56Over Speed. Pls slow down.
            57Over Speed. Pls slow down.
            58Over Speed. Pls slow down. 
            59Over Speed. Pls slow down.
            60Over Speed. Pls slow down.
            61Over Speed. Pls slow down.
            62Over Speed. Pls slow down.
            63Over Speed. Pls slow down.
            64Over Speed. Pls slow down.
            65Over Speed. Pls slow down.
            66Over Speed. Pls slow down.
            67Over Speed. Pls slow down.
            68Over Speed. Pls slow down.
            69Over Speed. Pls slow down.
            70Over Speed. Pls slow down.

Примечание. Даже после печати до 70 и после того, как объект b не используется в программе, существует неопределенность, что b очищается или не удаляется JVM, так как «Вызванный метод финализации в классе Bike ...» не печатается.


10
Вызов System.gc();не является гарантией того, что сборка мусора действительно будет запущена.
Саймон Форсберг,

3
Также не гарантируется , что тип коллекции будет работать. Уместно, так как большинство Java GC являются сборщиками поколений.
Стивен С

5

finalize распечатает счет для создания класса.

protected void finalize() throws Throwable {
    System.out.println("Run F" );
    if ( checkedOut)
        System.out.println("Error: Checked out");
        System.out.println("Class Create Count: " + classCreate);
}

основной

while ( true) {
    Book novel=new Book(true);
    //System.out.println(novel.checkedOut);
    //Runtime.getRuntime().runFinalization();
    novel.checkIn();
    new Book(true);
    //System.runFinalization();
    System.gc();

Как вы видете. Следующий вывод показывает, что gc был выполнен первый раз, когда счетчик классов равен 36.

C:\javaCode\firstClass>java TerminationCondition
Run F
Error: Checked out
Class Create Count: 36
Run F
Error: Checked out
Class Create Count: 48
Run F

4

В последнее время, борясь с методами финализатора (чтобы избавиться от пулов соединений во время тестирования), я должен сказать, что финализатору не хватает многих вещей. Используя VisualVM для наблюдения, а также используя слабые ссылки для отслеживания реального взаимодействия, я обнаружил, что в среде Java 8 верно следующее: Oracle JDK, Ubuntu 15):

  • Finalize не вызывается сразу, как только Finalizer (часть GC) владеет ссылкой.
  • Сборщик мусора по умолчанию объединяет недоступные объекты
  • Finalize вызывается массово, указывая на детали реализации, что существует определенный этап, когда сборщик мусора освобождает ресурсы.
  • Вызов System.gc () часто не приводит к тому, что объекты завершаются чаще, а просто приводит к тому, что Finalizer быстрее узнает о недоступном объекте
  • Создание дампа потока почти всегда приводит к запуску финализатора из-за высоких издержек кучи во время выполнения дампа кучи или какого-либо другого внутреннего механизма
  • Швы завершения должны быть связаны либо с требованиями к памяти (освобождают больше памяти), либо со списком объектов, помеченных для роста завершения до определенного внутреннего предела. Так что, если у вас есть много объектов, которые будут завершены, фаза завершения будет запускаться чаще и раньше, по сравнению с несколькими
  • Были обстоятельства, когда System.gc () вызывал финализацию напрямую, но только если ссылка была локальной и недолгой. Это может быть связано с поколением.

Последняя мысль

Метод Finalize ненадежен, но может использоваться только для одной цели. Вы можете убедиться, что объект был закрыт или утилизирован до того, как он был собран мусором, что позволяет реализовать отказоустойчивый объект, если объекты с более сложным жизненным циклом, включающим действие с истекшим сроком службы, обрабатываются правильно. Это единственная причина, по которой я могу придумать, стоит того, чтобы ее переопределить.


2

Объект получает право на сборку мусора или сборщик мусора, если он недоступен из каких-либо активных потоков или статических ссылок, другими словами, вы можете сказать, что объект получает право на сборку мусора, если все его ссылки равны нулю. Циклические зависимости не считаются ссылками, поэтому, если у объекта A есть ссылка на объект B, а у объекта B есть ссылка на объект A, и у них нет никакой другой активной ссылки, тогда и объекты A, и B будут иметь право на сборку мусора. Обычно объект получает право на сборку мусора в Java в следующих случаях:

  1. Все ссылки на этот объект явно установлены на нуль, например, объект = нуль
  2. Объект создается внутри блока, и ссылка выходит из области действия после выхода из этого блока.
  3. Родительскому объекту присваивается значение NULL, если объект содержит ссылку на другой объект и когда вы устанавливаете нулевую ссылку на объект контейнера, дочерний или содержащийся объект автоматически получает право на сборку мусора.
  4. Если объект имеет только живые ссылки через WeakHashMap, он будет иметь право на сборку мусора.

Если finalполе объекта используется многократно во время медленного вычисления, и объект никогда не будет использоваться после этого, будет ли объект сохранен живым до тех пор, пока исходный код не запросит поле, или JIT может скопировать поле в временная переменная, а затем отказаться от объекта до вычисления?
суперкат

@supercat: оптимизатор может переписать код в форму, где объект не создается в первую очередь; в этом случае он может быть завершен сразу после завершения конструктора, если только синхронизация не вызовет упорядочение между использованием объекта и финализатором.
Хольгер

@Holger: В тех случаях, когда JIT может видеть все, что когда-либо случится с объектом, между его созданием и отказом, я не вижу никакой причины для JIT запускать финализатор рано. Реальный вопрос заключается в том, что код должен сделать, чтобы финализатор не мог запускаться в конкретном методе. В .NET есть GC.KeepAlive()функция, которая ничего не делает, кроме как заставить GC предположить, что она может использовать объект, но я не знаю такой функции в Java. Можно использовать volatileпеременную для этой цели, но использование переменной исключительно для этой цели может показаться расточительным.
суперкат

@supercat: JIT не запускает финализацию, он просто упорядочивает код так, чтобы он не сохранял ссылку, хотя, возможно, было бы полезно напрямую поставить в очередь FinalizerReference, так что ему не нужен цикл GC, чтобы выяснить, что нет Ссылки. Синхронизация достаточна для того, чтобы обеспечить связь «до того как случится» ; поскольку финализация может (на самом деле) выполняться в другом потоке, она все равно формально необходима в любом случае. Java 9 собирается добавить Reference.reachabilityFence...
Хольгер

@Holger: Если JIT оптимизирует создание объекта, то Finalizeбудет вызван только один, если JIT создаст код, который сделал это напрямую. Можно ли ожидать код синхронизации для объектов, которые ожидают использования только в одном потоке? Если объект выполняет какое-либо действие, которое необходимо отменить перед тем, как его прекратить (например, открытие соединения с сокетом и получение исключительного использования для ресурса на другом конце), если финализатор закроет соединение, пока код все еще использует сокет, будет катастрофой , Было бы нормально, чтобы код использовал синхронизацию ...
supercat

2

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


3
Неправильно. Вы говорите, что объект завершается, когда он становится недоступным. Это на самом деле вызывается, когда метод фактически собран.
Стивен С

2

Иногда, когда он уничтожен, объект должен совершить действие. Например, если объект имеет не-Java-ресурс, такой как дескриптор файла или шрифт, вы можете проверить, что эти ресурсы освобождены, прежде чем уничтожать объект. Для управления такими ситуациями java предлагает механизм, называемый «финализация». Завершив его, вы можете определить конкретные действия, которые происходят, когда объект собирается удалить из сборщика мусора. Чтобы добавить финализатор в класс, просто определите метод finalize () . Время выполнения Java вызывает этот метод всякий раз, когда он собирается удалить объект этого класса. В методе финализации () вы указываете действия, которые должны быть выполнены перед уничтожением объекта. Сборщик мусора периодически ищет объекты, которые больше не ссылаются ни на какое запущенное состояние или косвенно на любой другой объект со ссылкой. Перед освобождением актива среда выполнения Java вызывает метод finalize () для объекта. finalize () имеет следующую общую форму:

protected void finalize(){
    // This is where the finalization code is entered
}

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


1

Класс, в котором мы переопределяем метод финализации

public class TestClass {    
    public TestClass() {
        System.out.println("constructor");
    }

    public void display() {
        System.out.println("display");
    }
    @Override
    public void finalize() {
        System.out.println("destructor");
    }
}

Возможности вызова метода финализации

public class TestGarbageCollection {
    public static void main(String[] args) {
        while (true) {
            TestClass s = new TestClass();
            s.display();
            System.gc();
        }
    }
}

когда память перегружена объектами дампа, gc вызовет метод finalize

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


0

Java позволяет объектам реализовывать метод finalize (), который может вызываться.

Метод finalize () вызывается, если сборщик мусора пытается собрать объект.

Если сборщик мусора не запускается, метод не вызывается.

Если сборщик мусора не может собрать объект и пытается запустить его снова, метод не вызывается во второй раз.

На практике вы вряд ли будете использовать его в реальных проектах.

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

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

Источник


0

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

Пример:

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


0

Как указано в https://wiki.sei.cmu.edu/confluence/display/java/MET12-J.+Do+not+use+finalizers ,

Не существует фиксированного времени, когда должны выполняться финализаторы, потому что время выполнения зависит от виртуальной машины Java (JVM). Единственная гарантия состоит в том, что любой метод финализатора, который выполняется, будет делать это когда-то после того, как связанный объект станет недоступным (обнаружен во время первого цикла сборки мусора), и когда-нибудь до того, как сборщик мусора освободит хранилище связанного объекта (во время второго цикла сборщика мусора) , Выполнение финализатора объекта может быть отложено на произвольно долгое время после того, как объект станет недоступным. Следовательно, вызов критичной по времени функциональности, такой как закрытие файловых дескрипторов, в методе finalize () объекта является проблематичным.


-3

Попробуйте запустить эту Программу для лучшего понимания

public class FinalizeTest 
{       
    static {
        System.out.println(Runtime.getRuntime().freeMemory());
    }

    public void run() {
        System.out.println("run");
        System.out.println(Runtime.getRuntime().freeMemory());
    }

     protected void finalize() throws Throwable { 
         System.out.println("finalize");
         while(true)
             break;          
     }

     public static void main(String[] args) {
            for (int i = 0 ; i < 500000 ; i++ ) {
                    new FinalizeTest().run();
            }
     }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.