JVM笔记

JVM笔记

所谓的JVM调优,就是调优方法区(方法区是一个特殊的堆)和堆,因为栈(本地方法栈、Java栈)和程序计数器不可能有垃圾

main方法在栈最底下,如果栈有了垃圾,程序直接就崩了。

1. JVM体系结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pNmekRpH-1618942980059)(/Users/shenhangran/Desktop/学习笔记/我的/JVM笔记.assets/image-20200604091239778.png)]

2. 类加载器

作用:加载Class文件~

1、虚拟机自带的加载器

2、启动类(根)加载器 java语言获取不到

rt.jar包里的类 通过根加载器加载

3、扩展类加载器

/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/ext里的东西通过扩展类加载器进行加载

4、应用程序加载器

自己写的类通过应用程序加载器进行加载

3. 双亲委派机制

用于保证安全

APP–>EXC–>BOOT(最终执行根加载器里的同名类)【向上委托

加载器总会优先加载根加载器里的同名类,找不到才加载扩展器加载器里的类(即、抛出异常,通知子类加载器进行加载),还找不到才会加载当前应用程序加载器

对于一些重要的东西,我们可以把他定义在ext文件目录下,用于防止被盗取。但是尽量不要修改rt.jar

类加载器采用的机制就是双亲委派机制

4. native

凡是带了native关键字的,说明java的作用范围达不到了,会去调用底层C语言的库,进入本地方法栈。

调用本地方法本地接口:JNI

JNI作用: 扩展Java的使用,融合不同的编程语言为java所用。

它在内存区域中专门开辟了一片标记区域:Native Mehtod Stack,登记native方法

在最终执行的时候,加载本地方法库中的方法通过JNI

5. 程序计数器

它是线程私有的,每个线程都有一个程序计数器。

6. 方法区

方法区被线程共享,所有字段和方法字节码,以及构造函数,接口代码等特殊方法也在此定义。所有定义的方法的信息都保存在该区域,此区域属于共享区间。

静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量在堆内存中,和方法区无关。

static、final、Class模板(类加载器)、常量池

7. 栈

栈内存,主管程序的运行,生命周期和线程同步

线程结束,栈内存也就释放,对于栈来说,不存在垃圾回收问题,一旦线程结束,栈就over

栈:8大基本类型+对象引用+实例方法

栈+堆+方法区的交互关系

栈里面的引用指向堆里面的具体位置

8. 三种JVM

  • Sun公司
  • BEA Oracle JRockit
  • IBM J9VM

我们学习的都是HotSpot 即第一种

9. 堆

Heap、一个JVM只有一个堆内存,堆内存的大小是可以调节的。

类加载器读取了类文件后,一般把什么东西放到堆中?

  • 类、方法、常量、变量,保存我们所有引用类型的真实对象。
  • 注意,常量池是在jdk1.7的时候,进入了堆中

堆内存中细分为三个区域:

  • 新生区(伊甸园区) Young/New
  • 养老区 Old
  • 永久区 Perm

新生区

  • 伊甸园(Eden Space)
  • 幸存区0区
  • 幸存区1区

轻量级GC在新生区进行


所有的对象都是在伊甸园里new出来的,假设伊甸园最多放10个对象,当伊甸园满了以后,就要进行一次轻量级GC,检查一下哪些是还有用的,哪些是没用的,没用的就删掉了,有用的就“活着”了,并且把活着的放到幸存区0区,放完以后,伊甸园就空了,又能存10个对象了。

假设幸存区也全都满了,就会进行一次重量级GC,把伊甸园和幸存区全部清理一遍,还活着的就放到老年区(养老区)里。

假设养老区也满了,那就OOM了,没法清理了。

养老区

重量级GC(Full GC)在养老区进行

真理:经过研究,99%的对象都是临时对象!所以能进养老区的对象本身就很少,所以很少见到OOM

永久区

即永久存储区

这个区域是常驻内存的。用来存放JDK自身携带的Class对象。Interface元数据,存储的是java运行时的一些环境或类信息。这个区域不存在垃圾回收!

关闭虚拟机就会释放这个区域的内存。

一个启动类,加载了大量的第三方jar包。或者Tomcat部署了太多的应用。或者大量动态生成的反射类不断的被加载。那么最终会导致OOM。

  • jdk1.6之前: 永久代,常量池在方法区里
  • jdk1.7:永久代,但是慢慢退化了,去永久代,常量池在堆中
  • jdk1.8:无永久代,常量池在元空间

jdk1.8之后可以这样理解:

  • 元空间里包含了方法区,方法区里包含了常量池
  • 元空间是逻辑上存在,但是物理上不存在的

虚拟机改变参数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1y1fhWUs-1618942980069)(/Users/shenhangran/Desktop/学习笔记/我的/JVM笔记.assets/截屏2021-04-19 下午8.38.37-8835961.png)]

注1:默认情况下,分配的总内存是电脑内存的1/4、而初始化的内存是电脑内存的1/64

注2:上面的-Xms1024m是连起来写的,没有空格

即:

-Xms1024m -Xmx1024m -XX:+PrintGCDetails

(总内存) (初始内存)

解决OOM的思路

  1. 尝试扩大堆内存看结果
  2. 分析内存,看一下哪个地方出问题了(专业工具)

10. 使用JProfiler工具分析OOM原因

在一个项目中,突然出现了OOM故障,如何排除?研究为什么出错~

  • 能够看到代码第几行出错:内存快照分析工具,MAT,JProfiler
  • DeBug,一行行分析代码!

MAT,JProfiler作用

  • 分析Dump内存文件,快速定位内存泄漏;
  • 获得堆中的数据
  • 获得大的对象

JProfiler安装与配置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UV68k25s-1618942980070)(/Users/shenhangran/Desktop/学习笔记/我的/JVM笔记.assets/image-20210420144003854.png)]

Jprfiler的具体使用案例

// JProfiler与OOM 测试如下:
public class TestUtils {
    byte[] arr = new byte[1*1024*1024]; // 1MB

    public static void main(String[] args) {
        ArrayList<Object> list = new ArrayList<>();
        int count = 0;

        try{
            // 测试通过JProfile查看OOM是什么情况
            // Dump
            /*
            * ---------------------------------------------
            * VM options里加参数:
            * -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
            * 参数分析:
            * -Xms 设置初始化内存分配大小 默认为电脑内存的1/64
            * -Xmx 设置最大分配内存 默认为电脑内存的1/4
            * -XX:+PrintGCDetails 打印GC清理垃圾信息
            * -XX:+HeapDumpOnOutOfMemoryError 生成OOM时的dump文件
            * ---------------------------------------------
            * 然后得到结果:
            * java.lang.OutOfMemoryError: Java heap space
              Dumping heap to java_pid71447.hprof ...
              Heap dump file created [5515476 bytes in 0.012 secs]
            * ---------------------------------------------
            * 然后分析结果:
            * 首先找到Dump文件
            * 左侧 open in finder
            * 然后使用JProfiler打开并分析
            * 使用完以后,记得删掉dump文件及打开dump以后生成的文件夹(里面有一堆文件)
            * ---------------------------------------------
            * */
            while(true){
                list.add(new TestUtils());
                count++;
            }
        }catch (Exception e){
            System.out.println("count:"+count);
        }
    }
}

11. 一些面试问题

  • 请你谈谈JVM的理解?
  • java8虚拟机和之前的变化更新?
  • JVM的常用调优参数有哪些?
  • 内存快照如何抓取?怎么分析Dump文件?
  • JMM?

12. GC垃圾回收机制

  • 新生区

    • 新生代(伊甸园)
    • 幸存区
      • from区
      • to区
  • 老年区

GC的种类

轻GC(普通GC)

重GC(全局GC)

GC算法

默认情况下,Eden:from:to == 8:1:1

				新生区:老年区 = 1:2
1. 引用计数法

一个对象用过一次,就让它的计数器+1,检查的时候,如果计数器为0,则删掉它。(因为没有地方用过嘛,说明没用)

成本:每个对象都有一个计数器

2. 复制算法

谁空谁是to

from和to是在轮流交换的,不是固定的

复制算法流程:

  1. 首先初始化两块等大小的空间from和to
  2. 每次GC都会将Eden活的对象移到幸存区from中(一旦GC,则Eden就会变成空的)
  3. 如果from满了,则把from里活的对象放到to中,同时清理掉死的对象,最后把to变成from,from变成to
  4. 当一个对象经历了15次(默认值)GC还没有死的话,则进入养老区

-XX:MaxTenuringThreshold=5 通过JVM调参可以修改最大存活时间

通过这个参数可以设定进入老年代的时间。

注:复制算法内部区域中,地址空间是连续的,且对象按照地址由小到大严格排序。

优点:没有碎片空间

缺点:一定会浪费一半的内存空间(即to区大小)

  • 假设对象100%存活(极端情况)则复制算法的消耗极大。
  • 复制算法最佳使用场景:对象存活度较低的时候 —> 新生区(存活率低)

使用地点:新生区(伊甸园区和幸存区)

3. 标记清除法

标记清除法的流程:

  1. 先扫描一次,对对象进行标记
  2. 再扫描一次,对没有标记的对象进行删除

优点:不需要额外的空间!

缺点:两次扫描严重浪费时间;会产生内存碎片

使用地点:老年区,因为区域大、对象存活率高。

4. 标记压缩法(标记整理法)

再优化:

  1. 再一次扫描,向一端移动存活的对象

优点:没有内存碎片

缺点:多了一次移动成本

可以选择执行数次标记清除法以后,再执行一次标记压缩。

使用地点:老年区,因为区域大、对象存活率高。

GC总结

大于号代表优于

时间复杂度:复制算法 > 标记清除 > 标记压缩

内存整齐度:复制算法 = 标记压缩 > 标记清除

内存利用: 标记压缩 = 标记清除 > 复制算法

13. JMM

Java Memory Model

Java内存模型

JMM的作用

它相当于一个缓存一致性协议,用于定义数据读写的规则(遵守这样的规则)

JMM定义了JVM在计算机内存(RAM)中的工作方式。JVM是整个计算机虚拟模型,所以JMM是隶属于JVM的。

JMM定义了 线程和主内存之间的抽象关系,所以JMM是一个抽象的概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器以及其他硬件和编译器的优化。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BuHkVIw6-1618942980072)(/Users/shenhangran/Desktop/学习笔记/我的/JVM笔记.assets/image-20210421020242261.png)]

如图,每个线程都有自己的私有的本地内存,它如何和主内存交互,以及保证数据的正确性,都是JMM来控制的。(因为线程改了数据以后,还没有到CPU那一层,没有经过flush,数据没有得到刷新)

解决共享对象可见性这个问题:volatile(保证可见性,不保证原子性

对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的

怎么解决原子性问题呢?可以使用juc包下的atomic包下的对象就可以了。详见JUC笔记。

JMM的一些规则

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qGmgLsbA-1618942980073)(/Users/shenhangran/Desktop/学习笔记/我的/JVM笔记.assets/image-20210421022003356.png)]

JVM进阶

深究JVM需要很多时间,可以阅读《深入理解JVM》,等先把基础补得差不多了以后,再看这本书吧。最近的时间任务重心应该放在:多线程、JUC、DP、项目实践以及设计模式,之后看ssm框架,中间件的使用与了解,然后有时间还要搞毕设、看论文。这些事情完了以后,再深究JVM吧!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值