素小暖讲JVM:第一章 走进Java,第二章 Java内存区域与内存溢出异常
本系列是用来记录《深入理解Java虚拟机》这本书的读书笔记。方便自己查看,也方便大家查阅。
欲速则不达,欲达则欲速!
第一章 走进Java
一、Java虚拟机发展史
这部分一带而过吧,有兴趣的可以阅读原著!
二、Java内存管理,class文件结构与执行引擎,编译器优化,多线程并发的简单解释(后续章节详述)
- 自动内存管理讲的是Java运行时数据区里的部分分为内存划分和垃圾收集器
- 执行子系讲的是类文件管理、类加载机制、字节码执行引擎
- 优化分为编译器优化和运行期优化
- 并发讲的是虚拟机是如何实现多线程的
具体可以结合下面的jvm结构图来看,这张图是基于JDK1.7的。JDK1.7之前,常量池是存放在方法区中的,1.7之后常量池存放在堆中。
第二章 Java内存区域与内存溢出异常
我们知道在C++语言里,如果想使用一个对象,需要对其进行new操作,如果不用这个对象了,需要对其进行delete操作,一旦开发人员忘记写delete语句,就会造成内存泄漏。
而java就很聪明,它将手动改为自动,把内存的控制权交给了虚拟机,下面我们就来探究一下JVM是怎么进行自动内存管理的。
手动内存管理分为两部分:给对象分配内存和回收分配给对象的内存。
一、运行时数据区域
线程公有
在运行时数据区中,方法区和堆是属于线程公有的,也就是两块区域是循环利用的,所以要对其进行垃圾回收。
线程私有
程序计数器、虚拟机栈、本地方法栈是属于线程私有的,与其线程“同生共死”,属于一次性的,不需要进行垃圾回收。
1、程序计数器
程序计数器中存放的是当前线程所执行的字节码的行号。jvm工作时,就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
2、Java虚拟机栈
Java虚拟机栈是线程私有的,它的声明周期与线程相同。
虚拟机栈里面存储的是栈帧,栈帧里面存储的是局部变量表,操作数栈,动态链接,方法出口等信息。
- 栈中的栈帧
每个方法从调用到执行的过程就是一个栈帧在虚拟机栈中入栈到出栈的过程。
- 栈帧中的局部变量表
存放的是编译期可知的各种基本数据类型,对象引用类型。所以其所需要的内存空间在编译期间就能完成分配,在运行期间不会改变其大小。
在分配基本数据类型所占的空间时,除了64位的long和double类型的数据会占用2个局部变量空间,其余的数据类型只占用1个。
3、本地方法栈
本地方法栈和虚拟机栈的作用是相同的,只不过虚拟机栈执行的是java方法,本地方法栈执行的是Native方法。
java方法就是开发人员写的java代码,Native方法就是一个java调用非java代码的接口。
4、Java堆
如果说栈解决的是程序运行问题,即程序如何处理数据;则堆解决的是数据存储问题,即数据怎么放,放在哪。
此内存区域的唯一目的是存放对象实例,Java堆是垃圾收集器管理的主要区域。
特定:堆是虚拟机内存中最大的一块,大概占内存的三分之二,堆可处于物理上不连续的内存空间中,只要逻辑上是连续的即可。
5、方法区
方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的信息、常量、静态变量、即时编译器编译后的代码等数据。方法区也可以看作是Java堆的一部分。
这部分区域可以不选择垃圾回收,这区域的内存回收主要针对常量池的回收和对类型的卸载。
这部分可能会导致未完全回收而导致内存泄漏。
二、内存分配
这部分我们说一下对象在java堆中是如何分配、布局、访问以及内存分配的原则。
1、对象的创建
我们用new来创建对象,来看看系统运行到new时,虚拟机在干什么。此时的类就像一块肉,他要经过层层安检,才能到达人类的饭桌。
(1)查看在常量池中是否有对应的符号引用。【在方法区中进行】
(2)查看此类是否被加载、解析和初始化过。【在方法区中进行】
(3)领取新生对象的内存。有两种方式:指针碰撞和空闲列表。【在堆中进行】
(4)将分配到的内存空间初始化为零。
(5)对对象进行必要的设置,比如其实哪个类的实例,对象的哈希码之类的。这些信息存放在对象的对象头中。
(6)如果java代码对对象进行了赋值,则会走到第六步,执行<init>方法。此方法的作用就是对对象进行初始化。
2、对象的内存布局
对象在内存中的存储布局分为三个部分:对象头+实例数据+对其补充
- 对象头
对象头里面有两部分信息:
(1)运行时数据,包括哈希码、GC分代年龄、锁状态标志灯。
(2)类型指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
- 实例数据
实例数据中存放的是代码中定义的各种类型的字段内容。
- 对其填充
对齐填充起的是占位符的作用,不是必然存在的,其只要保证对象的大小是8字节的整数倍即可。
3、对象的访问定位
建立完对象后,我们就可以使用对象了。通过句柄和直接指针两种方式。
- 句柄
句柄访问就是在java堆中划分出一块内存区域作为句柄池,句柄中包含了实例数据和类型数据各自具体的地址信息。
- 直接指针
直接指针之所以“直接”,是因为它去除了句柄这个中介。所以在速度上比句柄快。
在HotSpot虚拟机中,使用的是这种方式。
说完了对象在java堆中是如何分配,布局和访问的,接下来我们说说内存分配的原则。
4、内存分配的原则
堆大致分为新生代,老年代,永久代。对象的内存分配主要分配在新生代的Eden区,少数情况下会直接分配到老年代中。分配的规则不是100%固定的,取决于垃圾收集器组合和参数设置等。下面有几条分配原则可供参考。
- 对象优先在Eden分配
- 大对象直接进入老年代
- 长期存活的对象将进入老年代
- 动态对象年龄判定
- 空间分配担保
三、垃圾回收机制
英文名儿是GC(Garbage Collection)。
1、哪些内存需要回收?
堆和方法区中的内存需要回收,其它的不用回收。
因为只有堆和方法区是线程共享的,其余的是与线程“同生共死”的,线程结束,内存自然就跟着回收了,所以不用管它们。
2、什么时候回收?
(1)在堆里面:
当对象“死了”的时候就要对其进行内存回收了。啥叫对象死了?就是没有地方引用它了,它无用了。那怎么判断它是否死了呢?有两种方法。
- 引用计数算法
给对象添加一个引用计数器,每当有一个地方引用它时,计数器的值就+1,当引用失效时,计数器的值就-1,当计数器的值为0时,代表此对象已不被引用,也就是“可以死了”。
但这有一个弊端,就是循环引用的问题。就像下图,堆里的两个对象即使无用了也没办法对其进行回收,因为它们互相引用着,计数器的值至少为1。
- 可达性分析
所有生成的对象都是一个成为“GC Roots”的根的子树。从GC Roots开始向下搜索,搜索所经过的路径成为引用链。当一个对象到GC Roots没有任何引用链可以到达时,就称这个对象是不可达的,也就是可以被GC回收了。这个是java中采用较多的方式。
就像下图中的堆中未被引用的对象,就可以对其进行回收了。
怎么判断一个对象是否还存在着引用?java中的引用分为4种:
- 强引用:Object object = new Object(),只要强引用存在,GC永远不会回收掉被引用的对象。
- 软引用:描述一些还有用但非必需的对象,当系统即将发生内存溢出时,就会将其进行回收。
- 弱引用:只要进行GC,就会对其进行回收。
- 虚引用:这是最弱的一种引用关系,无法通过虚引用来取得一个对象实例。它的作用是:能在这个对象被收集器回收时收到一个系统通知。
(2)在方法区里面:
我们知道,方法区里存储的是已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。所以我们在方法区里面进行垃圾回收,回收的是一些废弃的常量和无用的类。
- 怎么判断一个常量是否被废弃了?
看引用计数就可以,如果没有对象引用该常量,则说明常量被废弃了,就可以回收了。
- 怎么判断一个类时无用的类?
a、该类所有的实例都已经被回收。
b、加载该类的ClassLoader已经被回收。
c、该类对应的java.lang.class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。
3、如何回收?
(1)有4中算法作为理论:
- 标记-清除算法
- 复制算法
- 标记-整理算法
- 分代收集算法
(2)有5中收集器作为实现
四、内存溢出与内存泄漏
1、内存溢出
系统无法再分配出你需要的控件。
比如在堆中无法再给新生的对象分配内存了,在栈里栈满了无法再让新栈帧进栈了。
2、内存泄漏:
内存被对象占用着不还,就叫内存泄漏。
鸣谢:特别感谢作者周志明提供的技术支持!
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
用云服务器搭建Typecho网站(开源PHP建站系统)
用云服务器搭建Typecho, Typecho来自于开发团队的头脑风暴,基于PHP5开发,支持多种数据库,是一款内核强健﹑扩展方便﹑体验友好﹑运行流畅的轻量级开源博客程序。 第一步、搭建环境小编使用的是宝塔面板来搭建环境和管理网站,主要是比较方便。打开xshell或者其他的Linux远程连接工具,输入命令: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh 过程中需要输入"y"确认,安装完成后界面会显示登陆地址、用户名、密码 打开浏览器粘贴上登陆地址,登录进入控制面板,第一次界面会提示安装套件。 点击LNMP一键安装,等待按装完成。 第二步、添加站点 安装完成后,点击"网站"->"添加站点" 第三步、安装Typecho 完成提交后到Typecho官网下载安装包,将安装包上传到网站根目录 选择文件完成解压,解压完成后会出现一个 build 文件夹 ,打开文件夹选择所有文件,将所有文件剪切到根目录...
- 下一篇
Java8之深入理解Lambda
lambda表达式实战 从例子引出lambda 传递Runnable创建Thread java8之前 Thread thread=new Thread(new Runnable() { @Override public void run() { // do something } }); java 8 之后 new Thread(()->{}); 上边的例子比较简单,但是有两个疑问。什么是Lambda表达式?怎么使用lambda表达式? 什么是Lambda表达式? 从上述例子入手,首先我们知道Lambda一般代表的是一个匿名对象;其次我们点击“->”,IDE会帮助我们进入到符合Lambda规范的函数接口。我们来观察下这个符合规范的类的变化。 java7 Runnable // 省略注释 package java.lang; public interface Runnable { public abstract void run(); } java8 Runnable // 省略注释 package java.lang; @FunctionalInterface public...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS关闭SELinux安全模块
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS8编译安装MySQL8.0.19
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Red5直播服务器,属于Java语言的直播服务器