关于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会自动调节堆内存大小,也就是抖动,这个比较耗费性能,建议一致。

 

posted @ 2020-05-28 13:53  落楝花  阅读(170)  评论(0编辑  收藏  举报

乘兴而来