1、根类加载器(Bootstrap)
2、扩展类加载器(Extension):加载位置jre\lib\ext中
3、系统(应用)类加载器(System\App):加载位置:classpath中
4、自定义类加载器(必须继承ClassLoader)
创建类的实例,也就是new一个对象
访问某个类或接口的静态变量,或者对该静态变量赋值
调用类的静态方法
反射(Class.forName)
初始化一个类的子类(首先初始化子类的父类)
JVM启动时标明的启动类,即文件名和类名相同的那个类
只有这6中情况才会导致类的初始化
类的初始化步骤:
如果这个类还没有被加载和链接,那先进行加载和链接
假如这个类存在的直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接地父类(不适用于接口)
假如勒种存在的初始化语句(如static变量和static块),那就依次执行这些初始化的语句
java是一种类型安全的语言,它有四类称为安全沙箱机制的安全机制来保证语言的安全性,这四类安全沙箱分别是:
类加载体系
class文件检验器
内置于java虚拟机(及语言)的安全特性
安全管理器及JAVA API
java程序中.java文件变异完会生成.class文件,二.class文件就是通过被称为类加载器的ClassLoader加载的,二ClassLoader在加载过程中会使用“双亲委派机制”来加载.class文件,
BootStrapClassLoader:启动类加载器,该ClassLoader是jvm在启动时创建的,用于加载$JAVA_HOME$/jre/lib下面的类库(或者通过参数-Xbootclasspath指定)。由于启动类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不能直接通过引用进行操作。
ExtClassLoader:扩展类加载器,该ClassLoader是在sun.misc.Launcher里作为一个内部类ExtClassLoader定义的(即sun.misc.Launcher$ExtClassLoader),ExtClassLoader会加载$JAVA_HOME/jre/lib/ext下的类库(或者通过参数-Djava.ext.dirs指定)。
AppClassLoader:应用程序类加载器,该ClassLoader同样是在sun.misc.Launcher里作为一个内部类AppClassLoader定义的(即sun.misc.Launcher$AppClassLoader),AppClassLoader会加载java环境变量CLASSPATH所指定的路径下的类库,而CLASSPATH所指定的路径可以通过System.getProperty("java.class.path")获取;当然,该变量也可以覆盖,可以使用参数-cp,例如:java -cp 路径(可以指定要执行的class目录)。
CustomClassLoader:自定义类加载器,该ClassLoader是指我们自定义的ClassLoader,比如tomcat的StandardClassLoader属于这一类;当然,大部分情况下使用AppClassLoader就足够了。
前面谈到了ClassLoader的几类加载器,而ClassLoader使用双亲委派机制来加载class文件的。ClassLoader的双亲委派机制是这样的(这里先忽略掉自定义类加载器CustomClassLoader):
1)当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
2)当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
3)如果BootStrapClassLoader加载失败(例如在$JAVA_HOME$/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
4)若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。
下面贴下ClassLoader的loadClass(String name, boolean resolve)的源码:
代码很明朗:首先找缓存(findLoadedClass),没有的话就判断有没有parent,有的话就用parent来递归的loadClass,然而ExtClassLoader并没有设置parent,则会通过findBootstrapClassOrNull来加载class,而findBootstrapClassOrNull则会通过JNI方法”private native Class findBootstrapClass(String name)“来使用BootStrapClassLoader来加载class。
然后如果parent未找到class,则会调用findClass来加载class,findClass是一个protected的空方法,可以覆盖它以便自定义class加载过程。
另外,虽然ClassLoader加载类是使用loadClass方法,但是鼓励用ClassLoader 的子类重写findClass(String),而不是重写loadClass,这样就不会覆盖了类加载默认的双亲委派机制。
双亲委派托机制为什么安全
举个例子,ClassLoader加载的class文件来源很多,比如编译器编译生成的class、或者网络下载的字节码。
而一些来源的class文件是不可靠的,比如我可以自定义一个java.lang.Integer类来覆盖jdk中默认的Integer类,例如下面这样:
初始化这个Integer的构造器是会退出JVM,破坏应用程序的正常进行,如果使用双亲委派机制的话该Integer类永远不会被调用,以为委托BootStrapClassLoader加载后会加载JDK中的Integer类而不会加载自定义的这个,可以看下下面这测试个用例:
执行时JVM并未在new Integer(1)时退出,说明未使用自定义的Integer,于是就保证了安全性。
1. 加载
加载,是指Java虚拟机查找字节流(查找.class文件),并且根据字节流创建java.lang.Class对象的过程。这个过程,将类的.class文件中的二进制数据读入内存,放在运行时区域的方法区内。然后在堆中创建java.lang.Class对象,用来封装类在方法区的数据结构。
类加载阶段:
(1)Java虚拟机将.class文件读入内存,并为之创建一个Class对象。
(2)任何类被使用时系统都会为其创建一个且仅有一个Class对象。
(3)这个Class对象描述了这个类创建出来的对象的所有信息,比如有哪些构造方法,都有哪些成员方法,都有哪些成员变量
2. 链接
链接包括验证、准备以及解析三个阶段。
(1)验证阶段。主要的目的是确保被加载的类(.class文件的字节流)满足Java虚拟机规范,不会造成安全错误。
(2)准备阶段。负责为类的静态成员分配内存,并设置默认初始值。
(3)解析阶段。将类的二进制数据中的符号引用替换为直接引用。
说明:
符号引用。即一个字符串,但是这个字符串给出了一些能够唯一性识别一个方法,一个变量,一个类的相关信息。
直接引用。可以理解为一个内存地址,或者一个偏移量。比如类方法,类变量的直接引用是指向方法区的指针;而实例方法,实例变量的直接引用则是从实例的头指针开始算起到这个实例变量位置的偏移量。
举个例子来说,现在调用方法hello(),这个方法的地址是0xaabbccdd,那么hello就是符号引用,0xaabbccdd就是直接引用。
在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。
3. 初始化
初始化,则是为标记为常量值的字段赋值的过程。换句话说,只对static修饰的变量或语句块进行初始化。
如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。
如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
安全性考虑
减少内存泄漏
减少开发者的工作量
内存运行时JVM会有一个运行时数据区来管理内存。它主要包括5大部分:程序计数器,虚拟机栈,本地方法栈,方法区,堆。
而其中程序计数器、虚拟机栈、本地方法栈使每个线程私有的内部空间,随线程生和亡。例如栈中每一个栈帧中分配多少内存基本上在类结构确定是哪个时就已知了,因此这三个区域的内存分配和回收是确定的,无需考虑内存回收的问题。
但方法区和堆就不同了,一个接口的多个实现类需要的内存可能不一样,我们只有在程序运行期间才会知道会创建哪些对象,这部分内存的分配和回收都是动态的,GC主要关注的是这部分的内存。
引起内存溢出的原因有很多,常见的有以下几种:
内存中加载的数据量过于庞大,如一次从数据库中读取过多数据;
集合类中有对对象的引用,但是使用完成之后未清空,使的JVM不能回收;
代码中存在死循环或者循环过多产生过多的重复对象实体;
使用的第三方软件中的BUG;
启动参数内存值设定的过小;
内存溢出解决方案:
修改JVM启动参数,直接增加内存(-Xms,-Xmx参数)
检查错误日志,查看OutOfMemory或其他异常错误;
对代码进行走读和分析,找出可能发生内存溢出的位置;