想拿高工资,想成为一名合格又优秀的java高级攻城狮,对于JVM的学习是必不可少的。
我本人找过很多课程,学过很多遍,却总是感觉学不太明白,感觉少点什么,我相信很多小伙伴会和我有一样的经历。
还好现在找到一个比较易于理解却不臃肿的视频教程,本笔记就是基于视频教程以及对视频中不易理解的部分进行多方咨询求证,力求写出一篇易于理解,帮助小伙伴们成长的教程,在这里非常感谢黑马的课程。视频:黑马程序员JVM完整教程
第一篇:JVM 完整教程(1/3):内存结构
第二篇:JVM 完整教程(2/3):垃圾回收
第三篇:JVM 完整教程(3/3):类加载与字节码技术&内存模型
Program Counter Register 程序计数器(寄存器)
java程序运行的逻辑大概是这样的,JVM虚拟就将java源码转化为JVM指令,然后解释器将JVM指令转化为机器码,机器码则可以直接运行在CPU上。程序计数器,在这里的作用就是记住下一条指令的地址,解释器会从这里取出地址并执行相应的操作。
强行解释
一个人要去做饭,那么具体怎么做需要拆解成很多详细步骤,比如买菜、洗菜、生火等等。
我们之所以会按照步骤一步步去完成,是因为我们的大脑早就提前将指令存放到了我们大脑的某一处,而存放指令的这一处就类似我们处理器中的寄存器,也就是我们所说的程序计数器,只不过程序计数器存的不是指令本身,而是指令所在的地址。
当一条指令被取出执行时,程序计数器就会存放下一条指令的地址,以此往复,来完成程序的连续运行。
程序计数器的作用就是存放下一条指令的地址。
知道了程序计数器的作用是存放下一条指令的地址,那么它有什么特点呢。多线程直接会不会乱取指令地址呢,答案是不会的因为程序计数器是线程私有的。程序计数器,保存的是当前执行的字节码的偏移地址,当执行到下一条指令的时候,改变的只是程序计数器中保存的地址,并不需要申请新的内存来保存新的指令地址。因此,永远都不可能内存溢出的
特点可以总结为两点:
Java Virtual Machine Stacks(Java虚拟机栈)
动态演示
总共三个方法,main 方法、method1方法、method2方法,其中main调用method1方法,method1又调用method2方法。所以总共又三个栈帧,最上面的那个就是正在运行的,也叫做活动栈帧。当method2调用完毕就会被释放,左下角Frames中也会消失,当全部方法被调用完毕后,左下角Frames中就没有栈帧了。
常见问题
1)垃圾回收是否涉及栈内存?
2)栈内存分配的越大越好吗?
3)方法内的局部变量是否线程安全?
java中栈内存异出的异常为: java.lang.stackOverflowError
什么情况会导致栈内存溢出?
栈帧过多或这栈帧过大都会导致栈内存溢出。
案例1:cpu占用过多
定位问题:
案例2:程序运行很长时间没有结果
一些带有 native 关键字的方法就是需要 JAVA 去调用本地的C或者C++方法,因为 JAVA 有时候没法直接和操作系统底层交互,所以需要用到本地方法栈,服务于带 native 关键字的方法。
通过new关键字创建的对象,都会使用堆内存。可以使用-Xml指定堆内存大小。
特点
java.lang.OutOfMemoryError:Java heap space(冒号前面代表内存溢出,后面代表堆空间不足导致的)。可以使用 -Xmx 来指定堆内存大小。
tip:调试代码时尽量把堆内存调小一点,这样可以尽早的暴露可能引起堆内存溢出的问题。
《Java 虚拟机规范》中明确说明:“尽管所有的方法区在逻辑上是属于堆的一部分,但一些简单的实现可能不会选择去进行垃圾收集或进行压缩。”
但对于 HotSpot JVM 而言,方法区还有一个别名叫做 Non-Heap(非堆),目的就是要和堆分开。所以,方法区看作是一块独立于 Java 堆的内存空间。
在 jdk7 及以前,习惯上把方法区称为永久代。jdk8 开始,使用元空间取代了永久代。元空间的本质和永久代类似,都是对 JVM 规范中方法区的实现。区别是:元空间不在虚拟机设置的内存中,而是使用本地内存。
jdk1.8以前,一般将方法区成为永久代,jdk1.8以后使用元空间替代方法区。不止是名字变了,其内存结构也发生了很大的变化,永久代是在虚拟机设置的内存空间中,而元空间依赖于系统的本地内存。
JDK1.6 存放在常量池中,而常量池在永久代,所以JDK1.6 StringTable存放在永久代,而从JDK1.7开始就移到了堆空间中。
为什么要放到堆中?
因为永久代的回收频率比较低,只在FullGC的时候才会被回收,FullGC只会在老年代或者永久代空间不足时才会触发。如果有大量的字符串被创建,放在永久代,由于永久代的回收频率低,会导致永久代空间不足。如果放到堆里,能够及时回收内存。
看代码,猜结果1
解析:
-Xmx10m -> 指定堆内存大小
-XX:+PrintStringTableStatistics -> 打印字符串常量池信息
-XX:+PrintGCDetails -verbose:gc -> 打印 gc 的次数,耗费时间等信息
/**
* 演示 StringTable 垃圾回收
* -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc
*/
public class Code_05_StringTableTest {
public static void main(String[] args) {
int i = 0;
try {
for(int j = 0; j < 10000; j++) {
// j = 100, j = 10000
String.valueOf(j).intern();
i++;
}
}catch (Exception e) {
e.printStackTrace();
}finally {
System.out.println(i);
}
}
}
因为StringTable是由HashTable实现的,所以可以适当增加HashTable桶的个数,来减少哈希冲突。如果系统里字符串常量特别多的话并且可能有大量重复的话,可以调大桶的个数,然后让字符串入池,来减少堆内存的使用。
-XX:StringTableSize=桶个数(最少设置为 1009 以上)