一、什么是JVM
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行
二、JVM干了什么
1、Java 内存模型
根据《Java 虚拟机规范(Java SE 7 版)》规定,Java 虚拟机所管理的内存图所示。
- 堆
存放对象实例、分配内存。所有线程共享 - 方法区
属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 - 本地方法栈
为native方法服务 - 程序计数器
当前线程多执行的字节码的行号指示器 - 虚拟机栈
存储局部变量表、操作栈、动态链接、方法出口
2、类加载器
- 加载器种类
- 自定义加载器
- 应用加载器
- 拓展加载器
- 启动类加载器
- 类加载过程
- 加载,查找并加载二进制的数据,在java堆中创建一个java.lang.class类的对象
- 连接
- 验证 文件格式、元数据、字节码、符号引用验证
- 准备 为类的静态变量分配内存,并将其初始化默认值
- 解析 把类的符号引用转换为直接引用
- 初始化 为累的静态变量赋予正确的初始值
3、GC
- 哪些内存需要回收
1. 堆区和方法去 - 怎么判断哪些对象需要被回收
1. 引用计数法(不用)
2. 可达性分析算法
3. 引用 - 都有哪些回收算法
- 标记-清除算法
- 复制算法
- 标记-整理算法
- 分代回收算法
- 年轻代的回收算法
- 所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。
- 新生代内存按照8:1:1的比例分为一个eden区和两个survivor(survivor0,survivor1)区。一个Eden区,两个 Survivor区(一般而言)。大部分对象在Eden区中生成。回收时先将eden区存活对象复制到一个survivor0区,然后清空eden区,当这个survivor0区也存放满了时,则将eden区和survivor0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后将survivor0区和survivor1区交换,即保持survivor1区为空, 如此往复。
- 当survivor1区不足以存放 eden和survivor0的存活对象时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收。
- 新生代发生的GC也叫做Minor GC,MinorGC发生频率比较高(不一定等Eden区满了才触发)。
- 年老代(Old Generation)的回收算法a)
- 在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。b) 内存比新生代也大很多(大概比例是1:2),当老年代内存满时触发Major GC即Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率标记高。
- 持久代/元数据区
- 1.8之后移除了永久代,取而代之的元数据区,元数据区和堆内存区分开了。
- 年轻代的回收算法
二、JVM优化
-
JIT即时编译及优化技术
- 两种模式(即对应编译器的种类)
- client(C1编译器)
- 启动速度较快,代码优化相对不好
- server(C2编译器)
- 启动速度慢,代码优化更好
- client(C1编译器)
- 热点代码
- 热点代码有哪些
- 被多次调用的方法
- 被多次执行的循环体
- 如何判断热点代码
- 抽样探测
- 周期性的检查各个栈顶的,如果发现某些方法一直处于栈顶,则是热点代码
- 好处是处理简单,缺点是容易被阻塞的线程影响精度
- 计数法(jvm默认使用)
- 为每个方法建立计数器
- 优点是精确度高,缺点是实现复杂
- 抽样探测
- 计数器种类
- 方法调用计数器
- 统计方法被调用的次数,默认阈值在 Client 模式下是 1500 次,在 Server 模式下是 10000 次
- 回边计数器
- 统计循环体执行次数
- 方法调用计数器
- 热点代码有哪些
- 什么是编译和解释
- 编译器:把源程序的每一条语句都编译成机器语言,并保存成二进制文件,这样运行时计算机可以直接以机器语言来运行此程序,速度很快
- 解释器:只在执行程序时,才一条一条的解释成机器语言给计算机来执行,所以运行速度是不如编译后的程序运行的快的;
- 编译优化技术
- 公共子表达式消除:如果一个表达式 E 已经计算过了,并且从先前的计算到现在 E 中所有变量的值都没有发生变化,那么 E 的这次出现就成为了公共子表达式。对于这种表达式,没必要花时间再对它进行计算,只需要直接使用前面计算过的表达式结果代替 E 就可以了。例子:int d = (c*b) * 12 + a + (a+ b * c) -> int d = E * 12 + a + (a+ E)
- 数组范围检查消除:在 Java 语言中访问数组元素的时候系统将会自动进行上下界的范围检查,超出边界会抛出异常。对于虚拟机的执行子系统来说,每次数组元素的读写都带有一次隐含的条件判定操作,对于拥有大量数组访问的程序代码,这无疑是一种性能负担。Java 在编译期根据数据流分析可以判定范围进而消除上下界检查,节省多次的条件判断操作。
- 方法内联:简单的理解为把目标方法的代码“复制”到发起调用的方法中,消除一些无用的代码。只是实际的 JVM 中的内联过程很复杂,在此不分析。
- 逃逸分析:逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中杯定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,称为方法逃逸。甚至可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。
- 如果能证明一个对象不会逃逸到方法或线程之外,也就是别的方法或线程无法通过任何途径访问到这个对象,则可以为这个变量进行一些高效的优化:
- 栈上分配:将不会逃逸的局部对象分配到栈上,那对象就会随着方法的结束而自动销毁,减少垃圾收集系统的压力。同步消除:如果该变量不会发生线程逃逸,也就是无法被其他线程访问,那么对这个变量的读写就不存在竞争,可以将同步措施消除掉(同步是需要付出代价的)
- 标量替换:标量是指无法在分解的数据类型,比如原始数据类型以及reference类型。而聚合量就是可继续分解的,比如 Java 中的对象。标量替换如果一个对象不会被外部访问,并且对象可以被拆散的话,真正执行时可能不创建这个对象,而是直接创建它的若干个被这个方法使用到的成员变量来代替。这种方式不仅可以让对象的成员变量在栈上分配和读写,还可以为后后续进一步的优化手段创建条件。
- 两种模式(即对应编译器的种类)
-
调优常用参数
参数 | 描述 |
---|---|
-XX:+PrintGC | 打印GC简要信息 |
-XX:+PrintGCDetails | 打印GC详细信息 |
-XX:PrintGCTimeStamps | 打印GC发生的时间戳 |
-Xloggc:log/gc.log | 指定GClog位置 |
-XX:+TraceClassLoading | 监控类的加载 |
-XX:+PrintClassHistogram | 按下Ctrl+Break后,打印类的信息 |
-Xms50m -Xms200m | 指定最大堆和最小堆 |
-Xmn | 设置新生代大小 |
-XX:NewRatio=3 | 新生代(eden+2*s)和老年代(包含永久区)比值 -3 表示 新生代:老年代=1:3 新生代占堆1/4 |
-XX:SurvivorRatio=8 | 设置两个Survivor区和eden的比 8表示两个Survivor:eden=2:8 一个Survivor占年轻带1/10 |
-XX:+HeapDumpOnOutOfMemoryError | OOM时导出堆到文件 |
-XX:+HeapDumpPath | 导出OOM的路径 |
-XX:OnOutOfMemoryError | 在OOM时执行一个脚本如" -XX:OnOutOfMemoryError=D:/tools/jdk1.7/bin/printstack.bat %p" |
-XX:PermSize -XX:MaxPermSize | 设置永久区的初始值和最大空间 |
-Xss | 栈空间 |
-XX:+UseSerialG | 在新生代和老年代使用串行收集器 |
-XX:+UseParNewGC | 在新生代使用并行回收器 |
-XX:+UseParallelGC | 在新生代使用并行回收器 |
-XX:+UseParallelOldGC | 老年代使用并行回收器 |
-XX:+ParallelGCThreads | 设置用于垃圾和i收的线程数 |
-XX:+UseConcMarkSweepGC | 新生代使用并行回收器,老年代使用CMS+串行回收器 |
-XX:ParallelCMSThreads | 设定CMS线程数量 |
-XX:CMSInitiatingOccupancyFraction | cms并行回收器设置触发阈值(老年代空间使用多少后触发) |
-XX:+UseCMSCompactAtFullCollection | Full GC后,进行一次碎片整理 独占丁顿时间长 |
-XX:+CMSFullGCsBeforeCompaction | 设置进行几次FullGC 进行一次碎片整理 |
-XX:+CMSClassUnloadingEnabled | 允许堆类元数据进行回收 |
-XX:CMSInitiatingPermOccupancyFraction | 当永久区占用率达到这一百分比时启用CMS回收器 |
-XX:UseCMSInitiatingOccupancyOnly | 表示只在达到阀值的时候才进行CMS回收 |