JVM学习总结

JVM学习笔记

运行时方法区

  1. Java栈

    • 栈是一种数据结构,程序=数据结构+算法,现在也可以说成:程序=框架+业务逻辑,毕竟现在框架太多,用起来也很方便,很多公司都开发都是这样用的。

    • 栈是先进后出,可以和队列对比,队列是先进先出。

    • 栈里一般存放的都是:8大基本数据、对象的引用、实例的方法

    • 栈是不存在垃圾回收机制的,因为栈内存主管程序运行,生命周期和线程同步。线程结束后,栈内存也就释放了。

    • 举个例子:在主程序main()方法中调用test()方法,当test()方法执行完后,栈就会立马将这个方法释放,之后释放main()方法。使用完后立马就释放了,所以栈中基本上不存在垃圾。
      如果在main()方法中调用了大量的方法,或者存储了大量的引用或者基本数据类型,那么就会出现栈溢出的错误。但是一般不会出现这个情况。

      如果有多个方法在main()方法中调用,按照先进后出的原则main()方法将会被压在栈的最底下,当上面的方法一个一个执行完了,被释放,这时才轮到main()方法,因为一个方法在执行时,可能会遇到需要执行新的方法,那么老的方法将被压在栈底,新的方法在栈顶,所以正在执行的方法永远都在栈顶,这叫做压栈,main()方法被释放后,这个线程也就结束了。
      在这里插入图片描述

    在这里插入图片描述

    • 堆的空间分为:新生区,老年区和永久存储区(JDK8后变为了元空间)。堆中一般存储的是对象,就是new出来的那些东西。

      1. 新生区是一个类诞生,成长甚至死亡的地方。分为伊甸区和两个幸存区。
      2. 老年区存储是经过15次GC(15次是JVM默认的次数,可以手动调),还没有死亡的数据。
      3. 永久存储区(元空间)存储的是JDK自带的Class对象或者一些运行环境(就是JDK以及第三方的jar包),这个区域不存在垃圾回收,关闭虚拟机的时候就会释放这个区域。一个启动类加载了大量的第三方jar包,或Tomcat部署了太多应用,大量动态的生成反射类,不断被加载,直到内存满,就会出现OOM。
        元空间逻辑上存在,物理上不存在,例如:给堆分配了100M的内存,通过测试得,新生区空间50M,老年区空间50M,那么物理上,元空间应该是不存在的。
    • 一般垃圾回收机制就是针对于堆。假设堆内存满了,就会报错OOM(java.lang.OutOfMemoryError)
      在这里插入图片描述

  2. 方法区:类信息、常量、静态变量、即时编译器编译后的代码

  3. 本地方法栈

    • native关键字:当Java中使用native关键字,就表示Java的作用范围已经达不到了,需要调用本地方法库中的方法。在jvm中表现为,会进入本地方法栈,调用对应的本地方法接口(JNI,即Java Native Interface),当然,native关键字修饰的方法必须是JNI中存在的才行。
    • JNI的作用:拓展Java的使用,融合不同的编程语言为Java所用,因为Java最初诞生的时候,C和C++很流行,如果Java想要立足,就要想办法调用C、C++的程序。所以就在内存中单独开辟了一块空间登记native关键字修饰的方法,在最终执行的时候通过JNI去加载本地方法库中的方法。
  4. 程序计数器:用于标记线程进度的,每个线程都有自己的程序计数器这样当线程执行切换的时候就可以在上次执行的基础上继续执行。特点:

    • 线程私有(这个必须得私有)
    • JVM规范中唯一没有规定OutOfMemoryError情况的区域
    • 如果正在执行的是Native 方法,则这个计数器值为空
  5. JVM的简图展示(请忽略水印,我错了,不该做白嫖党 >_<||| )
    在这里插入图片描述

对象在内存中时

在这里插入图片描述

垃圾回收机制

​ 垃圾回收机制分为轻GC和重GC(full GC,全局GC)。

  1. 轻GC:当新生区内存满了,就会触发一次轻GC。

  2. 重GC:当老年区满了,会触发一次重GC,会将包括新生区和老年区的内存全部清理一次。

GC算法

  1. 引用计数法:内存总一个对象使用了一次就记一次,没使用过就算做0次,触发GC时,就清除那些使用次数为0的对象。但是这种算法需要给每一个对象分配一个计数器,太消耗资源了,而且效率低,所以现在一般不采用了。

  2. 复制算法:在内存中,使用两块内存区作为复制算法的使用,一个from区,一个to区,判断方式:谁空谁是to,to区的空间会始终保持着为空。
    当伊甸区新建的对象经历一次GC存活下来后,会将对象放入to区,而原本from区的数据也会放入to区。这时to区不为空,变成了from区,而原本from区变成了空的,所以变成to区。等到下一次GC后,from区存活的数据又会连同伊甸区存活下来的数据放入原本空的to区(GC时,from区的数据也可能会被回收),这时from区又变成空着的to区,就这样from和to不断复制交替(当经历15次GC还存活,该数据就会从幸存区放入老年区),这就是复制算法。
    复制算法的最佳使用场景:对象存活度较低的情况下,而新生区的存活度一般较低,所以复制算法一般用在新生区。

    • 好处:没有内存碎片。
    • 坏处:浪费空间,有一半空间是空的。

在这里插入图片描述

在这里插入图片描述

  1. 标记清除法:在堆内存中,存有多个对象,回收时,扫描这些对象,对于活着的数据做上标记,然后扫描没有标记的对象,对其进行清除。

    • 优点:不需要额外的空间(对比复制算法)。

    • 缺点:两次扫描,增加了时间成本。会产生内存碎片(回收之后会残留一些没有数据填充的小坑,这就是内存碎片)。

在这里插入图片描述

  1. 标记整理(标记压缩):标记压缩是在标记清除算法上的优化,在标记清除算法后,再次进行扫描,然后进行排序,这样的话就是连续的内存空间了,就不存在内存碎片了。
    在这里插入图片描述

  2. 分代收集算法:现在JVM一般都不单使用某一种算法,在新生区使用复制算法,在老年区使用标记清除算法加标记压缩算法混合实现(例如:为了节约成本,可以多次标记清除后,再使用标记压缩一次性将其排序,去掉内存碎片)。所以总结下来就一句话:没有最好的算法,只有最合适的算法。

JVM调优

​ 因为垃圾回收一般出现在堆中,所以JVM的调优一般都是针对的堆。对于Java8来说,一般堆内存的初始容量为物理内存大小的1/64, 最大内存不超过物理内存的1/4或1G。

​ 当出现OOM时,一般采取以下方式:

  1. 尝试扩大堆内存看结果,如果还在报错,说明有可能程序中出现了死循环。
  2. 分析内存,看一下个地方出现了问题(这里需要用到专业工具(内存快照分析工具),像JPofilter,MAT这样的工具)

设置堆内存大小的方式,在idea中:

-Xms表示设置初始容量,设置为1024m,-Xmx表示设置最大内存,设置为1024m,当然这是测试,一般不会这样设置的,-XX:+PrintGCDetails表示在控制台打印出GC时的详情,这个一般测试或者调优需要用到打印。

在这里插入图片描述

这里只做了简单说明,网上搜索JVM调优参数,就会有很多文章,例如:JVM调优参数

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

佛祖保佑永不宕机

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

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

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

打赏作者

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

抵扣说明:

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

余额充值