扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
在DOS 窗口下。进入你写的程序目录。例如你把写的程序保存到D:\\a.java\x0d\x0a\x0d\x0a这个时候在DOS下进入D:\\\x0d\x0a\x0d\x0a然后调用JDK的编译器javac程序\x0d\x0a\x0d\x0aD:\\javac a.java\x0d\x0a\x0d\x0a如果成功,在同一目录下会产生一个扩展名为.class的字节码,然后调用JDK的java程序是用来启动虚拟机\x0d\x0a\x0d\x0aD:\\java a\x0d\x0a\x0d\x0a如果,没有编写上的错误,就可以看到运行结果了。
让客户满意是我们工作的目标,不断超越客户的期望值来自于我们对这个行业的热爱。我们立志把好的技术通过有效、简单的方式提供给客户,将通过不懈努力成为客户在信息化领域值得信任、有价值的长期合作伙伴,公司提供的服务项目有:申请域名、虚拟空间、营销软件、网站建设、韶关网站维护、网站推广。
虚拟机是一种抽象化的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的。
Java虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
这种解释应该算是正确的,但是只描述了虚拟机的外部行为和功能,并没有针对内部原理做出说明。一般情况下我们不需要知道虚拟机的运行原理,只要专注写java代码就可以了,这也正是虚拟机之所以存在的原因--屏蔽底层操作系统平台的不同并且减少基于原生语言开发的复杂性,使java这门语言能够跨各种平台(只要虚拟机厂商在特定平台上实现了虚拟机),并且简单易用。这些都是虚拟机的外部特性,但是从这些信息来解释虚拟机,未免太笼统了,无法让我们知道内部原理。
从进程的角度解释JVM
让我们尝试从操作系统的层面来理解虚拟机。我们知道,虚拟机是运行在操作系统之中的,那么什么东西才能在操作系统中运行呢?当然是进程,因为进程是操作系统中的执行单位。可以这样理解,当它在运行的时候,它就是一个操作系统中的进程实例,当它没有在运行时(作为可执行文件存放于文件系统中),可以把它叫做程序。
对命令行比较熟悉的同学,都知道其实一个命令对应一个可执行的二进制文件,当敲下这个命令并且回车后,就会创建一个进程,加载对应的可执行文件到进程的地址空间中,并且执行其中的指令。下面对比C语言和Java语言的HelloWorld程序来说明问题。
首先编写C语言版的HelloWorld程序。
编译C语言版的HelloWorld程序:
gcc HelloWorld.c -o HelloWorld
运行C语言版的HelloWorld程序:
zhangjg@linux:/deve/workspace/HelloWorld/src$ ./HelloWorld
hello world
gcc编译器编译后的文件直接就是可被操作系统识别的二进制可执行文件,当我们在命令行中敲下 ./HelloWorld这条命令的时候, 直接创建一个进程, 并且将可执行文件加载到进程的地址空间中, 执行文件中的指令。
作为对比, 我们看一下Java版HelloWord程序的编译和执行形式。
首先编写源文件HelloWord.java :
编译Java版的HelloWorld程序:
运行Java版的HelloWorld程序:
zhangjg@linux:/deve/workspace/HelloJava/src$ java -classpath . HelloWorld
HelloWorld
从上面的过程可以看到, 我们在运行Java版的HelloWorld程序的时候, 敲入的命令并不是 ./HelloWorld.class 。 因为class文件并不是可以直接被操作系统识别的二进制可执行文件 。 我们敲入的是java这个命令。 这个命令说明, 我们首先启动的是一个叫做java的程序, 这个java程序在运行起来之后就是一个JVM进程实例。
上面的命令执行流程是这样的:
java命令首先启动虚拟机进程,虚拟机进程成功启动后,读取参数“HelloWorld”,把他作为初始类加载到内存,对这个类进行初始化和动态链接(关于类的初始化和动态链接会在后面的博客中介绍),然后从这个类的main方法开始执行。也就是说我们的.class文件不是直接被系统加载后直接在cpu上执行的,而是被一个叫做虚拟机的进程托管的。首先必须虚拟机进程启动就绪,然后由虚拟机中的类加载器加载必要的class文件,包括jdk中的基础类(如String和Object等),然后由虚拟机进程解释class字节码指令,把这些字节码指令翻译成本机cpu能够识别的指令,才能在cpu上运行。
从这个层面上来看,在执行一个所谓的java程序的时候,真真正正在执行的是一个叫做Java虚拟机的进程,而不是我们写的一个个的class文件。这个叫做虚拟机的进程处理一些底层的操作,比如内存的分配和释放等等。我们编写的class文件只是虚拟机进程执行时需要的“原料”。这些“原料”在运行时被加载到虚拟机中,被虚拟机解释执行,以控制虚拟机实现我们java代码中所定义的一些相对高层的操作,比如创建一个文件等,可以将class文件中的信息看做对虚拟机的控制信息,也就是一种虚拟指令。
编程语言也有自己的原理, 学习一门语言, 主要是把它的原理搞明白。 看似一个简单的HelloWorld程序, 也有很多深入的内容值得剖析。
JVM体系结构简介
为了展示虚拟机进程和class文件的关系,特意画了下面一张图:
根据上图表达的内容,我们编译之后的class文件是作为Java虚拟机的原料被输入到Java虚拟机的内部的,那么具体由谁来做这一部分工作呢?其实在Java虚拟机内部,有一个叫做类加载器的子系统,这个子系统用来在运行时根据需要加载类。注意上面一句话中的“根据需要”四个字。在Java虚拟机执行过程中,只有他需要一个类的时候,才会调用类加载器来加载这个类,并不会在开始运行时加载所有的类。就像一个人,只有饿的时候才去吃饭,而不是一次把一年的饭都吃到肚子里。一般来说,虚拟机加载类的时机,在第一次使用一个新的类的时候。本专栏后面的文章会具体讨论Java中的类加载器。
由虚拟机加载的类,被加载到Java虚拟机内存中之后,虚拟机会读取并执行它里面存在的字节码指令。虚拟机中执行字节码指令的部分叫做执行引擎。就像一个人,不是把饭吃下去就完事了,还要进行消化,执行引擎就相当于人的肠胃系统。在执行的过程中还会把各个class文件动态的连接起来。关于执行引擎的具体行为和动态链接相关的内容也会在本专栏后续的文章中进行讨论。
我们知道,Java虚拟机会进行自动内存管理。具体说来就是自动释放没有用的对象,而不需要程序员编写代码来释放分配的内存。这部分工作由垃圾收集子系统负责。
从上面的论述可以知道, 一个Java虚拟机实例在运行过程中有三个子系统来保障它的正常运行,分别是类加载器子系统, 执行引擎子系统和垃圾收集子系统。 如下图所示:
虚拟机的运行,必须加载class文件,并且执行class文件中的字节码指令。它做这么多事情,必须需要自己的空间。就像人吃下去的东西首先要放在胃中。虚拟机也需要空间来存放个中数据。首先,加载的字节码,需要一个单独的内存空间来存放;一个线程的执行,也需要内存空间来维护方法的调用关系,存放方法中的数据和中间计算结果;在执行的过程中,无法避免的要创建对象,创建的对象需要一个专门的内存空间来存放。关于虚拟机运行时数据区的内容,也会出现在本专栏后续的文章中。虚拟机的运行时内存区大概可以分成下图所示的几个部分。(这里只是大概划分,并没有划分的很精细)
总结
写到这里,基本上关于我对java虚拟机的理解就写完了。这篇文章的主题虽然是深入理解Java虚拟机,但是你可能感觉一点也不“深入”,也只是泛泛而谈。我也有这样的感觉。限于自己水平有限,也只能这样了,要是想深入理解java虚拟机,强烈建议读一下三本书:
《深入Java虚拟机》
《深入理解Java虚拟机JVM高级特性与最佳实践》
《Java虚拟机规范》
其实我也读过这几本书,但是它们对虚拟机的解释也是基于一个外部模型,而没有深入剖析虚拟机内部的实现原理。虚拟机是一个大而复杂的东西,实现虚拟机的人都是大牛级别的,如果不是参与过虚拟机的实现,应该很少有人能把它参透。本专栏后面的一些文章也参考了这三本书, 虽然讲解Java语法的书不计其数, 但是深入讲解虚拟机的书, 目前为止我就见过这三本,并且网上的资料也不是很多。
最后做一个总结:
1 虚拟机并不神秘,在操作系统的角度看来,它只是一个普通进程。
2 这个叫做虚拟机的进程比较特殊,它能够加载我们编写的class文件。如果把JVM比作一个人,那么class文件就是我们吃的食物。
3 加载class文件的是一个叫做类加载器的子系统。就好比我们的嘴巴,把食物吃到肚子里。
4 虚拟机中的执行引擎用来执行class文件中的字节码指令。就好比我们的肠胃,对吃进去的食物进行消化。
5 虚拟机在执行过程中,要分配内存创建对象。当这些对象过时无用了,必须要自动清理这些无用的对象。清理对象回收内存的任务由垃圾收集器负责。就好比人吃进去的食物,在消化之后,必须把废物排出体外,腾出空间可以在下次饿的时候吃饭并消化食物。
扩展资料:
关于JAVA虚拟机的参数说明如下:
1、运行class文件
执行带main方法的class文件,Java虚拟机[3] 命令参数行为:
java CLASS文件名
注意:CLASS文件名不要带文件后缀.class
例如:
java Test
如果执行的class文件是带包的,即在类文件中使用了:
package ;包名
那应该在包的基路径下执行,Java虚拟机命令行参数:
java ;包名.CLASS文件名
例如:
PackageTest.java中,其包名为:com.ee2ee.test,对应的语句为:
package com.ee2ee.test;
PackageTest.java及编译后的class文件PackageTest.class的存放目录如下:
classes
|__com
|__ee2ee
|__test
|__PackageTest.java
|__PackageTest.class
要运行PackageTest.class,应在classes目录下执行:
java com.ee2ee.test.PackageTest
2、运行jar文件中的class
原理和运行class文件一样,只需加上参数-cp jar文件名;即可。
例如:执行test.jar中的类com.ee2ee.test.PackageTest,命令行如下:
java -cp test.jar com.ee2ee.test.PackageTest
3、显示JDK版本信息
当一台机器上有多个jdk版本时,需要知道当前使用的是那个版本的jdk,使用参数-version即可知道其版本,命令行为:
java -version
4、增加虚拟机可以使用的最大内存
Java虚拟机可使用的最大内存是有限制的,缺省值通常为64MB或128MB。
如果一个应用程序为了提高性能而把数据加载内存中而占用较大的内存,比如超过了默认的最大值128MB,需要加大java虚拟机可使用的最大内存,否则会出现Out of Memory的异常。启动java时,需要使用如下两个参数:
-Xms java虚拟机初始化时使用的内存大小
-Xmx java虚拟机可以使用的最大内存
以上两个命令行参数中设置的size,可以带单位,例如:256m表示256MB
举例说明:
java -Xms128m -Xmx256m ...
表示Java虚拟机初始化时使用的内存为128MB,可使用的最大内存为256MB。
对于tomcat,可以修改其脚本catalina. sh(Unix平台)或catalina.bat(Windows平台),设置变量JAVA_OPTS即可,例如:
JAVA_OPTS='-Xms128m -Xmx256m'
参考资料:百度百科-java虚拟机
第一步(编译): 创建完源文件之后,程序会先被编译为.class文件。Java编译一个类时,如果这个类所依赖的类还没有被编译,编译器就会先编译这个被依赖的类,然后引用,否则直接引用,这个有点象make。
如果java编译器在指定目录下找不到该类所其依赖的类的.class文件或者.java源文件的话,编译器话报“cant find symbol”的错误。
第二步(运行):java类运行的过程大概可分为两个过程:1、类的加载 2、类的执行。需要说明的是:JVM主要在程序第一次主动使用类的时候,才会去加载该类。也就是说,JVM并不是在一开始就把一个程序就所有的类都加载到内存中,而是到不得不用的时候才把它加载进来,而且只加载一次。
特别说明:java类中所有public和protected的实例方法都采用动态绑定机制,所有私有方法、静态方法、构造器及初始化方法clinit都是采用静态绑定机制。而使用动态绑定机制的时候会用到方法表,静态绑定时并不会用到。
扩展资料:
Java整个编译以及运行的过程相当繁琐,本文通过一个简单的程序来简单的说明整个流程。
Java代码编译:是由Java源码编译器来完成;
Java字节码的执行:是由JVM执行引擎来完成
Java程序从源文件创建到程序运行要经过两大步骤:
1、源文件由编译器编译成字节码(ByteCode)
2、字节码由java虚拟机解释运行。因为java程序既要编译同时也要经过JVM的解释运行,所以说Java被称为半解释语言( "semi-interpreted" language)。
由java编译器把源文件编译成虚拟机可以识别的二进制代码称为字节码。
而字节码是由java解释器去解释执行的。
网上有这样一套编码,能使java虚拟机崩溃:
package jvm;
public class Crash {
public static void main(String[] args) {
//Object[] o = {“abc”};初始值赋值,不会有影响。
Object[] o = null;
while (true) {
o = new Object[] { o };
//输出的话,jvm就不会崩溃。
//System.out.println(o);
}
}
}
程序运行十几秒之后,控制台会出现这样的错误:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at jvm.Crash.main(Crash.java:10)
很明显,超出内存空间错误。
我将原程序随意改了一下,如赋初始值等,对程序无影响。
可是我将死循环中的o输出在控制台的时候,jvm居然一直都不崩,为什么输出的话,就不会超出内存空间呢?
我看来,原程序能够使Jvm崩溃,是因为死循环中,通过旧对象,不断创建出新的对象,即创造的对象是互相引用的,所以GC是不会回收它们的,造成堆栈溢出。
仿照这个例子,我写了一个简单的类,模仿例子程序中的数组,如下:
package jvm;
public class MyCrash {
public static void main(String[] args) {
JvmBean j = null;
while(true){
j = new JvmBean(j);
//无论输出不输出,jvm都会崩溃
//System.out.println(j);
}
}
}
结果便是控制台输出如下的错误:
Exception in thread "main" java.lang.StackOverflowError
at jvm.JvmBean.init(JvmBean.java:5)
at jvm.JvmBean.init(JvmBean.java:5)
at jvm.JvmBean.init(JvmBean.java:5)
at jvm.JvmBean.init(JvmBean.java:5)
at jvm.JvmBean.init(JvmBean.java:5)
一长串的"at jvm.JvmBean.init(JvmBean.java:5)",后面的被省略了。
结果看来,同样也造成了jvm崩溃,可是错误类型跟例子程序的不同,说堆栈溢出错误,并且无论是否输出,错误都一样发生,为什么呢?
结论
第一个异常
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at jvm.Crash.main(Crash.java:10)是因为程序无法申请到足够的内存的时候抛出的异常,Object数组o不断指向新的Object数组,数组元素是原来的Object数组,这使得Object维数越来越高。不断申请内存空间,最终导致超出jvm中堆的最大值。堆内存溢出。为什么输出打印,时间会延长呢?!输出打印的话,虚拟机并不是不会崩溃,而是崩溃的时间大大延长了。而崩溃时间延长其实是假象,是因为输出属于IO事件,每次输出CPU都被中断,IO很耗时,所以,感觉上才会时间延长。
第二个异常
”类内部的静态属性 静态块 对象属性 构造方法。注意这一点,那就是说 bean属性会先于JvmBean的构造函数被初始化。在main函数中,new一个 JvmBean的构造函数之前,类内部的JvmBean对象要优先被初始化,这个类内部的属性bean的内部同样也包含了一个JvmBean对象需要被初始化,成循环调用,造 成了栈溢出。”所以异常才会是这个——Exception in thread "main" java.lang.StackOverflowError
1.其实就是用一个外部程序 调用java虚拟机运行你的java程序。
2.可以做一个批处理文件,在里面调用java 虚拟机运行你的java程序。
3.也可以用某种编程语言,像vb ,c 或c++编个程序,生成exe,能调用java虚拟机运行你的程序,很简单的。
【源代码】
源代码(也称源程序),是指一系列人类可读的计算机语言指令。 在现代程序语言中,源代码可以是以书籍或者磁带的形式出现,但最为常用的格式是文本文件,这种典型格式的目的是为了编译出计算机程序。
延展阅读;
Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程。
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流