搁置争议 系统性学一下JVM
原因:刷java题目的时候有很多JVM的题目,我之前完全就是靠经验或者蒙做出来的,知其然不知其所以然,所以决定花点时间对JVM入个门
学习内容来自于黑马程序员~ 我的要求只是jvm入门而已
如图 jre是java运行环境
JDK 编译工具 开发工具包
......
常见的JVM
学习路线
内存结构
1.程序计数器
#### 1.1定义
寄存器
#### 1.2作用
有点像指定重排的东西
左侧:二进制字节码 是JVM的指令
右侧: java源代码
解释器解释JVM指令转换为机器码 交给cpu执行。
程序计数器:作用记住吓一跳jvm指令的执行地址,
特点:
线程私有的 时间片的概念 代码分块执行
是不会存在内存溢出的区
2虚拟机栈
2.1 栈:~数据结构我就不多说了 先进后出 即是线程运行时的内存空间
栈帧~栈中的元素:即是一次方法的调用 一般是方法的参数,局部变量,返回地址
每个线程运行时所需要的内存,称为虚拟机栈
每个栈由多个栈帧组成,对应着每次方法调用时所占的内存
每个线程只能有一个活动栈帧,对应着当前正在执行的方法
我的看法:在数据结构中类似于方法调用方法,最后一定是最内层的方法先被释放再依次释放资源,这就意味着先进后出的数据结构,即是栈
问题:
垃圾回收是否会设计栈内存
答:垃圾回收只是回收堆内存而不会动栈内存
栈内存分配越大越好吗?
答:虽然我们可以通过制定参数分配栈内存大小默认为1M win是根据win的虚拟内存分配的
栈分配越大反而线程数更少,假如原本一个线程1m 我的物理内存为500 原本为500个线程同时执行现在线程默认栈内存大小为2了,反而减少到250个并发线程了。变大只是可以更多的递归调用而已,不建议。
方法内的局部变量是否是线程安全的?
答:局部变量是私有的,不会造成线程不安全的情况。
共享就要考虑否则就不用考虑
如果方法内局部变量没有逃离方法的作用访问,它是线程安全的
如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全
2.2 栈内存溢出
栈帧过多导致栈内存溢出
递归调用方法,一直入栈没有出栈导致溢出
栈帧过大导致栈内存溢出
栈内存分配过大,导致内存溢出
2.3线程运行诊断
案例一:cpu占用过多
linux下top命令监听cpu的使用 发现某个java命令执行过多cpu
只能定位进程不能定位线程
所以我们停止top运行ps命令查看线程 ps H -eo pid,tid,%cpu看到所有线程的这三指标 ps H -eo pid,tid,%cpu | grep 你知道的进程号
定位到线程
用top定位哪个进程对cpu的占用过高
ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
jstack 进程id
可以根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号
案例二:程序长时间不执行
猜测:可能是死锁了
同样操作
3.本地方法栈
java不能直接调用的方法要借助c或者c++的方法之间调用cpu 比如clone方法
4.堆
4.1Heap 堆
通过 new 关键字,创建对象都会使用堆内存
特点
它是线程共享的,堆中对象都需要考虑线程安全的问题
有垃圾回收机制
不再被引用的对象就被回收空间
4.2 堆内存溢出
如果我一直产生对象,且一直被调用,那就有可能导致堆内存溢出
4.3 堆内存诊断
- jps 工具
查看当前系统中有哪些 java 进程
- jmap 工具
查看堆内存占用情况 jmap - heap 进程id
- jconsole 工具
图形界面的,多功能的监测工具,可以连续监测
案例
垃圾回收后,内存占用仍然很高
5.方法区
5.1定义
方法区在JVM中也是一个非常重要的区域,它与堆一样,是被线程共享的区域。在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。
JVM规范-方法区定义
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html
5.2 组成
5.3方法区内存溢出
1.8 以前会导致永久代内存溢出
1.8 之后会导致元空间内存溢出
演示元空间内存溢出 java.lang.OutOfMemoryError: Metaspace * -XX:MaxMetaspaceSize=8m
5.4 运行时常量池
我们来尝试一下吧 打印helloworld程序
在class下反编译class文件
javap -v HelloWorld.class
然后我们得到这个玩意儿
E:\Desktop\cccc\zaxiang\ datastructure\out\production\datastructure\Test>javap -v HelloWorld.class
Classfile /E:/Desktop/cccc/zaxiang/ datastructure/out/production/datastructure/Test/HelloWorld.class 类文件
Last modified 2020-8-22; size 543 bytes 最后修改时间
MD5 checksum 014533965b7fd12972f7dbdeb71cc18f 签名
Compiled from "HelloWorld.java"
public class Test.HelloWorld
minor version: 0
major version: 52 //内部版本
flags: ACC_PUBLIC, ACC_SUPER 访问修饰符
Constant pool: 常量池 地址和符号
#1 = Methodref #6.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #23 // HELLO WORLD
#4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #26 // Test/HelloWorld
#6 = Class #27 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 LTest/HelloWorld;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 SourceFile
#19 = Utf8 HelloWorld.java
#20 = NameAndType #7:#8 // "<init>":()V
#21 = Class #28 // java/lang/System
#22 = NameAndType #29:#30 // out:Ljava/io/PrintStream;
#23 = Utf8 HELLO WORLD
#24 = Class #31 // java/io/PrintStream
#25 = NameAndType #32:#33 // println:(Ljava/lang/String;)V
#26 = Utf8 Test/HelloWorld
#27 = Utf8 java/lang/Object
#28 = Utf8 java/lang/System
#29 = Utf8 out
#30 = Utf8 Ljava/io/PrintStream;
#31 = Utf8 java/io/PrintStream
#32 = Utf8 println
#33 = Utf8 (Ljava/lang/String;)V
{
public Test.HelloWorld();//jvm生产的无参构造方法
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 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LTest/HelloWorld;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1 *****下面是虚拟机指令
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String HELLO WORLD
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 5: 0
line 6: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
}
SourceFile: "HelloWorld.java"
5.4运行时常量池
常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量
等信息
运行时常量池,常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量
池,并把里面的符号地址变为真实地址
String s1 = "a";//懒惰的,运行到了才加载
String s2 = "b";
String s3 = "a" + "b";//先在常量池中找ab 是javac在编译期的优化
String s4 = s1 + s2;//是new出来的//先new stringBuilder().append(“a”).append("b").tostring() new string("ab")
String s5 = "ab";//在字符串池
String s6 = s4.intern(); // 问
System.out.println(s5==s4);false
System.out.println(s3==s4);false
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();
// 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢
System.out.println(x1==x2);f t
5.5 StringTable 特性
常量池中的字符串仅是符号,第一次用到时才变为对象
利用串池的机制,来避免重复创建字符串对象
字符串变量拼接的原理是 StringBuilder (1.8)
字符串常量拼接的原理是编译期优化
可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池
1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串
池中的对象返回
1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,
放入串池, 会把串池中的对象返回
5.6 StringTable 位置

5.7 StringTable 垃圾回收
常量池 哈希表结构 不允许重复 有固定大小 60013
内存空间分配失败会执行垃圾回收机制 将无用的对象回收掉
5.8 StringTable 性能调优
StringTableSize=? 哈希桶的大小可以调整
intern放到常量池中,直接防止冲突
6. 直接内存
使用allocateDirect在操作系统中分配一块直接内存
该区域os可以操作,java也可以操作,减少了缓冲区的复制操作
6.1 定义
Direct Memory
常见于 NIO 操作时,用于数据缓冲区
分配回收成本较高,但读写性能高
不受 JVM 内存回收管理
6.2 分配和回收原理
使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法
ByteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffer 对象,一旦
ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调
用 freeMemory 来释放直接内存