一、描述一下JVM加载class文件的原理机制?

JAVA虚拟机(JVM)中类的装载是由类加载器(ClassLoader)和它的子类来实现的,

类加载器包括:

  • 根加载器(BootStrap Loader)、

  • 扩展加载器(ExtClassLoader)、

  • 应用加载器(AppClassLoader)

  • 用户自定义类加载器(java.lang.ClassLoader的子类)

类加载过程采取了双亲委派机制(PDM),

类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。


当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接和初始化。

类的加载是指把类的.class文件中的数据读入到内存中,然后产生对应的Class对象。

当类被加载后就进入:连接阶段

这一阶段包括

  • 验证(类文件的结构检查,语义检查,字节码验证,二级制兼容性的验证)、

  • 准备(为静态变量分配内存并设置默认的初始值)

  • 和解析(将符号引用替换为直接引用)

三个步骤。


最后JVM对类进行初始化,包括:

  • 如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;

  • 如果类中存在初始化语句,就依次执行这些初始化语句。


下面是关于几个类加载器的说明:

  • Bootstrap:
        一般用本地代码实现,负责加载JVM基础核心类库(rt.jar);

  • ExtensionClassLoader
        从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;

  • AppClassLoader
        又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。


二、垃圾回收器GC机制

现在的垃圾回收器(从J2SE1.2开始)都是使用:

分代收集(Generational Collecting)算法


基于对对象生命周期分析把对象分为:

(1)年轻代:

所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。

年轻代分三个区:一个Eden区,两个Survivor区(一般而言),

采用复制算法 (Copying)

  1. 新生成的对象大部分在Eden区中生成,当Eden区满时,就会触发新生代 GC(Minor GC),

  2. 还存活的对象将被复制到Survivor区(两个中的一个),

  3. 当这个Survivor1区满时,此区的存活对象将被复制到另外一个Survivor2区,

  4. 当这个Survivor2区也满了的时候,从第一个Survivor1区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。

需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。


(2)年老代

在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。

因此,可以认为年老代中存放的都是一些生命周期较长的对象。

年老代(Tenured)被写满会触发老年代 GC(Major GC  / Full GC),

采用标记-压缩算法 (Mark-Compact),

  1. 首先需要从根节点开始对所有可达对象做一次标记,

  2. 但之后,它并不简单地清理未标记的对象,而是将所有的存活对象压缩到内存的一端。

  3. 之后,清理边界外所有的空间。

这种方法既避免了碎片的产生,又不需要两块相同的内存空间,因此,其性价比比较高。


(3)持久代:

持久代即非堆内存,主要用于存储一些类的信息、常量池、方法数据、方法代码等信息。

持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,

例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。

持久代大小通过-XX:MaxPermSize=<N>进行设置。

JDK8中已经把持久代(PermGen Space) 干掉了,取而代之的元空间(Metaspace)。Metaspace占用的是本地内存,不再占用虚拟机内存。


附文:


在JVM中类加载过程中,在解析阶段,Java虚拟机会把类的二级制数据中的符号引用替换为直接引用。


1.符号引用(Symbolic References):


  符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可。例如,在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现。符号引用与虚拟机的内存布局无关,引用的目标并不一定加载到内存中。在Java中,一个java类将会编译成一个class文件。在编译时,java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language(假设是这个,当然实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址。各种虚拟机实现的内存布局可能有所不同,但是它们能接受的符号引用都是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。


 

2.直接引用:

 直接引用可以是

(1)直接指向目标的指针(比如,指向“类型”【Class对象】、类变量、类方法的直接引用可能是指向方法区的指针)

(2)相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量)

(3)一个能间接定位到目标的句柄

直接引用是和虚拟机的布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经被加载入内存中了。


参考:

http://blog.csdn.net/ithomer/article/details/9936743

https://www.ibm.com/developerworks/cn/java/j-lo-JVMGarbageCollection/

http://blog.csdn.net/z69183787/article/details/51606544

http://www.cnblogs.com/mingziday/p/4967337.html




注意:本文归作者所有,未经作者允许,不得转载