JVM探究
- 请你谈谈JVM的理解?java8虚拟机和之前的变化有哪些?
- 什么是OOM,什么是栈内存溢出StackOverFlowError?
- JVM常用的调优参数有哪些?
- 内存快照如何抓取,怎么分析Dump文件?
谈谈JVM中,类加载器你的认识?
JVM的位置?
JVM的体系结构
类加载器
双亲委派机制
Java是运行在Java的虚拟机(JVM)中的,但是它是如何运行在JVM中了呢?我们在IDE中编写的Java源代码被编译器编译成.class的字节码文件。然后由我们得ClassLoader负责将这些class文件给加载到JVM中去执行。
JVM中提供了三层的ClassLoader:
- Bootstrap classLoader:主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。
- ExtClassLoader:主要负责加载jre/lib/ext目录下的一些扩展的jar。
- AppClassLoader:主要负责加载应用程序的主函数类
双亲委派机制原理:
当一个.Class的文件被加载时,用户自定义的加载器不会先加载,首先会在AppClassLoader检查是否被加载过,如果有就无需加载了,如果没有就会拿到父类的加载器。然后调用父类的ClassLoader方法,父类同理也会检查自己是否被加载过,如果没有继续往上,这是一个递归的过程,直到达到了 Bootstrap ClassLoader之前都会检查自己是否加载过,而不是去加载。直到Bootstrap已经没有父加载器了,这时候才会考虑自己能否加载,如果自己没法加载则会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,则会爆出ClassNotFoundExeption
为什么要设计这种机制呢?
这样设计的好处就是,如果有人想替换系统级别的类,系统级别的类已经被BootstarpClassLoader加载过了。从一定程度上防止了危险代码的植入。
- 沙箱安全机制
Native
凡是带了 Native关键字的,说明java的作用范围达不到了,会去调用底层C语言的方法库
调用本地方法接口 JNI :java native interface
JNI的作用:扩展java的使用,融合不同的编程语言,为java所用。最初是为了融合:C 和C++
Java诞生的时候,C和C++很流行,必须要可以调用C和C++的程序
它在内存区域中,专门区分出来了一块 标记区域, 本地方法栈
它会在最终执行的时候,通过JNI加载本地方法。
调用其他语言的接口。PC寄存器: Program Counter Register
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码文件,在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。
方法区
- 方法区是被所有线程共享的,所有字段和字节码文件,以及一些特殊的方法,如构造函数,接口代码也在此定义,所有定义的信息都保存在此区域,此区域属于共享区间
- 方法区中都有哪些:静态变量(static )常量(final) 类信息(Class)常量池
栈:数据结构:
程序=数据结构+算法
栈:先进后出,后进先出。砌墙的砖头,后来居上。
队列(FIFO):先进先出,相当于通道。消息队列
栈:栈内存。主管程序的运行,声明周期和线程同步。
线程结束,栈内存就会释放了,对于栈来说,不存在垃圾回收问题。
栈里面有什么?:8大基本类型、对象引用、实例的方法、
栈的运行原理:
栈+堆+方法区:交互关系
三种JVM
堆(Heap)
- 一个JVM只有一个堆内存。堆的大小是可以手动调节的。
- 类加载器读取了类文件后,会把什么东西放在堆中。类具体的实例+常量+方法变量。会保存我们引用类型的真实对象实例。
- 堆内存中,分为三个区域:新生区(伊甸园区)Yound/New、养老区 Old、永久区
- GC垃圾回收,主要在新生区和养老区
- 假设内存满了,堆内存不够,OOM,
在JDK8之后,永久储存区改了个名字:元空间。
新生区:
- 类:诞生和成长,甚至死亡的地方!
- 伊甸园区:所有的对象最初创建,是在伊甸园区。
幸存者区
- 0区
- 1区
老年区:
永久区:
这个区域常驻内存的,用来存放JDK自身携带的Class对象,Interface元数据。存储的是java的运行环境,这个区域不存在垃圾回收,关闭虚拟机的时候,就会释放这个区域的内存。
在永久区出现OOM错误:一个启动类,加载了大量的第三方jar包。大量动态生成的反射类。
- 1.6之前:永久代,常量池是在方法区
- 1.7“:永久代,常量池在堆中
- 1.8之后:无永久代,常量池在元空间。
经过研究,有99%的对象,是临时对象。
元空间:逻辑上存在,物理上不存在。
## 堆内存调优
在一个项目中,如果出现OOM故障,该如何拍错?研究为什么出错
- 能够看到那行代码出错:内存快照分析工具,MAT,Jprofiler
Debug 代码一行一行分析。
MAT,Jprofiler的作用:
- 分析Dump内存文件,快速定位内存泄漏位置。
- 获得堆中的数据、
- 获得大的对象。
设置堆内存,初始化内存以及最大内存,以及当出现DumpOnOutOfMemoryError错误时:
-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
-Xms1m:初始化内存大小
-Xmx8m:最大内存
-XX:+HeapDumpOnOutOfMemoryError 当出现错误时Dump下来内存文件。栈溢出
-XX:+PrintGCDetails 打印GC垃圾回收信息。
- 堆内存调优
GC:垃圾回收
JVM在进行GC的时候,并不是对这三个区域统一回收。大部分的时候都是在新生区,也就是伊甸区
- 新生区(伊甸区)
- 幸存区
- 老年区
GC分为两种:轻量级G7,重量GC(全局GC)
GC题目:
- JVM内存模型和分区,详细到每个区都放什么?
- 堆里面的分区有哪些?说说他们的特点
- GC算法有哪些?标记清除法、标记整理、复制算法、引用计数器
- 轻GC和重GC分别发生在什么时候?
### 引用计数法:
## 复制算法:
- 每次GC,都会将Eden活着的对象移到幸存区,一旦Eden区被GC后,就会是空的。
- 幸存区to和幸存区from之间的转换,那个里面是空的那个就是to,
- Eden和幸存区之前的转换是用的复制算法,将剩下活着的对象,复制到其他区域,然后清空该区域的对象。
- 当一个对象经历了 15次GC后,都还没有死,就会进入到老年区。可以通过参数设置 这个参数,来设定进去老年区的时间。
这种算法的好处是:没有内存碎片
坏处是:浪费了内存空间,多了一半的内存永远是空的,
想要复制算法最佳使用场景,对象存活度较低的时候。也就是新生区
标记算法
- 对活着的对象进行标记,扫描整个空间,对没有标记的对象进行清除。
缺点:两次扫描 严重浪费时间。会产生内存碎片
优点:不需要额外的内存空间。
标记压缩
对标记清除的再优化:防止内存碎片的产生,再次扫描,向一端移动存活的对象。多了一个移动成本。
总结
- 内存效率(时间复杂度):复制算法>标记清除算法>标记清除压缩算法
- 内存整齐度:复制算法=标记清除算法>标记清除压缩算法
- 内存利用率:标记清除算法=标记清除压缩算法>复制算法
思考一个问题,难道就没有最优的解决办法了吗?
答案就是没有,没有最好的算法,只有最合适的算法,------->GC:分代收集算法
年轻代:
- 特点是:存活率低,极易被GC回收。
- 所以使用复制算法
老年代:
- 区域大:存活率高
- 所以使用标记清除算法+标记压缩算法混合实现。
必须要深入的研究《深入理解JVM》这一本书。
JMM:Java Memory Model--------------java内存模型
- 什么是JMM?
- 它是干嘛的?
作用:缓存一致性协议,用于定义数据读写的规则,我们必须遵守这个规则,
JMM定义了线程工作内存和主内存之间的抽象关系,线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存。
- 它如何学习