JVM中的对象

摘要:Java类加载、链接、初始化执行,以及对象的压缩指针。

类加载器

Class Loaders是JRE(Java Runtime Environment)的一部分,当JVM需要一个类的时候,Class Loader就会通过类的全名尝试定位到类文件(.class文件,字节码)的位置,并通过类文件定义成一个Java类(java.lang.Class的一个实例);另外的,ClassLoader还负责加载应用需要的资源,如配置文件,图片等等。

All classloaders, with the exception of the bootstrap classloader, are implemented as Java classes. Something must load the very first Java classloader to get the process started. Loading the first pure Java classloader is the job of the bootstrap classloader.
The bootstrap classloader also takes care of loading all of the code needed to support the basic Java Runtime Environment (JRE), including classes in the java.util and the java.lang packages.

Bootstrap classloader is implemented in Native Language Like C and C++. Bootstrap ClassLoader是JVM核心的一部分,由原生代码编写,不同的平台可能会有不同的实现。

每个ClassLoader可以通过getParent()获取其父ClassLoader,如果获取到ClassLoader为null的话,那么该类是通过Bootstrap ClassLoader加载的。

public class PrintClassLoaderTree {

    public static void main(String[] args) {

        ClassLoader classLoader = PrintClassLoaderTree.class.getClassLoader();

        StringBuilder split = new StringBuilder("|--");
        boolean needContinue = true;
        while (needContinue){
            System.out.println(split.toString() + classLoader);
            if(classLoader == null){
                needContinue = false;
            }else{
                classLoader = classLoader.getParent();
                split.insert(0, "\t");
            }
        }
    }

}

Extension Class Loader(扩展类加载器)

Bootstrap Class Loader的子类,负载加载标准核心Java类的扩展,加载JRE中lib/ext目录中的jar,以及JVM系统属性system property中java.ext.dirs配置目录中的类。

System Class Loader(系统类加载器)

Extension ClassLoader的子类,用于加载应用级别的类到JVM,即加载classpath目录下的Java类,通过ClassLoader.getSystemClassLoader()可以获得。

JVM中,一个类由类加载器实例和该类的全名唯一确定。

链接link过程

对于一个类的方法,编译器会生成包含目标方法所在类的名字、目标方法的名字、接收参数类型以及返回值类型的符号引用,用来指代所要调用的方法。链接过程就是要把加载的各个类之间互相引用的方法、字段等无歧义地定位到具体目标地址上。

验证

Java类的链接指的是将Java类的二进制代码合并到JVM的运行状态之中的过程。链接包含验证 -> 准备 -> 解析三个阶段。

class文件的验证包括结构验证、语法验证、字节码验证、符号引用的验证四步。

结构验证是指:魔数、主次版本号、class文件长度和类型等;

语法验证:类的继承关系等各种语义约束,比如final类不能拥有子类,final方法不能被重写,子类和超类之间没有不兼容的方法声明,常量池里引用的常量是否有效,访问范围合法性等;

字节码验证:主要检验字节码是否可以被java虚拟机安全地执行。

为对符号引用的验证在后面解析过程中进行。在动态连接过程中,通过保存在常量池的符号引用查找被引用的类、接口、字段、方法时,在把符号引用替换成直接引用时,首先需要确认查找的元素真正存在,然后需要检查访问权限、查找的元素是否是静态类成员而非实例成员。

准备

准备阶段主要是为类变量分配内存、设置默认初始值等。

解析

在类的常量池中寻找类、接口、字段、方法的符号引用,将这些符号引用替换成直接引用。
为类、接口、方法、成员变量的符号引用定位直接引用(如果符号引用先到常量池中寻找符号,再找先应的类型,无疑会耗费更多时间),完成内存结构的布局。

初始化

对类变量赋予指定的初始值(这个时候int i = 5就必须赋予i以初值5)。这个初始值的给定方式有两种,一种是通过类变量的初始化语句,一种是静态初始化语句。而这些初始化语句都将被Java编译器一起放在方法中。

在初始化阶段,只会为类变量(静态全局变量)进行初始化工作,并且当类变量声明为final类型切初始化语句采用了常量表达式方式进行初始化赋值,那么,也不会对其进行初始化,它将会直接被编译器计算并保存在常量池中,并且对这些变量的使用也将直接将其变量值嵌入到字节码中。

类的初始化

类的初始化会从祖先类到子类、按出现顺序,对类变量的初始化语句、静态初始化语句块依次进行初始化。而对类实例的初始化也类似,会从祖先类到子类、按出现顺序,对类成员的初始化语句、实例初始化块、构造方法依次进行初始化。

类的初始化过程是由 JVM 保证线程安全的。

静态绑定和动态绑定

静态绑定:

在程序执行前方法已经被绑定(也就是说在编译过程中就已经知道这个方法到底是哪个类中的方法),此时由编译器或其它连接程序实现。
除了被static 修饰的静态方法,所有被 private 修饰的私有方法、被 final 修饰的禁止子类覆盖的方法都会被编译成invokestatic指令。另外所有类的初始化方法和会被编译成invokespecial指令。JVM会采用静态绑定机制来顺利的调用这些方法。

动态绑定:

除了由private、final、static 所修饰的方法和构造方法外,JVM在运行期间决定方法由哪个对象调用的过程称为动态绑定。

在JVM加载类的同时,会在方法区中为这个类存放很多信息,其中就有一个数据结构叫方法表。它以数组的形式记录了当前类及其所有超类的可见方法字节码在内存中的直接地址。

根据对象(father)的声明类型(Father)还不能够确定调用方法f1的位置,必须根据father在堆中实际创建的对象类型Son来确定f1方法所在的位置。这种在程序运行过程中,通过动态创建的对象的方法表来定位方法的方式,我们叫做 动态绑定机制 。

JVM中识别一个方法时,除了方法名和参数类型,还会考虑返回类型。由于编译器已经区分了重载的方法,因此可以认为JVM中不存在重载。


java应用程序的启动在在/hotspot/src/share/tools/launcher/java.c的main()函数中,而在虚拟机初始化过程中,将创建并启动Java的Main线程。最后将调用JNIEnv的CallStaticVoidMethod()来执行main方法。

压缩指针

Java中整形数的值是32位,但是每个对象都含有一些“头部”内容。对象头分为两部分:Mark Word 与 Class Pointer(类型指针)。Mark Word存储了对象的hashCode、GC信息、锁信息三部分,Class Pointer存储了指向类对象信息的指针。在32位JVM上对象头占用的大小是8字节,64位JVM则是16字节,两种类型的Mark Word 和 Class Pointer各占一半空间大小。

在64位JVM上有一个压缩指针选项-XX:+UseCompressedOops,默认是开启的。开启之后Class Pointer部分就会压缩为4字节,此时对象头大小就会缩小到12字节。

为了尽量减少对象的内存使用量,64位JVM引入了压缩指针的概念,默认开启"-XX:+UseCompressedOops"。

CompressedOops,可以让跑在64位平台下的JVM,不需要因为更宽的寻址,而付出Heap容量损失的代价。 不过它的实现方式是在机器码中植入压缩与解压指令,可能会给JVM增加额外的开销。

64位地址分为堆的基地址+偏移量,当堆内存<32GB时候,在压缩过程中,把(偏移量/8)后保存到32位地址。解压时再把32位地址放大8倍,所以启用CompressedOops的条件是堆内存要在4GB*8=32GB以内。

  • 如果GC堆大小在4G以下,直接砍掉高32位,避免了编码解码过程;
  • 如果GC堆大小在4G以上32G以下,则启用UseCompressedOop;
  • 如果GC堆大小大于32G,压指失效,使用原来的64位

32位HotSpot VM是不支持UseCompressedOops参数的,只有64位HotSpot VM才支持。

你可能感兴趣的:(编程基础)