聊聊JVM

一、什么是JVM

JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行

二、JVM干了什么

1、Java 内存模型

在这里插入图片描述

根据《Java 虚拟机规范(Java SE 7 版)》规定,Java 虚拟机所管理的内存图所示。


  • 存放对象实例、分配内存。所有线程共享
  • 方法区
    属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  • 本地方法栈
    为native方法服务
  • 程序计数器
    当前线程多执行的字节码的行号指示器
  • 虚拟机栈
    存储局部变量表、操作栈、动态链接、方法出口
2、类加载器
  • 加载器种类
    1. 自定义加载器
    2. 应用加载器
    3. 拓展加载器
    4. 启动类加载器
  • 类加载过程
    1. 加载,查找并加载二进制的数据,在java堆中创建一个java.lang.class类的对象
    2. 连接
      1. 验证 文件格式、元数据、字节码、符号引用验证
      2. 准备 为类的静态变量分配内存,并将其初始化默认值
      3. 解析 把类的符号引用转换为直接引用
    3. 初始化 为累的静态变量赋予正确的初始值
3、GC
  • 哪些内存需要回收
    1. 堆区和方法去
  • 怎么判断哪些对象需要被回收
    1. 引用计数法(不用)
    2. 可达性分析算法
    3. 引用
  • 都有哪些回收算法
    1. 标记-清除算法
    2. 复制算法
    3. 标记-整理算法
    4. 分代回收算法
      1. 年轻代的回收算法
        • 所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。
        • 新生代内存按照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区满了才触发)。
      2. 年老代(Old Generation)的回收算法a)
        • 在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。b) 内存比新生代也大很多(大概比例是1:2),当老年代内存满时触发Major GC即Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率标记高。
      3. 持久代/元数据区
        • 1.8之后移除了永久代,取而代之的元数据区,元数据区和堆内存区分开了。

二、JVM优化

  1. JIT即时编译及优化技术

    1. 两种模式(即对应编译器的种类)
      • client(C1编译器)
        • 启动速度较快,代码优化相对不好
      • server(C2编译器)
        • 启动速度慢,代码优化更好
    • 热点代码
      1. 热点代码有哪些
        • 被多次调用的方法
        • 被多次执行的循环体
      2. 如何判断热点代码
        • 抽样探测
          • 周期性的检查各个栈顶的,如果发现某些方法一直处于栈顶,则是热点代码
          • 好处是处理简单,缺点是容易被阻塞的线程影响精度
        • 计数法(jvm默认使用)
          • 为每个方法建立计数器
          • 优点是精确度高,缺点是实现复杂
      3. 计数器种类
        1. 方法调用计数器
          • 统计方法被调用的次数,默认阈值在 Client 模式下是 1500 次,在 Server 模式下是 10000 次
        2. 回边计数器
          • 统计循环体执行次数
    • 什么是编译和解释
      • 编译器:把源程序的每一条语句都编译成机器语言,并保存成二进制文件,这样运行时计算机可以直接以机器语言来运行此程序,速度很快
      • 解释器:只在执行程序时,才一条一条的解释成机器语言给计算机来执行,所以运行速度是不如编译后的程序运行的快的;
    • 编译优化技术
      • 公共子表达式消除:如果一个表达式 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 中的对象。标量替换如果一个对象不会被外部访问,并且对象可以被拆散的话,真正执行时可能不创建这个对象,而是直接创建它的若干个被这个方法使用到的成员变量来代替。这种方式不仅可以让对象的成员变量在栈上分配和读写,还可以为后后续进一步的优化手段创建条件。
  2. 调优常用参数

参数描述
-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:+HeapDumpOnOutOfMemoryErrorOOM时导出堆到文件
-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:CMSInitiatingOccupancyFractioncms并行回收器设置触发阈值(老年代空间使用多少后触发)
-XX:+UseCMSCompactAtFullCollectionFull GC后,进行一次碎片整理 独占丁顿时间长
-XX:+CMSFullGCsBeforeCompaction设置进行几次FullGC 进行一次碎片整理
-XX:+CMSClassUnloadingEnabled允许堆类元数据进行回收
-XX:CMSInitiatingPermOccupancyFraction当永久区占用率达到这一百分比时启用CMS回收器
-XX:UseCMSInitiatingOccupancyOnly表示只在达到阀值的时候才进行CMS回收
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值