目录
一、JVM内存模型
当前三大JAVA虚拟机:
1. HotSpot(Sun/Oracle)、2.JRockit(Oracle)、3. J9(IBM,)
它们都有方法区,但只有HotSpot有永久代(老年代),且JDK1.8及之后改为元空间
五大内存区域:
1. 线程共有:堆,方法区
2. 栈,程序计数器,本地方法栈
1. 堆
线程共享,在虚拟机启动时创建,主要用于存放对象和数组。分为新生代和老年代,比例为1:2。
新生代代可划分为Eden区、survivor1区、survivor2区,默认比例为8:1:1
-Xms:值,初始堆内存大小
-Xmx:值,最大堆内存大小
-Xmn:值,新生代内存大小
2. 方法区
线程共享,在虚拟机启动时创建。JDK1.8之前是堆的一个逻辑分区,称之为“非堆”。该区域主要存放类的元数据信息、运行时常量池、静态变量、即时编译器编译的代码缓存。
即时编译:是执行计算机代码的一种方法,在程序执行过程中的执行期而不是执行之前的编译器,通常包括源代码或字节码到机器码的转换
在不同的JDK版本中,方法区中存储的位置及实现是不一样的。
JDK1.8之前:方法区——>永久代
JDK1.8及之后:方法区——>元空间
永久代和方法区是HotSpot虚拟机对虚拟机规范中方法区的两种不同实现方法
区别:永久代是在虚拟机内存中,元空间是在本地内存(不受虚拟机内存大小限制)
为什么要用元空间代替永久代?
1. Oracle为了融合HotSpot JVM和JRockit JVM(新技术)而做出的改变,因为JRockit没有永久代
2. 有了元空间基本上不会出现OOM内存溢出问题
永久代内存大小参数配置:
-XX:PermSize=N,永久代的初始内存大小
-XX:MaxPermSize=N,永久代的最大值,超出抛OOM异常
元空间内存大小参数配置:
XX:MatespaceSize=N,元空间初始值
-XX:MaxMatespaceSize=N,元空间最大值
3. 虚拟机栈
线程私有,java方法执行的内存模型,每个方法执行都会创建一个栈帧,用于存放局部变量表、操作树栈、动态链接、方法返回地址信息等。
局部变量表:一组局部变量值存储空间,用于存放方法参数和方法内部定义的局部变量
操作数栈:一个用于计算的数组,通过入栈和出栈完成一次数据访问(如:先将局部变量表第0和1个索引压入栈,再一次弹出栈并相加,再压入操作数栈中)
Person(方法区) person(虚拟机栈) = new Person()(堆)
4. 本地方法栈
线程私有,和栈内存类似,用来管理JVM调用Native方法时的内存空间。
5. 程序计数器
线程私有,记录当前线程要执行字节码的行号指示器,随着线程的创建和销毁而生灭。
二、类的加载过程
1. 类的加载机制
a. 加载器的分类
1). 启动类加载器(Boostrap ClassLoad):主要负责加载JAVA_HOME/lib目录中的部分类,比如rt.jar、所有java.*开头的类
2). 扩展类加载器(Extension ClassLoad):主要负责加载JAVA_HOME/lib/ext目录中的一些扩展类,比如javax.*开头的类
3). 应用程序类加载器(Application ClassLoad):主要负责加载用户类路径classPath指定的类
b. 双亲委派机制
如果类加载器要加载一个类,首先会把这个类加载请求委派给父类加载器去完成,如果父类还有父类,则接着向上层父类委托,一直递归到当父类加载器无法完成整个加载请求时,子类才会尝试去加载该类
双亲委派机制的优点
1). 止加载同一个.class文件,避免重复加载,确保一个类的全局唯一性
2). 保护程序安全,防止核心API被篡改
2. 类的加载过程
主要分为三个过程:加载,链接(验证、准备、解析),初始化
1). 加载:将字节码文件加载到jvm内存中,jvm通过双亲委派机制保护jvm内部安全,防止重复加载
2). 链接(分为三个阶段)
a. 验证:验证加载的字节码文件语法或语义是否符合jvm规范,或者对jvm是否有危害动作等
b. 准备:对静态变量赋默认值
c. 解析:把对象的符号引用通过静态和动态解析(多态)为直接引用
3). 初始化:为静态常量和成员变量赋初始值,执行静态代码块
准备阶段和初始化阶段的赋值区别:
3. 类成员的加载顺序
类加载时,类成员的初始化顺序:
a). 初始化父类中静态成员变量、静态代码块
b). 初始化子类中静态成员变量、静态代码块
c). 初始化父类中普通成员变量、非静态代码块、父类构造器
d). 初始化子类中普通成员变量、非静态代码块、父类构造器
静态变量和静态代码块实在类加载的时候进行的,只加载一次。静态方法只在调用的时候被加载
参考资源: