JVM学习
JVM学习
jvm的位置
jvm的体系结构
类加载器
作用
加载class文件
1.虚拟机自带的加载器
2.启动类(根)加载器
3.扩展类加载器
4.应用程序(系统类)加载器
双亲委派机制
1.通过委派的方式,可以避免类的重复加载,当父加载器已经加载过某一个类时,子加载器就不会再重新加载这个类。
2.通过双亲委派的方式,还保证了安全性。因为Bootstrap ClassLoader在加载的时候,只会加载JAVA_HOME中的jar包里面的类,如java.lang.Integer,那么这个类是不会被随意替换的,除非有人跑到你的机器上, 破坏你的JDK。那么,就可以避免有人自定义一个有破坏功能的java.lang.Integer被加载。这样可以有效的防止核心Java API被篡改。
沙箱安全机制
了解即可:权限安全策略
Native
凡是带了native关键字的方法,说明Java的作用范围达不到,会去调用底层c语言的库;
会进入本地方法栈;
调用本地方法接口:JNI;
JNI作用:扩展Java的使用,融合不同的编程语言为java所用;最初仅想融合C、C++;
Java诞生时,C、C++流行,想要立足,必须要有调用C和C++的程序;
它在内存区域中,专门开辟了一块标记区域:Native Method Stack,登记native方法;
在最终执行时,加载本地方法库中的方法通过JNI。
应用:Java程序驱动打印机,管理系统,Robot类,企业级应用中较为少见。
调用其它接口:Socket、web Service、http。
PC寄存器
程序计数器:Program Counter Register.
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码,是一个非常小的内存空间,几乎可以忽略不计。
方法区
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域中,此区域属于共享空间;
静态变量、常量、类信息(构造方法,接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,与方法区无关。
static、final、Class模板、常量池
栈
数据结构。
程序 = 数据结构 + 算法:持续学习;
程序 = 框架 + 业务逻辑:吃饭;
栈:先进后出、后进先出:桶概念;
队列:先进先出,后进后出:管道排队概念;(FIFO:First Input First Output);
喝多了吐就是栈,吃多了拉就是队列。
栈:栈内存,主管程序的运行,生命周期和线程同步;线程结束,栈内存就释放;对于栈来说,不存在垃圾回收问题;一旦线程结束,栈就结束。
栈:8大基本类型+对象的引用+实例的方法;
栈运行原理:栈帧
栈满了:StackOverflowError
栈+堆+方法区:交互关系;
三种JVM
- Sun公司
HotSpot
- BEA
JRockit
- IBM
J9 VM
堆
Heap:一个JVM只有一个堆内存;堆内存的大小是可调节的;
堆内存细分三个区域:
-
新生区:Young/New
-
老年区:Old
-
永久区:Perm
垃圾回收:轻量级(针对伊甸园区)与重量级(FullGC:新生区);主要是在新生区与养老区;假设内存满了会报OOM(OutOfMemoryError),堆内存不够。
在JDK8以后,永久存储区改名为元空间;
新生区、老年区
-
类 诞生和成长的地方,甚至死亡;
-
伊甸园,所有的对象都是在伊甸园区新生,
-
幸存者区(0,1)
经过研究,99%的对象都是临时对象。
永久区
常驻内存,用来存放JDK自身携带的Class对象,interface元数据,存储的是Java运行时的一些环境或类信息;
这个区域不存在垃圾回收;关闭虚拟机就会释放永久区内存。
一个启动类加载了大量的第三方jar包,Tomcat部署了太多的应用,大量动态生成的反射类;不断的被加载;直到内存满,就会出现OOM。
- JDK1.6之前:永久代,常量池在方法区中;
- JDK1.7:永久代,但是慢慢退化,去永久代,常量池在堆中;
- JDK1.8:无永久代,常量池在元空间
堆内存调优
元空间逻辑上存在,物理上不存在
默认情况下:分配的总内存是电脑内存的四分之一,而初始化的内存:六十四分之一;
-Xms1024m -Xmx1024m -XX:+printGCDetails
Xms1m -Xmx1m -XX:+HeapDumpOnOutOfMemoryError
// -Xms 设置初始化内存分配大小,默认为1/64
// -Xmx 设置最大分配内存,默认为1/4
// -XX:+PrintGCDetails 打印GC垃圾回收信息
// -XX:+HeapDumpOnOutOfMemoryError OOM DUMP文件
OOM:
1.尝试堆内存空间扩大;
2.分析内存(内存快照分析工具JProfiler)
1.分析dump内存文件,快速定位内存泄漏;
2.获得堆中的数据;
3.获得大的对象;
4. …
GC 垃圾回收
JVM在进行GC时,并不是对这三个区域统一回收,大部分时候,都是回收新生代;
- 新生代
- 幸存区(from,to:谁空谁是to)
- 老年代
GC两种类别:轻GC(普通的GC),重GC(全局GC);
当一个对象经历了15(默认)次GC后,都还没有死,就会进入养老区
-XX:MaxTenuringThreshold=5 设置进入老年代的时间
1.每次GC都会将Eden中活的对象移到幸存区中:一旦Eden区被GC后,就会是空的;
GC(常用算法)
-
标记清除算法
1.扫描对象:对活着的对象进行标记;
2.清除扫描:对没有标记的对象进行清除。
优点:不需要额外的空间!
缺点:两次扫描,严重浪费时间,会产生内存碎片。
-
标记整理/压缩法
对标记清除法的再优化:压缩:防止内存碎片产生,再次扫描,向一端移动存活的对象;
标记清除压缩:先标记清除几次,再压缩
-
复制清除算法:为了保证to区永远都是干净的
好处:没有内存的碎片坏处:浪费了内存空间:多了一半空间永远是空to;假设对象100%存活(极端情况),开销极大
复制算法最佳使用场景:对象存活度较低的时候(新生区)
-
引用计数算法(用的较少)
内存效率:复制算法>标记清除法>标记压缩法(时间复杂度)
内存整齐度:复制算法 = 标记压缩法>标记清除法
内存利用率:标记压缩法 = 标记清除法 > 复制算法
没有最好的算法,只有最合适的算法—>GC:分代收集算法
年轻代:
- 存活率低,复制算法
老年代:
- 存活率高,区域大,标记清除法 + 标记压缩法 混合实现
JMM:Java Memory Model
1.什么是JMM?
Java Memory Model。
2.它是做什么的?官方、其他人的博客、对应的视频
作用:缓存一致性协议,用于定义数据读写的规则(遵守,找到这个规则)。
JMM定义了线程工作内存与主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。
解决共享对象可见性这个问题:volatile
3.它该如何学习?
JMM:抽象的概念,理论
学习:volatile
总结
《深入理解Java虚拟机》