在b站看的网课,留下的笔记与截图
java二进制字节码运行环境
jvm+基础类库--------jre
jre+编译工具--------jdk
java源码-----》二进制字节码--------》解释器翻译为机器语言--------》cpu来执行
记录下一条jvm指令的执行地址,
例如指令如下
1 lalalal
2 啦啦啦啦啦
1执行时,会将2放入程序计数器中,待1执行结束之后就在程序计数器中取得2进行执行,这样依次进行执行
一般是使用寄存器来实现的
每个线程需要一个栈,存放着多个栈帧,一个栈帧对应一个方法,每个方法运行时需要的内存
一个栈中可以有多个栈帧
每个线程只能有一个活动栈帧()
这里方法调用的栈可以在idea中直观看到
定位
top:定位哪一个进程对cpu占用高
ps H -eo pid,tid,%cpu | grep 进程id:进一步定位哪一个线程引起的
jstack 进程id:列出该进程的所有的线程的信息
长时间未输出结果
jstack 进程id 列出所有的线程信息,可以展示出死锁信息
本地方法不是由java编写的,因为java无法直接与计算机底层进行交互,因此需要通过本地方法来进行对底层的交互,一般本地方法是由c或c++编写的。
这些本地方法利用的就是本地方法栈
不断生成新对象,并且所有对象一直在使用,就会导致堆内存溢出
修改堆空间大小
-Xmx 8m
以下代码可以用来测试堆空间是否溢出的问题
public static void main(String[] args) {
int count=0;
String s="123";
try {
List<String> list =new LinkedList<>();
count=0;
while(true){
s=s+s;
list.add(s);
count++;
}
}
catch (Exception e){
e.printStackTrace();
System.out.println(count);
}
}
启示
服务器内存越跑越小,可能是因为有一些内存未被来得及回收
jps工具:查看系统中有哪些java进程
jps
jmap工具:查看堆内存占用情况
jmap -heap 进程id
测试代码
public void testThread(){
try {
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(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
jconsole工具:有ui的,多功能的检测工具
jconsole
存放方法,构造器,成员属性之类的数据
方法区在虚拟机启动时就创建,逻辑上是堆的组成部分,但不同的厂商不一定按照这个实现
常量池:
就是一张常量表,虚拟机指令根据这张常量表找到要执行的类名和方法名,参数类型,字面量等信息
运行时常量池:
常量池是*.class中的,当该类被加载,他的常量池信息就会放入运行时常量池中,并且把里面的符号地址变为真实地址
javap -v Main.class
这里编译的class文件在out文件夹下
如下图就是常量池
最开始时常量池中是没有数据的,是在一步步加载中填入的,是一种懒加载机制
运行常量池(constant pool)中存放的仅仅是符号,而并非对象,串池(StringTable)中存放的则是字符串对象,作用就是防止创建重复的字符对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LaIaj5oj-1642754222930)(jvm/image-20220118165905767.png)]
-Xmx16m -XX: +PrintStringTablestatistics -XX: +PrintGCDetails -verbose:gc
-Xmx16m :设置堆的大小
-XX: +PrintStringTablestatistics :打印串池中的对象信息
-XX: +PrintGCDetails -verbose:gc : 若存在垃圾回收,则进行打印信息
-XX: StringTableSize=200000 : 因为串池的结构是数组加链表这种方式,数组中的一个关键字称为一个桶,这里就是设计桶的数量,桶的数量越大性能越好,但相对的占用空间就可能过大,造成资源浪费
操作系统内存
使用ByteBuffer实际上就是通过直接内存进行读取
传统io操作
因为java无法直接访问系统资源,因此需要再建立一个java缓冲区,整个过程就是:本地文件==》系统缓存==》java缓存==》使用
直接内存的io方式
此时文件直接放入直接内存缓冲区中,java可以直接读取,减少了一层缓冲区,从而使得速度得到提升
因为DM不受java垃圾机制管理,因此可能会出现内存溢出问题
测试代码
通过代码来申请直接内存的大小,这里直接内存不受jvm管理,因此需要在任务管理器里查看
ByteBuffer byteBuffer=ByteBuffer.allocateDirect(1024*1024*1024);
System.out.println("try");
System.in.read();
System.out.println("end");
直接内存的回收是通过unsafe对象来进行回收的
System.gc() //显式的垃圾回收
关闭显示垃圾回收机制,即System.gc()无效
-XX:+DisableExplicitGC
即有一个引用该对象,则计数器加一,为0则释放,
弊端
循环引用:即A引用B,B也引用A,没有其他引用他们,但是他们互相引用,都无法释放,就会导致内存泄漏
根对象:肯定不可以当作垃圾回收的对象
如果一个对象没有被根对象引用,就可以回收
解析
扫描堆中的对象,看是否能够沿着GC Root对象为起点的引用链找到该对象,找不到,表示可以回收
抓取当前堆使用的快照
jmap -dump :format=b,live,file=1.bin 21384
-dump ==》存储
format=b ==》存储二进制文件
live ==》只记录那些未被垃圾回收的内容
file=1.bin 设置存储文件
21384 进程id(jps获取活动的java的进程id)
mat查看gc root对象
System class 系统对象
Busy Monitor 加锁的对象
Thread 活动线程中的对象,局部对象所引用的对象可左gcroot,同时参数中对象也是可以作为gcroot对象
可以作为GC Root的对象
System class 系统对象
Busy Monitor 加锁的对象
Thread 活动线程中的对象,局部对象所引用的对象可左gcroot,同时参数中对象也是可以作为gcroot对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fXQDinnG-1642754222939)(jvm/image-20220119162157683.png)]
例如new出来的就是强引用
特点
特点
应用场景
强引用下导致堆空间溢出
/**
* 强引用会导致堆空间不够用
*/
int _1M=1024*1024;
List<byte[]> list=new LinkedList<>();
for(int i=0;i<5;i++){
list.add(new byte[_1M*2]);
}
软引用下
在这种方式下其实就是使用软引用进行嵌套强引用,也就是SoftReference嵌套byte数组,从而达到软引用的目的,这样一旦出现堆内存不够就会进行释放软引用对象
List<SoftReference<byte[]>> list=new LinkedList<>();
for (int i = 0; i <100 ; i++) {
SoftReference softReference=new SoftReference(new byte[_1M*2]);
list.add(softReference);
}
这个过程中一旦出现了堆空间不够,就会清理软引用对象引用的对象,但是此时软引用对象还在,虽然占据内存比较小,但最好还是清理一下
使用引用队列进行处理,下方代码,关联了软引用队列,软引用关联的对象回收时,软引用对象会加入队列中,从而实现回收
这里我个人的理解就是判断这些软引用有没有引用其他对象,如果没有,则将其在队列中删除,从而将队列对软引用对象的强引用解除掉,从而实现对象的回收
/**
* 关联了软引用队列,软引用关联的对象回收时,软引用对象会加入队列中,从而实现回收
*/
ReferenceQueue<byte[]> referenceQueue=new ReferenceQueue<>();
List<SoftReference<byte[]>> list=new LinkedList<>();
for (int i = 0; i <10 ; i++) {
SoftReference softReference=new SoftReference(new byte[_1M*2],referenceQueue);
list.add(softReference);
}
Reference<? extends byte[]> poll = referenceQueue.poll();
while(poll!=null){
referenceQueue.remove();
poll=referenceQueue.poll();
}
特点
应用实例
之前的bytebuffer就是需要一个虚引用对象Cleaner,因为ByteBuffer若是在强引用引用结束之后,会对其进行回收,但是此时直接内存不由jvm管理,这就需要把虚引用对象放置在引用队列中,从而实现对直接内存的回收(虚引用对象就是Cleaner,来调用Unsafe的Free memory()来进行释放)
例如A对象重写了finalize,并且A即将被垃圾回收,会调用finalize方法,将放置一个终结器引用到队列中,会有一个优先级很低的线程会来检查队列中有无需要释放的引用,从而实现对象的回收
优点
缺点
优点
没有内存碎片
缺点
耗费时间较多,例如如果有引用对象引用就是将移动的对象,需要修改大量内容,造成浪费时间
划分成两片区域,将from中存活的对对象复制到to中,待复制结束之后就对from所有的对象进行回收,然后交换from与to的位置
优点
缺点
需要占用双倍的内存空间
三种算法都会协同工作
String a="123"
String b="55"
String c=a+b
c的赋值其实是先调用Stringbuilder的toString方法生成一个新的String对象,然后返回给c
但是如下图这样
String a="a"
String b="b"
String c="a"+"b";
String d="ab";
此时,c==d是true,因为在编译时,javac会默认认为"a"+“b"就是"ab”,因此直接调用常量池的内容就可以
itern():将字符串对象放入串池,若不存在,则放入,否则不进行放入
原来的占用内存->回收后的内存,Full GC表示垃圾回收资源太少,因此采用更加强烈的垃圾回收,即软链接垃圾回收
初次回收时会将所有的弱引用对象引用的对象回收掉,若是回收之后内存依然不够,会对软引用在进行回收