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直接读取运行,因此和其它语言编的程序相比,执行速度最快。
- 机器指令与CPU紧密相关,所以不同种类的CPU所对应的机器指令也就不同。
指令
- 由于机器码是由0和1组成的二进制序列,可读性实在太差,于是,人们发明了指令。
- 指令就是把机器码中特定的0和1序列,简化成对应的指令,一般为英文简写,如mov、inc等,可读性好。
- 由于不同的硬件平台,执行同一个操作,对应的机器码可能不同,所以不同的硬件平台的同一种指令(比如mov),对应的机器码也可能不同。
指令集
- 不同的硬件平台,各自支持的指令是有差别的。因此每个平台所支持的指令,称之为对应平台的指令集。
- 如常见的,X86指令集对应X86的架构平台,ARM指令集对应的是ARM架构的平台。
汇编语言
- 由于指令的可读性还是太差,于是人们发明了汇编语言。
- 在汇编语言中,用助记符(Mnemonics)代替机器操作指令码,用地址符(Symbol)或标号(label)代替指令或者操作数的地址。
- 在不同的硬件平台,汇编语言对应着不同的机器语言指令集,通过汇编过程转换成机器指令。由于计算机只认识指令码,所以用汇编语言编写的程序还必须翻译成机器指令码,计算机才能识别和执行。
高级语言
- 为了使计算机用户编写程序更容易些,后来就出现了各种高级计算机语言。高级语言比机器语言、汇编语言更接近人的语言。
- 当计算机执行高级语言编写的程序时,仍然需要把程序解释和编译成机器的指令码,完成这个过程的程序就叫做解释程序或者编译程序。
字节码
- 字节码是一种中间状态(中间码)的二进制代码,它比机器码更抽象,需要直译器转译后才能成为机器码。
- 字节码主要为了实现特定软件运行和软件环境,与硬件环境无关。
- 字节码的实现方式是通过编译器和虚拟机器。编译器将源码文件编译成字节码,特定平台上的虚拟机器将字节码转译为可以直接执行的指令。
作用:是记录下一条 JVM指令的执行地址行号。
特点:
是线程私有的
不会存在内存溢出——唯一不会发生的区域
Java Virtual Machine Stacks (Java 虚拟机栈)
问题解析
垃圾回收是否管理栈内存
栈内存分配越大越好吗
-Xxs
(栈大小) 设置我们的虚拟机栈的大小方法内的局部变量是否是线程安全的
**
* 局部变量的线程安全问题
*/
public class Demo1_17 {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
sb.append(4);
sb.append(5);
sb.append(6);
new Thread(()->{
m2(sb);
}).start();
}
public static void m1() {
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(2);
sb.append(3);
System.out.println(sb.toString());
//线程安全的,因为我们的sb对象只在m1这个范围内执行 也就是说只会在一个栈帧中,只会是一个线程私有
}
public static void m2(StringBuilder sb) {
sb.append(1);
sb.append(2);
sb.append(3);
System.out.println(sb.toString());
//可能是线程不安全的,因为这个sb局部变量,是一个参数,可能会被其他线程调用
//这个传递参数还必须的对象,因为在Java中我们的参数是传值操作,如果是基本类型,只会传对应的值,而不会影响本来的数据
}
public static StringBuilder m3() {
StringBuilder sb = new StringBuilder();
sb.append(1);
sb.append(2);
sb.append(3);
return sb;
//可能是返回的结果,可能被其他的线程拿到这个对象来进行修改
}
}
package cn.itcast.jvm.t1.stack;
/**
* 演示栈内存溢出 java.lang.StackOverflowError
* -Xss256k
*/
public class Demo1_2 {
private static int count;
public static void main(String[] args) {
try {
method1();
} catch (Throwable e) {
e.printStackTrace();
System.out.println(count);
}
}
private static void method1() {
count++;
method1();//通过递归来造成栈内存溢出
}
}
线程运行诊断
案例一:cpu 占用过多
解决方法:Linux 环境下运行某些程序的时候,可能导致 CPU 的占用过高,这时需要定位占用 CPU 过高的线程
为什么使用Native Method
java使用起来非常方便,然而有些层次的任务用java实现起来不容易,或者在对程序的效率很在意时,问题就来了。与java环境外交互
有时Java应用需要与Java外面的环境交互,这是本地方法存在的主要原因。你可以想想Java需要与一些底层系统,如操作系统或某些硬件交换信息时的情况。本地方法正是这样的一种交流机制:它为我们提供了一个非常简洁的接口,而且我们无需区了解Java应用之外的繁琐细节。
与操作系统交互(比如线程最后要回归于操作系统线程)
JVM支持着java语言本身和 运行库,它是java程序赖以生存的平台,它由一个解释器(解释字节码)和一些连接到本地代码的库组成。然而不管怎样,它毕竟不是一个完整的系统,它经常依赖于一些底层系统的支持。这些底层系统常常是强大的操作系统。通过使用本地方法,我们得以用java实现了jre的与底层系统的交互,甚至JVM的一部分就是用C写的。还有,如果我们要使用一些java语言本身没有提供封装的操作系统特性时,我们也需要使用本地方法。
Sun‘s Java
Sun的解释器是用C实现的,这使得它能像一些普通的C一样与外部交互。jre大部分是用java实现的,它也通过一些本地方法与外界交互。例如:类java.lang.Thread的setPriority()方法是用Java实现的,但是它实现调用的事该类里的本地方法setPriority().这个本地方法是用C实现的,并被植入JVM内部,在Windows 95的平台上,这个本地方法最终将调用Win32 setpriority()API。这是一个本地方法的具体实现由JVM直接提供,更多的情况是本地方法由外部的动态链接库(external dynamic link library)提供,然后被JVM调用。
Heap 堆
特点
-Xmx
来指定堆内存最大值 -Xms
指定堆内存的最小值。public class Demo1_5 {
public static void main(String[] args) {
int i = 0;
try {
List<String> list = new ArrayList<>();
String a = "hello";
while (true) {
list.add(a); // hello, hellohello, hellohellohellohello ...
a = a + a; // hellohellohellohello
i++;
}
} catch (Throwable e) {
e.printStackTrace();
System.out.println(i);
}
}
}
jps 工具
查看当前系统中有哪些 java 进程
jmap 工具
查看堆内存占用情况 jmap - heap 进程id
jconsole 工具
图形界面的,多功能的监测工具,可以连续监测
jvisualvm 工具
定义
JVM规范对方法区的定义
The Java Virtual Machine has a method area that is shared among all Java Virtual Machine threads. The method area is analogous to the storage area for compiled code of a conventional language or analogous to the “text” segment in an operating system process.
It stores per-class structures such as the run-time constant pool, field and method data, and the code for methods and constructors, including the special methods ( used in class and instance initialization and interface initialization.
- 重要的是方法区里存储了运行时常量池,字段,方法数据以及方法和构造方法的代码,包括特殊方法,用于类和实例初始化以及接口初始化方法
The method area is created on virtual machine start-up. Although the method area is logically part of the heap, simple implementations may choose not to either garbage collect or compact it. This specification does not mandate the location of the method area or the policies used to manage compiled code. The method area may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger method area becomes unnecessary. The memory for the method area does not need to be contiguous.
- 方法区域是在虚拟机启动时创建的。尽管方法区域在逻辑上是堆的一部分,但简单的实现可能不会选择垃圾收集或压缩它。此规范不强制指定方法区的位置或用于管理已编译代码的策略(比如Hotspot中元空间和永久代两种不同的方式对方法区的实现)。方法区域可以具有固定的大小,或者可以根据计算的需要进行扩展,并且如果不需要更大的方法区域,则可以收缩。方法区域的内存不需要是连续的!
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the method area, as well as, in the case of a varying-size method area, control over the maximum and minimum method area size.
The following exceptional condition is associated with the method area:
- If memory in the method area cannot be made available to satisfy an allocation request, the Java Virtual Machine throws an
OutOfMemoryError
.****
1、常量池:俗称静态常量池,存在于*.class文件中,就是一张表,虚拟机指令根据这张表找到要执行的类名、类方法、参数类型、字面量等信息
常量池:虚拟机指令:
2、运行时常量池:类被加载时,其常量池信息会被放入运行时常量池,并把里面的符号地址变为真实地址。
3、StringTable 串池:运行时常量池的 一部分,储存字符串常量
package cn.itcast.jvm.t1.metaspace;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;
/**
* 演示元空间内存溢出 java.lang.OutOfMemoryError: Metaspace
* 在JDK1.8的环境下演示
* -XX:MaxMetaspaceSize=8m
*/
public class Demo1_8 extends ClassLoader { // 可以用来加载类的二进制字节码
public static void main(String[] args) {
int j = 0;
try {
Demo1_8 test = new Demo1_8();
for (int i = 0; i < 10000; i++, j++) {
// ClassWriter 作用是生成类的二进制字节码
ClassWriter cw = new ClassWriter(0);
// 版本号, public, 类名, 包名, 父类, 接口
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
// 返回 byte[]
byte[] code = cw.toByteArray();
// 执行了类的加载
test.defineClass("Class" + i, code, 0, code.length); // Class 对象
}
} finally {
System.out.println(j);
}
}
}
package cn.itcast.jvm;
import com.sun.xml.internal.ws.org.objectweb.asm.ClassWriter;
import com.sun.xml.internal.ws.org.objectweb.asm.Opcodes;
/**
* 演示永久代内存溢出 java.lang.OutOfMemoryError: PermGen space
* 这是在我们的JDK1.6环境下运行
* -XX:MaxPermSize=8m
*/
public class Demo1_8_1_6 extends ClassLoader {
public static void main(String[] args) {
int j = 0;
try {
Demo1_8_1_6 test = new Demo1_8_1_6();
for (int i = 0; i < 20000; i++, j++) {
ClassWriter cw = new ClassWriter(0);
cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
byte[] code = cw.toByteArray();
test.defineClass("Class" + i, code, 0, code.length);
}
} finally {
System.out.println(j);
}
}
}
实际开发中可能出现方法区的溢出
// 二进制字节码(类基本信息,常量池,类方法定义,包含了虚拟机指令)
public class HelloWorld{
public static void main(String[] args) {
System.out.println("hello world");
}
}
我们将class文件反编译后的内容
Classfile /E:/JAVA代码/jvm/out/production/jvm/cn/itcast/jvm/t5/HelloWorld.class
Last modified 2022-11-12; size 567 bytes
MD5 checksum 8efebdac91aa496515fa1c161184e354
Compiled from "HelloWorld.java"
public class cn.itcast.jvm.t5.HelloWorld
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#20 // java/lang/Object."":()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 // cn/itcast/jvm/t5/HelloWorld
#6 = Class #27 // java/lang/Object
#7 = Utf8
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcn/itcast/jvm/t5/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 // "":()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 cn/itcast/jvm/t5/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 cn.itcast.jvm.t5.HelloWorld();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 4: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcn/itcast/jvm/t5/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 6: 0
line 7: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
}
在上面的例子中我们能看出来
String s1 = "a";
String s2 = "b";
String s3 = "a" + "b";// javac 在编译期间的优化,结果已经在编译期确定为ab
String s4 = s1 + s2;// new StringBuilder().append("a").append("b").toString() new String("ab")
String s5 = "ab";
String s6 = s4.intern();
// 问
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();
System.out.println(x1 == x2); //1.6false 1.8false
String x2 = new String("c") + new String("d");
x2.intern();
String x1 = "cd";
System.out.println(x1 == x2); //1.6false 1.8true
字符串的延迟加载
/**
* 演示字符串字面量也是【延迟】成为对象的
*/
public class TestString {
public static void main(String[] args) {
int x = args.length;
System.out.println(); // 字符串个数 2275
System.out.print("1");
System.out.print("2");
System.out.print("3");
System.out.print("4");
System.out.print("5");
System.out.print("6");
System.out.print("7");
System.out.print("8");
System.out.print("9");
System.out.print("0");
System.out.print("1"); // 字符串个数 2285
System.out.print("2");
System.out.print("3");
System.out.print("4");
System.out.print("5");
System.out.print("6");
System.out.print("7");
System.out.print("8");
System.out.print("9");
System.out.print("0");
System.out.print(x); // 字符串个数
}
}
StringTable 位置
StringTable的垃圾回收
-Xmx10m 指定堆内存大小
-XX:+PrintStringTableStatistics 打印字符串常量池信息
-XX:+PrintGCDetails
-verbose:gc 打印 gc 的次数,耗费时间等信息
StringTable 性能调优
StringTable的底层是一个哈希表
Direct Memory
private static void io() {
long start = System.nanoTime();
try (FileInputStream from = new FileInputStream(FROM);
FileOutputStream to = new FileOutputStream(TO);
) {
byte[] buf = new byte[_1Mb];//Java的缓冲区
while (true) {
int len = from.read(buf);
if (len == -1) {
break;
}
to.write(buf, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
long end = System.nanoTime();
System.out.println("io 用时:" + (end - start) / 1000_000.0);
}
private static void directBuffer() {
long start = System.nanoTime();
try (FileChannel from = new FileInputStream(FROM).getChannel();
FileChannel to = new FileOutputStream(TO).getChannel();
) {
ByteBuffer bb = ByteBuffer.allocateDirect(_1Mb);//分配的是直接缓冲区
while (true) {
int len = from.read(bb);
if (len == -1) {
break;
}
bb.flip();
to.write(bb);
bb.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
long end = System.nanoTime();
System.out.println("directBuffer 用时:" + (end - start) / 1000_000.0);
}
public class Code_06_DirectMemoryTest {
public static int _1GB = 1024 * 1024 * 1024;
public static void main(String[] args) throws IOException, NoSuchFieldException,IllegalAccessException
{
// method();
method1();
}
// 演示 直接内存 是被 unsafe 创建与回收
private static void method1() throws IOException, NoSuchFieldException, IllegalAccessException {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe)field.get(Unsafe.class);
long base = unsafe.allocateMemory(_1GB);//分配直接内存 返回值是直接内存的地址
unsafe.setMemory(base,_1GB, (byte)0);
System.in.read();
unsafe.freeMemory(base);
System.in.read();
}
// 演示 直接内存被 释放
private static void method() throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1GB);
System.out.println("分配完毕");
System.in.read();
System.out.println("开始释放");
byteBuffer = null;
System.gc(); // 手动 gc
System.in.read();
}
}
直接内存的回收不是通过 JVM 的垃圾回收来释放的,而是通过unsafe.freeMemory 来手动释放。
第一步:allocateDirect 的实现
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
底层是创建了一个 DirectByteBuffer 对象。
第二步:DirectByteBuffer 类
DirectByteBuffer(int cap) { // package-private
super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
Bits.reserveMemory(size, cap);
long base = 0;
try {
base = unsafe.allocateMemory(size); // 使用了unsafe allocateMemory来进行直接内存的申请
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
unsafe.setMemory(base, size, (byte) 0);//和allocateMemory一起使用
if (pa && (base % ps != 0)) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
// 通过虚引用,来实现直接内存的释放,this为虚引用的实际对象, 第二个参数是一个回调,实现了 runnable 接口,run 方法 中通过 unsafe 释放内存。
att = null;
}
这里调用了一个 Cleaner 的 create 方法,且后台线程还会对虚引用的对象监测,如果虚引用的实际对象(这里是 DirectByteBuffer )被回收以后,就会调用 Cleaner 的 clean 方法,来清除直接内存中占用的内存。
public void clean() {
if (remove(this)) {
try {
// 都用函数的 run 方法, 释放内存
this.thunk.run();
} catch (final Throwable var2) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
if (System.err != null) {
(new Error("Cleaner terminated abnormally", var2)).printStackTrace();
}
System.exit(1);
return null;
}
});
}
}
}
可以看到关键的一行代码, this.thunk.run(),thunk 是 Runnable 对象。run 方法就是回调 Deallocator 中的 run 方法,
public void run() {
if (address == 0) {
// Paranoia
return;
}
// 释放内存
unsafe.freeMemory(address);
address = 0;
Bits.unreserveMemory(size, capacity);
}
直接内存的回收机制总结
使用了 Unsafe 类来完成直接内存的分配回收,回收需要主动调用freeMemory 方法
ByteBuffer 的实现内部使用了 Cleaner(虚引用)来检测 ByteBuffer 。一旦ByteBuffer 被垃圾回收,那么会由 ReferenceHandler(守护线程) 来调用 Cleaner 的 clean 方法调用 freeMemory 来释放内存
注意
/**
* -XX:+DisableExplicitGC 显示的GC失效
*/
private static void method() throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1GB);
System.out.println("分配完毕");
System.in.read();
System.out.println("开始释放");
byteBuffer = null;
System.gc(); // 手动 gc 失效
System.in.read();
}
一般用 jvm 调优时,会加上下面的参数:
-XX:+DisableExplicitGC // 静止显示的 GC
意思就是禁止我们手动的 GC,比如手动 System.gc() 无效,它是一种 full gc,会回收新生代、老年代,会造成程序执行的时间比较长。所以我们就通过 unsafe 对象调用 freeMemory 的方式释放内存。