JVM系列之一:基础

JVM的概念

    JVMJava Virtual Machine的简称意为Java虚拟机。JVM使用软件模拟Java 字节码的指令集

虚拟机

    指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统

有哪些虚拟机

 

 VMWare

 Visual Box

 JVM

VMWare或者Visual Box都是使用软件模拟物理CPU的指令集

 

Java语言规范

这里只列举了部分常见的规范并且简略介绍。详细请移步官网。

语法

语法定义:

IfThenStatement:

if(true){do sth;}

ArgumentList:

add(a,b,c,d);

词法结构:

- \u + 416进制数字 表示UTF-16

行终结符: CR, or LF, or CR LF.

- 空白符

• 空格 tab \t 换页 \f  行终结符

- 注释

- 标示符

- 关键字

命名规范

类型与变量

元类型

byte short int long float char

变量初始值

• boolean false

• char \u0000

更多

– Java内存模型

– 类加载链接的过程

– public static final abstract的定义

– 异常

– 数组的使用

– …….

 

JVM规范

JVM规范的定义

Java语言规范定义了什么是Java语言,Java语言和JVM相对独立,JVM主要定义二进制class文件和JVM指令集等。

JVM常见规范

– Class文件类型

– 数字的内部表示和存储

– 运行时数据

– 帧栈

– 虚拟机的启动

– 虚拟机的指令集

– 类型转化

– l2i  

– 出栈入栈操作

– aload  astore

– 运算

– iadd  isub

– 流程控制

– ifeq ifne

– 函数调用

invokevirtual invokeinterface  invokespecial  invokestatic

JVM需要对Java Library 提供以下支持

– 反射 java.lang.reflect

– ClassLoader

– 初始化classinterface

– 安全相关 java.security

– 多线程

– 弱引用

 

JVM启动流程

JVM基本结构

PC寄存器

– 每个线程拥有一个PC寄存器

– 在线程创建时 创建

– 指向下一条指令的地址

方法区

– 保存装载的类信息

     类型的常量池

     字段,方法信息

     方法字节码

      通常和永久区(Perm)关联在一起

Java

– 和程序开发密切相关

– 应用系统对象都保存在Java堆中

– 所有线程共享Java

– 对分代GC来说,堆也是分代的,堆空间里面主要分为新生代(eden+幸存区fromto)和老年代,from和to在后期会介绍到

– GC的主要工作区间

 

Java

– 线程私有

– 栈由一系列帧组成(因此Java栈也叫做帧栈)

– 帧保存一个方法的局部变量表、操作数栈、常量池指针

局部变量表:包含参数和局部变量

操作数栈:Java没有寄存器,所有参数传递使用操作数栈

每一次方法调用创建一个帧,并压栈

执行本地方法时,PC的值为undefined

– 栈上分配概念和实现

我们都知道Java中的对象都是在堆上分配的,而垃圾回收机制会回收堆中不再使用的对象,但是筛选可回收对象,回收对象还有整理内存都需要消耗时间。如果能够通过逃逸分析确定某些对象不会逃出方法之外,那就可以让这个对象在栈上分配内存。

– 小对象(一般几十个bytes),没有逃逸的情况下,可以分配在栈上

– 直接分配在栈上,可以自动回收,减轻GC压力

– 大对象或者逃逸对象无法栈上分配

– JVM使用 -XX:+DoEscapeAnalysis 来实现栈上分配

逃逸对象是指方法中定义的变量会被外部调用,外部指别的线程或方法。

逃逸分析会有时间消耗,所以性能未必提升多少,并且由于逃逸分析比较耗时,目前的实现都是采用不那么准确但是时间压力相对较小的算法来完成逃逸分析,这就可能导致效果不稳定,要慎用。

栈、堆、方法区交互

[java]  view plain  copy
  1. public   class  AppMain     <span style="font-weight:bold;">//运行时, jvm 把AppMain</span><span style="font-family:'宋体';"><strong>的信息都放入方法区</strong></span>  
  2. {   
  3.     //main 方法本身放入方法区。  
  4.     public   static   void  main(String[] args) {   
  5.        //test1是引用,所以放到栈区里, Sample是自定义对象应该放到堆里面  
  6.        Sample test1 = new  Sample( " 测试1 " );    
  7.     
  8.         Sample test2 = new  Sample( " 测试2 " );   
  9.         test1.printName();   
  10.         test2.printName();   
  11.     }   
  12. }  
  13. //运行时, jvm 把appmain的信息都放入方法区  
  14. public   class  Sample{        
  15.  //new Sample实例后, name 引用放入栈区里,  name 对象放入堆里   
  16.  private String name;       
  17.  public  Sample(String name) { this .name = name; }   
  18.  //print方法本身放入 方法区里。  
  19.  public   void  printName()    { System.out.println(name); }   
  20. }  

内存模型

– 每一个线程有一个工作内存和主存独立

– 工作内存存放主存中变量的值的拷贝

 

当数据从主内存复制到工作存储时,必须出现两个动作:第一,由主内存执行的读(read)操作;第二,由工作内存执行的相应的load操作;当数据从工作内存拷贝到主内存时,也出现两个操作:第一个,由工作内存执行的存储(store)操作;第二,由主内存执行的相应的写(write)操作。

对于普通变量,一个线程中更新的值,不能马上反应在其他变量中

如果需要在其他线程中立即可见,需要使用 volatile 关键字

因为内存模型的设计,会出现以下几种特性:

– 可见性

    一个线程修改了变量,其他线程可以立即知道。

        – 保证可见性的方法

        – volatile

        – synchronized unlock之前,写变量值回主存)

        – final(一旦初始化完成,其他线程就可见)

– 有序性

    在本线程内,操作都是有序的

  在线程外观察,操作都是无序的。(指令重排  主内存同步延时)

    指令重排

        - 线程内串行语义

            • 写后读 a = 1;b = a; 写一个变量之后,再读这个位置。

            • 写后写 a = 1;a = 2; 写一个变量之后,再写这个变量。

            • 读后写 a = b;b = 1; 读一个变量之后,再写这个变量。

            • 以上语句不可重排

            • 编译器不考虑多线程间的语义

            • 可重排: a=1;b=2;

        -破坏线程间的有序性

[java]  view plain  copy
  1. class OrderExample {  
  2.   
  3.     int a = 0;  
  4.   
  5.     boolean flag = false;  
  6.   
  7.    
  8.   
  9.     public void writer() {  
  10.   
  11.         a = 1;                     
  12.   
  13.         flag = true;             
  14.   
  15.     }  
  16.   
  17.    
  18.   
  19.     public void reader() {  
  20.   
  21.         if (flag) {                  
  22.   
  23.             int i =  a +1;        
  24.   
  25.             ……  
  26.   
  27.             }  
  28.         }  
  29.   
  30. }  

            线程A首先执行writer()方法

            线程B线程接着执行reader()方法

            线程Bint i=a+1 是不一定能看到a已经被赋值为1

            因为在writer中,两句话顺序可能打乱

        -保证有序性的方法

[java]  view plain  copy
  1. class OrderExample {  
  2.   
  3.     int a = 0;  
  4.   
  5.     boolean flag = false;  
  6.   
  7.    
  8.   
  9.     public synchronized void writer() {  
  10.   
  11.         a = 1;                     
  12.   
  13.         flag = true;             
  14.   
  15.     }  
  16.   
  17.    
  18.   
  19.     public synchronized void reader() {  
  20.   
  21.         if (flag) {                  
  22.   
  23.         int i =  a +1;        
  24.   
  25.         ……  
  26.   
  27.         }  
  28.   
  29.     }  
  30.   
  31. }  

          同步后,即使做了writer重排,因为互斥的缘故,reader 线程看writer线程也是顺序执行的。

        -指令重排的基本原则

            – 程序顺序原则:一个线程内保证语义的串行性

            – volatile规则:volatile变量的写,先发生于读

            – 锁规则:解锁(unlock)必然发生在随后的加锁(lock)

            – 传递性:A先于BB先于那么A必然先于C

            – 线程的start方法先于它的每一个动作

            – 线程的所有操作先于线程的终结(Thread.join()

            – 线程的中断(interrupt())先于被中断线程的代码

            – 对象的构造函数执行结束先于finalize()方法

 

编译和解释运行的概念

解释运行

    – 解释执行以解释方式运行字节码

    – 解释执行的意思是:读一句执行一句

编译运行(JIT

    – 将字节码编译成机器码

    – 直接执行机器码

    – 运行时编译

    – 编译后性能有数量级的提升

    新版本的jvm默认都是采用混合执行模式程序字节码经过JIT环境变量进行判断,是否属于热点代码(多次调用的方法,或循环等),如是,走JIT编译为具体硬件处理器(如sparcintel)机器码,如否,则直接由解释器解释执行。

猜你喜欢

转载自blog.csdn.net/qq_21508727/article/details/80622345