java中类加载顺序(深入Java)
未涉及虚拟机
引子
记得上次中秋一哥们写个需求,没写完。他中秋过后还请一天假,有点错,打电话叫我帮他继续搞。
由于测试支撑,叫到我加班了。第二天过来看,打开页面直接报错,再次点击报错就不一样了。前次报错是有代码行的,第二次直接页面说类发现什么的错。
看了下代码,类似如下:
package san; import java.io.FileNotFoundException;
import java.util.logging.Level;
import java.util.logging.Logger; import javax.xml.bind.JAXBContext;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement; //每个类有一个Log4j的静态日志成员
//这里是单例
public class ClassWithStatic {
private static ClassWithStatic instance = new ClassWithStatic();
private static Logger logger = Logger.getLogger(ClassWithStatic.class.getName()); private ClassWithStatic() {
JAXBContext jc;
try {
//do something that maybe throw IOExption;
throw new FileNotFoundException();
} catch (Exception e) {
logger.log(Level.ALL, "xxx", e);
}
} /**
* @return the instance
*/
public static ClassWithStatic getInstance() {
return instance;
} public void doSomeThing() {
System.out.println("doSomeThing");
} public static void main(String[] args) {
ClassWithStatic.getInstance().doSomeThing();
}
} @XmlRootElement(name = "Scenes")
class Scenes{
@XmlElement(name = "id", required = true)
protected String id; /**
* @return the id
*/
public String getId() {
return id;
} /**
* @param id the id to set
*/
public void setId(String id) {
this.id = id;
} }
报错
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.NullPointerException
at san.ClassWithStatic.<init>(ClassWithStatic.java:21)
at san.ClassWithStatic.<clinit>(ClassWithStatic.java:12)
这是和静态成员初始化顺序有关。
基础知识如下:
Java代码中一个类初始化顺序:static变量 -- 其他成员变量 -- 构造函数 三者的调用先后顺序:
初始化父类Static --> 子类的Static (如果是类实例化,接下来还会: 初始化父类的其他成员变量->父类构造方法->子类其他成员变量->子类的构造方法)。
系统默认值的给予比通过等号的赋予先执行。
一个类中的static变量或成员变量的初始化顺序,是按照声明的顺序初始化的。
测试类
public class ClassWithStatic extends Super{
private static int iTest0 = 0;
private static ClassWithStatic instance = new ClassWithStatic();
private static int iTest1;
private static int iTest2 = 0;
static {
System.out.println(ClassWithStatic.class.getName() + " : static{}");
iTest0++;
iTest1++;
iTest2++;
}
public ClassWithStatic() {
System.out.println(this.getClass().getName() + " : Constuctor.");
iTest0++;
iTest1++;
iTest2++;
}
/**
* @return the instance
*/
public static ClassWithStatic getInstance() {
return instance;
}
public void doSomeThing() {
System.out.println("iTest0 = " + iTest0);
System.out.println("iTest1 = " + iTest1);
System.out.println("iTest2 = " + iTest2);
}
public static void main(String[] args) {
//private static void main(String[] args)
//Error: Main method not found in class san.ClassWithStatic, please define the main method as:
// public static void main(String[] args)
// or a JavaFX application class must extend javafx.application.Application
System.out.println("public static void main(String[] args)");
ClassWithStatic.getInstance().doSomeThing();
}
}
class Super {
private static Super instance = new Super();
private static int iTest1;
private int iTest2 = 0;
static {
System.out.println(Super.class.getName() + " : static{}");
iTest1++;
}
public Super() {
System.out.println(this.getClass().getName() + " : Constuctor.");
iTest2++;
}
}
结果:
san.Super : Constuctor.
san.Super : static{}
san.ClassWithStatic : Constuctor.
san.ClassWithStatic : Constuctor.
san.ClassWithStatic : static{}
public static void main(String[] args)
iTest0 = 2
iTest1 = 2
iTest2 = 1
这里两遍子类构造,为了区分,改一下构造函数里的打印语句代码。
public class ClassWithStatic extends Super{
private static int iTest0 = 0;
private static ClassWithStatic instance = new ClassWithStatic();
private static int iTest1;
private static int iTest2 = 0;
static {
System.out.println(ClassWithStatic.class.getName() + " : static{}");
iTest0++;
iTest1++;
iTest2++;
}
public ClassWithStatic() {
System.out.println(ClassWithStatic.class.getName() + " : Constuctor with this = " + this);
iTest0++;
iTest1++;
iTest2++;
}
/**
* @return the instance
*/
public static ClassWithStatic getInstance() {
return instance;
}
public void doSomeThing() {
System.out.println("iTest0 = " + iTest0);
System.out.println("iTest1 = " + iTest1);
System.out.println("iTest2 = " + iTest2);
}
public static void main(String[] args) {
//private static void main(String[] args)
//Error: Main method not found in class san.ClassWithStatic, please define the main method as:
// public static void main(String[] args)
// or a JavaFX application class must extend javafx.application.Application
System.out.println("public static void main(String[] args)");
ClassWithStatic.getInstance().doSomeThing();
}
}
class Super {
private static Super instance = new Super();
private static int iTest1;
private int iTest2 = 0;
static {
System.out.println(Super.class.getName() + " : static{}");
iTest1++;
}
public Super() {
System.out.println(Super.class.getName() + " : Constuctor with this = " + this);
iTest2++;
}
}
结果:
san.Super : Constuctor with this = san.Super@15db9742
san.Super : static{}
san.Super : Constuctor with this = san.ClassWithStatic@6d06d69c
san.ClassWithStatic : Constuctor with this = san.ClassWithStatic@6d06d69c
san.ClassWithStatic : static{}
public static void main(String[] args)
iTest0 = 2
iTest1 = 2
iTest2 = 1
public class ClassWithStatic extends Super {
protected static int iTest0 = Super.iTest0 + 1;
private static ClassWithStatic instance = new ClassWithStatic();
protected static int iTest1;
private static int iTest2 = 0;
static {
System.out.println(ClassWithStatic.class.getName() + " : static{}");
iTest1++;
iTest2++;
}
public ClassWithStatic() {
System.out.println(ClassWithStatic.class.getName() + " : Constuctor with this = " + this);
iTest1++;
iTest2++;
}
/**
* @return the instance
*/
public static ClassWithStatic getInstance() {
return instance;
}
public void doSomeThing() {
System.out.println("ClassWithStatic.iTest0 = " + iTest0);
System.out.println("ClassWithStatic.iTest1 = " + iTest1);
System.out.println("ClassWithStatic.iTest2 = " + iTest2);
}
public static void main(String[] args) {
//private static void main(String[] args)
//Error: Main method not found in class san.ClassWithStatic, please define the main method as:
// public static void main(String[] args)
// or a JavaFX application class must extend javafx.application.Application
System.out.println("public static void main(String[] args)");
ClassWithStatic.getInstance().doSomeThing();
System.out.println("Super.iTest0 = " + Super.iTest0);
System.out.println(Const.constanceString);//对类的静态变量进行读取、赋值操作的。static,final且值确定是常量,是编译时确定的,调用的时候直接用,不会加载对应的类
System.out.println("------------------------");
Const.doStaticSomeThing();
}
}
class Super {
protected static int iTest0;
private static Super instance = new Super();
protected static int iTest1 = 0;
static {
System.out.println(Super.class.getName() + " : static{}");
iTest0 = ClassWithStatic.iTest0 + 1;//
}
public Super() {
System.out.println(Super.class.getName() + " : Constuctor with this = " + this + ", iTest0 = " + iTest0);
iTest1++;
}
}
class Const {
public static final String constanceString = "Const.constanceString";
static {
System.out.println(Const.class.getName() + " : static{}");
}
public static void doStaticSomeThing() {
System.out.println(Const.class.getName() + " : doStaticSomeThing();");
}
}
san.Super : Constuctor with this = san.Super@15db9742, iTest0 = 0
san.Super : static{}
san.Super : Constuctor with this = san.ClassWithStatic@6d06d69c, iTest0 = 1
san.ClassWithStatic : Constuctor with this = san.ClassWithStatic@6d06d69c
san.ClassWithStatic : static{}
public static void main(String[] args)
ClassWithStatic.iTest0 = 2
ClassWithStatic.iTest1 = 2
ClassWithStatic.iTest2 = 1
Super.iTest0 = 1
Const.constanceString
------------------------
san.Const : static{}
san.Const : doStaticSomeThing();
0、<init>与<clinit>的区别
其实:
在编译生成class文件时,会自动产生两个方法,一个是类的初始化方法<clinit>, 另一个是实例的初始化方法<init>
<clinit>:在jvm第一次加载class文件时调用,包括静态变量初始化语句和静态块的执行
<init>:在实例创建出来的时候调用,包括调用new操作符;调用Class或java.lang.reflect.Constructor对象的newInstance()方法;调用任何现有对象的clone()方法;通过java.io.ObjectInputStream类的getObject()方法反序列化。
简要说明下final、static、static final修饰的字段赋值的区别:
- static修饰的字段在类加载过程中的准备阶段被初始化为0或null等默认值,而后在初始化阶段(触发类构造器<clinit>)才会被赋予代码中设定的值,如果没有设定值,那么它的值就为默认值。
- final修饰的字段在运行时被初始化(可以直接赋值,也可以在实例构造器中赋值),一旦赋值便不可更改;
- static final修饰的字段在Javac时生成ConstantValue属性,在类加载的准备阶段根据ConstantValue的值为该字段赋值,它没有默认值,必须显式地赋值,否则Javac时会报错。可以理解为在编译期即把结果放入了常量池中。
1、类的加载过程
类加载的时机就很简单了:在用到的时候就加载(和系统内存管理差不多,一个进程都是写时复制CopyOnWrite)。下来看一下类加载的过程:
加载->验证->准备->解析->初始化->使用->卸载

所有的Java虚拟机实现必须在每个类或接口被Java程序 “首次主动使用”时才初始化他们。
有必要提一点的是:准备和初始化
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:
1、这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。
2、这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。
假设一个类变量的定义为:
public static int value = 3;
那么变量value在准备阶段过后的初始值为0,而不是3,因为这时候尚未开始执行任何Java方法,而把value赋值为3的putstatic指令是在程序编译后,存放于类构造器<clinit>()方法之中的,所以把value赋值为3的动作将在初始化阶段才会执行。
详细查看:【深入Java虚拟机】之四:类加载机制 http://blog.csdn.net/ns_code/article/details/17881581
2、类的使用方式
Java 程序对类的使用方式可分为两种 :
主动 |
创建类的实例 |
遇到 |
Test a = new Test(); |
访问某个类或接口的非编译期静态变量, |
读写某个类的静态变量 |
||
调用类的静态方法 |
调用某个类的静态方法 Test.doSomething(); |
||
反射 |
Class.forName("xxxx")
|
||
初始化一个类的子类(不是对父类的主动使用就初始化子类, |
Child.class、Parent.class,初始化Child时, |
||
Java虚拟机启动时被标明为启动类的类 |
就是main方法那个所在类 |
||
被动 |
除了以上六种情况,其他使用Java类的方式都被看作是对 |
||
•主动使用(六种)
– 创建类的实例 -------Test a = new Test(); – 访问某个类或接口的非编译期静态变量,或者对该非编译期静态变量赋值 -------读写某个类的静态变量 int b = a.staticVariable;或a.staticVariable=b; – 调用类的静态方法 -------调用某个类的静态方法 Test.doSomething(); – 反射(如 Class.forName (“ com.shengsiyuan.Test ”) ) -------比如Class.forName("xxxx"); – 初始化一个类的子类(不是对父类的主动使用就初始化子类,这样的话生成一个Object类,那岂不是每个类都要初始化) -------Child.class、Parent.class,初始化Child时,就是对Parent的主动使用,先初始化父类 – Java虚拟机启动时被标明为启动类的类( Java Test ) -------就是main方法那个所在类
•被动使用 除了以上六种情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化
前3个可以归一类:遇到new、getstatic、putstatic、invokestatic这四条字节码指令时,如果类还没有进行过初始化,则需要先触发其初始化。
主要说下开始:当jvm启动时,用户需要指定一个要执行的主类(包含static void main(String[] args)的那个类),则jvm会先去初始化这个类。 友链:【深入Java虚拟机】之三:类初始化 http://blog.csdn.net/ns_code/article/details/17845821
3、类的加载来源
• 类的加载指的是将类的 .class 文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class 对象,用来封装类在方法区内的数据结构 • 加载 .class 文件的方式 – 从本地系统中直接加载 – 通过网络下载 .class 文件(URLClassLoader) – 从 zip、jar 等归档文件中加载 .class 文件 – 从专有数据库中提取 .class 文件 – 将 Java源文件动态编译为 .class 文件 4、重载之泛型参数不同可以吗
泛型编译后是一样的类型。
java5后新特性方便了书写,字面误导,需要深刻了解一下class。
//返回值参数重载吗
public class Overload {
public int method(List<String> ls){
return 0;
}
/**
* Method method(List<Integer>) has the same erasure method(List<E>) as another method in type Demo.Overload
*/
public boolean method(List<Integer> li){
return false;
}
/**
* 会有问题吗,编译后是什么样的呢。。。
*/
public int method(Integer s){
return s;
}
public int method(String i){
return 0;
}
}
重载条件:
在Java语言中,要重载一个方法,除了要与原方法具有相同的简单名称外,还要求必须拥有一个与原方法不同的(不包含返回值的)特征签名,特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,也就是因为返回值不会包含在特征签名之中,因此Java语言里无法仅仅依靠返回值的不同来对一个已有方法进行重载。
如果进行过Android的native编程,便会非常熟悉这种签名,因为Android源码中可以看到。
用方法描述符描述方法时,按照先参数后返回值的顺序描述,参数要按照严格的顺序放在一组小括号内,如方法 int getIndex(String name,char[] tgc,int start,int end,char target)的描述符为“(Ljava/lang/String[CIIC)I”。
重载调用选择:
另外,根据参数选择重载方法时,是静态时需要确定的,即编译后就确定。
参考:http://blog.csdn.net/ns_code/article/details/17965867
public class Demo {
/**
* I am human
* I am human
*/
public static void main(String[] args){
Demo sp = new Demo();
Human man = sp.new Man();
Human woman = sp.new Woman();
sp.say(man);
sp.say(woman);
}
class Human{
}
class Man extends Human{
}
class Woman extends Human{
}
public void say(Human hum){
System.out.println("I am human");
}
public void say(Man hum){
System.out.println("I am man");
}
public void say(Woman hum){
System.out.println("I am woman");
}
}
5、参考:
http://www.cnblogs.com/tianchi/archive/2012/11/11/2761631.htmlhttp://www.cnblogs.com/o-andy-o/archive/2013/06/06/3120298.htmlhttp://blog.csdn.net/ns_code/article/details/17675609
java中类加载顺序(深入Java)的更多相关文章
- [转]JAVA程序执行顺序,你了解了吗:JAVA中执行顺序,JAVA中赋值顺序
本文主要介绍以下两块内容的执行顺序,熟悉的大虾可以直接飘过. 一.JAVA中执行顺序 静态块 块 构造器 父类构造器 二.JAVA中赋值顺序 静态块直接赋值 块直接赋值 父类继承的属性已赋值 静态变量 ...
- ClassLoader Java中类加载出现在哪个阶段,编译期和运行期? 类加载和类装载是一样的吗
1.ClassLoader Java中类加载出现在哪个阶段,编译期和运行期? 类加载和类装载是一样的吗? :当然是运行期间啊,我自己有个理解误区,改正后如下:编译期间编译器是不去加载类的,只负责编译而 ...
- Java 的类加载顺序
Java 的类加载顺序 一.加载顺序:先父类后子类,先静态后普通 1.父类的静态成员变量初始化 2.父类的静态代码块 3.子类的静态成员变量初始化 4.子类的静态代码块 5.父类的普通成员变量初始化 ...
- java 中类加载器
jar 运行过程和类加载机制有关,而类加载机制又和我们自定义的类加载器有关,现在我们先来了解一下双亲委派模式. java 中类加载器分为三个: BootstrapClassLoader 负责加载 ${ ...
- 第七节:详细讲解Java中的日期,java.util.date
前言 大家好,给大家带来详细讲解Java中的日期,java.util.date的概述,希望你们喜欢 类Date Java.lang.Object->java.util.Date public c ...
- 在java中使用JMH(Java Microbenchmark Harness)做性能测试
文章目录 使用JMH做性能测试 BenchmarkMode Fork和Warmup State和Scope 在java中使用JMH(Java Microbenchmark Harness)做性能测试 ...
- Java中类加载和反射技术实例
我们知道一个对象在运行时有两种类型,一个是编译类型,一个是运行时类型.在程序运行时,往往是需要发现类和对象的真实的信息的.那么如何获的这种信息呢? 其一,如果我们在编译和运行时都知道类型的具体信息,这 ...
- java中类加载的全过程及内存图分析
类加载机制: jvm把class文件加载到内存,并对数据进行校验.解析和初始化,最终形成jvm可以直接使用的java类型的过程. (1)加载 将class文件字节码内容加载到内存中,并将这些静态数据转 ...
- 【Java】Java中的Collections类——Java中升级版的数据结构【转】
一般来说课本上的数据结构包括数组.单链表.堆栈.树.图.我这里所指的数据结构,是一个怎么表示一个对象的问题,有时候,单单一个变量声明不堪大用,比如int,String,double甚至一维数组.二维数 ...
随机推荐
- 获取class
使用原生JavaScript,获取类操作符时:即使使用getElementByClassName,在Firefox和IE9以下是不兼容的.Firefox下是可以用它获取的到元素而IE不行,一般框架都会 ...
- Core Animation 动画
Core Animation框架 Core Animation可以作用与动画视图或者其他可视元素,为你完成了动画所需的大部分绘帧工作.你只需要配置少量的动画参数(如开始点的位置和结束点的位置)即可使用 ...
- Leetcode 334.递增的三元子序列
递增的三元子序列 给定一个未排序的数组,判断这个数组中是否存在长度为 3 的递增子序列. 数学表达式如下: 如果存在这样的 i, j, k, 且满足 0 ≤ i < j < k ≤ n- ...
- excel设置单元格为文本
可以使用分裂功能,解决单元格无法设置成文本的问题.
- redis介绍和安装(一)
Redis介绍:redis是一个key-value存储系统. 和Memcached类似,它支持存储的value类型相对更多,包括 string(字符串). list(链表).set(集合).zset( ...
- JSP表单提交中文乱码
简要笔记:由于jsp默认表单提交编码方式是:ISO-8859-1,而我们需要的是utf-8或者是gbk码,故需要转化. 具体方法是:在表单处理文件中,将获取到的变量进行转换. String userN ...
- hdu 4965 矩阵快速幂 矩阵相乘性质
Fast Matrix Calculation Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 131072/131072 K (Jav ...
- Codeforces Round #278 (Div. 2) B. Candy Boxes [brute force+constructive algorithms]
哎,最近弱爆了,,,不过这题还是不错滴~~ 要考虑完整各种情况 8795058 2014-11-22 06:52:58 njczy2010 B - Ca ...
- Tengine的concat模块与js、css合并
首先,先走出一个误区 ,下面是tengine-cn邮件列表里的一篇邮件原文:“看了这个例子就了解了,这个所谓的合并请求只是把所有的CSS或JAVASCRIPT请求合并,必须是同一个文件类型的.我开始想 ...
- 使用Reachability检测网格
#pragma mark - 网络连接检查 - (void) currentReach { // 网络检测 Reachability *curReach = [Reachability reacha ...