前言
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
JVM体系结构
jvm位置:操作系统上一层。
java->.class->类加载器->jvm。
类加载器
加载Class文件。
虚拟机自带类加载器、启动类(根)加载器、扩展类加载器、应用程序加载器。
AppClassLoader、ExtClassLoader、
双亲委派机制
为了保证安全,Root->Exc->App,一直往上找,先执行根及父类,找不到再执行子类。
- 类加载器收到类加载请求。
- 将这个请求向上委托给父类加载器去完成,一直向上委托到根加载器。
- 根加载器加载不了则再层层往下委派。
沙箱安全机制
组成:
- 字节码校验器:确保java文件遵循java规范。
- 类加载器:
- 防止恶意代码。(双亲委派)
- 守护了被信任的类库边界。
- 将代码归入保护域。
Native
Thread的start方法会去调用native的start0方法,因为jvm处理不了线程,需要跟操作系统调用,所以调用本地的start0方法。
//正常java类中不应该存在一个类似于接口的东西
//但是因为native所以导致它不不会报错
private native void start0();
带了native,jvm就管不到了,就会放入本地方法栈,再调用本地方法方法接口,从而调用本地方法库。这样就达到了扩展java的使用,达到了调用c语言的目的,也是当初为了兼容c而来。
PC寄存器
程序计数器:Program Counter Register
每个线程都有一个程序计数器,是线程私有的,是一个指针,用来存储指向一条指令的地址,在执行引擎读取下一条指令,是一个非常小的内存空间。
方法区
Method Area 方法区
方法区是被所有线程共享的,所有的字段和方法字节码,以及一些特殊方法,如构造函数、接口代码也在此定义。
静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区,但实例变量存在堆内存中。
栈
先进后出。main方法先执行后结束就是因为栈。
管理程序的运行,生命周期和线程同步,线程结束,栈内存就释放完了,所以不存在垃圾回收。
栈运行原理:栈帧。
堆
Heap,一个jvm只有一个堆内存,堆内存大小是可以调节的。
类加载器读取了类文件后,会把实例对象的方法,常量,变量放在堆中。
堆内存还要细分三个区域:
- (伊甸园)Eden Space。
- 幸存0区。
- 幸存1区。
- 养老区。
- 永久存储区。
堆内存满了就会报OOM错误。
java.lang.OutOfMemoryError: Java heap space。
在jdk8以后,永久存储区改了个名字(元空间)。
新生区:类诞生、成长、死亡的地方。
伊甸园:所有对象都是在伊甸园new出来的。
养老区:99%的对象都是临时对象,经过不了幸存区的筛选。
永久区:这个区常驻内存,用来存放JDK携带的Class对象。Interface元数据,存储的是java运行时的环境或者类信息,不存在垃圾回收。关闭虚拟机会释放。一个启动类加载大量jar包,tomcat部署太多应用,大量动态生成反射类,就会出现oom。
- jdk1.7:提出去永久堆,常量池在堆中。
- jdk1.8:无永久代,改成元空间,方法区也放在元空间。
调节堆内存:-Xms1024m -Xmx1024m -XX:+PrintGCDetails。
打印出PSYoungGen total 305664K || ParOldGen total 699392K,新生区和养老区已经占用1024m,那么元空间则存在本地内存中。
出现oom,如何快速排查:
内存快照分析工具,MAT,Jprofiler。
- 分析Dump内存文件,快速定位内存泄露。
- 获得堆中的数据。
- 获得大的对象。
GC
垃圾回收,GC作用区域:堆(新生代,幸存区,老年区)。
轻GC(主要针对新生代)、重GC(全局GC)。
引用计数法(不常用)
计算对象被用了几次,清理最少的。
复制算法(新生区)
- 每次GC都会将Eden伊甸园活的对象移到幸存区(to,from)中。
- 幸存区谁空谁就是to,要保证有一个to,就需要复制幸存区一边数据到另一个幸存区。
- 当一个对象经历了15次GC(默认)后还活着,就会进入养老区。
- 好处:没有内存碎片。坏处:浪费内存空间。适合新生区。
标记压缩清除算法(老年代)
标记清除:
标记存活对象
清除没有标记的对象。
缺点:两次扫描,浪费时间,会产生内存碎片。
优点:不需要额外空间。
标记压缩:
再次扫描,防止内存碎片产生。