(一)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();
}
输出:类只被加载一次,因为用的是同一个加载器,所以JVM认为是同一个类
(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方法所在的类
类的被动引用
- 访问一个静态域(就是静态变量),只有真正定义这个静态域的类被初始化
- 通过数组定义类引用,不会初始化
- 引用类的常量不会触发类的初始化