ÖNEMLİ : Kendim için aldığım notlar. Umarım size de bir faydası olur. Kullanılan her bir makale referans olarak eklenmiştir.

Merhabalar arkadaşlar, blog yazımı okumadan önce aşağıdaki youtube videomu izlemenizi öneririm. Bu konuda naçizane öğrendiklerimi sizlerle paylaşmak için detaylı bir video hazırlamaya karar verdim. Umarım kafanızdaki sorulara cevap verebilirim. Şimdiden iyi seyirler.

Java Memory Management

Konu ile alakalı bir bilgilendirme


Arkadaşlar, bu konu her ne kadar spesifik bir konu gibi gözükse de bir bağlam içerisinde konuyu ele almak çok daha önemlidir. Yani, statik nedir? sorusunu daha geniş bağlamda düşünmemiz gerekiyor. Bunun için statik olmayan değişken ve metotlar nedir? Statik ve statik olmayan değişkenler hafızada nasıl saklanır? gibi farklı soruları da beraberinde düşünmemiz elzemdir. Bunun için aşağıdaki içeriklerime sırasıyla bakmanızı öneriyorum.

Java’da Statik nedir?

Statik anahtar kelimesine sahip bir değişken veyahut metot, aslında sınıfa ait olan, sınıftan yaratılan objelere ait olmayan anlamına gelir. Hatta statik konteksi sınıfın hafızası gibi düşünebilirsiniz. Bu konteks sınıftan yaratılan bütün objelerle ortak olarak paylaşılır. Haliyle sınıftan yaratılan objeler bu hafızaya doğrudan erişebilirken, statik konteksten(yani sınıfın bu hafızasından) ilgili objelere doğrudan erişim yoktur. Bu durumu yukarıdaki videoda çok net bir şekilde izah ettiğimi düşünüyorum. Dilerseniz oradan bakabilirsiniz.

Birçok yerde static değiştiricisiyle karşılaşmışızdır. Hatta bu anahtar kelimeyi hem metodlar için hem de değişkenler için kullanıyoruz.

  • Peki bu ne anlama geliyor?
  • Hangi durumlarda static anahtar kelimesini kullanmamız gerekir?
  • JVM static yöntem ve değişkenleri nasıl ve nerede saklar?
  • Non-static method ‘’ cannot be referenced from a static context hatasını neden alıyoruz? (Cevabı videonun içinde)

Java’da Değişkenler

Java Örnek Değişkenleri

Örnek Değişkenlerinin Diğer Adları


  • Örnek Değişkenleri
  • Dinamik Değişkenler
  • Instance Variables (ingilizce)
  • Anlık değişkenler
  • Statik Olmayan Alanlar

Aynı sınıf şablonundan bir dizi nesne oluşturulduğunda, bunların her biri, ayrı ayrı kendi dinamik değişkenlerine sahiptir. Teknik olarak, nesneler, kendi durumlarını(yani ingilice state olarak ifade edilir) “statik olmayan alanlara”, yani statik anahtar kelime olmadan beyan edilen alanlara depolar. Statik olmayan alanlar da Instance Variables/dinamik değişkenler olarak bilinir. Çünkü tuttuğu değerler bir sınıfın her örneğine, bir başka deyişle her nesneye özgüdür;

Sınıf Şablonu


Note : Yukarıda sınıf şablonu(blueprint) derken kastettiğim aslında aşağıdaki gibi sıradan bir sınıf kodudur:

Her sınıf kodu, sınıftan oluşturulacak objeler için bir taslak gibidir.

1
2
3
4
5
6
public class SampleClass{
  int x;
  double y;
  static int z;
  ...
}

Aşağıdaki resimden de anlaşılacağı üzere sınıf şablonundan yaratılan 3 obje içinde statik olmayan alanlar tutulur. Dikkat edecek olursanız her obje farklı x ve y değeri saklamaktadır. Bu farklı değişkenlere objelerin state‘leri de denir. Objeden objeye değişiklik gösterdiği için dinamik değişken olarak da ifade edilir. Bu arada objeler yeşil renkle gösterilmektedir.


Java Memory Management

Java Sınıf Değişkenleri

Sınıf Değişkenlerinin Diğer Adları


  • Sınıf Değişkenleri
  • Class Variables (ingilizce)
  • Statik Alanlar
  • Sınıfın hafızası (çünkü ilgili sınıftan yaratılan bütün objeler için ortak bir veritabanı gibi davranır.)

Statik alan ise yukarıdaki resimde pembe renkle gösterilmektedir. Görüleceği üzere, sınıf şablonu içindeki statik değişkenleri içinde barındırmaktadır. Her obje için ortak oldukları için ve her objenin bu alana erişimi olduğu için statik alan olarak da ifade edilir. Yani sınıfın hafızasıdır.

Bazen, tüm nesneler için ortak olan değişkenlere sahip olmak istersiniz. Bu statik anahtar kelimesi ile gerçekleştirilir. Beyanında statik değiştiriciye sahip olan değişkenlere statik alanlar(static fields) veya sınıf değişkenleri(class variables) denir. Herhangi bir nesneden ziyade sınıfla ilişkilendirilirler. Sınıfın her örneği(yani objeler), bellekte sabit bir konumda bulunan bir sınıf değişkenini paylaşır.

Herhangi bir nesne, bir sınıf değişkeninin değerini değiştirebilir, ancak statik alan içinden sınıfın bir örneğine erişim mümkün değildir.

Buna ek olarak final anahtar kelimesinin eklenmesi değişkenin değerinin değişmeyeceği anlamına gelir.

Yerel Değişkenler (Local Variables)

Bir nesne kendi durumunu alanlarda(fields) sakladığı gibi, bir yöntem de geçici durumunu yerel değişkenlerde saklar. Yerel bir değişkenin deklarasyonu, bir örnek değişkeninin deklarasyonuna benzerdir (örneğin, int num = 0;). Yerel olarak bir değişken belirten özel bir anahtar kelime yoktur; bu belirleme tamamen değişkenin beyan edildiği yerden yani bir yöntemin açılış ve kapanış parantezleri arasında gelir. Bu nedenle, yerel değişkenler yalnızca bildirildikleri yöntemlerle görülebilir; sınıfın geri kalanından erişilemezler. Yani sadece bir metodun içinde deklare edildiklerinden, onlara erişim de ilgili metod üzerinden olur.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Deneme {
    public static void main(String[] args) {
        InnerClas c= new InnerClas();
        c.deneGor();
    }
    static class InnerClas {
        int x = 5;
         void deneGor() {
            int x = 7;
            System.out.println(x);
        }
    }
}

Yukarıdaki örnekte ne demek istediğimi sanırım daha net anlatabilirim. Dikkat edilecek olursa InnerClas‘ın içinde iki tane x değişkeni var. Bunlardan biri void deneGor() metoduna ait olan x değişkeni(yani yerel değişken), diğeri ise bir static olmayan dinamik bir değişken(yani instance variable).

Ben burada

1
System.out.println(x)

yaparak x‘i yazdırmak istiyorum.. Peki hangi x değişkenini yazdıracak dersiniz? Kod üzerinde Back, Forward butonlarını kullanarak kodun nasıl çalıştığını görebilirsiniz. Aslında burada, deneGor metodunun içindeki x yerel değişkenini ekrana basacaktır. Kısaca yaptığımız adımları gözden geçirelim.

  • ilk önce main metod içinde InnerClas sınıfının bir nesnesini yarattık.
  • Sonra bu nesneyi c değişkenine atadık.
  • c değişkeni sayesinde InnerClas içindeki metod ve dinamik değişkenlere artık ulaşabiliriz.
  • görüldüğü üzere c değişkeni deneGor metodunu çağırdı,
  • akabinde deneGor metodu ise kendi içindeki yerel değişkeni(local variable) olan x‘i ekrana bastı.

Eğer;

1
System.out.println(x)

yerine

1
System.out.println(this.x)

yazmış olsaydık, yerel değişken(local variable) yerine ekrana instance variable olan x‘i basacaktı. Java’da her zaman this anahtar sözcüğü instance variable’ı, yani nesnenin değişkenlerini niteler. Bu yüzden tanımları bilmekte yarar var. this anahtar sözcüğü kullanıldığında ise sonuç aşağıdaki gibi olacaktır.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Deneme {
    public static void main(String[] args) {
        InnerClas c= new InnerClas();
        c.deneGor();
    }
    static class InnerClas {
        int x = 5;
         void deneGor() {
            int x = 7;
            System.out.println(this.x);
        }
    }
}

Önemli Hatırlatma


Yerel değişkenler(local variables) ile ilgili bir diğer önemli nokta ise şudur. Yerel değişkenleri tanımlarken erişim değiştiriciler(yani access modifiers) kullanamayız(final dışında). Access Modifiers’ları hatırlayacak olursak;

  • public
  • protected
  • default
  • private
  • final

Illegal start of expression derleme hatası


Java Illegal Start of expression for local variables

Yukarıdaki görselde yerel değişkene bir erişim değiştirici uygulamaya çalıştım. Görüldüğü gibi “illegal start of expression” derleme hatası aldım. Hatadan da anlaşılacağı üzere yerel değişkenlere erişim değiştiricileri(access modifiers) uygulanamaz. Koda şu linkten ulaşabilirsiniz.

Parametreler (Parameters)

Bir yönteme veya oluşturucuya(constructor) bilgi aktarmak için kullanılan argümanlar parametre olarak adlandırılır. Bir yöntem veya constructor için deklarasyon, bu yöntem veya constructor için bağımsız değişkenlerin sayısını ve türünü bildirir.

Parametre ve Argüman Arasındaki Fark


Aslında parametre ile argüman arasında da ufak bir fark vardır.

  • Parametre yöntem deklerasyonunda kullanılan, yani yöntem imzasının bir parçası olan şeydir. Yani aşağıdaki num tipi int olan bir parametredir.

  • Argüman ise bu metodu kullanmak istediğimizde yani metodu çağırdığımızda metodun içine aldığı değerdir. Örneğin bu metodun içine değer olarak 32 veya 44 int değerlerini aldığımızda bunlar aslında argüman olarak ifade edilir.

1
2
3
public String methodA(int num) {
    // method body goes here
}

Yukarıdaki örnektede görüleceği üzere methodA‘yı kullanabilmek için int tipinde bir sayıya ihtihacınız olduğu anlamına geliyor. Yani bu şartı sağlamadan bu metodu kullanamazsınız. Aksi halde derleme hatası ile karşılaşırsınız. Unutulmaması gereken en önemli şey, parametrelerin her zaman “alanlar(fields)” olarak değil “değişkenler(variables)” olarak sınıflandırılmasıdır.

Static Anahtar Kelimesinin Ne Anlama Geldiğini Bir Örnekte İnceleyelim

Java ile yazılmış en basit kod içinde bile, isteğimiz dışında bir static ifadesini kullandığımız oluyor. Bunu devamlı kullandığımız main metodundan hatırlayabiliriz. main metodu ile ilgili detaylı bilgi için şu linkteki yazımı okuyabilirsiniz.

1
2
3
public static void main(String[] args) {
        //code
    }

Bu ifadeyi metod tanımlarken kullandığımızda, ilgili yöntemin genel anlamıyla bulunduğu sınıfa ait olduğu ve belirli başka bir örnekte olmadığı anlamına gelir. Bunun ne anlama geldiğini daha yakından görmek için, önce ilgili sınıfın her bir instance’ı için bir kopya olan statik olmayan(non-static) alanlara bakalım. Örnek olarak, bankacılık ile ilgili bir yazılım hazırladığınızı varsayalım. Bir banka hesabı için bir sınıf oluşturmaya ve alanları(fields) için, hesap bakiyesi ve hesap numarası bildirmeye karar verdiniz.

  • non-static(instance) = her nesnede(in each object)
1
2
3
4
5
class BankaHesabi{
  int hesapNum;
  double bakiye;
  ...
}

Her bir farklı banka hesabının kendi hesap numarası ve kendi bakiyesi olması gerekmektedir. Bankanızda, bu sınıfın üç örneğini, veri depolamada üç hesap için oluşturduysanız, böyle görünebilir. Burada her bir örneğin kendi hesap numarası ve bakiyesinin nasıl olduğunu görebiliriz. Bu alanlar statik değildir ve her nesne için ayrı ayrı oluşur.

Kişiler Hesap Numarası Bakiye
Görkem 100 12000
Hasan 101 12500
Bilal 102 0
1
2
3
4
5
class BankaHesabi{
  int hesapNum;
  double bakiye;
  int sonHesapNum;
}

Şimdi, bu kodu yazarken, bir değişkende atama yapmak için bir önceki hesap numarasını takip etmek istediğinizi varsayalım. Böylece yeni bir hesap oluşturduğunuzda, ona hangi numarayı vereceğinizi bileceksiniz. Eklediğimiz alan yukarıda gösterildiği gibi olursa, bu sonraki hesap numarası için oluşturduğumuz örnek beklediğimiz gibi olmayabilir. Aşağıdaki kodu görüntüleyemiyorsanız lütfen linke tıklayınız.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Banka {
    public static void main(String[] args) {
      BankaHesabi a1 = new BankaHesabi();
      BankaHesabi a2 = new BankaHesabi();
      BankaHesabi a3 = new BankaHesabi();
    }

    static class BankaHesabi{
      int hesapNum;
      double bakiye;
      int sonHesapNum;

      public BankaHesabi(){
        hesapNum = sonHesapNum;
        sonHesapNum++;
      }
    }
}

Yukarıdaki kod bloğunda dikkat etmemiz gereken iki şey var. Birincisi yukarıda oluşturduğumuz BankaHesabı sınıfının önünde bulunan static anahtar kelimesidir. Burada sınıfı bu şekilde değiştirmemin sebebi hem static sınıfların kullanımını göstermek hemde static bir ifadenin bazı kurallarından bahsetmek olacaktır.

Önemli Hatırlatma


Mesela bir kural da şudur. Yukarıdaki gibi bir örnekte, inner olarak oluşturduğumuz sınıfı non-static yapamayız.

Aksi halde şu şekilde bir hata alabiliriz.

1
Error: non-static variable this cannot be referenced from a static context

Çünkü main metodu static bir metodtur. Static bir durumda static değişkenler kullanmak zorundayız. Bu Java’nın kurallarından sadece biridir. Tabiki amacımız bu kuralları derinlemesine işlemek değil. Static ifadesiyle alakalı olduğu için değinmek istedim. Örneğe geri dönecek olursak, oluşturulan her bir nesne için instance variable’lar hep sıfırdan başlamaktadır. Ama biz sonHesapNum değişkeninin bir önceki hesap numarasını takip etmek istediğini ve onu baz alarak bir atama yapmasını sağlayacaktık. O zaman sonHesapNum değişkeni önüne static anahtarını koyabiliriz.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Banka {
    public static void main(String[] args) {
      BankaHesabi a1 = new BankaHesabi();
      BankaHesabi a2 = new BankaHesabi();
      BankaHesabi a3 = new BankaHesabi();
    }

    static class BankaHesabi{
      int hesapNum;
      double bakiye;
      static int sonHesapNum;

      public BankaHesabi(){
        hesapNum = sonHesapNum;
        sonHesapNum++;
      }
    }
}

Dikkat edilecek olursa hesapNum her yeni nesne için farklı bir rakam olarak gözükmektedir. static anahtarı ilgili değişkeni nesne değişkeni durumundan sınıf değişkeni durumuna geçirmiştir. Yani değişken nesneye bağlı değil, sınıfa bağlıdır. Ve ilgili sınıftan oluşturulan bütün nesneler için ortak bir değişken haline gelmiştir. Yukarıdaki örnek üzerinden gidecek olursak, ilk nesneyi oluşturduğumuzda sonHesapNum değişkenini bir arttırmıştık. İkinci nesnemizi oluşturduğumuzda ise bu değişken bir önceki hali ile yani bir arttırılmış hali ile yeni nesneyi ilklendirir. Ne demek istediğimi daha iyi anlamak için yukarıdaki kodu Back ve Forward butonlarını kullanarak ilerletmeye çalışın. sonHesapNum değişkeninin her bir nesne için sabit(yani her nesne oluşumunda sıfırlanmadığını) kaldığını göreceksiniz.

Statik Yöntem ve Değişkenler ile Statik Olmayan Değişken ve Yöntemlerin Arasındaki Farklılıklar

Bir Java sanal makinesindeki (JVM) bir sınıfın kullanım ömrü, bir nesnenin kullanım ömrü ile birçok benzerliğe sahiptir. Bir nesne, statik olmayan değişkenlerinin değerleri ile temsil edilen bir duruma sahip olabildiği gibi, bir sınıf, sınıf değişkenlerinin değerleri ile temsil edilen bir duruma sahip olabilir. JVM, başlatma kodunu çalıştırmadan önce statik olmayan(instance variables , non-static members) değişkenleri varsayılan başlangıç ​​değerlerine ayarladığı gibi, aynı JVM, başlatma kodunu çalıştırmadan önce sınıf değişkenlerini varsayılan başlangıç ​​değerlerine ayarlar. Ve nesneler gibi sınıflarda, çalışan uygulama tarafından uzun süre başvurulmuyorsa, toplanan çöp durumunda olabilirler.

Yine de, sınıflar ve nesneler arasında önemli farklılıklar vardır. Belki de en önemli fark, statik olmayan metodlar ile sınıf metodlarının çağrılma şeklidir: Statik olmayan yöntemler (çoğunlukla) dinamik olarak bağlıdır, ancak sınıf yöntemleri(yani static yöntemler) statik olarak bağlıdır. Üç özel durumda, statik olmayan yöntemler dinamik olarak bağlı değildir:

  • statik olmayan private(özel) yöntemlerinin çağrılması,
  • kurucu yöntemlerinin çağrılması(constructor)
  • super anahtar kelimesi ile çağrılma

Sınıflar ve nesneler arasındaki diğer bir fark, özel erişim seviyeleri tarafından verilen veri gizleme derecesidir. Bir statik olmayan değişken private(özel) olarak bildirilirse, yalnızca statik olmayan yöntemleri ona erişebilir. Bu, statik olmayan verilerinin bütünlüğünü sağlamanıza ve nesnelerin iş parçacığı güvenliğini sağlamanıza olanak tanır. Programın geri kalanı, bu statik olmayan değişkenlerine doğrudan erişemez, ancak statik olmayan değişkenlerini işlemek için statik olmayan yöntemlerini gözden geçirmelidir. Bir sınıfın iyi tasarlanmış bir nesne gibi davranmasını sağlamak için, sınıf değişkenlerini private(özel) yapabilir ve bunları manipüle eden sınıf yöntemlerini tanımlayabilirsiniz. Yine de, bu şekilde thread güvenliğini ve hatta veri bütünlüğünü garanti edemezsiniz, çünkü belirli bir kodun onlara private(özel) sınıf değişkenlerine doğrudan erişim sağlayan özel bir ayrıcalığı vardır: statik olmayan yöntemler ve hatta statik olmayan değişkenlerin başlatıcıları(initializers) , doğrudan bu private(özel) sınıf değişkenlerine erişebilir.

Böylece, statik alanlar ve sınıfların metotları, statik olmayan alanlara ve nesnelerin yöntemlerine pek çok açıdan benzer olsa da, bunları tasarımlarda kullanma şeklinizi etkileyecek önemli farklılıklara sahiptir.

JVM static yöntem ve değişkenleri nasıl ve nerede saklar?

JDK 8’den önce HotSpot JVM, kalıcı Nesil(Permanent Generation) olarak adlandırılan üçüncü bir nesle(third generation) sahipti. Statik yöntemler (aslında tüm yöntemler) ve bunun yanısıra statik değişkenler, sınıf meta verileri heap alanına bitişik(contiguous) olan Permgen adında bir alanda tutulurdu. Kısaca bahsetmem gerekirse, bu alanda sınıf yöntem ve değişkenlerinin yanısıra, sınıfların JVM iç gösterimi(internal representation) ve meta verileri ve interned strings‘ler yer almaktaydı. JDK 8 den sonra bu generation’ının yerini metaspace almıştır. Permgenden farklı olarak bu alan Java Heap ile bitişik değildir(Not contiguous). Metaspace “native memory” den ayrılmıştır ve metaspace için maksimum alan kullanılabilir sistem hafızasıdır. Fakat bu MaxMetaspaceSize JVM seçeneği ile sınırlandırılabilir. Yalnız öncesinde bu hafıza Permgende sınırlı idi. Özetle JDK 8 öncesi ve sonrası olmak üzere iki farklı cevabımız bulunmaktadır.

Daha detaylı bilgi almak isterseniz aşağıdaki referanslara göz gezdirebilirsiniz.

Referanslar: