zoukankan      html  css  js  c++  java
  • 关于JVM

    JVM:Java虚拟机。在这里讨论记录下JVM内存模型

    按照线程资源共享与非共享划分:

    • 线程资源共享区域:堆(heap),方法区(method area) 
    • 线程资源非共享区域:栈(stack),本地方法栈(native method area),PC(程序计数器)

    两区域内产生的异常略有不同,在共享区域如发生内存方面的异常是OutOfMemoryError(OOM),在线程独占区域发生异常则分为多线程情况与单线程情况,多线程时异常时OutOfMemory,单线程是StackOutflowError。

    再来说说对应区域存放的数据类型:

    线程资源共享区域:

    • 堆:存放通过new创建的对象,数组。一句话来说就是,堆中存放对象
    • 方法区:又称非堆(non heap)存放常量、静态变量、静态常量、以及静态代码块。还有已被JVM加载好的类信息比如类的字节码、class、field、method,此外常量池也在这里,永久代(PermGen )

      这里说一下非堆区中存放的类字节码等。

    加载的字节码:一个类首先将字节码加载进jvm中,可以是class文件格式的也可以通过网络传输也可以是cglib字节码框架增强生成的。class/field/method:属于元数据对象,字节码加载后,jvm根据其中内容为该类生成对应对象(反射中使用较多),这类生成的对象与堆中存放的对象不同,这种对象存放在方法区。

    关于非堆区中永久代

    永久代属于方法区的一种实现,包含jvm需要的元数据,也就是上述所说的 class/field/method这类东西,永久代存放jvm运行运行时使用的类,也包含JavaSE库中类、方法。此种对象在full GC时进行GC。

    从Java7开始,永久区在被取代,其中部分数据被转移到堆中,从Java8开始永久代被元空间取代(Metaspace)。两者本质上和作用上是相同的,最大的区别在于,元空间不在jvm中,而是使用本地内存,所以这块地的大小是很大的。

    再说一下常量池。

    类字节码在加载时会被解析并生成不同内容存入方法区中,内容包括字段、方法、接口、版本等信息,其中还包含常量池。用于存放在字节码中使用到的字面量以及字符符号引用。在类加载时,这些内容进入方法区的常量池存放。常量池相对来说比较动态,类加载时可以写,程序运行时也可以写。

      参看以下内容及注释

    new StringBuilder("abc").toString();
    

      上述代码在堆中创建字符串 “abc”

    new StringBuilder("abc").toString().intern();

      上述代码在堆中创建字符串“abc”,然后将使用intern()将其放入常量池

      证明:

            String ac="abc";
            String abc=new StringBuilder("abc").toString();
            String abc1 = new StringBuilder("abc").toString().intern();
            System.out.println(ac==abc);
            System.out.println(ac==abc1);    
    

      结果:

     线程资源独占区域:

    • 栈:又可称虚拟机栈,存放函数中定义的变量,包括基本类型和引用类型。也就是堆中对象的引用
    • 程序计数器:当前线程运行的字节码的行号指示器
    • 本地方法栈:也就是Java代码中写的native方法,这些方法对应的实现通常在Java提供的jar包中由C/C++编写,由JNI(Java native Interface)接口调用。作用和栈类似

    在这里比较一下 虚拟机栈 和 本地方法栈:

    虚拟机栈由JVM在函数(方法)运行时创建,存储局部变量、操作数栈、常量池引用等;所以一个函数运行就是一个栈帧在JVM中出栈入栈的过程。本地方法栈作用也是相同的,但是服务对象是Java提供的native函数。同时为了保证线程中局部变量不被其它线程访问到,所以虚拟机栈和本地方法栈是线程隔离的。

      所有对象的内存分配都是在堆上进行的,线程共享,然后对象的引用给栈,栈是线程独占的,这样子省内存。

    关于堆的GC

    堆存放对象是GC的主要目标,堆在逻辑上分为新生代,年老代。新创建的对象会被存放在新生代,在经过数次GC还未被回收掉的话便会进入年老代,这个次数是可以通过jvm参数设置的,新生代和年老代的回收策略不同。设置两个不同区域的原因:虽然大部分Java对象是很快被回收的,但是仍然存在一小部分对象需要长期存留在内存中。

    新生代包含两块区域: eden区和survivor区,前者是使用空间后者是使用空间的保留空间,通常前者要比后者大,默认是 4:1的比例分配。survivor中又分为两部分:from survivor 和to survivor两个区域,默认是1:1。总之三个区域是为了减小内存碎片的产物,年轻对象在from和to区互相复制,然后清空两者之一保证from和to区总有一个为空的。在经过GC后存活下来的对象满足参数设置的值时便会进入年老区。

      参数:

    -XX:MaxTenuringThreshold= [0-15]
    

     次数为0-15,设置为0时年轻对象将不再经过survivor直接进入年老区。该值设置较大时年轻对象会在survivor进行多次复制,这样可以增加对年轻代回收的概率。默认值是15

    设置jvm堆内存可以如下操作进行调整:

    -xmx 设置最大堆内存
    -xms 设置最小内存
    -xms 设置年轻代内存
    -xxsurvivorRation 设置年轻代中 eden区与survivor区的比值
    

      最大堆内存与最小堆内存不相等时,jvm会自动调节堆内存大小,也就是抖动,这个比较耗费性能,建议一致。

  • 相关阅读:
    多线程编程1-定义理解与三种实现方式
    Java类集框架详细汇总-底层分析
    Trie、并查集、堆、Hash表学习过程以及遇到的问题
    spring前导知识-Tomcat、Maven等配置
    双指针、位运算、离散化、区间合并的手动模拟
    单点登录原理与简单实现
    Spring引入外部配置文件
    Java异常机制
    Java多线程-线程的概念和创建
    java webservice服务器端获取request对象的三种方式
  • 原文地址:https://www.cnblogs.com/notably/p/12980389.html
Copyright © 2011-2022 走看看