java虚拟机入门(三)- 你了解对象吗
对象对于java程序员来说,那是想要多少就有多少,所以那些嘲笑程序员的单身狗,哼,只有无知使你们快乐,想我大java开发,何曾缺少过对象。我们不仅仅知道创建对象,还知道创建对象的过程是啥样的,不信?往下看。
一、论程序员的对象由来
我作为java程序员都知道new Object()可以创建一个对象,那么new Object()为啥就能创建一个对象呢,首先我们需要了解对象是怎么一步步创建的:
1.检查加载
首先我们要检查这个指令的参数能否在常量池中定位到一个类的符号引用(符号引用我理解就是由于对象具体地址位置,用字面量来代替,等运行时再用实际的引用地址替换),并检查类是否已经被加载,解析,初始化,(作为严谨的程序员,对象还是要先检查有没有男朋友,有没有结婚,坚决不做接盘侠)
2.分配内存
确定单身之后,那就要给她一个家,虚拟机会给新生对象分配内存(内存大小是确定的)。内存分配的方式主要有两种流派:指针碰撞,空闲列表
2.1 指针碰撞:当需要内存时,将指针向后移动一定大小的内存,指针将已用内存和未使用内存分开。这种方式要求内存是完全规整的。
2.2 空闲列表:这种方式虚拟机需要维护一个列表,记录哪些内存块可用,在内存分配时从列表中找到一个足够大的内存区域分配给对象实例(必须是连续的内存区域),并更新列表上的记录,这种方式看着就比上述指针碰撞复杂,但是并不是所有的垃圾收集器都可以贤惠的将内存规规整整的分好(具体后面垃圾回收章节会细讲)。
怎么选择其实上面也算是讲到了,指针碰撞简单粗暴,要求你有个贤惠的垃圾回收器,空闲列表不需要那么高的要求,但是你自己做的就多一点。对象的创建很频繁,可以使用指针碰撞自然最好,否则就用空闲列表,而在如此频繁的对象创建过程,内存分配是否会出现并发安全的问题呢,答案是会,那么我们来了解一下jvm虚拟机是如何处理并发分配内存的问题呢:
解决这个问题主要有两种方案:1. 对内存分配进行同步处理,其实虚拟机采用cas加失败重试保证更新操作的原子性,
2. 分配缓冲:默认是打开的,所以除非特殊需求,一般公司用的都是这种方式。分配缓冲就是在每个线程都在java堆中分配一块内存,jvm在线程初始化的时候,都会申请一块内存,只给当前线程使用,如果当前线程分配的内存不够用 了,在重新从Eden区申请一块继续使用,分配缓冲英文:Thread Local Allocation Buffer ,也叫TLAB(是不是看到这个比分配缓冲要熟悉很多),TLAB其实就是用空间换时间,让每个线程都有一块内存(不管你用没用),这样可以减少同步开销,但是也会加大内存开销。其实看到这里我发现jvm越来越趋向于用空间换时间,越新的垃圾回收器内存需求越大,但是吞吐量也越大。
3.内存空间初始化
小伙伴们,有没有发现实例对象明明只定义了一个引用,但是却依然可以访问(如int为0,普通对象为null),不过局部变量不可以,你想不初始化就用,编译器不会放过你的。这一步基本可以保证所有的没有初始化的对象都可以使用。
4.设置
这一步虚拟机会对你要创建的对象做必要的设置,比如对象是哪个类的实例,类的元数据信息设置,对象哈希码,GC分代年龄信息等,这些信息存放在对象的对象头中,这一步完成,从虚拟机角度来说,新对象诞生了,也可以看出,这一步之前,除对象头之外的所有信息都创建完成。
5.对象初始化
这一步才是我们普通码农角度的new对象,就是执行构造方法,进行对象初始化。
很神奇,我们明明就用了一个new,jvm却做了那么多事情,jvm还是很贴心的,作为java程序员还是很幸福的,但是同时,java程序员的差距也是很大的,正是因为jvm保姆太贴心,导致很多做java的只会写crud,尤其对于我们这些跨专业的人来说,体现的更是淋漓尽致,所以还是加油吧。
二、剥开对象的神秘外衣
对象创建完了,那么对象她的内涵是啥样的呢,是的你没听错,我们程序员最注重的还是内涵,那就让我们剥开她神秘的外衣,一探究竟吧:
首先说一下,这张图是我凭实力copy过来的,对象的存储布局主要分为对象头,实例数据,对其填充,
对象头包含对象的运行时数据如哈希码,GC分代年龄(这个后面垃圾回收会讲到,结合起来会对对象理解的更透彻),锁状态表示(这块可以了解下synchronized锁优化),线程持有的锁,偏向线程id,偏向时间戳等,对象头另一个重要的元素就是类型指针,字面意思就很好理解,指向这个对象是属于那种类的实例。如果是数组对象,还会额外有记录数组长度的数据(我不知道会不会有其他人跟我一样,在刚开始看源码的时候,看到list对象里面有长度,但是数组却没有,而且也无法看源码,心里很难受,总觉得这个数组多长不知道是谁控制的,后来了解了对象的类型,才算是有所领悟)。
对象的另一个组成部分实例数据应该没什么好说的,就是存储的数据嘛
对其填充其实就是hotsport vm要求对象大小必须为8字节整数倍,所以当不满8字节的时候,就需要这部分填满。
三、如何找对象
单身的朋友看过来了,大型情感类教学文章的精华开始了,请仔细观看,看到这里很多人就蒙了,程序员都这么找对象的吗,明明前面第一节讲了对象的由来,然后又剥开了对象外衣,这到现在才说如何找对象,不过这才是真正强悍的程序员,不是普通码农可比的。见过了强悍的实力,教学才更有说服力。
言归正传,本节讲的是如何找对象,找对象呢一般分为两种,媒婆介绍和自己直接认识,媒婆介绍呢就是其实就是我不知道我的对象在哪,但是我能找到媒婆,她知道我的对象在哪,这种就是句柄的方式,直接认识就很简单粗暴了,自己认识,直接找就行了,这种在程序员的世界也叫直接指针。
句柄:java堆中会划分一块内存做句柄池,句柄中包含对象的具体地址信息,我们的引用存储句柄地址
直接指针:对象引用存储的是对象的地址
这两种找对象的方式各有优劣,句柄方式由于不直接认识对象,所以想换对象很简单,跟媒婆说一声就行了,但是呢每次都必须通过媒婆去找,浪费时间和金钱,自己认识呢,好处就是找对象直接就可以找,但是想换就必须自己去重新找到下一个对象,还得把这个对象换掉。
估计要是有弹幕肯定很多人都会打出:裤子都脱了你就给我看这个? 哈哈 那我们聊一块钱正经的。
四、对象的引用
对象的引用主要分为四种:强引用,软引用,弱引用,虚引用
1.强引用:一般Object obj = new Object();就属于强引用,强引用对象只要有强引用关联(GC Root可达),垃圾回收器就不会回收。这种是我们平时用的最多的,毕竟new对象嘛。
2.软引用:软引用就是比强引用稍次一点的,它在系统将要发生内存溢出时,这些对象就会被回收。这种比较适合用来做缓存这种可以被随时回收,但是又不会经常被出发回收。我们看下代码:
public class TestSoftRef {
//对象
public static class User{
public int id = 0;
public String name = "";
public User(int id, String name) {
super();
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + "]";
} }
//
public static void main(String[] args) {
User u = new User(1,"King"); //new是强引用
SoftReference<User> userSoft = new SoftReference<User>(u);//软引用
u = null;//干掉强引用,确保这个实例只有userSoft的软引用
System.out.println(userSoft.get()); //看一下这个对象是否还在
System.gc();//进行一次GC垃圾回收 千万不要写在业务代码中。
System.out.println("After gc");
System.out.println(userSoft.get());
//往堆中填充数据,导致OOM
List<byte[]> list = new LinkedList<>();
try {
for(int i=0;i<100;i++) {
//System.out.println("*************"+userSoft.get());
list.add(new byte[1024*1024*1]); //1M的对象 100m
}
} catch (Throwable e) {
//抛出了OOM异常时打印软引用对象
System.out.println("Exception*************"+userSoft.get());
} }
}
设置vm参数:-Xms20m -Xmx20m -XX:+PrintGCDetails,前俩参数限制堆大小不超过20m,后一个打印gc日志看执行结果:
可以看到第一次gc甚至full GC之后,对象依然没有被回收,随后循环多次,执行了多次FullGc,但每次都没回收多少,而且一直内存占用很高,这说明即将oom了,明显报错之后,对象被回收了。
3.弱引用:弱引用就是更次一点了,用的地方不太多,比如经典的ThreadLocal(也因为这个存在内存泄漏问题),弱引用在每次gc时都会回收对象,看代码:
public class TestWeakRef {
public static class User{
public int id = 0;
public String name = "";
public User(int id, String name) {
super();
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + "]";
} } public static void main(String[] args) {
User u = new User(1,"King");
WeakReference<User> userWeak = new WeakReference<User>(u);
u = null;//干掉强引用,确保这个实例只有userWeak的弱引用
System.out.println(userWeak.get());
System.gc();//进行一次GC垃圾回收,千万不要写在业务代码中。
System.out.println("After gc");
System.out.println(userWeak.get());
}
}
代码很简单,创建一个弱引用对象,随后触发gc,然后看看对象是否被回收:
很明显结果是被回收了,所以也印证了上述结果。
4.虚引用:也叫幽灵引用,听名字就感觉随时都可能被回收掉,确实是这样,据我所知,只有类似心跳机制监控垃圾回收器是否正常工作会用到,尝试测过,基本上测出来的都是null,被回收掉了。
总结:
对象到这里基本上算是介绍完了,考虑过要不要把判断对象存活放到这一章节来讲,想想应该还是属于垃圾回收,避免只看垃圾回收的连根可达介绍都看不到,每一个程序员多多少少都会有求知欲,比如我们为啥我们写了new之后,对象就创建了,其实挖到更深也就是数据以一种什么样的方式放到内存中,看完这一章,对对象也有个基本的认识了,从new对象时jvm做了哪些操作,到对象构成,对象如何访问,以及对象的引用,最后也聊到了垃圾回收的一部分,这也表示我下一章要开始介绍垃圾回收了,垃圾回收和jvm调优密切相关,所以,应该也是jvm中最重要的,但是要彻底了解垃圾回收,还是需要前面这些知识的积累的,java由于非常庞大的生态,更新迭代也是很快的,而在新的版本也是对java做了很多优化,很多时候我们不需要做多余的配置,但是不同的公司,不同的业务场景,计算机也不可能做到每一步都兼顾,所以,这才有程序员----在有限的资源,做最多的事情。
java虚拟机入门(三)- 你了解对象吗的更多相关文章
- 深入理解java虚拟机(三)对象回收判断算法以及死亡过程
在堆里面存放着Java几乎所有的对象实例,垃圾收集器要进行垃圾回收,要做的第一步便是找出那些对象是需要回收的. 怎么判断对象是否需要回收? 常用的方法有两种. 1.引用计数算法.为每一个对象添加一个引 ...
- Java虚拟机(三)垃圾标记算法与Java对象的生命周期
前言 这一节我们来简单的介绍垃圾收集器,并学习垃圾标记的算法:引用计数算法和根搜索算法,为了更好的理解根搜索算法,会在文章的最后介绍Java对象在虚拟机中的生命周期. 1.垃圾收集器概述 垃圾收集器( ...
- java虚拟机入门(一)-jvm基础
转行学java之前,总是听着大佬们说着java像个渣男一样可以跨平台,一次编译到处运行,瞬间,我就坚定了学java的信念,哎呀妈呀,得劲.真的学java之后,好像渣男也不是那么好学的,尤其这货的必杀技 ...
- Java虚拟机(三):Java 类的加载机制
1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构 ...
- 深入理解Java虚拟机第三版,总结笔记【随时更新】
最近一直在看<深入理解Java虚拟机>第三版,无意中发现了第三版是最近才发行的,听说讲解的JDK版本升级,新增了近50%的内容. 这种神书,看懂了,看进去了,真的看的很快,并没有想象中的晦 ...
- java虚拟机入门(四)-垃圾回收的故事
谈到垃圾回收器,java程序员骄傲了起来,c语言你是够快,但是你有管家帮你打扫吗,还不是得靠自己的一双手,有钱就是任性.既然如此令java程序员骄傲的垃圾回收器,怎能让人不想去一探究竟呢! 垃圾回收器 ...
- Java虚拟机:如何判定哪些对象可回收?
版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! 在堆内存中存放着Java程序中几乎所有的对象实例,堆内存的容量是有限的,Java虚拟机会对堆内存进行管理,回收已经"死去&quo ...
- 手写JAVA虚拟机(三)——搜索class文件并读出内容
查看手写JAVA虚拟机系列可以进我的博客园主页查看. 前面我们介绍了准备工作以及命令行的编写.既然我们的任务实现命令行中的java命令,同时我们知道java命令是将class文件(字节码)转换成机器码 ...
- 深入理解Java虚拟机 第三章 垃圾收集器 笔记
1.1 垃圾收集器 垃圾收集器是内存回收的具体实现.以下讨论的收集器是基于JDK1.7Update14之后的HotSpot虚拟机.这个虚拟机包含的所有收集器有: 上图展示了7种作用于不同分代的收集 ...
随机推荐
- Python 函数对象的本质
Python 函数对象本质上是 function 类的实例. 1 从示例说起 def factorial(n): '''return n!''' return 1 if n < 2 else n ...
- 30G 上亿数据的超大文件,如何快速导入生产环境?
Hello,大家好,我是楼下小黑哥~ 如果给你一个包含一亿行数据的超大文件,让你在一周之内将数据转化导入生产数据库,你会如何操作? 上面的问题其实是小黑哥前段时间接到一个真实的业务需求,将一个老系统历 ...
- EF生成模型时Disigner中无信息
原博文 http://blog.sina.com.cn/s/blog_a1b63a730101ezs4.html 说明 DbContext是对ObjectContext的简化封装.原来的ObjectC ...
- Code-Review-Maven编译(第三方jar包引用)
Code-Review-SpringBoot-Maven编译(第三方jar包引用) 在使用maven编译项目时,有时候咱们可能会使用一些第三方的jar包依赖库,比如第三方支付类的接入,大多出于安全考虑 ...
- 通过`RestTemplate`上传文件(InputStreamResource详解)
通过RestTemplate上传文件 1.上传文件File 碰到一个需求,在代码中通过HTTP方式做一个验证的请求,请求的参数包含了文件类型.想想其实很简单,直接使用定义好的MultiValueMap ...
- Redis基础篇(五)AOF与RDB比较和选择策略
RDB和AOF对比 关于RDB和AOF的优缺点,官网上面也给了比较详细的说明redis.io/topics/pers- RDB 优点: RDB快照是一个压缩过的非常紧凑的文件,保存着某个时间点的数据集 ...
- intellij IDEA Mybatis入门案例
最近打算学习ssm框架 Mybatis 作为入门的第一个持久层框架,学习起来实在费劲.故写此文章作为入门案例. 先打开 IDEA建立一个Maven项目,目录结构如下: 源代码已经上传至GitHub ...
- R绘图(1): 在散点图边缘加上直方图/密度图/箱型图
当我们在绘制散点图的时候,可能会遇到点特别多的情况,这时点与点之间过度重合,影响我们对图的认知.为了更好地反映特征,我们可以加上点的密度信息,比如在原来散点所在的位置将密度用热图的形式呈现出来,再比如 ...
- 主从同步遇到 Got fatal error 1236 from master when reading data from binary log: 'Could not find first log file name in binary log index file'时怎么解决
首先遇到这个是因为binlog位置索引处的问题,不要reset slave: reset slave会将主从同步的文件以及位置恢复到初始状态,一开始没有数据还好,有数据的话,相当于重新开始同步,可能会 ...
- ES6 proxy代理详解及用法
proxy官方的详细解释为代理器,个人理解为相当于一个拦截器,外部的所有访问必须先通过这层拦截,监视到对象的读写过程,因此提供了这 种机制对外部的访问进行过滤和修改. 上述例子为proxy new一个 ...