/**
* 演示栈内存溢出 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();
}
}
默认不调参数
调小栈内存-Xss256k
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-npiu0y9q-1681392343049)(null)]
栈帧内存大于256k
用top定位哪个进程对cpu的占用过高
ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
jstack 进程id 可以根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号
Heap 堆 通过 new 关键字,
创建对象都会使用堆内存
特点:
它是线程共享的,堆中对象都需要考虑线程安全的问题
有垃圾回收机制
如何解决垃圾回收后内存占用高的问题
jvisualvm命令
点击线程Dump
Java 虚拟机的结构 (oracle.com)
Java 虚拟机具有 在所有 Java 虚拟机之间共享的方法区域 线程。方法区域类似于已编译的存储区域 传统语言的代码或类似于“文本”段的代码 操作系统进程。它存储每个类结构,如 运行时常量池、字段和方法数据以及 方法和构造函数,包括特殊方法 用于类和实例初始化 和接口初始化。
创建方法区域 在虚拟机启动时。虽然方法区域在逻辑上是 作为堆的一部分,简单的实现可以选择不 垃圾收集或压缩它。这 规范不要求方法区域的位置或 用于管理已编译代码的策略。方法区域可以是 固定大小或可根据计算需要进行扩展,并且可以 如果不需要更大的方法区域,则收缩。记忆 对于方法区域不需要是连续的。
Java 虚拟机实现可以为程序员或 用户控制方法区域的初始大小,以及, 对于不同尺寸的方法区域,控制最大值 和最小方法区域大小。
以下特殊 条件与方法区域相关联:
OutOfMemoryError
/**
* 演示元空间内存溢出 java.lang.OutOfMemoryError: Metaspace
* -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);
}
}
}
-XX:MaxMetaspaceSize=8m设置元空间参数
JDK1.8 元空间溢出 (元空间使用系统内存)
JDK1.6 永久代溢出
常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量 等信息
运行时常量池,常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量 池,并把里面的符号地址变为真实地址
生成*.class文件后使用javap -v *.class命令反编译二进制文件
Classfile /F:/jvm/jvm/jvm/out/production/jvm/cn/itcast/jvm/t5/Helloworld.class
Last modified 2023-4-2; 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 //常量池
{ //方法定义
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:
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;
}
SourceFile: "HelloWorld.java"
常量池中的字符串仅是符号,第一次用到时才变为对象
利用串池的机制,来避免重复创建字符串对象
字符串变量拼接的原理是 StringBuilder (1.8)
字符串常量拼接的原理是编译期优化
可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池
1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串 池中的对象返回
1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份, 放入串池, 会把串池中的对象返回
// StringTable [ "a", "b" ,"ab" ] hashtable 结构,不能扩容
public class Demo1_22 {
// 常量池中的信息,都会被加载到运行时常量池中, 这时 a b ab 都是常量池中的符号,还没有变为 java 字符串对象
// ldc #2 会把 a 符号变为 "a" 字符串对象
// ldc #3 会把 b 符号变为 "b" 字符串对象
// ldc #4 会把 ab 符号变为 "ab" 字符串对象
public static void main(String[] args) {
String s1 = "a"; // 懒惰的
String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString() new String("ab")
String s5 = "a" + "b"; // javac 在编译期间的优化,结果已经在编译期确定为ab
System.out.println(s3 == s5);
}
}
输出:true
/**
* 演示字符串字面量也是【延迟】成为对象的
*/
public class TestString {
public static void main(String[] args) {
int x = args.length;
System.out.println(); // 字符串个数 2273
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"); // 字符串个数 2283
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); // 字符串个数
}
}
debug模式下
串池中的数目为2273 ,我们单步执行到第二个 System.out.print(“1”);
串池中的数目为2283,因此我们得出结论,StringTable是见一个爱一个,见一个加载一个。这到底对不对?
我们再执行一轮相同的到 System.out.print(x);
串池中的数目依旧为2283,因此StringTable 加载字符串是不同的、池子没有的字符串才加载
可以使用 inter 方法,主动将串池中还没有的字符串对象放入串池
intern() 方法返回字符串对象的规范化表示形式。
String s = new String("a") + new String("b");
上面代码会在串池中创建[“a”,“b”,“ab”] ,堆 new String(“a”) new String(“b”) new String(“ab”)
String s = new String("a") + new String("b");
// 堆 new String("a") new String("b") new String("ab")
String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
System.out.println( s2 == "ab");
输出:true
修改代码
public static void main(String[] args) {
String x = "ab";
String s = new String("a") + new String("b");
// 堆 new String("a") new String("b") new String("ab")
String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
System.out.println( s2 == x);
System.out.println( s == x );
}
输出:
true
false
S2执行intern()方法发现串池有ab,就不放入了,并且返回串池值ab,对象是同一个。
String x = “ab”;
String s = new String(“a”) + new String(“b”);
// 堆 new String("a") new String("b") new String("ab")
String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
System.out.println( s2 == x);
System.out.println( s == x );
}
输出:
true
false
S2执行intern()方法发现串池有ab,就不放入了,并且返回串池值ab,对象是同一个。
S没有执行方intern()法,但是两个String在不同串池,相比较时发现串池有一样 的放不进去,就原路返回,对象不一样。
持续更新中...