Java 内存分析

Java 内存分析

内存是一个程序员很容易忽视的一个地方,对内存的不理解导致出现内存溢出不能很好的定位,及多线程不能排除问题。

本文也是博众家所说而做,网络文章太多,但是各种解说不统一,感觉像是婆说婆有理一样,这篇文章理解后,相信你会对其他文章的理解更加容易。

总的就是两个方面来讲,大,小。
1.内存从大的角度来看,有两个大的划分,一个是“堆”,一个是“栈”。
2.内存从更细的角度来看,有5个组成部分,分别是,栈,本地方法栈,方法区,堆,程序计数器。
在多线程的编码中,更重要的是要明白堆和栈。
先从大的角度分析,
1.1栈,又可以称之为“线程栈”。线程安全,不会出现线程数据混乱。
在虚拟机中运行的每一个线程,都有自己的栈。这个线程栈包含了这个线程调用的方法当前执行点相关的信息。一个线程仅能访问自己的线程栈。 一个线程创建的本地变量对其它线程不可见,仅自己可见。即使两个线程执行同样的代码,这两个线程任然在在自己的线程栈中的代码来创建本地变量。因此,每个线程拥有每个本地变量的独有版本。
即使一个线程将信息传递给另一个线程,传递的也只是拷贝,而不是真正信息本身。

栈中一般存储 基本类型和对象地址, 方法中局部变量在线程空间中。
而且 每个方法被执行时都会创建一个栈帧,存储局部变量表、操作栈、动态链接、方法出口等信息。

在Java虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。在Hot Spot虚拟机中,可以使用-Xss参数来设置栈的大小。栈的大小直接决定了函数调用的可达深度。
更简单的一句话就是 栈帧决定方法调用的深度。
1.2 堆,线程不安全
堆,用于存放对象实例以及数组。Java堆是GC管理的主要区域,从内存回收的角度来看,由于现在GC基本都采用分代收集算法,所以Java堆还可以细分为:新生代和老年代。
现在对大的角度进行总结:
一个本地变量可能是原始类型,在这种情况下,它总是“呆在”线程栈上。
一个本地变量也可能是指向一个对象的一个引用。在这种情况下,引用(这个本地变量)存放在线程栈上,但是对象本身存放在堆上。
一个对象可能包含方法,这些方法可能包含本地变量。这些本地变量任然存放在线程栈上,即使这些方法所属的对象存放在堆上。
一个对象的成员变量可能随着这个对象自身存放在堆上。不管这个成员变量是原始类型还是引用类型。
静态成员变量跟随着类定义一起也存放在堆上。
存放在堆上的对象可以被所有持有对这个对象引用的线程访问。当一个线程可以访问一个对象时,它也可以访问这个对象的成员变量。如果两个线程同时调用同一个对象上的同一个方法,它们将会都访问这个对象的成员变量,但是每一个线程都拥有这个本地变量的私有拷贝。
简单一句话总结:
线程安全的数据都在栈中,不安全的都在堆中。

内存从更细的角度来看:
2.1 栈,上述的已经很明白了,没办法了。
2.2 堆,存放对象实例及数组。
2.3 方法区,存放了要加载的类的信息(名称、修饰符等)、类中的静态常量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,当在程序中通过Class对象的getName.isInterface等方法来获取信息时,这些数据都来源于方法区。方法区是被Java线程锁共享的,不像Java堆中其他部分一样会频繁被GC回收,它存储的信息相对比较稳定,在一定条件下会被GC,当方法区要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。方法区也是堆中的一部分,就是我们通常所说的Java堆中的永久区 Permanet Generation,大小可以通过参数来设置,可以通过-XX:PermSize指定初始值,-XX:MaxPermSize指定最大值
2.4 PC寄存器/程序计数器: 严格来说是一个数据结构,用于保存当前正在执行的程序的内存地址,由于Java是支持多线程执行的,所以程序执行的轨迹不可能一直都是线性执行。当有多个线程交叉执行时,被中断的线程的程序当前执行到哪条内存地址必然要保存下来,以便用于被中断的线程恢复执行时再按照被中断时的指令地址继续执行下去。为了线程切换后能恢复到正确的执行位置,每个线程都需要有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存,这在某种程度上有点类似于“ThreadLocal”,是线程安全的。
2.5 本地方法栈:本地方法栈和Java栈所发挥的作用非常相似,区别不过是Java栈为JVM执行Java方法服务,而本地方法栈为JVM执行Native方法服务。本地方法栈也会抛出StackOverflowError和OutOfMemoryError异常。
2.6 常量池Constant Pool:
常量池本身是方法区中的一个数据结构。常量池中存储了如字符串、final变量值、类名和方法名常量。常量池在编译期间就被确定,并保存在已编译的.class文件中。一般分为两类:字面量和应用量。字面量就是字符串、final变量等。类名和方法名属于引用量。引用量最常见的是在调用方法的时候,根据方法名找到方法的引用,并以此定为到函数体进行函数代码的执行。引用量包含:类和接口的权限定名、字段的名称和描述符,方法的名称和描述符
简单总结上述分析:
对象A的引用, 方法中的局部变量,放在栈里,并且线程安全,每个线程都是对象的副本。
对象A的实例,存放在堆中,
对象A中的类的名称,修饰符,类的常量,类的属性,方法名,存放在方法区,方法区为堆中的一部分。注意方法区放置的都是类相关的信息。
对象A中的字符串,如“abc”,final修饰的,类或者方法的修饰词、名称及引用。为方法区的一部分。
线程安全的只有栈和程序计数器。
堆+方法区+常量池=堆,线程不安全。

文献

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值