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

2018-04-19  本文已影响0人  sofarsogoo_932d

前言

本文不会深入原理去讲解,而是通过具体的场景(代码)来理解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类的一个实例的原因

什么时候进行类加载

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

类加载过程

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

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

上一篇下一篇

猜你喜欢

热点阅读