public class String { static{ System.out.println("加载自定义String类"); } }
在同一个工程下面自己定义了一个String类,而且在java.lang包下,其中写了一个静态代码块,表示第一次初始化这类会输出一句话。在StringTest类中:
public class StringTest { public static void main(String[] args) { String str = new String(); } }
运行结果,没有输出静态代码块里面的语句。因为jvm在加载类的时候使用了双亲委外机制,下面详细介绍一下。
1.2 概念和工作原理Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象。而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式,即把请求交由父类处理,它是一种任务委派模式。
工作原理:
①如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;
②如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器;
③如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
在引入的例子中,因为最终找到了启动类加载器,而启动类加载器只加载包名为java、javax、sun等开头的类,而自己定义的String的包是java开头的,所以启动类加载器去寻找java包下的,但是是区寻找核心类库的java包,而不是自己定义的String类所在的包,所以不会加载自己定义的String类,则不会输出那一段话。
再看下面这个例子:在刚在自定义的String类中
运行结果:
这里的原因还是因为jvm去加载了核心类库的String类,没有加载自己定义的String类,所以没有找到main方法。
当我们再程序中需要使用SPI接口,并且去实现这个接口,因为这个接口属于核心的API,所以这里使用了双亲委派机制,依次找到引导类加载器,去加载rt.jar包,在SPI核心类中,有一些接口,需要有具体的实现类,比如JDBC就涉及到第三方的jar包,此时要加载JDBC的jar包,因为此jar包是第三方的,不属于核心API,所以这里引导类加载器反向委派,一直委派到系统类加载器,系统类加载器是当前线程的上下文加载器,由这个类加载器去加载第三方jar包。
总结:
优势:
①避免类的重复加载
②保护程序安全,防止核心API被随意串改
看下面一个小例子:自己定义一个java.lang包,并且在里面写上自定义类和main函数时,会有下面的报错
所以系统是进制以这样的包命名的。
自定义String类,但是在加载自定义String类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载jdk自带的文件(rt.jar包中javalangstring.class),报错信息说没有main方法,就是因为加载的是rt.jar包中的String类。这样可以保证对java核心源代码的保护,这就是沙箱安全机制。
在JVM中表示两个class对象是否为同一个类存在两个必要条件:
①类的完整类名必须一致,包括包名。
②加载这个类的ClassLoader(指ClassLoader实例对象)必须相同。
换句话说,在JVM中,即使这两个类对象(class对象)来源同一个class文件,被同一个虚拟机所加载,但只要加载它们的ClassLoader实例对象不同,那么这两个类对象也是不相等的。
JVM必须知道一个类型是由启动加载器加载的还是由用户类加载器加载的。如果一个类型是由用户类加载器加载的,那么JVM会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。当解析一个类型到另一个类型的引用的时候,JVM需要保证这两个类型的类加载器是相同的。
Java程序对类的使用方式分为:主动使用和被动使用。·
主动使用,又分为七种情况:
①创建类的实例
②访问某个类或接口的静态变量,或者对该静态变量赋值
③调用类的静态方法
④反射(比如:Class.forName ( “com.tbc.Test” ) )
⑤初始化一个类的子类
⑥Java虚拟机启动时被标明为启动类的类
⑦JDK 7开始提供的动态语言支持:
java . lang. invoke.MethodHandle实例的解析结果
REF_getStatic、REF_putStatic、REF_invokeStatic句柄对应的类没有初始化,则初始化
除了以上七种情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化(即初始化阶段的调用
字节码文件经过加载、链接、初始化之后被加载到内存中,即运行时数据区,由执行引擎执行。
内存是非常重要的系统资源,是硬盘和CPU的中间仓库及桥梁,承载着 *** 作系统和应用程序的实时运行。JVM内存布局规定了Java在运行过程中内存申请、分配、管理的策略,保证了JVM的高效稳定运行。不同的JVM对于内存的划分方式和管理机制存在着部分差异。
最典型的不同就是方法区有区别,JRockit和J9就没有方法区,上面的是典型的HotSpt的内存结构。
Java虚拟机定义了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁。另外一些则是与线程一一对应的,这些与线程对应的数据区域会随着线程开始和结束而创建和销毁。
每个线程:独立包括程序计数器、栈、本地栈
线程共享:堆、堆外内存(永久代或元空间或方法区 、代码缓存)。
阿里区分的运行时数据区:
每个JVM只有一个Runtime实例。即为运行环境,相当于内存结构中的运行时环境。
- 线程是一个程序里的运行单元。JVM允许一个应用有多个线并行的执行。
- 在HotSpot JVM里,每个线程都与 *** 作系统的本地线程直接映射:当一个Java线程准备好执行以后,此时一个 *** 作系统的本地线程也同时创建。Java线程执行终止后,本地线程也会回收。
- *** 作系统负责所有线程的安排调度到任何一个可用的CPU上。一旦本地线程初始化成功,它就会调用Java线程中的run ()方法。
- 如果你使用jconsole或者是任何一个调试工具,都能看到在后台有许多线程在运行。这些后台线程不包括调用public static void main(string [ ])的main线程以及所有这个maih线程自己创建的线程。
- 这些主要的后台系统线程在Hotspot JVM里主要是以下几个:
①虚拟机线程:这种线程的 *** 作是需要JVM达到安全点才会出现。这些 *** 作必须在不同的线程中发生的原因是他们都需要JVM达到安全点,这样堆才不会变化。这种线程的执行类型包括"stop-the-world"的垃圾收集,线程栈收集,线程挂起以及偏向锁撤销。
②周期任务线程:这种线程是时间周期事件的体现(比如中断),他们一般用于周期性 *** 作的调度执行。
③GC线程:这种线程对在JVM里不同种类的垃圾收集行为提供了支持。
④编译线程:这种线程在运行时会将字节码编译成到本地代码。
⑤信号调度线程︰这种线程接收信号并发送给JVM,在它内部通过调用适当的方法进行处理。
JVM中的程序计数寄存器(Program Counter Register)中, Register 的命名源于CPu的寄存器,寄存器存储指令相关的现场信息。CPU只有把数据装载到寄存器才能够运行。
这里,并非是广义上所指的物理寄存器,或许将其翻译为PC计数器(或指令计数器)会更加贴切(也称为程序钩子),并且也不容易引起一些不必要的误会。JVM中的PC寄存器是对物理PC寄存器的一种抽象模拟。
PC寄存器的作用:用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令。
- 它是一块很小的内存空间,几乎可以忽略不记。也是运行速度最快的存储区域。
- 在JVM规范中,每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程的生命周期保持一致。
- 任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的Java方法的JVM指令地址;或者,如果是在执行native方法,则是未指定值(undefned) 。
- 它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
- 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
- 它是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
public class PCRegisterTest { public static void main(String[] args) { int i = 10; int j = 20; int k = i + j; } }
以上是java代码,编译之后生成class文件,在class文件所在目录执行反编译指令就可以看到反编译后的指令:
左边所看到的0、2、3、5、6、7、8、9、10就是指令地址(偏移地址),就是PC寄存器存储的结构,右边的就是 *** 作指令。执行引擎会去取当前PC寄存器的值,然后根据指令去 *** 作局部变量表和 *** 作数栈。执行引擎还会把 *** 作指令转换为机器码让CPU执行。
1.使用PC寄存器存储字节码指令地址有什么用呢?为什么使用PC寄存器记录当前线程的执行地址呢?
因为CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪开始继续执行。
JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令。
2.PC寄存器为什么会被设定为线程私有?
我们都知道所谓的多线程在一个特定的时间段内只会执行其中某一个线程的方法,CPU会不停地做任务切换,这样必然导致经常中断或恢复,如何保证分毫无差呢?为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程都分配一个PC寄存器,这样一来各个线程之间便可以进行独立计算,从而不会出现相互千扰的情况。
由于CPU时间片轮限制,众多线程在并发执行过程中,任何一个确定的时刻,一个处理器或者多核处理器中的一个内核,只会执行某个线程中的一条指令。
这样必然导致经常中断或恢复,如何保证分毫无差呢?每个线程在创建后,都会产生自己的程序计数器和栈帧,程序计数器在各个线程之间互不影响。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)