类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,而后在堆区建立一个 java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的 Class对象, Class对象封装了类在方法区内的数据结构,而且向Java程序员提供了访问方法区内的数据结构的接口。java
类加载器并不须要等到某个类被“首次主动使用”时再加载它,JVM规范容许类加载器在预料某个类将要被使用时就预先加载它,若是在预先加载的过程当中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)若是这个类一直没有被程序主动使用,那么类加载器就不会报告错误。程序员
类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是肯定的,而解析阶段则不必定,它在某些状况下能够在初始化阶段以后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定)。算法
另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,由于这些阶段一般都是互相交叉地混合进行的,一般在一个阶段执行的过程当中调用或激活另外一个阶段。数据库
站在java虚拟机的角度来说,只存在两种不一样的类加载器:启动类加载器和其余的加载器。(由于启动类加载器是使用C++语言实现的,它是虚拟机自身的一部分,而其余的加载器则是由java语言实现的,并且都是继承自抽象类java.lang.ClassLoad,而这些加载器必须由启动类加载器加载到内存后,才能去加载其余的类。)缓存
可是站在java开发人员来看,类加载器大概分为四种,启动类加载器,扩展类加载器,应用程序类加载器,自定义加载器。安全
启动类加载器(BootStrap ClassLoad):网络
负责加载存在在jdk/jre/lib目录下的能被虚拟机识别的类库,并且启动类加载器是没法被java程序直接应用的。数据结构
扩展类加载器(Extension ClassLoad):多线程
该加载器是用来加载jdk/jre/lib/ext目录下的全部类库,开发者能够直接使用扩展类加载器。jvm
应用程序类加载器(Application ClassLoad): 该类加载器是负责加载用户类路径所指定的类,开发者能够直接使用该类加载器,若是应用程序中没有自定义过本身的加载器,那么这个加载器就是程序中默认的加载器。
自定义类加载器(User ClassLoad):
自定义加载器是用户本身定义的加载器,这种加载器主要注意三点:
1.在执行非置信代码以前,自动验证数字签名。
2.动态地建立符合用户特定须要的定制化构建类。
3.从特定的场所取得java class,例如数据库中和网络中。
复制代码
全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其余Class也将由该类加载器负责载入,除非显示使用另一个类加载器来载入。
父类委托,先让父类加载器试图加载该类,只有在父类加载器没法加载该类时才尝试从本身的类路径中加载该类。
缓存机制,缓存机制将会保证全部加载过的Class都会被缓存,当程序中须要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为何修改了Class后,必须重启JVM,程序的修改才会生效。
双亲委派模型的工做流程是:若是一个类加载器收到了类加载的请求,它首先不会本身去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,所以,全部的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即没法完成该加载,子加载器才会尝试本身去加载该类。
双亲委派模型的意义:
1.系统类防止出现内存中出现多份一样的字节码。
2.保证java程序安全稳定运行。
复制代码
Jvm的内存结构主要是由java堆,java栈,本地方法栈,方法区,程序计数器组成。
Java堆内存,是由全部线程共享的一块内存区域,这个内存区域的惟一目的就是存放对象实例,几乎全部的对象实例都在这里分配内存。
从内存回收的角度来说,由于如今的收集器基本都是采用的分代收集算法,因此java堆中还能够细分为:新生代和老年代。而新生代,则又进行划分,分为了Eden区,From Survivor区域,To Survivor区域。
方法区和java堆同样,都是由全部线程共享的内存区域。它主要的做用,是存储已经被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。虽然在java虚拟机规范里面,会把方法区看成java堆的一个逻辑部分,可是它却也有一个别名,叫作Non-Heap(非堆),目的是要与java堆分开来。
可是,在HotSpot虚拟机上,会把方法区称为“永久代”,本质上固然二者并不等价,而出现这种状况的缘由是,HotSpot虚拟机的团队将GC分代收集扩展到了方法区,与java堆联合在一块儿,造成了一个GC回收的网络。
当java文件被打包成class文件的时候,里面的代码都是被优化过的,可是java程序是多线程的,当一个线程在执行一个程序到一半的时候,突然被调去执行另一个程序了,那么如何保证等它执行另一个程序,返回过来执行以前的程序的时候,可以准确的找到以前执行到程序的哪一个点呢?这个时候就须要程序计数器了。
每个线程都会有一个单独的程序计数器,它会记录这个线程正在执行的虚拟机字节码指令的地址,可是只能记录java方法的,若是是Native方法,那么这个计数器的值就是为空(Undefined)。 这个区域,也是惟一一个在java虚拟机规范中没有规定任何内存溢出状况的区域。
和程序计数器同样,java栈也是线程私有的内存区域。
一个线程,当它开始的时候,就会内存中出现一个独属于它的java栈,当这个线程在执行一个方法的时候,java栈中会开辟一个栈帧,这个栈帧将会存储这个方法的局部变量表,操做栈,动态连接,方法出口等信息。因此线程会调用一个又一个的方法,那么也就会在独属于它的java栈中开辟出一个又一个的栈帧,而每个方法被调用直到这个方法被执行完成,就对应着一个栈帧在虚拟机栈中从入栈到出栈的一个过程。
可是java栈,只为虚拟机执行java方法服务。
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的做用是很是类似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并无强制规定,所以具体的虚拟机能够自由实现它。
甚至有的虚拟机(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈同样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。
在jvm中,程序计数器,java栈,本地方法栈都是随着线程而生也随着线程而灭,那么天然就实现了内存的清理。可是java堆和方法区,却不是这样,所以咱们如今的GC垃圾回收器主要就是集中在java堆和方法区。
判断对象是否存活通常有两种方式:
引用计数:每一个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时能够回收。此方法简单,没法解决对象相互循环引用的问题。
可达性分析(Reachability Analysis):从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证实此对象是不可用的。不可达对象。
分代收集算法的前提,是肯定绝大部分对象的生命周期都是很是短暂的,存活时间短。
分代收集算法的特色,是把java堆设置新生代和老年代,这样就能够根据不一样年代的特色来选择最合适的算法。(如今讨论的基本都是HotSpot虚拟机)
这个是java堆中,java堆主要是分为新生代和老年代,通常默认的比例是新生代占据整个java堆的1/3,新生代本身也会再度细分,目的是为了选择出更为合适的GC收集算法,提高回收的效率。 新生代分为了Eden区,From Survivor区域,To Survivor区域。
当系统建立一个对象的时候,老是会在Eden区操做,可是大部分对象,都是在建立后不久就永远都不会再使用了,所以也会很快变得不可达,当这个区域快要满了到时候,就会触发一次YongGC,将这些不可达的对象清理掉。而剩下的还存活着的对象呢,YongGC就会经过“复制算法”,转移到From Survivor区域。
当From Survivor区域的对象消亡后,会触发YongGC来清理掉这些对象,而剩下的对象,YongGC则会所有经过“复制算法”复制到To Survivor区域。而这个时候,To Survivor区域就变成了了From Survivor区域,而以前的From Survivor区域则就会被YongGC所有清空内存,变成了To Survivor。 当一个对象,在年轻代存活了足够长的时间后,若是没有被GC清理掉,那么就会经过“复制算法”复制到老年代去。还有一种状况就是,若是一次GC下来,存活的对象占据的内存超过了新生代的内存的10%,那么也会将这部份内存复制到老年代中去。
老年代存储的对象比年轻代多得多,并且不乏大对象,对老年代进行内存清理时,若是使用中止-复制算法,则至关低效。通常,老年代用的算法是标记-整理(也称之为标记-压缩算法)算法,即标记出仍然存活的对象(存在引用的),将全部存活的对象向一端移动,以保证内存的连续。
在发生Minor GC时,虚拟机会检查每次晋升进入老年代的大小是否大于老年代的剩余空间大小,若是大于,则直接触发一次Full GC。