文章目录
JVM快速入门
一、JVM的体系结构
1、位置
JVM
说到底就是一个程序JRE
中包含JVM
,也就是这些Java程序都是跑在JRE这个环境中的Java
的运行环境是跑在操作系统上的- 操作系统是跑在硬件上的
2、结构
二、类加载器及双亲委派机制
1、类加载器
- 虚拟机自带的加载器
- 启动类(根)加载器
rt.jar包下的类
- 扩展类加载器
- 应用程序加载器
2、双亲委派机制
- 从上图可以看出,我们创建一个同包同名的String类,然后在main中运行,报错
- 这里用到了双亲委派机制,说到底还是为了安全,这里new String时,就会根据优先级去使用 App -》 Ext -》 ROOT 中去加载类,如果ROOT中没有就找Ext,如果Ext没有就找App
流程:
- 类加载器收到类加载的请求
- 将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器
- 启动加载器检查是否能够加载当前这个类,能加载就结束,使用当前的加载器,否则,抛出异常,通知子加载器进行加载
- 重复步骤3
三、Native、PC寄存器、方法区
1、native
凡是带了native关键字的,说明java的作用范围达不到了,获取调用C语言的库,会进入本地方法栈,调用本地方法接口 JNI
JNI作用:扩展Java的使用,融合不同的变成语言为Java所用,最初就是去使用C、C++,它在内存中专门开辟了一个快内存Native Method Stack,等级Native方法,在最终执行的时候,加载本地方法库中的方法通过JNI,例如Java程序的的打印机,管理系统,掌握即可,在企业应用中较为少见
2、PC寄存器
程序计数器: Program Counter Register
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向像条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计
3、方法区
Method Area 方法区
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间
静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是 实例变量存在堆内存静态变量、常量、中,和方法区无关
四、深入理解栈
- 栈是一个数据结构
程序 = 数据结构 + 算法
程序 = 框架 + 业务逻辑
-
栈:先进后出、后进先出:桶
队列:先进先出(FIFO:First Input First Output) -
栈:栈内存,主管程序的运行,生命周期和线程同步,线程结束,栈内存也就释放了,对于栈来说,不存在垃圾回收机制
-
栈:8大基本类型 + 对象引用 + 实例的方法
-
栈的运行原理:栈帧
对象实例化的过程
五、走进HotSpot和堆
- 三种JVM
- Sun公司 HotSpot Java Hotpot™ 64-Bit Server VM (build 25.181-b13, mixed mode)
- BEA JRockit
- IBM 39 VM
- 堆
Heap,一个JVM只有一个堆内存(栈是线程级的,一个线程有一个栈,所以堆需要垃圾回收),堆的大小是可以调节的
类加载器读取了类文件后,一般会把类、方法、常量、变量放到堆中去
- 堆内存中还要细分为三个区域:
- 新生区(伊甸园):Young/New
- 养老区:old
- 永久区:Perm
GC垃圾回收,主要是在伊甸园和养老区~如果是内存满了的话,就会出现OOM,堆内存不够
如果伊甸区满了就会触发一次轻GC,存活下来的就会跑到幸存区(这里有两个,不停地切换),如果伊甸区和幸存区都满了的话就会触发一次重GC,存活下来的就会跑到养老区中,如果都满的话,就会发生OOM
在jdk8以后,永久存储区变了一个名字叫做元空间
六、新生区、永生区,堆内存调优
1、新生区
- 类:诞生和成长的地方,甚至死亡
- 伊甸园,所有的对象都是在伊甸区new出来的
- 幸存者区(0,1)
经过研究,99%的对象都是临时对象
2、永久代
这个常驻内存,用来存放JDK自身携带的Class对象,Interface元数据,java运行时的一些环境或类信息,这个区域不存在垃圾回收机制,关闭VM虚拟就会释放这个区域的内存
一个启动类,这个区域加载了大量的第三方jar包,tomcat部署了太多的应用,或者大量动态生成的反射类。如果他们被不断的加载
v
- jdk1.6:永久代,常量池是在方法区中
- jdk1.7:永久代,但是慢慢的退化了,
去永久代
,常量池在堆中 - jdk1.8:无永久代,常量池在元空间
元空间:逻辑上存在,物理上不存在
-Xms1024m -Xmx1024m -XX:+PrintGCDetails
305664K+699392K = 981.5MB
在一个项目中,突然出现了OOM故障,那么该如何排除~研究为什么出错
- 能够看到代码第几行出错
- Debbug,一行行看错
3、OOM
出现原因就是堆内存满了
解决方案:
- 尝试扩大堆内存
-Xms数值m -Xmx数值m -XX:+PrintGCDetails
- 如果还有错误,就分析内存,看一下是哪里出了问题(视频中使用的是Jprofiler)
下载jprofiler9,在idea中下载jprofiler插件,然后再设置的tools中的去配置jprofiler,选择你安装的jprofiler的目录下的bin里面那个后缀为exe的文件
使用Jprofiler去分析内存
-
-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
(dump出什么异常的命令)在VM处修改,然后启动
-
就可以在该java的文件的项目目录中看到dump出的文件了
-
这里就可以看见这个异常的地方了,通过看那个线程的甚至还能直接定位到报错的行数
七、GC
大名鼎鼎的垃圾回收机制
- JVM在进行GC时,并不是对这三个区域统一回收的。大部分的时候,回收的都是新生代
- 新生代
- 伊甸园区
- 幸存区 (from 、 to)
- 老年区
- 新生代
- GC:轻GC(普通GC)、重GC(全局GC)
1、GC的常用算法
- 标记清除法
- 标记整理法(标记压缩)
- 复制算法
- 引用计数器
2、引用计数法
通过给对象分配一个计数器,然后引用一次就加一来判断哪个没用了,进行回收
,但是分配计数器本身也有消耗空间,用的比较少
3、复制算法
这里涉及到了幸存区0,幸存区1,来回切换的问题,记住一个口诀谁空谁是to
- 比如,第一次GC的时候,存活下来的对象先到了一个幸存区中,这个幸存区就叫做from,另一个叫to
- 如果当又来一次gc的时候,存活下的对象就会先到to中,此时from,to都有对象了,此时,他就会将form中的对象复制到to中,然后之前那个from幸存区又变成了to,继续接受存活的对象并接受from中复制来的对象
- 之后一个对象如果经过了指定此处的gc还没有死的话,就会进入到老年区
好处:没有内存的碎片
坏处:浪费了内存空间:多了一半空间永远是空的
复制算法最佳使用场景:对象存活度较低的时候:新生区
4、标记清除算法
优点:不需要额外的空间
缺点:两次扫描,严重浪费时间,会产生垃圾碎片
5、标记压缩清除算法
先标记,再压缩
八、总结
- 内存效率:复制算法 > 标记清除算法 > 标记压缩算法(时间复杂度)
- 内存整齐度:复制算法 = 标记压缩算法 > 标记清除算法
- 内存利用率:标记压缩算法 = 标记清除算法 > 复制算法
有没有最优的算法?
没有,没有最好的算法。只有最合适的算法-》GC:分代收集算法
年轻代
- 复活率低
- 复制算法!
老年代
- 区域大:存活率
- 标记清除(内存碎片不是太多) + 标记压缩混合 实现