【JVM Ⅰ】内存与垃圾回收学习笔记

推荐一本书:《深入理解JVM》
再来一本:《自己动手写Java虚拟机》
这篇文章写得是挺乱的,但还是按照理解来写了一些比较重要的东西,有很多还不能够完全理解,等到时候去二刷吧,立一个Flag【2021年6月6日】

在这里插入图片描述

前言:

JVM不关心各种语言(不管是Java还是其他语言,都可以在JVM上面运行),JVM只关心字节码文件,需要符合JVM字节码规范。
11年出来了G1:包含了自动的内存管理 and 自动的垃圾回收功能

JVM架构:

  • stack:适配小设备,跨平台(各种平台都可以重复使用),可移植,指令更多,指令集更小,性能更差。
  • register:指令更少,性能比stack高

各种JVM版本:

  • classic VM(sun) :只有解释器,只会运行,没有热点探测,没有优化
  • EXACT VM :热点探测,混合工作(编译器,解释器)
  • HotSpot VM :热点探测,融合了JRockit 的优势
  • JRockit VM :最快的JVM,直接面向服务端
  • J9 VM :
  • Graal VM

JIT即时编译器:

  • 探测热点代码,暂停时间过长(因为JVM需要给他的内部进行优化)~等公交
  • 时间与性能(运行效率)的博弈

分类:

很重要:

  1. 类加载子类系统:
    • 引导类加载器
    • 拓展类加载器
    • 应用类加载器
    • 自定义类加载器
  2. 运行时数据区域:
    • 堆区、栈区、方法区、寄存器区
  3. 执行引擎:
    • 垃圾回收、编译器、即时编译器

类加载器子类系统:

各个ClassLoader类加载器简介:

包含了引导类、拓展类、应用类引导器

  • 引导类是由c/c++编写的,在程序内不可获取;Java的核心类库都是在这里加载的(只加载java、javax、sun开头的类)
  • 拓展类是由Java语言编写的,都是在lib\ext包下的。
  • 自定义类:可以对源码进行加密,可以防止源码泄露
  • 继承URLClassLoader可以更加方便的测试(不用像继承ClassLoader一样需要编写findClass方法和字节流……)

类的加载

  • 加载:生成Class对象,加载各种类(引导、拓展、应用/系统类)
  • 连接:
    • 验证:确保Class文件满足JVM字节码的要求
    • 准备:先准备好变量容器(变量就是容器,先赋零值),static final修饰的在编译的时候已经分配好了。
    • 解析:将符号转变为直接引用
  • 初始化:
    • 【0】init执行默认构造器,子类加载init之前,父类完成加载(一个类只会被加载一次
    • 【1】clinit == 执行方法:== class init
    • Tips:执行静态代码块 static {}/静态的变量会在编译的时候就给予赋值

ClassLoader的获取方法:

public static void main(String[] args) throws Exception {
  // 【1】当前类的类加载器
  ClassLoader classLoader1 = Class.forName("java.lang.String").getClassLoader();
  System.out.println(classLoader1);

  // 【2】当前线程上下文加载器
  ClassLoader contextClassLoader2 = Thread.currentThread().getContextClassLoader();
  System.out.println(contextClassLoader2);

  // 【3】引导类加载器(当前类的父级)
  ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader().getParent();
  System.out.println(systemClassLoader);
}

双亲委派机制:(加载class文件的方式)

  • 如果一个类加载器接收到了请求,不会自己先去加载,而是会把请求交给父类加载器,直至顶层的类加载器
  • 顶层的类加载器如果能够处理,那么就返回成功,不能处理则交给子类加载,层层下传直到最底层的自定义类加载器。

好处

  • 防止类的重复加载,保证Java核心包的类不被替换
  • 按需请求:只有用某个类的时候才会去请求加载某个类,并且将这个类加载到内存中。
  • 区分类:同时双亲委派机制也可以帮助我们去区分两个类,两个类的包名不一样是不同的类,两个类的加载器不一样也不是同一个类。

沙箱安全机制:

进入方法我们就需要执行main方法,但是此时main方法所在的类需要被加载,而String没有main方法,所以报错。
开始使用的时候,我们自定义的String类会使用引导类加载器加载;而引导类加载器会使用rt.jar包下的String类,因为rt.jar包下的String类里面没有对应的方法,所以报错,这也体现了对源码的保护功能 == 沙箱安全机制


运行时数据区:

包含了【本地方法栈、虚拟机栈、堆、方法区、PC寄存器(计数器)】
我们最常用的就是运行时数据区,“厨子”就是执行引擎

  • 共享:方法区、堆区【主要的GC对象】
  • 私有的:程序计数器,本地方法栈(本地方法接口、本地方法接口),虚拟机栈

内存】就是CPU和网络/硬盘的 桥梁

1、PC寄存器

Program Counter Register 程序计数器

  • Def:存储了下一条指令的地址(就像一个游标一样,告诉程序下一步应该怎么走。)
  • 被Thread私有,每个线程独有一个程序计数器
  • 不存在【GC、OOM】

2、JVM虚拟机栈

栈中包含了栈帧,一个栈帧对应着一个方法。
就像Stack一样,来一个就放一个,只处理栈顶元素,管理【调用&返回】,【不存在GC】,但是有【栈溢出】
使用的指令架构:【

栈溢出:

内存溢出:栈溢出,但存在动态扩展机制,直到内存占满了才会出现OOM
线程:独有栈,之间的栈帧不共用。

栈帧:LV OS DL RA andsoon

局部变量表(Local Variables)、操作数栈(operand Stack)、动态链接(指向常量池)、方法返回地址(Return Address)、符加信息

动态链接:访问/指向【运行时常量池】的方法引用
  • Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用(Symbolic Reference )保存在class文件的常量池里,此时我们就需要一个动态连接符号引用转变为方法的直接引用
  • Constant Pool : #1 = ……

方法调用的原理:

  • 静态绑定:早期绑定——编译时——构造方法
  • 动态绑定:晚期绑定——运行时——上转型对象(多态)
  • 前提:1.父子关系;2.重写(三方API)
  • 静态方法不可被重写,不加super/this == 虚方法(编译的时候不确定。)
  • 同时有俩方法,接口的优先级比较低,父类重写就用自己的。
局部变量表:方法结束就销毁,表是一维的表
  • 变量表需要显式赋值,否则无法使用。
  • Slot 变量槽
  • byte、short、boolean、char == > int,都是占用一个slot
  • long、double、float 占用两个slot
操作数栈:基于栈的执行引擎

栈= 【数组】{链表实现}

  • 拿到数据到执行引擎之后,执行引擎读到iadd指令,交给CPU进行运算,返回操作数给栈,再返回局变表。
  • 对值进行push、pop、add操作
  • TOS技术:Top-of-Stack Cashing技术 == 栈顶缓存技术
  • 将操作数缓存在【cpu寄存器中】,执行效率up(因为降低了内存的读写操作。)
方法返回地址:带有return信息

正常就是一个返回地址,里面包含了PC程序计数器的值。

附加信息

3、本地方法栈:

很多是底层由C语言写的,执行效率较高。

本地方法接口:

Native Method就是一个java调用非java代码的接口,调用的是非Java代码,没有具体的实现。
**native **是有方法体的,只是我们看不到,和abstract互斥。【非Java代码实现】
优点:执行效率高;与外界交互(底层);解释器也用到C库

4、堆

所有的实例对象+数组都存放在堆中,进程对应着实例,一个进程中的线程共享堆,共享实例对象。
GC流程:对于一个用户线程,不够用再GC(给大内存,减少频繁GC),减少STW,可以去优化用户线程的体验
逻辑连续:新生区+养老区+永久代/元空间

参数设置

-Xmx  最大堆内存大小
-Xms  初始堆内存大小
-Xmn  年轻代大小
-XXSurivivorRatio=3 年轻代被划分为 3:1:1

-Xms10m ; -Xmx10m设置相同的值可以去除扩展和减少的过程,
 <==>  memory start  使初始堆内存大小 = 最大内存大小
  • 默认参数:物理电脑内存/64;最大:电脑内存/4
jps
jstat -gc  进程id
- 设置-XX:PrintGCDetail 读取详细参数

OOM(OutOfMemory)

old区满了,装不下新创建的实例对象——Full GC

  • Default:
    • -XX:NewRatio=2,新生代和老年代比例为1:2
    • -XX:+UseAdaptiveSizePolicy,内存分配策略
    • -XX:SurvivorRatio=8,设置Eden和Survivor的比例

YGC/Minor GC:

  • 过程:
    • Eden区满了找垃圾,将Eden和S0一起回收,总有一个s区是空的(为to区)
    • 阈值默认为15,满了之后会promotion到永久代/元空间中
    • 未达到阈值的对象,把幸存的对象放入s区
  • GC:我们可以频繁的回收新生代,但是尽量不要对养老区进行垃圾回收(比较浪费资源)
    • Full GC:整堆回收
    • Major GC:老年区垃圾回收FGC
    • Minor GC:新生代垃圾回收YGC(占用的时间最短)

Promotion原则:

  • 优先Eden区
    • 大对象直接老年代(但是朝生夕死就很难受)
  • 晋升规则:
    • 长期存活达到阈值就晋升为老年代
    • 动态对象年龄判断
    • 空间分配担保

TLAB:Thread Local Allocation Buffer

堆共享,多个线程同时操作是不安全的,-XX:UseTLAB 内存分配机制
我们给每个线程都留下一些私有的TLAB空间

  • 空间分配担保:

    • 老年代连续空间大于新生代对象的总大小 or 大于之前晋升的平均大小 == YGC,否则Full GC
  • JIT:

    • 若新建的对象没有逃逸出方法区,优化的方式是【栈上分配
    • 可用局部变量就不在外面定义。
  • 基于逃逸分析:

    • 栈上分配
    • 线程同步省略
    • 标量替换(默认使用)

5、方法区

待写……

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

willorn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值