// 回过头学习了JVM,进行一次总结
JVM的结构
JVM是在硬件和操作系统支持之下的java虚拟机
类装载子系统
xxx.java 经过javac编译为xxx.class文件,此文件经过类装载子系统编译为xxx.Class。
类装载子系统共有四种。
- 启动类装载器:用来编译java运行所需的基础类模板。即jre中rt.jar包中的内容
- 扩展类装载器:用来编译java运行时需要用到的javax包中的内容
- 用户自定义类装载器:用来编译java运行时用户自定义的类的内容
类装载子系统的双亲委派机制保证了沙箱机制。即不会被用户自定义的类污染java原有的类
本地方法栈、本地方法接口
本地方法接口是指java代码中用native关键字表示的函数,没有方法体。java会调用第三方程序实现这些方法。
本地方法栈用于管理native方法的调用。
执行引擎
执行引擎把字节码转换成可以直接被JVM执行的语言
程序计数器
等价于汇编中的IP计数器,内部存放指向程序下一步代码的指针。
栈
存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中。而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中。)
方法区
存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码。其逻辑上在堆中。
至今,方法区的实现方式有为永久代或元空间两种。
jdk1.7之前,方法区实现方式为永久代,存储的是JVM启动类装载器的字节码文件。
jdk1.7时,字符串常量池从方法区移到堆中。也就是说String str=“abc”;String str=new String(“abc”);这两行等价,abc均在堆中。
jdk1.8,方法区改为元空间实现。方法区不再占用堆内存,改为占用系统内存。
这里网上会有不同观点,认为JDK1.8方法区还是在堆中。但实际经过下面的idea程序运行结果就可以进行反驳。
堆
存放所有new出来的对象。
在JDK8中:
堆中还可以划分出新生代(1/3)和老年代(2/3),元空间(逻辑上属于堆)。
新生代可以分为伊甸园(8/10)、幸存0区(1/10)、幸存1区(1/10)。
这里的逻辑举例:
- 程序new出来的100个对象都在伊甸园,然后经过jvm的minor gc进行筛选(大约删除98%),存活下来的两个进入幸存0区(又称作from)
- 程序继续new出100个新对象,伊甸园又满了,jvm将伊甸园和幸存0区(又称作from)的102个对象进行minor gc,存活下来的进入幸存1区(又称作to)
- 程序继续new出100个新对象,伊甸园又又满了,jvm将伊甸园和幸存1区(又称作from)的对象进行minor gc,存活下来的进入幸存0区(又称作to)
- 由此可见,幸存0区和幸存1区的进出是不断变化的,但都是从from到to。
- 若有对象经过15次minor gc依然存活(可通过-XX:MaxTenuringThreshold更改默认次数),则进入老年代。
- 如果老年代也满了,JVM就会执行major gc
- major gc通常会和full gc混合使用。minor gc只收集新生代垃圾,major gc只收集老年代垃圾,full gc是整个堆和方法区的垃圾收集
- 若多次major gc仍然无法保存新进老年代的对象,就会报OutOfMemoryError(OOM)
另外:- gc在处理的时候会导致用户线程的暂停(STW)。major gc和full gc所需时间是minor gc的十倍以上,所以JVM调优就是让前两种gc更少的出现。
- 由此可得平常写代码时要避免创建过多的临时的大对象,因为伊甸园剩余空间可能放不下,导致占用老年代内存或是执行无用的major gc。
- 所有线程访问唯一的堆空间,但为了避免加锁导致的性能降低,JVM默认在每个线程开启后会分配一小块线程独立的区域,称作TLAB(默认为伊甸园的1%,可通过-XX:TLABWasteTargetPercent修改)。
IDEA配置JVM
1. 验证方法区独立于堆内存之外
在Run/Debug Configurations窗口配置Application的VM options为 -Xms1024m -Xmx1024m -XX:+PrintGCDetails
添加好参数后运行程序:
JDK11测试结果如下:在控制台可以看出G1的总大小是等于堆内存的大小的。
这里可以得到,元空间逻辑上属于堆,但实际占用的是系统内存。
JDK8的测试结果如下:
2. 常用的JVM堆空间参数
-XX:+PrintFlagsInitail 查看所有参数的初始值
-XX:+PrintFlagsFinal 查看所有参数的当前值
-XX:+PrintGCDetails 打印GC处理日志
-XX:+PrintGC 打印GC简要信息
-Xms 设置初始堆空间的大小
-Xmx 设置最大堆空间大小
-Xmn 设置新生代大小
-XX:NewRatio 设置新生代与老年代的比值
-XX:SurvivorRatio 设置新生代与S0/S1的比值
-XX:MaxTenuringThreshold 设置新生代垃圾的最大年龄
-XX:HandlePromotionFailure 是否设置空间分配担保 => 在JDK6后不再使用。在minor gc之前,JVM会进行检查。如果老年代最大可用的连续空间大于新生代所有对象的总空间或者大于历次晋升的平均大小,就执行minor gc。其余情况改为执行full gc。