Sean's Blog


  • Home

  • Tags

  • Categories

  • Archives

  • Search

jvm类文件的结构1-魔数与Class文件的版本

Posted on 2019-08-08 | In jvm

魔数

每个Class文件的头4个字节称为魔数(Magic Number),它的唯一作用是用于确定这个文件是否为一个能被虚拟机接受的Class文件。
很多文件存储标准中都使用魔数来进行身份识别,比如图片格式,如gif或jpeg等在文件头中都存有魔数。
使用魔数而不是扩展名来进行识别主要是基于安全考虑,因为文件扩展名可以自由地选择魔数值,只要这个魔数值还没有被广泛采用过而且不会引起混淆即可。
Class文件的魔数的获得很有”浪漫气息”,值为: 0xCAFEBABE
CA:202 FE:254 BA:186 BE:190

Class文件的版本

紧接着魔数的4个字节存储的是Class文件的版本号: 第5和第6个字节的次版本号(Minor Version),第7个和第8个字节是主版本号(Major Version)。
Java的版本号是从45开始的,JDK1.1之后的每个JDK大版本发布主版本号向上加1(JDK 1.0~1.1 使用了45.0~45.3的版本号)
高版本的JDK能向下兼容以前版本的Class文件,但不能运行以后版本的Class文件,即使文件格式并未发生变化。
如JDK1.1能支持版本号为46.0以上的Class,而JDK1.2则能支持45.0~46.65535的Class文件。

jvm类文件的结构

Posted on 2019-08-08 | In jvm

Class类文件的结构

Class 文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部都是程序运行的必要数据,没有空隙存在。当遇到需要占用8位字节以上空间的数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储。

Class类的伪结构

Class文件格式采用一种类似于C语言结构体的伪结构来存储,这种伪结构中只有两种数据类型: 无符号数和表。

无符号数

无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值,或者按照UTF-8编码构成字符串值。

表

表是由多个无符号数或其他表作为数据项构成的复合数据类型,所有表都习惯性地以”_info”结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质上就是一张表。

Class格式

类型 名称 数量
u4 magic 1
u2 minor_version 1
u2 major_version 1
u2 constant_pool_count 1
cp_info constant_pool constant_pool_count - 1
u2 access_flags 1
u2 this_class 1
u2 super_class 1
u2 interfaces_count 1
u2 interfaces interfaces_count
u2 fields_count 1
field_info fields fields_count
u2 methods_count 1
method_info methods methods_count
u2 attributes_count 1
attribute_info attributes attributes_count

无论是无符号数还是表,当需要描述同一类型但数量不定的多个数据时,经常会使用一个前置的容量计数器加若干个连续的数据项的形式,这时候称这一系列连续的某一类型的数据为某一类型的集合。

jvm内存分配

Posted on 2019-08-08 | In jvm

jvm内存分配简介

对象的内存分配,往大方向上讲,就是在堆上分配(但也可能经过JIT编译后被拆散为标量类型并间接地在栈上分配),对象主要分配在新生代地Eden区上,如果启动了本地线程分配缓冲,将按线程优先优先在TLAB上分配。少数情况也可能会直接分配在老年代中,分配的规则并不是百分之百固定的,其细节取决于当前使用的是哪一种垃圾收集器组合,还有虚拟机中与内存相关的参数的设置。

对象优先在Eden分配

大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC。

Minor GC 与 Full GC

  • 新生代GC(Minor GC): 指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。

  • 老年代GC(Major GC/Full GC): 指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC (但非绝对的,在ParallelScavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。MajorGC的速度一般会比Minor GC 慢10倍以上。

大对象直接进入老年代

所谓大对象就是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串及数组(笔者例子中的byte[]数组就是典型的大对象)。大对象对虚拟机的内存分配来说就是一个坏消息。(最坏的是”朝生夕灭”的”短命大对象”),经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续空间来”安置”它们。

长期存活的对象将进入老年代

虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将对象年龄设为1。对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁)时,就会被晋升到老年代中。

动态对象年龄判定

为了能更好地适应不同程序的内存状况,虚拟机并不总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。

空间分配担保

在发生Minor GC时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于,则改为直接进行一次Full GC。如果小于,则查看HandlePromotionFailure设置是否允许担保失败;如果允许,那只会进行Minor GC;如果不允许,则也要改为进行一次Full GC。

新生代使用复制收集算法,但为了内存利用率,只使用其中一个Survivor空间来作为轮换备份,因此当出现大量对象在Minor GC后仍然存活的情况时(最极端的就是内存回收后新生代中所有对象都存活),就需要老年代进行分配担保,让Survivor无法容纳的对象直接进入老年代。

老年代要进行这样的担保,前提是老年代本身还有容纳这些对象的剩余空间,一共有多少对象会活下来,在实际完成内存回收之前是无法明确知道的,所以只好 取之前每一次回收晋升到老年代对象容量的平均大小值 作为经验值,与老年代的剩余空间进行比较,决定是否进行Full GC来让老年代腾出更多空间。

取平均值进行比较其实仍然是一种动态概率的手段,也就是说如果某次 Minor GC 存活后的对象突增,远远高于平均值的话,依然会导致担保失败(Handle Promotion Failure)。如果出现了HandlePromotionFailure失败,那就只好在失败后重新发起一次Full GC。虽然担保失败时绕的圈子是最大的,但大部分情况下都还是会将HandlePromotionFailure开关打开。

jvm垃圾收集算法

Posted on 2019-08-08 | In jvm

垃圾收集简介

垃圾收集(Garbage Collection, GC),并不是Java语言的伴生产物,1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言。
GC主要关注三个重点:

  1. 哪些内存需要回收

  2. 什么时候回收

  3. 如何回收

哪些内存需要回收

目前针对哪些内存需要回收这个问题,主要有两种算法:

  1. 引用计数算法

  2. 根搜索算法

引用计数算法

很多教科书判断对象是否存活的算法是:
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;
任何时刻计数器都为0的对象就是不可能再被使用的。
Java中没有选用这种算法来管理内存,最主要的原因是它很难解决对象之间的相互循环引用的问题:
对象 objA 和 objB 都有字段instance,赋值令objA.instance = objB 及 objB.instance = objA,除此之外,这两个对象再无任何引用,实际上这两个对象已经不可能再被访问,但是它们因为互相引用着对方,导致它们的引用计数都不为0,于是引用计数算法无法通知GC收集器回收它们。

根搜索算法

再主流的商用程序语言中(Java 和 C#,Lisp),都是使用根搜索算法(GC Roots Tracing) 判定对象是否存活的。

这个算法的基本思路是通过一系列名为”GC ROOT”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

GC Roots对象

在Java 语言里,可作为GC Roots的对象包括下面几种:

  • 虚拟机栈(栈帧中的本地变量表)中的引用的对象。

  • 方法区中的类静态属性引用的对象。

  • 方法区中的常量引用的对象。

  • 本地方法栈中方JNI(即一般说的Native方法)的引用的对象。

引用

无论是通过引用计数算法判断对象的引用数量, 还是通过跟搜索算法判断对象的引用链是否可达,判定对象是否存活都与”引用”有关。
在JDK 1.2之前,Java中的引用的定义很传统: 如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。 这种定义很存粹,但是太过狭隘,一个对象在这种定义下只有被引用或者没有被引用两种状态,我们希望能描述这样一类对象:
当内存空间还足够时,则能保留在内存之中;如果内存在进行垃圾收集后还是非常紧张,则可以抛弃这些对象。很多系统的缓存功能都符合这样的应用场景。
在JDK1.2之后,Java对引用的概念进行了扩充, 将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)四种,这四种引用强度依次逐渐减弱。

强引用

强引用就是指在程序代码中普遍存在的,类似”Object obj = new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。

软引用

软引用用来描述一些还有用,但并非必需的对象。对于软引用关联的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中并进行二次回收。如果这次回收还是没有足够的内存,才会抛出内存溢出异常。在JDK 1.2之后,提供了SoftReference类来实现软引用。

弱引用

弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2之后,提供了WeakReference类来实现弱引用。

虚引用

虚引用也称为幽灵引用或者欢迎引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是希望能在这个对象被收集器回收时受到一个系统通知。在JDK 1.2之后,提供了PhantomReference类来实现虚引用。

什么时候回收

在根搜索算法中不可达对象,也并非是”非死不可”的,这时候它们暂时处于”缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:

  1. 如果对象在进行根搜索后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize() 方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为”没有必要执行”。
    如果这个对象被判定为有必要执行 finalize() 方法,那么这个对象将会被放置在一个名为F-Queue的队列之中,并在稍后由一条由虚拟机自动建立的、低优先级的Finalizer线程去执行。这里所谓的”执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束。这样做的原因是,如果一个对象在 finalize() 方法中执行缓慢,或者发生了死循环(更极端的情况),将很可能会导致F-Queue队列中的其他对象永久处于等待状态,甚至导致整个内存回收系统崩溃。
  2. finalize()方法是对像逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果对象要在 finalize() 中成功拯救自己 —— 只要重新与引用链上的一个对象建立关联即可,譬如把自己(this 关键字)赋值给某个类变量或对象的成员变量,那再第二次标记时它将被移除出”即将回收”的集合;如果这个时候对象还没有逃脱,那它将被回收。

回收方法区

很多人认为方法区(或者HotSpot虚拟机中的永久代) 是没有垃圾收集的。Java虚拟机规范中确实说过可以不要求虚拟机再方法区实现垃圾收集,而且再方法区进行垃圾收集的”性价比”一般比较低:
在堆中,尤其是在新生代中,常规应用进行一次垃圾收集一般可以回收70% ~ 95%的空间,而永久代的垃圾收集效率远低于此。
永久代的垃圾收集主要回收两部分内容:

废弃常量

回收废弃常量与回收Java堆中的对象非常相似。以常量池中字面量的回收为例,假如一个字符串”abc”已经进入了常量池中,但是当前系统没有任何一个String对象是叫做”abc”的,换句话说是没有任何String对象引用常量池中的”abc”常量,也没有其他地方引用了这个字面量,如果在这时候发生内存回收,这个”abc”常量就会被回收掉。 常量池中的其他类(接口)、方法、字段的符号引用也与此类似。

无用的类

判定一个常量是否是”废弃常量”比较简单,而要判定一个类是否是”无用的类”的条件则相对苛刻许多。类需要同时满足下面三个条件才能算是”无用的类”:

  1. 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例。

  2. 加载该类的ClassLoader已经被回收。

  3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

如何回收(垃圾收集算法)

标记-清除算法

最基础的收集算法是”标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为”标记”和”清除”两个阶段:
首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象,它的标记过程其实就是之前说明的标记判定。
它的主要缺点由两个:

  1. 效率问题,标记和清除过程的效率都不高。

  2. 空间问题,标记清除后会产生大量不连续的内存碎片。空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

复制算法

为了解决效率问题,一种称为”复制”(Copying)的收集算法出现了,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对其中的一块进行内存回收。内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为原来的一半。
现在的商业虚拟机都采用这种收集算法来回收新生代,IBM的专门研究表明,新生代中的对象98%是朝生夕死的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的 Eden 空间和两块较小的 Survivor 空间。每次使用Eden和其中的一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性地拷贝到另外一块Survivor空间上,最后清理掉Eden和刚才用过地Survivor地空间。
HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,也就是每次新生代中可用内存空间为整个新生代容量的90%(80%+10%),只有10%的内存是会被”浪费”的。当然,98%的对象可回收只是一般场景下的数据,我们没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保(Handle Promotion)。

分配担保

如果另外一块Survivor空间没有足够的空间存放上一次新生代收集下来的存活对象,这些对象将直接通过分配担保机制进入老年代。

标记整理算法

复制收集算法在对象存活率较高时就要执行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的机端情况,所以在老年代一般不能直接选用这种算法。

根据老年代的特点,有人提出了另外一种”标记-整理”(Mark-Compact)算法,标记过程仍然与”标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

分代收集算法

当前商业虚拟机的垃圾收集都采用”分代收集”(Generational Collection)算法,这种算法并没有什么新的思想,只是根据对象的存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最合适的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要符出少量存活对象的复制成本就可以完成收集。 而老年代中因为对象存活率高,没有额外空间对它进行分配担保,就必须使用”标记-清理”或”标记-整理”算法来进行回收。

Java运行区域

Posted on 2019-08-08 | In jvm

Java运行区域简介

Java虚拟机所管理的内存将会包括以下几个运行时的数据区域:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
+----------------------------------------------------------+
| |
| 方法区 虚拟机栈 本地方法栈 |
| Method Area VM Stack Native Method Stack |
| |
| |
| 堆 程序计数器 |
| Heap Program Counter Register |
| |
+----------------------------------------------------------+
| | | |
-- -- -- --
\ / \ /
\ / \ /
执行引擎 ----> 本地库接口 -----> 本地方法库

程序计数器

程序计数器(Program Counter Register) 是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为线程私有的内存。

如果计数器正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;

如果正在执行的是Native方法,这个计数器值则为空(Undefined)。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

虚拟机栈

与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机描述的是Java方法执行的内存模型:

每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

在Java虚拟机规范中,对这个区域规定了两种异常状况:

  1. 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;

  2. 如果虚拟机栈可以动态扩展(当前大部分的Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。

局部变量表

局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。

其中64位长度的long和double类型的数据会占用2个局部变量空间(Slot),其余的数据类型只占用1个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

本地方法栈

本地方法栈(Native Method Stacks)和虚拟机栈的区别:

  1. 虚拟机栈为虚拟机执行Java方法(也就是字节码)服务

  2. 而本地方法栈则是为虚拟机使用到的Native方法服务

虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。

Java堆

对于大多数应用来说,Java堆(Java Heap) 是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。
此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:

1
所有的对象实例以及数组都要在堆上分配

但是随着JIT编译器的发展与逃逸分析技术的逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化发生。所有的对象都分配在堆上也渐渐变得不是那么绝对了。

Java堆是垃圾收集器管理的主要区域,因此很多时候也被称作GC堆(Garbage Collected Heap)。

如果从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,所以Java堆中还可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、 To Survivor空间等。
从内存分配的角度看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。
不过,无论如何划分,都与存放内容无关,无论哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好的回收内存,或者更快的分配内存。

根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。
在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx 和-Xms控制)。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

方法区

方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。

Java虚拟机规范对这个区域的限制非常宽松,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。
相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入了方法区就如永久代的名字一样”永久”存在了。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说这个区域的回收成绩比较难以令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区域的回收确实是有必要的。在Sun公司的BUG列表中,曾出现过的若干个严重的BUG就是由于低版本的HotSpot虚拟机对此区域未完全回收而导致内存泄漏。

根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

Java虚拟机对Class文件的每一部分(自然也包括常量池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会被虚拟机认可、装载和执行。但对于运行时常量池,Java虚拟机规范没有做任何细节的要求,不同的提供商实现的虚拟机可以按照自己的需要来实现这个内存区域。不过,一般来说,除了保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。

运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只能在编译期产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。

既然运行时常量池是方法区的一部分,自然会受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。

直接内存

直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是这部分内存也被频繁的使用,而且也可能导致OutOfMemoryError异常出现。
在JDK 1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。
本机直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,则肯定还是会受到本机总内存(包括RAM及SWAP区或者分页文件)的大小及处理器寻址空间的限制。服务器管理员配置虚拟机参数时,一般会根据实际内存设置-Xmx等参数信息,但经常会忽略掉直接内存,使得各个内存区域的总和大于物理内存限制(包括物理上的和操作系统级的限制),从而导致动态扩展时出现OutOfMemoryError异常。

Java技术体系简介

Posted on 2019-08-08 | In jvm

Java技术体系简介

广义

广义上讲,Clojure、JRuby、Groovy等运行在Java虚拟机上的语言及其相关的程序都属于Java技术体系的一员。

传统意义

Sun官方定义的Java技术体系包括以下几个组成部分:

  • Java程序设计语言

  • 各种硬件平台上的Java虚拟机

  • Class文件格式

  • Java API类库

  • 来自商业机构和开源社区的第三方Java类库

即: Java语言, Class文件规范, 各种虚拟机, API及第三方类库。

JDK = Java语言 + Java虚拟机 + Java API类库 (支持Java程序开发的最小环境)
JRE = Java虚拟机 + Java SE API子集 (支持Java程序运行的最小环境)

Java SE API子集

Java SE API子集包括:

功能 类库
用户界面 AWT, Swing, Java 2D
相关技术 Accessbility, Drag n Drop, Input Methods, Image I/O, Print Service, Sound
集成库 IDL, JDBC, JNDI, RMI, RMI.IIOP, Scripting
其他基础库 Beans, Intl Support, I/O, JMX, JNI, Math, Networking, Override Mechanism, Security, Serialization, Extension Mechanism, XML, JAXP
语言和工具基础库 lang and util, Collections, Concurrency Utilities, JAR, Logging, Management, Preferences API, Ref Objects, Reflection, Regular Expressions, Versioning, Zip,Instrument
Java虚拟机 Java Hotpot ClientVM, Java Hotpot Server VM

JRE

JRE除了完全包括Java SE API, 还包括程序发布相关组件:

Deployment, Java WEb Start, Java Plug-in

JDK

JDK除了完全包括JRE, 还包括:

  1. Java语言(Java Language)

  2. 工具及工具API(java, javac, javadoc, apt, jar, javap, JPDA, JConsole, Java VisualVM, Security, Int`l, RMI, IDL, Deploy, Monitoring, Troubleshoot, Scripting, JVM TI)

Java技术平台

Java技术关注的重点业务领域来划分,Java技术体系可分为四个平台:

  1. Java Card: 支持一些Java小程序(Applets) 运行在小内存设备(如智能卡)上的平台。

  2. Java ME(Micro Edition): 支持Java程序运行在移动终端(手机、PDA)上的平台,对Java API有所精简, 并加入了针对移动终端的支持,这个版本以前称为J2ME。

  3. Java SE(Standard Edition): 支持面向桌面级应用(如Windows下的应用程序)的Java平台,提供了完整的Java核心API,这个版本以前称为J2SE。

  4. Java EE(Enterprise Edition): 支持使用多层架构的企业应用(如ERP、CRM应用)的Java平台,除了提供Java SE API外,还对其做了大量的扩充并提供了相关的部署支持,这个版本以前称为J2EE。

shell中的awk

Posted on 2019-08-08 | In shell

awk工作模式简介

awk是一个文本处理工具,主要用于处理数据并生成结果报告

语法格式

1
2
awk 'BEGIN{}pattern{commands}END{}' file_name
standard output | awk 'BEGIN{}pattern{commands}END{}'

语法格式解释

语法格式 解释
BEGIN{} 正式处理数据之前执行
pattern 匹配模式
{commands} 处理命令,可能多行
END{} 处理完所有匹配数据后执行

awk中的内置变量

内置变量 含义
$0 整行内容
$1-$n 当前行的第1-n个字段
NF 当前行的字段个数,也就是有多少列
NR 当前行的行号,从1开始计数
FNR 多文件处理时,每个文件行号单独计数,都是从0开始
FS 输入字段分隔符。不指定默认以空格或tab分割
RS 输入行分隔符。默认回车换行
OFS 输出字段分隔符。默认为空格
ORS 输出行分隔符,默认为回车换行
FILENAME 当前输入的文件名字
ARGC 命令行参数个数
ARGV 命令行参数数组

printf的格式说明符

格式符 含义
%s 打印字符串
%d 打印十进制数字
%f 打印浮点数
%x 打印十六进制数
%o 打印八进制数
%e 打印数学的科学计数法形式
%c 打印单个字符的ASCII码
- 左对齐
+ 右对齐
# 显示8进制在前面加0,显示16进制在前面加0x

关系运算匹配

关系运算符 含义
~ 匹配正则表达式
!~ 不匹配正则表达式

字符串函数对照表

函数名 解释 函数返回值
length(str) 计算字符串长度 整数长度值
index(str1, str2) 在str1中查找str2的位置 返回值为位置索引,从1计数
tolower(str) 转换为小写 转换后的小写字符串
toupper(str) 转换为大写 转换后的大写字符串
substr(str,m,n) 从str的m个字符开始,截取n位 截取后的子串
split(str, arr, fs) 按fs切割字符串,结果保存arr 切割后的子串的个数
match(str, RE) 在str中按照RE查找, 返回位置 返回索引位置
sub(RE, RepStr, str) 在str中搜索符合RE的字串,将其替换为RepStr;只替换第一个 替换的个数
gsub(RE, RepStr, str) 在str中搜索符合RE的字串,将其替换为RepStr;替换所有 替换的个数

awk其他选项

选项 解释
-v 参数传递
-f 指定脚本文件
-F 指定分隔符
-V 查看awk的版本号

测试脚本

假如有文本test.txt如下:

1
2
3
4
5
root     pts/1   192.168.1.100  Tue Feb 10 11:21   still logged in
root pts/1 192.168.1.100 Tue Feb 10 00:46 - 02:28 (01:41)
root pts/1 192.168.1.100 Mon Feb 9 11:41 - 18:30 (06:48)
dmtsai pts/1 192.168.1.100 Mon Feb 9 11:41 - 11:41 (00:00)
root tty1 Fri Sep 5 14:09 - 14:10 (00:01)

展示第一列:

1
awk '{print $1}'

显示/etc/passwd的账户(以:分隔)

1
cat /etc/passwd | awk -F ':' '{print $1}'

如果显示账户和账户对应的shell

1
cat /etc/passwd | awk -F ':' '{print $1"\t"$7}'

显示列头列尾

1
cat /etc/passwd |awk  -F ':'  'BEGIN {print "name,shell"}  {print $1","$7} END {print "blue,/bin/nosh"}'

执行结果:

1
2
3
4
5
6
7
name,shell
root,/bin/bash
daemon,/bin/sh
bin,/bin/sh
sys,/bin/sh
....
blue,/bin/nosh

打印行号和列数

1
cat /etc/passwd |awk  -F ':'  'BEGIN {count=0;print "name\t\tshell"}  {count=count+1;print count":"$1"\t\t"$7} END {print "count=", count}'

打印nologin的用户展示行号和列数

1
cat /etc/passwd |awk  -F ':'  'BEGIN {count=0;print "name\t\tshell"} /nologin/ {count=count+1;print count":"$1"\t\t"$7} END {print "count=", count}'

shell中的sed

Posted on 2019-08-08 | In shell

sed简介

sed(Stream Editor), 流编辑器。对标准输出或文件逐行进行处理。

sed的语法格式

1
2
stdout|sed[option] "pattern command"
sed [option] "pattern command" file

sed参数列表

选项 含义 说明
-n quiet/silent 经过sed处理过的行才会被列出来
-e script 直接在命令行进行sed编辑,默认选项
-f script-file 直接运行script-file内的sed命令
-r redxp-extended 支持扩展正则表达式
-i 直接编辑源文件

sed中的编辑命令

类别 编辑命令 含义
查询 p 打印
增加 a 行后追加
增加 i 行前追加
增加 r 将文件内容追加到匹配行后面
增加 w 匹配行写入外部文件
删除 d 删除
修改 s/old/new 将行内第一个old替换为new
修改 s/old/new/g 将行内全部的old替换为new
修改 s/old/new/2g 将行内前2个old替换为new
修改 s/old/new/ig 将行内old全部替换为new, 忽略大小写

测试脚本

替换(c)

将第一行替换为shell

1
sed '1c shell' sed.txt

将第1-3行替换为java

1
sed '1,3c java' sed.txt

嵌入正则表达式, 用 //包住。替换所有包含win的行变为python

1
sed '/win/c python' sed.txt

删除(d)

删除第二行

1
sed '2d' sed.txt

删除最后一行($表示最后一行)

1
sed '$d' sed.txt

删除1-3行

1
sed '1, 3d' sed.txt

插入(i)

所有行之前插入#\

1
sed 'i##' sed.txt

1-3行之前插入0000

1
sed '1,3i0000' sed.txt

替换

替换文本中的每一行的第一个0 为 9

1
sed 's/0/9/' sed.txt

替换文本中的所有0 为 9

1
sed 's/0/9/g' sed.txt

shell中的grep

Posted on 2019-08-08 | In shell

grep 和 egrep

egrep 和 grep -E 等价, 支持扩展正则表达式

基本正则和扩展正则

扩展正则表达式与基础正则表达式的唯一区别在于: ? + () {} 这几个字符

基础正则表达式中,如果需要? + () {} 表达特殊含义, 需要将他们转义

扩展正则表达式中,如果需要? + () {} 不表达特殊含义,需要将他们转义

grep 语法格式

  • 第一种形式: grep[option][pattern][file1,file2…]

  • 第二种形式: command | grep [option][pattern]

常用参数

选项 含义
-v 不显示匹配行信息
-i 搜索时忽略大小写
-n 显示行号
-r 递归搜索
-E 支持扩展正则表达式
-F 不按正则表达式匹配,按照字符串字面意思匹配
-A 查看匹配行及匹配行后几行的信息
-B 查看匹配行及匹配行前几行的信息

shell中的find

Posted on 2019-08-08 | In shell

语法格式

1
find [路径] [选项] [操作]

选项参数对照表

选项 含义
-name 根据文件名查找
-iname 根据文件名查找(忽略大小写)
-perm 根据文件权限查找
-prune 该选项可以排除某些查找目录
-user 根据文件属主查找
-group 根据文件属组查找
-mtime -n \ +n 根据文件更改时间查找
-nogroup 查找无有效属组的文件
-nouser 查找无有效属主的文件
-newer file1 ! file2 查找更改时间比file1新但比file2旧的文件
-type 按文件类型查找
-size -n +n 按文件大小查找
-mindepth n 从n级子目录开始搜索
-maxdepth n 最多搜索到n级子目录

-type

类别 含义
f 文件
d 目录
c 字符设备文件
b 块设备文件
l 链接文件
p 管道文件

-size

类别 含义
+n 大小大于n
-n 大小小于n

-mtime

类别 含义
-time time天以内的文件
+time time天以外的文件

find locate whereis which的区别

find

在磁盘中查找对应文件

locate

文件查找命令,所属软件包mlocate

不同于find命令再整块磁盘中搜索,locate命令在数据库文件中查找

  1. 数据库文件一般每日更新,也可通过命令(updatedb)即时更新。
  2. 用户更新/var/lib/mlocate/mlocate.db
  3. 所使用配置文件/etc/updatedb.conf
  4. 该任务在后台cron计划任务中定期执行

find是默认全部匹配,locate则是默认部分匹配

whereis

只能查询二进制文件,帮助文档文件,源代码文件。

选项 含义
-b 只返回二进制文件
-m 只返回帮助文档文件
-s 只返回源代码文件

which

仅查找二进制程序文件

12345

Sean

46 posts
10 categories
18 tags
© 2019 Sean
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4