Перейти к содержимому

Где хранятся статические переменные java

  • автор:

Как в java хранятся статические поля?

Как в java хранятся статические поля класса? Знаю что был такой вопрос. (JAVA) В какой области памяти хранятся статические поля класса? Но там сказано что они хранятся в Permanent Generation. А в java 8 ведь он заменен на MetaSpace? Хранятся ли они теперь в MetaSpace? Если да, объясните пожалуйста, почему так? Ведь MetaSpace это область, в которой хранятся метаданные? А статические поля не являются же метаданными.

Отслеживать
задан 15 ноя 2015 в 10:18
Александр Елизаров Александр Елизаров
2,788 2 2 золотых знака 18 18 серебряных знаков 36 36 бронзовых знаков

2 ответа 2

Сортировка: Сброс на вариант по умолчанию

А в java 8 ведь он заменен на MetaSpace?

Да, если мы говорим о HotSpot jvm.

Хранятся ли они теперь в MetaSpace?

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

MetaSpace — это та же PermGen, только с плюшкой в виде динамического расширения. В Permanent Generation предельный размер зависел от многих факторов: количество классов, методов, размер пула констант и т.п. Теперь размер мета пространства ограничен лишь размеров доступной памяти. Если хотим поменять, то юзаем флаг MaxMetaspaceSize .

Таким образом исключили возможность выпадения java.lang.OutOfMemoryError . Ну да, если вы упрётесь в предел RAM, или в предел заданный в MaxMetaspaceSize , то исключение будет.

А статические поля не являются же метаданными.

Ммм. Если посмотреть книгу Java Data Objects, то там сказано, что статические поля — это мета данные.

Java-модель памяти (часть 1)

Привет, Хабр! Представляю вашему вниманию перевод первой части статьи «Java Memory Model» автора Jakob Jenkov.

Прохожу обучение по Java и понадобилось изучить статью Java Memory Model. Перевёл её для лучшего понимания, ну а чтоб добро не пропадало решил поделиться с сообществом. Думаю, для новичков будет полезно, и если кому-то понравится, то переведу остальное.

Первоначальная Java-модель памяти была недостаточно хороша, поэтому она была пересмотрена в Java 1.5. Эта версия модели все ещё используется сегодня (Java 14+).

Внутренняя Java-модель памяти

Java-модель памяти, используемая внутри JVM, делит память на стеки потоков (thread stacks) и кучу (heap). Эта диаграмма иллюстрирует Java-модель памяти с логической точки зрения:

image

Каждый поток, работающий в виртуальной машине Java, имеет свой собственный стек. Стек содержит информацию о том, какие методы вызвал поток. Я буду называть это «стеком вызовов». Как только поток выполняет свой код, стек вызовов изменяется.

Стек потока содержит все локальные переменные для каждого выполняемого метода. Поток может получить доступ только к своему стеку. Локальные переменные, невидимы для всех других потоков, кроме потока, который их создал. Даже если два потока выполняют один и тот же код, они всё равно будут создавать локальные переменные этого кода в своих собственных стеках. Таким образом, каждый поток имеет свою версию каждой локальной переменной.

Все локальные переменные примитивных типов (boolean, byte, short, char, int, long, float, double) полностью хранятся в стеке потоков и не видны другим потокам. Один поток может передать копию примитивной переменной другому потоку, но не может совместно использовать примитивную локальную переменную.

Куча содержит все объекты, созданные в вашем приложении, независимо от того, какой поток создал объект. К этому относятся и версии объектов примитивных типов (например, Byte, Integer, Long и т.д.). Неважно, был ли объект создан и присвоен локальной переменной или создан как переменная-член другого объекта, он хранится в куче.

Ниже диаграмма, которая иллюстрирует стек вызовов и локальные переменные (они хранятся в стеках), а также объекты (они хранятся в куче):

image

Локальная переменная может быть примитивного типа, в этом случае она полностью хранится в стеке потока.

Локальная переменная также может быть ссылкой на объект. В этом случае ссылка (локальная переменная) хранится в стеке потоков, но сам объект хранится в куче.

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

Переменные-члены объекта хранятся в куче вместе с самим объектом. Это верно как в случае, когда переменная-член имеет примитивный тип, так и в том случае, если она является ссылкой на объект.

Статические переменные класса также хранятся в куче вместе с определением класса.

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

Диаграмма, которая иллюстрирует описанное выше:

image

Два потока имеют набор локальных переменных. Local Variable 2 указывает на общий объект в куче (Object 3). Каждый из потоков имеет свою копию локальной переменной со своей ссылкой. Их ссылки являются локальными переменными и поэтому хранятся в стеках потоков. Тем не менее, две разные ссылки указывают на один и тот же объект в куче.

Обратите внимание, что общий Object 3 имеет ссылки на Object 2 и Object 4 как переменные-члены (показано стрелками). Через эти ссылки два потока могут получить доступ к Object 2 и Object 4.

На диаграмме также показана локальная переменная (Local variable 1). Каждая её копия содержит разные ссылки, которые указывают на два разных объекта (Object 1 и Object 5), а не на один и тот же. Теоретически оба потока могут обращаться как к Object 1, так и к Object 5, если они имеют ссылки на оба этих объекта. Но на диаграмме выше каждый поток имеет ссылку только на один из двух объектов.

Итак, мы посмотрели иллюстрацию, теперь давайте посмотрим, как тоже самое выглядит в Java-коде:

Public class MyRunnable implements Runnable() < public void run() < methodOne(); >public void methodOne() < int localVariable1 = 45; MySharedObject localVariable2 = MySharedObject.sharedInstance; //. do more with local variables. methodTwo(); >public void methodTwo() < Integer localVariable1 = new Integer(99); //. do more with local variable. >>
public class MySharedObject < //статическая переменная, указывающая на экземпляр MySharedObject public static final MySharedObject sharedInstance = new MySharedObject(); // переменные-члены, указывающие на два объекта в куче public Integer object2 = new Integer(22); public Integer object4 = new Integer(44); public long member1 = 12345; public long member2 = 67890; >

Метод run() вызывает methodOne(), а methodOne() вызывает methodTwo().

methodOne() объявляет примитивную локальную переменную (localVariable1) типа int и локальную переменную (localVariable2), которая является ссылкой на объект.

Каждый поток, выполняющий методOne(), создаст свою собственную копию localVariable1 и localVariable2 в своих соответствующих стеках. Переменные localVariable1 будут полностью отделены друг от друга, находясь в стеке каждого потока. Один поток не может видеть, какие изменения вносит другой поток в свою копию localVariable1.

Каждый поток, выполняющий методOne(), также создает свою собственную копию localVariable2. Однако две разные копии localVariable2 в конечном итоге указывают на один и тот же объект в куче. Дело в том, что localVariable2 указывает на объект, на который ссылается статическая переменная sharedInstance. Существует только одна копия статической переменной, и эта копия хранится в куче. Таким образом, обе копии localVariable2 в конечном итоге указывают на один и тот же экземпляр MySharedObject. Экземпляр MySharedObject также хранится в куче. Он соответствует Object 3 на диаграмме выше.

Обратите внимание, что класс MySharedObject также содержит две переменные-члены. Сами переменные-члены хранятся в куче вместе с объектом. Две переменные-члены указывают на два других объекта Integer. Эти целочисленные объекты соответствуют Object 2 и Object 4 на диаграмме.

Также обратите внимание, что methodTwo() создает локальную переменную с именем localVariable1. Эта локальная переменная является ссылкой на объект типа Integer. Метод устанавливает ссылку localVariable1 для указания на новый экземпляр Integer. Ссылка будет храниться в своей копии localVariable1 для каждого потока. Два экземпляра Integer будут сохранены в куче и, поскольку метод создает новый объект Integer при каждом выполнении, два потока, выполняющие этот метод, будут создавать отдельные экземпляры Integer. Они соответствуют Object 1 и Object 5 на диаграмме выше.

Обратите также внимание на две переменные-члены в классе MySharedObject типа long, который является примитивным типом. Поскольку эти переменные являются переменными-членами, они все еще хранятся в куче вместе с объектом. В стеке потоков хранятся только локальные переменные.

Где хранятся статические переменные java

В этом примере невозможно инициализировать объект List всеми начальными значениями вместе с объявлением, поэтому используется статический блок.

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

 public class Test < static < System.out.println("Вызов статического блока"); >Test() < System.out.println("Вызов конструктора"); >> class Main < public static void main(String[] args) < Test t1 = new Test(); Test t2 = new Test(); >> 

На консоль будет выведен следующий результат

5.2 Причины использовать статические блоки

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

5.3 Ключевые моменты

  • У класса может быть несколько статических блоков
  • Статические поля и статические блоки выполняются в том же порядке, в котором они присутствуют в классе
  • Из статического блока нельзя получить доступ к не статическим членам класса
  • Статический блок не может пробросить дальше перехваченные исключения, но может их выбросить. При этом всегда будет выкидываться только java.lang.ExceptionInInitializerError
  • Статические поля или переменные инициализируются после загрузки класса в память в том же порядке, в каком они описаны в классе

6. Статический вложенный класс (nested class)

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

Вложенные классы бывают двух видов:

  • вложенные классы, объявленные статическими, называются статическими вложенными классами (static nested classes)
  • вложенные классы, объявленные без static, называются внутренними классами (inner classes)

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

6.1 Пример статического класса

Наиболее широко используемый подход для создания объектов «одиночка» (singleton) — это статический вложенный класс, поскольку он не требует никакой синхронизации, его легко изучить и реализовать:

 public class Singleton < private Singleton() <>private static class SingletonHolder < private static final Singleton INSTANCE = new Singleton(); >public static Singleton getInstance() < return SingletonHolder.instance; >> 

6.2 Причины использовать статический внутренний класс

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

6.3 Ключевые моменты

  • Статические вложенные классы не имеют доступа к какому-либо члену экземпляра внешнего класса — он может получить к ним доступ только через ссылку на объект
  • Статические вложенные классы могут получить доступ ко всем статическим членам внешнего класса, включая приватные
  • Спецификация Java не позволяет объявлять класс верхнего уровня статическим. Только классы внутри других классов могут быть статическими
  • Опять же, этот класс привязан к внешнему классу и если внешний наследуется другим классом, то этот не будет унаследован. При этом данный класс можно наследовать, как и он может наследоваться от любого другого класса и имплементировать интерфейс
  • По сути статический вложенный класс ничем не отличается от любого другого внутреннего класса за исключением того, что его объект не содержит ссылку на создавший его объект внешнего класса
  • Для использования статических методов/переменных/классов нам не нужно создавать объект данного класса
  • Яркий пример вложенного статического класса — HashMap.Entry, который предоставляет структуру данных внутри HashMap. Стоит заметить, также как и любой другой внутренний класс, вложенные классы находятся в отдельном файле .class. Таким образом, если вы объявили пять вложенных классов в вашем главном классе, у вас будет 6 файлов с расширением .class

7. Константы

Говоря о ключевом слове static, нельзя не упомянуть о его применении в определении констант — переменных, которые никогда не изменяются.

В языке Java существует зарезервированное слово «const», но оно не используется, и Java не поддерживает константы на уровне языка. Выход из ситуации имеется: для определения константы необходимо добавить модификаторы «static final» к полю класса.

Константы — это статические финальные поля, содержимое которых неизменно. Это относится к примитивам, String, неизменяемым типам и неизменяемым коллекциям неизменяемых типов. Если состояние объекта может измениться, он не является константой.

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

Что такое ключевое слово static?

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

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

  • java.util.Objects содержит статические служебные операции для метода объекта.
  • java.util.Collections состоит исключительно из статических методов, которые работают с коллекциями или возвращают их.

Где можно употреблять ключевое слово static?

Мы можем использовать это ключевое слово в четырех контекстах:

  • статические методы;
  • статические переменные;
  • статические вложенные классы;
  • статические блоки.

Рассмотрим подробнее каждый из перечисленных пунктов.

Статические методы

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

public class StaticExample public static void add(int a, int b) System.out.println(a+b); 
>

public void multiply(int a, int b) System.out.println(a*b);
>
public static void main(String[] args) /** Вызов статического метода **/
StaticExample.add(1, 2);

/** Вызов не-статического метода**/
StaticExample staticExample = new StaticExample();
staticExample.multiply(1, 2);
>
>

В приведенном выше примере метод add — статический, поэтому его можно вызывать напрямую с именем класса. Нет необходимости создавать новый экземпляр класса StaticExample . Но метод multiply не является статическим. Таким образом, для нестатического метода необходимо создать новый экземпляр класса StaticExample .

Статические переменные

При создании объектов класса в Java каждый из них содержит собственную копию всех переменных класса.

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

public class VariableExample public String normalVariable = null; 
public static String staticVariable = null;
public static void main(String[] args) VariableExample firstExample = new VariableExample();
firstExample.normalVariable = "Hello";
firstExample.staticVariable = "Hello";//Это то же самое, что VariableExample.staticVariable = "Hello"
VariableExample secondExample = new VariableExample();
System.out.println("normalVariable: "+ secondExample.normalVariable);
System.out.println("staticVariable: "+ secondExample.staticVariable);
>
>

В приведенном выше примере normalVariable — переменная класса, а staticVariable — статическая переменная. Если вы объявите переменную, как показано ниже:

firstExample.staticVariable = “Hello”

Это похоже на доступ к статической переменной через имя класса:

VariableExample.staticVariable = “Hello”

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

normalVariable: nullstaticVariable: Hello

Статические переменные — редкость в Java. Вместо них применяют статические константы. Они определяются ключевым словом static final и представлены в верхнем регистре. Вот почему некоторые предпочитают использовать верхний регистр и для статических переменных.

Статические блоки

Здесь мы видим статический блок с синтаксисом:

static >

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

public class VariableExample public static String staticVariable = null; 

static staticVariable = "Hello Variable";
>

public static void main(String[] args) System.out.println(staticVariable);
>
>

Класс может содержать несколько статических блоков, а каждый из них выполняется в той же последовательности, в которой они написаны в коде.

public class VariableExample public static String staticVariable = null; 

static staticVariable = "Hello Variable";
>

static staticVariable = "Hello Again";
>

public static void main(String[] args) System.out.println(staticVariable);
>
>

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

Hello Again

Вложенный статический класс

В Java можно объявить класс внутри другого класса. Такие классы называются вложенными классами. Они бывают двух типов: статические и нестатические.

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

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

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

public class InnerClassExample public String outerElement; 
private String outerPrivateElement;
private void outerMethod()
>
public static class MyStaticInnerClass public void myFunction() System.out.println("calling static class inner function");
>
>
public class MyInnerClass public void myAnotherFunction() outerElement = "sample";
outerPrivateElement ="Hello Private";
outerMethod();
>
>
public static void main(String[] args) /**Вызов статического внутренего класса **/
InnerClassExample.MyStaticInnerClass staticInner = new InnerClassExample.MyStaticInnerClass();
staticInner.myFunction();
/*Вызов внутреннего класса **/
InnerClassExample.MyInnerClass innerClass = new InnerClassExample().new MyInnerClass();
innerClass.myAnotherFunction();
>
>

В приведенном выше примере видно, что внутренний класс MyInnerClass может получить доступ ко всем методам и переменным внешнего, включая приватные переменные. Статическому внутреннему классу, напротив, недоступны какие-либо методы или переменные внешнего класса.

Где в памяти Java хранятся статические классы и переменные?

Вплоть до 8-й версии Java статические методы и переменные хранились в пространстве permgen. Но потом было введено новое пространство памяти, называемое метапространством — в нем хранятся все эти имена и поля класса, методы класса с байт-кодом методов, пул констант, JIT-оптимизации и т. д. Причина удаления permgen в Java 8.0 в том, что очень сложно предсказать необходимый размер permgen.

Зачем нужно ключевое слово final?

В Java при объявлении сущности можно воспользоваться ключевым словом final . Оно предполагает, что значение не может быть изменено в будущем. Ключевое слово final может применяться для переменной, метода и класса.

  • Конечная переменная предназначена для создания постоянных значений.
  • Конечный метод предотвращает переопределение метода.
  • Конечный класс предотвращает наследование.

Что такое конечная переменная и когда ей стоит воспользоваться?

Когда переменная объявляется с помощью ключевого слова final , ее значение не может быть изменено. По сути, это константа. Это также означает, что конечную переменную необходимо инициализировать. Если конечная переменная является ссылкой, то ее нельзя будет повторно привязать к другому объекту, но внутреннее состояние объекта, на которое указывает эта ссылочная переменная, может быть изменено, т.е. вы можете добавлять или удалять элементы из конечного массива или конечной коллекции. Рекомендуется именовать конечные переменные целиком в верхнем регистре и с подчеркиванием для разделения слов.

Существует три способа инициализации конечной переменной.

  • Инициализировать конечную переменную, когда она будет объявлена. Это самый распространенный подход. Если конечная переменная не инициализирована при объявлении, она называется пустой конечной переменной. Ниже приведены два способа инициализации пустой конечной переменной.
  • Пустая конечная переменная может быть инициализирована внутри блока инициализатора экземпляра или внутри конструктора. Если в классе несколько конструкторов, то переменную необходимо инициализировать во всех из них, в противном случае во время компиляции появится ошибка.
  • Пустая конечная статическая переменная может быть инициализирована в статическом блоке.
public class FinalVariableExample public final String MY_VARIABLE_1 = "Variable Initialized"; 
public final static String MY_VARIABLE_2;
public final String MY_VARIABLE_3;
public final String MY_VARIABLE_4 ;

static MY_VARIABLE_2 = "Static Varible Initialized";
>

MY_VARIABLE_3 = "block Initialized";
>

public FinalVariableExample() MY_VARIABLE_4 = "Constructor Initialized";
>

>

Когда следует применять конечную переменную

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

Где использовать конечные классы

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

Первое, безусловно, заключается в предотвращении наследования, так как конечные классы не могут быть расширены. Например, все классы-оболочки, такие как Integer , Float и т. д. — конечные классы.

public final class MyFinalClass 
>

public class MyClass extends MyFinalClass
>

При попытке расширить конечный класс компилятор выдаст следующее исключение:

Другое применение final с классами заключается в создании неизменяемого класса, подобного предопределенному классу String . Нельзя сделать класс неизменяемым, не сделав его конечным.

Когда использовать конечные методы

Когда метод объявляется с ключевым словом final , он называется конечным методом. Такой метод не может быть переопределен. Это присутствует в классе Object — ряд его методов конечные. С ключевым словом final объявляются методы, для которых необходимо следовать одной и той же реализации во всех производных классах. Фрагмент ниже иллюстрирует метод с ключевым словом final :

public class MyParentClass public final void myFinalMethod() . 
>
>

public class MyClass extends MyParentClass public final void myFinalMethod() .
>
>

Здесь происходит попытка переопределить метод final из родительского класса. Java этого не позволяет и выдает следующее исключение:

Надеемся, теперь вы хорошо поняли ключевые слова static и final .

  • Используй Async/Await в JavaScript, как профессионал
  • Введение в байт-код Java
  • Жизненный цикл потока в Java

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *