JVM前世今生和java内存区域-1

一  概念 

 Java虚拟机(JVM)是可运行Java代码的假想计算机。只要根据JVM规格描述将解释器移植到特定的计算机上,就能保证经过编译的任何Java代码能够在该系统上运行。

1 虚拟机的发展

 目前我们用的都是HotSpot虚拟机

二  运行时内存区域划分

各区域解释

1 程序计数器:较小的内存空间,当前线程执行的字节 码的行号指示器;各线程之间独立存储,互不影响;

在虚拟机的概念模型里(仅仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时,就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成 

2 java栈:线程私有,生命周期和线程,每个方法在执行 的同时都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。方法的执行就对应着栈帧在虚拟机栈中入栈和出栈的过程;栈里面存放着各种基本数据类型变量(局部变量表)和值,以及对象的引用地址(-Xss栈的大小) ;

3 本地方法栈:本地方法栈保存的是native方法的信息, 当一个JVM创建的线程调用native方法后,JVM不再为 其在虚拟机栈中创建栈帧,JVM只是简单地动态链接 并直接调用native方法;

4 堆:Java堆是Javaer需要重点关注的一块区域,因为涉及到内存的分配 (new关键字,反射等)与回收(回收算法,收集器等),唯一目的就是存放对象实例(对象头+实例数据(各种类型字段内容)+对齐填充

(-Xms堆最小值;-Xmx堆的最大值; -Xmn新生代的大小;-XX:NewSize新生代最小值;-XX:MaxNewSize新生代最大值) ;

5 方法区:也叫永久区,用于存储已经被虚拟机加载的类信息,常量 ("zdy","123"等),静态变量(static变量),各种方法、字段,即时编译器编译后的代码等数据

JDK1.7及以前

-XX:PermSize内存最小值;XX:MaxPermSize内存最大值

JKD1.8及以后

-XX:MetaspaceSize内存最小值; XX:MaxMetaspaceSize内存最大值) 

6 运行时常量池:运行时常量池是方法区的一部分,用于存放编译期生成 的各种字面量("zdy","123"等)和符号引用。

JDK 1.6 运行时常量池,在方法区内

JKD1.7 运行时常量池,放到了堆中(永久代上)

JDK1.8 引入了元空间(运行时常量池还是在堆中)

方法区不复存在,元空间在JVM管理的内存之外,仅仅受限于物理内存大小

三  直接内存

      不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域;

如果使用了NIO,这块区域会被频繁使用,在java堆内可以用directByteBuffer对象直接引用并操作;

这块内存不受java堆大小限制,但受本机总内存的限制,可以通过-XX:MaxDirectMemorySize来设置(默认与堆内存最大值一样),所以也会出现OOM异常。

原理解释,为什么要有直接内存?

     数据通过网络发送给服务器网卡,OS(操作系统)对收到的数据进行管理(其中OS管理的这部分内存叫做系统态)java程序想要这份网络发送进来的数据,那么OS就要拷贝一份给到java程序内存中(JVM管理的内存叫用户态),但这种拷贝影响了应用程序的性能,能不能不拷贝?

    能! 所以才有了直接内存,OS和java程序都可以直接读取

四 深入辨析堆和栈

功能:

栈:以栈帧的方式存储方法调用的过程,并存储方法调用过程中基本数据类型的变量(int、short、long、byte、 float、double、boolean、char等)以及对象的引用变量,其内存分配在栈上,变量出了作用域就会自动释放;

堆:堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存 中;

线程独享还是共享:

栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。

堆内存中的对象对所有线程可见,堆内存中的对象可以被所有线程访问。

空间:

栈的内存要远远小于堆内存,栈的深度是有限制的,可能发生StackOverFlowError问题。

我们都知道,对象是存在于堆上的,由堆分配了内存存储,那么是不是对象一定都由堆分配内存呢

     答案:几乎所有对象都在堆上分配内存

那么哪些对象特殊,它们又如何给对象分配内存?

    答案:对线程私有的对象,而且对象使用范围是在方法内(未返回出方法外)的可以在栈上分配内存,而且栈上分配对象空间有个好处,方法执行完后栈帧就出栈了,对象也跟着销毁,不需要GC介入回收,可以提高性能

那么如何让新对象在栈帧上分配内存呢?

     使用逃逸分析(判断对象有没有逃出方法体,如把对象当返回值返回了,那就是逃逸了)

在IDEA中,针对类设置

上图就是开启了逃逸分析,在栈上分配对象内存

-server JVM运行的模式之一, server模式才能进行逃逸分析, JVM运行的模式还有mix/client

-Xmx10m和-Xms10m:堆的大小

-XX:+DoEscapeAnalysis:启用逃逸分析(默认打开)

-XX:+PrintGC:打印GC日志

-XX:+EliminateAllocations:标量替换(默认打开)

-XX:-UseTLAB 关闭本地线程分配缓冲

TLAB: 事先在堆里为每个线程分配一块私有内存,专门用来存放其实例对象(对象和普通对象一样,堆上的对象都能被程序访问到)

对栈上分配发生影响的参数就是三个,-server、-XX:+DoEscapeAnalysis和-XX:+EliminateAllocations,任何一个发生变化都不会发生栈上分配,因为启用逃逸分析和标量替换默认是打开的

-XX后面的+代表打开,-代表关闭

和普通的在堆上分配内存的方式相比,性能提升了几何倍

 普通模式耗时是 2000多毫秒

五  虚拟机中的对象

1 虚拟机给对象分配内存的方式

指针碰撞:剩余内存很规整的时候,指针后移就可以分配可用内存

空闲列表:剩余内存无规则,那么有个表来管理内存的使用情况,把表里空闲内存分配给对象

具体用哪种,要看虚拟机采用什么GC回收方式

2  创建对象的过程

检查加载类--->分配内存--->内存空间初始化(如初始化成员变量默认值)--->设置(如hashcode)---->对象初始化(构造方法)

3 对象的内存布局

对象头

   运行时数据,如hash码,对象琐的状态,年龄

   类型指针:指向这个对象的类

实例数据

   各种类型的字段内容

对齐填充

 hotspot虚拟机要求对象大小要是8字节的整数倍,所有当对象头+实例数据的大小 达不到8字节整数倍时,对齐填充来补全

6 堆参数设置和内存溢出实战

堆溢出

参数 : -Xms5m -Xmx5m -XX:+PrintGC

出现java.lang.OutOfMemoryError: GC overhead limit exceeded  一般是(某个循环里可能性最大)在不停的分配对象,但是分配的太多,把堆撑爆了。

出现java.lang.OutOfMemoryError: Java heap space一般是分配了巨型对象

栈溢出

参数:-Xss256k

java.lang.StackOverflowError  一般的方法调用是很难出现的,如果出现了要考虑是否有无限递归。

7  虚拟机栈带给我们的启示

      方法的执行因为要打包成栈桢,所以天生要比实现同样功能的循环慢,所以树的遍历算法中:递归和非递归(循环来实现)都有存在的意义。递归代码简洁,非递归代码复杂但是速度较快。所以不要轻易使用递归,除非你递归深度比较浅的情况。

8 栈帧详解

栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区的虚拟机栈(Virtual Machine Stack)的栈元素,不指一整块内存,它是个抽象概念。栈帧存储了方法的局部变量表,操作数栈,动态连接和方法返回地址等信息。第一个方法从调用开始到执行完成,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

运行时的栈桢结构

原文地址:https://www.cnblogs.com/hup666/p/13062285.html