Чи зберігається масив Java примітивів у стеку або купі?


85

У мене є таке оголошення масиву:

int a[];

Ось aмасив примітивного intтипу. Де зберігається цей масив? Чи зберігається це в купі чи стеку? Це примітивний тип int, усі примітивні типи не зберігаються в купі.


38
Це не масив. Це посилання на масив. Посилання може зберігатися в купі, якщо воно є членом класу або об'єкта, або в стеку, якщо це локальна змінна в методі. А примітивні типи можуть зберігатися в купі, якщо вони є членами класу або об'єкта.
дядькоO

Відповіді:


143

Як казали гурукулки, він зберігається на купі. Однак у вашій публікації випливає непорозуміння, ймовірно через те, що якась доброзичлива людина пропагує міф про те, що "примітиви завжди живуть у стосі". Це неправда. Локальні змінні мають свої значення в стеку, але не всі примітивні змінні є локальними ...

Наприклад, розглянемо це:

public class Foo
{
    int value;
}
...

public void someOtherMethod()
{
    Foo f = new Foo();
    ...
}

Зараз, де f.valueживе? Міф міг би припустити, що він знаходиться в стеку - але насправді це частина нового Fooоб’єкта і живе в купі 1 . (Зверніть увагу, що значення fсаме по собі є посиланням і живе у стеку.)

Звідти це простий крок до масивів. Ви можете думати про масив просто як про велику кількість змінних - так new int[3]це трохи як мати клас такої форми:

public class ArrayInt3
{
    public readonly int length = 3;
    public int value0;
    public int value1;
    public int value2;
}

1 Насправді це складніше, ніж це. Розрізнення стеку / купи в основному є деталлю реалізації - я вважаю, що деякі JVM, можливо експериментальні, можуть визначити, коли об'єкт ніколи не "втікає" від методу, і можуть виділити весь об'єкт у стек. Однак це концептуально на купі, якщо ви вирішите доглядати.


1
Про " аналітичний аналіз " на Java: blog.juma.me.uk/2008/12/17/objects-with-no-allocation-overhead Там сказано, що він присутній з моменту дострокового виходу JDK 6, оновлення 14, і включено за замовчуванням з JDK 6, оновлення 23.
Гвідо,

це щось змінює, якщо масив є загальнодоступним статичним фіналом? Тоді це не повинно бути частиною постійного пулу?
Малахіаш

@ Малахіаш: Ні. Масив ніколи не є константою.
Джон Скіт,

@JonSkeet: У всіх версіях бібліотеки Java, про які мені відомо, кожна Stringпідтримка char[]. Я вважаю, що буквальні рядки зберігаються у загальнодоступному пулі постійних; для оптимізації GC це означало б, що масиви резервної копії слід зберігати так само (інакше пул констант доведеться сканувати під час будь-якого циклу GC, де масив резервної копії в іншому випадку буде прийнятним для збору).
supercat

@supercat: Так, це мало б сенс. Але будь-який масив ви оголошуєте себе ніколи не закінчується, як частина постійного басейну.
Джон Скіт,

37

Він буде зберігатися в купі

тому що масив є об'єктом у Java.

EDIT : якщо у вас є

int [] testScores; 
testScores = new int[4];

Подумайте про цей код, як про те, що він сказав компілятору: "Створіть об’єкт масиву, який міститиме чотири вставки, і призначте його посилальній змінній із назвою testScores. Крім того, продовжуйте і встановлюйте кожен intелемент нульовим. Дякую."


2
І посилальна змінна з іменем testScores (вказуючи на масив у купі) буде в стеку.
Закі

11
Код говорить лише "Дякую", якщо ви надаєте -gопцію компілятору. В іншому випадку його буде оптимізовано.
моб

1
@mob Ви припускаєте, що це єдиний код. Можливо, краще припустити, що ці два рядки є частиною більшої програми, яка фактично використовує масив.
Code-Apprentice

22

Це масив примітивних типів, який сам по собі не є примітивним. Хорошим емпіричним правилом є те, що коли задіяно нове ключове слово, результат буде в купі.


18

Я просто хотів поділитися кількома тестами, які я провів з цього питання.

Масив розміром 10 мільйонів

public static void main(String[] args) {
    memInfo();
    double a[] = new double[10000000];
    memInfo();
}

Вихід:

------------------------
max mem = 130.0 MB
total mem = 85.0 MB
free mem = 83.6 MB
used mem = 1.4 MB
------------------------
------------------------
max mem = 130.0 MB
total mem = 130.0 MB
free mem = 48.9 MB
used mem = 81.1 MB
------------------------

Як бачите, розмір використовуваної купи збільшується на ~ 80 МБ, що становить 10 м * розмір (подвійний).

Але якщо ми маємо використовувати Double замість double

public static void main(String[] args) {
    memInfo();
    Double a[] = new Double[10000000];
    memInfo();
}

Вихід покаже 40 Мб. У нас є лише подвійні посилання, вони не ініціалізовані.

Наповнивши його Double

public static void main(String[] args) {
    memInfo();
    Double a[] = new Double[10000000];      
    Double qq = 3.1d;
    for (int i = 0; i < a.length; i++) {
        a[i] = qq;
    }
    memInfo();
}

Все-таки 40 Мб. Тому що всі вони вказують на один і той же Подвійний об'єкт.

Ініціалізація замість подвійного

public static void main(String[] args) {
    memInfo();
    Double a[] = new Double[10000000];
    Double qq = 3.1d;
    for (int i = 0; i < a.length; i++) {
        a[i] = qq.doubleValue();
    }
    memInfo();
}

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

Лінія

a[i] = qq.doubleValue();

еквівалентно

a[i] = Double.valueOf(qq.doubleValue());

що еквівалентно

a[i] = new Double(qq.doubleValue());

Оскільки ми створюємо нові Double об'єкти кожного разу, ми здуваємо купу. Це показує, що значення всередині класу Double зберігаються в купі.


1
Чи можете ви вставити деталі коду memInfo () pls? :)
hedleyyan

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.