JVM学习

1.1  JVM 种类

重用HOTSPOT。还有些列入 IBM开发的灯。

 

1.4 JVM和操作系统的关系

 

1.5 JVM JRE JDK

JDK -> JRE -> JVM

开发需要JDK,运行需要JRE

 

1.6 JVM虚拟机规范

JVM是栈结构。JVM翻译字节码有两种方式,解释执行,还有一种是JIT 。

 

1.7 JVM整体架构

 init 改为 initiation. 先执行 cinit (静态变量,静态方法),再为 init(非静态类成员,接口)。

linking 。

  • verify,验证字节码有没有危险(验证格式,元数据,字节码,符号引用)。
  • prepare。给静态变量赋初值 比如int 给个0, final 的直接赋值。初始化(成员变量不会在这赋值,init才给出始化值)
  • resolve。将符号引用换成直接引用。(直接引用,可以直接链接),解析阶段则把这个类激活了

 

pc registers

占用内存小,线程私有

thread-specific data

大致为字节码(byte code)行号   靠线程里面的程序计数器。因此可以找到下一条执行指令。每个线程有一个独立的计数器。因此不同线程切换执行可以正常执行
VM stack area 线程私有,空间连续 continuous space 局部变量表,操作栈,动态链接、方法返回地址等  -xss
heap area 线程共享,生命周期跟JVM相同,空间地址可以不连续 保存对象的实例, 所有对象实例都要在堆上分配

 -Xms

-Xsx

-Xmn

(JDK 8)

 

method area

(meta area)

线程共享,生命周期跟JVM相同,空间地址可以不连续

存储已被虚拟机加载的类信息,常量,静态变量。即时编译器编译

后的代码等数据

   
native method area 线程私有 为链接native方法使用    

 

1.8 JVM运行时内存介绍

1)runtime area JDK8

JDK 8 中,method area 换成了 meta space。meta space 和 direct memory 放在了jdk意外的地方,单独有一块内存

2)JDK8 VS JDK7 Runtime Area
JDK7

 

JDK8

 

3)JDK7 VS JDK8一些问题
  • JDK8移除了permgen,替换为 metaspace
  • 永久代中的class meta转移到了 native memory
  • 永久代的interned Strings 和 class native variables 转移到了 java heap

 

4) 为什么要把permgen 换成 metaspace
  • 字符串在永久代中,容易出现性能问题和内存溢出
  • 类及方法信息等比较难确定大小,对永久代的大小指定比较难,太小容易出现永久代溢出,太大容易出现老年代溢出
  • 永久代GC带来不必要的复杂度,回收效率低
  • hotspot 与 jrockit 可能合并,jrockit没有永久代

 

1.10 虚拟机栈

每个方法执行时候都有一个栈帧。

Local Variable Table
  • 存基本的8中基本类型
  • 存对象引用
  • 存返回地址类型
  • long double 占两个slot
Operand Stack
  • 把局部变量表或者方法中的常量压入到 OS栈中,方便后面执行,入栈
  • 计算完后返回给LVT 或者 方法
dynamic linking
  • 运行时候有常量池,存储方法的符号引用
  • dynamic linking 把符号引用转换为直接引用
return address
  • 存放调用该方法的PC寄存机的值(正常返回)

 

1.16 本地方法栈

为虚拟机使用的本地方法服务。

  • native 调用非java语言服务。
  • 线程私有

 

1.17 堆

JVM里面最大一块区域,堆里面基本存放所有对象(有些对象可以存放在方法区)

  • 最大一块区域
  • 线程共享 (Thread Local Allocation Buffer 是私有的)
  • 虚拟机启动时候创建
  • 存放对象实例
  • 垃圾回收的主要区域
  • 现在的垃圾回收算法基本为分代回收算法
  • java堆在物理存储是不连续的,逻辑是连续的
  • 方法结束后对象不会立马失效,等GC来回收
1)设置大小
  • 内存大小 -Xmx 内存最大值,  -Xms 内存最小值 

 

2) 堆的分类
JDK7

 

  • 年轻代 (Eden,S0,S1)
  • 老年代
  • 永久代
JDK8

 

  • 年轻代
  • 老年代
  • (没有永久代,换成了metaspace)

 

1)年轻代与老年代

 

  • 年轻代大小一般是1,老年代大小一般是2
  • Eden ,survivor0,survior1 默认为 8:1:1

 

-XX:NewRatio Old = 2,  Old : New。

-XX:SurvivorRation = 8  Eden  : Young.

-xmn young

 

1.21 对象分配过程

  • new 在Eden创建
  • Eden 满了会GC。MinorGC (整个young 都能清理)
  • 触发GC Eden剩余放入S0 年龄+1, 如果S0满了,放入S1,年龄+1
  • 触发GC S0没回收的放入S1,垃圾的年龄+1
  • 触发GC S1 再放入S0, 当年龄达到15,放入old.  --XX:MaxTenuringThreshold = N
  • 老年代内存不足,GC: Major GC. OutOfMemory, 当老年代也没法保存

 

1.22 GC

minor GC 新生代
  • 指的是Eden满了,Survior 满了不会 GC
  • Minor GC会引发STW。短暂暂停用户的线程,等待Minor GC结束
Major GC 老年代
  • 会先尝试触发Minor GC,如果还不足再Major GC
  • 如果还不足 OOM
  • Major GC速度慢10倍以上
Mixed GC 混合  

Full GC

全部回收
  • 调用System.gc(), 系统不会立马执行 Full GC
  • 老年代不足,会Full GC (老年代不足会同时触发Major 和 Full GC)
  • 方法区空间不足也会

 

1.24 元空间

  • 类的元信息放入metaspace,静态变量和常量放入堆中。以前(元信息,静态变量、常量放入方法区)
  • 元数据现在在本地空间则可以设置比较大。
  • 也不能无限大
  • --MetaspaceSize 初始空间大小

永久代 (元信息,静态变量,常量)内容太大,不好分配内存,GC会增加复杂度。

 

1.24 方法区

  • 线程共享,存运行时候常量池,类信息
  • 元空间和永久代是方法去的落地实现
  • .class 放入方法区 引用放入栈,new 对象放入堆

 

1.25 常量池

是class文件中。引用对应了相关信息

 

1.26 直接内存

操作本地内存. 

 

1.38 初始化

类的初始化是类加载的最后一个步骤。用cinit类构造方法,javac自动生成。初始化静态变量,静态代码块赋值。父类的cinit先执行

// Press Shift twice to open the Search Everywhere dialog and type `show whitespaces`,
// then press Enter. You can now see whitespace characters in your code.
public class Main {
    static {
        i = 1;
        // System.out.println(i); 报错
    }
    
    static int i = 2;
}
  • 先prepare i = 0 赋值
  • 再 i = 1;
  • System.out.println(i) 报错。
  • 最后再 i = 2; 

接口中不执行cinit方法。 

cinit 优于 init

 

1.39 类加载器

1)Bootstrap 加载资源,2)自己定义 加载路径,自定义等,例如不同的类加载到不同的应用需要写自定义类加载器。

 

1.48 垃圾回收

1) 垃圾回收算法

引用计数法 (判断对象生死)

reference counting

被引用的时候计数器+1,引用失效时候计数器-1

优:

  • 实现简单,执行效率高

缺:

  • 无法检测循环引用

object1.obj = object.2

object2.obj = object.1

 

object1 = null;

object2 = null;

可达性算法 (判断对象生死)

recheability analysis

从GC Root开始,看引用链。如果对象不再被引用,则为垃圾。

可作为GC Root的是,

  • 栈帧中局部变量表reference引用所引用的对象
  • static 静态引用对象
  • final 常量引用的对象
  • 本地栈中JNI (Native)方法引用的对象
  • Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(NullPointException)
  • 所有被同步锁持有的对象
  • 反应虚拟机内部情况的JVMBean,注册的回调,本地代码缓存

 

真正宣告一个对象死亡,要经过两次标记,然后会被一个低优先级线程Finalizer执行。所以finalize不会立刻执行

  • 没有跟GC root 链接,然后进行筛选,判断有没有必要执行finalize。如果该对象没有被finalize覆盖,或者finalize里面对象并没有建立连接,或者finalize已经执行过(finalize 只执行一次),则没必要回收,直接宣告死亡
  • 否则判定为执行finalize,则把这个对象放入 F-queue中。然后收集器会对F-Queue的对象进行二次标记。只要与引用链再建立连接即可。
 
分代收集算法(严格来说是一种理论)
  • Minor 清理年轻代,Eden,S0,S1一起清理,频率高
  • Major 清理 Old。频率低
  • Full GC 全清理,(JDK 7 前永久代也会清理)
 

标记清除法 (回收算法,其他垃圾回收算法的基础)

mark and sweep

两种

  • 标记存活,回收没被标记的
  • 标记死亡,回收被标记的

从GC ROOT开始查找引用链,没有被引用的标记一下。

缺:

  • 执行效率不稳定,要对大量对象进行标记,则效率比较低
  • 清除后空间不连续.. are seperated by small areas

 

标记复制法 (回收算法)

mark and copy

过程:

  • 把存活对象整体复制到保留区域
  • 把剩余区域初始化,初始化过程比较快

98% 的垃圾在 Eden被销毁,然后把Eden存活的和S0 或者S1 存活的放入 S1 或者S0,因为存活对象少,因此需要的保留区域小,所以在Young Generation中可以使用标记复制法。

 

缺:

  • 需要预留区域(浪费例如 S0,S1 默认设定的10%空间),可用内存会变少。因此GC频率会高
  • 如果存活对象比较多,复制的比较多。效率低
  • 老年代99%对象都是存活的,无法用这个算法

 

标记整理算法 (垃圾回收算法)

mark-compact algorithm

一开始和标记清除一样,只不过会把存活对象按照顺序依次移动成连续空间。

 

缺点:

移动对象复杂度高,停顿时间长。

 

优:

从程序吞吐量来看,移动对象更划算,

 

 

 

1.52 对象的引用

强引用 A a = new A(); 不会靠回收来解决内存不足的问题。指的是会抛出OutOfMemory
软引用 String str = new String("abc"); // 强引用
SoftReference<String> softRef = new SoftReference<String>(str);
当需要构造一些占用内存比较大的对象使用
内存不足时候会回收,内存足时候不会回收
弱引用 String str = new String("abc"); //强引用
WeakReference<String> weakRef = new WeakReference<String>(str); // 弱引用。当需要构造一些占用内存比较大的对象使用
下一次GC时候会被回收,不管内存是否足够
虚引用 PhantomReference 一定要与ReferenceQueue联合使用,肯定会被加入到队列中,用来跟踪对象被垃圾回收的过程,不决定对象的生死,只有当对象被回收的时候,如果发现该对象还有虚引用,会被加入到ReferenceQueue中

 

1.57 垃圾回收器

 

重点是 CMS,G1.

Serial GC

-XX: +UseSerialGC

年轻代、老年代都会是 Serial

会有Stop the world,暂停用户线程

  • 当用户线程在safepoint点,会采用标记复制法清除young
  • 当在safepoint时候,会采用标记整理清除old
Parallel GC

 

-XX: +ParNew

 

回收年轻代是多线程的Serial 版本,其他一样

看CPU是否多核,来选择

 

服务器用的多,大数据平台等,

JDK 8 默认用 Parallel Young, Parallel Old。

JDK 9 默认G1 比较主流

JDK 14 移除了 CMS,弃用 Para young Para Old

Parallel Scavenge  -XX: 

回收年轻代,多线程,复制算法,以吞吐量优先

自适应调节策略,动态改变 Eden,S0,S1 比例

适合后台运算,交互不多的任务,例如订单处理、科学计算

CMS GC  JDP 14废除

 停顿时间短,在交互多的应用上用效果比较好

  • 初始标记 (stop the world)
  • 并发标记 不暂停用户线程,跟用户线程并发执行, 此时有可能用户线程修改了引用状态
  • 重新标记 stop the world 修复上述步骤
  • 并发清除 和用户线程并发清除垃圾

并发清除有空间碎片问题。

三色标记

  • 白 没被访问过
  • 黑 本对象已经被访问过,并且引用到的其他对象也全被访问过
  • 灰 本对象已经访问过,本对象引用的其他对象尚未全部访问完

白-> 灰 -> 黑

 

缺点:

  • 对CPU资源敏感,有些资源分给了垃圾回收
  • CMS无法处理浮动垃圾 (无法处理的垃圾,留到下一次FULL GC)
  • 基于标记清除,有空间碎步
G1 GC  -XX:+UseG1GC

 多核CPU,大容量内存服务器 用G1,比较主流

  1. 把内存划分为多个区域,不是各种代的话,而是整体堆空间划分
  2. 区域保留了新生代,老年代,物理不联系,逻辑连续
  3. 充分利用多CPU,
  4. 整体用标记整理法,不会有碎片产生,部分用复制算法,超过region大小的1.5倍放入H区
  5. 停顿可预测,回收垃圾时间不超过设置时间
  6. 优先回收高价值垃圾(哪块区域的垃圾多)

 

吞吐量:用户时间  / (用户时间 + 垃圾回收时间)

 

1.60 Parallel Scavenge 收集器

Parallel 

posted @ 2024-03-27 21:17  ylxn  阅读(3)  评论(0编辑  收藏  举报