java之内存分析
本文参考jdk1.8,由于内存分析对理解反射也有很重要的辅助作用,所以笔者也将一些基础知识列出,有不对的还望指正
Java内存:
- 堆
- 用来存放new的对象和数组
- 可以被所有的线程共享,不会存放别的对象引用
- 栈
- 存放基本变量类型(会包含这个基本类型的具体数值)
- 引用对象的变量(会存放这个引用在堆里的具体地址)
- 方法区
- 可以被所有的线程共享
- 包含了所有的class和static变量
类的加载过程
- 类的加载(Load):将类的class文件读入内存将接卸静态数据转换成方法区的运行时数据结构,再为之创建一个java.lang.Class对象,此过程由类加载器完成
- 类的链接:将类的二进制数据合并到JRE中
- 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配
- 解析:虚拟机常量池中的符号引用(常量名)替换为直接引用(地址)的过程
- 类的初始化:JVM负责对类进行初始化
- 执行类构造器</clinit/>()方法的过程,类构造器方法由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生(类构造器是构造类信息的,不是构造该类对象的构造器)
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
- 虚拟机会保证一个类的方法在多线程环境中被正确加锁和同步
示例代码
public class JvmTest {
public static void main(String[] args) {
NewTest test=new NewTest();
System.out.println(test.m);
}
/**
* 1.首先,在第一阶段类加载,即在堆中创建Class
* 2.然后到类链接阶段:设置默认值,分配内存,此时m=0
* 3.然后再对类进行初始化
* 先收集静态代码块,此时m=100,然后类变量的赋值动作,就是m=300
* 所以合并后的代码就是:m=100;m=300,所以最终m的值是300
*/
}
class NewTest{
static{
System.out.println("初始化静态代码块");
m=100;
}
static int m=300;
public NewTest(){
System.out.println("初始化无参构造器");
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NC22h1Cq-1627019158275)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210723113455832.png)]
什么时候会发生类的初始化
类的主动引用(一定会发生类的初始化)
- 虚拟机启动,先初始化main方法所在的类
- new一个类的对象
- 调用类的静态成员(除了final变量)和静态方法
- 使用反射包内的方法对类进行反射调用
- 当初始化一个类,如果父类没有被初始化,那么就会先初始化它的父类
类的被动引用(不会发生类的初始化)
- 当访问一个静态域的时候,只有枕真正声明这个域的类才会被初始化,例如,当通过子类引用父类的静态变量,不会导致子类初始化
- 通过数组定义类引用,不会触发此类的初始化
- 引用常量不会触发此类的初始化(常量在上面所讲到的链接阶段就存入常量池中了)
示例代码
public class Test01 {
static {
System.out.println("main类被加载");
}
public static void main(String[] args) throws ClassNotFoundException {
/**
* 主动引用
*/
son s=new son();//依次输出main类被加载 父类被加载 子类被加载
//反射方法
Class.forName("son");//依次输出main类被加载 父类被加载 子类被加载
/**
* 被动调用
*/
//通过子类引用父类的静态变量,不会导致子类初始化
son.f=2;//依次输出 main类被加载 父类被加载
//数组定义类引用
son[] sons=new son[10];//只输出 main类被加载
//引用常量不会触发此类的初始化
System.out.println(son.s2);//只输出 main类被加载 9
}
}
class father{
static int f=1;
static {
System.out.println("父类被加载");
}
}
class son extends father{
static int s=2;
static{
System.out.println("子类被加载");
}
static final int s2=9;
}
类加载器的作用
将class字节码内容记载到内存中,并将这些静态数据转换成方法区的运行是数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口
类缓存
一旦一个类被加载到类加载器中,它将维持加载一段时间,不过JVM垃圾回收机制可以回收这些Class对象