程序計數(shù)器
并發(fā)情況下,會發(fā)生線程之間的上下文切換,當(dāng) 線程1 的CPU時間片用完后,需要程序計數(shù)器記錄 線程1 的下一條JVM指令的地址,等下一次 線程1 繼續(xù)運行的時,才能從正確的位置開始繼續(xù)執(zhí)行
程序計數(shù)器是線程私有的 , 既 一個線程計數(shù)器 記錄一個 線程 的指令位置
而且程序計數(shù)器是運行時數(shù)據(jù)區(qū)域唯一一個不存在內(nèi)存溢出的區(qū)域
棧
線程是什么?實際上可以理解為線程就是一個棧
線程調(diào)用方法 就是復(fù)制方法入棧的過程
方法入棧后就會形成棧幀
比如下面的一段代碼
public class JvmDemo {
public static void main(String[] args) {
System.out.println("開始");
func();
System.out.println("結(jié)束");
}
public static void func(){
}
}
執(zhí)行該程序會創(chuàng)建一個主線程,然后main方法入棧,示意圖如下
接著mian方法執(zhí)行到 func(); ,就會復(fù)制一份 func 方法入棧,如下
func方法執(zhí)行完后出棧,如下
最后mian方法再出棧
我們說線程可以簡單的認(rèn)為就是棧,所以也很明顯,棧是線程私有的
棧的容量有限,如果我們不斷調(diào)用方法入棧,就會導(dǎo)致棧溢出 ( 比如遞歸時沒有遞歸出口 )
棧分為 虛擬機(jī)棧 和 本地方法棧
可以簡單理解為 :
-入棧的方法由 java 語言編寫 的為虛擬機(jī)棧
-入棧的方法由 C語言編寫 的為本地方法棧
Object類中有很多本地方法,比如反射中用到的 public final native Class<?> getClass();
,添加了native 關(guān)鍵字,使用C語言編寫,為了能直接對接操作系統(tǒng)
關(guān)于棧的幾個問題:
- 垃圾回收是否涉及棧內(nèi)存? 不涉及 ,因為棧幀執(zhí)行完就會自動出棧,無需垃圾回收
- 棧內(nèi)存是否分配越大越好嗎?錯誤 ,物理內(nèi)存大小一定,棧內(nèi)存越大,能同時純在的棧就會越少,既 線程數(shù)會越少
- 方法內(nèi)的局部變量是否是線程安全的? 是線程安全的,線程調(diào)用方法是復(fù)制方法入棧,所以每個線程都有自己的方法副本,也就有了自己的局部變量副本,所以在操作時,不會受到其他線程的干擾
堆
堆 – 存儲對象實例
比如下面代碼
public class JvmDemo {
public static void main(String[] args) {
Person person = new Person();
}
}
class Person{}
其在內(nèi)存中的模型如下
person引用存在main方法的棧幀中,而person實例對象存在于堆區(qū)中
堆區(qū)是所有線程共享的 ,比如
public class JvmDemo {
public static void main(String[] args) {
Person person = new Person();
Thread thread01=new Thread(()->{
Person person01 = new Person();
});
thread01.start();
}
}
class Person{}
則內(nèi)存模型如下,為了突出重點,省略 thread01 引用和實例對象 在內(nèi)存中的展示,以及 thread01.start(); 的調(diào)用過程
堆的內(nèi)存有限,不能無限創(chuàng)建對象實例,否者會堆內(nèi)存溢出
因此JVM存在堆區(qū)的垃圾回收機(jī)制,清除垃圾對象,垃圾對象 可以簡單定義為沒有引用指向的實例對象
比如
Person person = new Person;
person = new Person;
此時就沒有引用指向第一個new出來的對象,那么它就會被垃圾回收
存在堆區(qū)中的對象實例是線程間共享的,對象中的 全局變量 需要考慮線程安全問題
比如
public class JvmDemo {
public static void main(String[] args) throws InterruptedException {
Person person = new Person();
Thread thread01=new Thread(()->{
for (int i=0;i<100000;i++){
person.money++;
}
});
thread01.start();
for (int i=0;i<100000;i++){
person.money++;
}
thread01.join();
System.out.println(person.money);
}
}
class Person{
public int money=0;
}
輸出的結(jié)果不一定是200000
方法區(qū)
方法區(qū) – 存儲 類信息 和 靜態(tài)變量、方法
在 jdk 1.8之前,方法區(qū)存在于JVM內(nèi)存中,其實現(xiàn)方法被稱為 永久代;jdk 1.8及之后,方法區(qū)從JVM內(nèi)存移出到本地內(nèi)存,其實現(xiàn)方法被稱為 元空間
方法區(qū)是線程共享的,因此多個線程修改同一個類的靜態(tài)變量的時候同樣存在線程安全問題,比如
public class JvmDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread01=new Thread(()->{
for (int i=0;i<100000;i++){
Person.money++;
}
});
thread01.start();
for (int i=0;i<100000;i++){
Person.money++;
}
thread01.join();
System.out.println(Person.money);
}
}
class Person{
public static int money=0;
}
輸出結(jié)果同樣不一定是200000,此處建議先學(xué)習(xí)一下什么是靜態(tài)變量
方法區(qū)同樣存在內(nèi)存溢出的問題
知識延申 – 字符串常量池
JVM中還有一個特殊又重要的區(qū)域,就是 字符串常量池(StringTable),可以參考我的另外一篇博客文章來源:http://www.zghlxwxcb.cn/news/detail-605367.html
【Java 基礎(chǔ)】你真的會用 String 嗎?文章來源地址http://www.zghlxwxcb.cn/news/detail-605367.html
到了這里,關(guān)于JVM - 運行時數(shù)據(jù)區(qū)域的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!