JVM虚拟机
一、字节码(Byte-Code)
1.1 如何查看字节码?
字节码是一种中间状态的二进制文件,由JVM虚拟机生成,方便跨平台执行,在我看来,这其实类似于C/C++里的汇编语言,事实上我们看到的字节码的形式,也确实类似于汇编,那么如何查看字节码呢?比如下面的这份文件,经过JVM的处理,字节码长什么样子?
//Main.java
public class Main{
public static void main(String[] args){
System.out.println("Hello World");
}
}
(1) 找到对应的
.class
文件。
(2) 反编译该.class
文件。
或者IDEA使用 jclasslib Bytecode viewer.
一、参考文章 |
---|
[1] 《查看java字节码 java查看字节码命令》 |
[2] 《Java字节码分析快速入门/字节码执行分析(一)》 |
1.2 如何理解字节码的作用?
其实字节码的目的就是为了实现Java的一次编译,到处运行,各种操作系统和硬件的特性都对应OS的JVM给屏蔽了。
二、JVM内存模型(极其重点,必须牢牢把握住)
![](https://i-blog.csdnimg.cn/blog_migrate/4ba3b8da1197dbb92bd914d9d57d8221.png)
2.1 方法区
它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
2.2 虚拟机栈
与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同
2.3 本地方法栈
本地方法栈(Native Method Stack)与虚拟机栈所发挥的作
2.4 堆
堆内存区域,在JVM启动时创建。堆也是Java GC管理的主要区域,因此很多时候也被称做“GC堆”,堆内存也是可以扩展的,通过-Xmx(堆内存初始值)和-Xms(堆内存最大值)参数进行控制。[5]
2.5 程序计数器
程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器
2.6 面试必问
Q1
:在Java中,String s = "abc"
与String s = new String("abc")
的区别是什么
根据上述知识点,
String s = "abc"
是创建在方法区Method Area中,也就是运行时常量池中的,
String s = new String("abc")
是创建在堆Heap内存区域中的。
使用字符串常量的方式创建字符串对象更高效,因为它可以共享相同字符串对象,减少内存消耗。
使用 new String() 方式创建字符串对象会创建新的对象,但在某些情况下,如需要修改字符串内容时,可能会更有用。
需要注意的是,对于字符串的拼接操作,如 String s1 = “ab” + “c”,Java 会自动优化为使用字符串常量的方式创建字符串对象,以避免不必要的对象创建。
Q2
:在Java中,String s = new String("abc")
创建了几个对象
两个。
首先,"abc"是一个字符串常量,它会被存储在字符串常量池中。当创建多个相同的字符串常量时,它们会共享同一个字符串对象,以节省内存空间。
然后,执行new String("abc")
时,会在堆上创建一个新的字符串对象。这个对象是通过调用String类的构造函数创建的,注意,new String("abc")
和常量池中的"abc"没关系。
public class StringTest {
public static void main(String[] args) {
String a1 = "abc";
String a2 = "abc";
String b1 = new String("abc");
String b2 = new String("abc");
//需要注意的是,System.identityHashCode方法返回的哈希码并不是对象在内存中的真实地址,而是对象的唯一标识。在不同的时间或不同的环境中,同一个对象可能会有不同的哈希码。因此,通过哈希码来获取对象的地址是不准确的,仅用于调试或演示目的。
System.out.println(Integer.toHexString(System.identityHashCode(a1)));
System.out.println(Integer.toHexString(System.identityHashCode(a2)));
System.out.println(Integer.toHexString(System.identityHashCode(b1)));
System.out.println(Integer.toHexString(System.identityHashCode(b2)));
// System.out.println(Integer.toHexString(System.identityHashCode(b1.value))); //debug去看b1.value的地址,和a不一样
// System.out.println(Integer.toHexString(System.identityHashCode(b2.value))); //debug去看b2.value的地址,和a不一样,但是和b1.value一样
System.out.println(a1==a2); //true
System.out.println(a1==b1); //false
System.out.println(a2==b2); //false
System.out.println(b1==b2); //false
}
}
二、参考文章 |
---|
[1] 《好文推荐:JVM之内存模型》 |
[2] 【[程序员5分钟] 白话JVM内存结构,死也忘不了】- bilibili |
[3] [《深入理解Java虚拟机 - JVM高级特性与最佳实践》第2.2节运行时数据区域] |
[4] Chapter 2. The Structure of the Java Virtual Machine - Oracle |
[5] 《Java 参数 -Xms 和 -Xmx》 - CSDN |
[6] 从String s = new String(“abc”)了解引用变量与对象 - 博客园 |
三、GC机制
简单的有引用计数与可达性算法,不同的虚拟机,会实现具有各自虚拟机特色的GC算法,就看各种虚拟机更关注哪方面内容了。
三、参考文章 |
---|
[1] 《初学Java垃圾回收机制 》- 微信公众号 |
四、JVM执行引擎
4.1 解释器与JIT(Just-In-Time)编译器
(1)解释器。解释执行
(2)JIT编译器。就是虚拟机将源代码直接编译成和本地机器平台相关的汇编语言,通过汇编生成机器代码。
一般来说,都是二者混着用。
Q1:如何让JVM只使用解释器或JIT编译器?
programmer@pc-ubuntu:~$ java -version openjdk version "1.8.0_392" OpenJDK Runtime Environment (build 1.8.0_392-8u392-ga-1~22.04-b08) OpenJDK 64-Bit Server VM (build 25.392-b08, mixed mode) # 默认。平常从来没注意这里是mixed mode
programmer@pc-ubuntu:~$ java -Xint -version openjdk version "1.8.0_392" OpenJDK Runtime Environment (build 1.8.0_392-8u392-ga-1~22.04-b08) OpenJDK 64-Bit Server VM (build 25.392-b08, interpreted mode) # interpreted
programmer@pc-ubuntu:~$ java -Xcomp -version openjdk version "1.8.0_392" OpenJDK Runtime Environment (build 1.8.0_392-8u392-ga-1~22.04-b08) OpenJDK 64-Bit Server VM (build 25.392-b08, compiled mode) # compiled
Q2:如何使用JIT编译器的Client(关注局部)与Server编译优化(关注全局)?
首先随便写个Hello World的代码,然后用Javac命令加上参数。$ javac -client HelloWorld.java $ javac -server HelloWorld.java $ javap -c HelloWorld.class # 你如果要看编译后的代码文件可以用这个命令
现在,你应该找到了一点以前车间老工人师傅调试机器的感觉了。
4.1 参考文章 |
---|
[1] 重点阅读,写的非常好 《深入理解JVM(七)一一执行引擎(解释器和JIT编译器) 》- 稀土掘金 |
[2] 《Java解释器和编译器》- 知乎 |
[3] The JIT compiler - IBM |
4.2 双亲委派和类加载器
为什么叫双亲委派,这个双体现在哪?英文是Parent Delegation Mechanism,字面意思是父委派机制,双亲委派谁翻译的?双亲体现在,子级类加载器先委派给父级类加载器,父级类加载器加载不了再派给子类。
4.2 参考文章 |
---|
[1] 《你确定你真的理解"双亲委派"了吗?!》- 博客园 |
[2] 《Java双亲委派模型:为什么要双亲委派?如何打破它?破在哪里?》- CSDN |