JVM探究
- 请你谈谈你对JVM的理解,java8虚拟机和之前的变化更新
- 什么是OOM,什么是栈溢出StackOverFlowError?怎么分析
- JVM的常用调优参数有哪些
- 内存快照如何抓取,怎么分析Dump文件
- 谈谈JVM中,类加载器的认识 rt-jar ext application
JVM的位置
JVM的体系结构
类加载器及双亲委派机制
类加载器作用:加载Class文件 new Student();
1.虚拟机自带的加载器
2.启动类(根)加载器
3.扩展类加载器 /jre/lib/ext
4.应用程序(系统类)加载器 rt.jar
双亲委派机制
委派机制的作用:
- 防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
- 保证核心.class不能被篡改。通过委托方式,不会去篡改核心.class,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。
Native
凡是带native关键字的,调用底层C的库,会进入本地方法栈,调用本地方法接口(JNI java native interface)
JNI的作用:扩展Java的使用,融合不同的编程语言为Java所用
Java在内存区域中开辟了一块标记区域,即本地方法栈,登记native方法,在最终执行的时候加载本地方法库中的方法
方法区:
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法如构造函数、接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间
静态变量、常量、类信息(构造方法 接口定义)、运行时的常量池存在方法区中
方法区:static final Class模版 常量池
但实例变量存在对内存中,和方法区无关
栈:主管程序的运行,生命周期和线程同步;线程结束,栈内存也就释放,对于栈来说,不存在垃圾回收问题
栈:8大基本类型 对象的引用 实例的方法
堆
一个JVM只有一个堆内存,堆内存的大小是可以调节的
类加载器读取了类文件后,一般会把类的实例 方法 常量 变量放到堆,保存我们所有引用类型的真实对象
堆内存中还要细分为三个区域:
- 新生区
- 老年代
- 永久区
GC垃圾回收主要在伊甸园区和养老区
JDK8之后,永久区修改为元空间
新生区 - 类:诞生和成长的地方,甚至死亡
- 伊甸园:所有的对象都是在伊甸园new
- 幸存区(0区 1区)
永久区
这个区域常驻内存 - JDK6 永久代,常量池在方法区
- JDK7 永久代,但是慢慢退化了,去永久代,常量池在堆中
- JDK8之后,无永久代,常量池在元空间
在这里插入图片描述
public class Demo01 {
public static void main(String[] args) {
long maxMemory = Runtime.getRuntime().maxMemory();
long totalMemory = Runtime.getRuntime().totalMemory();
System.out.println("max:" + (maxMemory/(double)1024/1024));
System.out.println("total:" + (totalMemory/(double)1024/1024));
}
}
//最大分配内存 maxMemory是机器内存的1/4
//初始化内存 totalMemory是maxMemory的1/16
OOM:
- 尝试扩大堆内存看结果 -Xms1024m(totalMemory) -Xmx1024m(maxMemory) -XX:+PrintGCDetails
- 分析内存,看哪个地方出现问题
max:3641.0
total:245.5
Heap
PSYoungGen(年轻代) total 76288K, used 3932K [0x000000076ab00000, 0x0000000770000000, 0x00000007c0000000)
eden space(伊甸园) 65536K, 6% used [0x000000076ab00000,0x000000076aed7240,0x000000076eb00000)
from space(幸存区) 10752K, 0% used [0x000000076f580000,0x000000076f580000,0x0000000770000000)
to space(幸存区) 10752K, 0% used [0x000000076eb00000,0x000000076eb00000,0x000000076f580000)
ParOldGen(老年代) total 175104K, used 0K [0x00000006c0000000, 0x00000006cab00000, 0x000000076ab00000)
object space 175104K, 0% used [0x00000006c0000000,0x00000006c0000000,0x00000006cab00000)
Metaspace(元空间) used 2889K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 313K, capacity 388K, committed 512K, reserved 1048576K
元空间逻辑上存在,物理不存在 年轻代76288K+老年代175104K=total245.5M
public class Demo02 {
public static void main(String[] args) {
String str = "test";
while (true) {
str = str + new Random().nextInt(88888888) + new Random().nextInt(99999999);
}
}
}
[GC (Allocation Failure) [PSYoungGen: 940K->128K(1024K)] 956K->497K(1536K), 0.0041953 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 418K->415K(1024K)] [ParOldGen: 418K->418K(512K)] 837K->834K(1536K), [Metaspace: 3600K->3600K(1056768K)], 0.0068552 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]
[Full GC (Allocation Failure) [PSYoungGen: 415K->415K(1024K)] [ParOldGen: 418K->418K(512K)] 834K->834K(1536K), [Metaspace: 3600K->3600K(1056768K)], 0.0062713 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:674)
at java.lang.StringBuilder.append(StringBuilder.java:208)
at com.wang.Demo02.main(Demo02.java:14)
OOM排障:eclipse MAT,idea Jprofiler
- 分析Dump内存文件,快速定位内存泄漏
- 获得堆中的数据
- 获得大的对象
-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
在Jprofiler查看BigObject
JVM在进行GC时,并不是对这三个区域统一回收,大部分时候,回收的是新生代
- 新生代
- 幸存区 0区1区 from to
- 老年代
GC:两种类型
- 轻GC 新生代&幸存区
- full GC 三个区域
GC的算法:标记清除法 标记整理(压缩)法 复制算法 引用计数法
引用计数法:
复制算法:
每次GC都会将伊甸园区活得对象移到幸存区中,一旦伊甸园区被GC后就会是空的
幸存区0和幸存区1 一个是from 一个是to 谁空谁是to,当两个幸存区都不是空的时候复制算法把其中一个区的对象复制到另一个区,形成一个空区
当一个对象经历了15次GC还没有被回收,进入老年代 -XX:MaxTenuringThreshold
年轻代主要使用的是复制算法(伊甸园 幸存0 幸存1)
- 好处 没有内存碎片
- 坏处 浪费了内存空间(一个幸存区)
复制算法最佳使用场景:对象存活度较低
标记清除算法:
- 优点:不需要额外的空间
- 缺点:两次扫描浪费时间,会产生内存碎片
总结:
内存效率(时间复杂度):复制算法>标记清除算法>标记压缩算法
内存整齐度:复制算法=标记压缩算法>标记清除算法
内存利用率:标记清除算法=标记清除算法>复制算法
GC:采用分代收集算法
即,年轻代使用复制算法,因为存活率低;老年代区域大,存活率高,使用标记清除+压缩算法,多次清除形成大量内存碎片之后执行一次压缩
JMM
JMM(Java Memory Model的缩写)允许编译器和缓存以数据在处理器特定的缓存(或寄存器)和主存之间移动的次序拥有重要的特权,除非程序员使用了volatile或synchronized明确请求了某些可见性的保证。
JMM是干嘛的?作用:缓存一致性协议,用于定义数据读写的规则
JMM定义了线程工作内存和主内存之间的抽象关系,线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存
解决共享对象可见行的问题:volilate