第一篇-jvm运行时内存分析

本文作者:黄海燕,叩丁狼高级讲师。原创文章,转载请注明出处。

开篇:有些程序员觉得学习jvm是一个装逼的行为开发中用不到,或者是为了应付面试而去学习的,我觉得如果你是甘于平庸的码农只是写写业务代码的,确实是没有学习的必要的.但是如果你对你的职业生涯是有规划想往架构师、高级程序员等方向发展的,或者是解决内存泄漏、gc频繁导致的程序响应慢等问题,或者是写出最优的代码,那么了解整个jvm的执行流程、内存模型、gc日志分析、gc算法、java自带工具等内容是很有必要的。

##1.了解什么是jvm?

首先vm指的是虚拟机(Virtual Machine)指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统,比如:VMware,它能在电脑本身的操作系统中模拟出其他独立的操作系统。
而jvm就是java在实际计算机上面仿真模拟各种计算机功能来实现的,拥有自己完善的精简架构,例如:处理器,堆栈,寄存器和相应java指令集的系统.

##2.jvm内存概述

为什么需要需要学习jvm内存结构?
程序的运行时,程序所需内存是由jvm本身进行管理的,在实际开发中经常会出现由于硬件或者是由于程序本身问题出现的内存溢出和内存泄漏问题,如果我们不了解jvm内存结构,我们无法找到问题本身从而去解决这些问题,和会根据实际情况对程序进行优化.

叩丁狼教育.png

程序员编写了java源文件,通过javac命令启动javac工具将源文件编译成为与平台无关的class字节码文件(编译过程不是我们关心的),class字节码文件通过类加载加载进内存中,运行时内存分为以下几部分:

JVM运行时内存=线程共享内存+线程私有内存

所以真要结合线程,内存应该表示如下:

叩丁狼教育.png

堆:

1)垃圾收集器管理的主要区域,可细分为新生代、老年代,新生代可再细分为Eden、From Survive、To Survive空间
2)存放对象实例和数组(特殊对象)
3)被所有线程共享.
4)内存大小可以设置

方法区:

1)方法区和永久代(PermGen space)不能等同,因为在其他的jvm中可能没有永久代的说法,但是在HotSpot上把GC分代收集扩展至方法区,我们可以说使用永久代来实现方法区。所以为了简化理解,在hotspot中我们可以将方法区理解为永久代。
2)因为在HotSpot上把GC分代收集扩展至方法区,所以方法区能被gc管理。
3)方法区中可以存放已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
4)被所有线程共享。
5)方法区中包含运行中的常量池,用于存放字面量和符号等,但是注意,这些数据不仅仅是编译的时候产生,运行的时候也能产生常量,比如String中的intern方法能在运行时产生常量,所以常量池是可以具有伸缩性的。
6)内存大小可以设置

java栈:

1)栈内存为线程私有的空间,每个线程都会创建私有的栈内存,用于存储栈帧(Frames)

叩丁狼教育.png
叩丁狼教育.png

a)局部变量数组:存放一个方法执行时的所有变量,包括this引用(当前对象的引用)、方法参数、方法内部的局部变量,注意如果对象方法this存在在局部变量数组0的位置上,如果是静态方法局部变量数组0的位置是变量,另外long和double类型的局部变量因为是64位双精度类型,所以占局部变量数组两个连续的位置,其他32位单精度的占一个位置。

b)操作栈(求知栈):可以理解为JVM线程的工作区,执行运算的区域。为了同学们能够了解运算过程,在网上下了个动图,如下:

public class Demo {  
    public static void foo() {  
        int a = 1;  
        int b = 2;  
        int c = (a + b) * 5;  
    }  
}  

叩丁狼教育.gif

c)返回值:跳转到当前被调用的地方,但是返回分为两种情况,一是正常返回,根据方法定义返回给调用者,二是出现异常,不会将返回值传给调用者。

d)动态链接:常量池中查找父类引用指向子类对象的真实实例对象。也就是方法覆盖调用那个方法就是动态链接决定的。

2)由于栈中存储的栈帧会在方法执行完毕后,就会自动出栈释放内存资源,所以不需要gc管理。

3)线程私有。

4)栈内存大小可以设置(注意设置的是栈不是栈帧),但是不能设置太大和太小,设置小了,一个方法调用创建一个栈帧,方法调用多了,装不下那么多栈帧,出现栈溢出。设置大了,多线程的时候就会同时创建很多个栈内存,整个栈内存=线程数*栈内存,导致总栈的内存过大,内存溢出。所以一般大小设置为128kb或者256kb.

本地方法栈:

本地方法栈的功能和特点类似于虚拟机栈,不同的是,本地方法栈服务的对象是JVM执行的native方法,而虚拟机栈服务的是JVM执行的java方法.,线程私有。

程序计数器:

可以看做是当前线程所执行的字节码的 行号指示器,通过程序计数器记录当前执行到第几条java指令。字节码指令,分支、循环、跳准、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。若执行Java方法则计数器记录字节码指令地址,若执行Native方法则计数器为Undefined。线程私有。程序计数器是java虚拟机中唯一一块不会产生error的内存区域

出现的错误:
StackOverflowError:栈溢出错误

错误位置 错误原因
java虚拟栈/本地方法栈 1)栈空间设置太小
2)方法调用太多导致栈帧太多,栈放不下,例如:方法无限递归错误位置

OutofMemoryError:内存溢出错误,简称OOM错误

错误位置 错误原因
java虚拟栈/本地方法栈 1)栈空间设置太大
2)线程创建过多,栈总内存=线程数*单个栈内存,当栈总内存>(进程内存-堆-方法区),就会出现内存溢出
1)不断的创建对象,并且保证对象是被引用的来避免垃圾回收机制清除这些对象
方法区 1)方法区中的运行常量池运行时可以存放数据,例如String中的intern方法。存放数据内存>方法区内存

结束语:本章只是作为一个抛砖引玉的作用,其实我们还没有真正深入到jvm中,但是要学习jvm我们还是要先了解本章的内容,作为学习java虚拟机的基础。欲知后事请听下回分解,敬请期待下一章《第二篇-gc内存分配》
>

猜你喜欢

转载自blog.csdn.net/wolfcode_cn/article/details/89403896