Günlük blog | Java ve işletim sistemi etkileşim ayrıntıları

Blog yazarı: pigpdong2

Bir satır Java kodunun nasıl yürütüldüğünü anlamak için CPU'yu birleştirin

Von Neumann'ın düşüncesine göre, bir bilgisayar sayı sisteminin temeli olarak ikili kullanır ve aşağıdaki şekilde gösterildiği gibi aritmetik birimler, denetleyiciler, depolama aygıtları ve giriş ve çıkış aygıtlarını içermelidir.

(Resim Baidu'dan geliyor)

Öncelikle CPU'nun çalışma prensibini analiz edelim Modern CPU yongalarının çoğu kontrol ünitelerini, hesaplama ünitelerini ve depolama ünitelerini entegre eder. Kontrol ünitesi, CPU'nun kontrol merkezidir. CPU'nun bundan sonra ne yapılacağını, yani hangi komutların çalıştırılacağını bilmek için onu kullanması gerekir.Kontrol ünitesi ayrıca şunları içerir: komut kaydı (IR), komut kod çözücü (ID) ve işlem kontrolörü (OC) . Program hafızaya yüklendiğinde, talimat hafızadadır.Bu anda hafıza, CPU dışındaki ana hafıza cihazından, yani bilgisayardaki hafıza çubuğundan bağımsızdır.Komut işaretçi kaydı IP'si hafızada yürütülecek bir sonraki maddeyi işaret eder. Komutun adresi için, kontrol ünitesi, ana hafızadaki talimatı, IP kaydının işaretine göre talimat yazmacına yükler.Bu talimat kaydı aynı zamanda bir depolama cihazıdır, ancak CPU'nun içine entegre edilmiştir. Komut ana hafızadan CPU'ya ulaştıktan sonra, sadece 010101 dizisidir. İşlem kodunun ne olduğunu ve işlenenlerin nerede olduğunu analiz etmek için ikili dizelerin kod çözücü tarafından çözülmesi gerekir. Bundan sonra, belirli aritmetik birim aritmetik işlemleri (toplama, çıkarma, çarpma ve bölme) ve mantık işlemleri (karşılaştırma, yer değiştirme) gerçekleştirir. CPU komut yürütme süreci kabaca şu şekildedir: adresi getir (talimatı ana bellekten al ve kayıt listesine koy), kodunu çöz (işleneni ana bellekten al ve önbelleğe L1 koy) ve çalıştır (işlem).

Ana bellekteki DRAM'a karşılık gelen yukarıdaki şekilde CPU içerisine entegre edilmiş depolama birimi SRAM'in bir açıklaması RAM rastgele erişimli bir bellektir, yani verilere bir adres verilerek erişilebilir.Diskin depolama ortamına sırayla erişilmelidir. RAM dinamik ve statik olarak ikiye ayrılır: Statik RAM düşük bir entegrasyon seviyesine sahiptir ve genellikle küçük bir kapasiteye ve hızlı hıza sahiptir, dinamik RAM ise yüksek entegrasyon seviyesine sahiptir, bu da esas olarak kapasitörlerin şarj edilmesi ve boşaltılması ile gerçekleştirilir.Hız statik RAM kadar hızlı değildir. Dinamik RAM, ana bellek olarak kullanılır ve statik RAM, CPU ile ana bellek arasındaki hız farkını korumak için CPU ile ana bellek arasındaki önbellek olarak kullanılır, bu da sıklıkla gördüğümüz L1 ve L2 önbellekleridir. Her seviyenin önbellek hızı düşer ve kapasite artar. Aşağıdaki şekil, belleğin hiyerarşik mimarisini ve ana belleğe erişen CPU'nun sürecini gösterir.Burada iki bilgi noktası vardır. Biri, çok seviyeli önbellekler arasında veri tutarlılığını sağlamak için tanıtılan önbellek tutarlılığı protokolüdür. Ayrıntılar için lütfen şu adrese bakın: Bu makaledeki diğer bir bilgi noktası, önbellek ve ana bellek arasındaki eşlemedir. Açıkça görülmesi gereken ilk şey, önbellek biriminin bir değişken değil, ana bellekteki bir bellek bloğuna karşılık gelen bir önbellek hattı olduğudur. Bunun başlıca nedeni ** CPU erişiminin alan sınırlaması: erişilen belirli bir depolama birimine kısa bir süre içinde tekrar erişilmesi muhtemeldir ve alan sınırlaması: kısa bir süre içinde erişilen belirli bir depolama birimi Bitişikteki depolama birimine de erişilecektir. ** Önbellek satır numarası = ana bellek blok numarası mod önbellek toplam satır sayısına benzer şekilde birçok eşleme yöntemi vardır, böylece bir ana bellek adresi her alındığında ana bellekteki blok sayısı bu adrese göre hesaplanabilir. Önbellekteki satır numarası.

CPU'nun komut yürütmesi hakkında konuşalım. Adresleme, kod çözme ve yürütme. Bu, bir komutun yürütme sürecidir. Tüm komutlar, bu sıraya tam olarak uyulur, ancak birden çok komut gerçekte paralelleştirilebilir. Tek çekirdekli bir CPU için, bir seferde yalnızca bir tane olabilir. Komutlar, çalıştırılacak yürütme birimini işgal edebilir. Burada bahsedilen yürütme, aritmetik birimin hesaplama görevi olan CPU komut işlemenin (getirme, kod çözme ve yürütme) üç adımının üçüncü adımıdır. Yani CPU'nun komut işleme hızını iyileştirmek için, bu nedenle Aritmetik birimin yürütmeden önce tamamlandığından emin olmak gerekir, böylece aritmetik birim her zaman işlemde olabilir, ancak şu anda seri işlemde, aritmetik birim, getirme ve kod çözme sırasında boşta ve eğer alma ve kod çözme yoksa Vuruş önbelleğine de ana bellekten erişilmesi gerekir ve ana belleğin hızı CPU ile aynı seviyede değildir, bu nedenle Talimat hattı CPU'nun işlem hızı büyük ölçüde iyileştirilebilir Aşağıdaki şekil üç aşamalı bir boru hattı örneğidir. Mevcut Pentium CPU'ların tümü 32 aşamalı iletişim hatlarıdır. Spesifik yöntem, yukarıdaki üç işlemi daha ayrıntılı olarak bölmektir.

Yönerge ardışık düzenine ek olarak, CPU ayrıca hızı optimize etmek için dal tahmini ve sıra dışı yürütme yöntemlerine sahiptir. Tamam, konuya geri dönelim, bir satır Java kodu nasıl çalıştırılır.

Bir kod satırı yürütülebilir ve komut kayıtları, veri kayıtları ve yığın alanı gibi bellek kaynakları dahil olmak üzere yürütülebilir bir bağlam olmalıdır.Daha sonra bu kod satırı, işletim sisteminin görev planlayıcısı tarafından bir yürütme akışı olarak tanınmalı ve ona verilmelidir. CPU kaynaklarını ayırın Tabii ki, bu kod satırıyla temsil edilen komutun kodu çözülmeli ve CPU tarafından tanınmalıdır, bu nedenle bir Java kodu satırı, yürütülmeden önce karşılık gelen bir CPU komutu olarak yorumlanmalıdır. System.out.println ("Merhaba dünya") satırının çeviri sürecine bir göz atalım.

Java, üst düzey bir dildir. Bu tür bir dil doğrudan donanım üzerinde çalıştırılamaz. Java dilinin özelliklerini tanıyan bir sanal makinede çalıştırılmalı ve Java kodu, sanal makinenin bir Java derleyicisi aracılığıyla tanıyabileceği bir talimatlar dizisine dönüştürülmelidir. , Java bayt kodu olarak da bilinir, bayt kodu olarak adlandırılır çünkü Java bayt kodunun işlem talimatı (OpCode) bir bayta sabitlenmiştir, aşağıdaki System.out.println ("Merhaba dünya") derlenmiştir Bayt kodu

0x00: b20002 getstatic Java .lang.System.out 0x03: 1203 ldc "Merhaba Dünya!" 0x05: b60004 sanal Java .io.PrintStream.println 0x08: b1 dönüşü

En soldaki sütun ofsettir; ortadaki sütun sanal makine için okunan bayt kodudur; en sağdaki sütun üst düzey dil kodudur, aşağıdaki montaj dili tarafından dönüştürülen makine talimatıdır, ortadaki makine kodu ve üçüncü sütun karşılık gelen Makine talimatları, son sütun ilgili montaj kodudur

0x00: 55 rbp'ye bas 0x01: 4889 e5 mov rbp, rsp 0x04: 4883 ec 10 alt rsp, 0x100x08: 488d 3d 3b 000000 lea rdi, ; "Merhaba, Dünya! \ N" yazın 0x0f: c745 fc 00000000 mov DWORD PTR, 0x00x16: b000 film, 0x00x18: e80d 0000000x12'i ara ; Printf yöntemini çağırın 0x1d: 31 c9 xor ecx, ecx 0x1f: 8945 f8 mov DWORD PTR, eax 0x22: 89 c8 mov eax, ecx 0x24: 4883 c410 rsp ekle, 0x100x28: 5d pop rbp 0x29: c3 ret

JVM, sınıf yükleyicisi aracılığıyla sınıf dosyasındaki bayt kodunu yükledikten sonra, yorumlayıcı tarafından derleme yönergelerine yorumlanacak ve son olarak CPU'nun tanıyabileceği makine yönergelerine dönüştürülecektir.Tercüman, esas olarak aynı kopyayı elde etmek için yazılım tarafından uygulanır. Java bayt kodu farklı donanım platformlarında çalıştırılabilir ve montaj talimatlarının makine talimatlarına dönüştürülmesi doğrudan donanım tarafından gerçekleştirilir.Bu adım çok hızlıdır.Elbette, JVM, işletim verimliliğini artırmak için bazı etkin kodları da (bir yöntem) dönüştürebilir. İçerideki kod) bir seferde makine talimatlarında derlenir ve daha sonra çalıştırılır, bu, yorumlanan yürütmeye karşılık gelen tam zamanında derleme (JIT) JVM başladığında, yürütme modunu -Xint ve -Xcomp aracılığıyla kontrol edebilirsiniz.

Yazılım düzeyinden, sınıf dosyası sanal makineye yüklendikten sonra, sınıf bilgileri yöntem alanında depolanacak ve yöntem alanındaki kod, gerçek işlem sırasında yürütülecektir.JVM'deki tüm iş parçacıkları yığın belleği ve yöntem alanını paylaşır ve her biri Her iş parçacığının kendi bağımsız Java yöntem yığını, yerel yöntem yığını (yerel yöntemler için), PC kaydı (iş parçacığı yürütme konumu) vardır, bir yöntem çağrıldığında, Java sanal makinesi, geçerli iş parçacığına karşılık gelen yöntem yığınındaki bir yığını iter. Çerçeveler, Java bayt kodu işlenenlerini ve yerel değişkenleri depolamak için kullanılır.Bu yöntem uygulandıktan sonra, yığın çerçevesi açılır.Bir iş parçacığı, farklı yığın çerçevelerinin itilip çıkarılmasına karşılık gelen birden çok yöntemi art arda yürütür. JVM, yürütme sürecini açıklar.

Kesmek

Az önce bahsettiğim gibi, CPU açık olduğu sürece, sürekli bir hareket makinesi gibidir, talimatları getirme, hesaplama ve tekrar tekrar ve kesme, işletim sisteminin ruhudur, bu yüzden adından da anlaşılacağı gibi, kesme CPU'nun çalışmasını kesintiye uğratmak ve sonra uzaklaşmaktır. Başka bir şey yapın, örneğin, sistem yürütme sırasında ölümcül bir hata meydana gelir ve yürütmenin sonlandırılması gerekir.Örneğin, kullanıcı programı mmp vb. Gibi bir sistem çağrısı yöntemini çağırır ve CPU bağlamı bir kesme yoluyla değiştirir ve bekleme gibi çekirdek alanına aktarır. Kullanıcı tarafından girilen program bloke ediyor ve kullanıcı klavye aracılığıyla girişi tamamladığında ve çekirdek verileri hazır olduğunda, verileri çekirdekten uzaklaştırmak için kullanıcı programını uyandırmak için bir kesme sinyali gönderilecek, aksi takdirde çekirdek verileri taşabilir ve disk bunu raporlayacaktır. Ölümcül bir istisna ayrıca bir kesinti yoluyla CPU'ya bildirilecek ve zamanlayıcı, zamanlayıcı saat adımını tamamladığında CPU'ya bildirimde bulunmak için bir saat kesintisi gönderecektir.

Burada kesinti türlerini alt gruplara ayırmayacağız. Kesintiler, sık sık söylediğimiz olay odaklı programlamaya biraz benzer. Bu olay bildirim mekanizması nasıl uygulanır? Donanım kesintilerinin uygulanması, kesme sinyallerini CPU'ya bağlı bir kablo aracılığıyla iletir. Yazılımda, bir iş parçacığı oluşturmak için sistem çağrısını yürütme talimatı gibi özel talimatlar olacaktır ve CPU her bir talimat yürüttüğünde, kesme kaydında bir kesinti olup olmadığını kontrol edecek ve bir kesme varsa, onu çıkaracak ve kesmenin ilgili işleme programını çalıştıracaktır.

Çekirdekte sıkışıp: Yazılım tasarlarken, program içeriği değiştirme sıklığını dikkate alacağız.Çok yüksek bir frekans kesinlikle programın performansını etkileyecektir.Kirekte sıkışıp CPU içindir. CPU'nun çalıştırılması kullanıcı modundan çekirdek moduna değişir. Kullanıcı programı CPU kullanıyor ve şimdi çekirdek programı CPU kullanıyor. Bu anahtar bir sistem çağrısı tarafından üretilir. Sistem çağrısı, işletim sisteminin temelindeki programı yürütmek içindir. Linux tasarımcısı, işletim sistemini korumak için işlemin yürütme durumunu kullanır. Çekirdek modu kullanıcı modundan ayrılır Aynı işlemde, çekirdek ve kullanıcı aynı adres alanını paylaşır, genellikle 4G sanal adresler, bunlardan 1G'si çekirdek modu için ve 3G kullanıcı modu için. Programı tasarlarken, kullanıcı modundan çekirdek moduna geçişi en aza indirmeliyiz.Örneğin, bir iş parçacığı oluşturmak bir sistem çağrısıdır, bu nedenle iş parçacığı havuzu uygulamasına sahibiz.

JVM bellek modelini Linux bellek yönetimi perspektifinden anlamak

Süreç bağlamı

Programı bir dizi çalıştırılabilir talimat olarak anlayabiliriz ve program başlatıldıktan sonra, işletim sistemi onun için CPU ve bellek gibi kaynakları tahsis eder ve bu çalışan program bizim süreç dediğimiz şeydir ve süreç işletim sisteminin işlenmesidir. Cihazda çalışan programın bir özeti ve işlem için ayrılan bellek ve CPU kaynakları, o anda yürütülen talimatları ve değişken değerleri kaydeden sürecin bağlamıdır.JVM başlatıldıktan sonra, aynı zamanda Linux'ta sıradan bir işlemdir. İşlemin yürütülmesini destekleyen fiziksel varlık ve ortama toplu olarak bağlam adı verilir ve bağlam anahtarı, o anda çalışan işlemin yerine geçecek ve işlemcinin çalıştırılması için yeni bir işlemi değiştirecek, böylece birden çok işlem eşzamanlı olarak yürütülebilir. Bağlam değiştirme mümkündür. İşletim sistemi planlamasından, programın içinden de gelebilir.Örneğin, IO okurken, kullanıcı kodu ve işletim sistemi kodu arasında geçiş yapacaktır.

Sanal depolama

Aynı anda birden fazla JVM başlattığımızda şunu çalıştırın: System.out.println (new Object ()); bu nesnenin hashcode'unu basacak, hashcode varsayılan olarak bellek adresine gidecek ve sonunda hepsinin Java .lang.Object @ 4fca772d yazdığını gördük, Yani, birden çok işlem tarafından döndürülen bellek adreslerinin aynı olduğu ortaya çıktı.

Yukarıdaki örnekle, Linux'taki her işlemin ayrı bir adres alanına sahip olduğunu kanıtlayabiliriz, bundan önce, CPU'nun belleğe nasıl eriştiğini anlayalım.

Henüz sanal bir adresimiz olmadığını, yalnızca fiziksel bir adresimiz olduğunu varsayalım. Derleyici programı derlediğinde, üst düzey dili makine talimatlarına dönüştürmesi gerekir. Ardından, belleğe erişirken CPU bir adres belirtmelidir. Bu adres mutlak bir fiziksel adres ise, Daha sonra program hafızada sabit bir yere yerleştirilmeli ve bu adres derlenirken onaylanmalıdır.Herkes kaç tane delik olduğunu düşünmeli.İki ofis kelime programını aynı anda çalıştırmak istersem, o zaman yapacaklar Aynı bellek parçasını çalıştırırsanız, dağınık olacaktır. Büyük bilgisayar öncülleri, CPU'nun belleğe erişmek için segment temel adresini + segment içindeki ofset adresini kullanmasına izin vermek için tasarlanmıştır. Segment temel adresi, bu segment temel adrese rağmen program başladığında onaylanır. Bu hala mutlak bir fiziksel adrestir, ancak sonuçta, aynı anda birden fazla program çalıştırılabilir. CPU, belleğe erişmek için bu yöntemi kullandığında, adresi depolamak için segment temel adres kaydına ve segment ofset adres kaydına ihtiyaç duyar ve son olarak iki adres birbirine eklenir ve gönderilir Adres yolu. Bellek bölümleme, her işlemin bir bellek bölümü ayırmasına eşdeğerdir ve bu bellek bölümünün sürekli bir alan olması gerekir ve ana bellekte birden çok bellek bölümü tutulur.Bir işlem daha fazla belleğe ihtiyaç duyduğunda ve fiziksel belleği aştığında O sırada, nadiren kullanılan bir bellek bölümünü sabit diske değiştirmek ve takas olan yeterli bellek olduğunda bunu sabit diskten yüklemek gerekir. Her değişimin tüm veri segmentini işlemesi gerekir.

Her şeyden önce, bitişik adres alanı çok değerlidir.Örneğin, 50M'lik bir bellek, bellek bölümleri arasında boşluklar olduğunda, çalışması için 10M bellek gerektiren 5 programı destekleyemeyecektir Bölümdeki adresler nasıl kesintili olabilir? Cevap hafıza sayfalandırmasıdır.

Korumalı modda, her işlemin kendi bağımsız adres alanı vardır, bu nedenle segment temel adresi sabittir.Sadece segment içindeki ofset adresini vermeniz gerekir. Bu ofset adresi doğrusal bir adres olarak adlandırılır ve doğrusal adres Sürekli ve bellek sayfalama, sayfalamadan sonra sürekli doğrusal adresleri fiziksel adreslerle ilişkilendirir, böylece mantıksal olarak sürekli doğrusal adresler, kesintili fiziksel adreslere karşılık gelebilir. Fiziksel adres alanı, birden çok işlem tarafından paylaşılabilir ve bu eşleme ilişkisi, sayfa tablosu aracılığıyla korunacaktır. Standart bir sayfanın boyutu genellikle 4 KB'dir. Sayfalandırmadan sonra, fiziksel bellek birkaç 4KB veri sayfasına bölünür. Bir işlem bellek için uygulandığında, birden çok 4KB fiziksel belleğe eşlenebilir. Uygulama verileri okuduğunda, sayfalara alınacaktır. En küçük birimdir ve sabit diskle değiş tokuş edilmesi gerektiğinde, birim olarak sayfayı da alır.

Modern bilgisayarlar çoğunlukla sanal depolama teknolojisini kullanır. Sanal depolama, her işlemin tüm bellek alanına sahip olduğunu düşündürür. Aslında bu sanal alan, ana bellek ve diskin bir soyutlamasıdır. Avantajı, her işlemin basitleştiren tutarlı bir sanal adres alanına sahip olmasıdır. Bellek yönetimi, işlemin bellek alanı için diğer işlemlerle rekabet etmesine gerek yoktur, çünkü özeldir ve ilgili süreçleri diğer işlemler tarafından yok edilmekten korur.Ayrıca, ana belleği diskin önbelleği olarak görür ve ana bellekte yalnızca aktif olanlar depolanır. Program bölümü ve veri bölümü, ana bellekte veri olmadığında, bir sayfa hatası kesintisi meydana gelir ve ardından diskten yüklenir, fiziksel bellek yetersiz olduğunda diske takas gerçekleşir. Sayfa tablosu, sanal adresler ve fiziksel adresler arasındaki eşleştirmeyi depolar. Sayfa tablosu bir dizidir ve her öğe bir sayfanın eşleme ilişkisidir Bu eşleme ilişkisi ana bellek adresiyle veya diskle olabilir, sayfa tablosu ana bellekte depolanır. Yüksek hızlı arabellek önbelleğinde depolanan sayfa tablosuna hızlı tablo TLAB diyoruz.

  • Yükleme biti, adres sayfası verilerin hala diskte olduğunu gösteriyorsa sayfanın ana bellekte olup olmadığını gösterir
  • Depolama konumu Adres çevirisi için sanal sayfalar ile fiziksel sayfalar arasında bir eşleme oluşturun. Boş ise, ayrılmamış bir sayfa anlamına gelir
  • Değiştirilmiş bit, verilerin değiştirilip değiştirilmediğini saklamak için kullanılır
  • İzin biti, okuma ve yazma izninin olup olmadığını kontrol etmek için kullanılır
  • Önbellek yasaklama biti, esas olarak önbellek ana bellek diskinin veri tutarlılığını sağlamak için kullanılır

Hafıza haritası

Normal şartlar altında, dosyaları okuma sürecimiz, önce sistem çağrıları aracılığıyla diskteki verileri okumak, bunu işletim sisteminin çekirdek arabelleğinde saklamak ve ardından çekirdek arabelleğinden kullanıcı alanına kopyalamak ve bellek eşlemesi disk dosyasını aktarmaktır. Doğrudan kullanıcının sanal depolama alanıyla eşleyin, sayfa tablosu aracılığıyla sanal adresi disk eşlemesine tutun ve dosyayı bellek eşlemesiyle okuyun, çünkü çekirdek arabelleğinden kullanıcı alanına kopyayı azaltır ve doğrudan diskten okur Verilerin belleğe alınması, sistem çağrılarının yükünü azaltır.Kullanıcılar için, doğrudan manipüle edilen bir disk üzerindeki bir dosya gibidir.Ayrıca, sanal depolama kullanımı nedeniyle, verileri depolamak için sürekli ana bellek alanına ihtiyaç yoktur.

Java'da, bellek eşlemesini uygulamak için MappedByteBuffer kullanıyoruz.Bu bir yığın dışı bellektir. Eşlemeden sonra, fiziksel belleği hemen işgal etmez. Veri sayfalarına erişirken, önce sayfa tablosunu kontrol edin ve yüklenmediğini bulun. Sayfa hatası anormaldir ve ardından veriler diskten belleğe yüklenir, bu nedenle roketmq gibi yüksek gerçek zamanlı performans gerektiren bazı ara yazılımlar, okuma ve yazma hızını hızlandırmak için mesajı 1G boyutunda bir dosyada saklar, bu dosya değiştirilecektir. Belleğe eşledikten sonra, her sayfaya bir bit veri yazın, böylece 1G dosyasının tamamı belleğe yüklenebilir ve gerçek okuma ve yazma sırasında herhangi bir sayfa hatası olmaz.Buna rocketmq içinde dosya ön ısıtma denir.

Aşağıda, MappedFile sınıfında bulunan rocketmq mesaj depolama modülü için bir kod parçası gönderiyoruz. Bu sınıf, rocketMq mesaj depolamanın temel sınıfıdır. Bunu kendiniz inceleyebilirsiniz.Aşağıdaki iki yöntem bir dosya eşlemesi oluşturmak ve diğeri dosyayı önceden ısıtmaktır. Sıcak 1000 veri sayfası, CPU yetkisinden vazgeçin.

private void init (final String fileName, final int fileSize) IOException { this.fileName = dosyaAdı; this.fileSize = fileSize; this.file = yeni Dosya (dosyaAdı); this.fileFromOffset = Long.parseLong (this.file.getName ()); boolean ok = yanlış; sureDirOK (this.file.getParent ()); Deneyin { this.fileChannel = new RandomAccessFile (bu dosya, "rw"). getChannel (); this.mappedByteBuffer = this.fileChannel.map (MapMode.READ_WRITE, 0, fileSize); TOTAL_MAPPED_VIRTUAL_MEMORY.addAndGet (fileSize); TOTAL_MAPPED_FILES.incrementAndGet (); ok = doğru; } catch (FileNotFoundException e) { log.error ("dosya kanalı oluştur" + this.fileName + "Başarısız.", e); e atmak; } catch (IOException e) { log.error ("eşleme dosyası" + this.fileName + "Başarısız.", e); e atmak; } en sonunda { eğer (! tamam this.fileChannel! = null) { this.fileChannel.close (); } } } // Dosya ön ısıtma, OS_PAGE_SIZE = 4kb, her 4kb'de bir bayt 0 yazmaya eşdeğerdir, tüm sayfalar belleğe yüklenir ve gerçekte kullanıldıklarında sayfa hataları oluşmaz public void warmMappedFile (FlushDiskType türü, int sayfaları) { long beginTime = System.currentTimeMillis (); ByteBuffer byteBuffer = this.mappedByteBuffer.slice (); int flush = 0; uzun zaman = System.currentTimeMillis (); for (int i = 0, j = 0; i < this.fileSize; i + = MappedFile.OS_PAGE_SIZE, j ++) { byteBuffer.put (i, (bayt) 0); // yıkama diski türü senkronize olduğunda yıkamaya zorla if (type == FlushDiskType.SYNC_FLUSH) { eğer ((i / OS_PAGE_SIZE) - (flush / OS_PAGE_SIZE) > = sayfalar) { flush = i; mappedByteBuffer.force (); } } // gc'yi önle eğer (j% 1000 == 0) { log.info ("j = {}, costTime = {}", j, System.currentTimeMillis () - zaman); zaman = System.currentTimeMillis (); Deneyin { // Burada uyku (0), iş parçacığının diğer yüksek öncelikli iş parçacıkları yürütmek için CPU izinlerinden vazgeçmesine izin verir ve bu iş parçacığı çalışmadan hazır duruma geçer Thread.sleep (0); } catch (InterruptedException e) { log.error ("Kesildi", e); } } } // yükleme tamamlandığında yıkamaya zorla if (type == FlushDiskType.SYNC_FLUSH) { log.info ("eşlenen dosya ısınması tamamlandı, diske zorla, mappedFile = {}, costTime = {}", this.getFileName (), System.currentTimeMillis () - beginTime); mappedByteBuffer.force (); } log.info ("eşlenen dosya ısınması yapıldı. mappedFile = {}, costTime = {}", this.getFileName (), System.currentTimeMillis () - beginTime); this.mlock (); }

JVM'deki nesnelerin bellek düzeni

Linux'ta bir değişkenin başlangıç adresini bildiğiniz sürece bu değişkenin değerini okuyabilirsiniz, çünkü başlangıç adresinden ilk 8 bit değişkenin boyutunu kaydeder, yani bitiş adresini bulabilirsiniz.Java'da kullanabiliriz Değişkenin değerini, yani yansımayı elde etmek için Field.get (nesne) yöntemi, nihayet UnSafe sınıfı aracılığıyla uygulanır. Belirli kodu analiz edebiliriz

Field nesnesinin getInt yöntemi önce güvenliği kontrol eder ve ardından FieldAccessor'ı çağırır. @CallerSensitive public int getInt (Nesne nesnesi) IllegalArgumentException, IllegalAccessException atar { if (! override) { eğer (! Reflection.quickCheckMemberAccess (clazz, değiştiriciler)) { Sınıf < ? > caller = Reflection.getCallerClass (); checkAccess (arayan, clazz, obj, değiştiriciler); } } getFieldAccessor (obj) .getInt (obj); } Nesne alan uzaklığındaki alan adresinin ofsetini alın UnsafeFieldAccessorImpl (Alan var1) { this.field = var1; eğer (Modifier.isStatic (var1.getModifiers ())) { this.fieldOffset = güvensiz.staticFieldOffset (var1); } Başka { this.fieldOffset = güvensiz.objectFieldOffset (var1); } this.isFinal = Değiştirici.isFinal (var1.getModifiers ()); } UnsafeStaticIntegerFieldAccessorImpl yöntemi güvensiz olarak çağırır public int getInt (Nesne var1) IllegalArgumentException {atar dönüş güvensiz.getInt (this.base, this.fieldOffset); }

Yukarıdaki kod aracılığıyla, nesnenin başlangıç adresine göre özniteliğin ofsetiyle özniteliğin değerini okuyabilir ve yazabiliriz.Bu aynı zamanda Java yansımasının ilkesidir.Bu mod, jdk'deki LockSupport gibi birçok senaryoda kullanılır. Engelleyen nesneyi parkta ayarlayın. Öyleyse, özniteliğin ofsetini belirlemek için özel kural nedir? Java nesnelerinin bellek düzenini analiz etmek için bu fırsatı değerlendirelim

Java sanal makinesinde, her Java nesnesinin, bir etiket alanı ve bir tür işaretçisinden oluşan bir nesne başlığı vardır.Etiket alanı, nesnenin karma kodunu, GC bilgilerini ve kilit bilgilerini ve türünü depolamak için kullanılır. İşaretçi, nesnenin sınıf Sınıfını gösterir 64 bitlik bir işletim sisteminde, etiket alanı 64 bit, tür işaretçisi de 64 bit, yani bir Java nesnesi, herhangi bir öznitelik olmadan 16 bayt alan kaplar. , Sıkıştırılmış işaretçiler geçerli JVM'de varsayılan olarak etkindir, bu nedenle yazım işaretçisi yalnızca 32 bit kaplayabilir, bu nedenle nesne başlığı 12 baytı kaplar ve sıkıştırılmış işaretçi nesne başlığı ve başvuru türü alanları üzerinde hareket edebilir. JVM, bellek hizalama için alanları yeniden sıralar. Buradaki hizalama, esas olarak, Java sanal makine yığınındaki nesnenin başlangıç adresinin 8'in katı olduğu anlamına gelir. Bir nesne 8N bayttan daha az kullanırsa, geri kalanı Doldurulur ve alt sınıf tarafından miras alınan özniteliğin ofseti, üst sınıfınkiyle aynıdır.Örnek olarak, yalnızca bir statik olmayan öznitelik değerine sahiptir ve nesne başlığı yalnızca 12 bayt kaplamasına rağmen, öznitelik değerinin göreli konumu yalnızca 16, 4 bayt yalnızca boşa harcanabilir, bu nedenle alan yeniden düzenlemesi bellek israfını önlemek içindir, bu nedenle Java bayt kodu yüklenmeden önce Java nesnesinin ne kadar yer kapladığını analiz etmek bizim için zordur. Yalnızca geçebiliriz Özyinelemeli üst sınıfın tüm özellikleri, nesnenin boyutunu tahmin etmek için kullanılır ve gerçek boyut, Java aracısındaki Enstrümantasyon aracılığıyla elde edilebilir. Elbette, bellek hizalamasının bir başka nedeni, alanın yalnızca aynı CPU'nun önbellek satırında görünmesini sağlamaktır Alan hizalı değilse, bir alanın bir kısmının önbellek satırı 1'de ve kalan yarısının önbellek satırı 2'de olması mümkündür. , Bu nedenle, bu alanın okunması iki önbellek satırının yerini almalıdır ve alanın yazılması, iki önbellek satırında önbelleğe alınan diğer verilerin geçersiz olmasına neden olacak ve bu da programın performansını etkileyecektir.

Bellek hizalaması, aynı anda iki önbellek satırında bir alanın mevcut olması durumundan kaçınabilir, ancak önbellek yanlış paylaşımı sorunundan tamamen kaçınmak hala imkansızdır, yani bir önbellek satırında birden fazla değişken vardır ve bu değişkenler çok çekirdekli CPU'da paraleldir. Şu anda, önbellek satırının yazma izni için rekabete yol açacaktır. CPU'lardan biri veri yazdığında, bu alana karşılık gelen önbellek satırı geçersiz hale gelecek ve bu önbellek satırının diğer alanlarının da geçersiz olmasına neden olacaktır.

Disruptor'da, birkaç anlamsız alanı doldurarak, nesnenin boyutu tam olarak 64 bayttır ve bir önbellek satırının boyutu 64 bayttır, böylece önbellek satırı yalnızca bu değişken için kullanılır, böylece önbellek satırından kaçınır. Yanlış paylaşım, ancak jdk7'de geçersiz alan temizlenir ve yöntem geçersizdir. Doldurulan alanın optimizasyonunu önlemek için yalnızca üst sınıf alanını devralabilirsiniz ve jdk8, bu değişken veya nesnenin yalnızca bir önbelleği paylaşacağını belirtmek için @Contended ek açıklamasını sağlar Evet, bu ek açıklamayı kullanmak için, JVM başlatıldığında -XX: -RestrictContended parametresini eklemelisiniz. Aslında, bu aynı zamanda bir zaman alanıdır.

jdk6 - 32 bit sistem altında genel nihai statik sınıf VolatileLong { halka açık uçucu uzun değer = 0L; public long p1, p2, p3, p4, p5, p6; // alanları doldurun } jdk7 miras yoluyla public class VolatileLongPadding { public volatile long p1, p2, p3, p4, p5, p6; // alanı doldurun } public class VolatileLong, VolatileLongPadding { halka açık uçucu uzun değer = 0L; } jdk8 ek açıklama yoluyla @Contended public class VolatileLong { halka açık uçucu uzun değer = 0L; }

NPTL ve Java iş parçacığı modeli

Ders kitabının tanımına göre, süreç en küçük kaynak yönetim birimidir ve iş parçacığı, CPU zamanlama yürütmesinin en küçük birimidir. İş parçacığının görünümü, sürecin bağlam anahtarını azaltmak (iş parçacığının bağlam anahtarı işlemden çok daha küçüktür) ve birçok kişiye daha iyi uyum sağlamaktır. Çekirdek CPU ortamı, örneğin, bir işlemdeki birden çok iş parçacığı farklı CPU'larda yürütülebilir ve çoklu iş parçacığı desteği Linux çekirdeğinde veya çekirdeğin dışında uygulanabilir. Çekirdeğin dışındaysa, yalnızca tamamlanması gerekir Çalışan yığının anahtarlanması, düşük programlama ek yüküne sahiptir, ancak bu yöntem çoklu CPU ortamına uyarlanamaz.Altında yatan işlem hala tek bir CPU üzerinde çalışır.Ayrıca, kullanıcılar için yüksek programlama gereksinimleri nedeniyle, mevcut ana işletim sistemleri çekirdekteki iş parçacıkları destekler. Linux'ta, iş parçacığı hafif bir süreçtir, ancak iş parçacığı zamanlamasının ek yükü optimize edilmiştir. JVM'de, evreler ve çekirdek evreleri arasında bire bir yazışma vardır.İş parçacıkların zamanlaması tamamen çekirdeğe devredilir.Thre.run çağrıldığında, fork () sistem çağrısı tarafından bir çekirdek iş parçacığı oluşturulur. Çekirdek modu ile çekirdek modu arasında geçiş yapıldığında, performans kullanıcı modununki kadar yüksek değildir.Tabii ki, çekirdek iş parçacığı doğrudan kullanıldığı için, yaratılabilecek maksimum evre sayısı da çekirdek tarafından kontrol edilmektedir. Linux'taki mevcut iş parçacığı modeli NPTL'dir (Yerel POSIX İş Parçacığı Kitaplığı) Bire bir mod kullanır ve POSIX standardı ile uyumludur.Yönetim iş parçacığı kullanmaz ve çok çekirdekli CPU'larda daha iyi çalışabilir.

Konu durumu

İşlem için hazır, çalışıyor ve engelleme olmak üzere üç durum vardır.JVM'de dört tür engelleme vardır. İş parçacığının durumunu görüntülemek için bir döküm dosyası oluşturmak için jstack kullanabiliriz.

  • ENGELLENDİ (nesne monitöründe) Senkronize (obj) senkronizasyon bloğu üzerinden kilidi alırken, diğer iş parçacığının nesne kilidini serbest bırakmasını bekleyin, döküm dosyası kilitlenmeyi bekliyor < 0x00000000e1c9f108 >
  • Kilidi aldıktan sonra, TIMED WAITING (nesne monitöründe) ve WAITING (nesne monitöründe) diğer iş parçacıklarının object.notify () 'yı çağırmasını beklemek için object.wait ()' i çağırın. İkisi arasındaki fark zaman aşımı olup olmadığıdır.
  • ZAMANLI BEKLEME (uyku) programı thread.sleep () 'yi çağırır, burada eğer uyku (0) engelleme durumuna girmezse, doğrudan çalışmadan hazır durumuna geçecektir.
  • ZAMANLI BEKLEME (park etme) ve BEKLEME (park etme) programları, Unsafe.park () çağırır, iş parçacığı askıya alınır, belirli bir durumun oluşmasını bekler, koşulu bekler

POSIX standardında, thread_block bir parametre statüsünü kabul eder, bu parametrenin ayrıca üç tipi vardır: TASK_BLOCKED, TASK_WAITING, TASK_HANGING ve zamanlayıcı, sadece thread durumu READY olan evreler için zamanlama yapacaktır.Bir başka nokta, thread'lerin bloke edilmesinin thread'ın kendi işlemi olmasıdır. Evet, iş parçacığının CPU zaman diliminden aktif olarak vazgeçmesine eşdeğerdir, bu nedenle iş parçacığı uyandırıldıktan sonra kalan zaman dilimi değişmez. İş parçacığı yalnızca kalan zaman diliminde çalışabilir. Zaman dilimi sona erdikten sonra iş parçacığı henüz sona ermemişse Sonunda, iş parçacığı durumu, programlayıcının bir sonraki programını bekleyerek ÇALIŞIYOR'dan HAZIR'a değiştirilecektir.

İş parçacığı analizi ile ilgili olarak, Java eşzamanlılık paketinin özü AQS'dedir. Alt katman, Güvensiz sınıfının cas yöntemi ve park yöntemi ile gerçekleştirilir. Ayrı ayrı analiz etmek için zaman arıyoruz. Şimdi Linux'a bakıyoruz. İşlem senkronizasyon şeması.

POSIX, UNIX'in Taşınabilir İşletim Sistemi Arayüzü (POSIX) anlamına gelir ve POSIX standardı, işletim sisteminin uygulamalar için sağlaması gereken arayüz standardını tanımlar.

CAS işlemi CPU desteği gerektirir. Karşılaştırma ve değişim tek bir talimat olarak yürütülür.CAS genellikle üç parametreye sahiptir: bellek konumu, beklenen orijinal değer ve yeni değer.Bu nedenle, UnSafe sınıfındaki CompareAndSwap, nesnenin ilk adresine göre özniteliğin ofsetini kullanır. Bellek konumunu bulun.

İş parçacığı senkronizasyonu

İş parçacığı senkronizasyonunun temel nedeni, genel kaynaklara erişmek için birden çok işlemin gerekli olması ve bu birden çok işlemin yürütülmesinin atomik olmaması ve görev zamanlayıcı tarafından ayrılması ve diğer iş parçacıklarının paylaşılan kaynakları yok etmesidir, bu nedenle kritik bölümde iş parçacıkları yapmak gerekir. Burada ilk önce kritik bölüm olan bir kavramı açıklığa kavuşturuyoruz. Bu, bellek veya dosyalar gibi paylaşılan kaynaklara birden çok görevin eriştiği zamanki talimatı ifade eder.Bu, erişilen bir kaynak değil, bir talimattır.

POSIX beş senkronizasyon nesnesini, muteksleri, koşul değişkenlerini, döndürme kilitlerini, okuma-yazma kilitlerini ve semaforları tanımlar. Bu nesnelerin JVM'de de karşılık gelen uygulamaları vardır ve bunların tümü POSIX tanımlı API'leri kullanmaz ve Java tarafından uygulanmaz. Daha esnektir ve yerel yöntemleri çağırmanın performans yükünü ortadan kaldırır. Tabii ki, alt katman, muteksi gerçekleştirmek için pthread muteksine bağlıdır. Bu, çok fazla ek yükü olan bir sistem çağrısıdır. Bu nedenle, JVM kilidi otomatik olarak yükseltir. AQS'ye dayalı gerçekleşme gelecekte analiz edilecek, burada esas olarak senkronize edilmiş anahtar kelimeden bahsedeceğiz.

Senkronize kod bloğu bildirildiğinde, derlenen bayt kodu bir izleme merkezi ve birden fazla monitorexit (çoklu çıkış yolları, normal ve anormal) içerecektir. İzleme merkezi çalıştırıldığında, hedef kilit nesnesinin sayacının 0 olup olmadığını kontrol edecektir. 0 ise, kilit nesnesini tutan iplik kendisine ayarlanır, ardından sayaç 1 artırılır ve kilit elde edilir, 0 değilse, kilit nesnesini tutan ipliğin kendisi olup olmadığını kontrol edin, eğer kendisiyse, elde etmek için sayacı 1 artırın Kilitle, değilse, sonra bloke et ve bekle.Çıkarken, sayaç 1 azaltılır ve 0'a düşürüldüğünde kilit nesnesinin diş işareti açıktır.Senkronize edilmiş evreni desteklediği görülebilir.

İş parçacığı engellemenin yüksek ek yükü olan bir sistem çağrısı olduğundan az önce bahsetmiştim, bu yüzden JVM uyarlanabilir bir döndürme kilidi tasarladı, yani kilit alınmadığında CPU dönme durumuna geri döner ve diğer iş parçacığının kilidi serbest bırakmasını bekler. En son kilidin ne kadar sürede alındığına bakın. Örneğin, kilit son döndürme sırasında 5 milisaniye boyunca elde edilmedi. Bu sefer 6 milisaniyeydi. Döndürmek CPU'nun boş çalışmasına neden olacak. Diğer bir yan etki ise haksız kilitleme mekanizması çünkü Diğer engelleme konuları hala beklerken Spin kilidi alır. Döndürme kilitlerine ek olarak, JVM, birden çok iş parçacığının kilide farklı zamanlarda eriştiği ve kilit yalnızca bir iş parçacığı tarafından kullanılacağı durumu hedeflemek için CAS aracılığıyla hafif kilitler ve önyargılı kilitler de uygular. Son iki kilit, temel semafor uygulamasını çağırmamaya eşdeğerdir (örneğin wait () 'i çağırmak gibi, A evresini kontrol etmek için semafor aracılığıyla, ve B evresi kilidi alabilir, bu sadece çekirdek tarafından sağlanabilir, son ikisi çünkü Sahnede rekabet yoktur, bu nedenle altta yatan semaforu kontrol etmeye gerek yoktur, ancak kilit tutma ilişkisi kullanıcı alanında korunur, bu nedenle daha etkilidir.

Yukarıdaki şekilde gösterildiği gibi, bir iş parçacığı monitorenter'a girerse, kendisini objectmonitor'un giriş seti kuyruğuna koyar ve ardından blok yapar.Geçerli tutma iş parçacığı bekleme yöntemini çağırırsa, kilit serbest bırakılır ve ardından kendisini bir nesne bekleyicisine sarar ve nesne monitörünün bekleme kuyruğuna koyar. Bu zamanda, giriş seti kuyruğundaki bir iş parçacığı kilit için rekabet edecek ve aktif duruma girecektir.Bu iş parçacığı bildirim yöntemini çağırırsa, bekleme setinin ilk nesne bekleticisi çıkarılacak ve giriş kümesine konulacaktır (bu sefer Önce döndürün) İş parçacığı çağrı bildirimi kilidi açmak için moniterexit'i çalıştırdığında, giriş kümesindeki iş parçacıkları kilit için rekabet etmeye başlar ve aktif duruma girer.

Uygulamanın veri rekabetinin müdahalesini önlemek için, Java bellek modeli, iki işlemin bellek görünürlüğünü, yani X işlemi Y işleminden önce gerçekleşir, ardından X işlemi sonucu Y tarafından görülebilir. JVM'de uçucu ve kilitlerin uygulanması için önceden olan kurallar vardır. JVM'nin alt katmanı, bir bellek engeli ekleyerek derleyicinin yeniden sıralanmasını kısıtlar.Örnek olarak uçucu olan bellek engeli, geçici alan yazma işleminin yeniden sıralanmasından önceki ifadelere izin vermeyecektir. Yazma işleminden sonra, okuma uçucu alandan sonraki ifadenin de okuma ifadesinden önce yeniden sıralanmasına izin verilmez. Bellek bariyerine yerleştirilen talimatlar, talimatın türüne bağlı olarak farklı etkilere sahip olacaktır.Örneğin, monitorexit kilidi serbest bıraktıktan sonra önbellek yenilenmeye zorlanacak ve uçucuya karşılık gelen bellek engeli, her yazma işleminden sonra ve geçici alan nedeniyle ana belleği yenilemeye zorlanacaktır. Derleyici onu kayda ayıramaz, bu nedenle her seferinde ana bellekten okunur, bu nedenle uçucu daha fazla okuma ve daha az yazmanın mümkün olduğu senaryolar için uygundur.Sık sık yazma neden oluyorsa, okumak için yalnızca bir iş parçacığının birden çok iş parçacığı yazması en iyisidir. Önbelleği sürekli yenilemek performansı etkileyecektir.

CPU * 2 Runtime.getRuntime).availableProcessors() CPU CPU CPU CPU = / 1 - RPCRPC

Java

5 Java api

Java timer JUC ScheduledExecutorService JVM CPU JVM ?

JDK API 3 3 :

  • object.wait(long millisecond) 0 0 timer wait() wait

public final void wait(long timeout int nanos) throws InterruptedException {

eğer (zaman aşımı < 0) {

throw new IllegalArgumentException("timeout value is negative");

}

if (nanos < 0 || nanos > 999999) {

throw new IllegalArgumentException(

"nanosecond timeout value out of range");

}

if (nanos > 0) {

timeout++;

}

wait(timeout);

}

1

  • Thread.sleep(long millisecond) CPU 0 CPU CPU wait 500000 1
public static void sleep(long millis int nanos) throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos > = 500000 || (nanos != 0 millis == 0)) { millis++; } sleep(millis); }
  • LockSupport.park(long nans) Condition.await() ScheduledExecutorService condition.await()
  • System.currentTimeMillis() System.nanoTime() windows 10ms 100ns

api PC

  • RTC Linux RTC
  • PIT 10 CPU CPU
  • TSC Intel8086 CPU CPU 1 PIT

:11193180

:PIT0

Linux RTC PIT RTC PIT 0 ScheduledExecutorService CPU CPU TSC

Java 13 JDK System.currentTimeMillis() Linux System.nanoTime() TSC Random nanoTime

Java

I/O IO CPU I/O CPU I/O I/O IO IO DMA Java

OSC

Java - pigpdong2 -

Cep telefonunun arkasında küçük bir kasabadaki genç adam: bal yemek ve zehir içmek
önceki
Domuz Yılı'nda bir "ağabey" olma olasılığı ne kadar yüksek? Ma Huateng, Zhang Yiming ve Cheng Wei'ye bakın.
Sonraki
E-platform düşük maliyetli ve iyi performansa sahiptir.Ev çağında hem balıklara hem de ayı pençelerine sahip olma yöntemi
Alman ordusunun şu anki durumu endişe verici: gençler orduya katılmak istemiyor ve askerlerin kalitesi kötü
Dongfeng Peugeot yeni nesil 508L'den başlayarak "kendini değiştiriyor"
Yazılım Güncellemesi Springblade 2.2 Serbest bırakıldı, Yükseltme Çoklu terminal jeton sertifikasyon Sistemi
Mi 6X Görünüm Onayı: 5,99 inç tam ekran, seçim için 5 renk!
Che Yun Morning Post | SAIC, Volkswagen ile Hisse Oranını Açmak İçin Tartışmayı Reddetti, Baidu, Weimar'ın 3 milyarlık yatırımı SAIC "Beyanı" Porsche Yurtiçi
Devlet, gazilerle ilgili işler için bir departman kurar, gazilere muameleyi iyileştirir ve şanlı kartlar çıkarır.
Suizhou'dan 75 yaşındaki, para biriktirdi ve dullara ve yalnız yaşlılara bağışta bulundu, 8 yılda nakit olarak 20.000 yuan'a 400'den fazla giysi bağışladı
"İyimser Kalın" Musk, "Olağanüstü Zor" Liu Qiangdong Popüler İnsan Envanteri
Annemin satın aldığı "akıllı ilaç" ı yedikten sonra bir uyuşturucu rehabilitasyon merkezine gönderildim!
Liu Chuanzhi ile özel diyalog: Melek yatırım ekibine suları test etmesi için neden 400 milyon yuan vermeye cüret ettim?
Çağda Haval Intelligent Network 3.0'ın yükselişine tepeden bakan akıllı bir şehrin tepesinde durmak
To Top