思考问题:
1,什么是jvm?
2,jvm是干嘛的?
3,如何学习jvm?
一,什么是jvm?
- jvm是操作系统中的一个进程,它可以执行由javac编译后的.class文件,所以它也是java程序运行的容器。也是一个虚拟计算机
二,jvm是干嘛的?
- 用来执行由javac编译后的.class文件,实现java语言的跨平台
三,入门学习要掌握的要点是什么?
1,jvm的位置
2,jvm的体系结构
3,类加载器
4,双亲委派机制
5,Native
6,PC寄存器
7,方法区
8,栈
9,三种JVM
10,堆
11,新生区,老年区
12,永久区
13,堆内存调优
14,GC的常用算法
15,JMM
四,学习
1,jvm的位置:
java文件被javac编译成class文件,class文件被jvm的类加载器加载,jvm解析class文件调用操作系统的功能
2,JVM的体系结构:
垃圾回收主要在回收java堆中的垃圾,方法区中有少量垃圾
3,类加载器:加载class文件和Java 应用所需的资源,如图像文件和配置文件等。
参考https://blog.csdn.net/fangqun663775/article/details/38925031
加载class过程:
方法 | 说明 |
---|---|
getParent() | 返回该类加载器的父类加载器。 |
loadClass(String name) | 加载名称为 name 的类,返回的结果是 java.lang.Class 类的实例。 |
findClass(String name) | 查找名称为 name 的类,返回的结果是 java.lang.Class 类的实例。 |
findLoadedClass(String name) | 查找名称为 name 的已经被加载过的类,返回的结果是 java.lang.Class 类的实例。 |
defineClass(String name, byte[] b, int off, int len) | 把字节数组 b 中的内容转换成 Java 类,返回的结果是 java.lang.Class 类的实例。这个方法被声明为final 的。 |
resolveClass(Class<?> c) | 链接指定的 Java 类。 |
加载器的继承:
双亲委派机制:
- 类加载器收到加载请求
- 类加载器将请求抛出给,父类加载器处理直到引导类加载器
- 引导类加载器检查是否可以处理这个请求,如果不能就交给抛出异常,交给子类加载器来处理。
- 重复步骤3
- 如果都找不到就ClassNotFound
native关键字:
- 告诉jvm该方法java覆盖不到,去掉底层的C语言库。
- 该方法名会进入本地方法栈,本地方法栈去调用本地方法接口,如何去本地方法库中查找方法。
- 本地方法接口:扩展java语言,融合不同的语言给java使用
- 历史:java诞生时,C和C++横行,java想要立足就需要调用C和C++程序,于是它在内存区域开辟了本地方法栈来登记native方法,最终执行的时候根据本地方法接口调用本地方法库中用其他语言写的方法。(想要融合其它语言可以socket编程,webService,Http,,,)
程序计数器:
- 每一个线程都有一个私有的程序计数器,本质上是一个指针用来指向将要指向的下一条指令的地址。通过它来控制程序中每一条语句执行的顺序。
- 它占有的内存空间很小几乎可以忽略不计
方法区:
- 方法区被所有的线程共享
- 其中定义了普通方法,特殊方法(构造方法,抽象方法)。所有定义方法的信息都保存在这个区域。静态变量,常量,类信息。运行时的常量池存在方法区中
- 实例变量存在堆内存中和方法区无关
栈:
- 它的生命周期与线程同步,当线程结束栈内存也就被释放了,所以不存在垃圾回收的问题
- 栈中存的是:八大基本类型+对象引用+实例方法
- 栈中的元素是栈帧,栈帧的组成:方法索引,输入输出参数,本地变量,Class文件的引用,父帧,子帧
- 栈满了:报StackOverError
对象实例化的过程:
视频:https://www.bilibili.com/video/BV1u54y1R7iu?p=10
如:A a=new A();
- 加载class文件到class内容区域,加载静态方法和静态变量到静态区(同时加载的)
- 调用main方法到栈内存
- 在栈内存中为a变量(A对象的引用)开辟空间
- 在堆内存为A对象申请空间
- 给成员变量进行默认初始化(此时 i=0),同时有一个方法标记,在方法区中创建一个A的方法区,将A的方法区的地址0x01给方法标记
- 给成员变量进行显示初始化(此时 i=1)
- 将A对象的地址值给变量a
堆:
- 存放对象实例(包含自己的成员,方法指针等,方法指针又执行方法区的方法)和数组,栈中的引用存放该对象实例的地址。
- 分区:新生区(Eden区,幸存区1,幸存区2),养老区,永久区(元空间)。
- GC垃圾回收主要在Eden区和养老区
- 元空间中有方法区,方法区中有常量池。元空间用来存储永久存储的东西
- 调节堆空间的大小 -Xms10m -Xmx10m -XX:+PrintGCDetails
- JProfiles可以用来排除OOM错误
GC算法:
- 引用计数法:每使用对象就给它的计数器加1,清除计数器为0的对象
- 标记清除:对使用的对象进行标记,对没有标记的对象进行清除(两次扫描浪费时间,会产生内部碎片,不需要额外的空间)
- 标记压缩:对标记清除进行优化,再扫描一次将对象向一端移动,防止内部碎片
- 复制算法:幸存区复制交换(无内存碎片但浪费一半的内存空间)对象存活度较低时
总结:
- 内存效率:复制算法>标记清除>标记压缩(时间复杂度)
- 内存整齐度:复制算法=标记压缩>标记清除
- 内存利用率:标记压缩=标记清除>复制算法
- 新生代:存活率低使用复制算法
- 老年代:存活率高,使用标记清除+标记压缩实现
JMM:
- 是java内存模型
- JMM作用:缓存一致性原则,用于定义数据读写的规则,定义了线程和内存之间的抽象关系,线程之间共享变量存在主内存中,每个线程都有一个私有的本地内存
- 解决:共享对象可见性的问题Volilate
JMM定义的规则:
- 不允许read和load,store和write单独出现
- 工作变量改变之后必须告诉主存
- 没有assign的数据不能同步到主存
- 一个新变量必须重主存诞生,对变量进行use,store操作之前必须先assign和load操作
- 一个变量同一时间只能有一个线程对它进行lock
- 如果对一个变量进行lock操作,多次lock后,必须执行相同次数的unlock才能解锁。如果清空了变量的值,再次使用这个变量之前必须重写进行assign和load操作
- 变量没有被lock就不能unlock
- 对一个变量进行unlock必须把变量同步写会主存
补充:
1,Java的类加载过程
- 加载:类加载器加载.class文件(可以用自定义类加载器加密,防止反编译)
- 验证:保证加载进来的字节流符合虚拟机规范,不会造成安全错误。
- 准备:为类变量赋予初值(引用类型null,基本类型null,final类型初值)
- 解析:将常量池内的符号引用替换为直接引用的过程。
- 初始化:对static修饰的变量或语句进行初始化。
2,什么情况下会发生栈内存溢出
- 栈是线程私有的,用来存储 八大基本类型+对象引用+实例方法
- 方法递归调用可能出现溢出,因为不断向栈里面添加实例化方法,常量,,,抛出StackOverflowError错误
- OutOfMemory 异常。(线程启动过多)
- 解决:参数 -Xss 去调整JVM栈的大小
3,请你讲一下JVM中一次完整的垃圾回收流程
前言:
- java堆=新生代+老年代
- 新生代=Eden+s0+s1
流程:
- Eden区满时,会触发一次垃圾回收,存活下来的小对象进入s0区,把它的年龄加1(大对象进入老年代)
- 新生代中的对象每经历一次垃圾回收,存活下来,年龄加1,单年龄达到15时进入老年代
- 当老年代也满了,会触发满垃圾回收,对新生代和老年代进行清理
4,你知道哪几种垃圾收集器
- CMS(Concurrent Mark Sweep) 收集器: 是一种以获得最短回收停顿时间为目标的收集器,标记清除算法,运作过程:初始标记,并发标记,重新标记,并发清除,收集结束会产生大量空间碎片
- G1收集器: 标记整理算法实现,运作流程主要包括以下:初始标记,并发标记,最终标记,筛选标记。不会产生空间碎片,可以精确地控制停顿。
区别:
- CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;
- G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用;
- CMS收集器以最小的停顿时间为目标的收集器;
- G1收集器可预测垃圾回收的停顿时间
- CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片
- G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。
4,谈谈你对JVM内存模型(JMM)的理解
JVM内存模型是一个规则
- 规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存(把该线程要用到的变量从主内存中copy过来存储)
- 线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。
- 不同的线程之间也无法直接访问对方工作内存中的变量,
- 线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。
5,谈谈你对双亲委派机制的理解
- 为什么要使用它:为了保证相同的class文件,在使用的时候,是相同的对象
- 它的流程是:如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;只有当父类加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载。
- 加载器继承关系:自定义类加载器——》应用程序类加载器——》扩展类加载器——》启动类加载器
- 如何打破双亲委派机制:要继承ClassLoader类,重写loadClass和findClass方法
- 一个jvm中只有一个堆,堆的大小是可以调节的
- 类加载器读取了文件后,将类