JVM学习笔记二

1. JVM内存参数

要求

  • 熟悉常见的 JVM 参数,尤其和大小相关的

堆内存,按大小设置

JVM学习笔记二_第1张图片解释:

  • -Xms 最小堆内存包括新生代和老年代

  • -Xmx 最大堆内存包括新生代和老年代

  • 通常建议将 -Xms 与 -Xmx 设置为大小相等,即不需要保留内存,不需要从小到大增长,这样性能较好

  • -XX:NewSize 与 -XX:MaxNewSize 设置新生代的最小与最大值但一般不建议设置,由 JVM 自己控制

  • -Xmn 设置新生代大小相当于同时设置了 -XX:NewSize 与 -XX:MaxNewSize 并且取值相等

  • 保留是指,一开始不会占用那么多内存,随着使用内存越来越多,会逐步使用这部分保留内存。下同

堆内存,按比例设置

JVM学习笔记二_第2张图片

解释:

  • -XX:NewRatio=2:1 表示老年代:新生代   表示老年代占两份,新生代占一份

  • -XX:SurvivorRatio=4:1 表示新生代中eden区与survivor区的比例表示新生代分成六份伊甸园占四份,from 和 to 各占一份

元空间内存设置  

 JVM学习笔记二_第3张图片

解释:

  • class space 存储类的最基本的信息(如类的名称、类的方法入口),最大值受 -XX:CompressedClassSpaceSize(默认是1个G) 控制

  • non-class space 存储除类的基本信息以外的其它信息(如方法字节码、注解等)

  • class space 和 non-class space 总大小受 -XX:MaxMetaspaceSize 控制,默认没有上限,直接使用电脑的物理内存

注意:

  • 这里 -XX:CompressedClassSpaceSize 这段空间还与是否开启了指针压缩有关,这里暂不深入展开,可以简单认为指针压缩默认开启

代码缓存(CodeCache)内存设置

JVM学习笔记二_第4张图片

解释:

  • 机器码就是存在CodeCache里面的,代码缓存区就是用来缓存编译后的机器码的。
  • 如果 -XX:ReservedCodeCacheSize < 240m,所有优化机器代码不加区分存在一起

  • 否则,分成三个区域(图中笔误 mthod 拼写错误,少一个 e)

    • non-nmethods - JVM 自己用的代码

    • profiled nmethods - 部分优化的机器码

    • non-profiled nmethods - 完全优化的机器码

线程内存设置

JVM学习笔记二_第5张图片

  • -Xss:控制每个线程(也就是虚拟机栈)占用的内存的。
  • 如果不设置,它的大小是跟操作系统有关,如果是64位的Linux操作系统,每个线程晖占用1M的内存。

2. JVM垃圾回收 --- 三色标记与并发漏标问题

三色标记

即用三种颜色记录对象的标记状态

  • 黑色 – 已标记

  • 灰色 – 标记中

  • 白色 – 还未标记

3. 项目中什么情况下会内存溢出,怎么解决的

  • 误用线程池导致的内存溢出(误用固定大小线程池和带缓冲线程池)
  • (单次)查询数据量太大导致的内存溢出
  • 动态生成的类过多导致的内存溢出

一个ArrayList对象占24个字节,一个ArrayList对象里面有一个Object[ ]数组,数组都是占16个字节。

4. 类加载过程、双亲委派机制

类加载过程的三个阶段

  1. 加载

    1. 将类的字节码载入方法区,并创建类.class 对象(类对象)

    2. 如果此类的父类没有加载,先加载父类

    3. 加载是懒惰执行(就是你真正用到这个类时,它才会触发它的类的加载,否则它是不会提前把类加载到这个方法区中的)

  2. 链接

    1. 验证 – 验证类是否符合 Class 字节码规范,合法性、安全性检查

    2. 准备 – 为 类中的静态static 变量分配空间,设置默认值,此时赋值语句并不会执行

    3. 解析 – 将常量池中的符号引用解析为直接引用

  3. 初始化

    1. 编译器会把静态代码块、static 修饰的变量赋值、static final 修饰的引用类型变量赋值,会被合并成一个  类的初始化方法,在类的初始化时 / 阶段被调用。静态变量在初始化时赋值。

    2. static final 修饰的基本类型变量赋值,在链接阶段就已完成

    3. 初始化是懒惰执行(真正用到它时才会进行初始化,没有用到它时不会进行初始化)

注意:类加载只会执行一次。

  • 类加载是懒惰的,首次用到时才加载(下面的初始化条件满足也会导致类加载)
  1. 使用了类.class
  2. 用类加载器的loadClass()方法加载类 
  • 类初始化是懒惰的,满足条件有
  1. main方法所在类
  2. 首次访问静态方法或者静态变量(非final)
  3. 子类初始化,导致的父类初始化
  4. Class.forName(类名,true,loader)或Class.forName(类名)
  5. new,clone,反序列化时

验证手段

  • 使用 jps 查看进程号

  • 使用 jhsdb 调试,执行命令 jhsdb.exe hsdb 打开它的图形界面

    • Class Browser 可以查看当前 jvm 中加载了哪些类

    • 控制台的 universe 命令查看堆内存范围

    • 控制台的 g1regiondetails 命令查看 region 详情

    • scanoops 起始地址 结束地址 对象类型 可以根据类型查找某个区间内的对象地址

    • 控制台的 inspect 地址 指令能够查看这个地址对应的对象详情

  • 使用 javap 命令可以查看 class 字节码 javap -c -v -p 字节码名称(类名.class)

  • javap -v  xx.class    打印堆栈大小,局部变量的数量和方法的参数。

  • javap -v Application.class 查看字节码结构(类的基本信息、常量池、方法定义)
     

  • 第一步是先编译代码而不是运行!!!

JVM学习笔记二_第6张图片

JVM学习笔记二_第7张图片JVM学习笔记二_第8张图片

类对象在java.lang.Class包下。

final修饰的静态变量跟普通static修饰的静态变量到底有什么不一样?

JVM学习笔记二_第9张图片

System.out是一个静态变量。

  1. 使用final修饰的基本类型的变量不会触发类加载
  2. 使用final修饰的引用类型的变量会导致类的加载
  3. 每个类都有一个自己的常量池
  4. 什么是常量池(Constant Pool)?

    就是你的类的运行过程中需要用到的一些常量数据,这些常量数据包括类名....

5. 四种引用

要求:掌握四种引用

面试题:对象引用类型分为哪几类?

强引用

  1. 普通变量赋值即为强引用,如 A a = new A();
  2. 只要使用赋值运算符就是建立了变量跟对象之间的强引用关系。

  3. 通过 GC Root 的引用链,如果强引用不到该对象,该对象才能被回收

软引用(SoftReference)  

  1. 例如:SoftReference a = new SoftReference(new A());

  2. 如果仅有软引用该对象时,首次垃圾回收不会回收该对象,如果内存仍不足,再次回收时才会释放对象(软引用自身占用的内存不会被释放)

  3. 软引用自身需要配合引用队列来释放

  4. 典型例子是反射数据(反射数据都是软引用的)

弱引用(WeakReference)  

  1. 例如:WeakReference a = new WeakReference(new A());

  2. 如果仅有弱引用引用该对象时,只要发生垃圾回收,就会释放该对象

  3. 弱引用自身需要配合引用队列来释放

  4. 典型例子是 ThreadLocalMap 中的 Entry 对象实现 / 使用了弱引用(也就是内存不够时,它会把Entry对象的key占用内存给它释放掉)

虚引用(PhantomReference)

  1. 例如: PhantomReference a = new PhantomReference(new A(), referenceQueue);

  2. 必须配合引用队列(ReferenceQueue)一起使用,当虚引用所引用的对象被回收时,会将虚引用对象入队,由 Reference Handler 线程将虚引用对象入队,这样就可以知道哪些对象被回收,从而对它们关联的资源做进一步处理,由Reference Handler线程释放其关联的外部资源(因为有些Java对象回收后,它们所关联的一些外部资源不是Java占用的,也不是Java的内存,可能占用的是直接内存等,而这些内存的释放需要等Java对象被垃圾回收掉了,才去释放外部的这些资源内存)

  3. 典型例子是 虚引用有一个实现类 --- Cleaner 释放 DirectByteBuffer 关联的直接内存

JVM学习笔记二_第10张图片

  • 引用队列的作用:目的都是为了找到哪些Java对象被垃圾回收,从而进行对它们关联的资源进一步清理。
  • 为了简化api难度,从jdk9开始引入了Cleaner对象(java.lang.ref.Cleaner)。
  • 获取Cleaner对象:Cleaner cleaner = Cleaner.create();Cleaner线程它是一个守护线程。

6. finalize

要求:掌握 finalize 的工作原理与缺点

面试题:请你说一下对finalize方法的理解

finalize

  • 一般回答:它是 Object 中的一个方法,如果子类重写它,垃圾回收时此方法会被调用,可以在其中进行资源释放和清理工作。

  • 优秀回答:将资源释放和清理放在 finalize 方法中非常不好,非常影响性能,严重时甚至会引起 OOM,从 Java9 开始就被标注为 @Deprecated,不建议被使用了。

但是,为什么呢?

你可能感兴趣的:(jvm,学习,笔记)