JVM内存结构

内存结构

在这里插入图片描述

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来释放直接内存。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值