一、JDK版本
1. 字符串常量池
在 JDK 1.7 之前,运行时常量池(包括字符串常量池)存放在方法区。
在 JDK 1.7 时,字符串常量池被从方法区转移至 Java 堆中,注意并不是运行时常量池,而是字符串常量池被单独转移到堆,运行时常量池剩下的东西还是方法区中。
在 JDK 1.8 时,此时字符串常量池还在堆中。
二、JVM内存管理机制
Java语言本身不能操作内存,它的一切是交给JVM进行管理和控制的。
当编译器将java源文件编译成.class文件后JVM会开辟出一个空间。会将类加载器加载的信息存放到运行时数据区
该区域分5个部分组成:栈、堆、方法区、程序计数器、本地方法栈。
1、栈
存放着java方法执行的内存模型,是线程私有的,每一个方法都对应一个栈帧(先进后出)。栈帧中存储局部表,操作数栈,动态链接、出口等。
局部变量表:存放八大基本数据类型和对象引用地址
操作数栈:用来操作的,加入有个代码中有 i = 6* 7;他在一开始就会进行操作,然后将操作后的结果存放到局部变量表中。
动态链接:如果该方法中调用了其他方法,就要链接到别的方法中区,这就是动态链接存储链接的地方。
出口:存储方法执行完成后返回的地址
2、堆:
内存中最大的一块区域。是线程共享的,是用来存放对象实例(对象本身和数组)的。、
从jdk1.7开始,字符串常量池在堆中。
3、方法区:
线程共享的,但是是线程安全的,多个线程访问时只能由一个线程使用该数据,其他线程需要等待
存储的内容:类的全路径名,类的访问修饰符,类的类型(类和接口)、虚拟机加载的类信息,静态变量。
public class PersonTest {
public static void main(String[] args) {
int a = 18;
int b = 10;
long c = add(a,b);
System.out.println("c = "+c);
Person2 p = new Person2("小明",18,'男');
p.showInfor();
}
public static long add(int a, int b) {
int x = a;
int y = b;
int z = x + y;
return z;
}
}
下图为上述代码的jvm
4.程序计数器
可以看作是线程所执行的字节码的行号指示器,字节码解释器工作时候根据这个计数器的值来执行下一条命令,通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理
在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程切换回来的时候能够知道该线程上次运行到哪了。
他的生命周期随着线程的创建而创建,线程的结束而死亡
5.本地方法栈
本地方法栈则为虚拟机使用到的 Native 方法服务
本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。
方法执行完毕后相应的栈帧也会出栈并释放内存空间,
三、GC
当main方法结束后,栈帧消失,第二条线断掉,此时没有任何引用指向3那个对象,这样的对象会被认为是垃圾垃圾,等待GC来回收。
如果这样的对象多了就会造成内存溢出。
编程时,如果对象用了几次后,不在使用,应该把对象指向null等待GC处理。
四、类加载过程
加载、连接(验证、准备、解析)、初始化
1.类加载
将.class文件的二进制字节流读入内存(1.7之前为JVM内存,1.8之后为本地内存)
本地内存和JVM内存的区别如下:JVM内存是虚拟机在执行时分配的内存区域,受虚拟机内存大小参数控制,超过参数设置的大小会报OOM;本地内存是不受虚拟机内存参数限制的,只受物理内存容量限制,不会发生GC,但如果占用超过物理内存,仍会报OOM12。JVM内存只是进程空间的一部分,除此之外还有代码段、数据段、内存映射区、内核空间等,这些被JVM之外的部分称为本地内存
并在堆内存中为之创建Class对象,作为 .class进入内存后数据的访问入口。在这里只是读入二进制字节流,后续的验证就是要拿二进制字节流来验证.class文件,验证通过,才会将.class文件转为运行时数据结构
2.连接
2.1 验证
保证加载进来的字节流符合JVM的规范,不会堆JVM有安全性问题,其中有元数据的验证,例如检查是否继承了final类,还有符号引用的验证,例如校验符号引用是否可以通过全限定名找到,或者是检查符号引用的权限(private,public)是否符合语法规定等。
(符号引用就是字符串,比如方法的全限定名 com.example.MyClass.myMethod)
2.2 准备
准备阶段的目的就是为类的类变量开辟空间并赋默认值。
1、静态变量是基本类型(int、long、short、char、byte、boolean、float、double)的默认值为0
2、静态变量是引用类型的,默认值为null
3、静态常量默认值为声明时设定的值
例如:public static final int i = 3; 在准备阶段,i的值即为3
2.3 解析
该阶段的主要职责为将Class在常量池中的符号引用转变为直接引用,此处针对的是静态方法及属性和私有方法与属性,因为这类方法与私有方法不能被重写,静态属性在运行期也没有多态这一说,即在编译器可知,运行期不可变,所以适合在该阶段解析,譬如类方法main替换为直接引用,为静态连接,区别于运行时的动态连接(后续我会写关于JVM内存结构的文章,在讲解栈帧时会介绍动态链接)。
符号引用即字符串,说白了可以是一个字段名,或者一个方法名;直接引用即偏移量,说白了就是类的元信息位于内存的地址串,例如,一个类的方法为test(),则符号引用即为test,这个方法存在于内存中的地址假设为0x123456,则这个地址则为直接引用。
3.初始化
该阶段主要是为类的类变量初始化值的,初始化有两种方式:
1、在声明类变量时,直接给变量赋值
2、在静态初始化块为类变量赋值
目录