我们知道java运行的是这样的,首先java编译器将我们的源代码编译成为字节码,然后由JVM将字节码load到内存中,接着我们的程序就可以创建对象了,我们知道JVM将字节码load到内存之后将将建立内存模型(JVM的内存模型我们将在稍后阐述),那JVM是怎么将类load到内存中的呢?对了,是通过Classloader,今天我们就来深入探讨一下Classloader。
首先我们来看一段诡异的代码(一段单实例测试代码)。
package com.yhj.jvm.classloader;
/**
* @Description:单例初始化探究
* @Author YHJ create at 2011-6-4 下午08:31:19
* @FileName com.yhj.jvm.classloader.ClassLoaderTest.java
*/
class Singleton{
private static Singleton singleton=new Singleton();
private static int counter1;
private static int counter2 = 0;
public Singleton() {
counter1++;
counter2++;
}
public static int getCounter1() {
return counter1;
}
public static int getCounter2() {
return counter2;
}
/**
* @Description:实例化
* @return
* @author YHJ create at 2011-6-4 下午08:34:43
*/
public static Singleton getInstance(){
return singleton;
}
}
/**
* @Description: 测试启动类
* @Author YHJ create at 2011-6-4 下午08:35:13
* @FileName com.yhj.jvm.classloader.ClassLoaderTest.java
*/
public class ClassLoaderTest {
/**
* @Description:启动类
* @param args
* @author YHJ create at 2011-6-4 下午08:30:12
*/
@SuppressWarnings("static-access")
public static void main(String[] args) {
Singleton singleton=Singleton.getInstance();
System.out.println("counter1:"+singleton.getCounter1());
System.out.println("counter2:"+singleton.getCounter2());
}
}
我们先猜测一下运行结果
然后我们再来调换一下单实例生成的顺序,将
private static Singleton singleton=new Singleton();
private static int counter1;
private static int counter2 = 0;
修改为
private static int counter1;
private static int counter2 = 0;
private static Singleton singleton=new Singleton();
再猜测一下结果,然后运行一下,看和你的猜测一致不?(是不是感觉很诡异)
好吧,我们先不看这段程序,先介绍相关的内容,等介绍完了你就明白这段诡异的代码为什么这么执行了!
我们知道我们运行刚才这段java程序是通过执行ClassLoaderTest的main函数引导起来的,而当我们执行完2个打印语句之后,JVM就停止了运行。这就是我们程序的生命周期。
在以下几种情况下JVM将结束自己的生命周期
1. 执行了System.exit()方法(具体可参见JDK的API文档)
2. 程序正常执行结束
3. 程序在执行过程中遇到了错误或异常而异常终止
4. 由于操作系统出现错误而导致JVM进程终止
类通过JVM的Classloader加载到内存经过以下几个步骤
加载 --> 连接 --> 初始化
?加载:查找并加载类的二进制数据
?连接
1. 验证:确保被加载的类的正确性
2. 准备:为类的静态变量分配内存,并将其初始化为默认值
3. 解析:把类中的符号引用转换为直接引用
?初始化:为类的静态变量赋予正确的初始值
我来分别解释一下这三个阶段都做了什么事情
1. 加载就是将二进制的字节码通过IO输入到JVM中,我们的字节码是存在于硬盘上面的,而所用的类都必须加载到内存中才能运行起来,加载就是通过IO把字节码从硬盘迁移到内存中。
2. 连接分为3个阶段,验证,准备和解析。
1) 验证这里可能大家会疑问了,我们的类不是通过JVM编译成的字节码的吗,为什么这里还要验证加载类的正确性,难道通过Java虚拟机的javac编译器生成的字节码还会有错误不成?当然,javac编译出来的类都是正确的,但是如果是通过其他途径生成的字节码呢?是不是正确的呢?就比如你自己建一个文本文件,然后重命名该文件为Test.class,然后让JVM来运行这个类,显然是错误的。当然因为JDK的源码是开放的,所以JVM字节码的生成规则也是公开的,所以也有一些第三方的软件可以生成符合JVM规范的字节码文件,如CGlib。
2) 准备:为类的静态变量分配内存,并将其初始化为默认值,这里我们一定要看清楚是为静态变量分配内存,而不是我们的实例变量,为什么我要强调静态变量,因为实例变量是什么时候产生的,是生成实例的时候产生的,而我们一般是在new一个对象的时候才对这个类进行实例化(前提是这个类已经被加载),而我们现在还没有加载完类,所以这个时候只能对静态变量分配内存空间(静态变量是属于这个类的而不属于某个对象),这个一定要分清楚。然后为该静态变量初始化为默认值(这个大家应该不陌生,int类型是0,boolean就是false,引用类型是null等)。
3) 解析:把类中的符号引用转换为直接引用,这个我们等下在讨论(后面我们会讲什么是符号引用,什么是直接引用)
3. 初始化:这个似乎与上面的初始化为默认值有点矛盾,我们再看一遍:为累的静态变量赋予正确的初始值,上面是赋予默认值,这里是赋予正确的初始值,什么是正确的初始值,就是用户给赋予的值。我们来看一个例子
class Test{
private static int a = 1;
}
我们知道,这个类加载好之后,a的值就是1,但实际是这样子的,类在加载的连接阶段,将a初始化为默认值0(int的默认值是0),然后在初始化阶段将a的值赋予为正确的初始值1. 我们看到最终a的值是等于1,但是实际的运行中是有一个将0赋予a的过程,这个过程放生在连接的准备阶段。类的初始化还有另外的一种形式,代码如下
class Test{
private static int a ;
static{
a=1;
}
}
这里强调一点,这个时候还是没有类的实例生成的,这点一定要注意!
《深入java虚拟机第二版》里面有一个图阐述了对应的关系,如下
Java
程序对类的使用方式可分为2种,主动使用和被动使用。所有的Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用时才初始化他们。”
主动使用(六种)
1) –创建类的实例 (如new Integer())
2) –访问某个类或接口的静态变量,或者对该静态变量赋值 (读写静态变量)
3) –调用类的静态方法
4) –反射
(如Class.forName(“com.yhj.jvm.classloader.ClassLoaderTest”))
5) –初始化一个类的子类 (初始化子类的过程中会主动使用父类的构造方法)
6) –Java虚拟机启动时被标明为启动类的类(含有main方法并且是启动方法的类)
除了以上六种情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化 (除了上述6种情况以外,都不会执行初始化,只会执行加载和连接)
好了,讲到这里我们大概知道类加载的几个步骤,那我们现在来详细的介绍一下类加载这个过程中的一些细节!
类的加载:累的加载是指将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区里面(具体的JVM内存模型我们会在后面讲到,这里可以参考下面JVM的内存模型图),然后在堆区创建一个java.lang.Class的对象,用于封装类在方法区内的数据结构!我们知道我们对于一个类可以创建很多个对象,但是这些对象共享同样的数据结构,而这个数据结构就是在加在过程中创建的这个class对象。我们可以通过 类名.class或者对象名.getClass()获取这个对象!无论创建了多少个实例对象,这个class的对象始终只有一个,类里面所有的结构都可以通过class对象获取,因此class对象就像一面镜子一样,可以反射一个类的内存结构,因此class是整个反射的入口!通过class对象我们可以反射的获取某个对象的数据结构,访问对应数据结构中的数据!
JVM
内存模型
《深入java虚拟机第二版》上面一个实例描述了一个类在加载过程中的内存模型,如下
加载
.class文件有几种方式
1. –从本地系统中直接加载 (直接加载本地硬盘上的.class文件加载)
2. –通过网络下载.class文件 (通过java.net.URLClassLoader加载网络上的某个.class文件)
3. –从zip,jar等归档文件中加载.class文件 (引入外部zip、jar包)
4. –从专有数据库中提取.class文件 (不常用)
5. –将Java源文件动态编译为.class文件 (动态代理)
首先我们来看一段诡异的代码(一段单实例测试代码)。
package com.yhj.jvm.classloader;
/**
* @Description:单例初始化探究
* @Author YHJ create at 2011-6-4 下午08:31:19
* @FileName com.yhj.jvm.classloader.ClassLoaderTest.java
*/
class Singleton{
private static Singleton singleton=new Singleton();
private static int counter1;
private static int counter2 = 0;
public Singleton() {
counter1++;
counter2++;
}
public static int getCounter1() {
return counter1;
}
public static int getCounter2() {
return counter2;
}
/**
* @Description:实例化
* @return
* @author YHJ create at 2011-6-4 下午08:34:43
*/
public static Singleton getInstance(){
return singleton;
}
}
/**
* @Description: 测试启动类
* @Author YHJ create at 2011-6-4 下午08:35:13
* @FileName com.yhj.jvm.classloader.ClassLoaderTest.java
*/
public class ClassLoaderTest {
/**
* @Description:启动类
* @param args
* @author YHJ create at 2011-6-4 下午08:30:12
*/
@SuppressWarnings("static-access")
public static void main(String[] args) {
Singleton singleton=Singleton.getInstance();
System.out.println("counter1:"+singleton.getCounter1());
System.out.println("counter2:"+singleton.getCounter2());
}
}
我们先猜测一下运行结果
然后我们再来调换一下单实例生成的顺序,将
private static Singleton singleton=new Singleton();
private static int counter1;
private static int counter2 = 0;
修改为
private static int counter1;
private static int counter2 = 0;
private static Singleton singleton=new Singleton();
再猜测一下结果,然后运行一下,看和你的猜测一致不?(是不是感觉很诡异)
好吧,我们先不看这段程序,先介绍相关的内容,等介绍完了你就明白这段诡异的代码为什么这么执行了!
我们知道我们运行刚才这段java程序是通过执行ClassLoaderTest的main函数引导起来的,而当我们执行完2个打印语句之后,JVM就停止了运行。这就是我们程序的生命周期。
在以下几种情况下JVM将结束自己的生命周期
1. 执行了System.exit()方法(具体可参见JDK的API文档)
2. 程序正常执行结束
3. 程序在执行过程中遇到了错误或异常而异常终止
4. 由于操作系统出现错误而导致JVM进程终止
类通过JVM的Classloader加载到内存经过以下几个步骤
加载 --> 连接 --> 初始化
?加载:查找并加载类的二进制数据
?连接
1. 验证:确保被加载的类的正确性
2. 准备:为类的静态变量分配内存,并将其初始化为默认值
3. 解析:把类中的符号引用转换为直接引用
?初始化:为类的静态变量赋予正确的初始值
我来分别解释一下这三个阶段都做了什么事情
1. 加载就是将二进制的字节码通过IO输入到JVM中,我们的字节码是存在于硬盘上面的,而所用的类都必须加载到内存中才能运行起来,加载就是通过IO把字节码从硬盘迁移到内存中。
2. 连接分为3个阶段,验证,准备和解析。
1) 验证这里可能大家会疑问了,我们的类不是通过JVM编译成的字节码的吗,为什么这里还要验证加载类的正确性,难道通过Java虚拟机的javac编译器生成的字节码还会有错误不成?当然,javac编译出来的类都是正确的,但是如果是通过其他途径生成的字节码呢?是不是正确的呢?就比如你自己建一个文本文件,然后重命名该文件为Test.class,然后让JVM来运行这个类,显然是错误的。当然因为JDK的源码是开放的,所以JVM字节码的生成规则也是公开的,所以也有一些第三方的软件可以生成符合JVM规范的字节码文件,如CGlib。
2) 准备:为类的静态变量分配内存,并将其初始化为默认值,这里我们一定要看清楚是为静态变量分配内存,而不是我们的实例变量,为什么我要强调静态变量,因为实例变量是什么时候产生的,是生成实例的时候产生的,而我们一般是在new一个对象的时候才对这个类进行实例化(前提是这个类已经被加载),而我们现在还没有加载完类,所以这个时候只能对静态变量分配内存空间(静态变量是属于这个类的而不属于某个对象),这个一定要分清楚。然后为该静态变量初始化为默认值(这个大家应该不陌生,int类型是0,boolean就是false,引用类型是null等)。
3) 解析:把类中的符号引用转换为直接引用,这个我们等下在讨论(后面我们会讲什么是符号引用,什么是直接引用)
3. 初始化:这个似乎与上面的初始化为默认值有点矛盾,我们再看一遍:为累的静态变量赋予正确的初始值,上面是赋予默认值,这里是赋予正确的初始值,什么是正确的初始值,就是用户给赋予的值。我们来看一个例子
class Test{
private static int a = 1;
}
我们知道,这个类加载好之后,a的值就是1,但实际是这样子的,类在加载的连接阶段,将a初始化为默认值0(int的默认值是0),然后在初始化阶段将a的值赋予为正确的初始值1. 我们看到最终a的值是等于1,但是实际的运行中是有一个将0赋予a的过程,这个过程放生在连接的准备阶段。类的初始化还有另外的一种形式,代码如下
class Test{
private static int a ;
static{
a=1;
}
}
这里强调一点,这个时候还是没有类的实例生成的,这点一定要注意!
《深入java虚拟机第二版》里面有一个图阐述了对应的关系,如下
Java
程序对类的使用方式可分为2种,主动使用和被动使用。所有的Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用时才初始化他们。”
主动使用(六种)
1) –创建类的实例 (如new Integer())
2) –访问某个类或接口的静态变量,或者对该静态变量赋值 (读写静态变量)
3) –调用类的静态方法
4) –反射
(如Class.forName(“com.yhj.jvm.classloader.ClassLoaderTest”))
5) –初始化一个类的子类 (初始化子类的过程中会主动使用父类的构造方法)
6) –Java虚拟机启动时被标明为启动类的类(含有main方法并且是启动方法的类)
除了以上六种情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化 (除了上述6种情况以外,都不会执行初始化,只会执行加载和连接)
好了,讲到这里我们大概知道类加载的几个步骤,那我们现在来详细的介绍一下类加载这个过程中的一些细节!
类的加载:累的加载是指将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区里面(具体的JVM内存模型我们会在后面讲到,这里可以参考下面JVM的内存模型图),然后在堆区创建一个java.lang.Class的对象,用于封装类在方法区内的数据结构!我们知道我们对于一个类可以创建很多个对象,但是这些对象共享同样的数据结构,而这个数据结构就是在加在过程中创建的这个class对象。我们可以通过 类名.class或者对象名.getClass()获取这个对象!无论创建了多少个实例对象,这个class的对象始终只有一个,类里面所有的结构都可以通过class对象获取,因此class对象就像一面镜子一样,可以反射一个类的内存结构,因此class是整个反射的入口!通过class对象我们可以反射的获取某个对象的数据结构,访问对应数据结构中的数据!
JVM
内存模型
《深入java虚拟机第二版》上面一个实例描述了一个类在加载过程中的内存模型,如下
加载
.class文件有几种方式
1. –从本地系统中直接加载 (直接加载本地硬盘上的.class文件加载)
2. –通过网络下载.class文件 (通过java.net.URLClassLoader加载网络上的某个.class文件)
3. –从zip,jar等归档文件中加载.class文件 (引入外部zip、jar包)
4. –从专有数据库中提取.class文件 (不常用)
5. –将Java源文件动态编译为.class文件 (动态代理)
发表评论
-
菜鸟 Spring 源码解读 推荐流程
2012-01-11 09:18 5128Spring源代码解析(一):IOC容器:http://www ... -
Java中连接字符串时是使用+号还是使用StringBuilder?
2011-12-26 14:04 923字符串是Java程序中最常用的一种数据结构之一。在Java中的 ... -
转一篇有关Java的内存泄露的文章(受益哦)
2011-07-20 09:28 7741 引言 Java的一个 ... -
Tomcat内存溢出的原因
2011-07-19 09:41 729Tomcat内存溢出的原因 在生产环境中tomcat内 ... -
深入研究java.lang.ThreadLocal类
2011-07-13 09:39 685一、概述 ThreadLocal是什么呢?其实Thread ... -
jboss中实现跨war包session同步
2011-06-12 23:28 1288跨war包session同步解决方 ... -
开源框架spring详解-----AOP的深刻理解
2011-05-26 22:13 1251开源框架spring详解-----AOP的深刻理解 AOP的 ... -
struts2核心工作流程与工作原理
2011-05-26 15:35 12891. Struts2架构图 这是S truts2官方站点提供的 ... -
Spring注入方式及用到的注解 -----@Component,@Service,@Controller,@Repository
2011-05-26 15:04 1226注入方式: 把DAO实现 ... -
Java中的native关键字浅析(Java Native Interface)
2011-05-21 23:13 740JNI是Java Native Interface的 ... -
Volatile 变量
2011-04-26 17:01 654Java 语言中的 volatile 变量可以被看作是一种 “ ... -
Java对象的强、软、弱和虚引用
2011-04-26 16:04 6271.Java对象的强、软、 ... -
Web 应用程序常见漏洞 CSRF 的入侵检测与防范
2011-04-23 15:00 1120简介: 互联网的安全问题一直存在,并且在可预见的未来中没有消弭 ... -
详解XSS跨站脚本攻击
2011-04-23 13:46 1142一、什么是XSS攻击 XSS ... -
CSRF攻击原理解析
2011-04-22 10:29 12820×00. 前言 在Web程序中 ... -
selenium 初步体检之富文本框操作
2011-04-20 20:10 1542public class LoginTest extends ... -
webx
2011-03-05 17:54 1017webx 学习笔记。 -
Java读带有BOM的UTF-8文件乱码解决方法
2011-03-02 11:12 2463Java default io reader does not ... -
java sftp tools
2011-02-24 13:30 1517import java.io.File; import jav ... -
HtmlUnit
2010-10-18 22:27 1658IntroductionThe dependencies pa ...
相关推荐
### Java ClassLoader 知识点解析 #### 一、引言 在《classloader教程 --- from ...总之,《classloader教程 --- from IBM》是一篇非常全面且实用的教程,对于希望深入了解Java类加载机制的开发者来说,绝对值得一读。
《XWiki Commons Classloader Protocol Jar 5.4与LaZyWorker开源项目解析》 XWiki Commons Classloader Protocol Jar 5.4是一个重要的组件,它属于XWiki开源项目的一部分。XWiki是一个强大的、可扩展的、面向企业的...
1. **继承`ClassLoader`类**:创建一个新的类,继承自`java.lang.ClassLoader`。 2. **重写`findClass()`方法**:实现具体的加载逻辑,读取类的二进制数据。 3. **调用`defineClass()`方法**:将读取到的二进制数据...
在“【IT十八掌徐培成】Java基础第25天-04.classLoader-系统资源-不可见类访问.zip”这个课程中,徐培成老师将深入讲解ClassLoader的工作原理以及如何处理系统资源,特别是关于不可见类访问的话题。 首先,...
《深入理解Java类加载器:基于classloader-playground开源项目》 在Java世界里,类加载器(ClassLoader)是理解JVM工作原理的关键一环。它负责将字节码文件(.class)从磁盘、网络或其他数据源加载到内存,并转化为...
>se.jiderhamn.classloader-leak-prevention</ groupId > < artifactId >classloader-leak-prevention-servlet3</ artifactId > < version >2.7.0</ version > </ dependency > 如果您遇到 ...
虚拟机规范定义了五种主动引用,其他情况则视为被动引用,不会立即触发初始化。 例如,通过子类引用父类的静态字段只会初始化父类,而不会初始化子类。同样,通过数组定义引用类不会触发类的初始化,但可能会触发类...
深入理解ClassLoader的工作原理对于优化应用性能、解决类加载问题以及实现自定义加载策略至关重要。 首先,JVM启动时,会构建一个类加载器的层次结构,主要包括三个基本类加载器: 1. Bootstrap ClassLoader:引导...
在"**classloader-study.zip**"中,你可能会看到如何创建自定义`ClassLoader`的代码示例,以及如何使用`URLClassLoader`从网络或其他位置加载类。此外,还可能包含关于如何管理类加载的生命周期,避免类加载冲突,...
轻量级ndk实用程序,可帮助绕过Android N的classloader-namespace限制ndk_dlopen轻量级ndk实用程序,可帮助绕过Android N的classloader-namespace限制技术OSR(堆栈替换)支持x86,x86_64,armeabi-v7a(拇指和手臂...
【标题】"restx-classloader-0.33.1.zip" 提供的是 Restx ClassLoader 的一个版本,这是 Restx 框架的一部分,主要负责处理类加载相关的任务。Restx 是一个轻量级、模块化的 Java web 应用框架,它允许开发者快速...
本文将深入探讨自定义Classloader的使用。 一、Classloader的工作原理 Java的类加载机制遵循双亲委派模型,即当一个类加载器需要加载类时,它首先委托父类加载器尝试加载,只有当父类加载器无法加载时,才会尝试...
【图解版】深入分析ClassLoader类加载工作机制,从原理到JVM的装载过程,详情分析了ClassLoader加载类以及自定义类加载器的过程,不可用于商业用途,如有版权问题,请联系删除!
本文将深入探讨ClassLoader的工作原理和类加载机制,帮助开发者理解这个至关重要的概念。 1. 类加载机制概述 Java的类加载机制遵循“双亲委派模型”(Delegation Model)。当一个类被加载时,它首先会尝试由当前...
Java程序对类的使用分为主动使用和被动使用。只有在六种特定情况下,类才会被初始化:创建类实例、访问或修改静态变量、调用静态方法、反射操作、初始化子类以及JVM启动时指定的启动类。其他情况被视为被动使用,...
根据提供的文件信息,本文将对《深入剖析 Tomcat》这一资料进行详细的知识点解析。Tomcat作为一款开源的Servlet容器,被广泛应用于Java Web应用程序的部署与运行环境中。本资料旨在帮助读者深入了解Tomcat的工作原理...
在Java编程语言中,ClassLoader是一个至关重要的组成部分,它负责加载类到JVM(Java虚拟机)中,使得程序能够执行。本示例"ClassLoader小例子"将深入探讨这个概念,并通过一个具体的程序来演示其工作原理。下面我们...