今天闲来无事来,看一下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. 决策树之C4.5算法学习

    决策树<Decision Tree>是一种预測模型,它由决策节点,分支和叶节点三个部分组成. 决策节点代表一个样本測试,通常代表待分类样本的某个属性,在该属性上的不同測试结果代表一个分支: ...

  2. JS/CSS 在屏幕底部弹出消息

    <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title> ...

  3. js的数据类型和typeof数据类型

    js的数据类型:number,string,null,undefined,Boolean,object typeof数据类型:number,string,object,function,undefin ...

  4. 图文具体解释 IntelliJ IDEA 15 创建 Maven 构建的 Java Web 项目(使用 Jetty 容器)

    图文具体解释 IntelliJ IDEA 15 创建 maven 的 Web 项目 搭建 maven 项目结构 1.使用 IntelliJ IDEA 15 新建一个项目. 2.设置 GAV 坐标 3. ...

  5. nagios 安装配置(包含nrpe端)全 (一)

    一.nagios安装: 1.安装下面命令: 这是本人监控服务时自己定义插件所用到的几个系统命令.可不安装. (1)iostat:监控磁盘IO信息: apt-getinstall sysstat (2) ...

  6. 开源课程管理系统(CMS):Moodle

    开源课程管理系统(CMS):Moodle 一.总结 1.php开发的cms,可借鉴参考用 二.Moodle(百度) Moodle(Modular Object-Oriented Dynamic Lea ...

  7. Node组装启动过程

    elasticsearch的启动过程是根据配置和环境组装需要的模块并启动的过程.这一过程就是通过guice注入各个功能模块并启动这些模块,从而得到一个功能完整的node.正如之前所说elasticse ...

  8. vue-cli打包项目后,可以修改配置文件

    问题: 前端需要修改后台服务器地址url,写好的配置文件会在npm run build 后压缩在一起,传到运行的前端服务器上后,需要到前端打包的源码,找到url地址进行修改.如果不在打包的源码修改,则 ...

  9. 使用Tomcat发布war包

    第一步:下载tomacat 1.下载地址:http://tomcat.apache.org 2.解压后目录如下 3.双击bin文件夹下startup.bat 即可启动tomcat, 计算机会弹出控制台 ...

  10. angular6添加material-svgIcon

    1. app/assets/util/util.svg.ts 统一管理svg字体库,避免各个模块分散加载.所以使用公共文件统一处理 再到core.module.ts中引入.在core模块下的所有组价都 ...