內存是很是重要的系統資源,是硬盤和cpu的中間倉庫及橋樑,承載着操做系統和應用程序的實時運行。JVM內存佈局規定了JAVA在運行過程當中內存申請、分配、管理的策略,保證了JVM的高效穩定運行。不一樣的jvm對於內存的劃分方式和管理機制存在着部分差別(對於Hotspot主要指方法區)java
java虛擬機定了了若干種程序運行期間會使用到的運行時數據區,其中有一些會隨着虛擬機啓動而建立,隨着虛擬機退出而銷燬。另一些則是與縣城一一對應的,這些與線程對應的數據區域會隨着線程開始和結束而建立和銷燬。
如圖,灰色的區域爲單獨線程私有的,紅色的爲多個線程共享的,即python
JVM中的程序計數寄存器(Program Counter Register)中,Register的命名源於CPU的寄存器,寄存器存儲指令相關的現場信息。CPU只有把數據裝載到寄存器纔可以運行。JVM中的PC寄存器是對屋裏PC寄存器的一種抽象模擬git
PC寄存器是用來存儲指向下一條指令的地址,也即將將要執行的指令代碼。由執行引擎讀取下一條指令。github
利用javap -v xxx.class反編譯字節碼文件,查看指令等信息面試
1.使用PC寄存器存儲字節碼指令地址有什麼用呢?/ 爲何使用PC寄存器記錄當前線程的執行地址呢? 由於CPU須要不停的切換各個線程,這時候切換回來之後,就得知道接着從哪開始繼續執行
JVM的字節碼解釋器就須要經過改變PC寄存器的值來明確下一條應該執行什麼樣的字節碼指令
2.PC寄存器爲何會設定爲線程私有
咱們都知道所謂的多線程在一個特定的時間段內指回執行其中某一個線程的方法,CPU會不停滴作任務切換,這樣必然會致使常常中斷或恢復,如何保證分毫無差呢?爲了可以準確地記錄各個線程正在執行的當前字節碼指令地址,最好的辦法天然是爲每個線程都分配一個PC寄存器,這樣一來各個線程之間即可以進行獨立計算,從而不會出現相互干擾的狀況。
因爲CPU時間片輪限制,衆多線程在併發執行過程當中,任何一個肯定的時刻,一個處理器或者多核處理器中的一個內核,只會執行某個線程中的一條指令。
這樣必然致使常常中斷或恢復,如何保證分毫無差呢?每一個線程在建立後,都會產生本身的程序計數器和棧幀,程序計數器在各個線程之間互不影響。算法
CPU時間片即CPU分配各各個程序的時間,每一個線程被分配一個時間段。稱做它的時間片。
在宏觀上:咱們能夠同時打開多個應用程序,每一個程序並行不悖,同時運行。 但在微觀上:因爲只有一個CPU,一次只能處理程序要求的一部分,如何處理公平,一種方法就是引入時間片,每一個程序輪流執行。
並行與併發
並行:同一時間多個線程同時執行;
併發:一個核快速切換多個線程,讓它們依次執行,看起來像並行,其實是併發編程
因爲跨平臺性的設計,java的指令都是根據棧來設計的。不一樣平臺CPU架構不一樣,因此不能設計爲基於寄存器的。
優勢是跨平臺,指令集小,編譯器容易實現,缺點是性能降低,實現一樣的功能須要更多的指令。數組
java虛擬機規範容許Java棧的大小是動態的或者是固定不變的緩存
/**
* 演示棧中的異常
*/
public class StackErrorTest {
public static void main(String[] args) {
main(args);
}
}
複製代碼
咱們可使用參數-Xss選項來設置線程的最大棧空間,棧的大小直接決定了函數調用的最大可達深度。 (IDEA設置方法:Run-EditConfigurations-VM options 填入指定棧的大小-Xss256k)安全
/**
* 演示棧中的異常
*
* 默認狀況下:count 10818
* 設置棧的大小: -Xss256k count 1872
*/
public class StackErrorTest {
private static int count = 1;
public static void main(String[] args) {
System.out.println(count);
count++;
main(args);
}
}
複製代碼
/**
* 棧幀
*/
public class StackFrameTest {
public static void main(String[] args) {
StackFrameTest test = new StackFrameTest();
test.method1();
//輸出 method1()和method2()都做爲當前棧幀出現了兩次,method3()一次
// method1()開始執行。。。
// method2()開始執行。。。
// method3()開始執行。。。
// method3()執行結束。。。
// method2()執行結束。。。
// method1()執行結束。。。
}
public void method1(){
System.out.println("method1()開始執行。。。");
method2();
System.out.println("method1()執行結束。。。");
}
public int method2(){
System.out.println("method2()開始執行。。。");
int i = 10;
int m = (int) method3();
System.out.println("method2()執行結束。。。");
return i+m;
}
public double method3(){
System.out.println("method3()開始執行。。。");
double j = 20.0;
System.out.println("method3()執行結束。。。");
return j;
}
}
複製代碼
每一個棧幀中存儲着:
利用javap命令對字節碼文件進行解析查看局部變量表,如圖:
public class LocalVariablesTest {
private int count = 1;
//靜態方法不能使用this
public static void testStatic(){
//編譯錯誤,由於this變量不存在與當前方法的局部變量表中!!!
System.out.println(this.count);
}
}
複製代碼
棧幀中的局部變量表中的槽位是能夠重複利用的,若是一個局部變量過了其做用域,那麼在其做用域以後申明的新的局部變量就頗有可能會複用過時局部變量的槽位,從而達到節省資源的目的。
private void test2() {
int a = 0;
{
int b = 0;
b = a+1;
}
//變量c使用以前以及經銷燬的變量b佔據的slot位置
int c = a+1;
}
複製代碼
變量的分類:
棧 :可使用數組或者鏈表來實現
爲何須要常量池呢
常量池的做用,就是爲了提供一些符號和常量,便於指令的識別。
** 在JVM中,將符號引用轉換爲調用方法的直接引用與方法的綁定機制相關 **
對應的方法的綁定機制爲:早起綁定(Early Binding)和晚期綁定(Late Bingding)。綁定是一個字段、方法或者類在符號引用被替換爲直接引用的過程,這僅僅發生一次。
隨着高級語言的橫空出世,相似於java同樣的基於面向對象的編程語言現在愈來愈多,儘管這類編程語言在語法風格上存在必定的差異,可是它們彼此之間始終保持着一個共性,那就是都支持封裝,集成和多態等面向對象特性,既然這一類的編程語言具有多態特性,那麼天然也就具有早期綁定和晚期綁定兩種綁定方式。
Java中任何一個普通的方法其實都具有虛函數的特徵,它們至關於C++語言中的虛函數(C++中則須要使用關鍵字virtual來顯式定義)。若是在Java程序中不但願某個方法擁有虛函數的特徵時,則可使用關鍵字final來標記這個方法。
子類對象的多態性使用前提:①類的繼承關係②方法的重寫
非虛方法
普通調用指令:
1.invokestatic:調用靜態方法,解析階段肯定惟一方法版本;
2.invokespecial:調用方法、私有及弗雷方法,解析階段肯定惟一方法版本;
3.invokevirtual調用全部虛方法;
4.invokeinterface:調用接口方法;
動態調用指令:
5.invokedynamic:動態解析出須要調用的方法,而後執行 .
前四條指令固化在虛擬機內部,方法的調用執行不可人爲干預,而invokedynamic指令則支持由用戶肯定方法版本。其中invokestatic指令和invokespecial指令調用的方法稱爲非虛方法,其他的(final修飾的除外)稱爲虛方法。
/**
* 解析調用中非虛方法、虛方法的測試
*/
class Father {
public Father(){
System.out.println("Father默認構造器");
}
public static void showStatic(String s){
System.out.println("Father show static"+s);
}
public final void showFinal(){
System.out.println("Father show final");
}
public void showCommon(){
System.out.println("Father show common");
}
}
public class Son extends Father{
public Son(){
super();
}
public Son(int age){
this();
}
public static void main(String[] args) {
Son son = new Son();
son.show();
}
//不是重寫的父類方法,由於靜態方法不能被重寫
public static void showStatic(String s){
System.out.println("Son show static"+s);
}
private void showPrivate(String s){
System.out.println("Son show private"+s);
}
public void show(){
//invokestatic
showStatic(" 大頭兒子");
//invokestatic
super.showStatic(" 大頭兒子");
//invokespecial
showPrivate(" hello!");
//invokespecial
super.showCommon();
//invokevirtual 由於此方法聲明有final 不能被子類重寫,因此也認爲該方法是非虛方法
showFinal();
//虛方法以下
//invokevirtual
showCommon();//沒有顯式加super,被認爲是虛方法,由於子類可能重寫showCommon
info();
MethodInterface in = null;
//invokeinterface 不肯定接口實現類是哪個 須要重寫
in.methodA();
}
public void info(){
}
}
interface MethodInterface {
void methodA();
}
複製代碼
當一個方法開始執行後,只要兩種方式能夠退出這個方法: 一、執行引擎遇到任意一個方法返回的字節碼指令(return),會有返回值傳遞給上層的方法調用者,簡稱正常完成出口;
二、在方法執行的過程當中遇到了異常(Exception),而且這個異常沒有在方法內進行處理,也就是隻要在本方法的異常表中沒有搜素到匹配的異常處理器,就會致使方法退出,簡稱異常完成出口
方法執行過程當中拋出異常時的異常處理,存儲在一個異常處理表,方便在發生異常的時候找處處理異常的代碼。
棧幀中還容許攜帶與java虛擬機實現相關的一些附加信息。例如,對程序調試提供支持的信息。(不少資料都忽略了附加信息)
1.舉例棧溢出的狀況?(StackOverflowError)
2.調整棧的大小,就能保證不出現溢出麼?
3.分配的棧內存越大越好麼?
4.垃圾回收是否會涉及到虛擬機棧?
內存區塊 | Error | GC |
---|---|---|
程序計數器 | ❌ | ❌ |
本地方法棧 | ✅ | ❌ |
jvm虛擬機棧 | ✅ | ❌ |
堆 | ✅ | ✅ |
方法區 | ✅ | ✅ |
5.方法中定義的局部變量是否線程安全?
/**
* 面試題:
* 方法中定義的局部變量是否線程安全?具體狀況具體分析
*
* 何爲線程安全?
* 若是隻有一個線程能夠操做此數據,則斃是線程安全的。
* 若是有多個線程操做此數據,則此數據是共享數據。若是不考慮同步機制的話,會存在線程安全問題
*
* StringBuffer是線程安全的,StringBuilder不是
*/
public class StringBuilderTest {
//s1的聲明方式是線程安全的
public static void method1(){
StringBuilder s1 = new StringBuilder();
s1.append("a");
s1.append("b");
}
//stringBuilder的操做過程:是不安全的,由於method2能夠被多個線程調用
public static void method2(StringBuilder stringBuilder){
stringBuilder.append("a");
stringBuilder.append("b");
}
//s1的操做:是線程不安全的 有返回值,可能被其餘線程共享
public static StringBuilder method3(){
StringBuilder s1 = new StringBuilder();
s1.append("a");
s1.append("b");
return s1;
}
//s1的操做:是線程安全的 ,StringBuilder的toString方法是建立了一個新的String,s1在內部消亡了
public static String method4(){
StringBuilder s1 = new StringBuilder();
s1.append("a");
s1.append("b");
return s1.toString();
}
public static void main(String[] args) {
StringBuilder s = new StringBuilder();
new Thread(()->{
s.append("a");
s.append("b");
}).start();
method2(s);
}
}
複製代碼
【代碼】
github.com/willShuhuan…
【筆記】
JVM_01 簡介
JVM_02 類加載子系統
JVM_03 運行時數據區1- [程序計數器+虛擬機棧+本地方法棧]
JVM_04 本地方法接口
JVM_05 運行時數據區2-堆
JVM_06 運行時數據區3-方法區
JVM_07 運行時數據區4-對象的實例化內存佈局與訪問定位+直接內存
JVM_08 執行引擎(Execution Engine)
JVM_09 字符串常量池StringTable
JVM_10 垃圾回收1-概述+相關算法
JVM_11 垃圾回收2-垃圾回收相關概念
JVM_12 垃圾回收3-垃圾回收器