目錄:
1. JVM啓動流程
2. JVM基本結構
3. 內存模型
4. 編譯和解釋運行的概念
1.JVM啓動流程
2.JVM基本結構
2.1 PC寄存器
- 每個線程擁有一個PC寄存器
- 在線程創建時 創建
- 指向下一條指令的地址
- 執行本地方法時,PC的值爲undefined
2.2 方法區
- 保存裝載的類信息
- 類型的常量池
- 字段,方法信息
- 方法字節碼
- 通常和永久區(Perm)關聯在一起
JDK6時,String等常量信息置於方法
JDK7時,已經移動到了堆
2.3 Java堆
- 和程序開發密切相關
- 應用系統對象都保存在Java堆中
- 所有線程共享Java堆
- 對分代GC來說,堆也是分代的
- GC的主要工作區間
2.4 Java棧
- 線程私有
- 棧由一系列幀組成(因此Java棧也叫做幀棧)
- 幀保存一個方法的局部變量、操作數棧、常量池指針
- 每一次方法調用創建一個幀,並壓棧
- 局部變量表 包含參數和局部變量
public class StackDemo {
public static int runStatic(int i,long l,float f,Object o ,byte b){
return 0;
}
public int runInstance(char c,short s,boolean b){
return 0;
}
}
- 函數調用組成幀棧
public static int runStatic(int i,long l,float f,Object o ,byte b){
return runStatic(i,l,f,o,b);
}
- 操作數棧
- Java沒有寄存器,所有參數傳遞使用操作數棧;
public static int add(int a,int b){
int c=0;
c=a+b;
return c;
}
0: iconst_0 // 0壓棧
1: istore_2 // 彈出int,存放於局部變量2
2: iload_0 // 把局部變量0壓棧
3: iload_1 // 局部變量1壓棧
4: iadd //彈出2個變量,求和,結果壓棧
5: istore_2 //彈出結果,放於局部變量2
6: iload_2 //局部變量2壓棧
7: ireturn //返回
- 棧上分配
public class OnStackTest {
public static void alloc(){
byte[] b=new byte[2];
b[0]=1;
}
public static void main(String[] args) {
long b=System.currentTimeMillis();
for(int i=0;i<100000000;i++){
alloc();
}
long e=System.currentTimeMillis();
System.out.println(e-b);
}
}
- 小對象(一般幾十個bytes),在沒有逃逸的情況下,可以直接分配在棧上
- 直接分配在棧上,可以自動回收,減輕GC壓力
- 大對象或者逃逸對象無法棧上分配
- 棧、堆、方法區交互
public class AppMain {
//運行時, jvm 把appmain的信息都放入方法區
public static void main(String[] args) {
//main 方法本身放入方法區。
Sample test1 = new Sample( " 測試1 " );
//test1是引用,所以放到棧區裏, Sample是自定義對象應該放到堆裏面 Sample test2 = new Sample( " 測試2 " );
test1.printName();
test2.printName();
}
public class Sample {
//運行時, jvm 把appmain的信息都放入方法區
private name;
//new Sample實例後, name 引用放入棧區裏, name 對象放入堆裏 public Sample(String name) {
this .name = name;
}
//print方法本身放入 方法區裏。
public void printName() {
System.out.println(name);
}
}
3.內存模型
每一個線程有一個工作內存和主存獨立
工作內存存放主存中變量的值的拷貝
當數據從主內存複製到工作存儲時,必須出現兩個動作:第一,由主內存執行的讀(read)操作;第二,由工作內存執行的相應的load操作;當數據從工作內存拷貝到主內存時,也出現兩個操作:第一個,由工作內存執行的存儲(store)操作;第二,由主內存執行的相應的寫(write)操作
每一個操作都是原子的,即執行期間不會被中斷
對於普通變量,一個線程中更新的值,不能馬上反應在其他變量中
如果需要在其他線程中立即可見,需要使用 volatile 關鍵字
- volatile
public class VolatileStopThread extends Thread{
private volatile boolean stop = false;
public void stopMe(){
stop=true;
}
public void run(){
int i=0;
while(!stop){
i++;
}
System.out.println("Stop thread");
}
public static void main(String args[]) throws InterruptedException{
VolatileStopThread t=new VolatileStopThread();
t.start();
Thread.sleep(1000);
t.stopMe();
Thread.sleep(1000);
}
- 沒有volatile -server 運行 無法停止
- volatile 不能代替鎖
- 一般認爲volatile 比鎖性能好(不絕對)
- 選擇使用volatile的條件是:語義是否滿足應用
-
可見性
- 一個線程修改了變量,其他線程可以立即知道
-
保證可見性的方法
- volatile
- synchronized (unlock之前,寫變量值回主存)
- final(一旦初始化完成,其他線程就可見)
-
有序性
- 在本線程內,操作都是有序的
- 在線程外觀察,操作都是無序的。(指令重排 或 主內存同步延時)
-
指令重排
- 線程內串行語義
- 寫後讀 a = 1;b = a; 寫一個變量之後,再讀這個位置。
- 寫後寫 a = 1;a = 2; 寫一個變量之後,再寫這個變量。
- 讀後寫 a = b;b = 1; 讀一個變量之後,再寫這個變量。
- 以上語句不可重排
- 編譯器不考慮多線程間的語義
- 可重排: a=1;b=2;
-
指令重排 – 破壞線程間的有序性
class OrderExample {
int a = 0;
boolean flag = false;
public void writer() {
a = 1;
flag = true;
}
public void reader() {
if (flag) {
int i = a +1;
……
}
}
}
線程A
flag=true
a=1
線程B
flag=true(此時a=0)
線程A首先執行writer()方法
線程B線程接着執行reader()方法
線程B在int i=a+1 是不一定能看到a已經被賦值爲1
因爲在writer中,兩句話順序可能打亂
- 指令重排 – 保證有序性的方法
class OrderExample {
int a = 0;
boolean flag = false;
public synchronized void writer() {
a = 1;
flag = true;
}
public synchronized void reader() {
if (flag) {
int i = a +1;
……
}
}
}
線程A
flag=true
a=1
線程B
flag=true(此時a=1)
同步後,即使做了writer重排,因爲互斥的緣故,reader 線程看writer線程也是順序執行的。
- 指令重排的基本原則
- 程序順序原則:一個線程內保證語義的串行性
- volatile規則:volatile變量的寫,先發生於讀
- 鎖規則:解鎖(unlock)必然發生在隨後的加鎖(lock)前
- 傳遞性:A先於B,B先於C 那麼A必然先於C
- 線程的start方法先於它的每一個動作
- 線程的所有操作先於線程的終結(Thread.join())
- 線程的中斷(interrupt())先於被中斷線程的代碼
- 對象的構造函數執行結束先於finalize()方法
4.解釋運行和編譯運行(JIT)
-
解釋運行
- 解釋執行以解釋方式運行字節碼
- 解釋執行的意思是:讀一句執行一句
-
編譯運行(JIT)
- 將字節碼編譯成機器碼
- 直接執行機器碼
- 運行時編譯
- 編譯後性能有數量級的提升