开始学习JVM之前,百度搜到这篇文章,JVM只需这一篇 第一眼看的人,只能感叹一句,tql !!!
首先思考几个问题
-
对JVM的理解,Java8虚拟机与之前的变化
-
什么是OOM,什么是栈溢出?怎么分析?
OOM:
OutOfMemoryError:如果虚拟机栈可以动态扩展,而扩展时无法申请到足够的内存。
关于OOM的一些解释
栈溢出:StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度。
如何解决栈溢出
栈溢出的原因 -
JVM的常用调优参数?
JVM常用的调优参数
-
内存快照如何抓取?怎么分析Dump文件?
内存快照:内存转储快照
Dump文件:Java Dump文件分析
-
谈谈JVM中,类加载器你的认识?rt-jar ext appliacation
类加载:类加载器详解
四个基础类加载器,类加载器是什么?有哪些?怎么用?
1. JVM的位置① 什么是JVM?
JVM(Java Virtual Machine)是 Java 虚拟机,用于运行 Java 编译后的二进制字节码,最后生成机器指令。
JVM 是 Java 能够跨平台的核心
② JDK JRE JVM 的关系
JDK :(Java Development Kit),Java 开发工具包。JDK 是整个 Java 开发的核心,集成了 JRE 和javac.exe,java.exe,jar.exe 等工具。
JRE :(Java Runtime Environment),Java 运行时环境。主要包含两个部分,JVM 的标准实现和 Java 的一些基本类库。它相对于 JVM 来说,多出来的是一部分的 Java 类库。
三者的关系是:一层层的嵌套关系。JDK>JRE>JVM
JVM的位置(这里也可以回顾下冯诺依曼体系)
2. JVM体系结构自己画张图:
首先是运行时数据区的五个区域及各自的作用:
- PC寄存器(程序计数器):程序计数器是线程私有的。关于PC寄存器这篇文章的介绍简单易懂https://blog.csdn.net/qq_44892091/article/details/104077934 该内存区域是唯一一个在《Java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域。
- 堆
new创建的实例化对象及数组,是存放在堆内存中的,用完之后靠垃圾回收机制不定期自动消除。
- 方法区:
在Java 虚拟机中,被加载类型的信息都保存在方法区中,方法区也可以被垃圾收集
- 本地方法栈
区别于 Java 虚拟机栈的是,Java 虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。也会有 StackOverflowError 和 OutOfMemoryError 异常。
5 Java虚拟机栈:
与程序计数器一样,Java虚拟机栈也是线程私有的。它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候,Java虚拟机栈都会同步一个栈帧(stack frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机中从入栈到出栈的过程。局部变量表(8大基本数据类型和对象引用)
局部变量表:存放了编译期可知的各种基本类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型)和 returnAddress 类型(指向了一条字节码指令的地址)
import java.util.Scanner;
public class Demo05 {
String name;
int age;
String gender;
public static void main(String[] args) {
int[] arr = new int[3];
int a = 3;
Scanner sc = new Scanner(System.in);
int x = sc.nextInt();
int y = sc.nextInt();
Demo05 demo05 = new Demo05();
int sum = demo05.sum(x,y);
System.out.println(sum);
demo05.name = "tom";
Demo05 d2 = demo05;
d2.age = 3;
System.out.println(d2.name + " "+ d2.age+" " + d2.gender);//tom 3 null
System.out.println(demo05.name + " "+ demo05.age+" " + demo05.gender);//tom 3 null
demo05 = null;
System.out.println("after demo = null");
System.out.println(d2.name + " "+ d2.age+" " + d2.gender);//tom 3 null
System.out.println(demo05.name + " "+ demo05.age+" " + demo05.gender);//Exception in thread "main" java.lang.NullPointerException
}
public int sum(int a,int b){
return a+b;
}
}
总结:
- 堆中存放的是new的实例化对象和数组,用完之后Java会不定期使用垃圾清除机制不定期清除
- 栈中存放的是基本数据类型和局部变量。用完就释放
- 对象的引用也存在栈中,例如demo05就是实例化对象new Demo05()的一个引用,而它引用对象的实例 化在堆中。引用对象在栈中只存储了他对应的地址,通过引用对象进行赋值的操作,值存储在堆中
- 数组和实例化对象一样,数组名相当于一个对象引用。这也体现了Java的一切皆对象的思想
关于Java中堆栈中各自存哪些信息,可以简单参考一下这篇 文章:堆栈
JVM架构图:JVM架构图和GC垃圾回收(文章值得细读!!!!!)
3. 类加载器,关于类加载器这篇文章可以看看,讲的比较基础,简单易懂:类加载器
classload
作用:加载Class文件,
类加载器的分类:
package com.test;
import java.net.URL;
public class Car {
public int age;
//java.lang包
public static void main(String[] args) {
Car car1 = new Car();
Car car2 = new Car();
Car car3 = new Car();
car1.age = 1;
car2.age = 2;
car3.age = 3;
System.out.println("======同一个类的不同的实例化对象具有不同的hashcode()值=======");
System.out.println(car1.hashCode());
System.out.println(car2.hashCode());
System.out.println(car3.hashCode());
Class extends Car> aClass1 = car1.getClass();
Class extends Car> aClass2 = car2.getClass();
Class extends Car> aClass3 = car3.getClass();
System.out.println("======同一个类具有相同的hashcode()值=======");
System.out.println(aClass1.hashCode());//hasCode()
System.out.println(aClass2.hashCode());
System.out.println(aClass3.hashCode());
System.out.println("======获取该类对应的类加载器=======");
ClassLoader classLoader1 = aClass1.getClassLoader();
ClassLoader classLoader2 = aClass2.getClassLoader();
ClassLoader classLoader3 = aClass3.getClassLoader();
// 系统类加载器(system class loader):被称为系统(也称为应用)类加载器
// 程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。
// 如果没有特别指定,则用户自定义的类加载器都以此类加载器作为父加载器。
// 由Java语言实现,父类加载器为ExtClassLoader。
System.out.println(classLoader1);//AppClassLoader,,rt.jar中java.lang包中 public abstract class ClassLoader
System.out.println(classLoader2);
System.out.println(classLoader3);
System.out.println("======获取该类对应的类加载器的父类=======");
// jre1.8libext
// 扩展类加载器(extensions class loader):它负责加载JRE的扩展目录,lib/ext或者
// 由java.ext.dirs系统属性指定的目录中的JAR包的类。由Java语言实现,父类加载器为null。
ClassLoader parent = classLoader1.getParent();//ExtClassLoader扩展类,
System.out.println(parent);
ClassLoader parent1 = parent.getParent();//null
// 结果为null的两种情况,1.不存在,2.Java获取不到 rt.jar中java.lang包,rt.jarjavalang
System.out.println(parent1);
// 根加载器
System.out.println("=======根加载器========");
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for(URL url : urls){
System.out.println(url.toExternalForm());
}
}
}
类加载器,向上委派,向下执行。双亲委派机制。APP-EXT-BOOT
类加载的步骤:
-
类加载器收到类加载的请求,
-
将这个请求向上委托给父亲去完成,一直向上委托,直到启动类加载器。
-
启动类加载器会检查是否能加载这个类,能加载就使用当前加载类,否则,抛出异常或者通知子类加载器进行加载
-
子类加载器重复步骤3
null:Java调用不到~Java底层是c和C++实现的
Java = c+±-,去掉繁琐的东西,指针,内存管理。
4. 双亲委派机制这里可以阅读一下这篇博文 双亲委派机制
里面的这张图可以说把双亲委派机制讲的非常清楚了
5. 沙箱安全机制这篇文章内容很多很详细看一遍可能不太容易理解
沙箱sandbox
沙箱安全机制介绍
Java安全模型的核心就是Java沙箱(sandbox)
沙箱主要限制系统资源访问,系统资源包括CPU、内存、文件系统、网络
这篇博文很容易入门理解native native
javah命令详解: javah
7. PC寄存器跟之前在第一点写过的一样,寄存器看这篇文章入门级理解应该是够了:程序计数器
8. 方法区java方法区的理解先码一下这篇文章
Method Area 方法区
方法区是被所有线程共享的,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,该区域属于共享区间
静态变量、常量、类信息(构造方法,接口定义),运行时常量池存放在方法区,但是实例化变量存放在堆内存中,和方法区无关
static , final , Class , 常量池
9. 栈栈:数据结构
程序 = 数据结构 + 算法:持续学习
程序 = 框架 + 业务逻辑 吃饭
栈:先进后出,后进先出。桶模型
队列:先进先出,后进后出(First Input First Output)
堆:堆通常是一个可以被看做一棵完全二叉树的数组对象。
关于堆,栈,队列及堆栈的知识补课可以看这篇博文:堆栈队列
为什么main()方法先执行后结束
程序一执行就将main()方法压入栈中,依次执行依次压栈,执行完依次弹出栈
栈:栈内存,主管程序的运行生命周期和线程同步,
线程结束,栈内存也就释放了。对于栈来说**不存在垃圾回收问题 **
栈:8大基本类型+对象引用+实例的方法
栈运行的原理:栈运行原理
Java中堆栈运行原理
搞清楚栈+堆+方法区三者之间 的调用关系
画出一个对象在内存中实例化的过程
10. 三种主流的JVM待补充
1. Sun公司的HotSpot 是目前使用范围最广的Java虚拟机.
2. BEA公司的JRockit(原来的 Bea JRockit)电脑软件,系列产品是一个全面的Java运行时解决方案组合。
3. IBM公司的J9 VM 是一个高性能的企业级 Java 虚拟机。
查看自己的JVM版本java -version
11. 堆Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的
IDEA中可以调节VM的参数以及给main()方法传递参数
类加载器读取了类文件后,一般会把什么东西放入到堆中呢?这篇文章Java的堆栈中究竟存放了些什么东西
堆内存中还要细分为三个区域
- 新生区(伊甸园区)new
- 老年区 old
- 永久区 perm
一个类 诞生 和 成长 的地方,甚至有可能死亡
新生区分为:伊甸园区和幸存区0区和1区
伊甸园区是new出来的
幸存者区(0,1)
经过研究,99%的对象都是临时对象
13. 老年区 14. 永久区这个区域常驻内存的,用来存放jdk自身携带的CLass对象,Interface元数据,存储的是Java运行时的一些环境或类信息,这个区域不存在垃圾回收!关闭JVM虚拟机就会释放这个域的内存!
一个启动类,加载了大量的第三方jar,tomcat部署了太多的应用,大量动态生成的反射类,如果这些东西不断被加载,直到内存满,就会出现OOM;
- jdk1.6之前:永久代,常量池是在方法区
- jdk1.7 :有永久代,但是慢慢退化了。去悠久代,常量池在堆中
- jdk1.8之后:无永久代,常量池在元空间
GC垃圾回收,主要在伊甸园区和养老区~
假设内存满了,OOM,堆内存不够!java.lang.OutOfMemoryError:java heap space
在jdk8以后,永久存储区改名为元空间
public class Demo02 {
public static void main(String[] args) {
long max = Runtime.getRuntime().maxMemory();
long total = Runtime.getRuntime().totalMemory();
System.out.println("max"+max+"字节t"+(max/(double)1024/1024)+"MB");
System.out.println("max"+total+"字节t"+(total>>20)+"MB");
// -Xms1024m -Xmx1024m -XX:+PrintGCDetails
// 305664K+699392K = 1,005,056 K = 981.5 M
// 元空间逻辑上存在,物理上不存在
// OOM
// 1.尝试扩大堆内存看结果
// 2.分析内存,看一下那个地方出现了问题(专业工具)
}
}
import java.util.Random;
// -Xms8m -Xmx8m -XX:+PrintGCDetails
public class Demo03 {
public static void main(String[] args) {
String str = "Tom & Jerry";
while (true)
{
str = str + new Random().nextInt(999999999) + new Random().nextInt(999999999);
}
}
}
元空间:逻辑上存在于堆中,物理上不存在。
在一个项目中,突然出现OOM故障,那么该如何排除,究竟为什么出错
- 能够看到第几行代码出错:内存快照分析工具,MAT,Jprofile
- Debug,一行行分析代码!
MAT,Jprofile作用
- 分析Dump内存文件,快速定位内存泄漏。
- 获取堆中的数据
- 获取大的对象
- …
import java.util.ArrayList;
// -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
public class Demo04 {
Byte[] array = new Byte[1*1024*1024];
public static void main(String[] args) {
ArrayList list = new ArrayList<>();
int count = 0;
try{
while(true){
list.add(new Demo04());
count = count + 1;
}
}catch (Exception e){
System.out.println(count);
e.printStackTrace();
}
}
}
安装Jprofile插件以及下载Jprofile安装版并配置IDEA
用Jprofile安装版打开Dump文件
15. 堆内存调优import java.util.ArrayList;
// -Xms 设置初始化分配内存大小,默认 1/64
// -Xmx 设置最大分配内存,默认 1/4
// -XX:+PrintGCDetails 打印GC垃圾回收信息
// -XX:+HeapDumponOutOfMemoryError 打印OOM和Dump文件
// -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
// Runtime类
public class Demo04 {
Byte[] array = new Byte[1*1024*1024];
public static void main(String[] args) {
ArrayList list = new ArrayList<>();
int count = 0;
try{
while(true){
list.add(new Demo04());//OOM问题所在
count = count + 1;
}
}catch (Error e){
System.out.println("count:"+count);
e.printStackTrace();
}
}
}
16. GC 垃圾回收
????在哪个区域回收??
可以手动吗?
JVM在进行GC时,并不是堆这三个区域进行统一回收,大部分时候只在新生区进行垃圾回收
- 新生区
- 幸存区(from , to) from 区和 to 区可以交换位置,谁空谁是to
- 老年区
GC分类:轻GC(普通GC),重GC(全局GC)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aLWn8kKT-1634807231426)(C:UsersAdministratorAppDataRoamingTyporatypora-user-imagesimage-20211018130827834.png)]
GC算法:可以阅读这2篇博文再整理
https://www.cnblogs.com/dongl961230/p/13280415.html
https://www.cnblogs.com/sunfie/p/5125283.html
https://blog.csdn.net/jisuanjiguoba/article/details/80156781
- 引用计数法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zjyxnnCp-1634807231428)(C:UsersAdministratorAppDataRoamingTyporatypora-user-imagesimage-20211018130957499.png)]
- 复制算法,谁空谁是to
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UxfuSJZW-1634807231428)(C:UsersAdministratorAppDataRoamingTyporatypora-user-imagesimage-20211018132048904.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rJsRai4n-1634807231429)(C:UsersAdministratorAppDataRoamingTyporatypora-user-imagesimage-20211018132325231.png)]
- 好处:没有内存碎片
- 坏处:浪费内存空间,多了一半空间永远是空的(幸存区to)。假设对象100%存活(极端情况)
复制算法的最佳使用场景:对象存活度较低的时候;新生区~
-
压缩清除、压缩算法
标记清除
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vg6jyvxg-1634807231430)(C:UsersAdministratorAppDataRoamingTyporatypora-user-imagesimage-20211018141548872.png)]
优点:不需要额外的空间!
缺点:两次扫描严重浪费时间,会产生内存碎片
标记压缩
再优化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LJlSetcq-1634807231431)(C:UsersAdministratorAppDataRoamingTyporatypora-user-imagesimage-20211018141902582.png)]
标记清除压缩
多次清除一次压缩
17. 总结- 内存效率:复制算法 > 标记清除算法 > 标记压缩算法 (时间复杂度)
- 内存整齐度:复制算法 = 标记压缩算法 >标记清除算法
- 内存利用率:标记清除算法 = 标记压缩算法 > 复制算法
最优算法:没有最优的算法,只有最合适的算法。GC也被称为分代收集算法
年轻代
- 存活率低
- 复制算法!
老年代
- 区域大,存活率高
- 标记清除(内存碎片不是太多)+标记压缩混合 实现
要深究JVM,花时间自学,看面试题以及经典书籍《深入理解JVM虚拟机》
这篇文章有点老,可以看一看 https://www.cnblogs.com/yjd_hycf_space/p/7544768.html
18. JMMhttps://blog.csdn.net/zjcjava/article/details/78406330
百度,思维导图,参考别人
单点登录-SSO架构师
VM CentOS 7



