Java Virtual Machine - java 程序的运行环境(java 二进制字节码的运行环境)
Program Counter Register 程序计数器(寄存器)
二进制字节码 jvm指令 java源代码 0: getstatic #20 // PrintStream out = System.out; 3: astore_1 // -- 4: aload_1 // out.println(1); 5: iconst_1 // -- 6: invokevirtual #26 // -- 9: aload_1 // out.println(2); 10: iconst_2 // -- 11: invokevirtual #26 // -- 14: aload_1 // out.println(3); 15: iconst_3 // -- 16: invokevirtual #26 // -- 19: aload_1 // out.println(4); 20: iconst_4 // -- 21: invokevirtual #26 // -- 24: aload_1 // out.println(5); 25: iconst_5 // -- 26: invokevirtual #26 // -- 29: return
拿到指令 --> 解释器转为机器码 --> 机器码给CPU
作用:是记住下一条jvm指令的执行地址
特点:
假如现在有一本书,有好几个同学都想看,我们采取这样的策略让所有同学都能看到:每个人看一天,不管看没看完都要交给下一个人看,不断循环,直到所有人看完。每个同学都有一个小卡片记录自己看到了哪里,这样下次轮到自己看的时候就能快速的接着上次看到的地方继续看。
Java虚拟机中多线程采用时间片轮转的方式实现,一个处理器(如果是多核处理器就是一个内核)同一时间只能被一个线程使用,同一时间只能执行一个线程的指令,当时间片用完,处理器就要交给别的线程使用,为了下一次轮到自己使用处理器是能够接着执行现在的指令,使用一个计数器来记录。
Java Virtual Machine Stacks (Java 虚拟机栈)
参数、局部变量、返回地址
public class Demo1_1 { public static void main(String[] args) { method1(); } private static void method1() { method2(1, 2); } private static int method2(int a, int b) { int c = a + b; return c; } }
问题辨析
java.lang.StackOverflowError
定位
top
命令定位哪个进程对cpu的占用过高ps H -eo pid,tid,%cpu | grep 进程id
(用ps命令进一步定位是哪个线程引起的cpu占用过高)jstack 进程id
本地方法栈的功能和特点类似于虚拟机栈,均具有线程隔离的特点以及都能抛出StackOverflowError和
OutOfMemoryError异常。
不同的是,本地方法栈服务的对象是JVM执行的native方法,而虚拟机栈服务的是JVM执行的java方法。
Heap 堆
通过 new 关键字,创建对象都会使用堆内存
特点
它是线程共享的,堆中对象都需要考虑线程安全的问题
有垃圾回收机制
java.lang.OutOfMemoryError: Java heap space
4.3 堆内存诊断
jps 工具
查看当前系统中有哪些 java 进程
jmap 工具
查看堆内存占用情况 jmap - heap 进程id
jconsole 工具
图形界面的,多功能的监测工具,可以连续监测
更好用的(堆转储dump):jvisualvm
1.8之前 永久代 使用堆内存
1.8之后 元空间 使用操作系统空间
1.8 以前会导致永久代内存溢出
* 演示永久代内存溢出 java.lang.OutOfMemoryError: PermGen space * -XX:MaxPermSize=8m
1.8 之后会导致元空间内存溢出
* 演示元空间内存溢出 java.lang.OutOfMemoryError: Metaspace * -XX:MaxMetaspaceSize=8m
场景
spring
mybatis
常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量
等信息
运行时常量池,常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量
池,并把里面的符号地址变为真实地址
先看几道面试题:
String s1 = "a"; String s2 = "b"; String s3 = "a" + "b"; //常量池:ab String s4 = s1 + s2; // 堆:new String("ab"); String s5 = "ab"; String s6 = s4.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 都会把串池中的对象返回 // https://zhuanlan.zhihu.com/p/55468381 intern() // 问 System.out.println(s3 == s4); // false s3是在常量池中的,s4是在堆中的 System.out.println(s3 == s5); // true 都在常量池中 System.out.println(s3 == s6); // true 都在常量池中 String x2 = new String("c") + new String("d"); String x1 = "cd"; x2.intern(); // 没有做任何操作,如果取返回值比较,则为true // 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢 // true,在常量cd进入常量池之前创建cd常量 // 1.6 的话,则会复制一份放入常量池,x2 == x1 false System.out.println(x1 == x2); // false
常量池中的字符串仅是符号,第一次用到时才变为对象
利用串池的机制,来避免重复创建字符串对象
字符串变量拼接的原理是 StringBuilder (1.8)
字符串常量拼接的原理是编译期优化
可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池
1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串
池中的对象返回
1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,
放入串池, 会把串池中的对象返回
* 演示 StringTable 位置 * 在jdk8下设置 -Xmx10m -XX:-UseGCOverheadLimit // 在堆空间下 * 在jdk6下设置 -XX:MaxPermSize=10m // 在永久代里面
-Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc
调整 -XX:StringTableSize=桶个数
考虑将字符串对象是否入池
操作系统的内存
Direct Memory
使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法
ByteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffer 对象,一旦
ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调
用 freeMemory 来释放直接内存
-XX:+DisableExplicitGC 禁用显式的调用System.gc();
此时需要释放直接内存时,则需要unsafe来直接管理内存