今日复习: JVM的类加载机制。
-
类加载器的种类和区别
-
类加载器的双亲委派机制
-
类加载的执行过程
-
JVM的主要组成和功能
-
JVM的运行时数据区
-
对象的创建
-
如何为对象分配内存
-
对象的访问定位
-
JVM的垃圾回收机制
-
常见的垃圾回收算法
- Jvm的类加载器一共有四种分别是BootstrapClassLoader(启动类加载器),ExtentionClassLoader(扩展类加载器),SystemClassLoader(系统类加载器)和自定义类加载器。启动类加载器主要是用来加载Java核心类库,无法被java应用程序直接引用。扩展类加载器主要是在java虚拟机提供的一个扩展类目录中查找并加载java类。系统类加载器主要是根据java应用的类路径来加载java类,一般来说java应用的类都是由它来加载完成的,可以通过ClassLoader.getSystemClassLoader()方法来获取它。用户自定义类加载器,通过继承java.lang.ClassLoader来实现。
- 类加载器的双亲委派模型:如果一个类加载器收到类加载的请求,首先它不会去加载这个类,而是将这个请求委托给它的父加载器进行加载,每一层的类加载器都是如此。这样所有的加载请求都会传递到顶层的启动类加载器上,只有当父加载器无法加载这个类时,子加载器才会去完成类的加载。
- 将class文件从硬盘中读取到内存中并对class文件的数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的java的类型。值得一提得是类加载的过程是动态的,它并不会一次性将所有类都加载后再运行,而是会先保证程序运行的基础类完全加载到jvm中,至于其他类,在需要时才被加载。
- JVM主要是由运行时数据区,本地方法接口,类加载子系统和执行引擎组成的。
- 运行时数据区是JVM的主要内存区域。它由程序计数器,虚拟机栈,本地方法栈,堆和方法区五个部分组成,程序计数器主要是用来选取当前线程需要执行的下一条指令。虚拟机栈主要用来存储局部变量表,操作数栈,动态链接和方法出口等信息。本地方法栈的作用和虚拟机栈类似,只不过虚拟机栈是服务Java方法的而本地方法栈是服务本地方法的。堆,java虚拟机中内存最大的一块区域,被所有线程所共享,主要是存储对象的实例。方法区,用来存储已被虚拟机加载的类信息,常量,静态变量,即时编译后的代码等数据。
- 一个对象是如何被创建的。创建对象的五种方式:使用new关键字,使用Class的newInstance()方法,使用Constructor的newInstance()方法,使用对象实例的clone()方法,使用反序列方法。当虚拟机遇到一条new指令时,会先去常量池检查是否加载了这个类,如果没有,必须执行相应的类加载,然后使用指针碰撞或空闲列表的方式类分配内存。在划分内存的时候需要考虑并发的问题,利用CAS或者TLAB法来进行并发处理。然后进行内存空间的初始化工作,接着是做一些必要的对象设置,执行方法。
- 利用两种方式为对象分配内存。1.指针碰撞,如果当前内存区域是连续规整的,即所有用的内存放在一边,没用的放在另一边。这样在分配内存的时候就可以将处于中间的内存指针指示器向空闲的内存移动一段与对象大小相等的距离,完成内存的分配。2.空闲列表:如果java堆的内存是不规整的,就需要让虚拟机额外维护一个列表来记录哪些内存是空闲可用的,这样在为对象分配内存的时候,就可以从列表中查询到足够大的内存分配给对象并更新空闲列表。
- 对象的访问定位取决于jvm的实现,主要有两种方式:直接指针和句柄访问。直接指针指的是在虚拟机栈的局部变量表中存放该变量在堆中的地址的指针。句柄访问有些类似于二级指针的概念,即在堆中额外维护一个句柄池,局部变量表存放的指针指向句柄池中指针的地址,句柄池中指针指向堆中实例池中实例的地址。这样的话万一实例发生改变,不需要改变局部变量表的指针数据,只需要改变句柄池中指针的数据。
- JVM的垃圾回收机制:在java中程序员不需要显式地去释放一个对象的内存,而是由虚拟机自行释放。在JVM中有一个低优先级的垃圾回收线程,在虚拟机空闲或者内存不够的时候,会触发执行,扫描哪些没有被任何引用的对象,并将他们添加到要回收的集合中,进行回收。
- 常见的垃圾回收算法有复制算法,标记清除算法和标记整理算法。复制算法,主要是将内存分为两个部分,一个是使用的一个是没有被使用的,当进行回收时,将存活的对象都转移到没有被使用的那部分内存中,并对剩下的内存区域进行回收,循环复始。特点是几乎不会有内存碎片的产生,运行高效,实现简单。缺点是,内存利用率不高,如果对象存活率高会导致频繁的复制。标记清除算法,标记那些需要被清除的然后进行清除,特点是,内存碎片多,标记清除需要两步,效率较低。标记整理算法是指对可回收的对象进行标记后,将所有存活的对象都放到内存的一边去,然后对边界以外的内存区域进行整体回收,特点是解决了标记清除算法内存碎片化高的问题,但是仍然需要对对象进行移动,一定程度上降低了效率。