JVM
JVM的位置
JVM的体系结构
类加载器
作用:加载Class文件类,加载机制是将⼀个类从字节码⽂件转化为虚拟机可以直接使⽤类的过程
public class Car {
public int age;
public static void main(String[] args) {
//类是模板,对象则是具体的
Car car1 = new Car();
Car car2 = new Car();
Car car3 = new Car();
System.out.println(car1.hashCode());
System.out.println(car2.hashCode());
System.out.println(car3.hashCode());
Class<? extends Car> aClass1 = car1.getClass();
ClassLoader classLoader = aClass1.getClassLoader();
System.out.println(classLoader);//AppClassLoader
System.out.println(classLoader.getParent());//ExtClassLoader
System.out.println(classLoader.getParent().getParent());//null 1.不存在 2.获取不到(权限不够)
}
}
- 虚拟机自带的加载器
- 启动类加载器
- 扩展类加载器
- 应用程序加载器
双亲委派机制
双亲委派模型的⼯作机制是:当类加载器接收到类加载的请求时,它不会⾃⼰去尝试加载这个类,⽽是
把这个请求委派给⽗加载器去完成,只有当⽗类加载器反馈⾃⼰⽆法完成这个加载请求时,⼦加载器才
会尝试⾃⼰去加载类。
类加载器收到类加载请求,将这个请求向上委托父类加载器去完成,一直向上委托,直到启动类加载器,启动加载器检查是否能够加载这个类。App–>EXC----->BOOT(最终执行)
好处
- 基于双亲委派模型规定的这种带有优先级的层次性关系,虚拟机运⾏程序时就能够避免类的重复加载。
- 双亲委派模型能够避免核⼼类篡改。⼀般我们描述的核⼼类是 rt.jar、tools.jar 这些由启动类加载器加载的类,这些类库在⽇常开发中被⼴泛运⽤,如果被篡改,后果将不堪设想。
##沙箱安全机制
沙箱是一个限制程序运行的环境,将代码限定在JVM特定运行范围中,并严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的 有效隔离,防止对本地系统造成破坏
##Native
package com;
public class Demo {
public static void main(String[] args) {
new Thread(()->{
},"my thread name").start();
}
//nation:凡是带了nation 关键字的,说明java的作用范围达不到了,调用底层C语言库
//会进入本地方法栈
//调用本地方法本地接口JNI
//JNI:扩展java使用,融合不同的编程语言为java所用,例如C,C++
//在内存区域内专门开辟了一块标记区域:native Method Stack,登记native
private native void hello();
//调用其他接口:Socket,WebServer。。http
}
##PC寄存器
程序技术器
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向像一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。
##方法区
Method Area方法区
方法区也叫静态区,是被所有线程共享,所有字段和方法字节码以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法信息都保存在该区域,此区域属于共享区间。
方法区包含的都是整个程序中永远唯一的元素, class 信息 和 static修饰的变量
静态变量,常量,类信息,运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法去无关
static final Class 常量池:方法区
##栈
栈是一种数据结构
程序=数据结构+算法:持续学习
程序=框架+业务逻辑 简易工作理解
栈:先进后出
队列:先进先出,(FIFO:First Input First Output)都能操作
main方法先执行
- 第一步把main方法压入栈中
- 再压入其他方法
- 弹出main方法结束
简易理解:栈也可以叫栈内存,主管程序的运行,生命周期和线程同步;线程结束,栈内存也就被释放,对于栈来说,不存在垃圾回收问题 一旦线程结束,栈也就over
栈:八大基本类型+对象引用+实例的方法
栈帧的理解:
栈+堆+方法区:交互关系
画出一个对象在内存中实例化的过程
##三种JVM
- Sun公司 HotSpot
Java HotSpot(TM) 64-Bit Server VM (build 25.261-b12, mixed mode)
- BEA
JRockit
- IBM
J9 vm
我们学的都是HotSpot
##堆
Heap,一个jvm只有一个堆内存堆内存大小可以调节
类加载器读取了类文件后,一般会把类,方法,常量放到堆中,保存我们所有引用类型的对象。
堆内存中还要细分为三个区域
- 新生区(伊甸园区eden)Yong/New
- 老年区 old
- 永久区
GC垃圾回收,主要是在伊甸区和养老区
假设内存满了,OOM,堆内存不够 java.lang.OutOfMemoryError:java heap space
##新生区,老年区
- 类:诞生和成长的地方,甚至是死亡
- 伊甸区,所有的对象都是在伊甸区new出来的
- 存活区
经过研究,99%的对象都是临时对象,活不到老年区。
新生代又分为伊甸区(Eden) 存活区(Survivor),其中存活区又分为两个大小空间一样的s0、s1,而且s0 和 s1 可以互相转化,存活区保存的一定是在伊甸区保存了很久的,并且经过好几次小的GC还存活下来的对象,存活区一定会有两块大小相等的空间。目的是一块存活区未来的晋升,另一块存活区是为了对象的回收。需要注意的是:这两块存活区一定有一块是空的。
##永生区
这个区域常驻内存,用来存放JDK自身携带的Class对象,Interface元数据,存储的是java运行时的一些环境或者类信息,这个区域不存在垃圾回收!关闭VM虚拟机的时候会释放这个区域的内存
一个启动类,加载了大量的第三方jar包。Tomcat部署了太多的应用,大量动态生成的反射类,不断的被加载。直到内存满,就会出现OOM(OutOfMemoryError);
- jdk1.6:永久代,常量池是在方法区中;
- jdk1.7:永久代,但是慢慢的退化了,常量池在堆中
- jdk1.8:无永久代,常量池在元空间
逻辑上存在,实际不存在
##堆内存调优
堆内存调优代码:
设置VM大小 //-Xms1024m -Xmx1024m -XX:+PrintGCDetails
package com;
public class Demo02 {
public static void main(String[] args) {
//返回虚拟机试图使用的最大内存
long max = Runtime.getRuntime().maxMemory(); //字节 1024*1024
//返回jvm的初始化总内存
long total = Runtime.getRuntime().totalMemory();
System.out.println("max ="+max+"字节\t"+(max/(double)1024/1024)+"MB");
System.out.println("total ="+max+"字节\t"+(total/(double)1024/1024)+"MB");
}
//OOM
//1.尝试扩大堆内存查看结果
//2.分析内存,看一下那个地方出错了
//-Xms1024m -Xmx1024m -XX:+PrintGCDetails
}
在一个项目中,突然出现了OOM故障,那么该如何排除,研究为什么出错
- 能够看到代码第几行出错:内存快照分析工具,MAT,Jprofile
- Dubug,一行行区分析
MAT,Jprofile作用
- 分析Dump内存文件,快速定位内存泄露
- 获得堆中的数据
- 获得大的对象
- …
通过Jprofile工具解决问题:狂神JVM视频p9
##GC
JVM在进行GC时,并不是堆这三个区域统一回收,大部分回收的都是新生代。
- 新生代
- 幸存区(from和to两个区域)
- 老年区
GC两种分类:轻GC(普通的GC)和重GC(全局GC)
GC的算法:标记清除法,标记压缩法,复制算法,引用计数器
复制算法
- 好处:没有内存的碎片
- 坏处:浪费了内存空间:多了一般空间永远时空的(to)
复制对象最佳使用的场景:对象存活度低的时候(及新生区)
标记清除法
- 优点:不需要额外空间
- 缺点:两次扫描,严重浪费时间,会产生内存碎片
标记压缩
GC总结:
内存效率:复制算法>标记清除算法>标记压缩算法
内存整齐度:复制算法=标记清除算法>标记压缩算法
内存利用率:标记压缩算法>标记清除算法>复制算法
没有最好的算法,只有最合适的算法。GC:分代收集算法
年轻代:
- 存活率低
- 复制算法
老年代:
- 区域大,存活率高
- 标记清除+标记压缩混合实现
多找时间学习JVM
- 优点:不需要额外空间
- 缺点:两次扫描,严重浪费时间,会产生内存碎片
标记压缩
[外链图片转存中…(img-M9KKuZur-1602681619630)]
GC总结:
内存效率:复制算法>标记清除算法>标记压缩算法
内存整齐度:复制算法=标记清除算法>标记压缩算法
内存利用率:标记压缩算法>标记清除算法>复制算法
没有最好的算法,只有最合适的算法。GC:分代收集算法
年轻代:
- 存活率低
- 复制算法
老年代:
- 区域大,存活率高
- 标记清除+标记压缩混合实现
多找时间学习JVM
推荐书《深入理解JVM》