当一个java项目启动的时候,JVM会找到main方法,根据对象之间的调用来对class文件和所引用的jar包中的class文件进行加载(其步骤分为加载、验证、准备、解析、初始化、使用和卸载),方法区中开辟内存来存储类的运行时数据结构(包括静态变量、静态方法、常量池、类结构等),同时在堆中生成相应的Class对象指向方法区中对应的类运行时数据结构。具体的类加载过程可以参考尚学堂高琪老师的视频教程:http://www.bjsxt.com/2014/down_0425/34.html第218集。

  用最简单的一句话来概括,类加载的过程就是JVM根据所需的class文件的路径,通过IO流的方式来读取class字节码文件,并通过一系列解析初始化等步骤来注入到内存。 java中的类加载器有:BootstrapClassLoader(最上层)、ExtClassLoader、AppClassLoader、以及用户自定义的ClassLoader(最下层)。JVM对于不同种类的jar包(或class文件),会有不同种类的类加载器进行加载。对应关系如下:  

BootstrapClassLoader  用于加载JVM运行所需要的类:

    JAVA_HOME/jre/lib/resources.jar:

    JAVA_HOME/jre/lib/rt.jar:

    JAVA_HOME/jre/lib/sunrsasign.jar:

    JAVA_HOME/jre/lib/jsse.jar:

    JAVA_HOME/jre/lib/jce.jar:

    JAVA_HOME/jre/lib/charsets.jar:

    JAVA_HOME/jre/lib/jfr.jar:

    JAVA_HOME/jre/classes

 

  ExtClassLoader 用于加载扩展类:  

    ../Java/Extensions:

    ../JAVA_HOME/jre/lib/ext:

    ../Library/Java/Extensions:/Network/Library/Java/Extensions:

    ../System/Library/Java/Extensions:

    ../lib/java

  

  AppClassLoader 用于加载我们项目中ClassPath下所创建的类和jar包中引用的类。

  整个类加载,是通过一种叫做双亲委派的机制来进行加载。

  举例来说,一个类被最下层的加载器(用户自定义ClassLoader)进行加载,此加载器首先会调用上一层的加载器(AppClassLoader)进行加载,而AppClassLoader会继续转交给上层(ExtClassLoader)的加载器进行加载,直到BootstrapClassLoader。  如果BootstrapClassLoader所加载的类路径找不到此类,那么才会交给下一层的加载器(ExtClassLoader)进行加载,如果找不到此类,继续交给下一层(AppClassLoader)进行加载。以此类推,如果用户自定义的ClassLoader也找不到此类,那么程序就会抛出一个ClassNotFoundError。整个加载过程图示如下:

(图片引用自:https://www.cnblogs.com/xing901022/p/4574961.html)

  类加载源的源码跟踪如下(在此对源码进行了适当的简化),读者可以点入源码进行查看:

package java.lang.ClassLoader;
import .... protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First,在虚拟机内存中查找是否已经加载过此类...类缓存的主要问题所在!!!
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
               //先让上一层加载器进行加载
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
} if (c == null) {
            //调用此类加载器所实现的findClass方法进行加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}

  在源码中可以完全领略到双亲委派机制的过程,其中最重要的三句代码已经进行了标注:

    findLoadedClass(在虚拟机内存中查找是否已经加载过此类...类缓存的主要问题所在!!!
    parent.loadClass(先让上一层加载器进行加载
    findClass(调用此类加载器所实现的findClass方法进行加载

  如果用户需要自定义加载器,加载自己指定路径的class文件,需要继承ClassLoader,并实现findClass(String name)方法。举例如下:
package com.jiefupay.utils;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream; public class ServiceClassLoader extends ClassLoader{ private String classPath; public ServiceClassLoader(String classPath) {
this.classPath = classPath;
} /**
* 重写父类的findClass 方法。 父类的loadClass会调用此方法
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException { Class<?> c = null; byte[] classData = getClassData(name); if (classData!=null) {
c = defineClass(name, classData, 0, classData.length);
}else {
throw new ClassNotFoundException();
} return c;
}
  
  
   // 将class文件通过IO流读取,转化为字节数组
private byte[] getClassData(String name) { String path = classPath + "/"+ name.replace('.', '/') + ".class"; InputStream iStream = null;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
iStream = new FileInputStream(path); byte[] buffer = new byte[1024];
int temp = 0;
while ((temp = iStream.read(buffer))!=-1) {
byteArrayOutputStream.write(buffer, 0, temp);
}
if (byteArrayOutputStream!=null) {
return byteArrayOutputStream.toByteArray();
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if (iStream!=null) {
iStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (byteArrayOutputStream!=null) {
byteArrayOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}

  对类加载器的使用代码如下:

ServiceClassLoader serviceClassLoader = new ServiceClassLoader("c:\myclass");
Czlass<?> c = ServiceClassLoader.loadClass("com.jiefupay.service.Myclass");

  如果用同一个ServiceClassLoader对象去加载同一个Class文件多次,每次加载后的Class对象为同一个! 然而如果new不同的自定义ClassLoader去加载同一个Class文件,则每次会返回不同的Class对象。

  注意:不能将所要加载的Class文件放到classpath目录及其任何子目录下,否则会被AppClassLoader优先加载(这是由于类加载采用双亲委派机制,同时AppClassLoader可以加载所有在classpath下的class文件),每次都是同一个AppClassLoader进行加载,因此会出现类缓存问题。

  这样就解决了通常在JVM类加载时,直接使用反射出现的类缓存的问题。

  



JVM类加载机制以及类缓存问题的处理的更多相关文章

  1. Java虚拟机(四):JVM类加载机制

    1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构 ...

  2. JVM类加载机制(转)

    原文出自:http://www.cnblogs.com/ityouknow/p/5603287.html 1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运 ...

  3. Android动态加载--JVM 类加载机制

    动态加载,本质上是通过JVM类加载机制将插件模块加载到宿主apk中,并通过android的相关运行机制,实现插件apk的运行.因此熟悉JVM类加载的机制非常重要. 类加载机制:虚拟机把描述类的数据从C ...

  4. 深入理解JVM虚拟机6:深入理解JVM类加载机制

    深入理解JVM类加载机制 简述:虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 下面我们具体 ...

  5. JVM笔记6:JVM类加载机制

    虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析.初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制 从类被加载到虚拟机内存中开始,到卸载出内存为止 ...

  6. JVM基础系列第7讲:JVM 类加载机制

    当 Java 虚拟机将 Java 源码编译为字节码之后,虚拟机便可以将字节码读取进内存,从而进行解析.运行等整个过程,这个过程我们叫:Java 虚拟机的类加载机制.JVM 虚拟机执行 class 字节 ...

  7. JVM总结(四):JVM类加载机制

    这一节我们来总结一下JVM类加载机制.具体目录如下: 类加载的过程 类加载过程概括 说说引用 详解类加载全过程: 加载 验证 准备 解析 初始化 虚拟机把描述类的数据从Class文件加载到内存,并对数 ...

  8. JVM 类加载机制详解

    如下图所示,JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程. 加载 加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的java.lan ...

  9. 理解JVM——类加载机制

    我们在编写Java程序之后,会通过编译器得到一个class文件,这个class文件是如何与JVM进行配合的呢?类中的信息是如何变成JVM可以使用的Java类型呢?这些都是类加载机制做到的. 虚拟机把描 ...

随机推荐

  1. PHP启动:动态库加载失败(PHP Warning: PHP Startup: Unable to load dynamic library '/usr/local/php7/lib/php/extensions/no-debug-non-zts-20151012/*.so')

    今天在linux上面智障一般搞了好久,本来想安装个swoole的,然后用  php -m 的命令想看下安装的PHP扩展库,发现有的扩展库有的可以出来,有的加载失败, 加载失败的错误类型: PHP Wa ...

  2. linux环境变量的配置

    1.全局环境变量 vi /etc/profile JAVA_HOME=/opt/modules/jdk1.8.0_11/   export PATH=$PATH:$JAVA_HOME/bin:$JAV ...

  3. python并发编程之多线程二

    一,开启线程的两种方式 方法一: from threading import Thread import random,time def eat(name): print('%s is eating. ...

  4. Win10 之最新最简单有效安装配置adb

    今天在新买的笔记本上安装配置adb,开始觉得挺简单的事,公司win7电脑上有现成的,但实际过程中--没想的那么简单了!好了,废话少说,直接正题. 研究了好一会下,总算搞定,总结如下: 1.下载Andr ...

  5. 蓝桥杯 牌型种数 DFS

    牌型种数 小明被劫持到X赌城,被迫与其他3人玩牌. 一副扑克牌(去掉大小王牌,共52张),均匀发给4个人,每个人13张. 这时,小明脑子里突然冒出一个问题: 如果不考虑花色,只考虑点数,也不考虑自己得 ...

  6. 负载均衡之nginx

    什么是负载均衡负载均衡(Load Balance)是分布式系统架构设计中必须考虑的因素之一,它通常是指,将请求/数据[均匀]分摊到多个操作单元上执行,负载均衡的关键在于[均匀].在使用nginx负载均 ...

  7. 两个实用linux小工具

    使用 sshpass 工具来做名密码输入 使用 alias 别名来做成命令语句. Linux命令之非交互SSH密码验证-sshpass ssh登陆不能在命令行中指定密码.sshpass的出现,解决了这 ...

  8. js+画曲线和圆 并限制圆的渲染范围

    通过三个点的坐标可确定一条双曲线. 公式: 1)y=ax^2+bx+c; 2) y=a(x-k)+h; 通过已知三点可确定a,b,c,h,k 2.通过圆心坐标(a,b)和半径r可确定一个圆,和已知的x ...

  9. java web学习笔记 servlet

    关于java web web.xml中一般配置的都是与servlet先关的可以配置servlet filter listener context-param用来配置web应用的启动参数,可用通过Ser ...

  10. iOS 读书笔记-国际化

    吐槽一下:国际化-我想说学习的这个project好痛苦. 也许是百度的原因,总是不能找到自己想要东西. 找到的内容不是不具体就是时间有点久了.让我这种小白非常头痛. 以下记录一下整个过程. 国际化是什 ...