导航

JVM调优原理

Posted on 2018-06-05 14:44  水木山川  阅读(187)  评论(0编辑  收藏  举报

  JVM堆栈  

  栈是运行时的单位,而堆是存储的单位。
  栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;堆解决的是数据存储的问题,即数据怎么放、放在哪儿。
  在Java中一个线程就会相应有一个线程栈与之对应,因为不同的线程执行逻辑有所不同,因此需要一个独立的线程栈。而堆则是所有线程共享的。栈因为是运行单位,因此里面存储的信息都是跟当前线程(或程序)相关信息的。包括局部变量、程序运行状态、方法返回值等等。而堆只负责存储对象信息。

  为什么要把堆和栈区分出来呢?栈中不是也可以存储数据吗?
  第一,从软件设计的角度看,栈代表了处理逻辑,而堆代表了数据。这样分开,使得处理逻辑更为清晰,分而治之的思想。这种隔离、模块化的思想在软件设计的方方面面都有体现。

  第二,堆与栈的分离,使得堆中的内容可以被多个栈共享(也可以理解为多个线程访问同一个对象)。这种共享的收益是很多的。一方面这种共享提供了一种有效的数据交互方式(如:共享内存),另一方面,堆中的共享常量和缓存可以被所有栈访问,节省了空间。

  第三,栈因为运行时的需要,比如保存系统运行的上下文,需要进行地址段的划分。由于栈只能向上增长,因此就会限制住栈存储内容的能力。而堆不同,堆中的对象是可以根据需要动态增长的,因此栈和堆的拆分,使得动态增长成为可能,相应栈中只需记录堆中的一个地址即可。

  第四,面向对象就是堆和栈的完美结合。其实,面向对象方式的程序与以前结构化的程序在执行上没有任何区别。但是,面向对象的引入,使得对待问题的思考方式发生了改变,而更接近于自然方式的思考。当我们把对象拆开,你会发现,对象的属性其实就是数据,存放在堆中;而对象的行为(方法),就是运行逻辑,放在栈中。我们在编写对象的时候,其实即编写了数据结构,也就是编写的处理数据的逻辑。不得不承认,面向对象的设计,确实很美。

  在Java中,Main函数就是栈的起始点,也是程序的起始点。

  堆中存什么?栈中存什么?
  堆中存的是对象。栈中存的是基本数据类型和堆中对象的引用。一个对象的大小是不可估计的,或者说是可以动态变化的,但是在栈中,一个对象只对应了一个4btye的引用(堆栈分离的好处:)。

  为什么不把基本类型放堆中呢?
  因为其占用的空间一般是1~8个字节——需要空间比较少,而且因为是基本类型,所以不会出现动态增长的情况——长度固定,因此栈中存储就够了,如果把他存在堆中是没有什么意义的。可以这么说,基本类型和对象的引用都是存放在栈中,而且都是几个字节的一个数,因此在程序运行时,他们的处理方式是统一的。但是基本类型、对象引用和对象本身就有所区别了,因为一个是栈中的数据一个是堆中的数据。最常见的一个问题就是,Java中参数传递时的问题。

  Java对象的大小
  在Java中,一个空Object对象(Object obj=new Object();)的大小是8byte,这个大小只是保存堆中一个没有任何属性的对象的大小。在程序中完成了一个Java对象的生命,但是它所占的空间为:4byte+8byte。4byte是上面部分所说的Java栈中保存引用的所需要的空间。而那8byte则是Java堆中对象的信息。因为所有的Java非基本类型的对象都需要默认继承Object对象,因此不论什么样的Java对象,其大小都必须是大于8byte

  引用类型:对象引用类型分为强引用软引用弱引用虚引用

  强引用:就是一般声明对象是时虚拟机生成的引用,强引用环境下,垃圾回收时需要严格判断当前对象是否被强引用,如果被强引用,则不会被垃圾回收。  例如:User user=new User();  //user就是强引用。

  软引用:软引用一般被做为缓存来使用。与强引用的区别是,软引用在垃圾回收时,虚拟机会根据当前系统的剩余内存来决定是否对软引用进行回收。如果剩余内存比较紧张,则虚拟机会回收软引用所引用的空间;如果剩余内存相对富裕,则不会进行回收。  

      例如:  SoftReference<Object> sf = new SoftReference<Object>(obj);   //sf就是一个软引用。

  弱引用:弱引用与软引用类似,都是作为缓存来使用。但与软引用不同,弱引用在进行垃圾回收时,是一定会被回收掉的,因此其生命周期只存在于一个垃圾回收周期内。

      例如:   WeakReference<Object> wf = new WeakReference<Object>(obj);   //wf就是一个弱引用。  

  虚引用:与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。

      例如:   PhantomReference<Object> pf = new PhantomReference<Object>(obj);   //pf就是一个虚引用。

  异常: java.lang.OutOfMemoryError: Java heap space
    解决:一般就是根据垃圾回收前后情况对比,同时根据对象引用情况(常见的集合对象引用)分析,基本都可以找到泄漏点。

  异常:java.lang.OutOfMemoryError: PermGen space
    解决:主要原因就是大量动态反射生成的类不断被加载,最终导致Perm区被占满。
    增加持久层空间大小: -XX:MaxPermSize=32m
    换用JDK。比如JRocket。

  异常:java.lang.StackOverflowError
    解决: 一般就是递归没返回,或者循环调用造成,检查递归调用和循环调用。

  异常:Fatal: Stack size too small
    说明:java中一个线程的空间大小是有限制的。JDK5.0以后这个值是1M。与这个线程相关的数据将会保存在其中。但是当线程空间满了以后,将会出现上面异常。
    解决: 增加线程栈大小:-Xss2m。若这个配置无法解决根本问题,还要看代码部分是否有造成泄漏的部分。

  异常:java.lang.OutOfMemoryError: unable to create new native thread
    说明:这个异常是由于操作系统没有足够的资源来产生这个线程造成的。系统创建线程时,除了要在Java堆中分配内存外,操作系统本身也需要分配资源来创建线程。因此,当线程数量大到一定程度以后,堆中或许还有空间,但是操作系统分配不出资源来了,就出现这个异常了。分配给Java虚拟机的内存愈多,系统剩余的资源就越少,因此,当系统内存固定时,分配给Java虚拟机的内存越多,那么,系统总共能够产生的线程也就越少,两者成反比的关系。同时,可以通过修改-Xss来减少分配给单个线程的空间,也可以增加系统总共内生产的线程数。
    解决: 重新设计系统减少线程数量。线程数量不能减少的情况下,通过 -Xss128k 减小单个线程大小。以便能生产更多的线程。