类的加载
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
加载
就是指将class文件读入内存,并为之创建一个Class对象。
任何类被使用时系统都会建立一个Class对象。
连接
验证 是否有正确的内部结构,并和其他类协调一致
准备 负责为类的静态成员分配内存,并设置默认初始化值
解析 将类的二进制数据中的符号引用替换为直接引用
初始化 通过new来实例化一个对象
JVM的类加载是通过ClassLoader及其子类来完成的
类初始化步骤:
1. 在一个类被初始化之前,也就是在任何非字面类常量的类字段被初始化之前,字面类常量先完成初始化,如字段staticfinalStirng a ="good"。
2. 当类被触发进行初始化,若其直接父类还没有被初始化,先对直接父类进行初始化;若直接父类的直接父类没有被初始化,则先对直接父类的直接父类进行初始化,以此类推,直到Object类或某一级别的祖父类已被初始化。
3. 初始化所有非类常量的类字段,同时初始化静态代码块,按文本序。
类加载器
Bootstrap ClassLoader根类加载器
也被称为引导类加载器,负责Java核心类的加载
比如System,String等。在JDK中JRE的lib目录下 rt.jar文件中
Extension ClassLoader扩展类加载器
负责JRE的扩展目录中jar包的加载。
在JDK中JRE的lib目录下ext目录
System ClassLoader系统类加载器
负责在JVM启动时加载来自java命令的class文件,以及 classpath环境变量所指定的jar包和类路径
类加载器顺序
1、虚拟机在首次加载Java类时,会对静态初始化块、静态成员变量、静态方法进行一次初始化
2、只有在调用new方法时才会创建类的实例
3、类实例创建过程:按照父子继承关系进行初始化,首先执行父类的初始化块部分,然后是父类的构造方法;再执行本类继承的子类的初始化块,最后是子类的构造方法
4、类实例销毁时候,首先销毁子类部分,再销毁父类部分
public class Parent {
public static int t = parentStaticMethod2(); // 静态成员变量第二个初始化
public static final int x=parentStaticMethod3(); //父类类常量在类初始化前会第一个初始化
{ System.out.println("父类非静态初始化块"); } //第五个初始化
static { System.out.println("父类静态初始化块"); } //第三个初始化
public Parent() {
System.out.println("父类的构造方法"); //第六个初始化
}
public static int parentStaticMethod() {
System.out.println("父类类的静态方法");
return 10;
}
public static int parentStaticMethod2() {
System.out.println("父类的静态方法2");
return 9;
}
public static int parentStaticMethod3() {
System.out.println("父类的静态方法3");
return 9;
}
}
public class Child extends Parent{
{ System.out.println("子类非静态初始化块");} //第七个初始化
static {
System.out.println("子类静态初始化块"); //第四个初始化
}
public Child() {
System.out.println("子类的构造方法"); //第八个初始化
}
public static int childStaticMethod() {
System.out.println("子类的静态方法");
return 1000;
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("销毁子类");
}
}
public class Test{
public static void main(String[] args)
{
// TODO Auto-generated method stub
// Parent.parentStaticMethod();
Child child = new Child();
System.out.println("子类初始化完成");
}
}
控制台结果结果如下:
父类的静态方法2
父类的静态方法3
父类静态初始化块
子类静态初始化块
父类非静态初始化块
父类的构造方法
子类非静态初始化块
子类的构造方法
子类初始化完成
JVM垃圾回收GarbageCollection(GC)
JVM的垃圾回收
JVM的垃圾回收指的是回收JVM内存堆内的数据对象
堆是什么
JVM堆内存储所有new、newarray、anewarray和 multianewarray等指令建立的对象。
回收是什么
一种动态存储管理技术,它自动地释放不再被程序引用的对象, 按照特定的垃圾收集算法来实现资源自动回收的功能。
GC的优点:
在C++中,对象在被程序结束或被明确的释放前不能被回收。
提高编码效率,不用花费大量时间考虑内存分配和对象的生命周期
GC的缺点:
不能控制对象的生命结束,意味着逻辑不能卸载析构函数。
在运行GC程序时,JVM会停止运行,对程序效率产生影响,潜在的加大了程序的负担
内存管理:如图
内存--堆
所有通过new创建的对象的内存都在堆中分配,其大小可以通过-Xmx和-Xms来控制。堆被划分为新生代和旧生代,新生代又被进一步划分为Eden和Survivor区,Survivor由FromSpace和ToSpace组成,结构图如下所示:
内存--栈
每个线程执行每个方法的时候都会在栈中申请一个栈帧,每个栈帧包括局部变量区和操作数栈,用于存放此次方法调用过程中的临时变量、参数和中间结果。
本地方法栈
用于支持native方法的执行,存储了每个native方法调用的状态。
方法区
存放了要加载的类信息、静态变量、final类型的常量、属性和方法信息。JVM用持久代(或者叫做永久代,PermanetGeneration)来存放方法区,可通过-XX:PermSize和-XX:MaxPermSize来指定最小值和最大值。
对象的状态转换如图
当一个对象(假定为Sample对象)被创建后,只要程序中还有引用变量引用它,那么它就始终处于可触及状态
当Java虚拟机执行完所有可复活对象的finalize()方法后,假如这些方法都没有使Sample对象转到可触及状态,那么Sample对象就进入不可触及状态。只有当对象处于不可触及状态时,垃圾回收器才会真正回收它占用的内存。
透视垃圾回收
1、命令行参数透视垃圾收集器的运行
2、使用System.gc()可以不管JVM使用的是哪一种垃圾回收的算法,都可以请求Java的垃圾回收。在命令行中有一个参数-verbosegc可以查看Java使用的堆内存的情况,它的格式如下:
java-verbosegc classfile
(1)不要试图去假定垃圾收集发生的时间,这一切都是未知的。
(2)Java中提供了一些和垃圾收集打交道的类,而且提供了一种强行执行垃圾收集的方法--调用System.gc(),但这同样是个不确定的方法。。
(3)挑选适合自己的垃圾收集器。一般来说,如果系统没有特殊和苛刻的性能要求,可以采用JVM的缺省选项。
(4)关键的也是难把握的问题是内存泄漏。良好的编程习惯和严谨的编程态度永远是最重要的,
(5)尽早释放无用对象的引用。大多数程序员在使用临时变量的时候,都是让引用变量在退出活动域(scope)后,自动设置为null,暗示垃圾收集器来收集该对象,还必须注意该引用的对象是否被监听,如果有,则要去掉监听器,然后再赋空值。