栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JVM day05

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JVM day05

垃圾回收调优 新生代调优 特点

  • TLAB即线程本地分配缓存区,这是一个线程专用的内存分配区域。作用是每个线程先在自己私有的与伊甸园分配内存,不会被其他线程所干扰
新生代内存设置越大越好吗
  • 不是,新生代越大,老年代空间越少,一旦新生代不断创建对象却不回收,去到老年代,一旦引发Gc就是fullFC
调优特点
  • 新生代能容纳所有【并发量 * (请求-响应)】的数据。 (因为可以将更多很快回收的数据保留在新生代中,这样就不会触发fullGC了)
  • 幸存区大到能保留【当前活跃对象+需要晋升对象】。

老年代调优

以 CMS 为例:
1.CMS 的老年代内存越大越好。因为如果当你Full GC 发生后,因为在CMS中垃圾回收是并发的,所以会产生浮动垃圾,当浮动垃圾特别多的时候就不会发生CMS Full GC,就会退化成SerialOld GC 这样就会导致时间变得很长。
2.先尝试不做调优,如果没有 Full GC 那么已经内存可以满足,否则先尝试调优新生代。
3.观察发生 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3
-XX:CMSInitiatingOccupancyFraction=percent (当老年代的空间占用占老年代总内存的多少时,就发生Full GC)

案例

案例1 Full GC 和 Minor GC频繁

Minor GC频繁说明内存空间紧张,新生代中内存紧张后,会把对象晋升老年代的阈值降低,这样就会导致老年代中存在生命周期很短的对象,触发了老年代的Full GC。

  • 解决方法:增大新生代内存空间大小,使晋升阈值增大,这样老年代中对象就会变少,Full GC就不会频繁。

案例2 请求高峰期发生 Full GC,单次暂停时间特别长 (CMS)

先查看GC日志,看看那个时间较长。发现重新标记时间较长,因为重新标记是需要扫描整个堆内存(新生代和老年代)。

  • 解决方法:可以在重新标记之前,在新生代触发一次垃圾回收,这样重新标记时查找的对象就会变少。

案例3 老年代内存充裕情况下,发生 Full GC (CMS jdk1.7)

因为在1.7是永久代,就算永久代内存不足也会导致Full GC 。

  • 解决办法:增大元空间的初始值和最大值。而在1.8是元空间,是由系统直接管理,内存很充裕。
javap工具

为了方便分析类文件结构,Oracle提供了javap工具来反编译class文件

javap -v HelloWorld.class // -v显示更多信息
  • 注释的意思是当前行号所查到的结果

类加载阶段 加载
  • 1)通过一个类的全限定名来获取定义此类的二进制字节流。

  • 2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

  • 3)在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。

  • 如果这个类还有父类没有加载,先加载父类

  • 加载和链接可能是交替运行的

链接 验证

验证是否符合JVM规范,安全性检查

  • 分为如下四个步骤
  1. 文件格式验证
  2. 元数据验证
  3. 字节码验证
  4. 符号引用验证
准备

为静态变量分配内存并设置默认值零值

  1. 静态变量分配空间和赋值是两个步骤,分配空间在准备阶段完成,赋值在初始化阶段完成。
  2. 如果静态变量是final的基本类型,以及字符串常量,那么赋值是在准备阶段就已经完成了
  3. 如果静态变量是final的,但属于引用类型(new对象),那么赋值也会在初始化阶段完成
解析

解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程。

  • 符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。
  • 直接引用(Direct References):直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。

解析的具体流程分为如下几个阶段:

  • 1.类或接口的解析
  • 2.字段解析
  • 3.方法解析
  • 4.接口方法解析
初始化 < init()> V 方法

在初始化阶段,则会根据程序代码去初始化类变量和其他资源(例如,静态变量赋值动作和静态语句块(static{})中的语句)。

初始化阶段就是执行类构造器< clinit>()方法的过程。

类加载器

  • 对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。

    这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。

类加载器的优先级(由高到低):启动类加载器 -> 扩展类加载器 -> 应用程序类加载器 -> 自定义类加载器

启动类加载器
  • -Xbootclasspath 表示设置 bootclasspath
  • 其中 /a:. 表示将当前目录追加至 bootclasspath 之后
  • 可以有以下几个方式替换启动类路径下的核心类:
    • java -Xbootclasspath: < new bootclasspath>
    • 前追加:java -Xbootclasspath/a:<追加路径>
    • 后追加:java -Xbootclasspath/p:<追加路径>
扩展类加载器
  • 扩展类加载器加载的类必须是以jar包方式存在
应用程序加载器
  • 这个类加载器由 sun.misc.Launcher$AppClassLoader 来实现。由于应用程序类加载器是ClassLoader类中的getSystem-ClassLoader() 方法的返回值,所以有些场合中也称它为“系统类加载器”。它负责加载用户类路径(ClassPath)上所有的类库,开发者同样可以直接在代码中使用这个类加载器。如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
自定义类加载器

什么时候需要自定义类加载器:

  • 1)想加载非 classpath 随意路径中的类文件
  • 2)都是通过接口来使用实现,希望解耦时,常用在框架设计
  • 3)这些类希望予以隔离,不同应用的同名类都可以加载,不冲突,常见于 tomcat 容器

步骤:

  • 继承 ClassLoader 父类。
  • 要遵从双亲委派机制,重写 findClass 方法 注意不是重写 loadClass 方法,否则不会走双亲委派机制。
  • 读取类文件的字节码。
  • 调用父类的 defineClass 方法来加载类。
  • 使用者调用该类加载器的 loadClass 方法。
双亲委派机制
  • 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

  • 为什么要用双亲委派机制?

避免重复加载 + 避免核心类篡改:

  • 采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父加载器已经加载了该类时,就没有必要子加载器再加载一次。
  • 其次是考虑到安全因素,java 核心 api 中定义类型不会被随意替换,假设通过网络传递一个名为 java.lang.Integer 的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的 java.lang.Integer,而直接返回已加载过的 Integer.class,这样便可以防止核心API库被随意篡改。
运行期编译 即时编译

逃逸分析

使用逃逸分析,编译器可以对代码做以下优化:

  1. 同步省略。如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步
  2. 将堆分配转换为栈分配。如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配。
  3. 分离对象或标量替换,有点对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。

这里开启和关闭逃逸分析用这个:

-XX:+DoEscapeAnalysis : 表示开启逃逸分析

-XX:-DoEscapeAnalysis : 表示关闭逃逸分析 从jdk 1.7开始已经默认开始逃逸分析,如需关闭,需要指定-XX:-DoEscapeAnalysis

方法内联

JMM内存模型

JMM 即 Java Memory Model,它定义了主存(共享内存)、工作内存(线程私有)抽象概念,底层对应着 CPU 寄存器、缓存、硬件内存、 CPU 指令优化等。JMM 体现在以下几个方面:

  • 原子性: 保证指令不会受到线程上下文切换的影响。

  • 可见性:保证指令不会受 cpu 缓存的影响。

  • 有序性:保证指令不会受 cpu 指令并行优化的影响。

    简单的说,JMM 定义了一套在多线程读写共享数据时(成员变量、数组)时,对数据的可见性、有序 性、和原子性的规则和保障。

原子性

synchronized(同步关键字):

synchronized( 对象 ) {
	要作为原子操作代码
}
可见性

解决方法
volatile(易变关键字):

它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到 主存中获取它的值,线程操作 volatile 变量都是直接操作主存。

 public static volatile boolean run = true; // 保证内存的可见性

它保证的是在多个线程之间,一个线程对 volatile 变量的修改对另一 个线程可见, 不能保证原子性,仅用在一个写线程,多个读线程的情况

有序性

指令重排简单来说可以,在程序结果不受影响的前提下,可以调整指令语句执行顺序。多线程下指令重排会影响正确性。

解决方法
volatile 修饰的变量,可以禁用指令重排,禁止的是加volatile 关键字变量之前的代码重排序

volatile 原理

  • volatile 的底层实现原理是内存屏障,Memory Barrier(Memory Fence)
  • 对 volatile 变量的写指令后会加入写屏障
  • 对 volatile 变量的读指令前会加入读屏障

如何保证可见性?

写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中

public void actor2(I_Result r) {
     num = 2;
     ready = true; // ready 是被 volatile 修饰的,赋值带写屏障
     // 写屏障
}

而读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据:

public void actor1(I_Result r) {
     // 读屏障
     // ready是被 volatile 修饰的,读取值带读屏障
     if(ready) {
        r.r1 = num + num;
     } else {
        r.r1 = 1;
     }
}

如何保证有序性?

写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后

public void actor2(I_Result r) {
     num = 2;
     ready = true; // ready 是被 volatile 修饰的,赋值带写屏障
     // 写屏障
}

读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前

public void actor1(I_Result r) {
     // 读屏障
     // ready 是被 volatile 修饰的,读取值带读屏障
     if(ready) {
        r.r1 = num + num;
     } else {
        r.r1 = 1;
     }
}

注意:

volatile 不能解决指令交错

写屏障仅仅是保证之后的读能够读到最新的结果,但不能保证其它线程的读跑到它前面去

而有序性的保证也只是保证了本线程内相关代码不被重排序

CAS与原子类

这里需要总结一下,CAS就是Java中的乐观锁,synchronized关键字就是Java中的悲观锁

CAS 是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系, 我吃亏点再重试呗。
synchronized 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁 你们都别想改,我改完了解开锁,你们才有机会。
原子操作类:juc(java.util.concurrent)中提供了原子操作类,可以提供线程安全的操作,例如:AtomicInteger、 AtomicBoolean等,它们底层就是采用 CAS 技术 + volatile 来实现的。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1035935.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号