天天看点

java中堆、栈知识点总结

1.图示

java中堆、栈知识点总结

2.图示解析

1.方法区和堆是所有线程可共享的区域(图示绿色)

2.本地方法栈、虚拟机栈、程序计数器是由各个线程隔离的数据区域,并不是共享的(图示黄色)

3.各区域作用详解:

  1. 程序计数器:当前线程执行的字节码指令,是线程私有的。
  2. 虚拟机栈:存放的是java执行方法的内存模型,每个方法被执行的时候,都会去创建一个帧栈,把帧栈压入栈,当方法执行完或抛出未捕获的异常时,帧栈就会出栈。
  3. 本地方法栈:调用本地native的内存模型,线程独享。
  4. 方法区:(1.8之前没有方法区,之后才有方法区)用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据,是线程共享的。方法区中有运行时常量池(运行时常量池是方法区的一部分,Class文件中除了存有类的版本、字段、方法、接口等描述信息,还有一项是常量池,存放编译期生成的各种字面量和符号引用,这部分内容在类加载后,存放到方法区的运行时常量池中)。
  5. 堆:java对象存储的地方,是虚拟机管理的内存中最大的一块,是所有线程共享的区域。堆在虚拟机启动时创建,次内存区域的唯一目的就是存放对象实例,几乎所有对象的实例都在这里分配内存,存放new生成的对象和数组。java堆是垃圾收集器管理的内存区域,因此很多时候被称为GC堆。

3.堆和栈的对比

  1. 栈解决的是程序的运行问题,即程序如何执行或如何处理数据;堆解决的是数据存储的问题,即数据怎么放、放在哪儿。
  2. 栈因为是运行单位,因此里面存储的信息都是跟当前的线程相关的信息,包括局部变量、程序的运行状态、方法返回值等等。而堆只负责存储对象的信息。
  3. 在方法中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配;堆内存用于存放由new创建的对象和数组。
  4. 在java中,一个线程就会有一个对应的线程栈与之对应,这点保证了程序的并发运行。而堆则是所有线程所共享的,也可以理解为多个线程访问同一个对象,比如多线程去读写同一个对象的值。

4.栈内存溢出问题

  1. StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度(例如存在递归调用或循环依赖调用)或创建的线程过多。
  2. OutOfMemoryError:如果虚拟机栈可以动态扩展,而扩展时无法申请到足够的内存,堆内存溢出是OutOfMemoryError。如果堆中没有内存完成实例分配,并且堆也无法再扩展至时,抛出OutOfMemoryError。

5.java堆溢出问题

java堆用于存储对象实例,只要不断地创建对象,当对象数量达到最大堆的容量限制后就会产生内存溢出的异常。最常见的内存溢出就是存在大的容器,而没法回收。比如Map、list等。

  1. 内存溢出:内存空间不足导致新对象还无法分配到足够的内存。
  2. 内存泄漏:应该释放后的对象没有被释放,多见于自己使用容器保存元素的情况下。

解决方法:

首先要找出最大的对象,判断最大对象的存在是否合理。如果合理就需要调整jvm内存大小。如果不合理,那么这个对象的存在,就是最有可能引起内存溢出的根源。通过GC Roots的引用链信息,就可以比较准确地定位出泄漏代码的位置。

6.垃圾回收算法

分代收集算法(目前大部分JVM的垃圾收集器所采用的的算法)

思路:把堆分为新生代和老年代。(永久代指的是方法区)

  1. 新生代:由于新生代中每次垃圾回收都要回收大部分对象,所以使用的垃圾回收算法是Copying算法(复制清除算法)。新生代里面分成一份较大的Eden空间和两份较小的Survivor空间。每次只使用Eden和一块Survivor空间,然后垃圾回收的时候,把存活对象放到未使用的Survivor空间中,清空Eden和刚使用过的Survivor空间。
  2. 老年代:由于老年代每次只回收少量的对象,因此采用的是Mark-compact算法(标记-整理算法)。先标记存活对象,然后把存活对象向一边移动,然后清理掉端边界以外的内存,不容易产生内存碎片。
  3. 永久代:在堆区外(方法区)有一个永久代。对永久代的回收主要是无效的类和常量。