一、Java代码编译和执行
程序员编写Java程序,通过编译器生成.class文件也就是字节码,字节码通过字节码本或网络进入Java运行平台,Java平台由Java虚拟机和Java应用程序接口搭建,字节码进入虚拟机被解释器执行。
简单的说:Java字节码在JRE中运行,JRE由API和JVM构成,JRE分析和执行字节码的核心在于JVM,因此JVM是是实现Java平台无关性,实现程序和操作系统分离的关键。
二、Java虚拟机的体系结构
JVM内部结构主要有三大部分:
1.类加载子系统:将.class文件加载到JVM内存
2.运行时数据区(内存):存放数据
3.执行引擎:执行运行时数据区内的数据
三、详谈运行时数据区
一个进程包含多个线程,运行时数据区包含堆,方法区,Java栈,本地方法栈和程序计数器。
其中堆和方法区时多个线程共享的,而Java栈,本地方法栈,程序计数器是每个线程独有的。
1、堆:堆则是存放运行时产生的对象的。 和C++不同的是, Java只能在堆中存放对象, 而不能在栈上分配对象, 所有运行时产生的对象全部都存放于堆中, 包括数组。 我们知道, 在Java中, 数组也是对象。一个JVM实例中只有一个堆, 所有线程共享堆中的数据(对象)。在JVM管理的内存中堆区是最大的,也是主要的区域,在虚拟机启动时创建。Java对象占用的内存在堆上实现,因为堆是线程共享的,因此在分配内存时要注意线程安全问题,通常我们采用加锁的形式,但是这样往往开销会很大。
为了提高内存回收的效率,对堆采用分代管理方式:
堆区分为年轻代和老年代
1)年轻代(Young Generation)
对象在被创建时的首先存进年轻代,当年轻代需要回收的时候会触发Minor GC
年轻代=Eden Space+S0+S1,S0和S1大小是相同的,这三个部分的大小都可以自定义。其中Eden区内存是连续的,分配非常快,回收也非常快。如果在执行垃圾回收之后,仍没有足够的内存分配,也不能再扩展,将会抛出OutOfMemoryError:Java Heap Space异常。
2)老年代(Old Generation)
老年代用于存放在年轻代中经过多次垃圾回收任然存活的对象,当老年代满了的时候就需要对老年代进行垃圾回收,老年代的垃圾回收称作Major GC
2、方法区:用来存放类型数据,类所有的字段和方法字节码(静态变量,final定义的常量和类中的field信息,方法信息)不要被名字误导,它不只是存放方法,那么为什么他是共享的,我们可以这样理解,一个程序可能有多个线程会调用一个方法,它从共享区取可以减少内存开销。
在Hotspot虚拟机中(一般我们使用的java都是这个虚拟机),这块区域对应的是持久带代,方法区上的垃圾收集很少,但是在一定情况下它也会被GC,如内存超过,就会抛出异常,它的GC主要针对常量池的内存回收和对已加载类的卸载。
在方法区中还有一部分就是运行时常量区,用于存放编译时期生成的字面常量,符号引用和直接引用,也存放运行时产生的常量,比如String类的intern方法,作用是String维护了一个常量池,如果调用的字符“abc”已经在常量池中,则返回池中的字符串地址,否则,新建一个常量加入池中,并返回地址。
3、Java栈:Java栈是一个线程的执行区域, 它保存着一个线程中的方法的调用状态, 也可以说, 一个Java线程的运行状态, 都由一个Java栈来保存。
4、本地方法栈:用来存放本地方法,支持native方法的执行,存储了每个native方法调用的状态。本地方法栈和Java方法栈运行机制一样,唯一的区别在于Java栈用来执行java方法,而本地方法栈用来执行native方法
5、程序计数器:程序计数器用于存放一条指令的地址, 这条指令就是虚拟机要执行的下一条指令。程序计数器和线程相关联, 每一个线程都有一个程序计数器。由于程序计数器只是记录当前指令地址,所以不存在内存溢出的情况,因此,程序计数器也是所有JVM内存区域中唯一一个没有定义OutOfMemoryError的区域。
举例说明
Object oj = new Object();
1.Object oj表示本地引用,存储在JVM栈的本地变量表中
2.new Object()作为对象数据存储在堆中
3.堆中还记录了能查询到Object对象的类型数据(接口,方法,field,对象类型等)地址,实际的数据是存储在方法区中的