引导类加载器(bootstrap class loader)java
用来加载java的核心库(String,Integer,List…)在jre/lib/rt.jar路径下的内容。使用c代码来实现的,并不继承自java.lang.ClassLoader.程序员
加载扩展类加载器和应用程序加载器,并指定他们的父类加载器。算法
扩展类加载器(extensions class loader)bootstrap
用来加载java的扩展库(jre/ext/*.jar路径下的内容),java虚拟机的实现会自动提供一个扩展目录。该类加载器在此目录里面查找并加载java类。数组
应用程序类加载器(application class loader)安全
它根据java应用的类路径(classpath路径),通常来讲java应用的类都是由它来完成加载的。服务器
自定义类加载器数据结构
开发人员能够经过继承java.lang.ClassLoader类的方式实如今即的类加载器,以知足一些特殊的要求。扩展类加载器、应用程序类加载器和自定义类加载器都是由java实现,都继承java.lang.ClassLoader类。多线程
当某个类加载器在接收到加载类的请求后,首先将加载任务委托给父类加载器,依次追溯,若是父类加载器可以完成类加载任务,就成功返回,只有父类加载器没法完成加载任务时,才本身加载。app
双亲机制是为了保证java核心库的类型安全,不会出现用户能自定义java.lang.Object类的状况。
双亲委托机制是代理模式的一种,并非全部类加载器都采用双亲委托机制,Tomcat服务器类加载器也使用代理模式,不一样的是它是首先尝试本身去加载某个类,若是找不到再代理给父类加载器。
jvm把class文件加载到内存,并对数据进行校验、解析和初始化,最终造成jvm能够直接使用的java类型的过程。
类加载过程:类从被加载到虚拟机内存中开始,直到卸载出内存为止,它的整个生命周期包括7个阶段:加载、验证、准备、解析、初始化、使用、卸载(其中验证、准备和解析这三个部分统称为链接)。其中加载、验证、准备、初始化和卸载这五个阶段的顺序是必定的,而解析阶段不必定,在某种状况下,能够在初始化以后再开始,这是为了支持java语言的运行时绑定。
加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个表明这个类的java.lang.Class对象,做为方法区类数据的访问入口。
链接:将java类的二进制代码合并到jvm的运行状态之中的过程。验证:确保加载的类信息符合jvm规范,没有安全方面的问题。准备:正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行。解析:虚拟机常量池内的符号引用替换为直接引用的过程。(好比Strings = “aaa”,转化为s的地址指向"aaa"的地址)。
初始化:初始化阶段是执行类构造器方法的过程,类构造器方法是由编译器自动收集类中的全部变量的赋值动做和静态语句块(static块)中的语句合并产生的。当初始化一个类的时候,若是发现其父类尚未进行初始化,则须要先进行其父类的初始化,虚拟机会保证一个类的构造器方法在多线程环境中被正确加锁和同步。当访问一个java类的静态域时,只有真正申明这个静态变量的类才会被初始化。
类的主动引用(必定会发生类的初始化):
new一个类的对象
调用类的静态成员(除了final常量)和静态方法
使用java.lang.reflect包的方法对类进行反射调用
当初始化一个类,若是父类没有被初始化,先初始化其父类
当要执行某个程序时,必定先启动main方法所在的类
类的被动引用(不会发生类的初始化)
当访问一个静态变量时,只有真正声明这个静态变量的类才会初始化(经过子类引用父类的静态变量,不会形成子类的初始化)
经过数组定义类应用,不会触发此类的初始化A[] a = new A[10];
引用常量(final类型)不会触发此类的初始化(常量在编译阶段就存入调用类的常量池中了)
虚拟机在首次加载java类时,会对静态初始化块、静态成员变量、静态方法进行一次初始化
只有在调用new方法时,才会建立类的实例
类实例建立过程:首先执行父类的初始化块部分,而后是父类的构造方法,再执行子类的初始化块,最后是子类的构造方法
类实例销毁时,先销毁子类部分,再销毁父类部分。
首先java源代码文件(.java)会被java编译为字节码文件(.class),而后由jvm中的类加载器加载各个类的字节码文件,加载完毕以后,交由jvm执行引擎执行。
jvm区域能够根据线程分红线程隔离和线程共享两个部分,其中线程隔离即这些区域是线程独有的,每一个线程都会分配这样的区域,包括程序计数器、Java栈和本地方法栈;线程共享的有方法区和堆。
程序计数器(Program Counter Register)
因为在JVM中,多线程是经过线程轮流切换来得到CPU执行事件的,所以在任意具体时刻,一个CPU只会执行一个线程中的指令,为了可以使得每一个线程都在线程切换或可以恢复到切换以前的程序执行位置,每一个线程都须要有本身独立的程序计数器,而且不能互相被干扰,不然就会影响到程序的正常执行次序。因此程序计数器是每一个线程所私有的。在jvm规范中规定,若是线程执行的是非native方法,则程序计数器中保存的是当前须要执行的指令的地址;若是线程执行的是native方法,则程序计数器中保存的值是undefined。
java栈(vm stack)
java栈也称为虚拟机栈(java vitual machine stack),java栈中存放的是一个个栈帧,每一个栈帧对应一个被调用的方法,在栈帧中包括局部变量表(local variables)、操做数栈(perand stack)、指向当前方法所属的类的运行时常量池的引用、方法返回地址和一些额外的附加信息。
本地方法栈(native method stack)
本地方法栈与java栈的做用和原理很是类似,只不过java栈是为执行java方法服务,而本地方法栈是为执行本地方法服务的。在jvm规范中,并无对本地方法的具体实现方法以及数据结构作强制规定,虚拟机能够自由实现它。在Hotsopt虚拟机中直接就把本地方法栈和java栈合二为一。
方法区(Method Area)
方法区在JVM中是一个很是重要的区域,与堆同样是被线程共享的区域。在方法区中,存储了每一个类的信息(包括类的名称、方法、字段信息)、静态变量、常量以及编译器编译后的代码。在方法区有一个很是重要的部分就是运行时常量池它是每个类或者接口的常量池的运行时表示形式,在类和接口被加载到jvm后,对应的运行时常量池就被建立出来。固然并不是Class文件常量池中的内容才能进入运行时常量池,在运行期间,也可将新的常量放入运行时常量池中,好比String的intern方法。能够认为方法区就是永久代。
堆(Heap)
java中的堆是用来存储对象以及数组,数组的引用是存放在java栈中的。堆被全部线程共享,在jvm中只有一个堆。
在java中,堆被划分红两个不一样的区域:新生代(Young)、老年代(Old)。
新生代又被划分为三个区域:Eden和两个幸存区。
这样划分的目的是为了使JVM可以更好地管理堆内存中的对象,包括内存的分配及回收。
新生代主要存储新建立的对象和还没有进入老年代的对象。老年代存储通过屡次新生代GC(Minor GC)后仍然存活的对象。
方法区主要存放类与类之间关系的数据,这部分数据被加载到内存之后,基本上不会发生变动,可是后期方法区也会被回收,回收的条件很是的苛刻;java堆中的数据基本上是朝生夕死的,用完以后就会被回收;java栈和本地方法栈中的数据,知足先进后出的原则,当要获取栈低的元素,必须把栈顶的元素出栈,回收率为100%;程序计数器是惟一一块不会内存溢出的区域。
java中若是一个对象,没有一个引用指向它,那么它就被认为是一个垃圾。
java内存管理分为内存分配和内存回收,不须要程序员参与。
垃圾回收机制主要看对象是否有引用指向。java对象的引用包括强引用、软引用、弱引用和虚引用。
强引用:是指建立一个对象,并把这个对象赋给一个引用变量。强引用有引用变量指向时永远都不会被回收,即便内存不足时。
软引用:经过SoftReference类来实现,当系统内存充足时,系统不会进行软引用的内存回收,软引用的对象和强引用没有太多区别,可是内存不足时会回收软引用的对象。
弱引用:经过WeakReference类来实现,具备很强的不肯定性,由于垃圾回收每次都会回收弱引用的对象。
虚引用:软引用和弱引用均可以单独使用,虚引用不能单独使用,必须关联引用队列。虚引用的做用就是跟踪对象被垃圾回收的状态,程序能够经过检测与虚引用关联的虚引用队列是否已经包含了指定的虚引用,从而了解虚引用对象是否即将被回收。它容许你知道对象什么时候从内存中移除。
java中引用越弱表示对垃圾回收器的限制越少,对象越容易被回收。
一、引用计数器算法:当建立对象时,为这个对象在堆栈空间中分配地址,同时会产生一个引用计数器,同时引用计数器+1,当有新的引用的时候,引用计数器继续+1,而当其中一个引用销毁时,引用计数器-1,当引用计数器被减为0的时候,标志着这个对象已经没有引用了,能够被回收。可是当代码出现下面的情形时,该算法没法适用,objA指向objB,而objB又指向objA,这样其余全部引用都消失了以后,objA和ObjB仍是有一个相互的引用,没法回收,但实际上这两个对象都已经没有额外的引用了,已是垃圾了。
ObjA.obj = ObjB;ObjB.obj = ObjA;1
二、根搜索算法(GC Root):把全部的引用关系看作一张图,从一个节点GC Root开始,寻找对应的引用节点,找到这个节点之后,继续寻找这个节点的引用节点,当全部的引用节点寻找完毕后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。java中可做为GC Root的对象有:虚拟机栈中的引用对象、方法区中静态属性引用的对象、方法区中常量引用的对象、本地方法栈中引用的对象。
三、收集后的垃圾经过什么算法来回收?
标记-清除算法:采用从根集合进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收。标记-清除算法不须要进行对象的移动,而且仅对不存活的对象进行处理,在存活对象比较多的状况下极为高效,可是因为标记-清除算法直接回收不存活的对象,所以会形成内存碎片。
复制算法(用于新生代):复制算法采用从根集合扫描,并将存活对象复制到一块新的、没有使用过的空间中,这种算法当内存中存活的对象比较少时,极为高效,可是带来的成本是须要一块内存交换空间用于进行对象的移动。复制算法中,新生代中每次只使用Eden区和一块幸存区存储数据,当幸存区达到饱和状态时,将幸存区的存活的对象移动到另外一块幸存区。
标记-整理算法(用于老年代):标记-整理算法和标记-清除算法采用同样的方式进行对象的标记,可是清除时不一样,在回收不存活的对象占用的空间后,会将全部存活的对象往左端空闲空间移动,并更新对应的指针。解决了内存碎片的问题。
分代回收机制:
新生代:绝大多数最新被建立的对象会被分配到这里,因为大部分对象在建立后会很快变得不可达,因此不少对象被建立在新生代,而后消失。对象此区域消失的过程称为“minor GC”.
一共有三个空间,其中包含一个伊甸园区(Eden)和两个幸存区(survivor)。各空间执行顺序以下:
一、绝大多数刚刚被建立的对象会存放在伊甸园空间。
二、在伊甸园空间执行了一次 GC后,存活的对象被移动到其中一个幸存者空间。
三、此后,在伊甸园空间执行GC后,存活的对象会被堆积在同一个幸存者空间。
四、当一个幸存者空间饱和户,还在存活的对象会被移动到另外一个幸存者空间,以后会清空已经饱和的那个幸存者空间。
五、在以上的步骤中重复几回依然存活的对象就会被移动到老年代。
老年代:对象没有变得不可达,而且重新生代中存活下来,就会被拷贝到这里,其所占的空间要比新生代多。也正是由于其相对较大的空间,发生在老年代上的GC要比新生代少得多。对象从老年代中消失的过程,称为“major GC”。
永久代:也被称为方法区,用来保存类常量以及字符串常量。所以这个区域不是用来永久的存储那些从老年代存活下来的对象。这个区域也可能发生GC,而且发生在这个区域上的GC事件也被称为major GC。