1. jvm运行时内存结构划分
在正式介绍之前,先看看jvm内存结构划分:
结合垃圾回收机制,将堆细化:
在加载阶段主要用到的是方法区:
方法区是可供各条线程共享的运行时内存区域。存储了每一个类的结构信息,例如运行时常量池(Runtime Constant Pool)、字段和方法数据、构造函数和普通方法的字节码内容、还包括一些在类、实例、接口初始化时用到的特殊方法。
如果把方法的代码看作它的“静态”部分,而把一次方法调用需要记录的临时数据看做它的“动态”部分,那么每个方法的代码是只有一份的,存储于JVM的方法区中;每次某方法被调用,则在该调用所在的线程的的Java栈上新分配一个栈帧,用于存放临时数据,在方法返回时栈帧自动撤销。
2. 类加载过程
jvm将类加载过程分成加载,连接,初始化三个阶段,其中连接阶段又细分为验证,准备,解析三个阶段。
上述三个阶段总体上会保持这个顺序,但是有些特殊情况,如加载阶段与连接阶段的部分内容(一部分字节码的验证工作)是交叉进行的。再如:解析阶段可以是推迟初次访问某个类的时候,因此它可能出现在初始化阶段之后。
(1)加载阶段:主要负责查找并且加载类的二进制文件,其实就是。class文件。
(2)连接阶段: 连接 阶段工作比较多。细说分为下面三个阶段;
1:验证:主要确保类文件的正确性,比如class版本,class文件的魔术银子是否正确
2:准备:为类的静态变量分配内存,并且为其初始化默认值;
3解析:把类中的符号引用转换为直接引用。
(3)初始化阶段:为类中的符号引用转换为直接引用
当一个jvm在我们执行Java命令启动之后,其中可能包含的类非常多。但不是每个类都会被初始化,jvm对类的初始化是一个延迟机制,,即使用的是lazy的方式,当一个类在首次使用的时候才会被初始化,在同一个运行包下。一个class只会被初始化一次,
一:类的主动使用和被动使用
jvm 虚拟机规范规定了,每个类或者接口被Java程序首次主动使用时才会对其进行初始化,当然随着jvm运行期间编译越来越智能不排除jvm在运行期间提前预判并初始化某个类;
jvm规范了一下6中主动使用类的场景,如下:】
(1)通过new 关键字会导致类的初始化,
(2)访问类的静态变量,包括读取和更新会导致类的初始化,实例如下:
public class Simple {
static {
System.out.println("i will be initialized");
}
public static int x = 10;
}
x是个简单的静态变量,其他类即使不对simple进行new创建,直接访问x也会导致类的初始化
(3)访问类的静态方法,会导致类的初始化,如下:
public class Simple {
static {
System.out.println("i will be initialized");
}
public static void test(){};
}
(4)对某个类进行反射操作。会导致类的初始化。如下:
public static void main(String[] args) throws ClassNotFoundException {
Class.forName("baseData.groupLog.service.impl.Simple");
}
}
运行输出:
i will be initialized
(5)初始化子类会导致父类的初始化,如下
public class Parent {
static {
System.out.println("the parent is initialized");
}
public static int y =100;
}
public class Child extends Parent{
static {
System.out.println("the child will be initialized");
}
public static int x=10;
}
public class ActiveLoadTest{
public static void main(String[] args) {
System.out.println(Child.x);
}
}
结果如下:
the parent is initialized
the child will be initialized
10
但是通过子类使用父类的今天变量只会导致父类的初始化子类则不会初始化:
(6)启动类: 也就是执行main函数所在的类会导致该类的初始化,
除了上面的几种情况,其余都称为被动使用不会导致类的记载和初始化。
下面有几个容易混淆的例子:
(1)构造某个类的数组时不会导致该类的初始化,如:、
public class ActiveLoadTest{
public static void main(String[] args) {
Simple[]simples=new Simple[10];
System.out.println(simples.length );
}
}
上面新建了一个simple类型的数组,但是不会导致simple的初始化,该操作不过是在堆内存中开辟了一连串的地址空间,、
(2)引用类的静态常量不会导致类的初始化,如:
public class Simple {
static {
System.out.println("i will be initialized");
}
public final static int x=10;
}
final类不能被继承,没有子类,final类中的方法默认是final的。
final方法不能被子类的方法覆盖,但可以被继承。
final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
final不能用于修饰构造方法。
注意:父类的private成员方法是不能被子类方法覆盖的,因此private类型的方法默认是final类型的。
1、final数据:
一个永不改变的编译时常量。
一个在运行时被初始化的值,而之后无法被改变;
一个既是static又是final的域:是一段不能改变的存储空间;
final类型运用于数据:
基本数据类型(int、double、char...)运用final时,使数值恒定不变;
对象引用运用final时,final使得引用恒定不变,引用内部的数据若不是final型,可以进行修改。
数组类型运用final时,final使得数组引用恒定不变,数组内部的数据若不是final型,可以进行修改。
final与static
final指明数据为一个常量,恒定无法修改;
static指明数据只占用一份存储区域;