Java虚拟机
Java虚拟机概念
-
为何使用Java虚拟机
- 实现Java的跨平台特性
- 把目标代码编译成字节码
-
Java虚拟机的生命周期
-
程序开始执行时虚拟机才运行,程序结束时它就停止。每个Java程序会单独运行一个Java虚拟机。
通过命令行启动java虚拟机:java xxxx(类名)
-
Java虚拟机总是开始于一个固定格式的main()方法,程序执行时,必须给虚拟机指明这个包含有main()方法的类名。
public static void main(String[] args)
-
-
Java虚拟机的体系结构
- Java虚拟机的规范中定义了一系列的子系统、内存区域、数据类型和使用指南。
- 每个java虚拟机都有一个类加载器子系统(class loader subsystem),负责加载程序中的类型(class和interface),并赋予唯一的名字。每个java虚拟机都有一个执行引擎(execution engine)负责执行被加载类中包含的指令。
-
Java虚拟机中使用的数据类型
-
原始数据类型(primitive types)
包括“四类八种”,整型类、浮点数类、字符型、布尔型。
-
引用数据类型(reference types)
注:
-
java虚拟机中存在一个不能使用的原始数据类型----返回值类型(return value),这种类型被用来实现“finally classes”
-
引用类型可能被创建为:类类型、接口类型、数组类型。
-
Java虚拟机内存划分
如下图所示:
-
程序计数器
JVM将这个计数看做是当前线程执行某条字节码的行数,根据计数器的值来选取需要执行的操作语句。
-
虚拟机栈
即进程说到的栈内存,java中每个方法从调用到执行的过程,对应一个栈帧在虚拟机栈中入栈到出栈的过程。
-
本地方法栈
用来执行本地方法,比如嵌入C/C++代码执行。
-
堆
堆是JVM中内存最大、线程共享的一块区域。唯一的目的是存储对象实例。垃圾回收器的主要回收区域。
-
方法区
JVM中内存共享的一块区域,用来存储类信息、常量、静态变量、class文件。垃圾回收器也会对该部分进行回收。
Java虚拟机类加载机制
虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型。
java语言里,类型的加载和连接过程是在程序的运行期间完成的。
-
类的生命周期
-
加载loading
通过一个类的全限定名来获取此类的二进制字节码,将该字节码所代表的的静态存储结构转换为方法区的运行时数据结构。在java堆中生成一个代表这个类的Class对象,作为方法区的这些数据的访问入口。
-
验证verification
- 虚拟机规范
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
-
准备preparation
为类变量(用static修饰的类的变量)分配内存并设置初始值,这些内存将在方法区中进行分配。
-
解析resolution
解析是在虚拟机中将常量池内的符号引用替换为直接引用的过程。
-
初始化initialization
- clinit()方法:收集类中所有类变量的赋值和静态语句块
-
使用using
- 类的主动引用
- 使用new、getstatic、putstatic、invokestatic方法
- 使用类的反射调用
- 父类初始化
- 虚拟机启动,初始化主类(包含main方法的那个类)
- 类的被动引用
- 通过子类引用父类的静态字段,不会导致子类的初始化(对于静态字段,只有直接定义这个字段的类才会被初始化)
- 通过数组定义类引用类:ClassA[] array = new ClassA[10]
- c常量会在编译阶段调用类的常量池
- 类的主动引用
-
卸载unloading
-
判断对象是否存活算法及对象引用
-
垃圾回收
当一个对象没有引用指向它时,这个对象就成为无用的内存(垃圾),就必须进行回收,以便用于后续其他对象的内存分配。
-
引用计数算法
Java语言没有选用引用计数算法来管理内存,最主要的一个原因是它很难解决对象之间相互循环引用的问题。
-
可达性分析算法(根搜索算法)
在主流的商用程序语言中(java和c#),都是可达性分析判断对象是否存活的。
根搜索算法是从离散数学中的图论引入的,程序把所有的引用关系看做一张图,从一个节点GC ROOT开始,如果一个节点与GC ROOT之间没有引用链存在,则该节点视为垃圾回收的对象。
在Java语言里,可以作为GC ROOT对象的包括如下几种:
- 虚拟机栈中引用的对象
- 方法区中的类静态属性引用的对象
- 方法区中的常量引用的对象
- 本地方法栈中JNI的引用的对象
-
对象引用-强引用
只要引用存在,垃圾回收期永远不会回收。
Object obj = new Object()
-
对象引用-软引用
非必须引用,内存溢出之前进行回收
Object obj = new Object(); SoftReference<Object> sf = new SoftReference<object>(obj); obj=null; sf.get();
软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。
-
对象引用-弱引用
在第二次垃圾回收时回收
Object obj = new Object(); WeakReference<Object> wf = new WeakReference<object>(obj); obj=null; wf.get(); //有时会返回null wf.isEnQueued();
弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过isEnQueued()方法返回对象是否被垃圾回收器所回收
-
对象引用-虚引用(幽灵/幻影引用)
在垃圾回收时回收,无法通过引用取到对象值
Object obj = new Object(); PhantomReference<Object> wf = new PhantomReference<object>(obj); obj=null; wf.get(); //永远返回null wf.isEnQueued();
虚引用主要用于检测对象是否已经从内存中删除
分代垃圾回收
在java代码中,java语言没有显式的提供分配内存和删除内存的方法。一些开发人员将引用对象设置为null或调用System.gc()来释放内存。
如果没有显式删除内存的话,垃圾回收期会去发现不需要(垃圾)的对象,然后删除它们,释放内存。
-
年轻代和老年代
年轻代:新创建的对象都存放在这里。minor GC
老年代:没有变得不可达,存活下来的年轻代对象被复制到这里。major GC(或full GC)
-
年轻代组成部分
典型的垃圾收集算法
-
Mark-Sweep(标记-清除)算法
实现简单,但是容易造成内存碎片
-
Copying(复制)算法
为了解决Mark-Sweep算法的缺陷,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块内存用完了,将存活的对象复制到另外一块上面,再把已使用的内存空间一次清理掉。不容易出现内存碎片问题,但是可使用的内存却缩减到原来的一半。
-
Mark-Compact(标记-整理)算法
为了解决Copying算法的缺陷,充分利用内存空间,在完成标记后,不直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。
-
Generational Collection(分代收集)算法
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。核心思想是将堆区划分为老年代(Tenuerd Generation)和新生代(Young Generation)。老年代的特点是每次垃圾回收只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都要有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的手机算法。
-
新生代
一般新生代采用Copying算法。一般来说将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。
-
老年代
一般老年代采用Mark-Compact算法
注:
堆区之外还有一个代是永久代(Permanent Gerneration),用来存储class类,常量,方法描述等。对永久代的回收主要两部分内容:废弃常量和无用的类。
-
典型的垃圾收集器
-
Serial/Serial Old
特点是单线程收集器,垃圾回收时必须暂停所有用户线程。Serial新生代收集器,Serial Old老年代收集器
-
ParNew
Serial收集器的多线程版本
-
Parallel Scavenge/Paralled Old
多线程收集器(并行收集器)
-
CMS(Current Mark Sweep)
以获取最短回收停顿时间为目标的收集器
-
G1
G1收集器是当今收集器技术发展最前沿的成果,面向服务端应用的收集器。