1、java代码是如何运行的,java是如何实现跨平台的?
理论支持
计算机科学领域名言“计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决”
java代码执行过程
- 编译期 java源文件-->java编译器(javac命令)-->字节码文件 【编译执行】
- 运行时 字节码文件-->java虚拟机(jvm)-->机器码 【混合执行】
跨平台原理
为不同操作系统设计了不同的虚拟机运行环境,这些虚拟机通过执行字节码(class文件)生成机器码实现跨平台执行
2、虚拟机是如何执行字节码的?
jvm是通过类加载器(Class-Loader)加载字节码,解释或者编译执行的,当前主流jdk(如jdk1.8)默认是一种混合执行的模式
jdk7以前执行方式
默认先解释执行字节码,对字节码中的热点代码以方法为单位编译直接生成机器码执行
内置两个JIT(Just In Time)编译器,分别是C1和C2
C1 又叫Client编译器,面向对启动性能有要求的客户端GUI程序,优化手段有限,执行时间较短,门限值默认1500
C2又叫Server编译器,面向对峰值性能有要求的服务端程序,优化手段复杂 ,执行时间较长,生成的代码执行效率高,门限值默认上万次
jdk8以后Hotspot默认采用分层编译,热点方法先备C1执行,热点中的热点再被C2执行
重要命令
-Xint 只解释执行
-Xcomp 关闭解释执行只编译执行
扩展知识
jdk9以后引入AOT特性(Ahead-of-Time Compilation) 直接将字节码编译成机器码
jdk10实验性引入Graal编译器通过GraalVM一种支持多种语言执行环境的编译器
Graal 是一个以 Java 为主要编程语言、面向 Java bytecode 的编译器。与用 C++ 实现的 C1 和 C2 相比,它的模块化更加明显,也更容易维护。Graal 既可以作为动态编译器,在运行时编译热点方法;也可以作为静态编译器,实现 AOT 编译。
3、jvm内存结构划分
-
程序计数器(线程私有)
定义可以看作是当前线程所执行字节码的行号指令器
工作过程字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令
使用场景分支、循环、跳转、异常处理、线程恢复都依赖该计数器
这个区域是jvm规范中唯一一个没有内存溢出的区域 -
虚拟机栈(线程私有)
虚拟机栈描述的是java方法执行时的内存模型,每个java方法执行时都会创建一个栈帧。
Stack Frame内部存储局部变量表、操作数栈、动态链接、方法出口等
方法调用的执行过程就对应着一个栈帧在虚拟机栈中的入栈和出栈过程
两种内存溢出
当调用栈的深度超过虚拟机允许的最大深度时抛出StackOverflowError
当虚拟机栈动态扩展时无法申请到内存时就会抛出OutOfMemoryError -
本地方法栈(线程私有)
与虚拟机栈不同的是本地方法栈是未Native方法服务的,java虚拟机规范中并未对其的实现做限制,在Hotspot虚拟机中直接将其与虚拟机栈合二为一,所以也会产生与虚拟机栈一样的内存溢出。 -
堆内存(线程共享)
所有线程共享的一块区域,在虚拟器启动时创建。
目的是存放对象实例和数组。
随着JIT编译器的发展和逃逸分析技术的逐渐成熟栈上分配、标量替换会使情况发生一些变化,所有对象都在堆上创建变得不那么绝对
栈上分配 逃逸分析发现一个对象只在方法内部使用就会将对象分配在栈上,目前java虚拟机并未实现
标量替换 逃逸分析发现一个对象只在方法内部使用对象可以拆分的话,直接创建拆分后的对象而不创建原始对象,目前已实现
内存不够时抛出OutOfMemoryError
通过Xms(默认堆内存大小)、Xmx(最大堆内存)控制对堆内存大小,一般设置为一样大 -
方法区(线程共享)
用来存放被java虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。
这块区域就是传统上所说的永久带(PermGen),其共享java堆内存且内存回收效果比较差,默认配置较小大概4m(jdk1.7以前),所以容易发生内存溢出。
可通过设置 -XX: MaxPermSize -XX: PermSize设置永久带最大内存和默认内存大小
迁移路线
(1)永久带中的常量池在jdk1.7中已移入堆内存
(2)jdk1.8中取消永久带,新增MetaSpace,其内存不再占用堆内存
通过-XX: MaxMetaSpaceSize指定元数据空间大小 -
运行时常量池
Class文件除了有类的版本、字段、方法、接口等信息外,还有一项就是常量池(Constant pool table),用于存放编译期生成的各种字面常量和符号引用。
java语言并不要求常量必须在编译期进入常量池,运行期间也可以将常量放入常量池,典型的使用方式有String.Intern()方法
jdk1.7后已从方法区移入堆内存 -
直接内存
不属于Java虚拟机规范中的一部分,但是在Jdk1.4中引入的Nio使用的DirectByteBuffer实际上就是直接使用Native函数为其直接分配的堆外内存
所以当系统内存不足时也会抛出OutOfMemoryError