美文网首页
java解惑之jvm内存区域和类加载机制

java解惑之jvm内存区域和类加载机制

作者: sofarsogoo_932d | 来源:发表于2018-04-19 22:38 被阅读0次

    前言

    本文不会深入原理去讲解,而是通过具体的场景(代码)来理解java的内存区域和类加载机制

    java内存区域划分

    从jvm角度看,分为堆,栈,方法区,本地方法栈,程序计数器


    1.png

    从操作系统角度看,分为堆,栈,数据段,代码段

    程序计数器
    线程私有,指示的是当前线程字节码执行到的位置

    虚拟机栈
    线程私有,描述的是java方法执行的内存模型,每一个方法从执行到结束,都对应一个方法栈帧的入栈和出栈,方法中的局部变量就存储在栈中,当然指的是基本类型和引用类型的引用,实例还是存放在堆中
    栈是一块连续的内存区域,大小由操作系统决定,不会产生碎片

    本地方法栈
    和虚拟机栈的作用类似,只不过本地方法栈是为jvm使用到的Native方法服务


    线程共享区域,存放着几乎所有的实例
    一块大但是不连续的内存区域,容易产生碎片,一般说的内存泄漏,主要指的就这块

    方法区
    线程共享区域,存储已经被jvm加载的类的信息,常量,静态变量,编译器编译后的代码等数据
    运行时常量池就是方法区的一部分,用于存放各种字面量和符号引用
    字面量:基本类型的包装类,字符串类型,final修饰的常量

    代码示例

    public class People{
      int a = 1;
      final int b=2;
      static int c=3;
      Student s1 = new Student();
      public void XXX(){
          int d = 1;
          Student s2 = new Student();
      }
    }
    

    问a,b,c,d的内存在哪里,s1,s2的内存在哪里?
    记住下面几句话。
    成员变量全部存储在堆中(包括基本数据类型,引用及引用的对象实体),因为他们属于对象,对象都是存储在堆中的
    局部变量的基本数据类型和引用存储于栈当中,引用的对象实体存储在堆中。因为他们属于方法当中的变量,生命周期会随着方法一起结束。
    常量,静态变量(属于类)存放在常量池
    存放在堆中的有a,s1和s1的实例,s2的实例
    存放在栈中的有d,s2
    存放在方法区(常量池)的有b,c

    类加载

    什么是类加载

    类加载指的是将.class文件的二进制数据读取到内存中,将其放入内存区域中的方法区,然后在堆中创建一个Class对象,这就是我们说的任何类都是Class类的一个实例的原因

    什么时候进行类加载

    简单说就是类在用到的时候才会被加载,下面我来列举一些情况来说明什么叫被用到

    • 创建类的实例
    • 调用类的静态变量(final修饰的静态变量除外)
    • 调用类的静态方法
    • 反射如Class.forName(com.xxx.xxx)
    类加载过程

    1.类加载
    类的加载是由类加载器来完成加载的
    说到类加载,我们来简单说下双亲委派机制
    java自带的3中类加载器分别是AppClassLoader,ExtClassLoader和Bootstrap
    我们自己写的类首先会调用AppClassLoader去加载自身,但它不会马上加载,而是调用ExtClassLoader去加载,它也不会马上加载,而是调用Bootstrap去加载,如果Bootstrap加载不了,就让ExtClassLoader去加载,还加载不了就让AppClassLoader加载

    2.类的连接
    类连接有3步,分别是验证,准备,解析
    这里我只说明一下准备阶段做的工作
    为类的静态变量分配内存并赋值默认的初始值

     public static int value=123;
    

    在准备阶段value的值是0

    3.类的初始化(顺序)

    • 没有父类的情况
      类的静态属性
      类的静态代码块
      类的非静态属性
      类的非静态代码块
      构造方法

    • 有父类的情况
      父类的静态属性
      父类的静态代码块
      子类的静态属性
      子类的静态代码块
      父类的非静态属性
      父类的非静态代码块
      父类的构造方法
      子类的非静态属性
      子类的非静态代码块
      子类的构造方法

    代码示例1
    public class SingleTon {
    
      private static SingleTon singleTon = new SingleTon();
      public static int count1;
      public static int count2 = 0;
    
      private SingleTon() {
        count1++;
        count2++;
      }
    
      public static SingleTon getInstance() {
        return singleTon;
      }
    
      public static void main(String[] args) {
        SingleTon singleTon = SingleTon.getInstance();
        System.out.println("count1=" + singleTon.count1);
        System.out.println("count2=" + singleTon.count2);
      }
    }
    

    结果输出

    count1=1
    count2=0
    

    结果分析
    1.调用类的静态方法会触发类的加载
    2.在准备阶段为静态变量分配内存并赋值默认初始值
    singleTon=null;
    count1=0;
    count2=0;
    3.初始化操作(赋值操作),初始化是从上到下依次执行的
    先调用new操作,count1=1,count2=1
    count1没有赋值操作
    count2进行赋值操作

    修改代码如下

    public class SingleTon {
    
      public static int count1;
      public static int count2 = 0;
      private static SingleTon singleTon = new SingleTon();
    
    
    private SingleTon() {
        count1++;
        count2++;
    }
    
    public static SingleTon getInstance() {
        return singleTon;
    }
    
    public static void main(String[] args) {
        SingleTon singleTon = SingleTon.getInstance();
        System.out.println("count1=" + singleTon.count1);
        System.out.println("count2=" + singleTon.count2);
      }
    }
    

    结果输出

    count1=1
    count2=1
    
    代码示例2
    public class HelloA {
    
      public static int a=1;
      
      static{
        System.out.println("Static A");
      }
    
      {
        System.out.println("I am A class");
      }
    
    public HelloA(int i){
        System.out.println("Hello A");
      }
    }
    
    public class HelloB  extends HelloA{
      public static int b=0;
    
      static{
        System.out.println("Static B");
      }
    
      {
        System.out.println("I am B class");
      }
    
      public HelloB(){
        super(1);
        System.out.println("Hello B");
      }
    
      public static void main(String[] args) {
        new HelloB();   
        //HelloB.a=1;
      }
    }
    

    输出结果

    Static A
    Static B
    I am A class
    Hello A
    I am B class
    Hello B
    

    结果分析
    参考前面类的初始化顺序

    相关文章

      网友评论

          本文标题:java解惑之jvm内存区域和类加载机制

          本文链接:https://www.haomeiwen.com/subject/cohklxtx.html