Java基础概念篇,带你入门,进入java的大门中!(上)

目录

Java | 如何开始学习Java

.关于Java

设置Java

在Java中设置环境

为Windows设置Java环境的步骤

适用于Linux的步骤

使用Hello World示例开始Java编程

Java命名约定

JVM如何工作 - JVM架构?

类加载器子系统它主要负责三个活动。

Java虚拟机(JVM)堆栈区域

Java中的JVM Shutdown Hook

Java类文件

JDK,JRE和JVM之间的差异

JAVA开发套件

JRE和JDK如何工作?

JRE包括什么?JRE由以下组件组成:

JRE如何运作?要了解JRE的工作原理,让我们考虑保存为Example.java的Java源文件。该文件被编译成一组字节代码,存储在“ .class ”文件中。这里将是“ Example.class ”。​下图描述了在编译时完成的操作。

JVM是否创建Main类的对象(具有main()的类)?


Java | 如何开始学习Java

Java是最流行和最广泛使用的编程语言和平台之一。平台是一种有助于开发和运行用任何编程语言编写的程序的环境。
Java快速,可靠且安全。从桌面到Web应用程序,从科学超级计算机到游戏控制台,从手机到互联网,Java都可以在每个角落使用。

.关于Java

因此,Java是一种非常成功的语言,它日益受到欢迎。

  • Java是一种简单的语言: Java易于学习,语法清晰简洁。它基于C ++(因此对于了解C ++的程序员来说更容易)。Java已经删除了许多令人困惑且很少使用的功能,例如显式指针,运算符重载等.Java还负责内存管理,它还提供自动垃圾收集器。这会自动收集未使用的对象。
  • Java是一种独立于平台的语言:在编译之后,用Java语言编写的程序被转换为一种称为字节码的中间级语言,它与Java平台无关,而与程序运行的机器无关。这使得java具有高度可移植性,因为它的字节码可以通过称为Java虚拟机(JVM)的解释器在任何机器上运行,因此java提供了“代码的可重用性”。
  • Java是一种面向对象的编程语言: OOP通过将整个程序划分为多个对象来使整个程序更简单。这些对象可以用作桥梁,使数据从一个功能流向另一个功能。我们可以根据程序的要求轻松修改数据和功能。
  • Java是一种强大的语言: Java程序必须可靠,因为它们用于消费者和任务关键型应用程序,从蓝光播放器到导航系统。
  • Java是一种多线程语言: Java可以通过定义多个线程一次执行许多任务。例如,在等待来自网络连接的输入时管理图形用户界面(GUI)的程序使用另一个线程来执行和等待,而不是使用默认GUI线程来执行这两个任务。这使GUI保持响应。
  • Java程序可以创建applet: Applet是在Web浏览器中运行的程序。
  • Java不需要任何预处理器:它不需要包含头文件来创建Java应用程序。

设置Java

你可以从这里下载java 。在这里,您将找到不同版本的Java。选择并下载与您的操作系统兼容的一个。
有关设置Java的详细说明,请参阅此文章。

正确设置Java环境后,尝试运行这个简单的程序:

  • 理解基础知识:
    学习任何编程语言的基础知识非常重要。这是开始学习新事物的最佳方式。不要有任何焦虑,开始学习语言的概念。熟悉环境,慢慢地你会很快习惯它。以下是一些可以帮助您入门的链接:

  • 耐心是关键:
    学习Java将是压倒性的,因为关于语言的材料量很大但要耐心,按照自己的进度学习,不要急于求成。掌握Java是一个需要时间的过程。请记住,即使是最好的程序员也会在某个时刻开始。所以这不是什么大不了的事情,只要尽可能多地继续下去。给你时间。耐心是成功的关键。
  • 练习编码
    一旦你理解了基础知识,最好的办法是通过常规练习提高你的技能。真正的知识只有在你实施所学知识的时候才能实现,正如“实践使人完美”所说的那样。所以,代码比你读的更多。这将建立你的信心。记住完美实践让你完美。
    练习编码:您可以在此处提高编码技能。快乐的编码!
  • 定期阅读Java
    不断阅读Java中的各种主题并尝试探索更多内容。这有助于保持您对Java的兴趣。
    浏览此链接以探索Java的广阔世界:
    探索Java >> 
    有关Java的最新文章>>
  • 研究组
    组研究是学习的东西更好的方法。通过这种方式,您可以了解有关该主题的新内容,因为每个人都可以提出他们的想法,您可以在现场讨论和解决您的编码问题。了解一群愿意学习java的普通人。
    从导师那里获得帮助,尽可能多地阅读有关java的书籍。市场上有很多好书可以帮助你学习java。
// A Java program to print GeeksforGeeks 
public class GFG { 
    public static void main(String args[]) 
    { 
        System.out.println("wellcome subscribe to geekmoney"); 
    } 
} 

输出:

wellcome subscribe to geekmoney

如果环境设置正确并且代码写得正确,您将在控制台上看到此输出。那是你的第一个Java程序!

在Java中设置环境

Java是一种通用的计算机编程语言,它是并发的,基于类的,面向对象的等
.Java应用程序通常被编译为可以在任何Java虚拟机(JVM)上运行的字节码,而不管计算机体系结构如何。最新版本是Java 11

以下是Linux和Windows的环境设置。JVM,JRE和JDK都是平台相关的,因为每个操作系统的配置都不同。但是,Java与平台无关。

在设置环境之前,必须清楚几件事情

  1. JDK(Java开发工具包):JDK适用于软件开发人员,包括Java编译器,Javadoc,Jar和调试器等开发工具。
  2. JRE(Java运行时环境):JRE包含运行Java程序所需的Java库部分,适用于最终用户。JRE可以作为JDK的子集进行查看。
  3. JVM: JVM(Java虚拟机)是一个抽象机器。它是一个规范,提供可以执行java字节码的运行时环境。JVM可用于许多硬件和软件平台。 

为Windows设置Java环境的步骤

  1. Java8 JDK可从Download Java 8获得
    单击Windows的最后一个链接(32位)和Windows的最后一个链接(64位),如下所示。
    捕获
  2. 下载后,运行.exe文件并按照说明在您的计算机上安装Java。在计算机上安装Java后,必须设置环境变量。
  3. 转到控制面板 - >系统和安全 - >系统。
    在“高级系统设置”选项下,单击下面突出显示的“ 环境变量”
    捕获
  4. 现在,您必须更改System变量下的“Path”变量,以便它还包含Java环境的路径。选择“路径”变量,然后单击“编辑”按钮,如下所示。
    java环境setuo
  5. 您将看到不同路径的列表,单击“新建”按钮,然后添加安装Java的路径。默认情况下,java安装在“C:\ Program Files \ Java \ jdk \ bin”文件夹或“C:\ Program Files(x86)\ Java \ jdk \ bin”中。如果您已在任何其他位置安装了Java,则添加该路径。

    Java环境设置

  6. 单击确定,保存设置,您就完成了!现在检查安装是否正确完成,打开命令提示符并键入javac -version。您将看到java正在您的计算机上运行。
  7. 为了确保是否已设置编译器,请在命令提示符下键入javac。您将看到与javac相关的列表。

适用于Linux的步骤

在linux中,有几种方法可以安装java。但是我们将参考使用终端安装java的最简单方法。对于linux,我们将安装OpenJDK。OpenJDK是Java编程语言的免费开源实现。

  1. 转到应用程序 - >附件 - >终端
  2. 键入命令如下..
    sudo apt-get install openjdk-8-jdk
    
  3. 对于“JAVA_HOME”(环境变量)类型命令,如下所示,在“终端”中使用您的安装路径...(注意:默认路径如图所示,但如果您已在其他位置安装OpenJDK,则设置该路径。)
    export JAVA_HOME = / usr / lib / jvm / java-8-openjdk
    
  4. 对于“PATH”(环境值)类型命令,如下所示,在“终端”中使用您的安装路径...注意:默认路径如图所示,但如果您已在其他位置安装OpenJDK,则设置该路径。)
    export PATH = $ PATH:/ usr / lib / jvm / java-8-openjdk / bin
    
  5. 你完成了!! 现在要检查安装是否正确完成,在终端中键入java -version。您将看到java正在您的计算机上运行。

热门Java编辑/ IDE

  1. Notepad / gedit:它们是用于编写java程序的简单文本编辑器。记事本在Windows上可用,gedit在Linux上可用。
  2. Eclipse IDE:它是用于在java中开发软件的最广泛使用的IDE(集成开发环境)。您可以从这里下载Eclipse 。

使用Hello World示例开始Java编程

Java编程的过程可以分三步简化:

  • 通过在文本编辑器中键入程序并将其保存到文件HelloWorld.java来创建程序。
  • 通过在终端窗口中键入“javac HelloWorld.java”来编译它。
  • 通过在终端窗口中键入“java HelloWorld”来执行(或运行)它。

下面给出的程序是Java打印“Hello World”的最简单程序。让我们一步一步地尝试理解每一段代码。

/* This is a simple Java program. 
   FileName : "HelloWorld.java". */
class HelloWorld 
{ 
    // Your program begins with a call to main(). 
    // Prints "Hello, World" to the terminal window. 
    public static void main(String args[]) 
    { 
        System.out.println("Hello, World"); 
    } 
} 

输出:

hello world

“Hello World!”程序由三个主要组件组成:HelloWorld类定义,main方法和源代码注释。以下说明将为您提供对代码的基本了解:

  1. 类定义:此行使用关键字class来声明正在定义新类。
    HelloWorld类 
    

    HelloWorld是一个标识符,它是类的名称。整个类定义(包括其所有成员)将位于左大括号   {  和右大括号  }之间

  2. main方法: 在Java编程语言中,每个应用程序必须包含一个main签名为的方法:
    public static void main(String [] args)
    
    public:这样JVM可以从任何地方执行该方法。
    static:主要方法是在没有对象的情况下调用。
    修饰符public和static可以按任意顺序编写。
    void:main方法不返回任何内容。
    main():在JVM中配置的名称。
    String []:main方法接受一个参数:
              String类型的元素数组。

    与C / C ++类似,main方法是应用程序的入口点,随后将调用程序所需的所有其他方法。

  3. 下一行代码显示在此处。请注意,它发生在main()中。
    System.out.println(“Hello,World”);
    

    该行输出字符串“Hello,World”,然后在屏幕上输出一个新行。输出实际上是由内置的println(方法完成的。System是一个提供对系统访问的预定义​​类,out是连接到控制台的输出流类型的变量。

  4. 评论:它们可以是多行或单行注释。
    / *这是一个简单的Java程序。 
    将此文件命名为“HelloWorld.java”。* /
    

    这是一条多行评论。此类注释必须以/ *开头,以* /结尾。对于单行,您可以直接使用//,如在C / C ++中。

重点 

  • 程序定义的类的名称是HelloWorld,它与文件名(HelloWorld.java)相同。这不是巧合。在Java中,所有代码必须驻留在类中,并且最多只有一个公共类包含main()方法。
  • 按照惯例,主类的名称(包含main方法的类)应该与保存程序的文件的名称相匹配。

编译程序

  • 成功设置环境后,我们可以在Windows / Unix中打开终端,并可以转到文件 - HelloWorld.java所在的目录。
  • 现在,要编译HelloWorld程序,执行编译器--javac,在命令行中指定文件的名称,如下所示:
    javac HelloWorld.java 
    
  • 编译器创建一个名为HelloWorld.class的文件(在当前工作目录中),该文件包含程序的字节码版本。现在,要执行我们的程序,需要使用java调用JVM(Java虚拟机),在命令行上指定文件的名称,如下所示:
    java HelloWorld
    

    这将在终端屏幕上打印“Hello World”。

在Windows中

捕获

在Linux中

VirtualBox_Hadoop_ubuntu_SN_07_02_2017_03_37_06

Java命名约定

以下是java编程语言的一些命名约定。在开发java中的软件时必须遵循它们,以便进行良好的维护和代码可读性。Java使用CamelCase作为编写方法,变量,类,包和常量名称的实践。

Java编程中的Camel案例:它由复合词或短语组成,每个单词或缩写以大写字母或带小写字母的第一个单词开头,全部用大写字母表示。

  1. 类和接口
    • 类名应该是名词,大小写混合,每个内部单词的字母大写。接口名称也应该像类名一样大写。
    • 使用整个单词,必须避免缩略语和缩写。

    例子:

    界面自行车
    MountainBike类实现了Bicyle
    
    界面运动
    class Football实现运动
    
  2. 方法 :
    • 方法应该是动词,混合大小写,首字母小写,每个内部单词的首字母大写。

    例子:

    void changeGear(int newValue);
    void speedUp(int increment);
    void applyBrakes(int decrement);
    
  3. 变量:变量名称应该简短而有意义。
    • 如果以下划线开始(“_”)或美元符号“$”字符。
    • 应该是助记符,即旨在向不经意的观察者表明其使用的意图。
    • 除临时变量外,应避免使用单字符变量名
    • 临时变量的通用名称是整数的i,j,k,m和n; c,d和e表示字符。

    例子:

     
        // MountainBike类的变量
        int speed = 0;
        int gear = 1;
    
  4. 常数变量:
    • 全部为大写,单词用下划线(“_”)分隔。
    • 在预定义的类中使用了各种常量,如Float,Long,String等。

    例子:

    static final int MIN_WIDTH = 4;
    
    //在预定义的Float类中使用的一些常量变量
    public static final float POSITIVE_INFINITY = 1.0f / 0.0f;
    public static final float NEGATIVE_INFINITY = -1.0f / 0.0f;
    public static final float NaN = 0.0f / 0.0f;
    
  5. 包:
    • 唯一包名称的前缀始终以全小写ASCII字母书写,并且应该是顶级域名之一,如com,edu,gov,mil,net,org。
    • 软件包名称的后续组件根据组织自己的内部命名约定而有所不同。

    例子:

    com.sun.eng
    com.apple.quicktime.v2
    
    // JDK中的java.lang包
    java.lang中

JVM如何工作 - JVM架构?

JVM(Java虚拟机)充当运行Java应用程序的运行时引擎。JVM实际上是调用java代码中存在的main方法的。JVM是JRE(Java运行时环境)的一部分。

Java应用程序称为WORA(Write Once Run Anywhere)。这意味着程序员可以在一个系统上开发Java代码,并且可以期望它在任何其他支持Java的系统上运行而无需任何调整。由于JVM,这一切都是可能的。

当我们编译.java文件时,Java编译器会生成.jlass文件中存在的具有相同类名的.class文件(包含字节代码)。当我们运行它时,这个.class文件会进入各个步骤。这些步骤一起描述了整个JVM。

类加载器子系统
它主要负责三个活动。

  • 载入中
  • 链接
  • 初始化

加载:类加载器读取.class文件,生成相应的二进制数据并将其保存在方法区域中。对于每个.class文件,JVM在方法区域中存储以下信息。

  • 加载类及其直接父类的完全限定名称。
  • 无论的.class文件与类或接口或枚举
  • 修改器,变量和方法信息等

加载.class文件后,JVM会创建一个Class类型的对象,以在堆内存中表示此文件。请注意,此对象的类型为java.lang包中预定义的类。程序员可以使用这个Class对象来获取类级别信息,如类名,父名,方法和变量信息等。要获得此对象引用,我们可以使用Object类的getClass()方法。

// A Java program to demonstrate working of a Class type 
// object created by JVM to represent .class file in 
// memory. 
import java.lang.reflect.Field; 
import java.lang.reflect.Method; 
  
// Java code to demonstrate use of Class object 
// created by JVM 
public class Test 
{ 
    public static void main(String[] args) 
    { 
        Student s1 = new Student(); 
  
        // Getting hold of Class object created 
        // by JVM. 
        Class c1 = s1.getClass(); 
  
        // Printing type of object using c1. 
        System.out.println(c1.getName()); 
  
        // getting all methods in an array 
        Method m[] = c1.getDeclaredMethods(); 
        for (Method method : m) 
            System.out.println(method.getName()); 
  
        // getting all fields in an array 
        Field f[] = c1.getDeclaredFields(); 
        for (Field field : f) 
            System.out.println(field.getName()); 
    } 
} 
  
// A sample class whose information is fetched above using 
// its Class object. 
class Student 
{ 
    private String name; 
    private int roll_No; 
  
    public String getName()  {  return name;   } 
    public void setName(String name) { this.name = name; } 
    public int getRoll_no()  { return roll_No;  } 
    public void setRoll_no(int roll_no) { 
        this.roll_No = roll_no; 
    } 
} 

输出:

Student
getName
setName
getRoll_no
setRoll_no
name
roll_No

注意:对于每个加载的.class文件,只创建一个 Class对象。

Student s2 = new Student();
// c2 will point to same object where 
// c1 is pointing
Class c2 = s2.getClass();
System.out.println(c1==c2); // true

链接:执行验证,准备和(可选)解决方案。

  • 验证:它确保.class文件的正确性,即它检查此文件是否正确格式化并由有效编译器生成。如果验证失败,我们会得到运行时异常java.lang.VerifyError
  • 准备:JVM为类变量分配内存并将内存初始化为默认值。
  • 解决方案:这是使用直接引用替换类型的符号引用的过程。通过搜索方法区域来定位引用的实体来完成。

初始化:在此阶段,所有静态变量都分配有在代码和静态块(如果有)中定义的值。这在类中从上到下执行,在类层次结构中从父到子执行。
一般来说,有三个类加载器:

  • Bootstrap类加载器:每个JVM实现必须有一个引导类加载器,能够加载受信任的类。它加载JAVA_HOME / jre / lib目录中的核心Java API类。此路径通常称为引导路径。它以C,C ++等本地语言实现。
  • 扩展类加载器:它是bootstrap类加载器的子代。它加载扩展目录JAVA_HOME / jre / lib / ext(扩展路径)中存在的类或java.ext.dirs系统属性指定的任何其他目录。它由sun.misc.Launcher $ ExtClassLoader类在java中实现。
  • 系统/应用程序类加载器:扩展类加载器的子代。它负责从应用程序类路径加载类。它在内部使用映射到java.class.path的Environment Variable。它也是由sun.misc.Launcher $ AppClassLoader类在Java中实现的。
// Java code to demonstrate Class Loader subsystem 
public class Test 
{ 
    public static void main(String[] args) 
    { 
        // String class is loaded by bootstrap loader, and 
        // bootstrap loader is not Java object, hence null 
        System.out.println(String.class.getClassLoader()); 
  
        // Test class is loaded by Application loader 
        System.out.println(Test.class.getClassLoader()); 
    } 
}     

输出:

null
sun.misc.Launcher$AppClassLoader@73d16e93

注意: JVM遵循委托 - 层次结构原则来加载类。系统类加载器委托加载请求到扩展类加载器和扩展类加载器委托请求到引导程序类加载器。如果在boot-strap路径中找到类,则加载类,否则请求再次转移到扩展类加载器,然后再转移到系统类加载器。最后,如果系统类加载器无法加载类,那么我们得到运行时异常java.lang.ClassNotFoundException

JVM

JVM Memory 
Method区域:在方法区域中,存储所有类级信息,如类名,直接父类名,方法和变量信息等,包括静态变量。每个JVM只有一个方法区域,它是一个共享资源。

堆区域:所有对象的信息存储在堆区域中。每个JVM还有一个堆区域。它也是一种共享资源。

堆栈区域:对于每个线程,JVM创建一个存储在此处的运行时堆栈。该堆栈的每个块都称为激活记录/堆栈帧,用于存储方法调用。该方法的所有局部变量都存储在相应的框架中。线程终止后,它的运行时堆栈将被JVM销毁。它不是共享资源。

PC寄存器:存储线程当前执行指令的地址。显然每个线程都有独立的PC寄存器。

本机方法堆栈:对于每个线程,都会创建单独的本机堆栈。它存储本机方法信息。

JVM2

执行引擎
执行引擎执行.class(字节码)。它逐行读取字节码,使用各种存储区中的数据和信息并执行指令。它可分为三个部分: -

  • 解释器:它逐行解释字节码然后执行。这里的缺点是,当多次调用一个方法时,每次都需要解释。
  • 即时编译器(JIT):它用于提高解释器的效率。它编译整个字节码并将其更改为本机代码,因此每当解释器看到重复的方法调用时,JIT就会为该部分提供直接的本机代码,以便重新解释不需要,因此提高了效率。
  • 垃圾收集器:它会销毁未引用的对象。有关垃圾收集器的更多信息,请参阅垃圾收集器

Java Native Interface(JNI):
它是一个与Native方法库交互并提供执行所需的本机库(C,C ++)的接口。它使JVM能够调用C / C ++库并由C / C ++库调用,这些库可能是特定于硬件的。

本机方法库:
它是执行引擎所需的本机库(C,C ++)的集合。

Java虚拟机(JVM)堆栈区域

对于每个线程,JVM在创建线程时创建一个单独的堆栈。Java虚拟机堆栈的内存不需要是连续的。Java虚拟机只在Java Stacks上直接执行两个操作:它推送和弹出帧。并且特定线程的堆栈可以被称为运行时堆栈。由该线程执行的每个方法调用都存储在相应的运行时堆栈中,包括参数,局部变量,中间计算和其他数据。完成方法后,将删除堆栈中的相应条目。在完成所有方法调用之后,堆栈变为空,并且在终止线程之前,JVM会破坏空堆栈。存储在堆栈中的数据可用于相应的线程,并且不可用于其余线程。因此我们可以说本地数据是线程安全的。堆栈中的每个条目称为堆栈帧激活记录

Java的堆

堆栈帧结构
堆栈帧基本上由部分组成:局部变量数组,操作数堆栈和帧数据。当JVM调用Java方法时,首先检查类数据以确定方法在局部变量数组中所需的字数(局部变量数组和操作数堆栈的大小,以单词为单位测量)和操作数堆栈。它为调用的方法创建适当大小的堆栈帧,并将其推送到Java堆栈。

1.局部变量阵列(LVA):

  • 堆栈帧的局部变量部分被组织为从零开始的单词数组。
  • 它包含方法的所有参数和局部变量。
  • 阵列中的每个插槽或条目都是4个字节。
  • int,float和reference类型的值占用数组中的1个条目或槽,即4个字节。
  • double和long的值占据阵列中的2个连续条目,即总共8个字节。
  • Byte,short和char值将在存储之前转换为int类型并占用1个槽,即4个字节。
  • 但是存储布尔值的方式从JVM到JVM是不同的。但是大多数JVM在局部变量数组中为布尔值提供了1个槽。
  • 参数首先按声明的顺序放入局部变量数组中。
  • 例如:让我们考虑一个具有方法bike()的类示例,然后局部变量数组将如下图所示:
// Class Declaration
class Example
{
  public void bike(int i, long l, float f, 
               double d, Object o, byte b)
  {
     return 0;
  } 
}   

自行车的局部变量数组()

2.操作数堆栈(OS):

  • JVM使用操作数堆栈作为粗略工作的工作空间,或者我们可以说存储中间计算的结果。
  • 操作数堆栈被组织为像本地变量数组一样的单词数组。但是这不是通过使用像局部变量数组这样的索引来访问的,而是通过一些可以将值推送到操作数堆栈的指令以及一些可以从操作数堆栈和一些可以执行所需操作的指令中弹出值的指令来访问它。
  • 例如:以下是JVM将如何使用下面的代码,它将减去包含两个整数的两个局部变量,并将int结果存储在第三个局部变量中:

操作数堆栈的汇编代码指令

  • 因此,前两条指令iload_0iload_1将从局部变量数组中推送操作数堆栈中的值。并且指令isub将减去这两个值并将结果存储回操作数堆栈,并且在istore_2之后,结果将从操作数堆栈中弹出并存储到位置2的局部变量数组中。

LVA和OS的工作

3.帧数据(FD):

  • 它包含与该特定方法相关的所有符号引用(常量池分辨率)和常规方法返回。
  • 它还包含对Exception表的引用,该表在异常情况下提供相应的catch块信息。

Java中的JVM Shutdown Hook

Shutdown Hooks是一种特殊的结构,允许开发人员插入一段代码,以便在JVM关闭时执行。如果我们需要在VM关闭的情况下进行特殊的清理操作,这就派上用场了。

使用常规构造处理此问题,例如确保在应用程序存在之前调用特殊过程(调用System.exit(0))将不适用于VM因外部原因而关闭的情况(例如kill请求)来自O / S),或由于资源问题(内存不足)。正如我们将很快看到的那样,关闭钩子可以轻松解决这个问题,允许我们提供一个任意的代码块,JVM在关闭时会调用它。

从表面看,使用关闭钩是直接向前的。我们所要做的就是编写一个扩展java.lang.Thread类的类,并在public void run()方法中提供我们想要在VM关闭时执行的逻辑。然后我们通过调用Runtime.getRuntime()。addShutdownHook(Thread)方法将此类的实例注册为VM的关闭挂钩。如果需要删除以前注册的关闭钩子,Runtime类也会提供removeShutdownHook(Thread)方法。

例如 :

public class ShutDownHook 
{ 
  public static void main(String[] args) 
  { 
  
    Runtime.getRuntime().addShutdownHook(new Thread() 
    { 
      public void run() 
      { 
        System.out.println("Shutdown Hook is running !"); 
      } 
    }); 
    System.out.println("Application Terminating ..."); 
  } 
} 

当我们运行上面的代码时,您将看到JVM在完成main方法的执行时调用了shutdown钩子。

输出:

Application Terminating ...
Shutdown Hook is running !

简单吧?是的。

虽然编写一个关闭钩子非常简单,但是需要知道关闭钩子后面的内部才能正确使用它们。因此,在本文中,我们将探讨关闭钩子设计背后的一些“陷阱”。

1.在某些情况下可能无法执行关机挂钩!
首先要记住的是,无法保证关闭挂钩始终会运行。如果JVM由于某些内部错误而崩溃,那么它可能会在没有机会执行单个指令的情况下崩溃。此外,如果O / S发出SIGKILL(http://en.wikipedia.org/wiki/SIGKILL)信号(Unix / Linux中的kill -9)或TerminateProcess(Windows),则应用程序需要立即终止甚至等待任何清理活动。除了上述内容之外,还可以通过调用Runime.halt()方法来终止JVM而不允许关闭挂钩运行。

当应用程序正常终止时(当所有线程完成或调用System.exit(0)时),将调用关闭挂钩。此外,当JVM由于外部原因(例如用户请求终止(Ctrl + C))而关闭时,由O / S(正常kill命令,不带-9)或操作系统关闭时发出SIGTERM 。

2.一旦启动,可在完成前强行停止关机钩。
这实际上是前面解释过的案例的一个特例。虽然钩子开始执行,但在操作系统关闭的情况下,可以在它完成之前终止。在这种情况下,一旦给出SIGTERM,O / S就等待进程终止指定的时间。如果进程未在此时间限制内终止,则O / S通过发出SIGTERM(或Windows中的对应方)强制终止进程。所以当关闭钩子执行到一半时就会发生这种情况。

因此,建议确保快速写入关机挂钩,确保它们快速完成,并且不会导致死锁等情况。此外,JavaDoc [1]特别提到不应该执行长计算或等待关闭钩子中的用户I / O操作。

3.我们可以有多个关机挂钩,但不保证执行顺序。
正如您可能已经通过addShutdownHook方法的方法名称(而不是setShutdownHook)正确猜到的那样,您可以注册多个关闭挂钩。但是JVM无法保证这些多个钩子的执行顺序。JVM可以以任意顺序执行关闭挂钩。此外,JVM可以同时执行所有这些挂钩。

4.我们无法在关闭挂钩中注册/取消注册关闭挂钩
一旦JVM启动关闭序列,就不允许添加更多或删除任何现有的关闭挂钩。如果尝试这样做,JVM将抛出IllegalStateException。

5.一旦关闭序列启动,它只能由Runtime.halt()停止。
一旦关闭序列启动,只有Runtime.halt()(强制终止JVM)才能停止执行关闭序列(除了SIGKILL等外部影响)。这意味着在Shutdown Hook中调用System.exit()将不起作用。实际上,如果你在Shutdown Hook中调用System.exit(),VM可能会卡住,我们可能不得不强行终止进程。

6.使用shutdown hooks需要安全权限。
如果我们使用Java安全管理器,则执行添加/删除关闭挂钩的代码需要在运行时具有shutdownHooks权限。如果我们在安全环境中未经许可调用此方法,则会导致SecurityException。

Java类文件

Java类文件是包含Java字节码和具有文件.class扩展,可以通过执行JVM。由于编译成功,Java编译器会从.java文件创建Java类文件。我们知道单个Java编程语言源文件(或者我们可以说.java文件)可能包含一个类或多个类。因此,如果.java文件具有多个类,则每个类将编译为单独的类文件。
例如: 将以下代码保存为系统上的Test.java

// Compiling this Java program would 
// result in multiple class files. 
  
class Sample 
{ 
  
} 
  
// Class Declaration 
class Student 
{ 
  
} 
// Class Declaration 
class Test 
{ 
       public static void main(String[] args)    
       { 
           System.out.println("Class File Structure"); 
       } 
}  

用于编译:

javac Test.java

编译后,相应文件夹中将有3个类文件命名为:

  • Sample.class
  • Student.class
  • Test.class

单个类文件结构包含描述类文件的属性。
类文件结构的表示


 

ClassFile 
{
     magic_number;
     minor_version;
     major_version;
     constant_pool_count;
     constant_pool[];
     access_flags;
     this_class;
     super_class;
     interfaces_count;
     interfaces[];
     fields_count;
     fields[];
     methods_count;
     methods[];
     attributes_count;
     attributes[];
}

类文件的元素如下:

// class Declaration
class Sample
{
   public static void main(String[] args)
   {
       System.out.println("Magic Number");           
   }
}

步骤1: 使用javac Sample.java进行编译
步骤2:现在打开Sample.class文件。它看起来像以下。

Sample.class文件

步骤3:现在从文件的起始处删除此Sample.class文件中的至少单个符号并保存。
第4步:现在尝试使用java Sample命令运行它并查看魔法,即您将获得运行时异常(请参见下图中突出显示的文本):
运行时异常由于幻数无效

注意:这取决于您删除.class文件数据的数量。

注意:较低版本编译器生成的.class文件可以由高版本JVM执行,但较高版本编译器生成的.class文件不能由较低版本的JVM执行。如果我们尝试执行,我们将获得运行时异常。

此演示适用于Windows操作系统,如下所示:

步骤1:打开命令提示符窗口并尝试分别使用以下命令检查java编译器版本和JVM版本(图像中突出显示的文本是命令)
1.8版本的输出将是:
Java编译器版本
JVM版本

第2步:现在检查可能高于或低于已安装的其他版本。这个下载链接。
并将其安装到您的PC或笔记本电脑上,并记下安装地址。
步骤3:打开第二个命令提示符窗口,并设置第二步安装的已安装的jdk的bin文件夹的路径。并检查Java编译器版本和JVM版本。
检查JDK 1.6的版本
第4步:现在在第一个命令提示符下编译任何有效的.java文件。例如:参见上面的Sample.java文件。编译为:
使用编译器版本1.8进行编译
步骤5:现在在第二个命令提示符窗口尝试运行上面编译的代码类文件,看看会发生什么。有一个运行时异常,我在下面的图像中突出显示。

由于类文件的主要版本和次要版本无效而导致运行时异常

注意:内部jdk 1.5版本表示49.0和1.6表示50.0和1.7表示51.0等类文件版本,其中小数点前的数字表示major_version,小数点后的数字表示minor_version。

  1. magic_number:类文件的前4个字节称为magic_number。这是一个预定义的值,JVM用它来识别.class文件是否由有效的编译器生成。预定义的值将是十六进制形式,即0xCAFEBABE
    现在让我们看看当JVM找不到有效的幻数时会发生什么。假设我们有一个名为Sample.java.java文件,如下所示,并按照系统上的分步过程进行操作。
  2. minor_version&major_version:这两者一起代表.class文件版本。JVM将使用这些版本来确定哪个版本的编译器生成当前的.class文件。我们将类文件的版本表示为Mm,其中M代表major_version,m代表minor_version
    1. constant_pool_count:它表示常量池中存在的常量数(编译Java文件时,对变量和方法的所有引用都存储在类的常量池中作为符号引用)
    2. constant_pool []:表示常量池文件中存在的常量信息。
    3. access_flags:它提供有关向类文件声明的修饰符的信息。
    4. this_class:它表示类文件的完全限定名称。
    5. super_class:它表示当前类的直接超类的完全限定名称。考虑上面的Sample.java文件。当我们编译它时,我们可以说this_class将是Sample类,super_class将是Object类。
    6. interface_count:返回当前类文件实现的接口数。
    7. interface []:返回当前类文件实现的接口信息。
    8. fields_count:表示当前类文件中存在的字段数(静态变量)
    9. fields []:表示当前类文件中存在的字段(静态变量)信息。
    10. method_count:表示当前类文件中存在的方法数。
    11. method []:返回当前类文件中存在的所有方法的信息。
    12. attributes_count:返回当前类文件中存在的属性(实例变量)的数量。
    13. attributes []:它提供有关当前类文件中存在的所有属性的信息。

JDK,JRE和JVM之间的差异

JAVA开发套件

Java Development Kit(JDK)是一个用于开发Java应用程序和applet的软件开发环境。它包括Java运行时环境(JRE),解释器/加载器(Java),编译器(javac),归档器(jar),文档生成器(Javadoc)以及Java开发中所需的其他工具。

JAVA运行时环境

JRE代表“Java Runtime Environment”,也可以写成“Java RTE” .Java Runtime Environment提供了执行Java应用程序的最低要求。它由Java虚拟机(JVM),核心类支持文件组成

查看上文中的jvm是如何工作的

它是:

  • 一个规范已指定Java虚拟机的工作。但是实现提供者可以独立选择算法。它的实施由Sun和其他公司提供。
  • 一个实现是符合JVM规范要求的计算机程序
  • 运行时实例每当在命令提示符下编写java命令以运行java类时,都会创建一个JVM实例。

JDK,JRE和JVM之间存在差异

为了理解这三者之间的区别,让我们考虑下图。
区别

  • JDK - Java Development Kit(简称JDK)是Kit,它提供了开发和执行(运行) Java程序的环境。JDK是一个包(或包),包括两件事
    • 开发工具(提供开发java程序的环境)
    • JRE(执行你的java程序)。

    注意: JDK仅供Java开发人员使用。

  • JRE - Java运行时环境(简称JRE)是一个安装包,它提供的环境只能在您的机器上运行(不开发) Java程序(或应用程序)。JRE仅供那些只想运行Java程序即系统最终用户的人使用。
  • JVM - Java虚拟机(JVM)是​​JDK和JRE中非常重要的一部分,因为它包含或内置于两者中。无论您使用JRE还是JDK运行的Java程序都进入JVM,JVM负责逐行执行java程序,因此它也称为解释程序。

JRE和JDK如何工作?

JRE包括什么?
JRE由以下组件组成:

  • 部署技术,包括部署,Java Web Start和Java Plug-in。
  • 用户界面工具包,包括抽象窗口工具包(AWT),Swing,Java 2D,辅助功能,图像I / O,打印服务,声音,拖放(DnD)输入方法
  • 集成库,包括接口定义语言(IDL),Java数据库连接(JDBC),Java命名和目录接口(JNDI),远程方法调用(RMI),Internet Inter-Orb协议上的远程方法调用(RMI-IIOP)脚本
  • 其他基础库,包括国际支持,输入/输出(I / O),扩展机制,Bean,Java管理扩展(JMX),Java本机接口(JNI),数学,网络,覆盖机制,安全性,序列化Java for Java处理(XML JAXP)
  • Lang和util库,包括lang和util,管理,版本控制,zip,仪器,反射,集合,并发实用程序,Java Archive(JAR),Logging,Preferences API,Ref ObjectsRegular Expressions
  • Java虚拟机(JVM),包括Java HotSpot客户端服务器虚拟机

JRE如何运作?
要了解JRE的工作原理,让我们考虑保存为Example.java的Java源文件。该文件被编译成一组字节代码,存储在“ .class ”文件中。这里将是“ Example.class ”。
编译时间

下图描述了在编译时完成的操作。

在运行时,解释器加载,检查和运行字节代码。解释器具有以下两个功能:

  • 类装载机

类加载器加载执行程序所需的所有必要类。它通过将本地文件系统的名称空间与通过网络导入的名称空间分开来提供安全性。这些文件可以从硬盘,网络或其他来源加载。

  • 字节码验证器

JVM通过字节代码验证程序放置代码,该代码验证程序检查格式并检查非法代码。例如,非法代码是违反对象访问权限或违反指针实现的代码。

字节代码验证程序可确保代码符合JVM规范,并且不会违反系统完整性。
运行

  • Intrepreter

在运行时,解释器加载,检查和运行字节代码。解释器具有以下两个功能:

  • 执行字节代码
  • 对底层硬件进行适当的调用

这两个操作都可以显示为:
interpreter and runtime
要了解JDK和JRE之间的交互,请考虑下图。
interaction

JVM是如何工作的?

JVM在Java程序的运行时成为JRE的实例。它被广泛称为运行时解释器.JVM很大程度上有助于从使用JDK程序库的程序员中抽象内部实现。

有关JVM的详细工作请查看上文中的jvm是如何工作的

JVM是否创建Main类的对象(具有main()的类)?

考虑以下程序。

class Main { 
    public static void main(String args[]) 
    { 
        System.out.println("wellcome subscribe to geekmoney"); 
    } 
} 

输出:

wellcome subscribe to geekmoney

JVM是否创建了Main类的对象?
答案是不”。我们已经研究过Java中main()静态的原因是确保可以在没有任何实例的情况下调用main()。为了证明这一点,我们可以看到以下程序编译并运行正常。

// Not Main is abstract 
abstract class Main { 
    public static void main(String args[]) 
    { 
        System.out.println("wellcome subscribe to geekmoney"); 
    } 
} 

输出:

wellcome subscribe to geekmoney

由于我们无法在Java中创建抽象类的对象,因此可以保证带有main()的类的对象不是由JVM创建的。


感谢大家,本文仅供学习查阅,不可用作商业用途,如果转发,请注明原文链接,谢谢。

本文翻译于国外知识论坛,如果翻译有误或者侵犯版权,请联系我,我会尽快删除。

如果觉得本文有帮助,请关注我,我会持续更新的。希望可以在程序员的路上越走越远。

文章我会同步更新到订阅号:geekmoney。如果有兴趣,欢迎订阅。后续精彩内容不断,希望能和各位一起进步,共同发展

发布了2 篇原创文章 · 获赞 6 · 访问量 430

猜你喜欢

转载自blog.csdn.net/smile_795/article/details/90411356