【校招VIP】JVM内存结构

2天前 收藏 0 评论 0 java开发

【校招VIP】JVM内存结构

转载声明:文章来源https://blog.csdn.net/weixin_47407737/article/details/122814228

文章目录
内存结构
1、程序计数器
2、虚拟机栈
2.1 定义
2.2 栈内存溢出
2.3 线程运行诊断
3、本地方法栈
4、堆
4.1 定义
4.2 堆内存溢出
4.3 堆内存诊断
5、方法区(元空间)
5.1 定义
5.2 内存溢出
5.3 运行时常量池
6、直接内存
6.1 定义


内存结构


1、程序计数器
作用:记住下一条要执行的二进制字节码指令地址(经过解释器翻译成机器码交由CPU运行)
特点:线程私有的;不会存在内存溢出。

2、虚拟机栈
2.1 定义
每个线程运行时所需要的内存,称为虚拟机栈
每个栈由多个栈帧组成,对应着每次方法调用时所占用的内存
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。

问题辨析:

1.垃圾回收是否涉及栈内存
不涉及,栈帧在每次方法执行完毕会自动排出栈,即被回收。

2.栈内存分配越大越好吗
可通过-Xss 改变栈内存分配大小;栈内存分配大了更多的是能够执行的方法更多了,那么相对的能够分配给线程的栈数量就减少了。

3.方法内的局部变量是否线程安全
其实就是看变量是共享还是私有的
    如果方法内局部变量没有逃离方法的作用访问,即私有的,则是线程安全;
    如果局部变量引用了对象,并逃离了方法的作用,需要考虑线程安全

2.2 栈内存溢出
栈帧过多导致溢出 --递归调用 StackOverFLOWError
栈帧过大导致溢出 – 数据的循环引用(A类的成员变量有B类,B类的成员变量有A类)

2.3 线程运行诊断
1、CPU占用过多:
    用top定位哪个进程对cpu的占用过高
    ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
    jstack 进程id (可以根据线程id找到有问题的线程,进一步定位到问题代码的源码行号)

2、程序运行很长时间都没得到结果 
    很可能是发生了线程死锁 deadlock,如两个线程循环等待对方释放资源
3、本地方法栈
    用c/c++语言编写的,java代码通过本地方法native间接的调用实现,如clone()、hashcode(); 查看java源码 用native修饰的;当调用native方法时,所需内存就是在本地方法栈分配。

4、堆
4.1 定义
通过new关键字,创建对象都会使用堆内存;
特点:
它是线程共享的,堆中对象都需要考虑线程安全问题
有垃圾回收机制

4.2 堆内存溢出
可通过-Xmx 改变堆内存大小
如果堆中对象没有引用就会被垃圾回收,那么如果一直产生新的对象,并且其他对象也一直被引用着,就可能会发生堆内存溢出。

//内存溢出例子
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 e){
e.printStackTrace();
System.out.println(i);
}

}

4.3 堆内存诊断
1.jps工具
    jps 命令
    查看当前系统有哪些java进程
2.jmap工具
    jmap -heap 进程id 命令
    查看堆内存占用情况
3.jconsole工具
    图形界面,多功能的检测工具,可以连续监测

案例:垃圾回收后,内存占用仍然很高
jvisualvm工具 通过堆转储 dump查看哪些对象占用问题

pubilc static void main(String[] args) throws InterruptedException{
List<Student> list = new ArrayList<>();
for(int i = 0; i < 200; i++)
list.add(new Student);

}
class Student{
private byte[] big = new byte[1024*1024];
}

5、方法区(元空间)
5.1 定义
存储每个类的构造信息,譬如运行时的常量池,字段,方法数据以及方法和构造方法的代码,包含一些在类和实例初始化和接口初始化时候使用的特殊方法。

5.2 内存溢出
演示内存溢出 通过-XX:MaxMetspaceSize=8m 改变元空间大小

public class Demo extends ClassLoader{//可以用来加载类的二进制字节码
public static void static (String[] args){
int j = 0;
try{
Demo test = new Demo();
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[] code = cw.toByteArray();
//执行了类的加载
test.defineClass("Class"+i,code,0,code.length);//class对象
}finally{
System.out.println(j);
}

}
}
}

实际场景:产生很多类
spring
mybatis


5.3 运行时常量池
反编译: javap -c *.class
常量池就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型,字面量等信息;
运行时常量池,常量池是*.class文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址。

常量池中的字符串仅是符号,第一次用到时才变为对象;
利用串池StringTable的机制,来避免重复创建字符串对象
字符串变量拼接的原理是StringBuilder(1.8)
字符串常量拼接的原理是编译优化
可以使用intern方法,主动将串池中还没有的字符串对象放入串池(1.8)

StringTable 位置:1.6时在方法区,1.8在堆内存。
调整-XX:StringTable=桶个数
考虑将字符串对象是否入池;如果有大量的字符串操作考虑将其入intern()串池,节约内存的使用

6、直接内存
6.1 定义
Direct Memory
    常见于NIO操作时,用于数据缓冲区
    分配回收成本较高,但读写性能高
    不受JVM内存回收管理



零拷贝机制



内存溢出

//idk6中方法区的实现称为永久代
//idk8 对方法区的实现称为元空间
static int _100Mb = 1024*1024*100;
public static void main(String[] args){
List<ByteBuffer> list = new ArrayList<>();
int i = 0;
try{
while(true){
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_100Mb);
list.add(byteBuffer);
i++;
}
}finally{
System.out.println(i);
}
}

释放原理:

实质上使用了unsafe对象完成直接内存的分配回收,并且回收需要主动调用freeMemory方法;
ByteBuffer的实现类内部,使用了Cleaner(虚引用)来监测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就会有ReferenceHandler线程通过Cleaner的clean方法调用freeMemory来释放直接内存。

C 0条回复 评论

帖子还没人回复快来抢沙发