JVM(一)类加载机制

(一)JVM内存结构主要有三大块:堆内存,方法区,栈

  • 堆内存 不连续的内存结构,是JVM管理的最大的内存结构。存放实例对象,是所有线程共享的。是垃圾回收器(gc)处理的主要区域,所以也叫gc堆
  • 方法区 存放代码,常量,类变量,里面包含常量池。一般会说方法区位于堆中。所以也被所有线程共享
  • 栈 物理内存连续的一块区域,是线程独有的,生命周期与线程相同,存放局部变量地址,指向堆,可以访问到堆
  • javac 命令将java程序经过编译生成.class文件

javac 命令将java程序经过编译生成.class文件

(二)java 调用JVM运行程序,其中JVM类加载机制分为3部分

  • 加载类   
  •    class字节码加载到内存中,将静态数据保存到方法区中,作为运行时数据结构,同时在堆中生成一个代表这个类的java.lang.Class对象,(这也就是为什么可以通过反射访问类信息)作为访问该类方法区中数据的入口。类加载器被调用。

  • 链接类 (将程序的二进制代码合并到JVM运行中)
  • 验证 验证类是否符合jvm规范
  • 准备 为static变量分配内存,设置初始值
  • 解析 常量池中的符号引用替换为直接引用(代码中只有变量或者常量符号,这个过程是给出每个变量,常量的地址)

  • 初始化

  • 执行类构造器<clinit>()方法的过程,由编译器自动收集类变量的赋值操作和static代码块,合并。先初始化父类变量。类构造器线程安全

如下图,是左边代码类加载以后,JVM内存图,在方法区形成运行时数据结构,生成Class对象,先是加载Demo01类,然后链接然后初始化。紧接着加载类A,在方法区形成A的运行时数据结构,以及Class对象,然后链接,为static变量分配内存,然后初始化,执行类构造器的<clinit>()方法,首先初始化width=100,然后执行static块,所以程序的输出为

静态初始化类A

创建A类对象

300

类加载完成后,就开始执行main方法,在栈中为main线程分配自己的栈内存,存入局部变量a=null,调用A的构造方法创建A的对象,存在堆中,这是a=A对象在堆中的地址


(三)详解JVM加载类的初始化

public class Demo01 {
static{
System.out.println("类Demo01的静态初始化块");
}
public static void main(String[] args) {
System.out.println("进入main方法");
A a=new A();
System.out.println(a.width);

}
}
class A_son extends A{
{
System.out.println("类A_son的静态初始化块");
}
}
class A extends A_father{
public static int width=80;
public final static int MAX=100;
static{
System.out.println("类A的静态初始化块");
}
public A(){
width=90;
System.out.println("类A的构造方法");
}
}
class A_father{
static{
System.out.println("类A_father的静态初始化块");
}

}

输出:首先加载类Demo01,执行他的静态块,进入main方法,出现A,加载A,首先初始化A的父类,执行A_father的static块,再初始化A,执行A的static块,最后调用构造方法,最后输出


(1)原程序只改变main方法:

public static void main(String[] args) {
System.out.println("进入main方法");
A a=new A();
System.out.println(a.width);
A a1=new A();
}

输出:类只被加载一次

(2)原程序只改变main方法:

public static void main(String[] args) {
System.out.println("进入main方法");
System.out.println(A_son.width);

}

输出:通过A的子类A_son,访问A的静态变量,不会初始化A_son,静态域定义在哪个类中加载哪个类

(3)原程序只改变main方法:

public static void main(String[] args) {
System.out.println(A.MAX);

}

输出:访问类A的常量(比如final修饰)不会初始化类


(4)原程序只改变main方法:

public static void main(String[] args) {
System.out.println(A.width);

}

输出:访问类A的静态变量或方法会初始化类

(5)原程序只改变main方法:

public static void main(String[] args) {
Class s=Class.forName("xidian.lili.Demo01.Demo01");
Class s1=Class.forName("xidian.lili.Demo01.A");

}

输出:反射调用类,初始化

(6)原程序只改变main方法:

public static void main(String[] args) {
A[] a=new A[10];

}

输出:数组初始化调用。不会触发初始化


总结

类的主动引用

  • 加载类时,先加载父类(包括初始化)
  • new一个类的对象
  • 使用java.lang.Reflection包的方法进行调用
  • 先启动main方法所在的类

类的被动引用

  • 访问一个静态域(就是静态变量),只有真正定义这个静态域的类被初始化
  • 通过数组定义类引用,不会初始化
  • 引用类的常量不会触发类的初始化


猜你喜欢

转载自blog.csdn.net/wangdongli_1993/article/details/80979861