JVM篇——货真价实、真材实料!一篇文章带你彻底搞懂JVM,涵盖堆、栈方法区、GC等!

JVM体系

JVM的位置

位于操作系统之上,其实一个JVM并不大,只是JRE中的一部分,所有的java代码都是运行在JVM虚拟机之上的

JVM体系结构

一、详细体系结构

二、简单化体系结构

方法区

方法区认识

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

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

新生区、养老区、永久区

新生区是一个类诞生和成长的地方,甚至是死亡
(一)新生区
1、伊甸园区

所有的对象都是在伊甸园区new出来的(当然了,99%的对象都是临时的对象,也就意味着GC大部分的工作都是在新生区

2、幸存区
每次GC都会将伊甸区中幸存的对象移到幸存区中
幸存区又细分为幸存1区和幸存0区,from和to,动态变化,谁空谁是to 

(二)养老区
当一个对象经历了系统默认的15次GC之后还存活的,就会转到养老区
针对系统默认的15次GC,我们可以手动修改,通过命令:-XX:MaxTenuringThreshold=?来设置进入养老区的时间
在伊甸园区、幸存区和养老区之间存在两种垃圾回收机制
(1)轻GC:
当伊甸园区都满了,会触发轻GC,存活下来的对象进入到幸存区

(2)重GC:
当伊甸园区和幸存区也满的时候,会触发重GC

(三)永久区
这个区域是常驻内存的,用来存放JDK自身携带的Class对象,比如interface元数据,存储的是Java运行时的一些环境或类信息,这个区域不存在垃圾回收,当关闭虚拟机的时候,就会释放这个区域的内存

栈和队列

一、理解栈中方法的执行顺序
先进后出,后进先出(类似于弹夹)

2先进,但是1先出

二、队列
先进先出,和MyBatis中的缓存的策略一致,都遵循FIFO策略,按对象进入的顺序来移除他们

2先进,2先出

三、形象理解栈和队列
喝多了吐就是栈,而吃饱了拉就是队列

四、递归出现栈溢出的原理就是因为两个方法互相调用,无限的压栈,那么栈就会被压满

五、栈内存
栈内存,是主管程序的运行,生命周期和线程同步,当最后一个main主线程执行完毕弹出之后,那么随机这个栈内存也就释放了,对于栈来说,不存在垃圾回收的问题,一旦线程结束,栈就over了

六、栈里面会存放什么东西呢?
8大数据类型、引用数据类型(0X...地址)、实例化的方法等等

七、栈的运行原理
涉及到一个专有名词“栈帧

八、栈、堆、方法区的交互关系
模拟对象创建的过程

堆Heap

一个JVM中只有一个堆内存,堆内存是可以人为调节的
堆内存分布:

伊甸园区是新的对象创建后存到这个区中,而幸存区是对于新生对象,gc垃圾回收机制会进行回收,如果没有被回收的,存到幸存区。
gc垃圾回收,他的工作区间也因此集中于了伊甸园区和养老区
如果堆内存满了,系统就会报OOM错误(堆内存不足)

jdk1.8之后永久存储区改名换成了元空间

手动修改堆内存

-Xms1024m -Xmx1024m -XX:+PrintGCDetails

编写测试代码:

public class Hello {
    public static void main(String[] args) {
        //返回虚拟机试图使用的最大内存
        long max = Runtime.getRuntime().maxMemory();
        //返回虚拟机初始化的总内存
        long total = Runtime.getRuntime().totalMemory();

        System.out.println("max=" + max + "字节\t" + (max/(double)1024/1024) + "MB" );
        System.out.println("total=" + total +  "字节\t" + (total/(double)1024/1024) + "MB" );
    }

控制台打印的内存变更为:
接近1024

运行程序,控制台打印结果,也正好印证了堆内存中存在这些区域

涉及到一个问题:
新生区的内存加上养老区的内存正好等于当前虚拟机初始化的总内存,所以印证了元空间逻辑上存在,而物理上是不存在的

使用JPofiler工具分析OOM原因

在一个项目中,突然出现了OOM错误,应该如何排查出错误呢?
原始的方法是先把虚拟机的内存手动调大,如果调大之后,不报错,就说明是堆内存问题,然后去代码中找哪里在死循环创建对象

现在提供一款内存快照分析工具JPofiler,可以直观的看到代码的哪一行出现了问题
JPofiler作用:
1、分许Dump内存文件,快速定位内存泄露
学习什么是Dump文件
(1)首先先手动缩小一下虚拟机内存,并且配置出现内存溢出的异常打印Dump文件
-Xms:设置初始化内存分配大小,默认是内存的1/64
-Xmx:设置最大分配内存,默认是内存的1/4
-XX:+(代表命令)
PrintGCDetails:表示打印GC垃圾回收信息
HeapDumpOnOutOfHemoryError:表示dumpOOM异常信息
-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfHemoryError(固定的公式,后面是加上想要Dump的异常就可以)

(2)写一个内存溢出的类,运行,观察控制台打印情况

(3)找到项目在文件夹中存储的路径,找到后缀为.hprof的文件

(4)双击打开即可(前提是安装了这个软件)

2、获得堆中的数据
3、获得大对象

GC

了解GC

一、了解GC的作用区间
GC的作用范围仅限于堆空间中,当然也包含了堆空间中的方法区,因为栈中不会有垃圾,他都是执行完毕后就弹出栈,不长时间占用栈空间

二、GC的分类
1、轻GC(minorgc)
作用区间:新生区(伊甸园区和幸存区)

2、重GC(fullgc)
作用区间:当他触发的时候,会在堆的全局都有效

GC算法

引用计数法

作为了解,现在不常用,就是当对象创建的时被使用,那么候,系统会给每一个new出来的对象分配一个计数器,每次当对象被使用,那么计数器加一,最后如果某个对象的计数器显示为0,那么就会通知GC将他回收。
这种方式是存在弊端的,如果我们写了一个死循环,疯狂的创建对象,那么就会分配n个计数器去实现这个计数的功能,造成的系统的功能的浪费是很大的,因此现在是很少被使用了

复制算法

一、理解什么是复制算法
所谓复制算法,就是利用了幸存区的from 和 to ,在幸存区中幸存1区和幸存0区,谁空谁就是to ,这两个区会一直动态的交替,from中的对象会转到to中,这样原来的from就空了,然后她们两个身份互换,最后持续的经历GC,如果某个对象经过15次GC还存活,那么他就会被转到养老区了

二、复制算法的优势和缺点
复制算法的优势:没有内存的碎片,一直处于一种交替的状态,不会这里存一个对象,那里存一个对象
缺点:浪费了内存的空间,因为我们需要一直保证一半的空间(To区域)为空,显然是浪费

三、最佳使用复制算法的场景
对象的存活度较低的时候,也就是在新生区的时候

标记压缩清除算法

一、标记清除

理解该算法
所谓标记压缩清除算法就是将使用的对象进行一个标记,然后通知GC对没有标记的对象回收

该算法的优缺点
1、优点:
优化了复制算法中要一直保持幸存区to区为空的内存浪费

2、缺点:
(1)容易产生内存的碎片
(2)两次扫描,严重浪费时间

二、标记压缩 实现了再优化

GC算法总结

一、对比这些算法,分析优劣

二、什么是最优的算法?
现有的技术告诉我们没有最好的算法,只有最合适的算法
那么GC的分代收集算法就是相比来说更合适的
1、年轻代:
因为他的存活率比较低,那么正好使用复制算法
2、老年代:
因为他的区域大,并且存活率特别高
所以我们使用标记清除 + 标记压缩混合方式来实现,这里需要注意,如果我们内存的碎片不是很多,就可以直接使用标记清除,如果内存碎片过于多,就结合标记压缩,定期对空间进行排序即可

Native

凡是带了Native关键字的,说明Java的作用范围达不到了,会去调用底层C语言的库
当我们某个类加载的时候,会进入本地方法栈,如果我们想使用一些底层的方法,比如start0()方法,这个方法会去调用更底层的C语言方法,需要通过JNI接口
涉及到的历史:
起初Java诞生之初,C和C++ 横行,如果想做一款好的语言,那么能去调用C和C++的程序,他在内存区域中专门靠皮了一块标记区域本地方法栈(Native Method Stack),登记native方法,在执行引擎(Execution Engine)最终执行的时候,通过JNI接口调用本地方法库(Native Libraries)中的Java之外的方法。

谢谢阅读!至此你已经对JVM的技术点掌握,涉及的重点有点多,建议收藏多次学习,并且结合网络教学视频效果更佳。后期还会更新相关技术点,希望大家能够持续关注,祝各位早日成为全栈工程师!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Be explorer

若认可笔者文章,手头富裕望支持

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

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

打赏作者

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

抵扣说明:

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

余额充值