• java的四种引用,强弱软虚和jvm优化


    1、强引用(StrongReference)
    强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。如下: 

    Object o=new Object();   //  强引用

    当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。如果不使用时,要通过如下方式来弱化引用,如下:

    o=null;     // 帮助垃圾收集器回收此对象

    显式地设置o为null,或超出对象的生命周期范围,则gc认为该对象不存在引用,这时就可以回收这个对象。具体什么时候收集这要取决于gc的算法。

    但是如果这个o是全局的变量时,就需要在不用这个对象时赋值为null,因为强引用不会被垃圾回收。

    强引用在实际中有非常重要的用处,举个ArrayList的实现源代码:

    private transient Object[] elementData;
    
    public void clear() {
    
            modCount++;
    
            // Let gc do its work
    
            for (int i = 0; i < size; i++)
    
                elementData[i] = null;
    
            size = 0;
    
    }

    在ArrayList类中定义了一个私有的变量elementData数组,在调用方法清空数组时可以看到为每个数组内容赋值为null。不同于elementData=null,强引用仍然存在,避免在后续调用 add()等方法添加元素时进行重新的内存分配。使用如clear()方法中释放内存的方法对数组中存放的引用类型特别适用,这样就可以及时释放内存。

    2、软引用(SoftReference)

     如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。   

    String str=new String("abc");                                     // 强引用
    
     SoftReference<String> softRef=new SoftReference<String>(str);     // 软引用
     

      当内存不足时,等价于:

    If(JVM.内存不足()) {
    
       str = null;  // 转换为软引用
    
       System.gc(); // 垃圾回收器进行回收
    
    }
     

    虚引用在实际中有重要的应用,例如浏览器的后退按钮。按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略了。

    (1)如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建

    (2)如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出

    这时候就可以使用软引用

    Browser prev = new Browser();               // 获取页面进行浏览
    
    SoftReference sr = new SoftReference(prev); // 浏览完毕后置为软引用    
    
    if(sr.get()!=null){ 
    
        rev = (Browser) sr.get();           // 还没有被回收器回收,直接获取
    
    }else{
    
        prev = new Browser();               // 由于内存吃紧,所以对软引用的对象回收了
    
        sr = new SoftReference(prev);       // 重新构建
    
    }
     

    这样就很好的解决了实际的问题。

           软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

    3、弱引用(WeakReference)

          弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。

    String str=new String("abc");    
    
    WeakReference<String> abcWeakRef = new WeakReference<String>(str);
    
    str=null;
     

    当垃圾回收器进行扫描回收时等价于:

    str = null;
    
    System.gc();
     

     如果这个对象是偶尔的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用 Weak Reference 来记住此对象。   

       下面的代码会让str再次变为一个强引用:

    String  abc = abcWeakRef.get();
     

    弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

    当你想引用一个对象,但是这个对象有自己的生命周期,你不想介入这个对象的生命周期,这时候你就是用弱引用。

    这个引用不会在对象的垃圾回收判断中产生任何附加的影响。

    public class ReferenceTest {
    
     
    
        private static ReferenceQueue<VeryBig> rq = new ReferenceQueue<VeryBig>();
    
     
    
        public static void checkQueue() {
    
            Reference<? extends VeryBig> ref = null;
    
            while ((ref = rq.poll()) != null) {
    
                if (ref != null) {
    
                    System.out.println("In queue: "   + ((VeryBigWeakReference) (ref)).id);
    
                }
    
            }
    
        }
    
     
    
        public static void main(String args[]) {
    
            int size = 3;
    
            LinkedList<WeakReference<VeryBig>> weakList = new LinkedList<WeakReference<VeryBig>>();
    
            for (int i = 0; i < size; i++) {
    
                weakList.add(new VeryBigWeakReference(new VeryBig("Weak " + i), rq));
    
                System.out.println("Just created weak: " + weakList.getLast());
    
     
    
            }
    
     
    
            System.gc(); 
    
            try { // 下面休息几分钟,让上面的垃圾回收线程运行完成
    
                Thread.currentThread().sleep(6000);
    
            } catch (InterruptedException e) {
    
                e.printStackTrace();
    
            }
    
            checkQueue();
    
        }
    
    }
    
     
    
    class VeryBig {
    
        public String id;
    
        // 占用空间,让线程进行回收
    
        byte[] b = new byte[2 * 1024];
    
     
    
        public VeryBig(String id) {
    
            this.id = id;
    
        }
    
     
    
        protected void finalize() {
    
            System.out.println("Finalizing VeryBig " + id);
    
        }
    
    }
    
     
    
    class VeryBigWeakReference extends WeakReference<VeryBig> {
    
        public String id;
    
     
    
        public VeryBigWeakReference(VeryBig big, ReferenceQueue<VeryBig> rq) {
    
            super(big, rq);
    
            this.id = big.id;
    
        }
    
     
    
        protected void finalize() {
    
            System.out.println("Finalizing VeryBigWeakReference " + id);
    
        }
    
    }
     

    最后的输出结果为:

    Just created weak: com.javabase.reference.VeryBigWeakReference@1641c0
    
    Just created weak: com.javabase.reference.VeryBigWeakReference@136ab79
    
    Just created weak: com.javabase.reference.VeryBigWeakReference@33c1aa
    
    Finalizing VeryBig Weak 2
    
    Finalizing VeryBig Weak 1
    
    Finalizing VeryBig Weak 0
    
    In queue: Weak 1
    
    In queue: Weak 2
    
    In queue: Weak 0
     

    4、虚引用(PhantomReference)

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

        虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

    5、总结

     Java4种引用的级别由高到低依次为:

    强引用  >  软引用  >  弱引用  >  虚引用

    通过图来看一下他们之间在垃圾回收时的区别:

    当垃圾回收器回收时,某些对象会被回收,某些不会被回收。垃圾回收器会从根对象Object来标记存活的对象,然后将某些不可达的对象和一些引用的对象进行回收

    jvm的堆栈内存。

    安装Java开发软件时,默认安装包含两个文件夹,一个JDK(Java开发工具箱),一个JRE(Java运行环境,内含JVM),其中JDK内另含一个JRE。如果只是运行Java程序,则JRE已足够;而JDK则只有开发人员才用到。

    优化内存,主要是在bin/catalina.bat/sh 配置文件中进行。linux上,在catalina.sh中添加:

    JAVA_OPTS="-server -Xms1G -Xmx2G -Xss256K -Djava.awt.headless=true -Dfile.encoding=utf-8 -XX:MaxPermSize=256m -XX:PermSize=128M -XX:MaxPermSize=256M"  

    1)错误提示:java.lang.OutOfMemoryError:Java heap space

    Tomcat默认可以使用的内存为128MB,在较大型的应用项目中,这点内存是不够的,有可能导致系统无法运行。常见的问题是报Tomcat内存溢出错误,Outof Memory(系统内存不足)的异常,从而导致客户端显示500错误,一般调整Tomcat的-Xms和-Xmx即可解决问题,通常将-Xms和-Xmx设置成一样,堆的最大值设置为物理可用内存的最大值的80%。

    set JAVA_OPTS=-Xms512m-Xmx512m

    2)错误提示:java.lang.OutOfMemoryError: PermGenspace

    PermGenspace的全称是Permanent Generationspace,是指内存的永久保存区域,这块内存主要是被JVM存放Class和Meta信息的,Class在被Loader时就会被放到PermGenspace中,它和存放类实例(Instance)的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对PermGenspace进行清理,所以如果你的应用中有很CLASS的话,就很可能出现PermGen space错误,这种错误常见在web服务器对JSP进行precompile的时候。如果你的WEB APP下都用了大量的第三方jar, 其大小超过了jvm默认的大小(4M)那么就会产生此错误信息了。解决方法:

    set JAVA_OPTS=-XX:PermSize=128M

    3)在使用-Xms和-Xmx调整tomcat的堆大小时,还需要考虑垃圾回收机制。如果系统花费很多的时间收集垃圾,请减小堆大小。一次完全的垃圾收集应该不超过3-5 秒。如果垃圾收集成为瓶颈,那么需要指定代的大小,检查垃圾收集的详细输出,研究垃圾收集参数对性能的影响。一般说来,你应该使用物理内存的 80% 作为堆大小。当增加处理器时,记得增加内存,因为分配可以并行进行,而垃圾收集不是并行的。

    2、连接数优化:

    #优化连接数,主要是在conf/server.xml配置文件中进行修改。

    2.1、优化线程数

    找到Connectorport="8080" protocol="HTTP/1.1",增加maxThreads和acceptCount属性(使acceptCount大于等于maxThreads),如下:

    Xml代码  收藏代码
    1. <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" acceptCount="500" maxThreads="400" />  

     其中:

    • maxThreads:tomcat可用于请求处理的最大线程数,默认是200
    • minSpareThreads:tomcat初始线程数,即最小空闲线程数
    • maxSpareThreads:tomcat最大空闲线程数,超过的会被关闭
    • acceptCount:当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理.默认100
    2.2、使用线程池

    在server.xml中增加executor节点,然后配置connector的executor属性,如下:

    Xml代码  收藏代码
    1. <Executor name="tomcatThreadPool" namePrefix="req-exec-" maxThreads="1000" minSpareThreads="50" maxIdleTime="60000"/>  
    2. <Connector port="8080" protocol="HTTP/1.1" executor="tomcatThreadPool"/>  

     其中:

    • namePrefix:线程池中线程的命名前缀
    • maxThreads:线程池的最大线程数
    • minSpareThreads:线程池的最小空闲线程数
    • maxIdleTime:超过最小空闲线程数时,多的线程会等待这个时间长度,然后关闭
    • threadPriority:线程优先级

    注:当tomcat并发用户量大的时候,单个jvm进程确实可能打开过多的文件句柄,这时会报java.net.SocketException:Too many open files错误。可使用下面步骤检查:

    • ps -ef |grep tomcat 查看tomcat的进程ID,记录ID号,假设进程ID为10001
    • lsof -p 10001|wc -l 查看当前进程id为10001的 文件操作数
    • 使用命令:ulimit -a 查看每个用户允许打开的最大文件数

    内存优化:

    • -server:启用jdk的server版本。
    • -Xms:虚拟机初始化时的最小堆内存。
    • -Xmx:虚拟机可使用的最大堆内存。 #-Xms与-Xmx设成一样的值,避免JVM因为频繁的GC导致性能大起大落
    • -XX:PermSize:设置非堆内存初始值,默认是物理内存的1/64。
    • -XX:MaxNewSize:新生代占整个堆内存的最大值。
    • -XX:MaxPermSize:Perm(俗称方法区)占整个堆内存的最大值,也称内存最大永久保留区域。
    一、Eclise 中设置jvm内存
    
    1、Eclise 中设置jvm内存: 改动eclipse的配置文件,对全部project都起作用
    
         改动eclipse根文件夹下的eclipse.ini文件
    
         -vmargs  //虚拟机设置
    
         -Xms40m //初始内存
    
         -Xmx256m //最大内存
    
         -Xmn16m //最小内存
    
         -XX:PermSize=128M //非堆内存
    
         -XX:MaxPermSize=256M
    
    2、Eclise 中设置jvm内存:jres VM Arguments參数的设置,对全部project都起作用
    
         打开eclipse  window-preferences-Java-Installed JREs -Edit-Default VM Arguments   
    
         在VM自变量中输入:-Xmx128m -Xms64m -Xmn32m -Xss16m
    
    3、Eclise 中设置jvm内存:RunConfigurations VM arguments參数设置,仅仅对这个project起作用
    
    在Eclipse中-->右击project/要执行的主类-->Run As-->RunConfigurations-->(x)=Arguments-->VM arguments
    
    中增加 -Xmx36m
    
    然后Apply-->Run
    这上面的36指的是给java虚拟机分配的最大堆内存大小,单位是MB,也就是说上面的那句话的含义是JVM的最大堆内存是36MB
    
     
    
    4、Eclise 中设置jvm内存:Debug Configurations  VM arguments參数设置,仅仅对这个project起作用
    
    在Eclipse中-->右击project/要执行的主类-->Debug As-->DebugConfigurations-->(x)=Arguments-->VM arguments
    
    中增加-Xmx36m
    
    然后Apply-->Run
    这上面的36指的是给java虚拟机分配的最大堆内存大小。单位是MB,也就是说上面的那句话的含义是JVM的最大堆内存是36MB
    、Tomcat内存设置
    
    windows下在catalina.bat的第一行添加: 
    
    Java代码 :set JAVA_OPTS=-Xms64m -Xmx256m -XX:PermSize=128M -XX:MaxNewSize=256m -XX:MaxPermSize=256m  
    
    linux下在catalina.sh的第一行添加: 
    
    Java代码 :JAVA_OPTS=-Xms64m -Xmx256m -XX:PermSize=128M -XX:MaxNewSize=256m -XX:MaxPermSize=256m
    
    
    实例+具体解释
    
    设置Tomcat启动的初始内存其初始空间(即-Xms)是物理内存的1/64。最大空间(-Xmx)是物理内存的1/4。
    
    能够利用JVM提供的-Xmn -Xms -Xmx等选项可进行设置
    
    实例,下面给出1G内存环境下java jvm 的參数设置參考:
    
    JAVA_OPTS="-server -Xms800m -Xmx800m -XX:PermSize=64M -XX:MaxNewSize=256m -XX:MaxPermSize=128m -Djava.awt.headless=true "
    
    JAVA_OPTS="-server -Xms768m -Xmx768m -XX:PermSize=128m -XX:MaxPermSize=256m -XX: NewSize=192m -XX:MaxNewSize=384m"
    
    CATALINA_OPTS="-server -Xms768m -Xmx768m -XX:PermSize=128m -XX:MaxPermSize=256m -XX:NewSize=192m -XX:MaxNewSize=384m"
    
    Linux:
    
    在/usr/local/apache-tomcat-5.5.23/bin 文件夹下的catalina.sh加入:
    
    JAVA_OPTS='-Xms512m -Xmx1024m'要加“m”说明是MB。否则就是KB了,在启动tomcat时会 报内存不足。
    
    -Xms:初始值-Xmx:最大值-Xmn:最小值
    
    Windows:
    
    在catalina.bat最前面增加set JAVA_OPTS=-Xms128m -Xmx350m 
    
    假设用startup.bat启动tomcat,OK设置生效.够成功的分配200M内存.
    
    可是假设不是运行startup.bat启动tomcat而是利用windows的系统服务启动tomcat服务,上面的设置就不生效了,就是说set JAVA_OPTS=-Xms128m -Xmx350m 没起作用.上面分配200M内存就OOM了..
    
    windows服务运行的是bin	omcat.exe.他读取注冊表中的值,而不是catalina.bat的设置.
    
    解决的方法:
    
    改动注冊表HKEY_LOCAL_MACHINESOFTWAREApache Software FoundationTomcat Service ManagerTomcat5ParametersJavaOptions
    
    原值为-Dcatalina.home="C:ApacheGroupTomcat 5.0"-Djava.endorsed.dirs="C:ApacheGroupTomcat 5.0commonendorsed"-Xrs增加 -Xms300m -Xmx350m 
    
    重起tomcat服务,设置生效

    常见的三种Java内存溢出

    1. java.lang.OutOfMemoryError: Java heap space —-JVM Heap(堆)溢出。JVM 在启动的时候会自动设置 JVM Heap 的值,其初始空间(即-Xms)是物理内存的1/64,最大空间(-Xmx)不可超过物理内存。可以利用 JVM提供的 -Xmn -Xms -Xmx 等选项可进行设置。Heap 的大小是 Young Generation 和 Tenured Generaion 之和。在 JVM 中如果 98% 的时间是用于 GC,且可用的 Heap size 不足 2% 的时候将抛出此异常信息。解决方法:手动设置 JVM Heap(堆)的大小。

    Heap size 设置 JVM堆的设置是指java程序执行过程中JVM能够调配使用的内存空间的设置.JVM在启动的时候会自己主动设置Heap size的值。其初始空间(即-Xms)是物理内存的1/64,最大空间(-Xmx)是物理内存的1/4。能够利用JVM提供的-Xmn -Xms -Xmx等选项可进行设置。Heap size 的大小是Young Generation 和Tenured Generaion 之和。
    
     
    
    提示:在JVM中假设98%的时间是用于GC且可用的Heap size 不足2%的时候将抛出此异常信息。 
    
    提示:Heap Size 最大不要超过可用物理内存的80%,一般的要将-Xms和-Xmx选项设置为同样,而-Xmn为1/4的-Xmx值。 
    
    解决方法:
    
    手动设置Heap size 改动TOMCAT_HOME/bin/catalina.bat,在“echo "Using CATALINA_BASE: $CATALINA_BASE"”上面增加下面行:
    
    Java代码 set JAVA_OPTS=%JAVA_OPTS% -server -Xms800m -Xmx800m -XX:MaxNewSize=256m
    
    或改动catalina.sh 在“echo "Using CATALINA_BASE: $CATALINA_BASE"”上面增加下面行: JAVA_OPTS="$JAVA_OPTS -server -Xms800m -Xmx800m -XX:MaxNewSize=256m" 

    2.java.lang.OutOfMemoryError: PermGen space  —- PermGen space溢出。PermGen space 的全称是 Permanent Generation space,是指内存的永久保存区域。为什么会内存溢出,这是由于这块内存主要是被 JVM 存放Class 和 Meta 信息的,Class 在被 Load 的时候被放入 PermGen space 区域,它和存放 Instance 的 Heap 区域不同,sun 的 GC 不会在主程序运行期对 PermGen space 进行清理,所以如果你的 APP 会载入很多 CLASS 的话,就很可能出现 PermGen space 溢出。解决方法: 手动设置 MaxPermSize 大小

    PermGen space的全称是Permanent Generation space,是指内存的永久保存区域。这块内存主要是被JVM存放Class和Meta信息的,Class在被Loader时就会被放到PermGen space中,它和存放类实例(Instance)的Heap区域不同,GC(Garbage Collection)不会在主程序执行期对PermGen space进行清理,所以假设你的应用中有非常CLASS的话,就非常可能出现PermGen space错误。这样的错误常见在webserver对JSP进行pre compile的时候。假设你的WEB APP下都用了大量的第三方jar, 其大小超过了jvm默认的大小(4M)那么就会产生此错误信息了。 
    
    解决方法:
    
    1. 手动设置MaxPermSize大小 改动TOMCAT_HOME/bin/catalina.bat(Linux下为catalina.sh),在Java代码 “echo "Using CATALINA_BASE: $CATALINA_BASE"”上面增加下面行: set JAVA_OPTS=%JAVA_OPTS% -server -XX:PermSize=128M -XX:MaxPermSize=512m
    
    catalina.sh下为: Java代码 JAVA_OPTS="$JAVA_OPTS -server -XX:PermSize=128M -XX:MaxPermSize=512m"

    3.java.lang.StackOverflowError   —- 栈溢出栈溢出了,JVM 依然是采用栈式的虚拟机,这个和 C 与 Pascal 都是一样的。函数的调用过程都体现在堆栈和退栈上了。调用构造函数的 “层”太多了,以致于把栈区溢出了。通常来讲,一般栈区远远小于堆区的,因为函数调用过程往往不会多于上千层,而即便每个函数调用需要 1K 的空间(这个大约相当于在一个 C 函数内声明了 256 个 int 类型的变量),那么栈区也不过是需要 1MB 的空间。通常栈的大小是 1-2MB 的。通常递归也不要递归的层次过多,很容易溢出。解决方法:修改程序。

  • 相关阅读:
    Spring Bean的生命周期
    Java中的Object类
    Java线程池七个参数
    ROS 第五讲 在模拟器中构建第一个机器人
    ROS 第四讲 让小车在RViz与Gazebo模拟器中跑起来
    ROS 第三讲 操控小乌龟
    ROS 第二讲 基本操作
    ROS 第一讲 引入
    自然语言处理(二) 新词发现或非监督词典构建
    递归找到一个复杂对象中的某个值和它的最小层级
  • 原文地址:https://www.cnblogs.com/h-c-g/p/11083431.html
Copyright © 2020-2023  润新知