这里写目录标题
jvm就是JAVA虚拟机,这个和我们软件虚拟机vmware不一样,他只是针对JAVA程序,针对字节码进行解释执行
像python JavaScript都是需要虚拟机的编程语言,为的是更好支持不同操作系统和CPU
jdk是JAVA开发工具包
jre是JAVA运行环境
jvm是JAVA虚拟机
它们一层包一层
开发JAVA代码,安装jdk就包含了jre
不需要开发就只运行了一个JAVA程序,只安装jre即可
jvm很多底层东西,和我们关系不大,主要是高频面试题
一、jvm内存区域划分
每次运行JAVA程序的必然就创建了对应的JVM,JAVA中使用的内存其实就是JVM内存。JVM内存是启动的时候,从操作系统申请的,应用程序后续需要使用的时候就可以从JVM内存进行分配
不同区域
1.程序计数器
很小的区域只保留数字,也就是下一条要执行JAVA字节码指令的地址,在内存中,通过软件维护
2.栈
面试的时候被问到那个啥是战和队,我们要反问是哪个栈哪个堆?是数据结构的,还是JVM的,还是操作系统?
分成虚拟机栈,本地方法站
- 虚拟机栈
放局部变量
给JAVA程序使用的栈,维护了方法调用关系
每调用一个方法,就会有一个栈帧(frame
里边放
调用方法的实参
方法内部的局部变量
方法结束后又返回的上层方块的位置
还有返回值
完事一个删掉一个,后进先出
- 本地方法栈
给C加加代码使用的JVM底层是C加加实现的
放native本地方法的栈帧
3.堆
内存最大区域
存放new的对象,还有普通成员变量
4.元数据区
之前称之为方法区
放类对象,还有static修饰的成员
用static修饰的属性就变成类属性,而不是实例属性了
实力属性是跟着对象走的,类属性是跟着类走的
给一段代码,问,其中的变量都是属于哪个内存区域中的?
二、内存溢出
1.栈溢出
栈里面包含了方法的调用关系,栈帧太多了,一般都是递归导致的
2.堆溢出
new的对象太多了,无限循环往集合中添加元素
以上内存区域
针对程序计数器和栈,存在多份的(每个线程有一份自己的)
堆和元数据区一个进程中只有一份了~
一个线程中new对象,是可以直接被另一个线程使用的~~
三、JVM类加载机制
类加载就是.class文件读取到内存中,构造出类对象过程
触发这个
- 创建实例
- 使用静态方法或者静态属性
- 使用子类影响到父类
1.类加载流程
(1)加载
把.class文件找到,打开文件,并且读取文件的数据到内存中
怎么找到?
JAVA代码中,要使用到某个类,需要import,后面跟着全限定类名,把代码中写的权限定类名,找到对应的点class文件
(2)验证
根据读到的二进制内容,检验是合法的格式
(3)准备
给要创建的类对象分配内存空间
从元数据区安排一块内存保存这里的类对象,JAVA默认把新申请的,未初始化的内存全都设为0,尝试获取static抽在你身边
(4)解析
针对字符串常量进行初始化
需要把当前.class中的字符串常量也放到内存中
此时字符串就有了起始地址,顺便可以把这些地址取出来,放到对应使用字符串的地方
(5)初始化
针对类对象进行初始化,
涉及到初始化的静态成员,执行静态代码块,对父类的加载
以上五个步骤背下来
什么时机加载某个类?
懒汉思想用到的时候才加载
- new一个类的实例
- 调用这个类的静态方法/访问静态成员
- 针对子类的加载也会触发父类的加载
类加载加载一次即可,每个类的对象在一个JVM进程中也是单例的
2.双亲委派模型
双亲委派模型用来找.class文件,
涉及到一个模块,成为类加载器,JVM中默认包含三个类加载器
- BootstrapClassLoader
- ExtensiohClassLoader
- ApplicationClassLoader
父子关系(不是父类子类
这个类加载器中有一个parent这样的属性,指向父亲的引用
从下往上指向父亲,类似于链式结构BootstrapClassLoader,负责加载JAVA标准库中的类
ExtensiohClassLoader ,负责加载JAVA扩展库中的类(jdk厂商做出的扩展,现在已经很少使用
ApplicationClassLoader,负责加载第三方库以及你当前项目中的类
一,标准类加载步骤
1)把全限定类名,交给ApplicationClassLoader
2) ApplicationClassloader把任务转交给父亲进行处理
3)ExtensionClassLoader还是把任务转交给父亲进行处理
4) BootstrapClassLoader没有父亲,只能自己处理.负责扫描指定的标准库类的目录,查找这个类
发现能够找到,因此就可以打开对应的.class文件读取文件,进行解析=>类加载的后续步骤~~
二,加载第三方
1)把全限定类名,交给ApplicationClassLoader
2) ApplicationClassLoader 把任务转交给父亲进行处理
3) ExtensionClassLoader还是把任务转交给父亲进行处理
4) BootstrapClassLoader没有父亲,只能自己处理.标准库中,找不到对应的类因此就把任务转交给孩子
5) ExtensionClassLoader 查询自己负责的目录发现也没有,继续把任务交给孩子
6) ApplicationClassLoader 查询第三方库的目录/当前项目的目录找到了就可以打开文件,读取文件如果在这一级还没有找到,抛出异常~~
本质上,双亲委派模型,约定了“类加载的优先级“标准库最先加载其次是扩展库最后才是第三方库/当前项目
垃圾回收GC
应对内存泄露
jvm专门指派一些线程,这些线程周期的扫描,你的代码已经申请的内存自动判定是否不再使用
1.找出谁是垃圾
在JAVA中,使用对象只有一种方式,就是通过引用的方式看某个对象是否引有引用指向,如果没有,就认为这个对象不再使用了,是垃圾回收
怎么判断有没有引用指向?
谈一谈JAVA的垃圾回收机制,就可以不谈应用技术,因为这是python和PHP的方案,如果是问谈一谈垃圾回收机制,就可以谈一下引用计数
第一种方案引用计数
你就是判断对方是不是垃圾
- 引用计数
给每个对象身上安排一个空间,这个空间存储一个整数,表示指向对象引用的个数
围绕对象进行引用赋值,就会更新这个数,如果计数是0就可以释放
但是有两个缺点,也是因为这两个缺点JAVA没有引用
(1消耗更多内存空间
如果计数器使用两个字节的,整个对象本来只有四个自己,这就占了50%了
(2产生循环引用会导致出现误判
微信
- 可达性分析
微信
通过可达性分析的方式,从所有GCRoots出发,尽可能遍历,访问到的对象都标记可达,剩下的就是不可达
一棵树有很多个根GCRoots构成
GCRoots有哪些
1 栈上的局部变量
栈有很多,每个栈栈帧也很多,每个栈帧中局部变量也有很多
2 常量池引用指向对象
字符串常量String也有一系列引用类型成员,至少有一个 byte[]
Integer值JVM把-128=>+127这个范围的数字提前创建好Integer 对象
3.所有的引用类型静态成员
好的地方
可达性分析,没有引入额外的内存空间
没有循环引用的问题
弊端
可达性分析的问题,需要消耗更多cpu资源/更多时间
这就是c加加不愿意引入gc的原因
识别出垃圾之后,如何释放内存
1。标记清除
把标记出来的垃圾,直接释放掉
但是对象是垃圾,随机的,可能会在频繁示范中出现大量的内存碎片,被释放的内存在地址上是离散的(不连续)申请内存都是连续内存,总空闲空间充足,尝试申请大块内存,申请失败
2。复制算法
有效解决内存碎片问题
使用内存空间的一般,把不是垃圾的对象复制到另一半
缺点 空间利用率低
如果当前存活的对象多,复制开销大
3。标记整理
对于空间利用率,得到了改善
类似于顺序表删除中间元素,也就是搬运
把存货的数据放到非存活的数据位置,开销同样可能很大
对于JAVA实际情况,综合上述策略,构成了一个更复杂的方案
4。分代回收,根据不同对象的特点,采取不同的方安
引入对象年龄,如果对象年龄比较大,有很大概率继续存活下去,GC扫描轮次是周期性进行的,经过很多轮都没有被释放,说明年龄比较大
分为两个大部分
新生代和老年代
新生代分为伊甸区和幸存区
伊甸区放刚创建的对象,经过第一轮GC的时候,把大部分淘汰掉
经过GC没有淘汰的对象同过复制算法进入幸存区
幸存区有两个部分,每次只用其中一个部分
下一轮GC也会针对幸存区进行扫描,淘汰掉一大批对象,通过复制算法,进入到另一个幸存区
每一轮GC淘汰很多,因此出发复制的对象少,解决了复制算法拷贝开销大的问题
另一面,幸存区只占新生代10百分之,浪费内存空间小
针对老年代的扫描,称为 Major GC 开销大,频率低
针对新生代的扫描,称为 Minor GC 开销小,频率高
这两者整体称为 FulI GC
4.随着每一轮GC的进行,对象就会在新生代的幸存区来回拷贝没经过一次拷贝,对象的年龄就+
5.经过一定时间之后,对象的年龄达到一定的阈值,此时就会把这个对象拷贝到老年代
6.对象进入老年代之后,针对老年代的GC频率比新生代低很多了.减少了扫描的开销
如果是某个对象占据内存很大的,直接进入老年代
老年代发现对象是垃圾,采取标记整理的方式处理
虽然整理一次,比较消耗资源的 但频率比较低
OK了,再见
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2601_95402431/article/details/167037742



