栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

【Java进阶营】Java技术专题-进阶分析Java类型信息(Class对象)

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【Java进阶营】Java技术专题-进阶分析Java类型信息(Class对象)

前提摘要

java之所以实现了跨平台性的特性,正式因为建立了某种约定和规范,那就是JVM定义规范机制

JVM编译器.class文件

大概分析一下:头4个字节称为魔数(Magic Number) 16进制表中为0xCAFEBABE,它的唯一作用是确定这个文件是否是一个能被虚拟机接收的Class文件,是用来标识Class文件的。

第5和第6个字节是次版本号(Minor Version),第7和第8个字节是主版本号(Major Version),Java的版本号是从45开始的,JDK1.1之后的每个JDK大版本发布主版本号向上加1,高版本的JDK能向下兼容以前版本的Class文件,但不能运行以后版本的Class文件。次版本号值为0x0000,主版本号值为0x0034,也就是十进制的52,代表JDK是1.8版本。

紧接着主版本号之后两个字节的是常量池计数值(constant_pool_count),表示常量池中的项目数量,这个容量计数是从1开始的。


JVM的执行模式

上面我们有提到: JVM解释执行Java字节码文件。其实这个说法不够严谨。

目前用的比较主流的是hotspot虚拟机的JIT即时编译器/AOT编译器。

那得先搞懂解释器和编译器的概念:

解释器是一条条的解释执行源语言,比如PHP, JS。

编译器是将源代码先编译成目标代码,执行时直接在支持支持目标代码的平台上运行,这样效率快很多,比如C, C++。(这种方式兼容性和适配性不高)

对于解释执行,不经过JIT直接由解释器解释执行所有字节码,执行效率不高。 而编译执行不加筛选的将全部代码进行编译机器码不论其执行频率是否有编译价值,在程序响应时间的限制下,编译器没法采用编译耗时较高的优化技术(因为JIT的编译是首次运行或启动的时候进行的!),所以,在纯编译执行模式下的java程序执行效率C/C++也是具有较大差距的。

因此,新版本的jvm默认都是采用混合执行模式。

看下面步骤就能明白:

1、源代码经javac编译成字节码,class文件

2、程序字节码经过JIT环境变量进行判断,是否属于“热点代码”(多次调用的方法,或循环等)

3、如是,走JIT编译为具体硬件处理器(如sparc、intel)机器码

4、如否,则直接由解释器解释执行

5、操作系统及类库调用

6、硬件

**补充一下,其实Android的Dalvik是依靠一个JIT编译器去解释字节码,而新版Android的虚拟机ART则完全改变了这套做法,在应用安装时就预编译字节码到机器语言,在移除解释代码这一过程后,应用程序执行将更有效率,启动更快,但是安装过程更长,更占本地存储空间。**在此我向大家推荐一个架构学习交流圈。交流学习指导伪鑫:1253431195(里面有大量的面试题及答案)里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

简单聊聊字节码的加载流程。

Java字节码的执行需要经过以下3个步骤:

  • (1)由类装载器(class loader)负责把类文件(.class文件)加载到Java虚拟机中。在此过程需要检验该类文件是否符合类文件规范。
  • (2)字节码校验器(bytecode verifier)检查该类文件的代码中是否存在着某些非法操作,例如Applet程序中写本地计算机文件系统的操作。 这一步最耗时,JVM会执行大量测试用例去校验。
  • (3)如果字节码校验器检验通过,由Java解释器负责把该类文件解释成为机器码进行执行。

而类加载器可以分为:

1. 启动类加载器(Bootstrap ClassLoader):

这个类加载器负责将lib目录下(-Xbootclasspath:)的类库加载到虚拟机内存中,用来加载java的核心库,此类加载器并不继承于java.lang.ClassLoader,不能被java程序直接调用,代码是使用C++编写的.是虚拟机自身的一部分.

2. 扩展类加载器(Extendsion ClassLoader):

这个类加载器负责加载libext(系统变量:java.ext.dirs)目录下的类库,用来加载java的扩展库,开发者可以直接使用这个类加载器.

3. 应用程序类加载器(Application ClassLoader):

这个类加载器负责加载用户类路径(CLASSPATH)下的类库,一般我们编写的java类都是由这个类加载器加载,这个类加载器是CLassLoader中的getSystemClassLoader()方法的返回值,所以也称为系统类加载器.一般情况下这就是系统默认的类加载器.

除此之外,我们还可以加入自己定义的类加载器,以满足特殊的需求,需要继承java.lang.ClassLoader类。

类加载器就是根据指定全限定名称将class文件加载到JVM内存,转为Class对象。而使用的机制是:双亲委派模型。

双亲委派模型工作过程是:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时,子加载器才会尝试自己去加载

为什么要这么做?

黑客自定义一个java.lang.String类,该String类具有系统的String类一样的功能,只是在某个函数稍作修改。比如equals函数,这个函数经常使用,如果在这这个函数中,黑客加入一些“病毒代码”。并且通过自定义类加载器加入到JVM中。此时,如果没有双亲委派模型,那么JVM就可能误以为黑客自定义的java.lang.String类是系统的String类,导致“病毒代码”被执行。而有了双亲委派模型,黑客自定义的java.lang.String类永远都不会被加载进内存。因为首先是最顶端的类加载器加载系统的java.lang.String类,最终自定义的类加载器无法加载java.lang.String类。

5. JVM的运行期数据区

运行期数据区分为方法区,Java堆,Java栈,程序计数器,本地方法栈。

  • (1) 程序计数器:(每个线程有一个,不是线程共享的区域)

程序计数器是一块比较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。

  • 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器来完成。
  • Java虚拟的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。
  • 因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。(pc寄存器是虚拟机栈独有的对于本地方法栈是没有意义的,因为本地方法栈执行在c或c++中)。
  • (2)Java虚拟机栈:(每个线程有一个,不是线程共享的区域)

每个方法被执行的时候都会同时创建一个栈帧用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

在Java 虚拟机规范中,对这个区域规定了两种异常状况:

  • 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError 异常;
  • 如果虚拟机栈可以动态扩展(当前大部分的Java 虚拟机都可动态扩展,只不过Java 虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。

当出现问题时候两者都会暴漏出来,只是先看从维度先暴漏出来从而抛出哪种异常。

  • (3)本地方法栈:(每个线程有一个,不是线程共享的区域)

和Java虚拟机栈差不多,只不过它是执行本地方法的(C, C++)。

  • (4)Java 堆:(线程共享区域,总共1个,GC管理的区域)

Java 堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。在堆中没有内存完成实例分配,并且堆也无法再扩展时(回收时间到达阈值98%,回收范围2%),会抛出OutOfMemoryError异常。

  • (5)方法区:(线程共享区域,总共1个,GC管理的区域)

方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分(内存连接接壤,但是PermGen区也会存在FullGC),但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。

很多人愿意把方法区称为“永久代”(Permanent Generation),本质上两者并不等价,仅仅是因为HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法区。

  • (6)方法区中包含 - 运行时常量池

  • 前面讲了Class 文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

  • (7)方法区中包含 - Class字节码常量池

  • 运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只能在编译期产生,也就是并非预置入Class 文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern() 方法(还有包含的相关运行时候执行的方法操作(非Literal 字符串值或者Integer常量)而是静态方法的赋值操作)。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/830450.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号