一、了解JVM
- JVM就是Java虚拟机。
- 好处:
- 一次编写,到处运行;
- 自动内存管理,垃圾回收功能;
- 数组下标越界越界检查;
- 多态。
二、学习JVM
1.程序计数器(寄存器):
- 作用:记住下一条jvm指令的执行地址;
- 特点:
(1)线程私有;
(2)不会存在内存溢出(唯一一个不会内存溢出的区域);
2.栈和栈帧:
- 栈:线程运行时需要的内存空间;
- 栈帧:每个方法运行时需要的内存(包括参数,局部变量,返回地址等);
- 一个栈由多个栈帧组成,对应着每次方法调用时所占用的内存,遵循先进后出的方法;
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。
- 注意:
(1)垃圾回收不涉及栈内存,涉及堆内存;
(2)栈内存的分配并不是越大越好;
(3)方法内的局部变量是否线程安全?
a.如果方法内局部变量没有逃离方法的作用范围,它是线程安全的;
b.如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全。
2.2栈内存溢出:
- 栈帧过多导致栈内存溢出(例如方法的递归调用,没有设置正确的终止条件);
- 栈帧多大导致栈内存溢出;
2.3线程运行诊断:
- CPU占用过高:
- 定位:用top定位哪个进程对CPU占用过高;
- ps H -eo pid, tid, &cup | grep 进程id(用ps命令进一步定位是哪个线程引起的cpu占用过高);
- jstack 进程id,可以根据线程id找到有问题的线程,进一步定位到问题代码的源码行号;
- 程序运行很长时间没有结果:
- 可能原因是线程死锁,可以用jstack 进程id定位到出问题的线程行数和原因,进行修改。
3.本地方法栈:
版权声明:本文为CSDN博主「情迷IntelliJ IDEA」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/phg739436146/article/details/108412116
4.堆:
- 通过new关键字,创建对象都会使用堆内存;
- 特点:
- 它是线程共享的,堆中对象都需要考虑线程安全的问题;
- 它有垃圾回收机制;
4.1 堆内存溢出:
- java.lang.OutOfMemoryError:Java heap space
- 在判断是否存在堆内存溢出的问题的时候,可以将堆内存相对设置的小一点。
4.2 堆内存诊断:
5.方法区:
- 定义:
版权声明:本文为CSDN博主「敏叔V587」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhuxuemin1991/article/details/103900190
5.2:方法区内存溢出:
- 1.8之前会导致永久代内存溢出:java.lang.OutOfMemoryError:PermGen space;
- 1.8之后会导致元空间内存溢出:java.lang.OutOfMemoryError:Metaspace;
6.运行时常量池:
- 二进制字节码:类的基本信息,常量池,类方法定义(包含虚拟机指令);
- 常量池:就是一张常量表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量(字符串,基本的整数,布尔类型)等信息;
- 运行时常量池:常量池是*.class文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址;
6.2 StringTable(串池):
-
public class StringTable { public static void main(String[] args) { String s1 = "a"; String s2 = "b"; String s3 = "a" + "b";//ab String s4 = s1 + s2;//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");//new String("cd") String x1 = "cd";//"cd" x2.intern(); //问,如果调换了 x1,x2的位置呢?如果是jdk1.6呢? System.out.println(x1 == x2);//false } }
- 特性:
- 常量池中的字符串仅是符号,第一次用到时才会变为对象;
- 利用串池的机制,来避免重复创建字符串对象。创建对象时是延迟(懒惰)模式;
- 字符串变量拼接的原理是StringBuilder(1.8);
- 字符串常量拼接的原理是编译期优化;
- 可以使用intern()方法,主动将串池中还没有的字符串对象放入串池。
a.1.8 将这个字符串对象尝试放入串池,如果有则不放入,如果没有则放入,并且会把串池中的对象返回;
b.1.6 将这个字符串对象尝试放入串池,如果有则不放入,如果没有会把此对象复制一份,放入串池,并且会把串池中的对象返回;
- StringTable位置:
- 1.6在永久代中,1.8在堆内存中;
- StringTable垃圾回收:
- 在内存紧张时才会触发垃圾回收;
- StringTable性能调优:
- 底层是用hash表存储数据,所以分配的内存越大,桶越多,存储的数值越分散,链表短,查找和加入数据效率更高。
- 考虑将字符串对象是否入池:如果所提供的字符串中存在大量的重复字符串对象,可以考虑将这些重复的字符串对象使用intern()方法入池,以此来节省内存的占用
- 直接内存(不属于JVM内存,属于系统内存):
- 定义:
a.常见于NIO操作,用于数据缓冲区;
b.分配回收成本较高,但读写性能高;
c.不受JVM内存回收管理(调用一个非常底层的unsafe类的freeMemory()来进行内存释放);
2.分配和回收原理:
a.使用了Unsafe对象完成直接内存的分配回收,并且回收需要主动调用freeMemory方法;
b.ByteBuffer的实现类内部,使用了Cleaner(虚引用)来监测ByteBuffer对象,一旦
ByteBuffer对象被垃圾回收,那么就会由ReferenceHandler线程通过Cleaner的clean方法调
用freeMemory来释放直接内存;
7.垃圾回收
- 如何判断对象可以回收:
- 引用计数法(java未采用):存在两个对象相互引用,但并没有第三个对象引用这两个对象的情况。
- 可达性分析算法:
a.java虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象;
b.扫描堆中的对象,看是否能够沿着GC Root对象为起点的引用链找到该对象,找到,保留;
找不到,表示可以回收;
c.哪些对象可以作为GC Root?可以用Memory Analyzer(MAT)工具进行可视化查看。
3.五种引用:
//实现表示强引用
a.强引用: 通常我们使用 new 操作符创建一个对象时所返回的引用即为强引用,只有所有GC Root对象都不通过强引用引用该对象,该对象才能被垃圾回收。
b.软引用: 若一个对象只能通过软引用到达,且没有GC Root指向,那么这个对象在内存不足时会被回收,可用于图片缓存中,内存不足时系统会自动回收不再使用的Bitmap
c.弱引用: 若一个对象只能通过弱引用到达,且没有GC Root指向,那么它就会被回收(即使内存充足),同样可用于图片缓存中,这时候只要 Bitmap 不再使用就会被回
//软弱引用在没有引用对象的时候,可以配合被放入引用队列中,从而释放掉引用所占用的内存空间
————————————————
版权声明:本文为CSDN博主「zuiziyoudexiao」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zuiziyoudexiao/article/details/89093896