java内存区域介绍

1、什么是JDK、JRE、JVM

JVM(Java Virtual Machine):主要功能就把字节码解释成具体平台上的机器指令执行,目的是实现跨平台,一次编译,到处运行。
JRE(Java Runtime Environment):支持Java程序运行的标准环境,Java SE API + JVM。
JDK(Java Development Kit):java程序设计语言 + java虚拟机 + java API类库。
 lip_image002

2、Java内存区域

Jvm在执行java程序的过程中会把它所管理的内存划分成若干个不同的数据区域,他们有各自的用途和生命周期,如下图
ip_image004

程序计数器

它可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变计算器的值里获取下一条要执行的指令的,分支、循环、跳转、异常处理等等都是依赖计数器来完成的。Java虚拟机规范中唯一没有规定任何OutOfMemoryError情况的区域。

Java虚拟机栈

每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用到执行完成的过程,就对应一个栈帧在虚拟机栈中入栈到出栈的过程。局部变量表存放了编译期可知的各种基本数据类型、对象引用和returnAddress类型(指向一条字节码指令的地址)。
ip_image006 ip_image008
  1. 局部变量表:是一组局部变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在Java文件编译为Class文件时,就在方法表的Code属性的max_locals数据项中确定了该方法需要分配的最大局部变量表的容量
  1. 操作数栈:JVM底层字节码指令集是基于栈类型的,所有的操作码都是对操作数栈上的数据进行操作,对于每一个方法的调用,JVM会建立一个操作数栈,以供计算使用。和局部变量一样。操作数栈的最大深度也是编译的时候写入到方法表的code属性的max_stacks数据项中。操作数栈的每一个元素可以是任意的Java数据类型,包括long、double。
  1. 动态连接:每个栈帧都包含一个指向运行时常量池中该栈帧所属性方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。在Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用一部分会在类加载阶段或第一次使用的时候转化为直接引用,这种转化称为静态解析。另外一部分将在每一次的运行期期间转化为直接引用,这部分称为动态连接。
  1. 返回地址:当一个方法被执行后,有两种方式退出这个方法。第一种方式是执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者(调用当前方法的的方法称为调用者),是否有返回值和返回值的类型将根据遇到何种方法返回指令来决定,这种退出方法方式称为正常完成出口(Normal Method Invocation Completion)。另外一种退出方式是,在方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理,无论是Java虚拟机内部产生的异常,还是代码中使用athrow字节码指令产生的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方式称为异常完成出口(Abrupt Method Invocation Completion)。一个方法使用异常完成出口的方式退出,是不会给它的调用都产生任何返回值的。     无论采用何种方式退出,在方法退出之前,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者PC计数器的值就可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器来确定的,栈帧中一般不会保存这部分信息。 方法退出的过程实际上等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用都栈帧的操作数栈中,调用PC计数器的值以指向方法调用指令后面的一条指令等
  1. 附加信息:虚拟机规范允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧中,例如与高度相关的信息,这部分信息完全取决于具体的虚拟机实现。在实际开发中,一般会把动态连接,方法返回地址与其它附加信息全部归为一类,称为栈帧信息。
JAVA虚拟机规范中,对这个区域规定了两种异常状况:1、如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;2、如果虚拟机可动态扩展,如果扩展时无法申请到足够的内存,将抛出OutOfMemoryError异常。
如递归方法层级过多就会出现StackOverflowError

本地方法栈

本地方法栈( Native Method Stack) 与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。在虚拟机规范中对本地方法栈中方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如 Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError 和 OutOfMemoryError 异常。

Java堆

对于大多数应用来说,Java堆( Java Heap)是Java 虚拟机所管理的内存中最大的一块。 Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在 Java 虚拟机规范中的描述 是:所有的对象实例以及数组都要在堆上分配,但是随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、 标量替换优化技术将会导致一些微妙的变化发生, 所有的对象都分配在堆上也渐渐变得不是那么“ 绝对”了。
Java 堆 是 垃圾 收集 器 管理 的 主要 区域, 因此 很多 时候 也 被 称做“ GC 堆” 从内 存 回收 的 角度 来看, 由于现在收集器基本都采用分代收算法, 所以 Java 堆 中 还可以细分为:新生代和老年代;再细致一点的有Eden空间、 From Survivor空间、 To Survivor 空间等。从内存分配的角度来看,线程共享的Java 堆中可能划分出多个线程私有的分配缓冲区( Thread Local Allocation Buffer, TLAB)。不过无论如何划分,都与存放内容无关,无论 哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好地回收内存,或者更快 地分配内存。
根据 Java 虚拟机规范的规定,Java 堆可以处于物理上不连续的内存空间中,只要逻辑上是 连续的即可,就像我们的磁盘空间一样。 在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的( 通过- Xmx 和- Xms 控制)。 如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

方法区

方法区( Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
根据Java 虚拟机规范的规定,当方法区无法满足内存分配需求时将抛出 OutOfMemoryError 异常。
运行时常量池( Runtime Constant Pool)是方法区的一部分。 Class文件中除了有类的版本、 字段、 方法、 接口等描述信息外,还有一项信息是常量池( Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量 池中存放。运行期间也可能将新的常量放入池中,如 String 类的intern() 方法。

直接内存

直接 内存( Direct Memory) 并不是 虚拟 机 运行时 数据区 的 一部分, 也不 是 Java 虚拟 机 规范 中 定义 的 内存 区域。在 JDK 1. 4 中 新 加入 了 NIO( New Input/ Output) 类, 引入 了 一种 基于 通道( Channel) 与 缓冲区( Buffer) 的 I/ O 方式, 它可 以 使用 Native 函数 库 直接 分配 堆 外 内存, 然后 通过 一个 存储 在 Java 堆 中的 DirectByteBuffer 对象 作为 这块 内存 的 引用 进行 操作。 这样 能在 一些 场景 中 显著 提高 性能, 因为 避免 了 在 Java 堆 和 Native 堆 中 来回 复制 数据。
本机 直接 内存 的 分配 不会 受到 Java 堆 大小 的 限制, 但是,既然 是 内存, 肯定 还是 会受 到 本机 总 内存( 包括 RAM 以及 SWAP 区 或者 分页 文件) 大小 以及 处理器 寻址 空间 的 限制。也可能 导致 OutOfMemoryError 异常 出现。

分布式系统事务一致性解决方案

原文链接:http://www.cnblogs.com/dinglang/p/5679542.html

我的理解:

首先传统的两阶段提交的分布式事务对性能的影响会比较大,不适合高并发和高性能要求的场景。
考虑了阿里的GTS/TXC,发现使用的人太少,暂时先不用它。
考虑了阿里云ons的事务消息,发现功能不是很完善,而且实现本地事务的策略有点繁琐,担心会加重负担,所以先不用。
最后决定使用非事务消息,将本地业务和发送消息先后执行,用try catch包起来,或者用spring的本地事务注解。如果出错则执行回滚策略。可能的情况有三种:
1.操作数据库成功,向MQ中投递消息也成功。
2.操作数据库失败,不会向MQ中投递消息了。
3.操作数据库成功,但是向MQ中投递消息时失败,向外抛出了异常,刚刚执行的更新数据库的操作将被回滚。
因为要保证最终一致性,因此有两个问题需要关注:
1.消息出列后,消费者对应的业务操作要执行成功。如果下游业务执行失败,消息不能失效或者丢失,要么重复执行到成功为止,要么通知人工介入。
2.尽量避免消息重复消费。如果重复消费,也不能因此影响业务结果。
保证消息与下游业务操作一致,不丢失,主流的MQ产品都具有持久化消息的功能。如果消费者宕机或者消费失败,都可以执行重试机制。其中阿里云的MQ可以自定义重试次数,我开发的消息中间件已经集成进来了,如果需要,可以后续加入超过重试上限后自动通知人工介入的功能。
避免消息被重复消费造成的问题,需要保证消费者调用业务的服务接口的幂等性,消息中间件已经通过全局唯一id的方式保证了幂等性,重复消息不会执行。
这种非事务消息的方式比较常见,如果MQ自身和业务都具有高可用性,理论上是可以满足大部分的业务场景的。

谈运维认知读后感

读完京东高级PE王超的应用运维认知,觉得京东对自动化运维的理解与实施很值得我们学习。他们的团队从几个人到20几个人,从传统运维到自动化到智能运维,从开发和运维的矛盾点到一个标准实时的流程。这就是我们想要的! 目前我们虽然我们虽然还在初阶段,但我们有了明确的目标,还有这些大神们的经验,我们要快速学习应用到爱学习当中。 困难还是有的,老项目的复杂凌乱,项目的繁琐众多,但是看到力勇说的一个字:干!我也精神振奋! 我们先定个小目标:做好项目发布自动化。加快速度与业务连续性,降低风险提高安全。让爱学习更好很快更强!

Gerrit使用规范

本机master上的代码坚决不动,开发的代码永远在本机切换新的分支进行新的业务开发(只让master保持跟服务器master上的同步,能省去很多不必要的问题)。下面是一个正常情况下的栗子🌰
  git checkout master
  git pull #拉远端最新版本代码到本机
  git checkout -b dev #假设你要创建一个叫dev的分支进行开发
  git rebase master #合并远端最新代码到开发的分支(常见是在gerrit后台review后merge时发生冲突后需要本机rebase后再次追加提交)
  #如果有冲突(rebase后会有提示),手动解决冲突后,进行以下操作
  git add -A
  git rebase - -continue
  #本地代码已经可以提交到gerrit,执行以下push操作,(注意不能直接像以前那样push到远端的master上,而是一个特殊的gerrit分支)
  git push origin HEAD:refs/for/master


如果你在master上status出现了以下情况:
 (master) git status
 On branch master
 Your branch is ahead of 'origin/master' by 2 commits.
   (use "git push" to publish your local commits)
 nothing to commit, working tree clean
 出现这个说明没有严格按照以上规范,这时需要通过git reset 进行回滚后再pull(回滚前确保这上面的代码再gerrit上已有,已有的分支可以checkout下来继续在个人分支上继续开发)
a.多个开发任务并行时,不要都提交在一个commit里(相互有依赖的可以)
 b.需要在gerrit中同时维护多个未merge的分支(同时提交多个commit,或者上一个提交因为一些原因还没有merge又需要再提交一个新的),对于每个分支均从master上checkout一个新的分支进行开发,不在一个开发分支里commit多个,这样能最大程度避免依赖。避免出现你的后提交的代码需要上线,但所依赖的代码还不能merge的情况。
 c.提交的subject规范:1.格式:“关键字 正文”  2.关键字,固定以下几个(如有新的需要定义找CTO协商):added(新增,简写add)、fixed(修复,简写fix)、changed(修改原来的业务逻辑,简写change或cg)、upgraded(优化或组件升级,简写upgrade或up) 3.正文:解释是什么和为什么的  。例如:“cg 同步数据接口增加几个表单项”,“fix 同步数据接口缺乏必要校验”
我们使用gerrit的最大的好处,就在与可以review代码,可以看到我们本次提交所涉及的改动,对比老代码能更容易发现本次改动的疏漏或bug。
 因此提出建议规范:自己的代码无论改动多少,+2前必须先给指定人review+1(组员的代码给组长review,组长的代码自己指定组内他人review)。自己review可以在研发过程中的任何时候,不一定要等到开发完了再review