网站开发项目职责,学做网站要学什么,搜索大全,如何让百度搜到我的网站1. 什么是JVM 我们都知道在 Windows 系统上一个软件包装包是 exe 后缀的#xff0c;而这个软件包在苹果的 Mac OSX 系统上是无法安装的。类似地#xff0c;Mac OSX 系统上软件安装包则是 dmg 后缀#xff0c;同样无法在 Windows 系统上安装。 Java 代码为什么可以在 Windows…1. 什么是JVM 我们都知道在 Windows 系统上一个软件包装包是 exe 后缀的而这个软件包在苹果的 Mac OSX 系统上是无法安装的。类似地Mac OSX 系统上软件安装包则是 dmg 后缀同样无法在 Windows 系统上安装。 Java 代码为什么可以在 Windows 系统运行也可以在 Linux 系统运行这就是 jvm 的功劳 Java 虚拟机可以理解为一个翻译官在 Linux 系统上将 Java 代码翻译成 Linux 机器码在 Windows 系统上将 Java 代码翻译成 Windows 机器码所以 Java 有了虚拟机之后可以让 Java 代码运行在不同的系统上。 2. JVM内存结构运行时数据区
运行时数据区是官方说法但很多时候这个名词并不是很形象再加上日积月累的习惯很多人都习惯用 JVM 内存结构这个说法 JVM 内存结构运行时数据区主要包括堆、栈虚拟机栈、本地方法栈、方法区、程序计数器等。 线程公有堆、方法区
线程私有栈、本地方法栈、程序计数器 2.1 堆公有
堆内存被所有线程共享。堆内存用于存放由 new 创建的对象和数组。 Java堆分为年轻代Young Generation和老年代Old Generation年轻代又分为乐园Eden和幸存区Survivor区幸存区又分为 From 区From Survivor 区和 To 区To Survivor 区。 而 JVM 垃圾回收机制主要收集堆中年轻代和老年代对象所占用的内存空间。 为什么默认的虚拟机配置Edenfrom to 8:1:1 ? 这是经过大量统计得出的结果发现 80% 的对象存活时间都很短于是将 Eden 区设置为年轻代的 80%这样可以减少内存空间的浪费提高内存空间利用率。 年轻代中的 Minor GC 1、绝大多数刚刚被创建的对象会存放在乐园Eden。
2、在乐园内存满时执行第一次GCMinor GC之后存活的对象被移动到其中一个幸存区Survivor。
3、此后每次乐园执行GC后存活的对象会被堆积在同一个幸存区。
4、当一个幸存区饱和还在存活的对象会被移动到另一个幸存区。然后会清空已经饱和的那个幸存区。
5、在以上步骤中重复N次N MaxTenuringThreshold年龄阀值设定默认15依然存活的对象就会被移动到老年代。 从上面的步骤可以发现两个幸存者空间必须有一个是保持空的。 需要重点记住的是对象在刚刚被创建之后是保存在乐园的Eden。那些长期存活的对象会经由幸存区Survivor转存到老年代Old generation。 也有例外出现对于一些比较大的对象需要分配一块比较大的连续内存空间则直接进入到老年代内存分配担保机制。 注意当年轻代满时就会触发Minor GC这里的年轻代满指的是Eden满Survivor满不会引发Minor GC。由于年轻代中的对象存活时间比较短Minor GC比较频繁GC 速度也很快。 老年代中的 Full GC 老年代空间的构成很简单它不像新生代空间那样划分为几个区域它只有一个区域里面存储的对象并不像新生代一样存活时间很短。这里的对象几乎都是从Survivor 区中熬过来的它们绝不会轻易的被回收掉。 注意Full GC 是清理整个堆空间包括年轻代和老年代如果Full GC之后堆中仍然无法存储对象就会抛出OutOfMemoryError异常。由于老年代内存不会轻易的被回收掉因此 Full GC 发生的次数不会有 Minor GC 那么频繁。但是老年代内存大做一次 Full GC 的时间比 Minor GC 要更长约10倍。 2.2 方法区公有
方法区被所有线程共享。方法区用于存放静态变量、常量、类信息版本、方法、字段等、常量池。可以看做是将类Class的元数据保存在方法区里。 Integer常量池 都知道数据类型 比较的是内存地址先看个下边的例子。 public static void main(String[] args)
{ Integer i1 66; Integer i2 66; Integer i3 150; Integer i4 150; System.out.println(i1 i2);//true System.out.println(i3 i4);//false
}
i1 i2 结果为 truei3 i4 结果为 false。由结果得知 i1 和 i2 的内存地址是相同的而 i3 和 i4 内存地址是不同的。 产生这样结果的原因是 Integer i1 66 实际上有一步装箱的操作通过 Integer 的 valueOf 方法将 int 型的 66 装箱成 Integer。下边是 Integer 中的 valudOf 方法。 public static Integer valueOf(int i) { if (i IntegerCache.low i IntegerCache.high) return IntegerCache.cache[i (-IntegerCache.low)]; return new Integer(i);
}
Integer 的 valueOf 方法很简单它判断变量是否在 IntegerCache 的最小值-128和最大值127之间如果在则返回常量池中的内容否则 new 一个 Integer 对象。 由于 66 在 -128 ~ 127 之间所以 66 装箱时使用的是常量池中的 66所以 结果为 true。
而 150 不在范围内在装箱时执行了 new Integer(150)所以返回的是新创建的对象所以 结果为 false。 String常量池 String 是由 final 修饰的类是不可以被继承的。通常有两种方式来创建对象。 // 1
String str new String(abc);
// 2
String str abc;
第一种使用 new 创建的对象存放在堆中每次 new 出来的内存地址都不同。
第二种先在常量池中找有没有 “abc”。有则直接取常量池内存地址赋值给 str。没有先在常量池创建“abc”再取内存地址赋值给 str。 通过代码验证上面理论。 public static void main(String[] args) { String s1 new String(abc); String s2 new String(abc); String s3 abc; String s4 abc; System.out.println(s1 s2);// false System.out.println(s3 s4);// true
}
s1 s2 为 false 原因str1 和 str2 使用 new 创建对象分别在堆上创建了不同的对象。两个引用指向堆中两个不同的对象所以为 false。
s3 s4 为 true 原因首先在栈上存放变量引用 s3然后去常量池中找是否有 abc没有则将 abc 存储在常量池中然后将 s3 指向常量池的 abc。当 s4 abc 时去常量池中发现已经有 abc 了就将 s4 引用指向常量池已有的 abc 。所以s3 s4指向同一个内存地址。
String 类中有一个方法 intern可以返回池中的字符串如下代码 public static void main(String[] args) { String s1 new String(abc); String s2 abc; System.out.println(s1 s2);// false System.out.println(s1.intern() s2);// true
}
上边的结果可以看下 intern() 方法注释就知道结果。当调用 intern 方法时如果常量池中已经该字符串则返回池中的字符串否则将此字符串添加到常量池中并返回字符串的引用。 2.3 栈私有
栈是后进先出的。栈是线程私有的他的生命周期与线程相同。每个线程都会分配一个栈的空间一个线程会对应一个栈。 栈存储什么 栈中存储的是栈帧。每个方法在执行时都会创建一个栈帧。栈帧中存储了局部变量表、操作数栈、动态连接和方法出口等信息。每个方法从调用到运行结束的过程就对应着一个栈帧在栈中压栈到出栈的过程。可以理解为栈帧就是线程所执行的方法。 使用递归时会导致 StackOverflowError 错误就是因为不断的在栈中创建栈帧当栈帧的数量超过了栈的大小时就会导致报错。 2.4 本地方法栈私有
本地方法栈是线程私有的主要为 JVM 使用到的 Native 方法服务。Native 方法不是以 Java 语言实现的而是以本地语言实现的比如 C 或 C。 可以理解为 Native 方法是与操作系统直接交互的比如通知垃圾收集器进行垃圾回收的代码 System.gc()获取常量池中的字符串引用 String.intern()都是使用 native 修饰的。 2.5 程序计数器私有
程序计数器是一个比较小的内存区域可能是CPU寄存器或者操作系统内存其主要用于指示当前线程所执行的字节码执行到了第几行可以理解为是当前线程的行号指示器。 字节码解释器在工作时会通过改变这个计数器的值来取下一条语句指令。 每个程序计数器只用来记录一个线程的行号所以它是线程私有一个线程就有一个程序计数器的。