【来自B站视频:https://www.bilibili.com/video/BV1BJ41177cp?p=66】
【JVM学习路径:上面这张图就是的】
【JVM虚拟机规范各种参数官方手册:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html#BGBCIEFC】
【初始堆大小和最大堆大小建议设置成一样的:防止堆内存扩容和缩容带来的额外的系统开销。】
package com.atguigu.java;
import java.util.ArrayList;
import java.util.Random;
//-Xms600m -Xmx600m
public class OOMTest {
public static void main(String[] args) {
ArrayList<Picture> list = new ArrayList<>();
while(true){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add(new Picture(new Random().nextInt(1024 * 1024)));
}
}
}
class Picture{
private byte[] pixels;
public Picture(int length) {
this.pixels = new byte[length];
}
}
【注意:默认情况下,JVM中 年轻代与老年代大小比例是 1 : 2,一般情况下,不会修改这个比例。
但是如果,声名周期很长的对象有很多,可以适当增大老年代空间大小。】
【注意:查看JVM中的各个参数的值。jinfo -flag NewRatio 6988、jinfo -flag SurvivorRatio 6988】
【每次执行完GC之后,谁空谁是SurvivorTo区,与之对应的是SurvivorFrom区。绝大部分的对象都是朝生夕死,90%以上。】
下面的三张图详细的描述了对象的分配过程,请细看,要会描述:
【年龄计数器:每个对象都有一个年龄计数器,这个年龄计数器代表着该对象从年轻代晋升到老年代所需要的年龄值。如果这个年龄计数器大于年龄阈值就需要晋升了。这个年龄阈值默认是15,可以通过JVM参数设置(-XX:MaxTenuringThreshold=15)。当Eden区满的时候,会触发YGC;但是当S0区或S1区满的时候,不会触发YGC。Eden区是主动GC,S0与S1区是被动GC。】
【记住下面这张图!!!】
package com.atguigu.java1;
import java.util.ArrayList;
import java.util.List;
/**
* 测试MinorGC 、 MajorGC、FullGC
* -Xms9m -Xmx9m -XX:+PrintGCDetails
*/
public class GCTest {
public static void main(String[] args) {
int i = 0;
try {
List<String> list = new ArrayList<>();
String a = "atguigu.com";
while (true) {
list.add(a);
a = a + a;
i++;
}
} catch (Throwable t) {
t.printStackTrace();
System.out.println("遍历次数为:" + i);
}
}
}
【堆空间一定都是共享的么?不一定,比如TLAB,它就是每个线程私有的一小块区域,是JVM分配内存的首选。】
package com.atguigu.java1;
//测试 -XX:UseTLAB 参数是否开启的情况:默认情况是开启的
public class TLABArgsTest {
public static void main(String[] args) {
System.out.println("我只是来打个酱油~");
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
【JVM参数官网(600多个):https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html#BGBCIEFC】
【JVM参数官网:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html#BGBCIEFC】
【如果新生代空间设置过小,那么好多对象还没来及被回收就晋升到老年代去了,导致YoungGC意义就不大了。】
空间分配担保 参数的解释如下:(JDK7之后不再受人为设置影响了)
package com.atguigu.java2;
/**
* 栈上分配测试
* -Xmx1G -Xms1G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails
* 默认情况下是开启逃逸分析的:-Xmx1G -Xms1G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails
*/
public class StackAllocation {
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
alloc();
}
// 查看执行时间
long end = System.currentTimeMillis();
System.out.println("花费的时间为: " + (end - start) + " ms");
// 为了方便查看堆内存中对象个数,线程sleep
try {
Thread.sleep(1000000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
private static void alloc() {
User user = new User();//未发生逃逸
}
static class User { }
}
同步省略测试:
标量替换测试:
【-XX:+UseTLAB -XX :+EliminateAllocations -XX:+DoEscapeAnalysis -XX:+PrintGCDetails】
package com.atguigu.java2;
/**
* 标量替换测试:标量替换功能默认是开启的
* -Xmx100m -Xms100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations
* -Xmx100m -Xms100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations
*/
public class ScalarReplace {
public static class User {
public int id;
public String name;
}
public static void alloc() {
User u = new User();//未发生逃逸
u.id = 5;
u.name = "www.atguigu.com";
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
alloc();
}
long end = System.currentTimeMillis();
System.out.println("花费的时间为: " + (end - start) + " ms");
}
}
/*
class Customer{
String name; //标量
int id; //标量
Account acct; //聚合量
}
class Account{
double balance;
}
*/
【标量替换已经应用了,但是栈上分配并没有在JVM中应用】
【注意1:虚拟机栈和本地方法栈,会出现异常,但不会出现GC。程序计数器,不会出现异常,也不会出现GC。】
【注意2:堆和元空间,会出现异常,也会出现GC。】
【JVM中方法区描述:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.4】
【随着java版本的升级,内存结构基本上变化很小了,但是GC是有些变化的。】
【类的元数据(也就是类的结构信息),即 Class文件是存储在方法区中的。】
【方法区类似于接口,不同的JVM对它有不同的实现。HotSpot JVM 实现时,把方法区和堆区分开,并给它一个别名,叫非堆。同时,不同的java版本,对它也有不同的落地实现,JDK7及之前,落地实现叫永久代;JDK8及之后,落地实现叫元空间。】
【JVM参数列表:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html#BGBCIEFC】
反编译命令:【javap -v JVM.class】【javap -v -p JVM.class】【javap -v -p JVM.class > test.txt】
package com.atguigu.java;
import java.io.Serializable;
//测试方法区的内部构成
public class MethodInnerStrucTest extends Object implements Comparable<String>,Serializable {
//属性
public int num = 10;
private static String str = "测试方法的内部结构";
//构造器
//方法
public void test1(){
int count = 20;
System.out.println("count = " + count);
}
public static int test2(int cal){
int result = 0;
try {
int value = 30;
result = value / cal;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
@Override
public int compareTo(String o) {
return 0;
}
}
package com.atguigu.java1;
public class MethodAreaDemo {
public static void main(String[] args) {
int x = 500;
int y = 100;
int a = x / y;
int b = 50;
System.out.println(a + b);
}
}
【字节码反编译:javap -v -p MethodAreaDemo.class】 反编译之后,结果如下:
Classfile /D:/workspace_idea5/JVMDemo/out/production/chapter09/com/atguigu/java1/MethodAreaDemo.class
Last modified 2020-4-23; size 640 bytes
MD5 checksum a2e8a0e034e2dd986b45d36a3401a63b
Compiled from "MethodAreaDemo.java"
public class com.atguigu.java1.MethodAreaDemo
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#24 // java/lang/Object."<init>":()V
#2 = Fieldref #25.#26 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Methodref #27.#28 // java/io/PrintStream.println:(I)V
#4 = Class #29 // com/atguigu/java1/MethodAreaDemo
#5 = Class #30 // java/lang/Object
#6 = Utf8 <init>
#7 = Utf8 ()V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 LocalVariableTable
#11 = Utf8 this
#12 = Utf8 Lcom/atguigu/java1/MethodAreaDemo;
#13 = Utf8 main
#14 = Utf8 ([Ljava/lang/String;)V
#15 = Utf8 args
#16 = Utf8 [Ljava/lang/String;
#17 = Utf8 x
#18 = Utf8 I
#19 = Utf8 y
#20 = Utf8 a
#21 = Utf8 b
#22 = Utf8 SourceFile
#23 = Utf8 MethodAreaDemo.java
#24 = NameAndType #6:#7 // "<init>":()V
#25 = Class #31 // java/lang/System
#26 = NameAndType #32:#33 // out:Ljava/io/PrintStream;
#27 = Class #34 // java/io/PrintStream
#28 = NameAndType #35:#36 // println:(I)V
#29 = Utf8 com/atguigu/java1/MethodAreaDemo
#30 = Utf8 java/lang/Object
#31 = Utf8 java/lang/System
#32 = Utf8 out
#33 = Utf8 Ljava/io/PrintStream;
#34 = Utf8 java/io/PrintStream
#35 = Utf8 println
#36 = Utf8 (I)V
{
public com.atguigu.java1.MethodAreaDemo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/atguigu/java1/MethodAreaDemo;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=5, args_size=1
0: sipush 500
3: istore_1
4: bipush 100
6: istore_2
7: iload_1
8: iload_2
9: idiv
10: istore_3
11: bipush 50
13: istore 4
15: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
18: iload_3
19: iload 4
21: iadd
22: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
25: return
LineNumberTable:
line 9: 0
line 10: 4
line 11: 7
line 12: 11
line 13: 15
line 14: 25
LocalVariableTable:
Start Length Slot Name Signature
0 26 0 args [Ljava/lang/String;
4 22 1 x I
7 19 2 y I
11 15 3 a I
15 11 4 b I
}
SourceFile: "MethodAreaDemo.java"
【istore_3 指令将 5 押入操作数栈】
【站点:http://openjdk.java.net/jeps/122】不断的去问什么?才能不断加深自身技术!
【前面说了,在JDK6以及之前,都是使用永久代来存储类元信息/静态变量/字符串常量池等,在JDK7的时候就有了去永久代的想法,但是直到JDK8的时候才真正去除,并用元空间来代替永久代的功能。要注意,JDK7的时候有了这个想法,它不能什么也不做啊,所以它也做了一些动作,比如,把静态变量和字符串常量池从永久代移到了堆空间,并一直坚持下去,直到JDK8以及之后,都保存在堆空间。那么,为什么要把静态变量(名)和字符串常量池移到堆空间呢?请看下面的截图解释。【StringTable:就是字符串常量池、或者叫做字符串字面量】】
arr这个是对象引用,JDK6及之前存储在永久代,JDK7及之后存储在堆空间。
new byte[1024*1024*100] ,这个对象不管是JDK几都是存储在堆空间。
【JVM规范:https://docs.oracle.com/javase/specs/jvms/se8/html/index.html】
【对象的内存布局=对象头+实例数据+对齐填充】
一个对象内存布局的举例如下:
【Java虚拟机规范中并没有说要使用哪一种对象访问方式。但是,HotSpot虚拟机使用的是直接指针方式!优点:节省空间(不用在堆空间单独开辟一块内存去维护句柄)、速度快(直接通过局部变量表中对象引用来获取对象数据,不用句柄做中间媒介了)。缺点:GC的时候需要移动对象,一旦对象发生了移动,它的内存地址就发生变化了,所以对象的对象头里面维护的对象地址也需要跟着改变(也即对象头不稳定)。另外:句柄池方式的话,对象的对象头是稳定的,因为它只需要修改句柄池就行了,局部变量表中对象对应的句柄引用是不用修改的。】
JDK8及其之后,元空间使用的就是直接内存(也叫做本地内存,直接向系统申请的内存)。
【IO:阻塞式传输。NIO:非阻塞式传输。】
【一开始分配的是1G本地内存,之后就释放了。】
【非直接缓冲区的方式:存在同一份数据从用户态向内核态拷贝的过程,导致数据存储了两份,浪费空间,效率低。】
【这里可以学习下netty:https://www.bilibili.com/video/BV1DJ411m7NR?from=search&seid=11346077094614353796】
【方法区(元空间),也存在异常和GC,这个GC是指FullGC过程,不过回收垃圾的时候过程很艰辛。】
【OOM:Heap Space。OOM:Meta Space。OOM:Direct Buffer Memory。OOM:Perm Space。】
执行引擎是非常重要的,下面开始你的表演吧。。。
【前端编译(编译java代码生成字节码)+后端编译(执行引擎中的JIT即时编译器做的热点代码编译)】
同声传译,牛!
【 从程序源码到抽象语法树,是编译javac的过程。java是半解释型半编译型语言:半解释型,就从程序源码到解释执行这一个过程。半编译型,就是从程序源码到目标代码这一过程。橙色部分和JVM无关,绿色和蓝色是JVM要考虑的事情。】
【java语言,有两种代码执行方式,一种是解释型执行过程,一种是JIT编译型执行过程,两者是相互结合起来的。java的执行速度发展到今天,可以说是已经不慢了,可以和C/C++媲美了。】
【机器码 <—— 机器指令(指令集) <—— 汇编语言 <—— 高级语言】
【机器码 <—— 机器指令(指令集) <—— 汇编语言 <—— 高级语言】
【字节码文件,是一种中间状态。执行引擎,专门翻译字节码文件为机器指令,交给CPU执行。】
【C、C++:编译过程 ——> 汇编过程】
【 从程序源码到抽象语法树,是编译javac的过程。java是半解释型半编译型语言:半解释型,就从程序源码到解释执行这一个过程。半编译型,就是从程序源码到目标代码这一过程。橙色部分和JVM无关,绿色和蓝色是JVM要考虑的事情。橙色部分是前端编译器的过程,蓝色部分是JIT编译器的过程。】
【冷机状态:机器刚刚启动之后的状态。热机状态:机器已经启动并且稳定运行很长时间了。注意:机器在热机状态下可以承受的负载要高于冷机状态。如果以热机状态的流量进行切流,有可能会使处于冷机状态的服务器因无法一下子承载这么多的流量而假死。】
【JVM监视命令:jconsole、jvisualvm、jps、jinfo、jstat】
【JIT编译器啥时候工作?见下图中的“如何选择”。热点代码探测,方法调用计数器,回边计数器。】
package com.atguigu.java;
//测试解释器模式和JIT编译模式
//-Xint:6520ms,纯解释器模式。-Xcomp:950ms,纯编译器模式。-Xmixed:936ms,解释器与编译器的混合模式
public class IntCompTest {
public static void main(String[] args) {
long start = System.currentTimeMillis();
testPrimeNumber(1000000);
long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start));
}
public static void testPrimeNumber(int count){
for (int i = 0; i < count; i++) {
//计算100以内的质数
label:for(int j = 2;j <= 100;j++){
for(int k = 2;k <= Math.sqrt(j);k++){
if(j % k == 0){
continue label;
}
}
//System.out.println(j);
}
}
}
}
【JVM参数(client/server):https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html#BGBCIEFC】