精华内容
下载资源
问答
  • Java 类加载器解析及常见类加载问题java.lang.ClassLoader每个类加载器本身也是个对象——一个继承 java.lang.ClassLoader 的实例。每个被其中一个实例加载。我们下面来看看 java.lang.ClassLoader 中的 API, 不太...

    Java 类加载器解析及常见类加载问题

    java.lang.ClassLoader

    每个类加载器本身也是个对象——一个继承 java.lang.ClassLoader 的实例。每个类被其中一个实例加载。我们下面来看看 java.lang.ClassLoader 中的 API, 不太相关的部分已忽略。

    package java.lang;

    public abstract class ClassLoader {

    public Class loadClass(String name);

    protected Class defineClass(byte[] b);

    public URL getResource(String name);

    public Enumeration getResources(String name);

    public ClassLoader getParent()

    }

    loadClass: 目前 java.lang.ClassLoader 中最重要的方法是 loadClass 方法,它获取要加载的类的全限定名返回 Class 对象。

    defineClass: defineClass 方法用于具体化 JVM 的类。byte 数组参数是加载自磁盘或其他位置的类字节码。

    getResource 和 getResources: 返回资源路径。loadClass 大致相当于 defineClass(getResource(name).getBytes())。

    getParent: 返回父加载器。

    Java 的懒惰特性影响了类加载器的工作方式——所有事情都应该在最后一刻完成。类只有在以某种方式被引用时才会被加载-通过调用构造函数、静态方法或字段。看个例子:

    类 A 实例化类 B:

    public class A {

    public void doSomething() {

    B b = new B();

    b.doSomethingElse();

    }

    }

    语句 B b = new B() 在语义上等同于 B b = A.class. getClassLoader().loadClass(“B”).newInstance()。如我们所见,Java 中的每个对象都与其类 (A.class) 相关联,并且每个类都与用于加载类的类加载器 (A.class.getClassLoader()) 相关联。

    当我们实例化类加载器时,我们可以将父类加载器指定为构造函数参数。如果未显式指定父类加载器,则会将虚拟机的系统类加载器指定为默认父类。

    类加载器层次结构

    每当启动新的 JVM 时,引导类加载器(bootstrap classloader)负责首先将关键 Java 类(来自 Java.lang 包)和其他运行时类加载到内存中。引导类加载器是所有其他类加载器的父类。因此,它是唯一没有父类的。

    接下来是扩展类加载器(extension classloader)。引导类加载器(bootstrap classloader)是它父类, 它负责从 java.ext.dirs 路径中保存的所有 .jar 文件加载类。

    从开发人员的角度来看,第三个也是最重要的类加载器是系统类路径类加载器(system classpath classloader),它是扩展类加载器(extension classloader)的直接子类。它从由 CLASSPATH 环境变量 java.class.pat h系统属性或 -classpath 命令行选项指定的目录和 jar 文件加载类。

    a136210e768710d05e5e617cb67b5722.png

    请注意,类加载器层次结构不是继承层次结构,而是委托层次结构。大多数类加载器在搜索自己的类路径之前将查找类和资源委托给其父类。如果父类加载器找不到类或资源,则类加载器只能尝试在本地找到它们。实际上,类加载器只负责加载父级不可用的类;层次结构中较高的类加载器加载的类不能引用层次结构中较低的可用类。类加载器委托行为的动机是避免多次加载同一个类。

    在 Java EE 中,查找的顺序通常是相反的:类加载器可能在转到父类之前尝试在本地查找类。

    Java EE 委托模型

    下面是应用程序容器的类加载器层次结构的典型视图:容器本身有一个类加载器,每个 EAR 模块都有自己的类加载器,每个 WAR 都有自己的类加载器。 Java Servlet 规范建议 web 模块的类加载器在委托给其父类之前先在本地类加载器中查找——父类加载器只要求提供模块中找不到的资源和类。

    c4d5c366eba5b42542043cbb5488818e.png

    在某些应用程序容器中,遵循此建议,但在其他应用程序容器中,web 模块的类加载器配置为遵循与其他类加载器相同的委托模型,因此建议参考您使用的应用程序容器的文档。

    颠倒本地查找和委托查找之间的顺序的原因是,应用程序容器附带了许多具有自己的发布周期的库,这些库可能不适用于应用程序开发人员。典型的例子是 log4j 库——它的一个版本通常随容器一起提供,不同的版本与应用程序捆绑在一起。

    现在,让我们来看看我们可能遇到的几个常见的类加载问题,并提供可能的解决方案。

    常见类加载问题

    Java EE 委托模型会导致类加载的一些有趣的问题。NoClassDefFoundError、LinkageError、ClassNotFoundException、NoSuchMethodError、ClassCasteException等是开发 Java EE 应用程序时遇到的非常常见的异常。我们可以对这些问题的根本原因做出各种假设,但重要的是要验证它们。

    NoClassDefFoundError

    NoClassDefFoundError 是开发 Java EE Java 应用程序时最常见的问题之一。

    根本原因分析和解决过程的复杂性主要取决于 Java EE 中间件环境的大小;特别是考虑到各种 Java EE 应用程序中存在大量的类加载器。

    正如 Javadoc 条目所说,如果 Java 虚拟机或类加载器实例试图在类的定义中加载,而找不到类的定义,则抛出 NoClassDefFoundError。这意味着,在编译当前执行的类时,搜索到的类定义存在,但在运行时找不到该定义。

    这就是为什么你不能总是依赖你的 IDE 告诉你一切正常,代码编译应该正常工作。相反,这是一个运行时问题,IDE 在这里无法提供帮助。

    让我们看看下面的例子:

    public class HelloServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request,

    HttpServletResponse response)

    throws ServletException, IOException {

    PrintWriter out = response.getWriter();

    out.print(new Util().sayHello());

    }

    servlet HelloServlet 实例化了 Util 类的一个实例,该实例提供了要打印的消息。遗憾的是,当请求执行时,我们可能会看到以下内容:

    java.lang.NoClassdefFoundError: Util

    HelloServlet:doGet(HelloServlet.java:17)

    javax.servlet.http.HttpServlet.service(HttpServlet.java:617)

    javax.servlet.http.HttpServlet.service(HttpServlet.java:717)

    我们如何解决这个问题?好吧,您可能要做的最明显的操作是检查丢失的 Util 类是否已实际包含在包中。

    我们在这里可以使用的技巧之一是让容器类加载器承认它从何处加载资源。为此,我们可以尝试将 HelloServlet 的类加载器转换为 URLClassLoader 并请求其类路径。

    public class HelloServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request,

    HttpServletResponse response)

    throws ServletException, IOException {

    PrintWriter out = response.getWriter();

    out.print(Arrays.toString(

    ((URLClassLoader)HelloServlet.class.getClassLoader()).getURLs()));

    }

    结果很可能是这样:

    file:/Users/myuser/eclipse/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/demo/WEB-INF/classes,

    file:/Users/myuser/eclipse/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/demo/WEB-INF/lib/demo-lib.jar

    资源的路径(file:/Users/myuser/eclipse/workspace/.metadata/)实际上显示容器是从 Eclipse 启动的,这是 IDE 解压归档文件来进行部署的地方。现在我们可以检查丢失的 Util 是否真的包含在 demo-lib.jar 中,或者它是否存在于扩展存档的 WEB-INF/classes 目录中。

    因此,对于我们的特定示例,可能是这样的情况:Util 类应该打包到 demo-lib.jar 中,但是我们没有重新启动构建过程,并且该类没有包含在以前存在的包中,因此出现了错误。

    URLClassLoader 技巧可能不适用于所有应用服务器。另一种方法是使用jconsole 实用程序附加到容器JVM进程,以检查类路径。例如,屏幕截图(如下)演示了连接到 JBoss application server 进程的 jconsole 窗口,我们可以从运行时属性中看到 ClassPath 属性值。

    432b593f9bbe3059e1f25f9d39cdea3c.png

    NoSuchMethodError

    在另一个具有相同示例的场景中,我们可能会遇到以下异常:

    java.lang.NoSuchMethodError: Util.sayHello()Ljava/lang/String;

    HelloServlet:doGet(HelloServlet.java:17)

    javax.servlet.http.HttpServlet.service(HttpServlet.java:617)

    javax.servlet.http.HttpServlet.service(HttpServlet.java:717)

    NoSuchMethodError 代表另一个问题。在本例中,我们所引用的类存在,但加载的类版本不正确,因此找不到所需的方法。

    要解决这个问题,我们首先必须了解类是从何处加载的。最简单的方法是向 JVM 添加 '-verbose:class' 命令行参数,但是如果您可以快速更改代码,那么您可以使用 getResource 搜索与 loadClass 相同的类路径。

    public class HelloServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request,

    HttpServletResponse response)

    throws ServletException, IOException {

    PrintWriter out = response.getWriter();

    out.print(HelloServlet.class.getClassLoader().getResource(

    Util.class.getName.replace(‘.’, ‘/’) + “.class”));

    }

    假设,上述示例的请求执行结果如下.

    file:/Users/myuser/eclipse/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/demo/WEB-INF/lib/demo-lib.jar!/Util.class

    现在我们需要验证关于类的错误版本的假设。我们可以使用javap实用程序来反编译类,然后我们可以看到所需的方法是否实际存在。

    $ javap -private Util

    Compiled from “Util.java”

    public class Util extends java.lang.Object {

    public Util();

    }

    如您所见,Util 类的反编译版本中没有sayHello方法。可能,我们在 demo-lib.jar 中打包了 Util 类的初始版本,但是在添加了新的 sayHello 方法之后,我们没有重新构建这个包。

    在处理 Java EE 应用程序时,错误类问题 NoClassDefFoundError 和 NoSuchMethodError 的变体是非常典型的,这是 Java 开发人员理解这些错误的本质以有效解决问题所必需的技能。

    这些问题有很多变体:AbstractMethodError、ClassCastException、IllegalAccessError——基本上,当我们认为应用程序使用类的一个版本,但实际上它使用了其他版本,或者类的加载方式与需要的不同时,这些问题都会遇到。

    ClassCastException

    这里我们只演示 ClassCastException 例子。我们将以使用工厂修改初始示例,以便提供提供问候消息的类的实现。这看起来很做作,但这是很常见的模式。

    public class HelloServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request,

    HttpServletResponse response)

    throws ServletException, IOException {

    PrintWriter out = response.getWriter();

    out.print(((Util)Factory.getUtil()).sayHello());

    }

    class Factory {

    public static Object getUtil() {

    return new Util();

    }

    }

    请求的可能结果是:

    java.lang.ClassCastException: Util cannot be cast to Util

    HelloServlet:doGet(HelloServlet.java:18)

    javax.servlet.http.HttpServlet.service(HttpServlet.java:617)

    javax.servlet.http.HttpServlet.service(HttpServlet.java:717)

    这意味着 HelloServlet 和 Factory 类在不同的上下文中操作。我们必须弄清楚这些类是如何加载的。让我们使用 -verbose:class 并找出如何加载与HelloServlet 和 Factory 类相关的 Util 类。

    [Loaded Util from file:/Users/ekabanov/Applications/ apache-tomcat-6.0.20/lib/cl-shared-jar.jar]

    [Loaded Util from file:/Users/ekabanov/Documents/workspace-javazone/.metadata/.plugins/org.eclipse.wst. server.core/tmp0/wtpwebapps/cl-demo/WEB-INF/lib/cl-demo- jar.jar]

    因此,Util类由不同的类加载器从两个不同的位置加载。一个在web应用程序类加载器中,另一个在应用程序容器类加载器中。它们是不兼容的,不能相互转换。

    972e81025ad25aa2b35560f11b15b746.png

    但它们为什么不相容呢?原来Java中的每个类都是由其完全限定名唯一标识的。但在1997年发表的一篇论文揭露了由此引起的一个广泛的安全问题,即沙盒应用程序(例如: applet)可以定义任何类,包括 java.lang.String,并在沙盒外注入自己的代码。

    解决方案是通过完全限定名和类加载器的组合来标识类!这意味着从类加载器 A 加载的 Util 类和从类加载器 B 加载的 Util 类在 JVM 中是不同的类,不能将一个类转换为另一个类!

    这个问题的根源是 web 类加载器的反向行为。如果 web 类加载器的行为与其他类加载器相同,那么 Util 类将从应用程序容器类加载器加载一次,并且不会抛出类 CastException。

    LinkageError

    让我们从前面的示例中稍微修改一下 Factory 类,这样 getUtil 方法现在返回的是 Util 类型而不是 Object:

    class Factory {

    public static Util getUtil() {

    return new Util();

    }

    }

    现在,执行的结果是 LinkageError:

    ClassCastException: java.lang.LinkageError: loader constraint violation: when resolving method Factory.getUtil()LUtil;

    HelloServlet:doGet(HelloServlet.java:18)

    javax.servlet.http.HttpServlet.service(HttpServlet.java:617) javax.servlet.http.HttpServlet.service(HttpServlet.java:717)

    根本问题与 ClassCastException 相同——唯一的区别是我们不强制转换对象,而是加载程序约束导致Linkage错误。

    在处理类加载器时,一个非常重要的原则是认识到类加载器的行为常常会在你的直觉之外,因此验证您的假设非常重要。例如,在 LinkageError 的情况下,查看代码或构建过程将阻碍而不是帮助您。关键是查看类的确切加载位置,它们是如何到达那里的,以及在将来如何防止发生这种情况。

    多个类加载器中存在相同类的一个常见原因是,同一个库的不同版本捆绑在不同的位置,例如应用服务器和 web 应用程序。这通常发生在像 log4j 或 hibernate 这样的实际标准库中。在这种情况下,解决方案要么是将库与 web 应用程序分开,要么是非常小心地避免使用父类加载器中的类。

    IllegalAccessError

    其实,不仅类由其全限定名和类加载器标识,而且该规则也适用于包。为了演示这一点,我们将 Factory.getUtil 方法的访问修饰符更改为默认值:

    class Factory {

    static Object getUtil() {

    return new Util();

    }

    }

    假设 HelloServlet 和 Factory 都位于同一个(默认)包中,因此 getUtil 在 HelloServlet 类中可见。不幸的是,如果我们试图在运行时访问它,我们将看到 IllegalAccessError 异常。

    java.lang.IllegalAccessError: tried to access method Factory.getUtil()Ljava/lang/Object;

    HelloServlet:doGet(HelloServlet.java:18)

    javax.servlet.http.HttpServlet.service(HttpServlet.java:617)

    javax.servlet.http.HttpServlet.service(HttpServlet.java:717)

    尽管访问修饰符对于应用程序的编译是正确的,但是在运行时,这些类是从不同的类加载器加载的,应用程序无法运行。这是由于与类一样,包也由它们的完全限定名和类加载器来标识,出于同样的安全原因。

    ClassCastException、LinkageError 和 IllegalAccessError 根据实现有点不同,但根本原因是相同的类被不同的类加载器加载。

    Java 类加载器备忘单

    de28fb1ee6dcfbaa934d157842058599.png

    No class found

    Variants

    ClassNotFoundException

    NoClassDefFoundError

    Helpful

    IDE class lookup (Ctrl+Shift+T in Eclipse)

    find *.jar -exec jar -tf '{}'; | grep MyClass

    URLClassLoader.getUrls() Container specific logs

    Wrong class found

    Variants

    IncompatibleClassChangeError AbstractMethodError NoSuch(Method|Field)Error

    ClassCastException, IllegalAccessError

    Helpful

    -verbose:class

    ClassLoader.getResource() javap -private MyClass

    More than one class found

    LinkageError (class loading constraints violated)

    ClassCastException, IllegalAccessError

    Helpful

    -verbose:class

    ClassLoader.getResource()

    参考链接:

    展开全文
  • javaDemo: /** * 2021年4月1日下午5:06:10 */ package testClsLoader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.Method; /**

    先准备一个正确的class文件:

    先在E:\Temp\javatest目录写了一个简单的java文件并生成一个合法的class文件:

    javaDemo:

    /**
     * 2021年4月1日下午5:06:10
     */
    package testClsLoader;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.lang.reflect.Method;
    
    /**
     * @author XWF
     *
     */
    public class TestClsLoader {
    
    	/**
    	 * @param args
    	 */
    	public static void main(String[] args) {
    		try {
    			MyClsLoader mcl = new MyClsLoader("E:\\Temp\\javatest");
    			Class<?> cls = mcl.loadClass("Add");
    			Object obj = cls.getDeclaredConstructor(null).newInstance(null);
    			Method m = cls.getDeclaredMethod("add", int.class, int.class);
    			m.invoke(obj, 1, 2);
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    
    }
    
    class MyClsLoader extends ClassLoader {
    	private String path;
    	public MyClsLoader(String classFilePath) {
    		this.path = classFilePath;
    	}
    	
    	@Override
    	protected Class<?> findClass(String name) throws ClassNotFoundException {
    		try {
    			byte[] bytes = null;
    			try(FileInputStream fis = new FileInputStream(new File(this.path, name + ".class"))) {
    				bytes = fis.readAllBytes();
    			}
    			return defineClass(name, bytes, 0, bytes.length);
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    		return super.findClass(name);
    	}
    }
    

    执行结果:

    继承ClassLoader重写findClass方法,将.class文件读取放到byte数组,传递给defineClass方法生成Class对象,然后使用反射调用方法即可;

    一个classLoader对同一个类只能加载一次,若要动态加载,只需新new一个类加载器重新加载即可,旧实例等gc回收,要注意防止存在引用造成内存泄漏;

    展开全文
  • 背景:在使用谷歌开源的本地缓存解决经常查询数据库导致的查询效率低下,将从数据库查询好的数据放入到缓存中,然后设计...问题:需要对CacheService进行初始化,设计的初衷是:当Service的bean被加载之后,其中的缓...

    背景:

    在使用谷歌开源的本地缓存解决经常查询数据库导致的查询效率低下,将从数据库查询好的数据放入到缓存中,然后设计过期时间,接着设计一个get方法缓存汇总获取数据,进一步将整个流程封装成一个CacheSerice,然后在Controller层调用这个Service,从Service中获取数据。

    问题:

    需要对CacheService进行初始化,设计的初衷是:当Service的bean被加载之后,其中的缓存数据就已经被初始化(即利用数据库查询Service获取数据,并塞入缓存),而这个初始化的过程被我放到了CacheService类的构造函数中。结果在发布的时候就一直报空指针。

    @Service("test")

    public class Test implements IAppnameCache {

    @Autowired

    IAppnameService iAppnameService;

    public Test(){

    iAppnameService.queryAppname();// 抛出空指针

    }

    @Override

    public List get(String app){

    return iAppnameService.queryAppname();

    }

    }

    问题定位:

    经过查询日志,发现是CacheService的构造函数在执行的时候发生空指针问题。那么有可能是引入的谷歌开源库的问题有可能不是,采用排除法很快就发现了不是这个库的问题,不含谷歌开源库的测试类采用这种写法也发生了空指针的问题。

    问题思考:

    既然跟引入的谷歌开源库没有关系,那就说明当CacheService被构造的时候(采用构造函数),里面依赖的其他bean还没有被构造出来,因而导致空指针问题。针对这个问题进一步对Spring的bean构造过程进行研究。

    Spring的bean加载过程:

    bean的主要生成过程如下:

    1,AbstractBeanFactory.getBean(String)

    2,AbstractBeanFactory.doGetBean(String, Class, Object[], boolean)

    3,DefaultSingletonBeanRegistry.getSingleton(String)

    4,AbstractAutowireCapableBeanFactory.createBean(String, RootBeanDefinition, Object[])

    5,AbstractAutowireCapableBeanFactory.doCreateBean(String, RootBeanDefinition, Object[])

    6,AbstractAutowireCapableBeanFactory.createBeanInstance(String, RootBeanDefinition, Object[])

    7,AbstractAutowireCapableBeanFactory.instantiateBean(String, RootBeanDefinition)

    8,SimpleInstantiationStrategy.instantiate(RootBeanDefinition, String, BeanFactory)

    9, AbstractAutowireCapableBeanFactory.populateBean(String, RootBeanDefinition, BeanWrapper)

    10,AbstractAutowireCapableBeanFactory.initializeBean(String, Object, RootBeanDefinition)

    11,AbstractAutowireCapableBeanFactory.invokeAwareMethods(String, Object)

    12,AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(Object, String)

    13,AbstractAutowireCapableBeanFactory.invokeInitMethods(String, Object, RootBeanDefinition)

    14,AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(Object, String)

    (1)在通过BeanFactory获取bean实例对象的时候,会先去单例集合中找是否已经创建了对应的实例,如果有就直接返回了,这里是第一次获取,所以没有拿到;

    (2)然后AbastractBeanFactory会根据bean的名称获取对应的BeanDefinition对象,BeanDefinition对象代表了对应类的各种元数据,所以根据BeanDefinition对象就可以判断是否是单例,是否依赖其他对象,如果依赖了其他对象那么先生成其依赖,这里是递归调用。

    在步骤7之前都是为了生成bean做准备,真正生成bean是在AbstractAutowireCapableBeanFactory的instantiateBean方法:

    protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {

    try {

    Object beanInstance;

    final BeanFactory parent = this;

    if (System.getSecurityManager() != null) {

    beanInstance = AccessController.doPrivileged(new PrivilegedAction() {

    @Override

    public Object run() {

    return getInstantiationStrategy().instantiate(mbd, beanName, parent);

    }

    }, getAccessControlContext());

    }

    else {

    beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);

    }

    BeanWrapper bw = new BeanWrapperImpl(beanInstance);

    initBeanWrapper(bw);

    return bw;

    }

    catch (Throwable ex) {

    throw new BeanCreationException(

    mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);

    }

    }

    -----------------------------------------------------------------------------------------------------

    @Override

    public Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner) {

    // Don't override the class with CGLIB if no overrides.

    if (bd.getMethodOverrides().isEmpty()) {

    Constructor> constructorToUse;

    synchronized (bd.constructorArgumentLock) {

    //这里一堆安全检查

    }

    //默认使用构造函数利用反射实例化bean

    return BeanUtils.instantiateClass(constructorToUse);

    }

    else {

    // Must generate CGLIB subclass.

    return instantiateWithMethodInjection(bd, beanName, owner);

    }

    }

    可以看到实际上bean的生成是直接使用BeanUtils工具类通过反射获取类的实例。

    而反射获取类实例的过程如下:

    Class> cls = Class.forName("cn.mldn.demo.Person"); // 取得Class对象

    Object obj = cls.newInstance() //反射实例化对象

    Constructor> cons = cls.getConstructor(String.class, int.class);//获得构造方法

    Method m3 = cls.getDeclaredMethod("getName"); //获得get方法

    Field nameField = cls.getDeclaredField("name"); // 获得name属性

    同时在JVM进行类加载的时,再进行到初始化这一步骤的时候,首先会调用默认构造器进行变量初始化:

    类构造器()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句快可以赋值,但是不能访问。

    类构造器()方法与类的构造函数(实例构造函数()方法)不同,它不需要显式调用父类构造,虚拟机会保证在子类()方法执行之前,父类的()方法已经执行完毕。因此在虚拟机中的第一个执行的()方法的类肯定是java.lang.Object。

    由于父类的()方法先执行,也就意味着父类中定义的静态语句快要优先于子类的变量赋值操作。

    ()方法对于类或接口来说并不是必须的,如果一个类中没有静态语句,也没有变量赋值的操作,那么编译器可以不为这个类生成()方法。(默认值是内存分配的时候赋予的,与初始化过程无关)

    接口中不能使用静态语句块,但接口与类不同是,执行接口的()方法不需要先执行父接口的()方法。只有当父接口中定义的变量被使用时,父接口才会被初始化。另外,接口的实现类在初始化时也一样不会执行接口的()方法。

    虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步,如果多个线程同时去初始化一个类,那么只会有一个线程执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕。如果一个类的()方法中有耗时很长的操作,那就可能造成多个进程阻塞。

    CacheService类中没有赋值行为,然后则会调用默认的构造函数,所以在CacheService类中,被反射获取构造器的时候会调用明确的构造器。回归到本次的问题,在构造器中使用了其他的bean,而Spring的bean生成其实是没有规律的(也就是依赖的bean还没有被注入),所以抛出空指针的异常。

    那么问题来了,Spring说好的有自动检测依赖的功能呢?

    请列位看官慢慢往下看,请小生为各位一一分解。

    我们把目光往前看,如果在容器中没有拿到目标bean,然后AbastractBeanFactory会根据bean的名称获取对应的BeanDefinition对象,BeanDefinition对象代表了对应类的各种元数据,

    // 运行到这里说明bean没有被创建,先获取此bean依赖的bean

    String[] dependsOn = mbd.getDependsOn();

    if (dependsOn != null) {

    for (String dep : dependsOn) {

    if (isDependent(beanName, dep)) {

    throw new BeanCreationException(mbd.getResourceDescription(), beanName,

    "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");

    }

    registerDependentBean(dep, beanName);

    //实例化依赖的bean

    getBean(dep);

    }

    }

    可以看到是通过getDependsOn方法获取依赖的bean,而这个过程是通过Setter将CacheService的属性bean进行注入,然后获取bean。那么既然属性注入的时候

    IAppnameService就已经完成bean注入了,为何构造器还是抛出了异常呢?原来上述过程注入明确指出的依赖,即在bean的配置中加入depends-on(也支持注解),

    如果没有配置,那么CacheService的属性注入是在getbean()完成之后。所以在执行CacheService的构造函数时当然抛出异常啦!那么也就是说Spring的依赖检查其实没有开启的,

    是需要手动在配置文件中开启的,在Spring中有四种依赖检查的方式。依赖检查有四种模式:simple,objects,all,none,都通过bean的dependency-check属性进行模式设置。

    当然Spring中的加载过程中,其加载过程还是遵循JVM的加载过程。

    JVM的主要加载过程

    5298ca7d6ed74ef69fb941b4ab4ca3d3.png

    在JVM中是通过类加载器以及类的全限定名来保证类的唯一性的,也就是说如果两个类的路径名称完全一样,但是只要是加载它们的类加载器不一样就可以认为是两个不一样的类。而在JVM中含有如下几种类加载器:

    JVM中包括集中类加载器:

    1 BootStrapClassLoader 引导类加载器

    2 ExtClassLoader 扩展类加载器

    3 AppClassLoader 应用类加载器

    4 CustomClassLoader 用户自定义类加载器

    并且在JVM中使用双亲委派模型进行加载。什么是双亲委派模型呢?就是在2,3,4的类加载器加载类的时候,都会向上调用父类加载器来实现,也就是说最后都是交给BootStrapClassLoader加载器完成加载的。这样做的好处一是因为安全性,因为JVM的类加载过程中有验证这一步骤,会对class文件进行校验,判断是否符合JVM规范。二是因为保证类不会被重复加载,因为在执行new的时候,会首先从元数据区查找类符号,如果没有则会加载相应的文件。所以为了避免在这个过程中重复加载的现象,最终都是通过系统提供的类加载器完成加载。

    加载细节:

    展开全文
  • 一、什么是类加载类加载器ClassLoader就是将我们的.class文件转换成Class对象。Class只有被加载到jvm中后才能运行,jvm会将编译生成的字节码.class文件加载到内存中,组织成一个完整的java应用程序,这个过程...
    一、什么是类加载器
    • 类加载器ClassLoader就是将我们的.class文件转换成Class对象。类Class只有被加载到jvm中后才能运行,jvm会将编译生成的字节码.class文件加载到内存中,组织成一个完整的java应用程序,这个过程是由类加载器ClassLoader和它的子类来完成的。
    二、jvm中的类加载器

    jvm中有三个类加载器:

    • 1、引导类加载器:BootStrap,它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader
    • 2、扩展类加载器:ExtClassLoader,它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
    • 3、应用类加载器:AppClassLoader,它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的
      可以通过类名.class.getClassLoader();获取类加载器
      例如:
    ClassLoader classLoader = AESCipher.class.getClassLoader();
    		
    System.out.println(classLoader);//结果:sun.misc.Launcher$ExtClassLoader@1c78e57
    
    ClassLoader classLoader = Demo1.class.getClassLoader();
    		
    System.out.println(classLoader);//结果:sun.misc.Launcher$AppClassLoader@6b97fd
    
    三、类加载器的机制

    类加载器的的机制是全盘负责委托机制

    • 全盘负责:
      只要有一个类加载器对当前的.class文件进行了转换Class对象的过程,那么关于这个类所有类都是由这个类加载器负责加载。
    • 委托机制:
      类加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载这个类,是从父类加载器开始,如果父类加载器可以处理,子类就不处理;如果父类加载器不能处理,就由子类来处理加载。
    四、类加载器的过程
    • 1、类的加载阶段是将class文件中的二进制数据读取到内存中,然后将该字节流所代表的静态存储结构转化为方法区中运行时的数据结构,并且在堆内存中生成该类的java.lang.class对象,作为方法区数据结构的入口。类加载器的最终产物是堆内存中的class对象,类加载器提供cache机制,就是说如果.class文件已经加载为Class对象,cache中已经存在就不会在生成,直接从cache中获取。
    • 2、加载过程:
      (1)验证:
      验证的主要目的是确保class文件的字节流所包含的内容符合jvm规范,并且不会出现危害jvm自身安全的代码;
      (2)准备:
      准备阶段主要做的事情是在方法区为静态变量配备内存已经赋值默认初始值;
      (3)解析:
      解析就是在常量池中寻找类、接口、字段和方法的符号引用,并将这些符号引用替换成直接引用的过程;
    展开全文
  • wps加载demo

    2021-02-05 09:56:22
    wps加载项的官方demo下载 点击此链接:wps加载项OA助手官方demo 下载项目压缩包,并解压。 解压后得到以下文件: 前端关注oaassist文件夹,进入后得到几个文件夹: EtOAAssist WPS 表格组件的OA助手WPS加载项,...
  • 在许多Java面试中,我们经常会看到关于Java类加载机制的考察,例如下面这道题:class Grandpa{static{System.out.println("爷爷在静态代码块");}}class Father extends Grandpa{static{System.out.println("爸爸在...
  • .NET Lazy懒加载Demo(技术拓展) 最新在朋友那里看到有问这个Lazy是干什么用的,深思熟虑以后决定写一篇记录博客,记录以往的学习成果- 概念:延时加载(延时实例化或延时初始化)重点是延时,用时加载。意思是对象在...
  • 类加载阶段中的“通过一个的全限定名(博主注:绝对路径)来获取描述此类的二进制字节流”这个动作放在Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的。实现这个动作的代码模块成为”类加载器...
  • AB包加载Demo

    2021-12-16 18:05:26
    简单的异步AB加载框架 简述打包流程 设置资源的BundleName以及BundleVariant 打包AssetBundle (将AB包上传到服务端,若放在本地则可跳过) 加载AssetBundle 实例化AssetBundle中的资源 AssetBundle的卸载 着重...
  • Java类加载机制

    2020-12-22 23:42:38
    1 加载 加载是指将的.class文件读取进内存中,并将其放在JVM运行时数据区的方法区内,然后在堆中创建一个java.lang.Class对象,用于封装在方法区中的数据结构,同时作为方法区数据的访问入口。 2 的...
  • Java 类加载

    2021-09-26 18:02:04
    类加载器 ClassLoader:在Java 内存模型我们介绍了 Java 字节码文件(.class)的格式。一个完整的 Java 程序是由多个 .class 文件组成的,在程序运行过程中,需要将这些 .class 文件加载到 JVM 中才可以使用。而...
  • Java 的类加载机制

    2021-02-28 11:59:36
    在聊 Java 类加载机制之前,需要先了解一下 Java 字节码,因为它和类加载机制息息相关。 计算机只认识 0 和 1,所以任何语言编写的程序都需要编译成机器码才能被计算机理解,然后执行,Java 也不例外。 Java 在...
  • package com.example.demo;import java.io.File;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.autoc...
  • 类加载器环境下使用synchronized的注意事项

    千次阅读 多人点赞 2021-03-31 01:52:22
    } } }   然后分别使用两个类加载器来加载JavaMain这个,创建两个实例对象,run方法还是不变,使用this.getClass()进行加锁: public class JavaMain { public void run() { synchronized (this.getClass()) { ...
  • 因为有不同版本、不同数据库的驱动jar,因此不可能将所有的驱动jar都放在一起,因此使用了动态加载jar的方式来隔离不同的驱动jar。 初版设计 首先设计了所有数据源共同使用的接口,放在web模块,接口如下: public ...
  • 加载、连接和初始化1.1 加载1.2 的连接1.3 的初始化1.3.1 会触发的初始化操作1.3.2 不会触发的初始化操作2.详解类加载器(面试常问)2.1 Java类加载器2.2 Java中类加载器的双亲委托模式总结 在学习...
  • 该楼层疑似违规已被系统折叠隐藏此楼查看此楼Java类加载机制jvm把class文件加载到内存,并对数据进行校验、解析和初始化,终形成jvm可以直 接使用的java类型的过程。1、加载:将class文件字节码内容加载到内存中,并...
  • java在何时被加载

    千次阅读 2021-03-05 12:43:08
    我们接着上一章的代码继续来了解一下java是在什么时候加载的。在开始验证之前,我们现在IDEA做如下配置。-XX:+TraceClassLoading 监控加载我们新建了一个TestController 来测试加载时机,代码如下:@...
  • 前几天跟同事聊怎么用自定义类加载加载java.lang.String的问题,正好又遇到一个类加载器的问题,决定花点时间研究一下 在查看源码研究的过程中,我发现很多人都有个误区:双亲委派机制不能被打破,不能使用自定义...
  • 目录前言一、双亲委派1.1 类加载器结构1.2 双亲委派二、使用步骤1.引入库2.读入数据总结 前言   在深入openjdk源码全面理解Java类加载器(上 – JVM源码篇) 我们分析了JVM是如何启动,并且初始化...
  • JVM结构及加载过程

    2020-12-30 14:43:26
    Java的每个,在JVM中,都有一个对应的Klass实例与之对应,存储的元信息如:常量池、属性信息、方法信息。 jvm中结构 klass InstanceKlass java(非数组)普通的Java在JVM中对应的是instanceKlass...
  • 自定义类加载器如下: public class MyClassLoaderToJar extends ClassLoader{ URLClassPath ucp; public MyClassLoaderToJar(URL[] urls) { // 初始化一个资源工具,可加载jar包和文件夹中 ucp = new ...
  • java默认的类加载器有三个,分别是引导类加载器、扩展类加载器以及应用类加载器。其分别加载内容如下所示: 引导类加载器: 加载jre下lib文件夹中的核心。 扩展类加载器: 加载jre下lib文件夹中ext扩展目录...
  • 点击此链接:wps加载项OA助手官方demo 下载项目压缩包,并解压。 解压后得到以下文件: 前端关注oaassist文件夹,进入后得到几个文件夹: EtOAAssist WPS 表格组件的OA助手WPS加载项,提供简单的OA场景功能示例。...
  • 当我们需要加载一个的时候,比如如下几种情况new一个的对象调用的静态成员(除了final常量)和静态方法使用java.lang.reflect包的方法对进行反射调用当虚拟机启动,java Demo01,则一定会初始化Demo01加载...
  • 9、类加载

    2021-09-18 16:07:55
    类加载器 用来加载.class文件 可以将本地磁盘上的字节码文件加载进JVM的方法区,生成字节码文件对象 分类 引导类加载器(根类加载器、启动类加载器,BootstrapClassLoader) 负责加载的区域是jdk中的jre中lib中rt....
  • package demo;import demo.Enclosingone;import demo.Enclosingone.InsideOne;import demo.Enclosingone.InsideOne2;class Enclosingone{static final int a=100;static {System.out.println("Enclosingone.enclosin...
  • 错误: 找不到或无法加载主类 Demo01.class 解决办法: 把后缀名.class去掉 如果文件来自于某个包,例如 package xxx.xxx; 这个时候需要退换目录到包外执行,并带上包名。 比如:我的demo01放在com.ccl包下,运行...
  • JDK源码JVM类加载机制

    千次阅读 热门讨论 2021-03-31 14:22:56
    首先我们的java小程序demo,经过编译后变成.class文件,他是如何加载到内存的将.class文件 内存中有两大对象:1.的字节码对象,只有一份在内存。2.对象会有多份 文章目录JVM类加载机制前言一、类加载运行全...
  • JVM学习-类加载

    千次阅读 2021-02-03 18:40:33
    1.加载的字节码载入方法区(1.8后为元空间,在本地内存中)中,内部采用 C++ 的 instanceKlass 描述 java ,它的重要 field 有: _java_mirror 即 java 的镜像,例如对 String 来说,它的镜像就是 String...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 323,214
精华内容 129,285
关键字:

类加载demo