JVM原理

一:Java运行原理
Java包括:Java编程语言,类文件格式,Java虚拟机,Java应用程序接口
编译环境
Java源文件(.java文件)–【编译器】–字节码文件(.class文件)-
运行环境
-【解释器】–操作系统–硬件
跨平台就是不同版本的Java虚拟机,将同样的内容翻译成不同操作系统执行的命令
二:Java虚拟机内部流程
(1)类加载器(Class Loader System)负责加载.class文件
JVM原理
加载:
编译后的class文件先加载,获取此类的二进制直接流,将字节流所代表的静态存储结构转换为方法区运行时数据结构,在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口
验证:
验证文件格式,元数据,符号等
准备:
为类变量分配内存并设置初始值,在方法区中分配(实例变量不在这里处理)
解析:
虚拟机常量池内的符号引用替换为直接应用
初始化:
对类的静态成员进行初始化操作(类变量,静态快)
不是一个程序中所有的类,都一次性全部加载
一个类只有在使用的时候(new对象)才会真正被加载
最先加载拥有main方法的主线程所在的类
类的加载时机:
new对象
reflect对类进行反射调用的时候,如果这个类没有进行过初始化,那就走一遍加载,初始化一个类的时候,如果发现这个类的父类还没有被初始化,那么先去初始化它的父类
什么情况不用初始化:
只访问静态变量,只有真正声明这个变量的类才会初始化
通过数组定义类引用,不会触发此类的初始化
静态常量不会触发类的初始化(准备阶段就已经放在常量池中,不需要初始化阶段就能用)
(2)运行时数据区
JVM原理
栈负责运行
堆负责存储
JVM调优主要就是优化Heap堆和Method Area方法区
堆:
应用的对象和数据都是存在这个区域的,这块区域线程共享,也是GC主要的回收区,一个JVM实例只存在一个堆内存,大小是可以调节的。类加载器读取了类文件之后,需要把类,方法,变量放到堆内存中
方法区:
所有定义的方法的信息,所有字段和方法的字节码,以及特殊方法(抽象,构造),简单说就是所有方法的信息
静态变量+常量+类信息+运行时常量存在方法区中
实例变量存在堆内存中
栈:
也叫栈内存,主管Java程序的运行,线程创建时创建,线程结束时栈内存就释放,所以不需要回收,是线程私有的,基本类型的变量和对象的引用变量,在函数的栈内存中分配
程序计数器
就是一个指针,指向方法区中的字节码,就是下一条将要执行的指令,由执行引擎读取下一条指令,可以忽略
本地方法栈
登记Native方法,加载本地库
JVM运行时会分配好方法区和堆,然后,JVM每遇到一个线程,就为其分配一个计数器,一个Java栈,一个本地方法栈,线程终止时,这三个就没了
方法区和堆是线程共享的,那三个是非线程共享的,所以GC只发生在线程共享的区域
细节:
一个线程有一个自己的栈,正常的一个Java程序运行时,会有多个栈
每一个Java栈,存放的是多个栈帧,每一个栈帧对应一个被调用的方法,栈帧包括
局部变量表
(基本数据类型直接存储它的值,引用数据类型,存的是指向对象的引用,表的大小在编译期就可以确定,程序执行期间,大小不会改变)
操作数栈
执行语句,计算的过程
指向常量池的引用
方法返回地址
一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址
B:
因为Java程序是多线程轮流获取CPU资源
且任意时刻一个CPU内核只能执行一个线程,所以,为了能够使得每个线程都在线程切换后能恢复之前的程序执行位置,每个线程都会有自己独立的程序计数器
C:
java中的native可以调用本地方法(不是这个Java项目写的,但本机可以使用的方法),Java栈(虚拟机栈)是为执行Java方法服务的,本地方法栈就是为执行本地方法服务的(比如C++方法)
D:
每个方法中,引用数据类型在堆内存中
所有线程都可以使用,所以堆内存中的对象需要加锁,会产生一定的开销,所以new对象是比较耗资源的
为了提升对象内存的分配效率,每条线程有一个独立空间TLAB,在TLAB上分配对象时不需要加锁,JVM在内存充足的时候,尽可能的给线程分配这个空间,但是对象过大,或者空间紧张时,还是直接使用堆内存。
E:
方法区中有一个部分叫运行时常量池,用于存放静态编译产生的引用以及运行时生成的常量,在类和接口被加载到jvm后,对应的运行时常量池就被创建