从上帝视角看Java如何运行
JVM内存结构
可以看出JVM从宏观上可以分为 ‘内部’ 及 ‘外部’ 两个部分(便于记忆理解):
‘内部’包含:线程共享(公有)数据区 和 线程隔离(私有)数据区
‘外部’包含:类加载子系统、垃圾回收器、执行引擎、本地库接口、本地方法库
以上部件构成了整个jvm,接下来我们一个一个零件拆开了看。
class文件
类加载子系统
类加载子系统:负责查找和装载class文件,将其中的二进制数据加载到jvm中。
字节码 --> 加载 --> 验证 --> 准备 --> 解析 --> 初始化
加载:通过类的完全限定名找到类文件所在位置,根据其中的字节码创建java.lang.Class对象,所以才会说万物皆对象,我们也可以继承ClassLoader,重写findClass方法来自定义实现类加载器。默认情况下我们都使用AppClassLoader
验证:确保加载的字节码的是否符合虚拟机的要求,是java提供的一种自我保护机制,不让其危害虚拟机安全。其主要包括四种验证,字节码验证、文件格式验证,元数据验证、符号引用验证。
准备:为类变量分配地址和初始化值,类变量会分配到方法区(元空间)中,这里的初始化是指该数据类型的默认初始值,例如int对应的是0,long对应的0L,只有在初始化时才会动显示赋值
解析:把类中的二进制数据中的符号引用转换为直接引用;例如我们通过user.getInfo();这里的.getInfo()就是符号引用,在解析阶段会将它指向真正的内存位置,这就是直接引用
初始化:主要为类的静态变量赋予正确的值,比如int num = 10; 这里num的值会从准备阶段的0变为10;并且若该类有父类,会对其进行初始化操作;如果类中有初始化语句,系统会按照顺序进行初始化
双亲委派模式
双亲委派:自底向上检查是否加载成功,自顶向下尝试加载。
当一个类加载器收到类加载请求,它不会自己进行加载,而是将该请求丢给父类加载,如果父类还存在父类,则会依次向上请求,直到到达顶级加载器,如果父类加载器能加载完成就返回加载成功,否则子类加载器才会自己尝试加载。
System.out.println(Test.class.getClassLoader());
System.out.println(Test.class.getClassLoader().getParent());
System.out.println(Test.class.getClassLoader().getParent().getParent());
System.out.println(String.class.getClassLoader());
通过代码验证,可以很轻松的了解 AppClassLoader -> ExtClassLoader -> BootstrapClassLoader 这三层的关系。
类加载的三种方式
1. new关键字加载
User user = new User();
静态加载,在运行时候通过new关键字创建类实例
2. Class.forName()加载
Class clazz = Class.forName(“User”);
Object user=clazz.newInstance();
动态加载,通过Class.forName()来加载类,然后调用类的newInstance()方法实例化对象
3. ClassLoader 实例的 loadClass() 方法
Class clazz = classLoader.loadClass(“User”);
Object user=clazz.newInstance();
动态加载,可通过继承ClassLoader实现自定义类加载器
线程私有和线程公有
JVM内存区从宏观上可以分为 线程私有 和 线程公有 两块。
线程私有部分
这部分没有线程安全问题,随着线程执行结束而结束;包含程序计数器、虚拟机栈、本地方法栈三个部件。
程序计数器:
程序计数器也叫PC寄存器,作用是cpu进行切换的时候,指向当前时刻需要获取指令的位置。
特点:
- 线程私有
 - 一块较小的区域
 - 记录程序执行的位置
 - 不存在内存溢出OutOfMemoryError
 
虚拟机栈:
栈数据结构实现,入口和出口只有一个,称之为入栈和出栈,先进后出(FILO)
栈的作用主要是执行方法,先执行的方法在最下面,然后依次放入,方法执行完毕之后从上往下依次退出;所以方法执行就是压栈,方法结束就是出栈(销毁栈帧)。
public void start(){
    say();
    run();
}
虚拟机栈如何执行
栈帧
栈帧存在Java虚拟机栈中,是虚拟机栈中的单位元素。方法执行会创建栈帧,一个方法就是一个栈帧,一个栈帧分为四个部分:
1. 局部变量表
存放方法参数或者内部定义的一组变量列表;例如方法中声明的对象:
User user = new User(); //局部变量user
2. 操作数栈
执行字节码指令的时候使用,通俗的讲就是方法的执行在操作数栈中进行,通过压栈和出栈进行访问
3. 动态链接
Java运行期间是动态链接的,需要将指向方法的符号引用转换为直接引用(内存地址);在类加载解析阶段,将符号引用转换为直接引用称之为静态解析。而此处正好就是动态链接
user.getInfo(); //找到这个getInfo()方法的内存位置
4. 返回地址
方法不管正常执行结束还是异常退出,需要返回方法被调用的位置
以上四个部分对应方法执行的过程。虚拟里面包含很多个栈帧,每个方法对应一个栈帧。
将一个class文件,通过bin/javap.exe文件进行反汇编可以查看出以上四个部分。
栈溢出:当栈的深度大于虚拟机允许会报StackOverflowError,-Xss可设置大小
/** 递归演示如何栈溢出 */
public static int num = 0;
public static void a(){
num++;
a();
}
public static void main(String[] args) {
try{
a();
}catch (Exception ex){
System.out.println("调用次数:"+num);
}
}
内存溢出:当栈需要扩展而无法申请空间会报OutOfMemoryError
本地方法栈
本地方法栈和虚拟机栈类似,区别在于虚拟机栈主要为jvm执行字节码服务,而本地方法栈为Native方法服务,即本地方法服务;所以本地方法栈也是一块内存私有区域,与虚拟机栈相同也有同样的异常问题。
特点:
- 与虚拟机栈基本类似
 - 区域在于本地方法栈为Native方法服务(windows下调用dll文件)
 - Sun HotSpot将虚拟机栈和本地方法栈合并
 - 有
StackOverflowError和OutOfMemoryError 
线程公有部分
这部分存在线程安全问题,平常我们所指的内存优化,溢出等问题都是需要关注这个区域。包含堆、方法区(也叫元空间)两个部件。
方法区(元空间)
类加载器加载类的时候,会将一些类的元数据信息(字节码)保存在这个区域,例如:类变量,静态方法,普通方法等,方法区是线程共享的,多个线程能用到同一个类
jdk1.7合并方法区到了堆里面
jdk1.8保留了方法区的概念,只不过实现方式不同,jdk1.8称为元空间,与堆不相连,但是与堆共享物理内存,逻辑上可以认为是在堆中
特点:
- 线程共享
 - 存储类信息、常量、静态变量、方法描述等信息
 - HotSpot虚拟机中称之为永久代
 - GC很少回收这个区域
 - 存在
OutOfMemoryError,可以通过-XX:MaxPermSize设置大小 
堆
堆中用于存放所有实例化对象和数组,堆中信息线程共享,所有jvm部件中分配内存中最大的区域,在虚拟机启动时就创建,垃圾回收器主要管理该区域,堆分为新生代(占堆内存1/3)和老年代(占堆内存2/3),新生代更细致可以分为Eden、From Survivor、To Survivor空间,比例8:1:1 ;可以通过-Xmx、-Xms设置大小
在堆中产生了一个实例对象或数组,可以在栈中声明一个变量,用于指向堆中的对象,该变量的取值等于堆中对象的内存地址,所以我们在打印变量名的时候是一串内存地址
Test test = new Test();
System.out.println(test); //输出Test@1b6d3586
万物皆对象,当我们在实际开发中,创建了许多对象,为了防止内存泄露,java确保有效的使用内存,会由java虚拟机自动垃圾回收器来管理;且把堆分为新生代和老年代进行管理
新生代与老年代
  新生代是Java对象出生的地方,是新对象分配内存的地方,大部分对象存活时间都不需要太久,这个区域会频繁触发MinorGC进行垃圾回收;
  而老年代存放的都是存活时间较久或者内存较大的对象,所以Full GC不会频繁执行。
Minor GC
发生在新生代中的垃圾回收机制,采用复制算法(扫描存活对象,复制到一块新内存空间中),From Survivor 和 to Survivor是相对的,也就是说Minor GC发生时,Eden区和其中一个Survivor区会把一些仍然存活的对象放置另外一个Survivor 区,然后清理Eden区和之前的Survivor 区,下次同理,当达到一定 ‘年龄’ 后,新生代会把对象放入老年代(每发生一次Minor GC增加1岁,默认15岁)
Full GC
发生在老年代中的垃圾回收机制,采用标记-清除(标记存活的对象,清除未标记的对象,即需要回收的对象),因为老年代中的对象较稳定,所以发生Full GC的频率相对Minor GC较少,但是一次回收的时间会比Minor GC更长
从上帝视角看Java如何运行的更多相关文章
- JAVA继承:编译与运行的关系(编译看左边,运行看右边)
		
"成员变量,静态方法看左边:非静态方法:编译看左边,运行看右边." 意思是:当父类变量引用子类对象时(Fu f = new Zi();),在这个引用变量f指向的对象中,他的成员变量 ...
 - 使用 Docker 搭建 Java Web 运行环境
		
黄勇的博客 Docker 是 2014 年最为火爆的技术之一,几乎所有的程序员都听说过它.Docker 是一种“轻量级”容器技术,它几乎动摇了传统虚拟化技术的地位,现在国内外已经有越来越多的公司开始逐 ...
 - 转:使用 Docker 搭建 Java Web 运行环境
		
原文来自于:http://www.codeceo.com/article/docker-java-web-runtime.html Docker 是 2014 年最为火爆的技术之一,几乎所有的程序员都 ...
 - JAVA EE 运行环境配置(包含JAVA SE)
		
JAVA EE 运行环境配置(包含JAVA SE) 1.下载并安装jre-7u7-windows-i586.exe (最新的JAVA运行环境) 2.下载并安装java_ee_sdk-6u4-jdk7- ...
 - 从 HelloWorld 看 Java 字节码文件结构
		
很多时候,我们都是从代码层面去学习如何编程,却很少去看看一个个 Java 代码背后到底是什么.今天就让我们从一个最简单的 Hello World 开始看一看 Java 的类文件结构. 在开始之前,我们 ...
 - Java虚拟机运行时栈帧结构--《深入理解Java虚拟机》学习笔记及个人理解(二)
		
Java虚拟机运行时栈帧结构(周志明书上P237页) 栈帧是什么? 栈帧是一种数据结构,用于虚拟机进行方法的调用和执行. 栈帧是虚拟机栈的栈元素,也就是入栈和出栈的一个单元. 2018.1.2更新(在 ...
 - 带着新人看java虚拟机01
		
1.前言(基于JDK1.7) 最近想把一些java基础的东西整理一下,但是又不知道从哪里开始!想了好久,还是从最基本的jvm开始吧!这一节就简单过一遍基础知识,后面慢慢深入... 水平有限,我自己也是 ...
 - [零] Java 语言运行原理 JVM原理浅析 入门了解简介 Java语言组成部分 javap命令使用
		
Java Virtual Machine 官方介绍 Java虚拟机规范官方文档 https://docs.oracle.com/javase/specs/index.html 其中以java8的为 ...
 - 使用 Docker 搭建 Java Web 运行环境(转)
		
原文 http://www.importnew.com/21798.html Docker 是 2014 年最为火爆的技术之一,几乎所有的程序员都听说过它.Docker 是一种“轻量级”容器技术,它几 ...
 
随机推荐
- 软件WEB自动化测试工具之智能元素定位
			
江湖一直有着这么一句名言“天下武功,唯快不破".那么在软件测试领域,自然而然我们会想到软件自动化测试.软件自动化测试的实现自然离不开软件自动化测试工具.软件自动化测试工具是软件自动化的载体, ...
 - 基于osg的python三维程序开发(三)------几何形体及纹理
			
def createScene(): geode = osg.Geode() pointsGeom = osg.Geometry() vertices = osg.Vec3Array() vertic ...
 - html标签及网页语义化理解
			
最近重新看了一遍html标签的知识,有很多新的体会,对语义化有了一个新的理解. 那么什么叫做语义化呢,说的通俗点就是:明白每个标签的用途(在什么情况下使用此标签合理)比如,网页上的文章的标题就可以用标 ...
 - 关于SDL的一些坑:找不到WinMain,不显示控制台,添加链接库等
			
目录: 用CMake构建SDL时报错 Gcc添加链接库 Gcc找不到入口(WinMain) 让SDL启动时不带控制台窗口 用CMake构建SDL时报错 root@ubuntu:~/SDL# cmake ...
 - Vue在点击内部元素时(获得焦点),怎样让外部div元素样式变化?
			
问题: div内部有很多元素,div. p. span .input等,各元素有嵌套,现在点击某元素时需要最外面这个div边框高亮,例如,点击了input开始输入 假设html 结构如下 <d ...
 - js 模拟滚动条
			
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
 - 在ES批量插入数据超时时自动重试
			
当我们使用ES批量插入数据的时候,一般会这样写代码: from elasticsearch import Elasticsearch,helpers es =Elasticsearch(hosts=[ ...
 - 【Python】2.12学习笔记 变量
			
变量 关于变量我有一个不能理解的,关于全局变量作用域与地址的问题,学函数的时候我可能会搞懂它并且写下来 另外,其实昨天说的是有些不准确的,\(Python\)里的变量不是不用声明类型,只是声明方式特殊 ...
 - delphi真正实现延时暂停功能
			
用delphi怎么实现延时功能?在delphi中有一个sleep()函数是用来暂停线程的,使用了它好像和死掉了似得,不好用,这么简单的延时动作用Timer控件有显得复杂了.下面给大家分享一个真正好用的 ...
 - 一般人不知道的Flask框架SQLAlchemy的那些事
			
目录 SQLAlchemy 1.介绍 2.简单使用(能创建表,删除表,不能修改表) 3.一对多关系 4.多对多关系 5.操作数据表 6.基于scoped_session实现线程安全 7.基本增删查改 ...