java代码是怎样运行的?
首先编译器把java文件(.java)编译成字节码文件(.class)
-->JVM(java virtual machine)把编译好的字节码对象加载到内存
-->JVM会通过字节码执行引擎,运行已加载的字节码文件。
JDK/JRE/JVM
JRE(JavaRuntimeEnvironment)java的运行环境,普通用户要运行已经开发好的java程序,安装jre即可。
JDK(Java Decelopment Kit)是java的开发工具包,里面的工具也是java程序,需要jre来运行,在jdk文件下的jre目录就用来存放jre。
JVM(JavaVirtualMachine)java虚拟机,是jre的一部分,它是一个虚构的计算机,通过仿真模拟各种计算机功能来实现的。jvm有自己完善的硬件架构,如处理器、堆栈、寄存器等,还有相应的指令系统。它赋予java语言最重要的特点就是跨平台运行。
JVM体系结构
1、Class Loader类加载器
a、负责加载字节码文件
b、定位和导入二进制的字节码文件
c、验证导入的正确性
d、为类分配初始化的内存
e、帮助解析符号的引用
2、Native Interface本地接口
在java刚诞生的时候,会调用Native工作区的c接口,在现在的一些关于硬件的功能中也会使用,不过现在使用的已经越来越少。
3、Execution Engine执行引擎
用来执行已经加载在内存里面的字节码对象和java方法
4、Runtime data area运行时数据区(即JVM内存)
jvm运行的数据区主要包括:堆区、方法区、虚拟机栈、本地方法栈、程序计数器
a、程序计数器(Program Counter Resister)
是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号处理器,指向下一个将要执行的指令代码,交由执行引擎来读取下一条指令。(自己的理解:由字节码解释器改变当前程序计数器线程的值,执行引擎读取这个值来获取下一条需要执行的字节码指令,从而确保线程的正确执行)。
b、方法区(Method Area)
用于存储虚拟机加载的 静态变量+常量+类信息+运行时常量池(类信息:类的版本、字段、方法、接口、构造函数等描述信息),默认最小为16MB,最大为64MB,可以通过-XX:PermSize和-XX:MaxPermSize参数限制方法区的大小。
c、本地方法栈(Native Method Stack)
与虚拟机栈类似,区别在于虚拟机栈为虚拟机执行的java方法服务,而本地方法栈则是为Native方法服务。企业开发用的很少,主要用于硬件。
d、虚拟机栈(JVM Stack)
编译器可知的各种基本数据类型和引用类型指针(并非对象本身)。
每个方法被执行的时候都会创建一个“栈帧”用于存储局部变量表(包括参数)、操作栈、方法出口等信息。每个方法被调用到执行完的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
栈的生命周期是跟随线程的生命期,线程创建时创建,线程结束栈内存也就释放,是线程私有的。
e、堆(Java Heap)
所有的对象实例以及数组都要在堆上分配,此内存区域的唯一目的就是存放对象实例。堆是Java虚拟机所管理的内存区域中最大的一块,java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。
堆的结构:
新生代(Eden区+2个Survivor区):新创建的对象放在Eden区, Eden区是连续的内存空间,分配内存很快,当Eden区满了以后会触发gc垃圾回收,将消亡的对象清理掉,存活的对象放在s0(两个Survivor区,简称s0和s1)中。之后当Eden区满了以后会将存活的对象和不为空的survival区存活对象复制到另一个空白survivor区,将多次gc后survivor区存活对象放去老年代Tenured区中。这样两个survivor区总有一个是空白的。
老年代:新生代中在几次gc后存活下来的,会被复制到老年代。新创建对象比较大(比如长字符串和大数组等等),新生代空间不足,则会直接分配到老年代中(大对象可能提前触发GC,应尽量少用,更应该避免使用短命的大对象)。老年代的空间一般比新生代大,能存放更多的对象,在老年代上发生的GC次数也比新生代少。
永久代:可以简单理解为方法区(本质上两者并不等价),在现在的高版本jvm中,永久代已被取消。
Java对象的访问方式
一般来说,java对象的引用访问涉及到3个内存区域:JVM栈、堆、方法区
以Object o = new Object()来说;
Object o 表示一个本地引用,存储在JVM栈的本地变量表中;
new Object()作为实例对象数据存储在堆中,堆中还记录Object类的类型信息(接口、方法、属性、对象类型等)的地址,这些地址所执行的数据存储在方法区中。
实例: