为了账号安全,请及时绑定邮箱和手机立即绑定

jvm 的内存中可以把一个类加载多次么

/ 猿问

jvm 的内存中可以把一个类加载多次么

JVM
明月笑刀无情 2018-08-06 00:00:26

jvm 的内存中可以把一个类加载多次么


查看完整描述

1 回答

?
白猪掌柜的

1.类加载器深入剖析Java虚拟机与程序的生命周期:当我们执行一个java程序的时候,会启动一个JVM进程,当程序执行完之后,JVM进程就消亡了;在如下情况下JVM将结束声明周期:System.exit(int)方法,当执行这个方法的时候,虚拟机会退出;这个方法传入一个整形参数,这个参数是状态吗:如果这个整形是0的话,就是正常退出,如果不是0的话,就是异常退出;程序正常结束;程序执行过程中,遇到了异常或错误,而异常终止:如果我们在程序中出现了异常,而不去处理,会将异常一直抛给main函数,main函数会将异常抛给JVM,JVM如果处理不了异常,JVM就会异常退出;由于操作系统出现错误导致JVM进程终止:JVM所依赖的平台出现错误,导致JVM终止;2.类的加载,连接和初始化加载:查找并加载类的二进制数据,将class字节码文件加载到内存中;连接:-验证:确保被加载的类的正确性,使用javac编译工具生成的字节码文件能通过验证,如果不是由javac编译生成的字节码文件,如果自己生成的字节码文件不符合JVM虚拟机对字节码文件的要求的话,可能会出现验证通不过的情况;比如说随便拿一个文件,将后缀名直接修改为.class,这样的字节码文件肯定不合法;-准备:为类的静态变量分配内存,并将其初始化为默认值;-解析:把类中的符号引用转为直接引用;初始化:为类的静态变量赋予正确的初始值(正确的值指的是用户赋的值);-好像这个与连接阶段的准备有些重复,在连接的准备阶段只是赋予初始变量,如果用户给这个变量赋了初始值,那么这个变量在连接的准备阶段仍然会赋予初始值;-在这个阶段,才会真正的将初始值赋给静态变量;Java程序对类的使用方式有主动使用和被动使用;所有的JVM实现,必须在每个类或者接口,被java程序“首次主动使用”时才初始化他们;主动使用:创建类的实例;访问某个类或接口的静态变量,或者对该静态变量赋值;调用类的静态方法;反射:Class.forName(“类名”);初始化一个类的子类,看做对父类的主动使用;java虚拟机启动的时候,被标明启动类的类,即包含main方法的类,程序的入口;除了上面6种主动使用之外,其它的情况均为被动使用,其它情况都不会执行第三步初始化;3.类的加载(1)概念类的加载:指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后再堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构;反射:反射就是跟句堆区的字节码文件,获取方法去的数据结构;解析:Class对象是由JVM自己创建的,所有的对象都是经过Class对象创建,这个Class对象是反射的入口,通过Class对象,可以关联到目标class字节码文件的内部结构;所有的类对应的Class对象都是唯一的一个,这个类是由JVM进行创建的,并且只有JVM才会创建Class对象;类加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口(反射用的接口);(2)加载.class文件的方式从本地系统中直接加载:编译好的.class字节码文件直接从硬盘中加载;通过网络下载.class文件:将class字节码文件放在网络空间中,使用URLClassLoader来加载在网络上的.class字节码文件,使用默认的父亲委托机制加载字节码文件;从zip,jar等压缩文件中加载字节码文件:在开发的时候,导入jar包,就是这种方式;从专有的数据库中提取字节码文件;将java源文件动态编译为字节码文件;(3)类加载器lJava虚拟机自带的类加载器:-根类加载器(Bootstrap):是C++写的,程序员无法再java代码中获取这个类,如果使用getClassLoader()方法获取到的是一个null值;packagejvm;Java代码publicclassClassLoaderTest{publicstaticvoidmain(String[]args)throwsException{//java.lang包下的类使用的是跟类加载器进行加载的Classclazz=Class.forName("java.lang.String");System.out.println(clazz.getClassLoader());//自定义的类使用的是应用类加载器(系统加载器)Classclazz2=Class.forName("jvm.C");System.out.println(clazz2.getClassLoader());}}classC{}执行结果:nullJava代码sun.misc.Launcher$AppClassLoader@1372a1a-扩展类加载器(Extension):Java编写;-系统类加载器(应用加载器)(System):Java编写;用户自定义的类加载器:-自定义的类加载器都是java.lang.ClassLoader子类;-用户可以定制类的加载方式String类是由根类加载器进行加载的,我们可以调用Class对象的关于代理中创建对象的类加载器:创建代理对象的时候,动态创建一个类,然后使用指定的类加载器将这个类加载到内存中,然后用加载到内存中的类生成代理对象;创建代理对象的方法:newProxyInstance(ClassLoaderloader,Class[]Interfaces,InvocationHandlerh)loader是定义的代理类的类加载器,中间的接口数组是代理类的要实现的接口列表,h是指派方法调用的调用处理程序;类加载器并不需要在某个类被“首次主动使用”时再加载它:-预加载机制:JVM规范允许类加载器在预料某个类将要被使用的时就预先加载它;-报错时机:如果在预加载的过程中遇到了字节码文件缺失或者存在错误的情况,类加载器会在程序首次主动使用(上面提到的六种情况)该类的时候报错(LinkageError错误);-不报错时机:如果这个错误的字节码文件所对应的类一直没有被使用,那么类加载器就不会报告错误,即便有错误也不会报错;LinkageError:这个错误是Error的子类,程序不能处理这些错误,这些错误都是由虚拟机来处理,这个错误表示出错的是子类,在一定程序上依赖于另一个类,在编译了前面一个类的时候,与后面所依赖的类出现了不兼容的情况;例如:我们使用了jdk1.6在编译一个程序,但是运行环境是jre1.5的,就会出现LinkageError错误;4.类的连接(1)定义类被加载之后,就进入链接阶段;链接:将已读入内存的二进制数据合并到虚拟机的运行时环境中去;链接顾名思义就是讲类与类之间进行关联,例如我们在类A中调用了类B,在链接过程中,就将A与B进行链接,将面向对象语言转化为面向过程语言;(2)类的验证类文件的结构检查:确保类文件遵从java类文件的固定格式,开始类的描述,声明,方法调用格式等;语义检查:确保类本身符合java语言的语法规定,比如final类型的类没有子类,final类型的方法没有被覆盖,在eclipse中这种错误编译的时候不能通过,但是通过其他的方法可以生成错误的字节码文件,这里就是检测恶意生成的字节码文件;字节码验证:确保字节码流可以被JVM安全的执行,字节码流代表java方法(包括静态方法和实例方法),它是由被称作操作码的单字节指令组成的序列,每一个操作码后面跟着一个或多个操作数,字节码验证步骤会检查每个操作码是否合法,即是否有着合法的操作数;下面是指令码组成的序列,类似于微指令:Jvm编译指令代码代码//CompiledfromByteToCharCp1122.java(version1.5:49.0,superbit)publicclasssun.io.ByteToCharCp1122extendssun.io.ByteToCharSingleByte{//Fielddescriptor#17Lsun/nio/cs/ext/IBM1122;privatestaticfinalsun.nio.cs.ext.IBM1122nioCoder;//Methoddescriptor#18()Ljava/lang/String;//Stack:1,Locals:1publicjava.lang.StringgetCharacterEncoding();0ldc[1]2areturnLinenumbers:[pc:0,line:25]//Methoddescriptor#2()V//Stack:2,Locals:1publicByteToCharCp1122();0aload_0[this]1invokespecialsun.io.ByteToCharSingleByte()[25]4aload_0[this]5getstaticsun.io.ByteToCharCp1122.nioCoder:sun.nio.cs.ext.IBM1122[23]8invokevirtualsun.nio.cs.ext.IBM1122.getDecoderSingleByteMappings():java.lang.String[27]11putfieldsun.io.ByteToCharSingleByte.byteToCharTable:java.lang.String[24]14returnLinenumbers:[pc:0,line:28][pc:4,line:29][pc:14,line:30]//Methoddescriptor#2()V//Stack:2,Locals:0static{};0newsun.nio.cs.ext.IBM1122[15]3dup4invokespecialsun.nio.cs.ext.IBM1122()[26]7putstaticsun.io.ByteToCharCp1122.nioCoder:sun.nio.cs.ext.IBM1122[23]10returnLinenumbers:[pc:0,line:22]}l二进制兼容性的验证:确保相互引用的类之间协调一致的;例如在A类的a()方法中调用B类的b()方法,JVM在验证A类的时候,会验证B类的b()方法,加入b()方法不存在,或者版本不兼容(A,B两类使用不同的JDK版本编译),会抛出NoSuchMethodError错误;(3)准备阶段在准备阶段,JVM为类的静态变量分配内存空间,并设置默认的初始值.例如下面的Sample类,在准备阶段,为int类型的静态变量分配4个字节,并赋予初始值0;为long类型的静态变量b,分配8个字节,并赋予初始值0;PS:在java中基本类型变量占用的空间是一定的,java运行在JVM上的,在C中,就要根据平台变化而变化了;publicclassSample{Java代码privatestaticinta=1;privatestaticlongb;static{b=2;}(4)类的解析在解析阶段,JVM会把类的二进制数据中的符号引用替换为直接引用,例如在A类中的a()方法引用B类中的b()方法;在A类的二进制数据中包含了一个对B类的b()方法的符号引用,这个符号引用由b()方法的全名和相关的描述符组成,在Java解析阶段,就会把这个符号引用替换为指针,这个指针就是C语言中的指针了,该指针指向B类的b()方法在方法区中的内存位置,这个指针就是直接引用;5.类的初始化在初始化阶段,Java虚拟机执行类的初始化操作,为类的静态变量赋予初始值,在程序中,静态变量初始化有两种途径:直接在声明处进行初始化,例如下面的Sample中的变量a;在静态代码块中进行初始化,例如下面的Sample中的变量b;Java代码publicclassSample{privatestaticinta=1;privatestaticlongb;static{b=2;}}6.面试题介绍Java代码publicclassPrepareOrInit{publicstaticvoidmain(String[]args){Singletonsingleton=Singleton.getInstance();System.out.println(singleton.count1);System.out.println(singleton.count2);}}classSingleton{privatestaticSingletonsingleton=newSingleton();publicstaticintcount1;publicstaticintcount2=0;privateSingleton(){count1++;count2++;}publicstaticSingletongetInstance(){returnsingleton;}}执行结果:10分析:这段代码与类的链接中的准备阶段和初始化阶段有关系,准备阶段是给静态的字段赋予默认值,初始化阶段给静态变量赋予正确的值,即用户的值;在主函数中,调用了类的静态方法,相当于主动使用,这里调用了类的静态方法;之后进行连接的准备操作,给类中的静态变量赋予初值,singleton值为null,count1与count2值为0;执行初始化操作,给类中的静态变量赋予正确的值,给singleton变量赋予正确的值,调用构造方法,此时count1与count2执行自增操作,两者都变成1,然后执行count1的赋予正确值操作,这里用户没有赋值操作,count2用户进行了赋值为0的操作,0将原来的1覆盖掉了,因此结果为1,0;Java代码publicclassPrepareOrInit{publicstaticvoidmain(String[]args){Singletonsingleton=Singleton.getInstance();System.out.println(singleton.count1);System.out.println(singleton.count2);}}classSingleton{publicstaticintcount1;publicstaticintcount2=0;privatestaticSingletonsingleton=newSingleton();privateSingleton(){count1++;count2++;}publicstaticSingletongetInstance(){returnsingleton;}}执行结果:11在准备阶段count1和count2都赋值为0,然后在初始化阶段,全部赋值为1;

查看完整回答
反对 回复 2018-09-02
  • 1 回答
  • 0 关注
  • 1085 浏览
我要回答

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信