typora-root-url: ./
文章目录
JVM学习前言
- 谈谈你对JVM的理解;Java8虚拟机与之前等待变化
- OOM,栈溢出StackOverFlowError,如何分析
- JVM的常用调优参数
- 内存快照如何抓取,如何分析Dump文件
- JVM中类加载器
JVM体系结构
更加详细的结构为:
类加载器
作用:加载.class文件
流程大致为:
分类:按照从低到高可分为:
- 虚拟机加载器(引导类加载器)
- 扩展类加载器
- 系统类(启动类)加载器
- 应用类加载器
双亲委派机制
某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
全都找不到报: Class Not Found
沙箱安全机制
在早期的jvm中,对于本地代码信任,可以直接运行,而远程代码是放置在沙箱中运行,限制对系统资源的访问。
此后增加了 安全策略,可以在给定一些权限后对本地资源访问。
然后又增加了 代码签名,按照用户的安全策略设定,又类加载器来加载道虚拟机中权限不同的运行空间运行
现在的安全机制引入 域 的概念,虚拟机将所有的代加载不同的系统域和应用域中,系统域负责关键资源,应用域通过系统域的部分代理来操作资源。不同的受保护域对应不同的权限。
组成沙箱的基本条件:
-
字节码校验器: 确保Java文件遵循Java语言规范,帮助Java程序实现内存保护,但不是所有的类文件都会经过节码校验(核心类)
-
类装载器: 类装载器在3个地方对Java沙箱起作用:
-
放置恶意代码干涉善意代码
-
守护被信任的类库边界
-
将代码归入保护域
虚拟机为不同的类加载器提供不同的命名空间,有唯一的标识,且由Java虚拟机的每一个类装载器维护,相互不可见
-
类加载器使用的是双亲委派机制:
-
从最内层jvm自带类加载器开始加载,外层恶意同名类得不到加载从而无法使用
-
由于严格通过包来区分访问域,外层恶意的类通过内置代码也无法获得权限访问道内层类,破坏代码自然无法生效
-
存取控制器: 存取控制器可以控制核心API对操作系统的存取权限,控制的策略可以通过用户指定
-
安全管理器:是核心API和操作系统之间的主要接口。实现权限控制,比存取控制器优先级高
-
安全软件包:
java.security
下的类和扩展包下的类,允许用户为自己的应用增加新的安全特性:- 安全提供者
- 消息摘要
- 数字签名
- 加密
- 鉴别
Native
native:凡是使用 native
关键字修饰的,说明Java的作用范围达不到,会调用底层C/C++的库文件等等
调用 native
修饰的方法会进入 本地方法栈 ,然后通过 本地方法接口JNI 调用本地方法。
所以JNI的作用就是扩展Java的使用,融合不同的编程语言为Java使用。jvm会在内存区域中开辟一个标记区域,即本地方法栈,登记所有的native方法,在最终执行时通过JNI加载本地方法库中的方法。(不同虚拟机这里会有所不同)
PC
程序计数器
方法区
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法(构造函数、接口代码)都在此定义。所有定义的方法的信息都保存在此区域,此区域属于共享区间
静态变量(static)、常量(final)、类信息(class)(构造方法、接口)、运行使得常量池 都存放在方法区中,但是,实例变量存在堆中,不是方法区
(最下方就是方法区,存储上面提到的东西)
栈(虚拟机栈)
栈内存,主管程序的运行,生命周期和线程同步
线程结束,栈内存也释放,对于栈来说,不存在垃圾回收问题
栈中的类型:8大基本类型+对象类型+实例方法
实际的栈中存储的是一个个的栈帧,结构大致如下:
三种JVM
- sun: HotSpot (openjdk)
- BEA JRockit
- IBM J9 VM
堆
结构
Heap,一个JVM仅有一个堆内存,可调节大小
堆中保存的是 实例化的类及方法、常量、变量等
堆内存分为:
- 新生区(伊甸园区)young
- 老年区 old
- 永久区 perm(1.8改为元空间)
GC垃圾回收,主要是在伊甸园区和养老区,,如果内存满了就会出现OOM
具体的结构可以看这里:https://www.processon.com/view/60a25c38f346fb1df41ec011
新生区和老年区
- 所有实例化 诞生和成长的地方,甚至死亡
- 主要分为 伊甸园区,幸存者区(S0, S1)
当伊甸园区满后,会进行一次轻量级垃圾回收,对于还存在的对象将转到幸存者区。如果新生区老年区满了会进行一次重量级垃圾回收,将存活的转到老年区,全部满了表示没有内存空间,即OOM
整个流程大致如下:
永久区(元空间)
常驻内存,用来存放JDK自身携带的Class对象。Interface元数据,存储的是Java运行时的一些环境或类信息,不存在垃圾回收,仅在关闭虚拟机时释放
一个启动类,如果加载大量的第三方jar包,如Tomcat部署太多应用,大量动态生成反射类等,会导致OOM
- 1.6前: 永久代,常量池在方法区
- 1.7: 永久代,但是退化 去永久代 ,常量池在堆中
- 1.8: 无永久代,常量池在元空间
Runtime.getRuntime().maxMemroy()
和 Runtime.getRuntime().totalMemroy()
可以获取jvm可以使用的最大内存以及当前初始化的内存大小。
Xms1024m -Xmx1024m -XX:+PrintGCDetails
可以设置初始化内存大小(1/64)与最大内存大小(1/4)。
初始内存大小=新生区+老年区,元数据区在另一个地方
出现OOM的尝试解决方法:
- Debug分析
- 内存快照工具分析Dump文件:MAT、Jprofiler
MAT Jprofiler的作用:
- 分析Dump内存文件,快速定位内存泄漏
- 获得堆中的数据
- 获得大的对象
- 。。。
Java虚拟机dump: -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError[path]
GC:垃圾回收
gc作用于方法区和堆
gc分类:轻GC(YGC)、重GC(Full GC)
gc的算法:标记清除法、标记整理、复制算法、分代收集,
判断对象是否存活的方法:引用计数法和可达性分析
引用计数法
顾名思义,记录每一个对象的引用数,对于没有引用的清理即可
可达性分析
复制算法
新生区中,伊甸园区和两个幸存区间中非空的将存活的对象复制到空的幸存区。其中为空的是 to ,另一个是 from
当一个对象经历15次gc仍存活进入老年区,通过 -XX:MaxTenuringThreshold=5
可以设置进入老年区的时间
- 好处: 没有内存碎片
- 坏处:浪费了一部分空间,to是空的
最佳使用场景:对象存活度较低时。
标记清除
对存活对象标记,然后清除
- 优点:不需要额外空间
- 缺点:两次扫描费时、内存碎片
标记整理
对标记清除后的整理压缩,,减少碎片
分代收集
对于新生代:使用复制算法
对于老年代:使用标记清除(整理)算法
总结
内存效率:复制算法>标记清除>标记整理
内存整齐度:复制算法=标记整理>标记清除
内存利用率:标记整理=标记清除>复制算法
所以使用分代收集算法,尽可能的提高性能
(JMM指的是Java的内存模型,主要与多线程有关系,就是那8种方式)
一些比较好的总结:
https://www.processon.com/view/5ec5d7c60791290fe0768668
https://www.processon.com/view/60a25c38f346fb1df41ec011