JVM笔记

内存结构

1.程序计数器

作用:记住下一条jvm指令的执行地址

特点:

        1.是线程私有的

        2.是不会内存溢出的

2.虚拟机栈

2.1定义

  • 栈:线程运行需要的内存空间
  • 栈帧:每个方法运行时需要的内存(参数,局部变量,返回地址)
  • 每个线程只能有一个活动栈帧,对应着当时正在执行的那个方法

问题:

  1. 垃圾分类不会涉及栈内存
  2. 栈内存分配并不是越大越好,因为栈内存越大,线程数目就越小
  3. 方法内的局部变量不一定是线程安全,如果方法内局部变量没有逃离方法的作用的访问,他是线程安全的;如果局部变量引用了对象,并逃离了方法,需要考虑线程安全

2.2栈内存溢出

java.lang.StackOverflowError

3.本地方法栈

java虚拟机调用本地的方法时提供的内存空间

4.堆

  • 通过new 关键字创建的对象都会使用堆内存
  • 他是线程共享的,堆中的对象都需要考虑线程安全的问题
  • 有垃圾回收的机制

5.方法区

1.定义

  • 所有虚拟机线程共享的区域
  • 存储了跟类结构相关的信息(成员变量,方法数据,成员方法和构造器方法的代码)
  • 1.8之前存在永久代
  • 1.8之后存在元空间(本地内存)

2.运行时常量池

  • 源代码要先编译成二进制字节码(类基本信息,常量池,类方法定义,包含了虚拟机指令)
  • 常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名,方法名,参数类型,字面量等信息
  • 运行时常量池,常量池是*.class文件中的,当该类被加载,他的常量池信息就会放入运行时常量池,并把里面的符号地址变成真实地址

3.StringTable串池

  • 底层由hashtable实现
  • 常量池中的字符串仅是符号,第一次用到的时候才变为对象放到串池里面
  • 利用串池的机制,来避免创建字符串对象
  • 字符串变量的拼接原理是StringBuilder new String()
  String s4= s1+ s2;//new StringBuilder()).append(s1).append(s2).toString() new String("ab")  会生成一个新的字符串在堆里面
  • 字符串常量拼接的原理是编译期优化 
/在常量池中找一个ab对象,找到了,所以不会新建对象  javac在编译期间的优化,结果已经在编译期间确定为ab
  • 可以使用intern方法,主动将串池中还没有的字符串放入串池
  • 1.8调用intern方法时,如果池中已经包含了一个与这个字符串相等的字符串,则返回来自这个池的字符串,否则,将此字符串对象添加到池里,并返回对此字符串对象的引用
  • 1.6将这个字符串对象尝试放入串池,如果有则不会放入,如果没有会把此对象复制一份,放入串池,会把串池中的对象返回

4.StringTable位置

1.8堆空间

1.6永久代

5.StringTable垃圾回收

只有在内存紧张时才会触发

6.StringTable性能调优

  • 调整hashtable桶的个数
  • 考虑将字符串对象入池

6.直接内存

  • 常见于NIO操作时,用于数据缓存区
  • 分配回收成本较高,但读写性能高
  • 不受JVM内存回收管理
  • 使用Unsafe对象完成直接内存的分配回收,并且回收需要主动调用freeMemory
  • ByteBuffer的实现类内部,使用了Cleaner(虚引用)来检测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就会由ReferenceHandler线程通过Cleaner的clean方法调用freeMemory来释放内存

垃圾回收

1.如何判断对象可以回收

引用计数法

被引用+1,不被引用-1,为0时回收

弊端:循环引用

可达性分析算法

  • java虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象
  • 扫描堆中的对象,看是否能够沿着GC Root对象为起点的引用链找到该对象,找不到,表示可以回收
  • 这些对象可以作为GC ROOT
    • 启动类加载器的类,核心类,运行期间会用到的类
    • 虚拟机会调用操作系统的方法,操作系统方法引用的java对象
    • 活动线程中使用的对象,栈帧中使用的
    • 正在加锁的对象

四种引用

  1. 强引用 :平时用的引用都是强引用,只有GC Root对象不通过强引用引用该对象时,该对象才会垃圾回收。
  2. 软引用:仅有软引用引用该对象时,在垃圾回收后,内存仍然不足时会再次发生垃圾回收,回收软引用,可以配合引用队列来释放软引用自身
  3. 弱引用:仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用,可以配合引用队列来释放弱用本身
  4. 虚引用:必须配合引用队列使用,主要配合ByteBuffer使用,ByteBuffer被回收时,让虚引用对象cleaner进入引用队列,由ReferenceHandler线程定时在引用队列里查找是否有新入队的cleaner,如果有的话就调用cleaner里的clean方法,调用unsafe.freeMemory

2.垃圾回收算法

  1. 标记清除:速度快,但是空间不连续,会造成内存碎片
  2. 标记整理:没有内存碎片,效率低下
  3. 复制:不会产生内存碎片,占用双倍内存

3.分代垃圾回收

  • 对象首先分配在伊甸园区域
  • 新生代空间不足时,触发minor gc,伊甸园和from存活的对象使用copy复制到to中,存活的对象年龄加一并且交换from to
  • minor gc会引发stw,暂停其他用户的线程,等待垃圾回收结束,用户线程才恢复运行
  • 当对象的寿命超过阈值时,会晋升至老年代,最大寿命是15
  • 如果老年代空间不足,会先尝试触发minor gc ,如果之后空间仍不足,那么触发full gc,stw的时间更长
  • 大对象在老年代空间足够,但新生代空间肯定不足的时候,会直接晋升

4.垃圾回收器

1.串行

  • 单线程
  • 堆内存较少,适合个人电脑
  • 新生代使用复制算法,老年代使用标记+整理算法

2.吞吐量优先(一定时间内垃圾回收的多少)

  • 多线程
  • 堆内存较大,多核cpu
  • 让单位时间内,stw的时间最短
  • 新生代使用复制算法,老年代使用标记+整理算法

3.响应时间优先(CMS)

  • 多线程
  • 堆内存较大,多核CPU
  • 尽可能让单次stw的时间最短
  • 初始标记(标记根对象)----->并发标记(找出剩余的垃圾stw)------>重新标记(因为在并发标记时,用户线程也在工作,所以有可能产生一些新的对象,改变对象的引用)
  • 基于标记+清除算法,有可能造成比较多的内存碎片,导致内存不足,并发失败,cms就会退化为SerialOld,做一次单线程的串行回收(整理碎片)

类加载和字节码技术

1.执行流程

  1. 源代码
  2. 编译成字节码文件
  3. 常量池载入运行时常量池(short类型存在字节码,超过short范围的存在常量池)
  4. 方法字节码载入方法区
  5. main线程开始运行,分配栈帧内存(栈帧里有操作数栈和局部变量表)
  • 条件和循环:字节码中使用goto
  • 异常处理:采用Exception table 有from to table type,一旦在[from,to)范围内出现异常,则通过type匹配异常类型,如果一致,进入target所指示行数
  • finally:所有的异常处理之后或者正常运行都会goto finally那一行,并且会自行catch finally中的异常并throw(但如果finally中出现了return,会吞掉异常)

2.类加载阶段

1.加载

  • 将类的字节码载入方法区中,内部采用C++的instanceKlass描述Java类,他的重要field:
    • _java_mirror即java的类镜像,例如对String来说,就是String.class,通过java代码进行访问的时候,不能直接访问到instanceKlass,只能先找到String.class才能进一步访问到instanceKlass,起到桥梁的作用(instanceKlass存储在方法区,java_mirror存储在堆中)
    • _super父类
    • _fiels成员变量
    • _methods方法
    • _constants常量池
    • _class_locader类加载器
    • _vtable虚方法表
    • _itable接口方法表
  • 如果这个类还有父类没有加载,先加载父类
  • 加载和链接可能是交替进行的

2.链接

  • 验证
    • 验证类是否符合jvm规范,安全性检查
  • 准备
    • 为static变量分配空间,设置默认值
    • static变量在JDK7之前存储于instanceKlass末尾,从JDK7开始,存储于_java_mirror末尾
    • static变量分配空间和赋值是两个步骤,分配空间在准备阶段完成,赋值在初始化阶段完成
    • 如果static变量是final的基本类型和字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成
    • 如果static变量是final的,但属于引用类型,那么赋值会在初始化阶段完成
  • 解析
    • 将常量池中的符号引用解析为直接引用

3.初始化

  • 初始化即调用<cinit>()V,虚拟机会保证这个类的构造方法线程安全(编译器会按从上至下的顺序,收集所有 static 静态代码块和静态成员赋值的代码,合并为一个特殊的方 法 <cinit>()V
  • 发生的时机(类初始化是懒惰的)
    • 会导致类初始化的情况
      • main方法所在类,首先会被初始化
      • 首次访问这个类的静态变量或静态方法时
      • 子类初始化,如果父类还没初始化,会引发
      • 子类访问父类的静态变量时,只会触发父类的初始化
      • Class.forName()
      • new会导致初始化
    • 不会导致初始化的情况
      • 访问类的static final静态常量(基本类型和字符串)不会触发初始化
      • 类对象.class不会触发初始化
      • 创建该类的数组不会触发初始化
      • 类加载器的loadClass方法
      • Class.forName的参数2为false时

3.类加载器

1.分类

括号里为加载的类的地址

  • Bootstrap ClassLoader(JAVA_HOME/jre/lib
  • Extension ClassLoader(JAVA_HOME/jre/lib/ext
  • Application ClassLoader(classpath
  • 自定义类加载器(自定义

会先询问上级是否已经加载了这个类,如果上级都没有加载,则自己加载

2.双亲委派模式

  • 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类(递归),因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时(他的搜索范围中没有找到所需要的的类),子加载器才会尝试去自己加载

3.自定义类加载器

  • 什么时候需要自定义类加载器?
  1. 想加载非classpath,随意路径中的类文件
  2. 都是通过接口来使用不同的实现不同的实现,实现软件的解耦时,常用在框架设计
  3. 一个类有不同的版本,希望新旧的版本同时工作。这些类希望予以隔离,不同应用的同名类都可以加载,不冲突,常用于tomcat容器
  • 操作
  1. 继承ClassLoader父类
  2. 要遵从双亲委派机制,重写findClass方法(不是loadClass方法)
  3. 读取类文件的字节码
  4. 调用父类的defifineClass 方法来加载类
  5. 使用者调用该类加载器的loadClass方法

4.运行时优化(即时编译JIT)

1.分层编译

  • JVM将执行状态分成了5个层次
    • 0层,解释执行(Interpreter )将字节码解释成机器码
    • 1层,使用C1即时编译器编译执行(不带profiling)
    • 2层,使用C1即时编译器编译执行(带基本的profiling)
    • 3层,使用C1即时编译器执行(带完全的profiling)
    • 4层,使用C2即时编译器编译执行
profifiling 是指在运行过程中收集一些程序执行状态的数据,例如【方法的调用次数】,【循环的
回边次数】等
  • 即时编译器(JIT)与解释器的区别
    • 解释器是将字节码解释为机器码,下次即使遇到相同的字节码,仍会执行重复的解释
    • JIT是将一些字节码编译为机器码,并且存入Code Cache,下次遇到相同的代码,直接执行,无需再编译
    • 解释器是将字节码解释为针对全平台都通用的机器码
    • JIT会根据平台类型,生成平台特点的机器码
  • 对于占据大部分的不常用代码,我们无需耗费时间将其编译成为机器码,而是采取解释执行的方法运行;另一方面,对于仅占据小部分的热点代码,我们则可以将其编译成机器码,以达到理想的运行速度。执行效率Interpreter<C1<C2
  • 即时编译器(JIT)的总的目标是发现热点代码,并加以优化
  • 逃逸分析:在C2编译器里运行,分析new Object()是否逃逸,会不会在循环外被使用,发现不会。所以JIT进行逃逸分析之后,将对象创建的字节码替换掉

2.方法内联

如果发现某方法是热点方法,并且长度不长时,会进行内联,所谓内联就是把方法内代码拷贝粘贴到调用者的位置

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值