虚拟机概述
- 虚拟机是java程序运行环境,使得java可以一次编译,处处运行
- 常见的术语
- JVM:java运行最基本的环境
- JRE: Java Runtime Environment = JVM + 基础类库
- JDK:Java Development Kit = JVM+ 基础类库 + 编译工具
- JavaSE: Java Standard Edition = JDK+基础开发
- JavaEE: Java Enterprise Edition = JDK+基础开发+应用服务器
- 基本结构
- 内存结构
- 垃圾回收器
- Java程序加载机制
- 学习的目标
- 了解java程序运行的原理
- 掌握常用虚拟机的调试 和 配置的相关工具
- 具备可以线上调优的能力
- java程序的生命周期源码编写 -> 代码编译 -> 虚拟机加载 -> 虚拟机存储 -> 虚拟机运行 -> 垃圾回收
-
Java代码的运行机制
代码编译机制
将.java文件编译成.class文件 :::info javac xxx.java :::
对.class的翻译方式有两种
- 解释执行,即逐条将字节码翻译成机器码并执行;
- 第二种是即时编译(Just-In-Time,JIT),即将一个方法中包含的所有的字节码编译成机器码后再执行
- Hotspot默认使用是混合编译(mixed mode),那些编译,那些优化,则是由监视器(Profile Monitor)决定
可以使用javap命令文件.class文件 :::info javap -c xxx.class > xxx.txt :::
代码加载机制
代码加载过程
- Loading:使用类加载器加载类
- Linking:是指将创建成的类合并至Java虚拟机中,使之能够执行的过程。
- Initializing:则是为标记为常量值的字段赋值。利用这个机制可以使用的单例模式的代码可以延时加载
类加载器
- 多个类加载器
- Bootstrap ClassLoader:加载$JAVA_HOME中jre/lib/rt.jar里面所有的class或Xbootclassoatch选项指定的jar包
- Extension ClassLoader:加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包
- App ClassLoader:加载classpath中指定的jar包及Djava.class.path所指定目录下的类和jar
- Custom ClassLoader:通过java.lang.ClassLoader的子类自定义加载class,属于应用程序根据自身 需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader
- 双亲委派原则
- 多个类加载器
内存可以分为两个部分
- 代码的存储区域
- 方法区
- 堆
- 代码的运行区域
- 栈
- 计数器
- 本地方法区(主要运行native的方法)
- 代码的存储区域
- 代码被加载之后,都存在方法区;代码执行过程中创建的对象存储在堆中
代码主要是在栈里面运行
程序计数器(Program Counter Register):用于多线程在切换了记录当前运行的位置,以保证切换回来的时候可以继续
-
虚拟机栈
Java Virtual Machine Stacks(Java 虚拟机栈) 是每个线程运行时所需要的内存。
- 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
- 栈内存可能出现的异常的情况-(1)当固定大小情况下,线程请求分配的栈容量大于Java虚拟机栈最大容量时,抛出异常:StackOverFlowError。- (2)当可拓展时,如果在拓展过程中,无法申请到足够的内存时,抛出异常:OutOfMemoryError(比如:JVM运行内存被占满,此时已经无处可以申请内存了)。
栈内存的大小
可以自定义设置栈的大小,通过java运行的执行设置栈的大小
java -Xss1024k Test001
java -XX:ThreadStackSize=1024 Test002在idea中设置vm的参数
- 在eclipse中的设置
查看默认的栈的大小
java -XX:+PrintFlagsFinal -version | findstr /i “ThreadStackSize”
不同操作系统的栈的默认大小不一样
Linux / ARM(32位):320 KB
Linux / i386(32位):320 KB
Linux / x64(64位):1024 KB
OS X(64位):1024 KB
Oracle Solaris / i386(32位):320 KB
Oracle Solaris / x64(64位):1024 KB
一台服务器可以创建线程的计算公式,根据栈的大小计算可以创建线程的数量
(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads
- MaxProcessMemory 指的是一个进程的最大内存
- JVMMemory JVM内存
- ReservedOsMemory 保留的操作系统内存
- ThreadStackSize 线程栈的大小
本地方法栈
1,本地方法栈的功能和特点类似于虚拟机栈,均具有线程隔离的特点以及都能抛出StackOverflowError和OutOfMemoryError异常。本地方法栈服务的对象是JVM执行的native方法,其就是一个java调用非java代码的接口,作用是与操作系统和外部环境交互
2,某个虚拟机实现的本地方法接口是使用C连接模型的话,那么它的本地方法栈就是C栈。当C程序调用一个C函数时,其栈操作都是确定的。传递给该函数的参数以某个确定的顺序压入栈,它的返回值也以确定的方式传回调用者。同样,这就是虚拟机实现中本地方法栈的行为。
3,能本地方法接口需要回调Java虚拟机中的Java方法,在这种情况下,该线程会保存本地方法栈的状态并进入到另一个Java栈。
4,描绘了这样一个情景,就是当一个线程调用一个本地方法时,本地方法又回调虚拟机中的另一个Java方法。
堆
heap定义
- 堆内存被所有的线程共享
- 通过new出来的对象都是在堆里面
-
堆的大小
有初始的大小
有最大值的大小
//字节大小 K -> M -> G -> T<br /> double initialMemory= Runtime.getRuntime().totalMemory()/1024/1024;<br /> double maxMemory = Runtime.getRuntime().maxMemory()/1024/1024;<br /> System.out.println("堆内存的初始总量Xms:"+initialMemory);<br /> System.out.println("堆内存的最大总量Xmx:"+maxMemory);
默认情况下,初始内存大小=物理电脑内存大小/64 ;最大内存大小=物理电脑内存/4
修改初始值和最大值的堆内存的大小 :::info -Xms10m -Xmx10m :::
-Xms10m 初始值的大小 -XX:InitialHeapSize=10m -Xmx10m 最大值的大小 -XX:MaxHeapSize=10m
查看正在运行的java程序的状态
堆内存要分为一下结构
- 新生代 + 老年代 + 永生代(1.7)
- 新生代 + 老年代 + 元空间(1.8)
- 新生代:新创建的对象就是放在的新生代
- eden区:所有的对象最先出生的地方
- Servivor区:又分为s0和s1也就是from和to
- 新生代 和 老年代的比例是1:2
- eden区 和 Servivor区的比例是8:1:1
- 如果eden满了会出发MinorGC
- 在eden区内存满了之后,会触发MinorGC,将eden区存活下来的对象放入到servivor区的s1,然后清空eden中的内存
- 等eden的区的内存在在满了之后,又会触发MinorGC,会将S1中所有存活下来的对象和eden中存活下来的对象都放入到s0的区域,然后将s1和eden区的对象全部清空
- 如果在minorGC中,Servivor区放不下了,就会放入到老年代中
- Servivor区如果满了,会将一部分对象放入到老年代
- 老年代
- 生命周期长的内存对象
- 一般而言新生代的对象经过15次GC没有被释放就会被放入到老年代
- 如果老年代满了就会进行Major GC
- 如果Major GC并释放老年代的对象,那么就会OOM
永生代
程序运行会先加载程序的元数据放入到meta区
- 在程序的过程中逐步的创建对象放入到Eden区
- 当Eden区中的内存满了以后,会进行Young GC/minGC,其中大多数的对象被回收,没有被回收的拷贝到(Copying)s0区
- 再次YGC,将eden区和S0区的没有被回收的对象拷贝到S1区
- 再次YGC,eden+S1 -> S0,然后循环4,5
- 年龄足够拷贝到老年代(一般是15次,CMS6次)
如果新创建的对象占用内存很大,则直接分配到老年代,当老年代也满了装不下的时候,就会抛出 OOM(Out of Memory)异常。
关于GC
在内存GC时候所有线程都会暂停SWT
- minGC触发条件,就是eden区内存满了
- oldGC触发条件,就是老年代内存满了
- fullGC = oldGC + minGC +mateGC
- 调用System.gc()时,系统建议执行Full GC,但是不必然执行。- 老年代的空间不足。- 方法区的空间不足。- 通过Minor GC后进入老年代的平均大小大于老年代的可用内存。- 年轻代放不下,放到老年代中,若该对象大小大于老年代,也会触发Full GC。
-
方法区
和堆内存差不多,其实在逻辑上也是堆的一部分
- 主要存放的内容
- 方法区需要存储每个加载的类(类,接口,枚举,注解)
- 域(属性)信息
- JVM需要保存所有方法的信息及其声明的顺序
- non-final的类变量(static)
方法区大小的设置
1.7的设置 :::info -XX:Permsize 设置永久代初始分配空间
-XX:MaxPermsize 设定永久代最大可分配空间
OutOfMemoryError:PermGen space OOM错误 :::1.8的设置 :::info -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 元数据区大小,其默认值依赖于平台。windows下,-XX:MetaspaceSize=21M -XX:MaxMetaspaceSize=-1 即没有限制。 :::
方法区大小的测试
//-XX:MaxMetaspaceSize=100m
public class TestMethod extends ClassLoader {
public static void main(String[] args) {
int j=0;
TestMethod test = new TestMethod();
try{
for(int i=0;i<300000;i++){
//ClassWriter作用是生成类的二进制字节码
ClassWriter cw = new ClassWriter(0);
//版本号,public,类名,包名,父类,接口
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC,"Class"+i,null,"java/lang/Object",null);
//返回byte[]
byte[] code = cw.toByteArray();
//执行了类的加载
test.defineClass("Class"+i,code,0,code.length);
j++;
}
}finally {
System.out.println(j);
}
}
}
垃圾回收
如何定义垃圾
- 引用计数法(reference count):如果指向当前对象引用是0那么这个对象就会被回收,虽然简单,但是不能解决循环引用的问题
- 根可达性分析(RootSearch):为了解决引用计数法的循环引用问题,Java 使用了可达性分析的方法。通过一系列的“GC roots”对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。
- 垃圾如何清除
- 标记清除(Mark-Sweep):最基础的垃圾回收算法,分为两个阶段,标注和清除。标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。该算法最大的问题是内存碎片化严重,后续可能发生大对象不能找到可利用空间的问题
- 复制算法(copying):为了解决 Mark-Sweep 算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为等大小的两块。但是最大的问题是可用内存被压缩到了原本的一半**。且存活对象增多的话,Copying 算法的效率会大大降低。
- 标记压缩算法(Mark-Compact**)**:结合了以上两个算法,为了避免缺陷而提出。标记阶段和 Mark-Sweep 算法相同,标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象
- 分带收集算法:分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(Young Generation)。老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。
垃圾回收算法
JVM的基本构成
- 代码运行机制
- 内存的结构
- 垃圾回收算法
- 代码运行机制
- 解释翻译 + JIT即使翻译
- 双亲委派机制
- 内存结构
- 程序计数器
- 唯一没有内存泄漏的问题
- 虚拟机栈
- 每个线程是独立拥有
- 设置栈的大小
- 本地方法栈
- 执行C方法的内存空间
- 堆空间
- 新生代
- eden
- suvivior区
- 老年代
- 元空间
- 垃圾回收方式
- minGC
- oldGC
- fullGC =minGC+oldGC
- 设置堆大小的指令 和 查看内存变化的工具
- 新生代
- 方法区
- 用于存储代码,静态变量的区域
- 程序计数器
- 垃圾回收
- 如何定义垃圾
- 回收的基本算法
- 各种垃圾回收期
- 在1.8设置使用G1垃圾回收期
- 目标
- 了解JVM的基本概念
- 掌握JVM的内存的结构
- 会设置JVM的一些参数和调试工具