JVM
JVM结构
java文件,通过javac变成Class.File,加入类加载器Class Loader,与下图交互。
方法区 Method Area
堆 Heap
栈 Stack
本地方法栈 Native Method Stack
栈里不可能存在垃圾(放的是一个指向堆的地址)
垃圾只存在堆中,99%的调优都是调整方法区和堆
类加载器
收到.class文件,进入JVM的Class Loader,加载初始化为CLass的模板,以后声明这个类都是引用这个模板实例成对象。
|JVM
|
XX.class---|--->Class Loader----(加载,初始化)--->XX Class---(new)--->XX的实例
| ^ | ^ |
| |---(getClassLoader()方法)---| |---(getClass(方法))---|
1.虚拟机自带加载器
2.启动类(根)加载器
3.扩展类加载器
4.应用程序加载器
双亲委派机制
当前写程序的位置应用程序加载器:APP
上一级为扩展类加载器:EXC
最上级为根加载器:BOOT
运行一个程序会不断向上查找class
如果该class在BOOT层有,则执行BOOT层class。
如果不在BOOT层,在EXC层找到,则执行EXC层class。
如果前面都没有找到,在APP层找到,则执行APP层class。
如果都找不到,报错Class Not Found。
1.类加载器收到类加载请求
2. 将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器
3.启动加载器检查是否能加载当前这个类,能加载就结束,使用当前加载器,否则,抛出异常通知子加载器进行加载
4.重复步骤3,如果最后仍未找到,报错Class Not Found
沙箱安全机制
沙箱机制就是将java代码限定在虚拟机JVM特定的运行访问中
沙箱主要限制系统资源访问
沙箱组成
字节码校检器
类装载器(存取构造器、安全管理器、安全软件包)使用双亲委派机制
native关键字
说明java语言达不到了,需要调用底层C语言的库
会进入本地方法栈
调用本地方法本地接口 JNI
JNI作用:扩展java的使用融合不同的编程语言为java所用(最初C,C++)
在内存区域中专门开辟了一块标记区域:Native Method Stack
在最终执行的时候,加载本地方法库中的方法通过JNI
PC寄存器
程序计数器
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区的方法字节码(用来存储指向像一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。
方法区
Method Area 方法区
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间;
==静态变量、常量。类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关==
static final Class 常量池
栈
栈:先进后出,后进先出
队列:先进先出
三种JVM
Sun公司 HotSpot (使用最多)
BEA公司 JRockit
IBM公司 J9 VM
堆Heap
一个JVM只有一个堆内存,堆内存的大小是可以调节的。
类加载器读取了类文件后,会把类、方法、常量、变量放在堆中,保存所有对象引用的实例。
堆内存中细分三个区域
新生区:伊甸园(Eden Space),幸存区0区(from),幸存区1区(to)
幸存区0区和幸存区1区一直发生动态交换
养老区:养老区(Old)
GC垃圾回收,主要在伊甸园区和养老区
真理:经过研究99%的对象都是临时对象!
永久区:永久存储区(Perm)
这个区域常驻内存的。用了存放JDK自身携带的Class对象。Interface元数据,存储的是java运行时的一些环境或类信息,这个区域不存在垃圾回收!关闭VM虚拟就会释放这个区域的内存。
一个启动类,加载了大量的第三方jar包。Tomcat部署了太多的应用,大量动态生成的反射类。不断的被加载,直到内存满,就会出现OOM(堆内存满了)。
jdk1.6之前:永久代,常量池是在方法区
jdk1.7:永久代,但是慢慢退化了,去永久代,常量池在堆中
jdk1.8:无永久代,常量池在元空间
出现OOM故障
1.尝试扩大内存看结果
2.分析内存看看哪出了问题
内存快照分析工具MAT,JPofiler
Dubug,一行行分析代码
MAT,JPofiler工具的作用:分析Dump内存文件,快速定位内存泄露;获得堆中的数据;获得大的对象……
GC
GC两种类:轻GC(普通GC,针对新生区和幸存区),重GC(全局GC)
堆里的分区(Eden,from,to,老年区)
GC算法
标记清除法
扫描对象,对活着的对象标记
扫描对象,对没有标记的对象清除
优点:不需要额外的空间
缺点:两次扫描,严重浪费时间,会产生内存碎片
标记压缩
对标记清除优化**
扫描对象,对活着的对象标记
扫描对象,对没有标记的对象清除
再次扫描,压缩,防止内存碎片产生,把存活的对象移动
引用计数法
计算被使用的次数,为0的清除,会占用一部分性能,JVM已经不怎么用这个算法了(很low)
复制算法
对幸存区操作,把用的对象放在from区,清空伊甸园区和to区,超过一定引用次数(MaxTenuringThreshold设置进入养老区的次数)会放在养老区
优点:没有内存碎片
缺点:浪费内存空间,多了一半空间永远是空to
最佳使用场景:对象存活度低,所以一般放在新生区用这个算法
总结
内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)
内存整齐度:复杂算法=标记压缩算法>标记清除算法
内存利用率:标记压缩算法=标记清除算法>复制算法
年轻代:存活率低,复制算法
老年代:区域大,存活率,标记清除+标记压缩混合
GC调优,就是调节年轻代和老年代的算法
JMM:Java Memory Model
java内存模型
作用:缓存一致性协议,用于定义数据读写规则
JMM定义了线程工作内存和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory)
解决共享内存可见性这个问题:volilate
JMM:抽象的概念,理论
等等……