Java 内存分析
堆:存放new的对象和数组【可以被所有线程共享】;
栈:存放基本变量类型(和具体值)、引用对象的变量(在堆里的具体地址);
方法区:存放所有的class和static变量【可以被所有线程共享】。
《java 堆栈,方法区(永久代)的理解》:
类的对象放在heap(堆)中,所有的类对象都是通过new方法创建,创建后,在stack(栈)会创建类对象的引用(内存地址)。
类的加载
类的加载(Load) - 类的链接(Link) - 类的初始化(Initialize)
Load:将类的.class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成。
类加载器
引导类加载器
拓展类加载器
系统类加载器
Link:将类的二进制数据合并到JRE中
Initialize:JVM负责堆类进行初始化
加载(生成Class对象)
将 .class文件 字节码内容 加载到内存 中,并将 静态数据 转换成 方法区的运行时数据结构,然后在堆中生成一个代表该类的java.lang.Class对象
链接
将Java类的二进制代码合并到JVM的运行状态之中的过程。
- 验证:确保加载的类信息符合JVM规范,没有安全方面的问题
- 准备:正式为类变量(static)分配内存,并设置类变量默认初始值的阶段。这些内存都将在方法区中进行分配
- 解析:虚拟机常量池内的**符号引用(常量名)替换为直接引用(地址)**的过程
一文解析Java常量池、静态常量池、运行时常量池和字符串常量池的区别与联系:
Java常量池是Java编译器在编译Java源代码时,为了优化性能和节省空间所创建的一种常量缓存机制。它包含了所有的基本数据类型、字符串常量、符号引用等常量,这些常量都是在编译期被确定下来的,并被存储在.class文件的常量池中。在Java程序运行时,当需要使用这些常量时,就可以直接从常量池中取出,而不需要再进行计算或创建对象,从而提高了程序的运行效率。同时,Java常量池还可以避免重复创建相同的常量,从而减少了内存占用。
int a; //变量
const int b = 10; //b为常量,10为字面量
string str = “hello world!”; // str 为变量,hello world!为字面量
初识化
- 执行 类构造器
<clinit>()
class init
方法的过程。类构造器<clinit>()
方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块*中的语句合并产生的。
(类构造器是构造类信息的,不是构造该类对象的构造器)
静态代码块是Java中的一个代码块,使用关键字 “static” 声明,并被包含在类的声明中。
- 静态代码块常用于在类加载时进行一些初始化操作,例如初始化静态变量或执行静态方法。
- 它们的执行顺序是在类被加载时自上而下执行。
- 静态代码块在类加载时只执行一次,且只能初始化类变量,即static修饰的数据成员。
static { ... }
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
- 虚拟机 会保证一个类的
<clinit>()
方法在多线程环境中杯正确加锁和同步。
什么时候会发生类初始化?
类的主动引用(一定会发生类初始化)
- 虚拟机启动,先初始化main所在的类
- new一个类的对象
- 调用类的静态成员(除了final常量)和静态方法
- 使用java.lang.reflect包的方法对类进行反射调用
Class.forName("com.Person");
- 当初始化一个类,如果其父类没有被初始化,则先回初始化它的父类
类的被动引用(不会发生类初始化)
- 通过子类引用父类的静态变量,不会导致子类初始化
- 通过数组定义类引用,不会触发此类的初始化
User[] array = new User[5];
- 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
在Java中,引用常量是指一个引用,它被声明为final并且初始化为一个值。这个值不能被改变,因此这个引用是一个常量。
public static final String HELLO_WORLD = "Hello World";
在这个例子中,HELLO_WORLD是一个引用常量,它是一个字符串类型的常量。