读《深入理解JAVA虚拟机》-内存篇

新开一个坑,读这本书的主要目的在于拉进自己和JAVA的距离,了解他的“内心”,并能更好的发挥它的作用。

首先我会用我读书后的理解从内存的各个部分的作用,以及每一部分可能发生的oom进行介绍,然后加上一些我在读书后想到的问题。

JAVA的数据区包括:有六个部分,其中线程共享的部分是方法区的堆,线程私有的区域是栈空间(虚拟机栈)本地方法栈和程序计数器。

程序计数器

是不会发生oom的先来介绍一下,他的主要作用是协调线程的调用。我们知道通常所说的线程并发并不是真正的所有线程同时工作,而是轮流使用处理器的资源,那么如何切换,上次任务执行到哪里,负责这项任务的就是程序计数器了。

虚拟机栈

也就是我们通常所说的JAVA的栈空间,主要存储了对象的引用,调用的方法以及基本数据参数值,虚拟机栈和本地方法栈的回收并不依赖于gc,而是取决于方法的执行周期,变量的作用域,方法结束了相关的内存就会释放掉。可以看出虚拟机栈其实并没有存储很多信息,所以默认的占空间大小也就1M左右,有两种情况会产生栈空间的oom,一种是方法深度过深,因为这样的关系中由于生命周期一直没有结束,所以会有很多基本数据不能释放积少成多。还有一种是线程过多,可能也会有栈空间溢出。

本地方法栈

我理解这个部分类似于一个语言的适配器,因为JAVA也存在不足,也需要通过其他语言实现的功能,本地方法栈就是为这种native方法提供服务。oom的方式类比栈空间。

下面就是两个线程共享的空间

堆空间

堆空间是我们最熟悉的也是内存分配最多的地方,通过Xmx和Xms来设置堆内存分配的资源大小。堆内存主要存储对象(数组)。堆内存不足会抛出oom:java heap space的异常。这里顺便提一句,这种问题有可能是因为系统资源不足,还有可能是内存泄露。还有一种可能出现的oom是gc overhead limit exceed,他发生于系统花费98%的时间gc但是只回收了小于2%的资源,这是一个即将oom的信号。如果出现堆内存溢出的情况,查询方式一般是记录并获得heap dump文件,然后使用工具(比如 memory analyser)分析内存中的对象情况。

方法区

方法区同样作为线程共享区域,它主要存储类的结构,对象的静态变量,常量和基本变量,可以通过设置PermSize来设置他的大小,大小不用设置很大,方法区的使用情况主要取决于加载类的数量和大小。方法区发生oom会抛出PermGen space的异常,解决方式一般是增加方法区的space。

下面就是几个思考的问题:

问题一:应用实际使用空间问题

在实际应用中,我们经常会说:我们分配了2g/4g/……空间给我们的应用。这句话是什么意思呢?其实这并不代表我们真正给我们的应用这么大的空间,这种说法一般是我们在容器中设置了Xms xms参数。其实这个参数是我们分配的堆空间大小,并不是可使用空间。或者就是我们设置的permsize,是方法区的大小。了解这些参数是很重要的,比如我们看到permgen space oom的alert,然后查看dump文件发现其实内存的占用可能并不想我们想像的那么多。同理,栈空间也是如此。

问题二:栈空间溢出问题

我之前解决过一个oom的问题,当时以为栈空间不足会向堆空间申请内存,其实不是这样的。栈空间的大小默认一般是1M,每个线程有自己的初始大小,不够用的时候申请新的空间(当然也可以用过配置Xss设置线程大小),所以我遇到的getNewTla和create new native thread的问题其实是栈空间不足的问题,线程不能从栈中申请到新的内存。

问题三:单例模式相关问题

上周开会讨论过一个单例的线程安全问题,其中涉及到那些参数是线程安全的。那我们知道对象的参数存在于堆空间,是线程共享的,线程不安全。而局部变量是在栈内存,是线程私有的,线程安全。另外突然想到一个问题,单例是否需要垃圾回收,单例的对象只声明一次,且被自己的类中的静态属性引用,存在于方法区,gc的原则其一是该类所有的实例都被回收,那么可以发现单例是不满足该条件的,所以不会被回收。

你可能感兴趣的:(读《深入理解JAVA虚拟机》-内存篇)