内存结构 1.程序计数器:
1.1 定义 Program Counter Register 程序计数器(寄存器)
作用是记住下一条jvm指令的执行地址
特点:
1 线程私有
2 不会存在内存溢出
1.2 作用
2.虚拟机栈:
2.1 定义 Java Virtual Machine Stacks (Java虚拟机栈)
每个线程运行时所需要的内存,称为虚拟机栈
每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
问题思考:
垃圾回收是否涉及栈内存?
不需要,每次方法调用产生栈帧内存,方法执行完成后,都会被弹出栈,被自动回收掉。
栈内存分配越大越好吗?
-Xss size: 默认1024KB。栈内存越大,线程数越少。
方法内的局部变量是否线程安全?
3.1 如果方法内局部变量没有逃离方法的作用访问,它是线程安全的
3.2 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全
2.2 栈内存溢出 StackOverflowError
栈帧过多导致栈内存溢出
栈帧过大导致栈内存溢出
2.3 线程运行诊断
CPU占用过多
程序运行很长时间没有结果
定位:
用top定位哪个进程对CPU的占用过高
ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的CPU占用过高)
jstack 进程id (可以根据线程id找到有问题的线程,10进制的进程id转成16进制,进一步定位到问题代码的源码行号)
3.本地方法栈
4.堆
4.1 定义 Heap 堆 : 通过new关键字,创建对象都会使用堆内存
特点:
线程共享,堆中对象都需要考虑线程安全的问题
有垃圾回收机制
4.2 堆内存溢出 OutofMemoryError 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public static void main(String [] args) { int i = 0 ; try { List <String > list = new ArrayList<>(); String a = "hello" ; while (true ){ list .add(a); a = a + a; i++; } }catch (Throwable throwable ){ throwable .printStackTrace(); System.out.println(i); } }
运行结果:
1 2 3 4 5 6 7 java .lang .OutOfMemoryError : Java heap space at java .util .Arrays .copyOf (Arrays .java :3332) at java .lang .AbstractStringBuilder .ensureCapacityInternal (AbstractStringBuilder .java :124) at java .lang .AbstractStringBuilder .append (AbstractStringBuilder .java :448) at java .lang .StringBuilder .append (StringBuilder .java :136) at pub .iyu .se .jvmday .day01 .TestDemo01 .main (TestDemo01 .java :20) 26
4.3 堆内存诊断
jps工具:查看当前系统中有哪些java进程
jmap工具:查看堆内存占用情况 jmap -heap 进程id
jconsole工具:图形界面,多功能的监测工具,可以连续监测
jvisualvm工具:更强大的图形界面
1 2 3 4 5 6 7 8 9 10 11 public static void main(String[] args) throws InterruptedException { System.out.println("1..." ); Thread.sleep(30000); byte[] array = new byte[1024*1024*10]; System.out.println("2..." ); Thread.sleep(30000); array = null; System.gc(); System.out.println("3..." ); Thread.sleep(1000000L); }
运行方法后,在控制台输入
显示如下结果:
1 2 3 12140 Jps14380 Launcher7644 TestDemo02
之后当输出如下内容时:
在控制台输入
这时候可以看到显示的结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Heap Usage:PS Young GenerationEden Space: capacity = 67108864 (64 .0 MB) used = 6718152 (6 .406929016113281 MB) free = 60390712 (57 .59307098388672 MB) 10 .010826587677002 % used From Space: capacity = 11010048 (10 .5 MB) used = 0 (0 .0 MB) free = 11010048 (10 .5 MB) 0 .0 % used To Space: capacity = 11010048 (10 .5 MB) used = 0 (0 .0 MB) free = 11010048 (10 .5 MB) 0 .0 % used PS Old Generation capacity = 179306496 (171 .0 MB) used = 0 (0 .0 MB) free = 179306496 (171 .0 MB) 0 .0 % used
当输出如下内容时:
在控制台输入
这时候可以看到显示的结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Heap Usage:PS Young GenerationEden Space: capacity = 67108864 (64 .0 MB) used = 17203928 (16 .406944274902344 MB) free = 49904936 (47 .593055725097656 MB) 25 .635850429534912 % used From Space: capacity = 11010048 (10 .5 MB) used = 0 (0 .0 MB) free = 11010048 (10 .5 MB) 0 .0 % used To Space: capacity = 11010048 (10 .5 MB) used = 0 (0 .0 MB) free = 11010048 (10 .5 MB) 0 .0 % used PS Old Generation capacity = 179306496 (171 .0 MB) used = 0 (0 .0 MB) free = 179306496 (171 .0 MB) 0 .0 % used
当输出如下内容时:
在控制台输入
这时候可以看到显示的结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Heap Usage:PS Young GenerationEden Space: capacity = 67108864 (64 .0 MB) used = 1342200 (1 .2800216674804688 MB) free = 65766664 (62 .71997833251953 MB) 2 .0000338554382324 % used From Space: capacity = 11010048 (10 .5 MB) used = 0 (0 .0 MB) free = 11010048 (10 .5 MB) 0 .0 % used To Space: capacity = 11010048 (10 .5 MB) used = 0 (0 .0 MB) free = 11010048 (10 .5 MB) 0 .0 % used PS Old Generation capacity = 179306496 (171 .0 MB) used = 1345272 (1 .2829513549804688 MB) free = 177961224 (169 .71704864501953 MB) 0 .7502639502809759 % used
对照上面的代码,可以看到堆中内存的占用和回收情况。
重新运行代码,并在终端上输入
这时候,会打开jconsole的图形界面,观察一段时间,可以看到如下的结果:
问题:垃圾回收后,内存占用仍然很高
5.方法区
5.1 定义
参考资料:JVM规范-方法区定义
5.2 组成
5.3 方法区内存溢出 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class TestDemo03 extends ClassLoader { public static void main(String [] args) { int j = 0 ; try { TestDemo03 testDemo03 = new TestDemo03 (); for (int i = 0 ; i < 10000 ; i++,j++) { ClassWriter classWriter = new ClassWriter (0 ); classWriter.visit(Opcodes .V1_8 ,Opcodes .ACC_PUBLIC ,"Class" +i,null ,"java/lang/Object" ,null ); byte[] code = classWriter.toByteArray(); testDemo03.defineClass("Class" +i,code,0 ,code.length); } }catch (Exception e){ e.printStackTrace(); }finally { System .out.println(j); } } }
java8以前会导致永久代内存溢出
演示永久代内存溢出java.lang.OutofMemoryError: PermGen space
java8以后会导致元空间内存溢出
由于java8后元空间在物理内存中,这里演示元空间内存溢出java.lang.OutofMemoryError: Metaspace
1 -XX:MaxMetaspaceSize =10m
结果如下:
1 2 3 4 5 6 3331 Exception in thread "main " java .lang .OutOfMemoryError : Compressed class space at java .lang .ClassLoader .defineClass1 (Native Method ) at java .lang .ClassLoader .defineClass (ClassLoader .java :756) at java .lang .ClassLoader .defineClass (ClassLoader .java :635) at pub .iyu .se .jvmday .day01 .TestDemo03 .main (TestDemo03 .java :25)
实际场景 动态产生class并加载的场景很多:spring,mybatis…
cglib
5.4 运行时常量池
常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名,方法名,参数类型,字面量等信息
运行时常量池,常量池是*.class文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
1 2 3 4 5 6 public class TestDemo04 { public static void main (String[] args ) { System.out .println("hello niko" ); } }
使用javap指令(javap能对给定的class文件提供的字节代码进行反编译)
1 javap -v TestDemo04 .class
输出结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 Classfile /E:/TestJava/target/classes/pub/iyu/se/jvmday/day01/TestDemo04.class Last modified 2020 -11 -02 ; size 610 bytes MD5 checksum 98 f958bafd4c5329e4a8734cac14cf8a Compiled from "TestDemo04.java" public class pub.iyu.se.jvmday.day01.TestDemo04 minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #6.#21 #2 = Fieldref #22.#23 #3 = String #24 #4 = Methodref #25.#26 #5 = Class #27 #6 = Class #28 #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 Lpub/iyu/se/jvmday/day01/TestDemo04; #14 = Utf8 main #15 = Utf8 ([ Ljava/lang/String ;)V #16 = Utf8 args #17 = Utf8 [ Ljava/lang/String ; #18 = Utf8 MethodParameters #19 = Utf8 SourceFile #20 = Utf8 TestDemo04.java #21 = NameAndType #7:#8 #22 = Class #29 #23 = NameAndType #30:#31 #24 = Utf8 hello niko #25 = Class #32 #26 = NameAndType #33:#34 #27 = Utf8 pub/iyu/se/jvmday/day01/TestDemo04 #28 = Utf8 java/lang/Object #29 = Utf8 java/lang/System #30 = Utf8 out #31 = Utf8 Ljava/io/PrintStream; #32 = Utf8 java/io/PrintStream #33 = Utf8 println #34 = Utf8 (Ljava/lang/String ;)V { public pub.iyu.se.jvmday.day01.TestDemo04(); descriptor: ()V flags: ACC_PUBLIC Code: stack =1 , locals=1 , args_size=1 0 : aload_0 1 : invokespecial #1 4 : return LineNumberTable: line 11 : 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lpub/iyu/se/jvmday/day01/TestDemo04; 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 3 : ldc #3 5 : invokevirtual #4 8 : return LineNumberTable: line 13 : 0 line 14 : 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [ Ljava/lang/String ; MethodParameters: Name Flags args } SourceFile: "TestDemo04.java"
5.5 String Table 思考如下代码的打印结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 String s1 = "a" ;String s2 = "b" ;String s3 = "a" + "b" ;String s4 = s1 + s2 ;String s5 = "ab" ;String s6 = s4 .intern();System .out.println(s3 == s4 );System .out.println(s3 == s5 );System .out.println(s3 == s6 );String x2 = new String("c" ) + new String("d" );String x1 = "cd" ;x2 .intern();System .out.println(x1 == x2 );
运行结果如下:
1 String Table 特性
常量池中的字符串仅是符号,第一次用到时才变为对象
利用串池的机制,来避免重复创建字符串对象
字符串变量拼接的原理是StringBuilder(1.8)
字符串常量拼接的原理是编译期优化
可以使用intern方法,主动将串池中还没有的字符串对象放入串池
java8将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,会把串池中的对象返回
java6将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份放入串池,会把串池中的对象返回
2 String Table 位置
java6 String Table在永久代PermGen中
java8 String Table在堆Heap中
3 String Table 垃圾回收 4 String Table 性能调优
调整 -XX:StringTableSize=桶个数
考虑将字符串对象是否入池(串池)
6.直接内存 6.1 定义 Direct Memory 属于操作系统内存
常见于NIO操作时,用于数据缓冲区
分配回收成本较高,但读写性能高
不受JVM内存回收管理
6.2 分配和回收原理
使用了Unsafe对象完成直接内存的分配回收,并且回收需要主动调用freeMemory方法
ByteBuffer的实现类内部,使用了Cleaner(虚引用)来监测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就会由ReferenceHandler线程通过Cleaner的clean方法调用freeMemory来释放直接内存
评论加载中