1.JVM(Java Virtual Machine):Java虚拟机是在计算机系统上用软件实现的一台假想机;Java程序在运行的时候,JVM把Java字节码解析成机器码。
1.1JVM有自己完善的硬件架构,例如:处理器、堆栈、寄存器等,还具有相应的指令系统,它屏蔽了具体操作系统平台相关信息。每当一个Java程序运行时,都会有一个对应的JVM实例,只有当程序运行结束后,这个JVM才会退出。JVM实例通过**main()**方法来启动一个Java程序。
2.JDK(Java Development Kit):JDK是用于开发Java程序的最小的环境。我们把Java程序设计语言,Java虚拟机,Java API类库这三部分统称为JDK
3.JRE(JavaRuntime Enviroment):JRE是支持Java程序运行的标准环境。我们可以把Java API类库中的Java SE API子集和JVM这两部分统称为JRE
4.Java API(Application Program Interface) :应用程序接口;它提供了许多有用的接口,这些接口也是java语言编写的,并且运行在JVM上。
Java平台逻辑结果:
JVM启动流程
JVM内存区域的划分
JVM在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域(程序计数器,虚拟机栈,本地方法栈,堆,方法区)。
内存区域为线程私有:程序计数器,虚拟机栈,本地方法栈;
内存区域为线程共享:堆,方法区。
把内存区域划分为线程共享和线程私有的原因:JVM在 初始运行的时候 都会分配好 方法区 和 堆,当JVM每遇到 一个线程 的时候,就会为其分配一个 程序计数器,虚拟机栈,堆,当线程终止时,这个三个内存空间将会被释放。简单来说就是:线程私有的三个区域的生命周期与其所属的 线程 相同,而 线程共享 的区域的生命周期和 Java程序 相同,所以这也是 系统垃圾回收场所 只发生在 线程共享 的区域(实际上对于大部分的虚拟机来说只发生在 堆 上)。
- 1.程序计数器(Program Counter Register)(线程私有):是一块很小的内存,它表明了当前线程所执行的字节码的行号指示器。分支、循环、跳转、异常处理和现场恢复等基础功能都是需要依赖这个它来完成。
- 每一个线程拥有一个PC寄存器
- 在线程创建时 创建
- 指向下一条指令的地址
- 执行本地方法时,PC的值为underfined
- 2.Java虚拟机栈(VM Stack)(线程私有):描述的是方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储 局部变量表 (包含了对应的方法参数和局部变量)、操作栈(Operand Stack,记录出栈、入栈的操作)、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。
局部变量表:表所需的内存空间在 编译期间 完成分配, 局部变量表存放了编译期可知的各种基本的 数据类型(boolean,byte,char,short,int ,long,double)、对象的引用(reference)类型(注意:对象的引用不等同于对象本身,根据不同的虚拟机,可能是一个指向对象起始地址的引用指针,也可能是一个代表对象的句柄或者其他与对象相关的位置)、returnAdress(指向下一条字节码指令的地址)。
- 3.本地方法栈(Native Method Stack)(线程私有):它和虚拟机栈发挥的作用是非常相似,它们之间的区别就是虚拟机栈为虚拟机执行Java方法(字节码)服务;而本地方法栈就是为虚拟机使用到的Native方法服务。Sun HotSpot虚拟机直接把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈也会抛出StackOverflowError和OutOfMemoryError异常。
-
注:Java栈 (虚拟机栈+本地方法栈):线程私有,Java栈由一系列的帧组成因此Java栈也称为帧栈;帧中保存着一个方法的局部变量,操作数栈,常量池指针;每一次方法调用时创建一个帧,并压入栈。
-
小对象(一般为几十个bytes)
-
直接分配在栈上,可以自动回收,减轻GC压力
-
大对象或者逃逸对象无法在栈上分配
- 4.Java堆 (Heap)(线程共享):存放 数组、对象实例(在Heap中分配一定的内存来保存对象实例;实际上也是保存对象实例的属性值,属性类型以及对象本身的类型标记等,但是对象的方法是一帧栈的形式保存在Stack中);对象实例在Heap分配好以后,需要在 Stack中 保存一个4字节的 Heap内存地址,用来定位该对象实例在Heap中的位置(该位置是垃圾回收的主要场所)。Java堆处理物理上不连续的内存空间,只需要逻辑上是连续的即可。Java堆是Java虚拟机所管理的内存中 最大 的一块。
- 5.方法区(Method Area)(线程共享):用于存储已经被JVM加载的类信息,常量**,静态变量**,即使编译器编译后的代码等数据。方法区也可以是内存不连续的区域组成的,并且可设置为固定大小或者设置为可扩展,这点和堆是一样的!垃圾回收 比较少 出现在这个区域中,这个区域内存回收的目的主要是针对常量池的回收和类的卸载。
运行时常量池: (Runtime Constant Pool):是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种 字面量 和 符号引用,这部分的内容将在类加载后进入方法区的运行时常量池中存放。
示例代码:
package dong.test;
public class Test {
private String a = "aa";
public boolean methodB(){
String b = "bb";
final String c = "cc";
return false;
}
public static void main(){
}
}
上面代码中变量 a,b,c 分别在内存的堆区、栈区、栈区;其中a为全局变量(线程共享),b、c为局部变量。
小结:
1.我们所了解的Java程序工作过程是一个Java源程序文件,会被编译为字节码文件(.class文件)。每个Java程序都需要运行在自己的JVM上,然后告知JVM程序的运行入口,再被JVM通过 字节码解析器 加载运行。
2.JVM启动,我们的.java代码会编译成.class文件,然后被类加载器 加载 进入jvm内存,类中对象Object会加载到方法区中,而创建了Object类的类对象(注意是类对象,不是new出来的对象,而是类的类型对象。每一个类只有一个class对象,作为方法区的数据结构的接口)加载到堆中。
3.jvm在创建对象前,会先检查 类 是否加载。寻找类对应的class对象,如果加载好,则为对象分配内存,初始化 也就是new Object()。
4.:栈中的数据和堆中的数据销毁并不是同步的。方法一旦结束,栈中的局部变量立即销毁,但是堆中的对象不一定会销毁。因为可能会有其他变量也指向了和这个变量,直到没有栈中没有变量指向堆中的对象是,它才销毁,而且不是马上赶销毁,要等到垃圾回收扫描时才可以被销毁。
5.类的成员变量在不同对象中各不相同,都有自己的存储空间(成员变量存储在堆中的对象中);而类的方法却是该类的所有的对象共享的,当对象使用方法的时候才会将方法压入栈中,当方法不被调用时则是不占用内存的。
补充:1.在JDK1.8前方法区属于堆中永久区(PremSize)的一部分,但是在JDK1.8中舍去了永久区取而代之的是元空间(MetaSpace),在元空间中存储信息使用的是个更大的物理内存,这样一来在元空间发生的GC的次数也会相应的减少。
同样的JVM调优参数上:由PremGenSize和MaxPrenGenSize 对应这 MetaspaceSize和MaxMetaSpaceSize;
补充2:本地方法接口,是用C或者C++的实现的操作系统相关方法。即JVM通过本地方法接口可以和底层的操作系统打交道。
demo:
C:/Program Files/Java/jdk1.8.0_161/src.zip!/java/lang/ClassLoader.java:23
private native final Class<?> findLoadedClass0(String name);
补充3:JDK6和JDK6+版本下的String intern()方法的是说明:链接地址