zoukankan      html  css  js  c++  java
  • JVM运行机制

    jvm启动流程

    JVM基本结构

    JVM内存空间:

    1、方法区:

      保存装载的类信息

      • 类型常量池  
      • 字段、方法信息
      • 方法字节码

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

    JDK6时,String等常量信息置于方法
    JDK7时,已经移动到了堆

    2、PC寄存器

      • 每个线程拥有一个PC寄存器
      • 在线程创建时创建
      • 指向下一条指令的地址
      • 执行本地方法时,PC的值为undefined

    3、JAVA堆  

      • 和程序开发密切相关

      • 应用系统对象都保存在Java堆中
      • 所有线程共享Java堆
      • 对分代GC来说,堆也是分代的
      • GC的主要工作区间

    4、Java栈

      • 线程私有
      • 栈由一系列帧组成(因此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; } }
      runStatic            runInstance

                                         

    java栈之操作数栈:

      与局部变量表一样,均以字长为单位的数组。不过局部变量表用的是索引,操作数栈是弹栈/压栈来访问。操作数栈可理解为java虚拟机栈中的一个用于计算的临时数据存储区。

    存储的数据与局部变量表一致含int、long、float、double、reference、returnType,操作数栈中byte、short、char压栈前(bipush)会被转为int。
    - 数据运算的地方,大多数指令都在操作数栈弹栈运算,然后结果压栈。
    - java虚拟机栈是方法调用和执行的空间,每个方法会封装成一个栈帧压入占中。其中里面的操作数栈用于进行运算,当前线程只有当前执行的方法才会在操作数栈中调用指令(可见java虚拟机栈的指令主要取于操作数栈)。

     java栈之函数调用:

    public static int runStatic(int i,long l,float  f,Object o ,byte b){
    return runStatic(i,l,f,o,b);
    }

     每次方法调用会创建一个栈帧并压栈(注意:此方法由于递归调用没有结束判断,最终会导致栈内存溢出)

                  栈帧              栈帧                栈帧

    java栈之栈上分配:

      JAVA中的对象都是在堆上进行分配,当对象没有被引用的时候,需要依靠GC进行回收内存,如果对象数量较多的时候,会给GC带来较大压力,也间接影响了应用的性能。为了减少临时对象在堆内分配的数量,JVM通过逃逸分析确定该对象不会被外部访问。那就通过标量替换将该对象分解在栈上分配内存,这样该对象所占用的内存空间就可以随栈帧出栈而销毁,就减轻了垃圾回收的压力。

    • 小对象(一般几十个bytes),在没有逃逸的情况下,可以直接分配在栈上
    • 直接分配在栈上,可以自动回收,减轻GC压力
    • 大对象或者逃逸对象无法栈上分配

      逃逸分析:通过对象的作用范围的分析,为其他优化手段提供分析数据从而进行优化。

    逃逸分析包括:

    • 全局变量赋值逃逸
    • 方法返回值逃逸
    • 实例引用发生逃逸
    • 线程逃逸:赋值给类变量或可以在其他线程中访问的实例变量.

     举例:

    public class EscapeAnalysis {
     
         public static Object object;
         
         public void globalVariableEscape(){//全局变量赋值逃逸  
             object =new Object();  
          }  
         
         public Object methodEscape(){  //方法返回值逃逸
             return new Object();
         }
         
         public void instancePassEscape(){ //实例引用发生逃逸
            this.speak(this);
         }
         
         public void speak(EscapeAnalysis escapeAnalysis){
             System.out.println("Escape Hello");
         }
    }

     使用方法逃逸的案例进行分析:

    public StringBuffer createString(String ... values){
             StringBuffer stringBuffer = new StringBuffer(); 
             for (String string : values) {
                 stringBuffer.append(string+",");
            }
             return stringBuffer;
         }
      从上面的案例我们看出stringBuffer是属于方法返回值逃逸。我们可以通过改变返回值得类型为String限定了StringBuffer的作用域在createString方法中从而不发生逃逸。
    修改后:
     public String createString(String ... values){
             StringBuffer stringBuffer = new StringBuffer(); 
             for (String string : values) {
                 stringBuffer.append(string+",");
            }
             return stringBuffer.toString();
         }
     通过-XX:-DoEscapeAnalysis关闭逃逸分析,默认是开启的

    栈上分配举例:

    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);
        }
    }

      

     开启逃逸分析:

    -server -Xmx10m -Xms10m
    -XX:+DoEscapeAnalysis -XX:+PrintGC

    输出结果:12

    关闭逃逸分析:

    -server -Xmx10m -Xms10m
    -XX:-DoEscapeAnalysis -XX:+PrintGC

    [GC 3076K->516K(10240K), 0.0197071 secs]
    [GC 3076K->516K(10240K), 0.0023330 secs]
    [GC 3076K->516K(10240K), 0.0080232 secs]
    [GC 3076K->516K(10240K), 0.0014545 secs]
    [GC 3076K->516K(10240K), 0.0010420 secs]
    [GC 3076K->516K(10240K), 0.0010933 secs]
    3112

    栈、堆、方法区交互:

           

     

    内存模型

    • 每一个线程有一个工作内存和主存独立
    • 工作内存存放主存中变量的值的拷贝
    1. 当数据从主内存复制到工作存储时,必须出现两个动作:第一,由主内存执行的读(read)操作;第二,由工作内存执行的相应的load操作;当数据从工作内存拷贝到主内存时,也出现两个操作:第一个,由工作内存执行的存储(store)操作;第二,由主内存执行的相应的写(write)操作
    2. 每一个操作都是原子的,即执行期间不会被中断
    3. 对于普通变量,一个线程中更新的值,不能马上反应在其他变量中
    4. 如果需要在其他线程中立即可见,需要使用 volatile 关键字

     volatile:

    下面代码如果没有volatile -server 运行 无法停止 ,volatile 不能代替锁 一般认为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
    • synchronized (unlock之前,写变量值回主存)
    • final(一旦初始化完成,其他线程就可见)

      有序性

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

      指令重排

    • 线程内串行语义写后读 a = 1;b = a; 写一个变量之后,再读这个位置。
    • 写后写 a = 1;a = 2; 写一个变量之后,再写这个变量。
    • 读后写 a = b;b = 1; 读一个变量之后,再写这个变量。
    • 以上语句不可重排
    • 编译器不考虑多线程间的语义
    • 可重排: a=1;b=2;

      指令重排的基本原则

    • 程序顺序原则:一个线程内保证语义的串行性
    • volatile规则:volatile变量的写,先发生于读
    • 锁规则:解锁(unlock)必然发生在随后的加锁(lock)前
    • 传递性:A先于B,B先于C 那么A必然先于C
    • 线程的start方法先于它的每一个动作
    • 线程的所有操作先于线程的终结(Thread.join())
    • 线程的中断(interrupt())先于被中断线程的代码
    • 对象的构造函数执行结束先于finalize()方法
  • 相关阅读:
    js ++i和i++的区别
    js斐波那契数列
    js二分查找算法
    js查找、自组织数据
    查找数组最小值、最大值
    CSS布局(圣杯、双飞翼、flex)
    碰撞检测实现
    ECharts注释
    购物查看放大
    动手封装一个滚轮事件吧!
  • 原文地址:https://www.cnblogs.com/qq99514925/p/10922203.html
Copyright © 2011-2022 走看看