初识JAVA与JVM (JAVA篇第一节)

 

1  JAVA的相关简单介绍 & HelloWorld程序

介绍:

Java是通用的计算机编程语言,它是并发的,基于类的,面向对象的,并且经过专门设计,以尽可能减少实现依赖。 它旨在让应用程序开发人员“一次编写,就可以在任何地方运行”(WORA),这意味着已编译的Java代码可以在支持Java的所有平台上运行,而无需重新编译。

例如,您可以在UNIX上编写和编译Java程序,然后在Microsoft Windows,Macintosh或UNIX计算机上运行它,而无需对源代码进行任何修改。 WORA是通过将Java程序编译为称为字节码的中间语言来实现的。 字节码的格式与平台无关。 一个称为Java虚拟机(JVM)的虚拟机用于在每个平台上运行字节码。

 

JAVA的历史 :

 

    Java最初由Sun Microsystems的James Gosling开发(此后已被Oracle Corporation收购),并于1995年作为Sun Microsystems Java平台的核心组件发布。该语言的大部分语法均来自C和C ++,但与任何一种相比,它的低级功能都更少。

在2010年1月27日收购Sun Microsystems之后,Oracle Corporation是Java SE平台正式实现的当前所有者。此实现基于Sun最初的Java实现。 Oracle实施可用于Microsoft Windows,Mac OS X,Linux和Solaris。

Oracle实现打包为两个不同的发行版:

Java运行时环境(JRE)包含运行Java程序所需的Java SE平台的各个部分,并且面向最终用户。
Java开发工具包(JDK)供软件开发人员使用,并包括开发工具,例如Java编译器,Javadoc,Jar和调试器。

 

什么是JVM?:


Java虚拟机(JVM)是​​运行Java字节码的虚拟机。您可以通过将.java文件编译成.class文件来获得此字节码。 .class文件包含JVM可以理解的字节码。

在现实世界中,JVM是提供可在其中执行Java字节码的运行时环境的规范。不同的供应商提供此规范的不同实现。例如,此Wiki页面列出了不同的JVM实现。

JVM最受欢迎的实现是Oracle公司拥有和提供的Hotspot。 (以前是Sun Microsystems,Inc.)。

 

PS:

除了HotSpot,最新国产JDK也很不错,一方面为国产打一个广告,另一方面可以去了解国产JDK的一些优化项(基于openJDK的一个下游分支),链接如下:

https://github.com/alibaba/dragonwell8

用户导航手册里边有一些优化介绍,其中有一个WarmUp(预热),之前无意在Dubbo的负载均衡中看到类似的代码,位于公共算法抽象类里,根据配置的权重,选举不会马上达到对应权重而是根据时间缓慢进行,龙井的JWarmUp预热我的理解应该就是官方JIT的一个优化,因为JIT也是动态编译,具体我后面在出一个文章讨论一下JWarmup 吧,这里不赘述。

 

JVM使用许多先进技术,结合了最新的内存模型,垃圾收集器和自适应优化器,为Java应用程序提供了最佳性能。

JVM具有两种不同的风格-client和server。尽管服务器VM和客户端VM相似,但已经对服务器VM进行了特殊调整,以最大程度地提高峰值运行速度。它用于执行长时间运行的服务器应用程序,这些应用程序需要比快速启动时间或较小的运行时内存占用更多​​的最快的运行速度。开发人员可以通过指定-client或-server来选择所需的系统(默认启动以服务端启动)。

JVM之所以称为虚拟机,是因为它提供的机器接口不依赖于底层操作系统和机器硬件体系结构。这种与硬件和操作系统的独立性是Java程序一次写入,随处运行的价值的基石。

 

什么是JRE?:


Java运行时环境(JRE)是一个软件包,它将库(jar)和Java虚拟机以及其他组件捆绑在一起,以运行用Java编写的应用程序。 JVM只是JRE发行版的一部分。

要执行任何Java应用程序,您需要在计算机中安装JRE。在任何计算机上执行Java应用程序都是最低要求。

JRE捆绑了以下组件–

Java HotSpot客户端虚拟机使用的DLL文件。
Java HotSpot服务器虚拟机使用的DLL文件。
Java运行时环境使用的代码库,属性设置和资源文件。例如rt.jar和charsets.jar。
Java扩展文件,例如localedata.jar。
包含用于安全管理的文件。这些文件包括安全策略(java.policy)和安全属性(java.security)文件。
包含applet支持类的Jar文件。
包含供平台使用的TrueType字体文件。


JRE可以作为JDK的一部分下载,也可以单独下载。 JRE与平台有关。这意味着您必须根据计算机的类型(操作系统和体系结构)选择要导入和安装的JRE软件包。

例如,您不能在32位计算机上安装64位JRE分发。同样,用于Windows的JRE发行版在Linux上将无法运行。反之亦然。

 

那什么是JDK?:


    JDK是JRE的超集。 JDK包含JRE拥有的所有内容以及用于开发,调试和监视Java应用程序的开发工具。当需要开发Java应用程序时,需要JDK。

JDK附带的几个重要组件如下:

appletviewer –此工具可用于在没有Web浏览器的情况下运行和调试Java applet
apt –注释处理工具
extcheck –一种检测JAR文件冲突的实用程序
javadoc –文档生成器,可从源代码注释自动生成文档
jar –存档程序,它将相关的类库打包到一个JAR文件中。该工具还有助于管理JAR文件
jarsigner – jar签名和验证工具
javap –类文件反汇编程序
javaws – JNLP应用程序的Java Web Start启动器
JConsole – Java监视和管理控制台
jhat – Java堆分析工具
jrunscript – Java命令行脚本外壳
jstack –打印Java线程的Java堆栈跟踪的实用程序
keytool –用于操作密钥库的工具
policytool –策略创建和管理工具
xjc – XML绑定Java API(JAXB)API的一部分。它接受XML模式并生成Java类
与JRE一样,JDK也依赖于平台。因此,在为您的计算机下载JDK软件包时请多加注意。

 

那么JDK JRE JVM 有什么区别呢?

 

JDK,JRE和JVM之间的区别:
基于以上,我们可以得出以下三个方面的关系–

JRE = JVM +运行Java应用程序的库。

JDK = JRE +开发Java应用程序的工具。

如下图:

 

 

 

 

Java类文件:
Java源文件必须以它们包含的公共类命名,并附加后缀.java,例如HelloWorldApplication.java。
必须首先使用Java编译器将其编译为字节码,然后生成一个名为HelloWorldApplication.class的文件。 只有这样才能执行或“启动”。
Java源文件只能包含一个公共类,但是可以包含多个类,除了公共访问权限和任何数量的公共内部类之外。
当源文件包含多个类时,将一个类设为“ public”,然后使用该公共类名命名源文件。

类似如下:

也就是说一个JAVA类文件只能有一个class是public 的,你可能看到过很多教程里面写测试都是写在一个类里面的,那是为了方遍教学,正常来说我们是不会这么定义的,按照设计模式的八大原则之一的单一职责原则,只有同业务的才会写在一个类里边。

 

 

hello-world程序: 

public class HelloWorldApplication {
    public static void main(String[] args) {
        System.out.println("Hello World!"); 	// Prints Hello World! to the console.
    }
}

在Unix 环境下运行模式如下:

类加载器: 

类加载器是用于加载类文件的子系统(加载模式为双亲委派)。它执行三个主要功能,即类加载,链接和初始化。

  • 载入中
  • 为了加载类,JVM有3种类加载器。Bootstrapextension and application class loader.
  • 加载类文件时,JVM会找到某个任意类XYZ.class的依赖项。
  • 第一个引导程序类加载器尝试查找该类。它扫描JRE lib文件夹中的rt.jar文件。
  • 如果找不到类,那么扩展类加载器将在jre \ lib \ ext文件夹中搜索类文件。
  • 同样,如果未找到类,则应用程序类加载器将在系统的CLASSPATH环境变量中搜索所有Jar文件和类。
  • 如果任何加载程序找到了类,则由类加载程序加载类;否则抛出ClassNotFoundException。
  • 连结中 由类加载器加载类后,将执行链接。字节码验证程序将验证生成的字节码是否正确,如果验证失败,我们将收到验证错误。它还对类中的静态变量和方法执行内存分配。
  • 初始化,这是类加载的最后阶段,此处将为所有静态变量分配原始值,并执行静态块。

 

在我们了解了类加载器和JVM的简单介绍之后我们在来看一下下面这个图:

JVM内存区域

JVM中的内存区域分为多个部分,以存储应用程序数据的特定部分。

  • 方法区域存储类结构,如元数据,常量运行时池和方法代码。
  • 堆存储在应用程序执行期间创建的所有对象。
  • 堆栈存储局部变量和中间结果。 所有这些变量对于创建它们的线程都是本地的。 每个线程都有自己的JVM堆栈,并在创建线程时同时创建。 所以所有这些局部变量都称为线程局部变量(这里只的是局部变量,那共享变量将会出现线程共享的情况,注意下)。
  • PC寄存器存储当前正在执行的语句的物理内存地址。 在Java中,每个线程都有其单独的PC寄存器。
  • Java也支持和使用本机代码。 许多底层代码都是用C和C ++等语言编写的。 本机方法堆栈保存本机代码的指令。

execution-engine:

分配给JVM的所有代码均由执行引擎执行。 执行引擎读取字节码并一一执行。 它使用两个内置的解释器和JIT编译器将字节码转换为机器代码并执行。

使用JVM,解释器和编译器均会生成本机代码。 不同之处在于它们如何生成本机代码,其优化程度以及优化成本。

任何JAVA代码由类加载器加载入JVM ,最终由JVM内部编译器编译过后的可执行文件(bytecodes)然后由执行引擎read one by one ,类似 .c 文件被汇编器汇编后 .o 结尾的可执行目标文件(二进制机器码)

 

2 Download JDK

     

您可以在Oracle的Java发行页面中找到特定于平台的JDK和JRE软件包。

例如,此页面列出了Java 8的所有可用JDK发行版。

 

 3 基于栈的寄存器对应字节码常用指令分析

介绍:

JAVA和Python在某些方面还是比较相似的,比如反编译字节码都是基于栈的虚拟机既然是基于栈的

我们需要了解一下基于栈的常用字节码指令,因为这是在JVM层面最接近机器码的代码指令,那么常用的我简单列几个给大家入个们:

Bipush :  将操作数局部变量表的值压入内存栈顶。

Istore_n: 将操作数栈顶的值出栈存入本地局部变量表序号为N的值。

Iload_N ;  将局部变量表序号为N的值复制入栈顶。

所有的class文件最终都是以指令在JVM被内部解释器一行一行执行的

其他的我附上一个表格如下:

以上只是讲栈顶数据拿到本地变量的相关指令,其他的还有很多,感兴趣的可以去查一下。

 

4   class文件的反编译结构以及反编译方法

介绍:

这里就以helloworld程序为例,给大家做一个介绍。

我们可以看到之前编译的class文件和JAVA文件

这里我介绍两种方式反编译class文件

  1. IDEA 的Jclasslib的插件

      之前我的文章里面有所提及,感兴趣的可以去看下

       https://blog.csdn.net/qq_34597894/article/details/94634722

  1. JDK内置的javap工具

       

简单说明一下,从上到下一条一条指令我们可以很清晰的看明白,可以看到默认会初始化同名构造函数也就是图中的public HelloWorld();方法,以及对应的相关指令java.io.printStream打印的 String 类型的 helloworld,OK ,这里不赘述,相信大家都看的懂,只做一个简单入门级介绍。

画外语:

为了体现基于堆栈和基于寄存器的虚拟机字节码的对比,我这里以C语言为例给大家感受一下(lua也是一样):

 

图中的类似%eax % edx是CPU内不同寄存器的名字,旁边类似movl(移动指定内存地址的值到指定内存地址)的,就是汇编指令,你们也可以理解为汇编语言

 

5  JIT compiler (JVM及时编译器)

介绍:

即时(JIT)编译器是运行时环境的组件,该组件通过在运行时将字节码编译为本地机器代码来提高Java™应用程序的性能。

Java程序由类组成,这些类包含与平台无关的字节码,可以由JVM在许多不同的计算机体系结构上解释。在运行时,JVM加载类文件,确定每个单个字节码的语义,并执行适当的计算。解释期间额外的处理器和内存使用情况意味着Java应用程序比本地应用程序执行得更慢。 JIT编译器通过在运行时将字节码编译为本机代码来帮助提高Java程序的性能。

默认情况下,JIT编译器处于启用状态。编译方法后,JVM会直接调用该方法的已编译代码,而不是对其进行解释。从理论上讲,如果编译不需要处理器时间和内存使用量,则编译每种方法都可以使Java程序的速度接近本机应用程序的速度。

JIT编译确实需要处理器时间和内存使用率。 JVM首次启动时,将调用数千种方法。即使程序最终达到了非常好的峰值性能,编译所有这些方法也会严重影响启动时间。

实际上,方法不是在第一次调用时编译的。对于每种方法,JVM都会维护一个调用计数,该计数从预定义的编译阈值开始,并在每次调用该方法时递减。当调用计数达到零时,将触发该方法的即时编译。因此,经常使用的方法在JVM启动后立即进行编译,而较少使用的方法则在以后编译,或者根本不编译。 JIT编译阈值可帮助JVM快速启动,并仍具有改进的性能。选择该阈值以获得启动时间和长期性能之间的最佳平衡。

JIT编译器可以在不同的优化级别上编译方法:冷,暖,热,veryHot或烧焦(请参见-Xjit中的optlevel)。期望更高的优化级别可以提供更好的性能,但是就CPU和内存而言,它们的编译成本也更高。方法的初始或默认优化级别为热启动,但有时JIT启发式将优化级别降级为冷启动,以缩短启动时间。

可以通过不同的机制将方法重新编译到更高的优化级别。这些机制之一是采样:JIT编译器维护一个专用的采样线程,该线程会定期唤醒,并确定哪些Java方法出现在堆栈顶部的频率更高。此类方法被认为对性能更重要,它们是在较高的高温,高温或高温下重新优化的候选方法。

您可以禁用JIT编译器,在这种情况下,将解释整个Java程序。除了诊断或解决JIT编译问题外,不建议禁用JIT编译器。

看到这里其实可以回想一下之前说过龙井的JwarnUp特性,讲述运行时编译方向,以我现在的理解应该是差不多的,只是热启动的方式或者记录数据不同而已

这里贴一下,了解的可以私信我,欢迎讨论。

关于JIT入门我画了一个图给大家理解一下:

入门级介绍就到这里

7   GC 

介绍:
Java使用自动垃圾收集器来管理对象生命周期中的内存。 程序员确定何时创建对象,一旦不再使用对象,Java运行时将负责恢复内存。 一旦没有对对象的引用,则垃圾回收器将有资格自动释放无法访问的内存。

如果程序员的代码持有对不再需要的对象的引用,则通常仍会发生类似于内存泄漏的情况,通常是将不再需要的对象存储在仍在使用的容器中时。 如果调用了不存在的对象的方法,则会引发“ NullPointerException”。

垃圾收集可能随时发生。 理想情况下,它将在程序空闲时发生。 如果堆上的可用内存不足以分配新对象,则可以保证触发该事件。 这可能会导致程序暂时停止。 在Java中无法进行显式内存管理。

GC我们现阶段只用知道在JAVA里面无需程序员管理,较C中的free()函数就会有明显的对比

 

附上相关课后题(课后题作为加深印象的补充):

1 Java中有几种类型的类加载器?

2 Java中的类加载器如何工作?

3 JRE和JVM之间的区别?

4 解释器和JIT编译器之间的区别?

5 请简述类加载器的双亲委派模型和对应优点?

 

因主要用于入门了解,部分内容只做简单介绍,提高部分只做简单提及,感兴趣的可以关注我的微信公众号,欢迎指教

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值