jvm一之java内存

java内存模型

在这里插入图片描述

Java7之前,常量池是存放在方法区(永久代)中的。

Java7,将常量池是存放到了堆的老年代中。

Java8之后,取消了整个永久代区域,取而代之的是元空间。常量池依然存放在老年代中。

为什么程序计数器,栈和本地方法栈是线程独有的?
程序计数器记录的是下一个指令所在地址,用于恢复程序逻辑跳转,这个是跟程序绑定的,也就是某个执行进程,所以线程独有
方法栈,因为生命周期比较短,方法执行结束就销毁,所以线程执行完就over了,也就不需要存储如栈,常量池里面了.

方法区,常量池,和堆线程共享?
常量池存储的是常量,生命周期比较长
方法区记录了类的信息,接口,方法,静态变量,成员变量的信息这些信息才能确定如何执行方法,和获取字段信息,这个类不会只被一个线程获取信息,所以共享咯
堆存储的是具体的对象,只要谁具有引用,谁就可以调用.所以与线程无关.

程序计数器

当前线程所执行的字节码的行号指示器。
字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复…都依赖这个计数器完成
如果线程正在执行的是一个java方法,这个计数器记录的是正在执行的虚拟机字节码字节码指令的地址
如果正在执行的是native方法,这个计数器值为空
此内存区域是唯一一个在java虚拟机规范中没有规定任何outOfMemoryError情况的区域

java虚拟机栈

为虚拟机执行java方法(字节码)服务
线程私有,生命周期与线程相同,
虚拟机栈描述的是java方法执行的内存模型,每个方法被执行的时候都会同时创建一个栈帧,用于存储局部变量表,
操作栈,动态链接,方法出口等信息,每一个方法被调用直至执行完成的过程,
就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程

局部变量表存放了编译器可知的各种基本数据类型,boolean,byte,char,long(2个局部变量空间),double(2个局部变量空间)。。。
对象引用(reference类型)他不等于对象本身。根据不同的虚拟机实现,
他可能是一个指向对象起始地址的引用指针,
可能指向一个代表对象的句柄或者其他与此对象相关的位置和returnAddress类型(指向一条字节码指令的地址)

局部变量所需的内存空间在编译期间完成分配,当进入一个方法时,
这个方法需要在帧中分配多大的局部变量空间完全确定的。
在方法运行期间不会改变局部变量表的大小

此区域规定2种异常状态
线程请求栈深入大于虚拟机所允许的深度:StackOverflowError
如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时会抛出outofMemoryError异常

小结

因为64位变量需要2个局部变量空间,所以在并发情况下,会出现低32位为这个值,另外32位为另外一个值,想要避免这种情况,添加volatile

本地方法栈

就是调用的native修饰的方法
当调用的是本地方法时,虚拟机会保持Java栈不变,不再在线程的Java栈中压入新的帧,虚拟机只是简单地动态连接并直接调用指定的本地方法。

java堆

是java虚拟机所管理的内存中最大的一块,
所有线程共享的一块区域
虚拟机启动时创建
作用:存放对象实例,几乎所有对象实例都在这里分配内存
是垃圾收集器管理的主要区域,
如果在堆中没有内存完成实例分配,并且堆也无法在扩展,抛出outofmemoryError
可内存不连续存储

小结

对象的具体实例就存储在堆中,栈仅仅只是存储一个引用罢了,因为存储的是一个引用,所以每个对象的引用是不同的,也就导致了堆中的存储对象内存不连续.
在一些特殊情况下,堆直接存储对象和引用.

方法区

(HotSpot虚拟机中的永久代)
是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息,常量,静态变量,方法,字段,即时编译后的代码等数据
这个区域内存回收目标只要是针对常量池的回收和对类型的卸载。
无法满足内存分配时:outofmemoryError

JDK8之后已经废弃,采用本地内存中实现的元空间.

运行时常量池

方法区一部分,用于存放编译器生成的各种字面量和符号引用,翻译出来的直接引用
具备动态性,java语言并不要求常量一定只能在编译器产生,也就是并非预植入class文件中常量池的内容
才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,如String.intern();
无法申请内存:outofMemoryError

直接内存

不是虚拟机运行时数据区的一部分,也不是java虚拟机规范定义的内存区域
使用native函数库直接分配堆外内存,然后通过一个存储在java堆厘米那的directByteBuffer对象作为这块区域的引用进行操作,
避免在java堆和native堆中来回复制数据,提高性能

总结

使用本地方法直接申请的内存,在nio中用的就是这个玩意儿.

对象访问

Object obj=new Object();
Object obj引用存储在栈中
new Object()存储在堆中
Object类型,父类,实现接口,方法等信息存储在方法区中

java虚拟机遇到字节码new后,检测常量池是否能定位这个类的符号引用,并且检查这个类是否被加载,解析,初始化果,如果没则加载类
接着虚拟机为对象分配内存.
如果内存绝对规整,假设需要N空间大小,则指针移动N个位置,并将N个位置保存对象,指针碰撞
如果内存不是规整的,则需要维护一个列表,记录那些内存块可用,分配的时候划分足够大的空间给对象,然后更新列表中的记录.称之为"空闲列表"
具体使用哪种方式,跟垃圾回收器有关,

当并发保存对象的时候,虚拟机采用CAS+重试机制保证
另外一种方式给每个线程在堆中分配一小块内存,称之为本地线程分配缓冲(TLAB),本地内存存完了才会存储堆中需要CAS+重试
-XX:+UseTLAB设置是否启用

将对象实例初始化零值,确保实例字段不用赋初始值就可以访问,如果开启了TLAB,则随着TLAB的时候初始化

设置对象头,hashcode,元数据信息,GC分代年龄等信息
调用构造,初始化成功.

使用句柄访问

java堆中会划分一块内存作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的地址信息
优点:在对象被移动时只会改变句柄中的实例数据指针,而reference不需要修改
在这里插入图片描述

直接访问

reference中直接存储对象地址,对象地址里面指定对象数据类型
优点:速度快,节省一次指针定位的时间开销。
在这里插入图片描述

溢出

堆溢出

循环添加对象过多
对象无法被回收,会造成内存泄漏
如果对象确实没引用了,造成的内存溢出,考虑扩容堆的容量,或者合理的管理对象生命周期.

/*
 * 
 * 测试堆
 * 
 * Debug-java Application配置
 * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
 *
 * 测试方式
 * 限制java堆大小为20M,不可扩展
 *  -verbose:gc:表示输出虚拟机中GC的详细情况
 * -Xms20m -Xmx20M -XX:+HeapDumpOnOutOfMemoryError
 * -Xms20M|-Xmx20M:堆的最小值和最大值设置为一样即可避免堆自动扩展、
 * -XX:+HeapDumpOnOutOfMemoryError:虚拟机出现内存溢出时dump出当前的内存堆转储快照以便事后进行分析
 * 
 * 测试堆溢出
 * java.lang.OutOfMemoryError异常
 * 解决方案:通过Eclipse Memory Analazer对dump出来的堆转储快照进行分析,判断是否出现了内存泄漏(Memory Leak),还是内存溢出(Memory Overflow)
 * 
 * 
 */
 public class A_HeapOOM {
	static class OOMObject {
	}

	public static void main(String[] args) {

		ArrayList<OOMObject> arrayList = new ArrayList<OOMObject>();
		while (true)
			arrayList.add(new OOMObject());
	}
}

栈溢出

方法调用深度超过限制,则栈溢出,特别是递归情况,设置合理的方法调用结束条件,或者设置栈的大小
无法申请到栈内存,则内存溢出

/**
 * 占用局部变量表,测试本地方法栈溢出
 * @author WangChao
 * @create 2020/6/5 17:09
 */
public class JavaVMStackSOF2 {
    private static int stackLength = 0;

    public static void test() {
        long unused1, unused2, unused3, unused4, unused5,
                unused6, unused7, unused8, unused9, unused10,
                unused11, unused12, unused13, unused14, unused15,
                unused16, unused17, unused18, unused19, unused20,
                unused21, unused22, unused23, unused24, unused25,
                unused26, unused27, unused28, unused29, unused30,
                unused31, unused32, unused33, unused34, unused35,
                unused36, unused37, unused38, unused39, unused40,
                unused41, unused42, unused43, unused44, unused45,
                unused46, unused47, unused48, unused49, unused50,
                unused51, unused52, unused53, unused54, unused55,
                unused56, unused57, unused58, unused59, unused60,
                unused61, unused62, unused63, unused64, unused65,
                unused66, unused67, unused68, unused69, unused70,
                unused71, unused72, unused73, unused74, unused75,
                unused76, unused77, unused78, unused79, unused80,
                unused81, unused82, unused83, unused84, unused85,
                unused86, unused87, unused88, unused89, unused90,
                unused91, unused92, unused93, unused94, unused95,
                unused96, unused97, unused98, unused99, unused100;
        test();
        unused1 = unused2 = unused3 = unused4 = unused5 =
                unused6 = unused7 = unused8 = unused9 = unused10 =
                        unused11 = unused12 = unused13 = unused14 = unused15 =
                                unused16 = unused17 = unused18 = unused19 = unused20 =
                                        unused21 = unused22 = unused23 = unused24 = unused25 = unused26 = unused27 = unused28 = unused29 = unused30 =
                                                unused31 = unused32 = unused33 = unused34 = unused35 =
                                                        unused36 = unused37 = unused38 = unused39 = unused40 =
                                                                unused41 = unused42 = unused43 = unused44 = unused45 =
                                                                        unused46 = unused47 = unused48 = unused49 = unused50 =
                                                                                unused51 = unused52 = unused53 = unused54 = unused55 =
                                                                                        unused56 = unused57 = unused58 = unused59 = unused60 =
                                                                                                unused61 = unused62 = unused63 = unused64 = unused65 =
                                                                                                        unused66 = unused67 = unused68 = unused69 = unused70 =
                                                                                                                unused71 = unused72 = unused73 = unused74 = unused75 =
                                                                                                                        unused76 = unused77 = unused78 = unused79 = unused80 =
                                                                                                                                unused81 = unused82 = unused83 = unused84 = unused85 =
                                                                                                                                        unused86 = unused87 = unused88 = unused89 = unused90 =
                                                                                                                                                unused91 = unused92 = unused93 = unused94 = unused95 =
                                                                                                                                                        unused96 = unused97 = unused98 = unused99 = unused100 = 0;
    }

    public static void main(String[] args) {
        try {
            test();
        } catch (Error e) {
            System.out.println("stack length:" + stackLength);
            throw e;
        }
    }

}
/*
 * 栈溢出
 * -Xoss:设置本地方法栈大小,由于hotspot虚拟机不区分虚拟机栈和本地方法栈,因此对于hotspot来说,这个参数无效
 * 如果线程请求的栈深度大于虚拟机所允许的最大深度,StackOverflowError异常
 * 如果虚拟机在扩展栈时无法申请到足够的内存空间,OutOfMemoryError异常
 * 存在重叠问题,当栈空间无法继续分配时,到底是内存太小,还是栈空间太大。其本质是对同一事物的2种描述
 * 
 * 测试方式
 *  -Xss128k
 * 1:-Xss配置:减少栈内存容量
 * 2:定义大量的本地变量
 * 
 * 
 * 实验结果:在单个线程下,无论栈帧大,还是虚拟机栈容量小,当内存无法分配时,虚拟机抛出的都是StackOverflowErrow异常
 * 创建线程时每次在栈开辟一个独立的空间,不停的开,倒是可以抛出OutOfMemoryError异常
 * 如果建立过多线程导致内存溢出
 * 解决方案:1:减少线程数
 * 		2:更换64位虚拟机(内存限制为4GB,32位为2GB)
 * 		3:减少最大堆和栈容量
 */
public class B_JavaVMStackSOF {
	private int stackLength = 1;

	public void stackLeak() {
		stackLength++;
		stackLeak();
	}

	public static void main(String[] args) throws Throwable {
		B_JavaVMStackSOF javaVMStackSOF = new B_JavaVMStackSOF();

		try {
			javaVMStackSOF.stackLeak();
		} catch (Throwable e) {
			System.out.println("stack length:" + javaVMStackSOF.stackLength);
			throw e;
			//stack length:11434
		}
	}
}

本地方法栈溢出

通过减少堆和栈的内存,间接扩充直接内存的容量,从而提升创建线程的数目

创建线程导致内存溢出异常,与栈内存无关,主要取决于操作系统本身的内存状态

/*
内存溢出Out Of Memory Error
创建线程导致内存溢出异常,与栈内存无关,主要取决于操作系统本身的内存状态

 * 可能会导致系统假死、
 * 
 * -Xss2M:配置栈内存
 */
public class B_JavaVMStackOOM {
	private void dontStop() {
		while (true) {
		}
	}

	public void stackLeakByThread() {
		while (true) {
			new Thread(new Runnable() {
				public void run() {
					dontStop();
				}
			}).start();
		}
	}

	public static void main(String[] args) {
		B_JavaVMStackOOM javaVMStackOOM = new B_JavaVMStackOOM();
		javaVMStackOOM.stackLeakByThread();
	}

}

常量池溢出

jdk7之前会失败,jdk8则不会
常量池自7起,就存储到堆中了,所以可以通过设置堆的大小造成溢出

/*
 *
 *限制方法区大小间接的限制常量池的容量
 *-XX:PermSize=10M -XX:MaxPermSize=10M
 *	-XX:PermSize:限制方法区大小
 *	-XX:MaxPermSize:最大大小
 *
 *Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
 *PermGen space:运行时常量池属于方法区
 *
 *String.intern():native方法,如果池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象,否则将此String对象包含的字符串添加到常量池中
 */
public class C_RuntimeConstantPoolOOM {
	public static void main(String[] args) {
		ArrayList<String> list = new ArrayList<String>();
		int i = 0;
		while (true)
			list.add(String.valueOf(i++).intern());
	}
}

在这里插入图片描述

方法区溢出

存放Class相关信息,类名,接口,方法信息之内的.加载的类越多抛出异常
jdk7之前会失败,jdk8则不会
常量池自7起,就存储到堆中了,所以可以通过设置堆的大小造成溢出

·-XX:MaxMetaspaceSize:设置元空间最大值
·-XX:MetaspaceSize:指定元空间的初始空间大小
·-XX:MinMetaspaceFreeRatio:作用是在垃圾收集之后控制最小的元空间剩余容量的百分比,可
减少因为元空间不足导致的垃圾收集的频率。

/*
 * 借助cglib(创建多个类)使得方法区内存溢出异常,还需要导入相应的包,cglib-nodep-3.2.4.jar导入其他异常
 *  限制方法区大小
 *-XX:PermSize=10M -XX:MaxPermSize=10M
 *	-XX:PermSize:限制方法区大小
 *	-XX:MaxPermSize:最大大小
 *
 *
 *java.lang.OutOfMemoryError: PermGen space
 */

public class E_JavaMethodAreaOOM {

	public static void main(String[] args) {
		while (true) {
			Enhancer enhancer = new Enhancer();
			enhancer.setSuperclass(OOMObject.class);
			enhancer.setUseCache(false);
			enhancer.setCallback(new MethodInterceptor() {
				public Object intercept(Object obj, Method arg1, Object[] arg2,
						MethodProxy proxy) throws Throwable {
					return proxy.invokeSuper(obj, arg2);
				}
			});
			enhancer.create();
		}
	}

	static class OOMObject {
		public void say(String name) {
			System.out.println(name);
		}
	}
}

直接内存溢出

/*
 * 本机直接内存溢出
 * 使用DirectByteBuffer也会抛出异常,但是它并没有真正的向操作系统申请分配内存,而是通过计算内存得知无法分配,抛出的
 * 
 * 
 * DirectMemory容量可以通过 -MM:MaxDirectMemorySize指定,如果不指定,则默认与java堆的最大值(-Xmx指定)一样。
 * 
 * -Xmx20M -XX:MaxDirectMemorySize=10M
 * 
 * Exception in thread "main" java.lang.OutOfMemoryError
 */
public class D_DirectMemoryOOM {
	private static final int _1MB = 1024 * 1024;

	public static void main(String[] args) throws Exception {
		// 使用unsafe申请分配内存
		Field field = Unsafe.class.getDeclaredFields()[0];
		field.setAccessible(true);
		Unsafe object = (Unsafe) field.get(null);
		while (true) {
			object.allocateMemory(_1MB);
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值