JVM——棧和堆概述,以及有什么區別?

方法棧

方法棧并不是某一個 JVM 的內存空間,而是我們描述方法被調用過程的一個邏輯概念。

在同一個線程內,T1()調用T2():

  • T1()先開始,T2()后開始;
  • T2()先結束,T1()后結束。

images

堆和棧概述

從英文單詞角度來說

  • 棧:stack
  • 堆:heap

從數據結構角度來說

  • 棧和堆一樣:都是先進后出,后進先出的數據結構

從 JVM 內存空間結構角度來說

  • 棧:通常指 Java 方法棧,存放方法每一次執行時生成的棧幀。
  • 堆:JVM 中存放對象的內存空間。包括新生代、老年代、永久代等組成部分。?

?棧幀

棧幀存儲的數據

方法在本次執行過程中所用到的局部變量、動態鏈接、方法出口等信息。棧幀中主要保存3 類數據:

  • 本地變量(Local Variables):輸入參數和輸出參數以及方法內的變量。

  • 棧操作(Operand Stack):記錄出棧、入棧的操作。

  • 棧幀數據(Frame Data):包括類文件、方法等等。

棧幀的結構

  • 局部變量表:方法執行時的參數、方法體內聲明的局部變量
  • 操作數棧:存儲中間運算結果,是一個臨時存儲空間
  • 幀數據區:保存訪問常量池指針,異常處理表

棧幀工作機制

當一個方法 A 被調用時就產生了一個棧幀 F1,并被壓入到棧中,

A 方法又調用了 B 方法,于是產生棧幀 F2 也被壓入棧,

B 方法又調用了 C 方法,于是產生棧幀 F3 也被壓入棧,

……

C 方法執行完畢后,彈出 F3 棧幀;

B 方法執行完畢后,彈出 F2 棧幀;

A 方法執行完畢后,彈出 F1棧幀;

……

遵循“先進后出”或者“后進先出”原則。

images

圖示在一個棧中有兩個棧幀:

棧幀 2 是最先被調用的方法,先入棧,

然后方法 2 又調用了方法 1,棧幀 1 處于棧頂的位置,

棧幀 2 處于棧底,執行完畢后,依次彈出棧幀 1 和棧幀 2,

線程結束,棧釋放。

每執行一個方法都會產生一個棧幀,保存到棧的頂部,頂部棧就是當前方法,該方法執行完畢后會自動將此棧幀出棧。

典型案例

請預測下面代碼打印的結果:34

int n = 10;
n += (n++) + (++n);
System.out.println(n);

實際執行結果:32

使用 javap 命令查看字節碼文件內容:

D:\record-video-original\day03\code>javap -c Demo03JavaStackExample.class
Compiled from "Demo03JavaStackExample.java"
public class Demo03JavaStackExample{
public Demo03JavaStackExample();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>: ()V
4: return

public static void main(java.lang.String[]);
Code:
0: bipush 10
2: istore_1
3: iload_1
4: iload_1
5: iinc 1, 1
8: iinc 1, 1
11: iload_1
12: iadd
13: iadd
14: istore_1
15: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
18: iload_1
19: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
22: return
}

內存執行過程分析:

images

棧溢出異常

異常名稱

java.lang.StackOverflowError

異常產生的原因

下面的例子是一個沒有退出機制的遞歸:

public class StackOverFlowTest {public static void main(String[] args) {methodInvokeToDie();}public static void methodInvokeToDie() {methodInvokeToDie();}}

拋出的異常信息:

Exception in thread "main" java.lang.StackOverflowError at com.atguigu.jvm.test.StackOverFlowTest.methodInvokeToDie(StackOverFlowTest.java:10) at com.atguigu.jvm.test.StackOverFlowTest.methodInvokeToDie(StackOverFlowTest.java:10) at com.atguigu.jvm.test.StackOverFlowTest.methodInvokeToDie(StackOverFlowTest.java:10) at com.atguigu.jvm.test.StackOverFlowTest.methodInvokeToDie(StackOverFlowTest.java:10) at com.atguigu.jvm.test.StackOverFlowTest.methodInvokeToDie(StackOverFlowTest.java:10)

原因總結:方法每一次調用都會在棧空間中申請一個棧幀,來保存本次方法執行時所需要用到的數據。但是一個沒有退出機制的遞歸調用,會不斷申請新的空間,而又不釋放空間,這樣遲早會把當前線程在棧內存中自己的空間耗盡。

棧空間的線程私有驗證

提出問題

某一個線程拋出『棧溢出異常』,會導致其他線程也崩潰嗎?從以往的經驗中我們判斷應該是不會,下面通過代碼來實際驗證一下。

代碼

new Thread(()->{while(true) {try {TimeUnit.SECONDS.sleep(2);System.out.println(Thread.currentThread().getName() + " working");} catch (InterruptedException e) {e.printStackTrace();}}
}, "thread-01").start();new Thread(()->{while(true) {try {TimeUnit.SECONDS.sleep(2);// 遞歸調用一個沒有退出機制的遞歸方法methodInvokeToDie();System.out.println(Thread.currentThread().getName() + " working");} catch (InterruptedException e) {e.printStackTrace();}}
}, "thread-02").start();new Thread(()->{while(true) {try {TimeUnit.SECONDS.sleep(2);System.out.println(Thread.currentThread().getName() + " working");} catch (InterruptedException e) {e.printStackTrace();}}
}, "thread-03").start();

結論:02 線程拋異常終止后,01 和 03 線程仍然能夠繼續正常運行,說明 02 拋異常并沒有影響到 01 和 03,說明線程對棧內存空間的使用方式是彼此隔離的。每個線程都是在自己獨享的空間內運行,反過來也可以說,這個空間是當前線程私有的。

images

堆空間

images

堆空間工作機制

  • 新創建的對象會被放在Eden區
  • 當Eden區中已使用的空間達到一定比例,會觸發Minor GC
  • 每一次在Minor GC中沒有被清理掉的對象就成了幸存者
  • 幸存者對象會被轉移到幸存者區
  • 幸存者區分成from區和to區
  • from區快滿的時候,會將仍然在使用的對象轉移到to區
  • 然后from和to這兩個指針彼此交換位置

口訣:復制必交換,誰空誰為to

  • 如果一個對象,經歷15次GC仍然幸存,那么它將會被轉移到老年代
  • 如果幸存者區已經滿了,即使某個對象尚不到15歲,仍然會被移動到老年代
  • 最終效果:
    1. Eden區主要是生命周期很短的對象來來往往
    2. 老年代主要是生命周期很長的對象,例如:IOC容器對象、線程池對象、數據庫連接池對象等等
    3. 幸存者區作為二者之間的過渡地帶
  • 關于永久代:
    • 從理論上來說屬于堆
    • 從具體實現上來說不屬于堆

永久代在各個JDK版本之間的演變

永久代常量池
≤JDK1.6在方法區
=JDK1.7有,但開始逐步“去永久代”在堆
≥JDK1.8在元空間

方法區、元空間、永久代之間關系

堆、棧、方法區之間關系

images

堆溢出異常

異常名稱

java.lang.OutOfMemoryError,也往往簡稱為 OOM。

異常信息

  • Java heap space:針對新生代、老年代整體進行Full GC后,內存空間還是放不下新產生的對象
  • PermGen space:方法區中加載的類太多了(典型情況是框架創建的動態類太多,導致方法區溢出)

我們可以參考下面的控制臺日志打印:

[GC (Allocation Failure) 4478364K->4479044K(5161984K), 4.3454766 secs] [Full GC (Ergonomics) 4479044K->3862071K(5416448K), 39.3706285 secs] [Full GC (Ergonomics) 4410423K->4410422K(5416448K), 27.7039534 secs] [Full GC (Ergonomics) 4629575K->4621239K(5416448K), 24.9298221 secs] [Full GC (Allocation Failure) 4621239K->4621186K(5416448K), 29.0616791 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3210) at java.util.Arrays.copyOf(Arrays.java:3181) at java.util.ArrayList.grow(ArrayList.java:261) at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235) at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227) at java.util.ArrayList.add(ArrayList.java:458) at com.atguigu.jvm.test.JavaHeapTest.main(JavaHeapTest.java:16)

小練習

測試代碼

查看下面程序在每個步驟中內存的狀態:

public class Review {// 靜態變量,類變量public static Review review = new Review();public void showMessage() {// 局部變量Review reviewLocal = new Review();}// 程序入口public static void main(String[] args) {// 局部變量Review reviewMain = new Review();// 通過局部變量調用對象的方法reviewMain.showMessage();// 手動 GCSystem.gc();}
}

各狀態分析

images

images

images

images

images

images

棧和堆的區別

堆和棧的區別主要體現在以下幾個方面。

  • 內存分配方式

棧(stack)和堆(heap)都是內存中的一段區域,但它們的內存分配方式是不同的。棧是由程序自動創建和釋放的,通常用于存儲函數調用時的臨時變量、函數的返回地址等信息。而堆則是由程序員手動申請和釋放的,通常用于存儲程序中需要動態分配的內存(如動態數組、對象等)。

  • 內存管理方式

棧的內存分配是按照“后進先出”的原則進行的,即最后一個進入棧的變量最先被釋放。因此,棧中的內存管理是由系統自動完成的,程序員不需要過多考慮內存的分配和釋放問題。堆的內存管理則需要程序員自行負責,使用完畢后必須手動釋放,否則會導致內存泄漏或其他問題。

  • 內存大小

棧的容量較小,一般只有幾百KB到幾MB的空間,具體容量由操作系統和編譯器決定。相對而言,堆用于存儲較大的數據結構,大小一般比棧要大得多,可以動態擴展內存空間。但是,因為堆需要手動管理內存,如果不及時釋放,會導致內存泄漏,進而影響系統性能。

  • 訪問速度

因為棧的內存分配是系統自動完成的,所以訪問速度相對堆更快。棧中的數據直接存放在系統內存中,而訪問堆中的數據需要通過指針進行間接訪問,會造成一定的時間損耗。此外,在多線程環境下,由于棧的線程獨享,所以不會發生競爭問題。而堆則需要考慮多線程并發訪問時的同步和互斥機制。

  • 應用場景

棧適合用于存儲局部變量和函數調用,主要用于內存的臨時分配;而堆適合用于存儲需要動態分配和管理的數據結構,如動態數組、字符串、對象等。在實際開發中,應該根據具體的應用場景選擇合適的內存分配方式。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/35979.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/35979.shtml
英文地址,請注明出處:http://en.pswp.cn/news/35979.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Maven介紹,部署在eclipse中

目錄 一.Maven介紹 1&#xff0c;什么是maven&#xff1f; 2. 為什么maven會在企業中大量使用&#xff1f; 3.沒有使用maven的前后區別? 4.maven在Java開發中的實際效果圖 二.maven部署在eclipse中 1.下載maven在其官方網址下載&#xff08;當然實際下載也要根據個人的…

服務器安裝Tomcat

下載Tomcat 下載地址在這&#xff1a; Tomcat官網 下載完成以后把壓縮包上傳到服務器中&#xff08;我傳到了www/java&#xff09;,進行解壓(解壓到)&#xff0c;如果沒有進行指定解壓到哪里&#xff0c;默認是到root文件夾中 tar -zxvf /www/java/apache-tomcat-9.0.103.tar.…

統計學補充概念03-核密度估計

概念 核密度估計&#xff08;Kernel Density Estimation&#xff0c;簡稱 KDE&#xff09;是一種非參數統計方法&#xff0c;用于估計隨機變量的概率密度函數&#xff08;Probability Density Function&#xff0c;PDF&#xff09;。它通過在每個數據點周圍放置核函數&#xf…

day 10 | 232.用棧實現隊列、 225. 用隊列實現棧

目錄&#xff1a; 解題及思路學習 232.用棧實現隊列 https://leetcode.cn/problems/implement-queue-using-stacks/ 模擬題&#xff0c;用兩個棧來實現隊列的功能。 class MyQueue { public:stack<int> stIn;stack<int> stOut;/** Initialize your data struc…

HCIP學習--BGP3

目錄 前置內容 BGP下一跳的修改問題 BGP的屬性 配置 PrefVal權重屬性 負載分擔 LocPrf 負載分擔 NextHop AS-PATH Ogn 配置 MED 配置 BGP選路規則 BGP的社團屬性 配置及解釋 前置內容 HCIP學習--BGP1_板栗妖怪的博客-CSDN博客 HCIP學習--BGP2_板栗妖怪的博客…

031_小馳私房菜_MTK平臺Camera基本流程,日志信息打印

這篇文章主要介紹mtk平臺,camera基本流程的日志信息打印。針對下面幾點展開: 一) camera打開流程; 二) 幀請求 && 幀回調; 三) 拍照; MTK平臺camera模塊,如果想要打開更多日志,一般需要先設置 adb shell setprop "vendor.debug.camera.log" 1 然后…

STM32控制SG90舵機原理及代碼

STM32控制SG90舵機原理及代碼 一.SG90舵機原理二.控制SG90舵機三.代碼實例3.1 配置定時器3.2 main 函數 四.實驗現象 一.SG90舵機原理 舵機的運用還是比較廣泛的&#xff0c;那么舵機工作原理是什么呢&#xff0c;一般來說我們給舵機一個信號他就能工作了&#xff0c;那么這個…

00 - 環境配置

查看所有文章鏈接&#xff1a;&#xff08;更新中&#xff09;GIT常用場景- 目錄 文章目錄 1. 環境說明2. 安裝配置2.1 配置user信息2.2 config的三個作用域 3. 建git倉庫3.1 把已有的項目代碼納入git管理3.2 新建的項目直接用git管理3.3 配置local的user和email3.4 優先級&…

Redis_緩存1_緩存類型

14.redis緩存 14.1簡介 穿透型緩存&#xff1a; 緩存與后端數據交互在一起&#xff0c;對服務端的調用隱藏細節。如果從緩存中可以讀到數據&#xff0c;就直接返回&#xff0c;如果讀不到&#xff0c;就到數據庫中去讀取&#xff0c;從數據庫中讀到數據&#xff0c;也是先更…

股票指數——RSI指數

RSI指數的計算非常簡單&#xff0c;就是使用一段時間內的平均上漲除以平均上漲加平均下跌&#xff08;取正值&#xff09;。也就意味著RSI指數的取值是[0,100]之間&#xff0c;其中0表示周期內沒有上漲的&#xff0c;100表示周期內沒有下跌的。RSI的直觀意義是它表示了一段周期…

學習筆記整理-JS-06-函數

一、函數基本使用 1. 什么是函數 函數就是語句的封裝&#xff0c;可以讓這些代碼方便地被復用。函數具有"一次定義&#xff0c;多次調用"的優點。使用函數&#xff0c;可以簡化代碼&#xff0c;讓代碼更具有可讀性。 2. 函數的定義和調用 和變量類似&#xff0c;函…

Jupyter并發測試以后出現EOFError marshal data too short

Jupyter 并發測試以后出現EOFError: marshal data too short 背景 由于項目需求需要用戶能進行網頁在線運行python代碼程序&#xff0c;調研后決定使用Jupyter的服務接口實現此功能&#xff0c;目前使用docker進行容器化部署&#xff0c;測試針對次服務進行并發測試。測試并發…

JimuReport積木報表 v1.6.0版本發布—免費的可視化報表

項目介紹 一款免費的數據可視化報表&#xff0c;含報表和大屏設計&#xff0c;像搭建積木一樣在線設計報表&#xff01;功能涵蓋&#xff0c;數據報表、打印設計、圖表報表、大屏設計等&#xff01; Web 版報表設計器&#xff0c;類似于excel操作風格&#xff0c;通過拖拽完成報…

開源代碼分享(13)—整合本地電力市場與級聯批發市場的投標策略(附matlab代碼)

1.引言 1.1摘要 本地電力市場是在分配層面促進可再生能源的效率和使用的一種有前景的理念。然而&#xff0c;作為一個新概念&#xff0c;如何設計和將這些本地市場整合到現有市場結構中&#xff0c;并從中獲得最大利潤仍然不清楚。在本文中&#xff0c;我們提出了一個本地市場…

中睿天下Coremail | 2023年第二季度企業郵箱安全態勢觀察

今日&#xff0c;中睿天下聯合Coremail郵件安全發布《2023第二季度企業郵箱安全性研究報告》&#xff0c;對2023第二季度和2023上半年的企業郵箱的安全風險進行了分析。 一 垃圾郵件同比下降16.38% 根據監測&#xff0c;2023年Q2垃圾郵件數量達到6.47億封&#xff0c;環比下降…

003-Spring boot 啟動流程分析

目錄 啟動流程分析創建 SpringApplication啟動 run(String... args) 啟動流程分析 SpringApplication.run(App.class, args);return new SpringApplication(primarySources).run(args);創建 SpringApplication SpringApplication(primarySources):this.primarySources new L…

opencv圖片灰度二值化

INCLUDEPATH D:\work\opencv_3.4.2_Qt\include LIBS D:\work\opencv_3.4.2_Qt\x86\bin\libopencv_*.dll #include <iostream> #include<opencv2/opencv.hpp> //引入頭文件using namespace cv; //命名空間 using namespace std;//opencv這個機器視…

Springloc和aop的基礎概念

什么是控制反轉和依賴注入&#xff1f; 控制反轉(IoC)和依賴注入(DI)是軟件開發中常用的編程范式&#xff0c; 它們極大地提高了代碼可維護性和可復用性&#xff0c;簡化了代碼結構。 什么是控制反轉(IoC) 控制反轉是- - 種編程模式&#xff0c;它將應用程序中的控制權轉移到…

使用 SSL/TLS 加強 MQTT 通信安全

在之前的文章中&#xff0c;我們探討了認證和訪問控制機制。接下來&#xff0c;我們將介紹傳輸層安全協議&#xff08;TLS&#xff09;在提升 MQTT 通信安全方面的重要作用。本文將著重介紹 TLS 以及它如何保證 MQTT 通信的完整性、機密性和真實性。 概念解釋 在開始之前&…

TypeScript項目中Axios的封裝

目錄 前言 一、axios中的常見類型 1. AxiosInstance 2. AxiosRequestConfig 3. AxiosResponse 4. AxiosError 二、axios封裝步驟 三、封裝后的完整代碼 1. 基礎封裝 2. 高級封裝 前言 為了實現統一的網絡請求處理和管理&#xff0c;在日常開發中我們常常封裝 axios&…