今天闲来无事来,看一下Java中的内存模型和垃圾回收机制的原理。关于这个方面的知识,网上已经有非常多现成的资料能够供我们參考,可是知识还是比較杂的,在这部分知识点中有一本书不得不推荐:《深入理解Java虚拟机》,如今已经是第二版了。这本书就从头開始详细介绍了Java整个虚拟机的模型以及Java的类文件结构,载入机制等。这里大部分的知识点都是能够在这本书中找到的,当然我是主要还是借鉴这本书中的非常多内容的。以下就不多说了。进入主题吧。

首先来看一下Java中的内存模型图:

第一、程序计数器(PC)

程序计数器(Program Counter Register)是一块较小的内存空间,它能够看做当前线程所运行的字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来取下一条须要运行的字节码指令,分支、跳转、循环、异常处理、线程恢复等基础功能都须要这个计数器来完毕

注:程序计数器是线程私有的。每条线程都会有一个独立的程序计数器

第二、Java栈(虚拟机栈)

Java栈就是Java中的方法运行的内存模型,每一个方法在运行的同一时候都会创建一个栈帧(关于栈帧后面介绍)。这个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每一个方法从调用直至运行完毕的过程,就相应着一个栈帧在虚拟机栈中入栈到出栈的过程。

注:Java栈也是线程私有的。

异常可能性:对于栈有两种异常情况:假设线程请求的栈深度大于栈所同意的深度。将抛出StackOverflowError异常,假设虚拟机栈能够动态拓展,在拓展的时无法申请到足够的内存,将会抛出OutOfMemoryError异常

栈帧的概念:

栈帧用于支持虚拟机进行方法调用和运行的数据结构。

1) 局部变量表

局部变量表(Local Variable Table)是一组 变量值存储空间。用于存放 方法參数和方法内部定义的局部变量.局部变量表的容量以变量槽(Variable Slot。下称Slot)为最小单位. 一个Slot能够存放一个32位以内的数据类型,Java中占用32位以内的数据类型有boolean、byte、char、short、int、float、reference[3]和returnAddress 8种类型,对于 64位的数据类型,虚拟机会以高位对齐的方式为其 分配两个连续的Slot空间(long double).

2) 操作数栈

操作数栈(Operand Stack)也常称为操作栈,它是一个后入先出(Last In First Out,LIFO)栈,当一个方法刚刚运行的时候。这种方法的操作数栈是空的。在方法的运行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈/入栈操作,比如。在做算术运算的时候通过操作数栈来进行的,又或者在调用其它方法的时候是通过操作数栈来进行參数传递的。

举个样例:整数假发的字节码指令iadd在运行的时候操作数栈中最接近栈顶的两个元素已经存入了两个int类型的数值,当运行这个指令时。会将这两个int值出栈并相加,然后将相加的结果入栈。

3) 方法返回地址

一个方法開始运行后,仅仅有 两种方式能够退出这种方法。

第一种方式是运行引擎遇到随意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者(调用当前方法的方法称为调用者),是否有返回值和返回值的类型将依据遇到何种方法返回指令来决定,这样的退出方法的方式称为 正常完毕出口(Normal Method Invocation Completion)。第二种退出方式是。在方法运行过程中 遇到了异常,而且这个异常没有在方法体内得到处理,不管是Java虚拟机内部产生的异常,还是代码中使用athrow字节码指令产生的异常,仅仅要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这样的退出方法的方式称为 异常完毕出口(Abrupt Method Invocation Completion)。 一个方法使用异常完毕出口的方式退出,是不会给它的上层调用者产生不论什么返回值的

4) 附加信息

虚拟机规范同意详细的虚拟机实现添加一些规范里没有描写叙述的信息到栈帧之中,比如与调试相关的信息,这部分信息全然取决于详细的虚拟机实现,这里不再详述。在实际开发中,通常会把动态连接、方法返回地址与其它附加信息所有归为一类,称为栈帧信息。

第三、本地方法栈

本地方法栈与Java栈所发挥的作用是非常类似的,它们之间的差别只是是Java栈运行Java方法,本地方法栈运行的是本地方法。

注:本地方法栈也是线程私有的

异常可能性:和Java栈一样。可能抛出StackOverflowError和OutOfMemeryError异常

第四、Java堆

对于大多数应用来说,Java堆是Java虚拟机所管理的内存中最大的一块,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,差点儿所有的对象实例都在这里分配内存,当然我们后面说到的垃圾回收器的内容的时候,事实上Java堆就是垃圾回收器管理的主要区域。

注:堆是线程共享的

异常可能性:假设堆中没有内存完毕实例分配,而且堆也无法再拓展时,将会抛出OutOfMemeryError异常

第五、方法区

方法区它用于存储已被虚拟机载入的类信息、常量、静态常量、即时编译器编译后的代码等数据。

注:方法区和堆一样是线程共享的

异常可能性:当方法区无法满足内存分配需求时,将抛出OutOfMemeryError异常

1)运行时常量池

运行时常量池是方法区的一部分,Class文件里除了有类的版本号、字段、方法、接口等描写叙述信息外,另一项信息是常量池,用于存放编译器生成的各种字面量和符号引用,这部分内容将在类载入器后进入方法区的运行时异经常量池存放。

上面就介绍了Java的内存的几个模块的相关概念,事实上我们须要知道这些知识。最基本的目的是不要在项目中写那些OOM的代码,由于我们假设知道了内存模型之后。即使代码中出现了OOM的问题,我们能够定位到哪里出了问题。

以下也来看一下上面说到的几个内存模块导致的内存溢出异常问题:

(这个也是面试的时候经常会被问到:比方叫你写一段让堆内存溢出的代码,或者是问你假设假设改动堆大小)

第一、堆溢出

public class HeapOOM {

	static class OOMObject{}

	/**
* @param args
*/
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>(); while(true){
list.add(new OOMObject());
}
} }

我们上面看到堆主要是存放对象的,所以我们假设想让堆出现OOM的话。能够开一个死循环,然后产生新的对象就能够了。

然后在将堆的大小调小点。

加上JVM參数

-verbose:gc -Xms10M -Xmx10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+HeapDumpOnOutOfMemoryError。

就能非常快报出OOM:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

第二、栈溢出

package com.cutesource;

public class StackOOM {

	/**
* @param args
*/ private int stackLength = 1; public void stackLeak(){
stackLength++;
stackLeak();
} public static void main(String[] args) throws Throwable{
// TODO Auto-generated method stub
StackOOM oom = new StackOOM();
try{
oom.stackLeak();
}catch(Throwable err){
System.out.println("Stack length:" + oom.stackLength);
throw err;
} } }

我们知道栈中存放的方法运行的过程中须要的空间,所以我们能够下一个循环递归,这样方法栈就会出现OOM的异常了。

设置JVM參数:-Xss128k。报出异常:

Exception in thread "main" java.lang.StackOverflowError

打印出Stack length:1007。这里能够看出。在我的机器上128k的栈容量能承载深度为1007的方法调用。

当然报这样的错非常少见,一般仅仅会出现无限循环的递归中。另外,线程太多也会占满栈区域:

package com.cutesource;

public class StackOOM {

	/**
* @param args
*/ private int stackLength = 1; private void dontStop(){
while(true){
try{Thread.sleep(1000);}catch(Exception err){}
}
} public void stackLeakByThread(){
while(true){
Thread t = new Thread(new Runnable(){ @Override
public void run() {
// TODO Auto-generated method stub
dontStop();
} });
t.start();
stackLength++;
}
} public static void main(String[] args) throws Throwable{
// TODO Auto-generated method stub
StackOOM oom = new StackOOM();
try{
oom.stackLeakByThread();
}catch(Throwable err){
System.out.println("Stack length:" + oom.stackLength);
throw err;
} } }

这个栈的溢出,就是我们上面说到栈的时候的两种异常情况。

报出异常:Exception in thread "main" java.lang.OutOfMemoryError:unable to create new native thread

第三、方法区溢出

public class MethodAreaOOM {

	static class OOMOjbect{}

	/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
while(true){
Enhancer eh = new Enhancer();
eh.setSuperclass(OOMOjbect.class);
eh.setUseCache(false);
eh.setCallback(new MethodInterceptor(){ @Override
public Object intercept(Object arg0, Method arg1,
Object[] arg2, MethodProxy arg3) throws Throwable {
// TODO Auto-generated method stub
return arg3.invokeSuper(arg0, arg2);
} });
eh.create();
}
} }

我们知道方法区是存放一些类的信息等,所以我们能够使用类载入无限循环载入class,这样就会出现方法区的OOM异常。

手动将栈的大小调小点

加上JVM參数:-XX:PermSize=10M -XX:MaxPermSize=10M,运行后会报例如以下异常:

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space

第四、常量池溢出

public class ConstantOOM {

	/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
List<String> list = new ArrayList<String>();
int i=0;
while(true){
list.add(String.valueOf(i++).intern());
}
} }

我们知道常量池中存放的是运行过程中的常量,同一时候我们知道String类型的intern方法是将字符串的值放到常量池中的。所以上面弄能够开一个死循环将字符串的值都放到常量池中。这样常量池就会出现OOM异常了。由于常量池本身就是方法区的一部分,所以我们也能够手动的调节一下栈的大小。

总结:上面仅仅是从宏观的角度介绍了一下内存模型,详细关于内存中每一个区域的详细信息,能够阅读开头说到的那本非常不错的书籍。

当然我们在学习Java的时候能够分为四大模块:Java的Api、Java虚拟机(内存模型和垃圾回收器)、Java的Class文件、设计模式,关于Api的知识我们在工作的过程中用到的比較多,而且这部分内容全然是靠使用度,你用多了,api你自然就知道了。Java虚拟机和Java的Class文件的相关知识在工作中可能不一定能用到,可是这方面的知识能够让你更了解Java的整个体系结构。至于设计模式这个就是修炼的过程,也是最难的过程。得慢慢的体会其的强大之处。

Java虚拟机解析篇之---内存模型的更多相关文章

  1. Java虚拟机解析篇之---垃圾回收器

    上一篇说了虚拟机的内存模型,在说到堆内存的时候我们提到了,堆内存是Java内存中区域最大的一部分,而且垃圾回收器主要就是回收这部分的内容.那么这篇就来介绍一下垃圾回收器的原理以及回收的算法. Java ...

  2. 深入理解java虚拟机(6)---内存模型与线程 & Volatile

    其实关于线程的使用,之前已经写过博客讲解过这部分的内容: http://www.cnblogs.com/deman/category/621531.html JVM里面关于多线程的部分,主要是多线程是 ...

  3. 深入理解Java虚拟机读书笔记8----Java内存模型与线程

    八 Java内存模型与线程   1 Java内存模型     ---主要目标:定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节.     ---此处的变量和J ...

  4. 转载: Java虚拟机:运行时内存数据区域、对象内存分配与访问

    转载:  https://blog.csdn.net/a745233700/article/details/80291694  (虽然大部分内容都其实是深入理解jvm虚拟机这本书里的,不过整理的很牛逼 ...

  5. Java虚拟机垃圾收集器与内存分配策略

    Java虚拟机垃圾收集器与内存分配策略 概述 那些内存须要回收,什么时候回收.怎样回收是GC须要完毕的3件事情. 程序计数器.虚拟机栈与本地方法栈这三个区域都是线程私有的,内存的分配与回收都具有确定性 ...

  6. 【深入理解JAVA虚拟机】第二部分.内存自动管理机制.1.内存区域

    1.内存区域 根据<Java虚拟机规范(Java SE 7版)> 的规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域,如图所示.  程序计数器 当前线程所执行的字节码的行号指 ...

  7. 深入理解java虚拟机读书笔记1--java内存区域

    Java在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途.创建和销毁的时间,有一些是随虚拟机的启动而创建,随虚拟机的退出而销毁,有些则是与线程一一对应,随 ...

  8. 《深入理解 Java 虚拟机》学习笔记 -- 内存区域

    <深入理解 Java 虚拟机>学习笔记 -- 内存区域 运行时数据区域 主要分为 6 部分: 程序计数器 虚拟机栈 本地方法栈 Java 堆 方法区 如图所示: 1. 程序计数器(线程私有 ...

  9. 深入理解Java虚拟机读书笔记1----Java内存区域与HotSpot虚拟机对象

    一 Java内存区域与HotSpot虚拟机对象 1 Java技术体系.JDK.JRE?     Java技术体系包括:         · Java程序设计语言:         · 各种硬件平台上的 ...

随机推荐

  1. [NowCoder]牛客网NOIP赛前集训营-提高组(第六场)题解

    A.最长路 题意:给定有向图,每条边有个字符\([0,10^9]\),求每个点最长路字典序最小的方案.\(N,M\le 10^6\) 建反图跑拓扑排序,显然入过队的点都有最长路,考虑如何判断字典序大小 ...

  2. 16.REPL 命令

    转自:http://www.runoob.com/nodejs/nodejs-tutorial.html ctrl + c - 退出当前终端. ctrl + c 按下两次 - 退出 Node REPL ...

  3. 基于面向对象js的弹窗的组件的开发案例

    var aInput = document.getElementsByTagName("input"); 2 aInput[0].onclick = function() { 3 ...

  4. 用vue.js的v-for,v-if,computed写一个分页样式

    在学Vue,总想写个分页,先写了一个样式. 主要看思路: 思路简单,得到总页数,判断总页数,循环. 先判断总页数是否需要分页,总页数==1页就不分了. 再判断总页数<11就不用--. 总页数&g ...

  5. 四、Docker+Tomcat

    原文:四.Docker+Tomcat 一.下载Tomcat镜像 具体可以search 搜索tomcat 相关镜像 docker pull sonodar/jdk8-tomcat8 二.创建容器 doc ...

  6. Direct2D 图形计算

    D2D不仅可以绘制,还可以对多个几何图形对象进行空间运算.这功能应该在GIS界比较吃香. 这些计算包括: 合并几何对象,可以设置求交还是求并,CombineWithGeometry 边界,加宽边界,查 ...

  7. 配置mysql的ODBC数据源

    如果你已经安装好了mysql和mysql连接驱动,则可以向下进行了 打开控制面板,以小图标的形式查看,找到管理工具 打开管理工具,找到数据源(odbc),打开 在图片中所圈出的三个标签中随便选一个,点 ...

  8. ArcGIS 10 影像、栅格数据格式批量转换

    转自原文 ArcGIS 10 影像.栅格数据格式批量转换 在做三维场景的时候,经常会涉及多种不同格式DEM数据或者影像的转换,如ASCII.GRID.IMG.TIFF等等,遇到大数据量时,我们就需要批 ...

  9. HDU1203 I NEED A OFFER! 【贪心】

    I NEED A OFFER! Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) ...

  10. 看好腾讯,鄙视百度(腾讯的核心竞争力,不是超过10亿的QQ的注册用户,也不是某一项产品、技术方面优势,而是“耐心”:懂得在合适的时间推出合适的产品。”)

    百度,自始至终只是一个低劣的模仿者,且一切向前看,完全违背了一个搜索引擎所应该遵循的基本原则.谁给的钱多就能搜着谁,这跟贩毒有什么区别? 腾讯也在模仿别人,但是,它是模仿然后超越.在中国互联网发展历史 ...