java编译期、运行期
Java程序从源文件创建到程序运行要经过两大步骤:编译期:也叫前期,即源文件由编译器编译成字节码(ByteCode)运行期:也叫后期,即字节码由java虚拟机解释运行。因为java程序在后期运行时除了经过JVM的解释运行,还存在一种JIT运行,所以说Java被称为半解释语言( "semi-interpreted" language)。一、编译期编译期是指把源码交给编译器编译成计算...
Java程序从源文件创建到程序运行要经过两大步骤:
- 编译期:也叫前期,即源文件由编译器编译成字节码(ByteCode)
- 运行期:也叫后期,即字节码由java虚拟机解释运行。
因为java程序在后期运行时除了经过JVM的解释运行,还存在一种JIT运行,所以说Java被称为半解释语言( "semi-interpreted" language)。
一、编译期
编译期是指把源码交给编译器编译成计算机可以执行的文件的过程。
在Java中也就是把Java代码编成class文件的过程。编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成文本进行操作,比如:词法、语法分析,解语法糖、字节码生成等。
1、内存分配:
- 编译期分配内存:并不是说在编译期就把程序所需要的空间在内存中分配好,而是说在编译期生成的代码中产生一些指令,在运行代码时通过这些指令把程序所需的内存分配好。只不过在编译期的时候就知道分配的大小,并且知道这些内存的位置。
- 运行期分配内存:是指只有在运行期才确定内存的大小,存放的位置。
说明:在java中内存的分配是在运行期的,这也是为什么我们把jvm的内存模型称作运行时数据区(五大部分)。
2、对比静态编译:
在java中编译阶段只是生成了字节码,没有生成计算机认识的机器码。java在运行阶段才开始解释字节码,然后生成对应的机器码;或者通过JIT技术,在运行阶段将字节码编译成对应的机器码缓存起来使用。所以,java称为一种动态编译。
C++使用的是静态编译,即在编译过程就生成了对应的机器码。
1)相比静态编译,java的动态编译有一些劣势:
- JIT虽然可以极大提高性能,但是他实在运行时进行的优化,会占用用户程序运行资源;
java是动态的类型安全语言,意味着JVM需要频繁的进行类型检查;- java绝大部分方法是动态绑定的,意味着JIT在进行一些优化时(如方法内联)的难度要远大于C++的静态编译;
- java是动态扩展语言,意味着运行时加载的类可能改变程序类型的继承关系,这使得编译期很多全局优化难于C++的静态编译;
- java对象的内存分配绝大部分在堆上,而C++的对象既可以分配在栈上,也可以分配在堆上(需要手动回收),所以在内存回收时java性能要差一些
2)java动态编译优势:
- java语言的一些性能劣势是为了换取开发效率上的优势,如:动态类型安全、动态扩展、垃圾回收。
- 由于C++是静态编译,以运行期性能监控为基础的优化措施都不能做:频率预测、分支预测、裁剪未被选择的分支等;而这些java都是可以做的。
3、动态/静态语言、动态/静态类型语言、强/弱类型语言:
1)动态、静态语言:
动态语言是指运行期间可以改变其结构的语言:例如增加或者删除函数、对象、甚至代码。例如:JavaScript、Ruby、Python都是动态语言。java、C++属于静态语言。
动态语言举例:(javascript)
function Person(name, age, job)
{
this.name = name;
this.age = age;
this.job = job
this.hello = function(name){
alert("Hello, " + name);
};
person = new Person("Eric", 28, 'worker');
alert(person.name + '' + person.age + '');
person.hello("Alice");
//为对象添加方法
person.work = function(){
alert('I am working');
}
person.work();
//删除方法
delete person.work;
person.work();
2)动态、静态类型语言:
- 动态类型语言是指类型的检查是在运行时做的。在写代码时不用给变量指定数据类型,第一次赋值给变量时,在内部将数据类型记录下来。例如:JavaScript、Ruby、Python是典型的动态类型语言。
- 静态类型语言的数据类型检查发生在在编译阶段,也就是说在写程序时要声明变量的数据类型。C/C++、C#、Java都是静态类型语言的典型代表。
动态类型语言举例:(python)
def sum(a, b):
return a + b;
print sum(1,2);
print sum("Hello ", "Word")
3)强、弱类型语言:
- 强类型语言:强制数据类型定义的语言。没有强制类型转化前,不允许两种不同类型的变量相互操作,强类型定义语言是类型安全的语言。如Java属于强类型。例如Java中“int i = 0.0;”是无法通过编译的;
- 弱类型语言:数据类型可以被忽略的语言。一个变量可以赋不同数据类型的值,允许将一块内存看做多种类型,比如直接将整型变量与字符变量相加。如C++、php属于弱类型。
注:强类型语言在性能上略逊色于弱类型语言,但可读性上要高于弱类型的。
4)为什么说java是一种准动态的语言?
根据上面的定义我们可以知道,java是一种静态语言,而且是静态类型的语言(强类型)。但是在java中引入很多动态的特性:
- 反射机制
- 动态编译
- 动态执行javascript代码
- 动态字节码操作
- 动态转换类型
二、运行期
1、java类运行的过程大概可分为两个过程:
- 类的加载:链接、初始化(分配内存、创建对象)等;
- 类的执行:解释执行(将字节码翻译成机器码),或者通过JTI(编译成机器码);
需要说明的是:JVM主要在程序第一次主动使用类的时候,才会去加载该类。也就是说,JVM并不是在一开始就把一个程序就所有的类都加载到内存中,而是到不得不用的时候才把它加载进来,而且只加载一次。
说明:在运行期,会涉及到前面讲得所有知识点:
- 类加载;
- JVM内存模型;
- 对象创建、访问、布局;
- GC;
2、绑定:
1)定义:
绑定指的是把一个方法的调用与方法所在的类(方法主体)关联起来(这个方法被哪个类调用)。对Java来说,分为静态绑定和动态绑定。
- 静态调用:也叫前期绑定。在程序执行前方法已经被绑定,也就是在编译期方法明确知道被哪个类调用。java当中的方法只有final、static、private和构造方法是前期绑定的。
- 动态调用:也叫后期绑定。在运行时根据具体对象的类型进行绑定(只有运行时才知道方法被哪个类调用)。在java中,几乎所有的方法都是后期绑定的。
注:动态调用(后期绑定)是多态的一种方式。
2)对比C++:
在C++中默认的方法调用都是静态调用,只有方法被使用virutal关键字声明后(变成了虚函数),才具有动态调用(后期绑定)的特性。
3、方法内联:
1)定义:
简单通俗的讲就是把方法内部调用的其它方法的逻辑,嵌入到自身的方法中去,变成自身的一部分,之后不再调用该方法,从而节省调用函数带来的额外开支。
之所以出现方法内联是因为函数调用除了执行自身逻辑的开销外,还有一些不为人知的额外开销。这部分额外的开销主要来自方法栈帧的生成、参数字段的压入、栈帧的弹出、还有指令执行地址的跳转。
2)条件:
一个方法如果满足以下条件就很可能被jvm内联。
- 热点代码:如果一个方法的执行频率很高就表示优化的潜在价值就越大。那代码执行多少次才能确定为热点代码?这是根据编译器的编译模式来决定的。如果是客户端编译模式则次数是1500,服务端编译模式是10000。次数的大小可以通过-XX:CompileThreshold来调整。
- 方法体不能太大:jvm中被内联的方法会编译成机器码放在code cache中。如果方法体太大,则能缓存热点方法就少,反而会影响性能。
- 如果希望方法被内联,尽量用private、static、final修饰,这样jvm可以直接内联。如果是public、protected修饰方法jvm则需要进行类型判断,因为这些方法可以被子类继承和覆盖,jvm需要判断内联究竟内联是父类还是其中某个子类的方法。
所以了解jvm方法内联机制之后,会有助于我们工作中写出能让jvm更容易优化的代码,有助于提升程序的性能。
3)对比C++:
在java中,内联是靠运行时JVM自动进行判断、优化的。
在C++中需要开发人员主动告诉编译器哪些类成员函数需要内联,然后在编译的时候进行优化的。C++有两种方式定义内联函数:
- 当在类的定义体外时,在该成员函数的定义前面加"inline"关键字;
- 当在类的定义体内且声明该成员函数时,同时提供该成员函数的实现体(不需要关键字inline);
3、JIT以及一些优化手段(逃逸分析):
略。
更多推荐
所有评论(0)