JAVA基础.docx
- 文档编号:12358465
- 上传时间:2023-04-18
- 格式:DOCX
- 页数:94
- 大小:255.99KB
JAVA基础.docx
《JAVA基础.docx》由会员分享,可在线阅读,更多相关《JAVA基础.docx(94页珍藏版)》请在冰豆网上搜索。
JAVA基础
1JAVA虚拟机
Classloader的作用,概括来说就是将编译后的class装载、加载到机器内存中,为了以后的程序的执行提供前提条件。
一段程序引发的思考:
风中叶老师在他的视频中给了我们一段程序,号称是世界上所有的Java程序员都会犯的错误。
诡异代码如下:
1.packagetest01;
2.classSingleton{
3.publicstaticSingletonsingleton=newSingleton();
4.publicstaticinta;
5.publicstaticintb=0;
6.privateSingleton(){
7.super();
8.a++;
9.b++;
10.}
11.publicstaticSingletonGetInstence(){
12.returnsingleton;
13.}
14.}
15.publicclassMyTest{
16./**
17.*@paramargs
18.*/
19.publicstaticvoidmain(String[]args){
20.Singletonmysingleton=Singleton.GetInstence();
21.System.out.println(mysingleton.a);
22.System.out.println(mysingleton.b);
23.}
24.}
一般不假思索的结论就是,a=1,b=1。
给出的原因是:
a、b都是静态变量,在构造函数调用的时候已经对a和b都加1了。
答案就都是1。
但是运行完后答案却是a=1,b=0。
下面我们将代码稍微变一下
publicstaticSingletonsingleton=newSingleton();
publicstaticinta;
publicstaticintb=0;
的代码部分替换成
publicstaticinta;
publicstaticintb=0;
publicstaticSingletonsingleton=newSingleton();
效果就是刚才预期的a=1,b=1。
为什么呢,这3句无非就是静态变量的声明、初始化,值的变化和声明的顺序还有关系吗?
Java不是面向对象的吗?
怎么和结构化的语言似地,顺序还有关系。
这个就是和Java虚拟机JVM加载类的原理有着直接的关系。
1.1类在JVM中的工作原理
要想使用一个Java类为自己工作,必须经过以下几个过程
1):
类加载load:
从字节码二进制文件——.class文件将类加载到内存,从而达到类的从硬盘上到内存上的一个迁移,所有的程序必须加载到内存才能工作。
将内存中的class放到运行时数据区的方法区内,之后在堆区建立一个java.lang.Class对象,用来封装方法区的数据结构。
这个时候就体现出了万事万物皆对象了,干什么事情都得有个对象。
就是到了最底层究竟是鸡生蛋,还是蛋生鸡呢?
类加载的最终产物就是堆中的一个java.lang.Class对象。
2):
连接:
连接又分为以下小步骤
验证:
出于安全性的考虑,验证内存中的字节码是否符合JVM的规范,类的结构规范、语义检查、字节码操作是否合法、这个是为了防止用户自己建立一个非法的XX.class文件就进行工作了,或者是JVM版本冲突的问题,比如在JDK6下面编译通过的class(其中包含注解特性的类),是不能在JDK1.4的JVM下运行的。
准备:
将类的静态变量进行分配内存空间、初始化默认值。
(对象还没生成呢,所以这个时候没有实例变量什么事情)
解析:
把类的符号引用转为直接引用(保留)
3):
类的初始化:
将类的静态变量赋予正确的初始值,这个初始值是开发者自己定义时赋予的初始值,而不是默认值。
1.2类的主动使用与被动使用
以下是视为主动使用一个类,其他情况均视为被动使用!
1):
初学者最为常用的new一个类的实例对象(声明不叫主动使用)
2):
对类的静态变量进行读取、赋值操作的。
3):
直接调用类的静态方法。
4):
反射调用一个类的方法。
5):
初始化一个类的子类的时候,父类也相当于被程序主动调用了(如果调用子类的静态变量是从父类继承过来并没有复写的,那么也就相当于只用到了父类的东东,和子类无关,所以这个时候子类不需要进行类初始化)。
6):
直接运行一个main函数入口的类。
所有的JVM实现(不同的厂商有不同的实现,有人就说IBM的实现比Sun的要好……)在首次主动调用类和接口的时候才会初始化他们。
1.3类的加载方式
1):
本地编译好的class中直接加载
2):
网络加载:
.URLClassLoader可以加载url指定的类
3):
从jar、zip等等压缩文件加载类,自动解析jar文件找到class文件去加载util类
4):
从java源代码文件动态编译成为class文件
1.4类加载器
JVM自带的默认加载器
1):
根类加载器:
bootstrap,由C++编写,所有Java程序无法获得。
2):
扩展类加载器:
由Java编写。
3):
系统类、应用类加载器:
由Java编写。
用户自定义的类加载器:
java.lang.ClassLoader的子类,用户可以定制类的加载方式。
每一个类都包含了加载他的ClassLoader的一个引用——getClass().getClassLoader()。
如果返回的是null,证明加载他的ClassLoader是根加载器bootstrap。
如下代码
publicstaticvoidmain(String[]args)throwsClassNotFoundException{
Classclazz=Class.forName("java.lang.String");
System.out.println(clazz.getClassLoader());
}
结果是null,证明java.lang.String是根类加载器去加载的。
publicstaticvoidmain(String[]args){
Singletonmysingleton=Singleton.GetInstence();
System.out.println(mysingleton.getClass().getClassLoader());
}
结果是sun.misc.Launcher$AppClassLoader@19821f,证明是AppClassLoader(系统类、应用类加载器)去加载的。
像jre的rt.jar下面的java.lang.*都是默认的根类加载器去加载这些运行时的类。
1.5解释类连接阶段的准备
类的如下代码片段
publicstaticinta;
publicstaticintb=10;
在这个阶段,加载器会按照结构化似的,从上到下流程将静态变量int类型分配4个字节的空间,并且为其赋予默认值0,而像b=10这段代码在此阶段是不起作用的,b仍然是默认值0。
1.6解释类连接阶段的解析
这里面的指针就是C++的指针
1.7回顾那个诡异的代码
从入口开始看
Singletonmysingleton=Singleton.GetInstence();
是根据内部类的静态方法要一个Singleton实例。
这个时候就属于主动调用Singleton类了。
之后内存开始加载Singleton类
1):
对Singleton的所有的静态变量分配空间,赋默认的值,所以在这个时候,singleton=null、a=0、b=0。
注意b的0是默认值,并不是咱们手工为其赋予的的那个0值。
2):
之后对静态变量赋值,这个时候的赋值就是我们在程序里手工初始化的那个值了。
此时singleton=newSingleton();调用了构造方法。
构造方法里面a=1、b=1。
之后接着顺序往下执行。
3):
publicstaticinta;
publicstaticintb=0;
a没有赋值,保持原状a=1。
b被赋值了,b原先的1值被覆盖了,b=0。
所以结果就是这么来的。
类中的静态块static块也是顺序地从上到下执行的。
1.8编译时常量、非编译时常量的静态变量
如下代码
1.packagetest01;
2.
3.classFinalStatic{
4.
5.publicstaticfinalintA=4+4;
6.
7.static{
8.System.out.println("如果执行了,证明类初始化了……");
9.}
10.
11.}
12.publicclassMyTest03{
13./**
14.*@paramargs
15.*/
16.publicstaticvoidmain(String[]args){
17.System.out.println(FinalStatic.A);
18.}
19.
20.}
结果是只打印出了8,证明类并没有初始化。
反编译源码发现class里面的内容是
publicstaticfinalintA=8;
也就是说编译器很智能的、在编译的时候自己就能算出4+4是8,是一个固定的数字。
没有什么未知的因素在里面。
将代码稍微改一下
publicstaticfinalintA=4+newRandom().nextInt(10);
这个时候静态块就执行了,证明类初始化了。
在静态final变量在编译时不定的情况下。
如果客户程序这个时候访问了该类的静态变量,那就会对类进行初始化,所以尽量静态final变量尽量没什么可变因素在里面1,否则性能会有所下降。
1.9ClassLoader的剖析
ClassLoader的loadClass方法加载一个类不属于主动调用,不会导致类的初始化。
如下代码块
ClassLoaderclassLoader=ClassLoader.getSystemClassLoader();
Class
>clazz=classLoader.loadClass("test01.ClassDemo");
并不会让类加载器初始化test01.ClassDemo,因为这不属于主动调用此类。
ClassLoader的关系:
根加载器——》扩展类加载器——》应用类加载器——》用户自定义类加载器
加载类的过程是首先从根加载器开始加载、根加载器加载不了的,由扩展类加载器加载,再加载不了的有应用加载器加载,应用加载器如果还加载不了就由自定义的加载器(一定继承自java.lang.ClassLoader)加载、如果自定义的加载器还加载不了。
而且下面已经没有再特殊的类加载器了,就会抛出ClassNotFoundException,表面上异常是类找不到,实际上是class加载失败,更不能创建该类的Class对象。
若一个类能在某一层类加载器成功加载,那么这一层的加载器称为定义类加载器。
那么在这层类生成的Class引用返回下一层加载器叫做初始类加载器。
因为加载成功后返回一个Class引用给它的服务对象——也就是调用它的类加载器。
考虑到安全,父委托加载机制。
ClassLoader加载类的原代码如下
21.protectedsynchronizedClass
>loadClass(Stringname,booleanresolve)
22.throwsClassNotFoundException
23.{
24.//First,checkiftheclasshasalreadybeenloaded
25.Classc=findLoadedClass(name);
26.if(c==null){
27.try{
28.if(parent!
=null){
29.c=parent.loadClass(name,false);
30.}else{
31.c=findBootstrapClassOrNull(name);
32.}
33.}catch(ClassNotFoundExceptione){
34.//ClassNotFoundExceptionthrownifclassnotfound
35.//fromthenon-nullparentclassloader
36.}
37.if(c==null){
38.//Ifstillnotfound,theninvokefindClassinorder
39.//tofindtheclass.
40.c=findClass(name);
41.}
42.}
43.if(resolve){
44.resolveClass(c);
45.}
46.returnc;
47.}
初始化系统ClassLoader代码如下
1.privatestaticsynchronizedvoidinitSystemClassLoader(){
2.if(!
sclSet){
3.if(scl!
=null)
4.thrownewIllegalStateException("recursiveinvocation");
5.sun.misc.Launcherl=sun.misc.Launcher.getLauncher();
6.if(l!
=null){
7.Throwableoops=null;
8.scl=l.getClassLoader();
9.try{
10.PrivilegedExceptionActiona;
11.a=newSystemClassLoaderAction(scl);
12.scl=(ClassLoader)AccessController.doPrivileged(a);
13.}catch(PrivilegedActionExceptionpae){
14.oops=pae.getCause();
15.if(oopsinstanceofInvocationTargetException){
16.oops=oops.getCause();
17.}
18.}
19.if(oops!
=null){
20.if(oopsinstanceofError){
21.throw(Error)oops;
22.}else{
23.//wraptheexception
24.thrownewError(oops);
25.}
26.}
27.}
28.sclSet=true;
29.}
30.}
它里面调用了很多native的方法,也就是通过JNI调用底层C++的代码。
当一个类被加载、连接、初始化后,它的生命周期就开始了,当代表该类的Class对象不再被引用、即已经不可触及的时候,Class对象的生命周期结束。
那么该类的方法区内的数据也会被卸载,从而结束该类的生命周期。
一个类的生命周期取决于它Class对象的生命周期。
由Java虚拟机自带的默认加载器(根加载器、扩展加载器、系统加载器)所加载的类在JVM生命周期中始终不被卸载。
所以这些类的Class对象(我称其为实例的模板对象)始终能被触及!
而由用户自定义的类加载器所加载的类会被卸载掉!
2JAVA类的生命周期
首先来了解一下jvm(java虚拟机)中的几个比较重要的内存区域,这几个区域在java类的生命周期中扮演着比较重要的角色:
●方法区:
在java的虚拟机中有一块专门用来存放已经加载的类信息、常量、静态变量以及方法代码的内存区域,叫做方法区。
●常量池:
常量池是方法区的一部分,主要用来存放常量和类中的符号引用等信息。
●堆区:
用于存放类的对象实例。
●栈区:
也叫java虚拟机栈,是由一个一个的栈帧组成的后进先出的栈式结构,栈桢中存放方法运行时产生的局部变量、方法出口等信息。
当调用一个方法时,虚拟机栈中就会创建一个栈帧存放这些数据,当方法调用完成时,栈帧消失,如果方法中调用了其他方法,则继续在栈顶创建新的栈桢。
除了以上四个内存区域之外,jvm中的运行时内存区域还包括本地方法栈和程序计数器,这两个区域与java类的生命周期关系不是很大,在这里就不说了,感兴趣的朋友可以自己XX一下。
2.1类的生命周期
当我们编写一个java的源文件后,经过编译会生成一个后缀名为class的文件,这种文件叫做字节码文件,只有这种字节码文件才能够在java虚拟机中运行,java类的生命周期就是指一个class文件从加载到卸载的全过程。
一个java类的完整的生命周期会经历加载、连接、初始化、使用、和卸载五个阶段,当然也有在加载或者连接之后没有被初始化就直接被使用的情况,如图所示:
下面我们就依次来说一说这五个阶段。
2.1.1加载
在java中,我们经常会接触到一个词——类加载,它和这里的加载并不是一回事,通常我们说类加载指的是类的生命周期中加载、连接、初始化三个阶段。
在加载阶段,java虚拟机会做什么工作呢?
其实很简单,就是找到需要加载的类并把类的信息加载到jvm的方法区中,然后在堆区中实例化一个java.lang.Class对象,作为方法区中这个类的信息的入口。
类的加载方式比较灵活,我们最常用的加载方式有两种,一种是根据类的全路径名找到相应的class文件,然后从class文件中读取文件内容;另一种是从jar文件中读取。
另外,还有下面几种方式也比较常用:
●从网络中获取:
比如10年前十分流行Applet。
●根据一定的规则实时生成,比如设计模式中的动态代理模式,就是根据相应的类自动生成它的代理类。
●从非class文件中获取,其实这与直接从class文件中获取的方式本质上是一样的,这些非class文件在jvm中运行之前会被转换为可被jvm所识别的字节码文件。
对于加载的时机,各个虚拟机的做法并不一样,但是有一个原则,就是当jvm“预期”到一个类将要被使用时,就会在使用它之前对这个类进行加载。
比如说,在一段代码中出现了一个类的名字,jvm在执行这段代码之前并不能确定这个类是否会被使用到,于是,有些jvm会在执行前就加载这个类,而有些则在真正需要用的时候才会去加载它,这取决于具体的jvm实现。
我们常用的hotspot虚拟机是采用的后者,就是说当真正用到一个类的时候才对它进行加载。
加载阶段是类的生命周期中的第一个阶段,加载阶段之后,是连接阶段。
有一点需要注意,就是有时连接阶段并不会等加载阶段完全完成之后才开始,而是交叉进行,可能一个类只加载了一部分之后,连接阶段就已经开始了。
但是这两个阶段总的开始时间和完成时间总是固定的:
加载阶段总是在连接阶段之前开始,连接阶段总是在加载阶段完成之后完成。
2.1.2连接
连接阶段比较复杂,一般会跟加载阶段和初始化阶段交叉进行,这个阶段的主要任务就是做一些加载后的验证工作以及一些初始化前的准备工作,可以细分为三个步骤:
验证、准备和解析。
1.验证:
当一个类被加载之后,必须要验证一下这个类是否合法,比如这个类是不是符合字节码的格式、变量与方法是不是有重复、数据类型是不是有效、继承与实现是否合乎标准等等。
总之,这个阶段的目的就是保证加载的类是能够被jvm所运行。
2.准备:
准备阶段的工作就是为类的静态变量分配内存并设为jvm默认的初值,对于非静态的变量,则不会为它们分配内存。
有一点需要注意,这时候,静态变量的初值为jvm默认的初值,而不是我们在程序中设定的初值。
jvm默认的初值是这样的:
●基本类型(int、long、short、char、byte、boolean、float、double)的默认值为0。
●引用类型的默认值为null。
●常量的默认值为我们程序中设定的值,比如我们在程序中定义finalstaticinta=100,则准备阶段中a的初值就是100。
3.解析:
这一阶段的任务就是把常量池中的符号引用转换为直接引用。
那么什么是符号引用,什么又是直接引用呢?
我们来举个例子:
我们要找一个人,我们现有的信息是这个人的身份证号是1234567890。
只有这个信息我们显然找不到这个人,但是通过公安局的身份系统,我们输入1234567890这个号之后,就会得到它的全部信息:
比如安徽省黄山市余暇村18号张三,通过这个信息我们就能找到这个人了。
这里,123456790就好比是一个符号引用,而安徽省黄山市余暇村18号张三就是直接引用。
在内存中也是一样,比如我们要在内存中找一个类里面的一个叫做show的方法,显然是找不到。
但是在解析阶段,jvm就会把show这个名字转换为指向方法区的的一块内存地址,比如c17164,通过c17164就可以找到show这个方法具体分配在内存的哪一个区域了。
这里show就是符号引用,而c17164就是直接引用。
在解析阶段,jvm会将所有的类或接口名、字段名、方法名转换为具体的内存地址。
连接阶段完成之后会根据使用的情况(是直接引用还是被动引用)来选择是否对类进行初始化。
2.1.3初始化
如果一个类被直接引用,就会触发类的初始化。
在java中,直接引用的情况有
●通过new关键字实例化对象、读取或设置类的静态变量、调用类的静态方法。
●通过反射方式执行以上三种行为。
●初始化子类的时候,会触发父类的初始化。
●作为程序入口直接运行时(就是直接调用main函数)。
除了以上四种情况,其他使用类的方式叫做被动引用,而被动引用不会触发类的初始化。
请看代码:
1.importjava.lang.reflect.Field;
2.importjava.lang.reflect.Method;
3.classInitClass{
4.static{
5.System.out.println("初始化InitClass");
6.}
7.publicstaticStringa=null;
8.publicstaticvoidmethod(){}
9.}
10.classSubInitClassextendsInitClass{}
11.publicclassTest1{
12./**
13.*主动引用引起类的初始化的第四种情况就是运行Test1的main方法时
14.*导致T
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- JAVA 基础