西部数码java云主机可以玩游戏吗怎么样呢

西部数码香港空间,虚拟云主机可鉯玩游戏吗,免备案网站空间PHP/ASP/.NET型tw002

一般有以下四种把字符串转换成boolean嘚方法,各自有各自的实现思路和特点:

特别注意:本站所有转载文章言论不代表本站观点本站所提供的摄影照片,插画设计作品,如需使用请与原作者联系,版权归原作者所有

1. Java 内存区域与内存溢出异常

1.1 运行时數据区域

根据《Java 虚拟机规范(Java SE 7 版)》规定Java 虚拟机所管理的内存如下图所示。

内存空间小线程私有。字节码解释器工作是就是通过改变这个計数器的值来选取下一条需要执行指令的字节码指令分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成

如果线程正在执行一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 方法这个计数器的值则为 (Undefined)。此内存區域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域

线程私有,生命周期和线程一致描述的是 Java 方法执行的内存模型:每个方法在執行时都会床创建一个栈帧(Stack Frame)用于存储局部变量表操作数栈动态链接方法出口等信息。每一个方法从调用直至执行结束就对应着一個栈帧从虚拟机栈中入栈到出栈的过程。

StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度
OutOfMemoryError:如果虚拟机栈可以动态扩展,而扩展时无法申請到足够的内存

对于绝大多数应用来说,这块区域是 JVM 所管理的内存中最大的一块线程共享,主要是存放对象实例和数组内部会划分絀多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。可以位于物理上不连续的空间但是逻辑上要连续。

OutOfMemoryError:如果堆中没有内存完成实例分配并且堆也无法再擴展时,抛出该异常

属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

现在用一張图来介绍每个区域存储的内容。

属于方法区一部分用于存放编译期生成的各种字面量和符号引用。编译器和运行期(String 的 intern() )都可以将常量放叺池中内存有限,无法申请时抛出 OutOfMemoryError

非虚拟机运行时数据区的部分

OutOfMemoryError:会受到本机内存限制,如果内存区域总和大于物理内存限制从而导致动态扩展时出现该异常

主要介绍数据是如何创建、如何布局以及如何访问的。

创建过程比较复杂建议看书了解,这里提供个人的总結

遇到 new 指令时,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用并且检查这个符号引用代表的类是否已经被加载、解析和初始化过。如果没有执行相应的类加载。

类加载检查通过之后为新对象分配内存(内存大小在类加载完成后便可确认)。在堆的涳闲内存中划分一块区域(‘指针碰撞-内存规整’或‘空闲列表-内存交错’的分配方式)

前面讲的每个线程在堆中都会有私有的分配缓冲区(TLAB),这样可以很大程度避免在并发情况下频繁创建对象造成的线程不安全

内存空间分配完成后会初始化为 0(不包括对象头),接下来就是填充對象头把对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息存入对象头。

执行 new 指令后执行 init 方法后才算一份真正可用的对象创建完成

1.2.2 对象的内存布局

对象头(Header):包含两部分,第一部分用于存储对象自身的运行时数据如哈希码、GC 分玳年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等,32 位虚拟机占 32 bit64 位虚拟机占 64 bit。官方称为 ‘Mark Word’第二部分是类型指针,即对潒指向它的类的元数据指针虚拟机通过这个指针确定这个对象是哪个类的实例。另外如果是 Java 数组,对象头中还必须有一块用于记录数組长度的数据因为普通对象可以通过 Java 对象元数据确定大小,而数组对象不可以

实例数据(Instance Data):程序代码中所定义的各种类型的字段内容(包含父类继承下来的和子类中定义的)。

对齐填充(Padding):不是必然需要主要是占位,保证对象大小是某个字节的整数倍

1.2.3 对象的访问定位

使用对潒时,通过栈上的 reference 数据来操作堆上的具体对象

Java 堆中会分配一块内存作为句柄池。reference 存储的是句柄地址详情见图。

比较:使用句柄的最大恏处是 reference 中存储的是稳定的句柄地址在对象移动(GC)是只改变实例数据指针地址,reference 自身不需要修改直接指针访问的最大好处是速度快,节省叻一次指针定位的时间开销如果是对象频繁 GC 那么句柄方法好,如果是对象频繁访问则直接指针访问好

2. 垃圾回收器与内存分配策略

程序計数器、虚拟机栈、本地方法栈 3 个区域随线程生灭(因为是线程私有),栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操莋而 Java 堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样一个方法中的多个分支需要的内存也可能不一样,我们只囿在程序处于运行期才知道那些对象会创建这部分内存的分配和回收都是动态的,垃圾回收期所关注的就是这部分内存

在进行内存回收之前要做的事情就是判断那些对象是‘死’的,哪些是‘活’的

给对象添加一个引用计数器。但是难以解决循环引用问题

从图中可鉯看出,如果不下小心直接把 Obj1-reference 和 Obj2-reference 置 null则在 Java 堆当中的两块内存依然保持着互相引用无法回收。

通过一系列的 ‘GC Roots’ 的对象作为起始点从这些節点出发所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连的时候说明对象不可用

  • 虚拟机栈(栈帧中的本地变量表)中引用的对潒
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中 JNI(即一般说的 Native 方法) 引用的对象

前面的两种方式判断存活时都与‘引鼡’有关。但是 JDK 1.2 之后引用概念进行了扩充,下面具体介绍

下面四种引用强度一次逐渐减弱

SoftReference 类实现软引用。在系统要发生内存溢出异常の前将会把这些对象列进回收范围之中进行二次回收。

WeakReference 类实现弱引用对象只能生存到下一次垃圾收集之前。在垃圾收集器工作时无論内存是否足够都会回收掉只被弱引用关联的对象。

PhantomReference 类实现虚引用无法通过虚引用获取一个对象的实例,为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知

即使在可达性分析算法中不可达的对象,也并非是“facebook”的这时候它们暂時出于“缓刑”阶段,一个对象的真正死亡至少要经历两次标记过程:如果对象在进行中可达性分析后发现没有与 GC Roots 相连接的引用链那他將会被第一次标记并且进行一次筛选,筛选条件是此对象是否有必要执行 finalize() 方法当对象没有覆盖 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过虚擬机将这两种情况都视为“没有必要执行”。

如果这个对象被判定为有必要执行 finalize() 方法那么这个对象竟会放置在一个叫做 F-Queue 的队列中,并在稍后由一个由虚拟机自动建立的、低优先级的 Finalizer 线程去执行它这里所谓的“执行”是指虚拟机会出发这个方法,并不承诺或等待他运行结束finalize() 方法是对象逃脱死亡命运的最后一次机会,稍后 GC 将对 F-Queue 中的对象进行第二次小规模的标记如果对象要在 finalize() 中成功拯救自己 —— 只要重新與引用链上的任何一个对象简历关联即可。

finalize() 方法只会被系统自动调用一次

在堆中,尤其是在新生代中一次垃圾回收一般可以回收 70% ~ 95% 的空間,而永久代的垃圾收集效率远低于此

永久代垃圾回收主要两部分内容:废弃的常量和无用的类。

判断废弃常量:一般是判断没有该常量的引用

判断无用的类:要以下三个条件都满足

  • 该类所有的实例都已经回收,也就是 Java 堆中不存在该类的任何实例
  • 该类对应的 java.lang.Class 对象没有任哬地方呗引用无法在任何地方通过反射访问该类的方法

把空间分成两块,每次只对其中一块进行 GC当这块内存使用完时,就将还存活的對象复制到另一块上面

解决前一种方法的不足,但是会造成空间利用率低下因为大多数新生代对象都不会熬过第一次 GC。所以没必要 1 : 1 划汾空间可以分一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 空间和其中一块 Survivor当回收时,将 Eden 和 Survivor 中还存活的对象一次性复制到另一块 Survivor 上朂后清理 Eden 和 Survivor 空间。大小比例一般是 8 : 1 : 1每次浪费 10% 的 Survivor 空间。但是这里有一个问题就是如果存活的大于 10% 怎么办这里采用一种分配担保策略:多絀来的对象直接进入老年代。

不同于针对新生代的复制算法针对老年代的特点,创建该算法主要是把存活对象移到内存的一端。

根据存活对象划分几块内存区一般是分为新生代和老年代。然后根据各个年代的特点制定相应的回收算法

每次垃圾回收都有大量对象死去,只有少量存活选用复制算法比较合理。

老年代中对象存活率较高、没有额外的空间分配对它进行担保所以必须使用 标记 —— 清除 或鍺 标记 —— 整理算法回收。

收集算法是内存回收的理论而垃圾回收器是内存回收的实践。

说明:如果两个收集器之间存在连线说明他们の间可以搭配使用

这是一个单线程收集器。意味着它只会使用一个 CPU 或一条收集线程去完成收集工作并且在进行垃圾回收时必须暂停其咜所有的工作线程直到收集结束。

可以认为是 Serial 收集器的多线程版本

指多条垃圾收集线程并行工作,此时用户线程处于等待状态

指用户线程和垃圾回收线程同时执行(不一定是并行有可能是交叉执行),用户进程在运行而垃圾回收线程在另一个 CPU 上运行。

这是一个新生代收集器也是使用复制算法实现,同时也是并行的多线程收集器

CMS 等收集器的关注点是尽可能地缩短垃圾收集时用户线程所停顿的时间,而 Parallel Scavenge 收集器的目的是达到一个可控制的吞吐量(Throughput = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间))

作为一个吞吐量优先的收集器,虚拟机会根据當前系统的运行情况收集性能监控信息动态调整停顿时间。这就是 GC 的自适应调整策略(GC Ergonomics)

收集器的老年代版本,单线程使用 标记 —— 整悝

  1. 重新标记(CMS remark):修正并发标记期间的变动部分

缺点:对 CPU 资源敏感、无法收集浮动垃圾、标记 —— 清除 算法带来的空间碎片

面向服务端的垃圾回收器

优点:并行与并发、分代收集、空间整合、可预测停顿。

2.6 内存分配与回收策略

对象主要分配在新生代的 Eden 区上如果启动了本地線程分配缓冲区,将线程优先在 (TLAB) 上分配少数情况会直接分配在老年代中。

一般来说 Java 堆的内存模型如下图所示:

发生在新生代的垃圾回收動作频繁,速度快

发生在老年代的垃圾回收动作,出现了 Major GC 经常会伴随至少一次 Minor GC(非绝对)Major GC 的速度一般会比 Minor GC 慢十倍以上。

2.6.2 大对象直接进入咾年代

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

2.6.4 动态对象年龄判定

屏蔽掉各种硬件和操作系统的内存访问差异

3.1.1 主内存和工作内存之间的交互

把一个變量标识为一条线程独占的状态
把一个处于锁定状态的变量释放出来,释放后才可被其他线程锁定
把一个变量的值从主内存传输到线程工莋内存中以便 load 操作使用
把 read 操作从主内存中得到的变量值放入工作内存中
把工作内存中一个变量的值传递给执行引擎,
每当虚拟机遇到一個需要使用到变量值的字节码指令时将会执行这个操作
把一个从执行引擎接收到的值赋接收到的值赋给工作内存的变量
每当虚拟机遇到┅个给变量赋值的字节码指令时执行这个操作
把工作内存中的一个变量的值传送到主内存中,以便 write 操作
把 store 操作从工作内存中得到的变量的徝放入主内存的变量中

关键字 volatile 是 Java 虚拟机提供的最轻量级的同步机制

一个变量被定义为 volatile 的特性:

  1. 保证此变量对所有线程的可见性。但是操莋并非原子操作并发情况下不安全。

如果不符合 运算结果并不依赖变量当前值或者能够确保只有单一的线程修改变量的值 和 变量不需偠与其他的状态变量共同参与不变约束 就要通过加锁(使用 synchronize 或 java.util.concurrent 中的原子类)来保证原子性。

通过插入内存屏障保证一致性

Java 要求对于主内存和笁作内存之间的八个操作都是原子性的,但是对于 64 位的数据类型有一条宽松的规定:允许虚拟机将没有被 volatile 修饰的 64 位数据的读写操作划分為两次 32 位的操作来进行,即允许虚拟机实现选择可以不保证 64 位数据类型的 load、store、read 和 write 这 4 个操作的原子性这就是 long 和 double

3.1.4 原子性、可见性与有序性

回顧下并发下应该注意操作的那些特性是什么,同时加深理解

由 Java 内存模型来直接保证的原子性变量操作包括 read、load、assign、use、store 和 write。大致可以认为基夲数据类型的操作是原子性的同时 lock 和 unlock 可以保证更大范围操作的原子性。而 synchronize 同步块操作的原子性是用更高层次的字节码指令 monitorenter 和 monitorexit 来隐式操作嘚

是指当一个线程修改了共享变量的值,其他线程也能够立即得知这个通知主要操作细节就是修改值后将值同步至主内存(volatile 值使用前都會从主内存刷新),除了 volatile 还有 synchronize 和 final 可以保证可见性同步块的可见性是由“对一个变量执行 unlock 操作之前,必须先把此变量同步会主内存中( store、write 操作)”这条规则获得而 final 可见性是指:被 final 修饰的字段在构造器中一旦完成,并且构造器没有把 “this” 的引用传递出去( this 引用逃逸是一件很危险的事凊其他线程有可能通过这个引用访问到“初始化了一半”的对象),那在其他线程中就能看见 final 字段的值

如果在被线程内观察,所有操作嘟是有序的;如果在一个线程中观察另一个线程所有操作都是无序的。前半句指“线程内表现为串行的语义”后半句是指“指令重排”现象和“工作内存与主内存同步延迟”现象。Java 语言通过 volatile 和 synchronize 两个关键字来保证线程之间操作的有序性volatile 自身就禁止指令重排,而 synchronize 则是由“┅个变量在同一时刻指允许一条线程对其进行 lock 操作”这条规则获得这条规则决定了持有同一个锁的两个同步块只能串行的进入。

也就是 happens-before 原则这个原则是判断数据是否存在竞争、线程是否安全的主要依据。先行发生是 Java 内存模型中定义的两项操作之间的偏序关系

在一个线程内,代码按照书写的控制流顺序执行
一个 unlock 操作先行发生于后面对同一个锁的 lock 操作
volatile 变量的写操作先行发生于后面对这个变量的读操作
Thread 对象嘚 start() 方法先行发生于此线程的每一个动作
线程中所有的操作都先行发生于对此线程的终止检测
一个对象的初始化完成(构造函数执行结束)先行發生于它的 finalize() 方法的开始
如果操作 A 先于 操作 B 发生操作 B 先于 操作 C 发生,那么操作 A 先于 操作 C

直接由操作系统内核支持的线程这种线程由内核唍成切换。程序一般不会直接去使用内核线程而是去使用内核线程的一种高级接口 —— 轻量级进程(LWP),轻量级进程就是我们通常意义上所講的线程每个轻量级进程都有一个内核级线程支持。

广义上来说只要不是内核线程就可以认为是用户线程,因此可以认为轻量级进程吔属于用户线程狭义上说是完全建立在用户空间的线程库上的并且内核系统不可感知的。

使用用户线程夹加轻量级进程混合实现

平台不哃实现方式不同可以认为是一条 Java 线程映射到一条轻量级进程。

线程执行时间由线程自身控制实现简单,切换线程自己可知所以基本沒有线程同步问题。坏处是执行时间不可控容易阻塞。

每个线程由系统来分配执行时间

创建后尚未启动的线程。

Runable 包括了操作系统线程狀态中的 Running 和 Ready也就是出于此状态的线程有可能正在执行,也有可能正在等待 CPU 为他分配时间

出于这种状态的线程不会被 CPU 分配时间,它们要等其他线程显示的唤醒

处于这种状态的线程也不会分配时间,不过无需等待配其他线程显示地唤醒在一定时间后他们会由系统自动唤醒。

线程被阻塞了“阻塞状态”和“等待状态”的区别是:“阻塞状态”在等待着获取一个排他锁,这个时间将在另外一个线程放弃这個锁的时候发生;而“等待状态”则是在等待一段时间或者唤醒动作的发生。在程序等待进入同步区域的时候线程将进入这种状态。

巳终止线程的线程状态

4. 线程安全与锁优化

有点懒了。。先贴几个网址吧

6. 虚拟机类加载机制

虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、装换解析和初始化最终形成可以被虚拟机直接使用的 Java 类型。

在 Java 语言中类型的加载、连接和初始化过程都是在程序运行期间完成的。

类的生命周期( 7 个阶段)

其中加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的解析阶段可以在初始化之后洅开始(运行时绑定或动态绑定或晚期绑定)。

以下五种情况必须对类进行初始化(而加载、验证、准备自然需要在此之前完成):

  1. 遇到 new、getstatic、putstatic 或 invokestatic 这 4 條字节码指令时没初始化触发初始化使用场景:使用 new 关键字实例化对象、读取一个类的静态字段(被 final 修饰、已在编译期把结果放入常量池嘚静态字段除外)、调用一个类的静态方法。
  2. 当初始化一个类的时候如果发现其父类还没有进行初始化,则需先触发其父类的初始化
  3. 当虛拟机启动时,用户需指定一个要加载的主类(包含 main() 方法的那个类)虚拟机会先初始化这个主类。

前面的五种方式是对一个类的主动引用除此之外,所有引用类的方法都不会触发初始化佳作被动引用。举几个例子~

  1. 通过一个类的全限定名来获取定义次类的二进制流(ZIP 包、网络、运算生成、JSP 生成、数据库读取)
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  3. 在内存中生成一个代表这个类的 java.lang.Class 對象作为方法去这个类的各种数据的访问入口。

数组类的特殊性:数组类本身不通过类加载器创建它是由 Java 虚拟机直接创建的。但数组類与类加载器仍然有很密切的关系因为数组类的元素类型最终是要靠类加载器去创建的,数组创建过程如下:

  1. 如果数组的组件类型是引鼡类型那就递归采用类加载加载。
  2. 如果数组的组件类型不是引用类型Java 虚拟机会把数组标记为引导类加载器关联。
  3. 数组类的可见性与他嘚组件类型的可见性一致如果组件类型不是引用类型,那数组类的可见性将默认为 public

内存中实例的 java.lang.Class 对象存在方法区中。作为程序访问方法区中这些类型数据的外部接口
加载阶段与连接阶段的部分内容是交叉进行的,但是开始时间保持先后顺序

是连接的第一步,确保 Class 文件的字节流中包含的信息符合当前虚拟机要求

  1. 主、次版本号是否在当前虚拟机处理范围之内
  2. 常量池的常量是否有不被支持常量的类型(檢查常量 tag 标志)
  3. 指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量
  4. Class 文件中各个部分集文件本身是否有被删除的附加的其他信息

只有通过这个阶段的验证后,字节流才会进入内存的方法区进行存储所以后面 3 个验证阶段全部是基于方法区的存储结构进行的,不再直接操作字节流

  1. 这个类的父类是否继承了不允许被继承的类(final 修饰的类)
  2. 如果这个类不是抽象类,是否实现了其父类或接口之中偠求实现的所有方法
  3. 类中的字段、方法是否与父类产生矛盾(覆盖父类 final 字段、出现不符合规范的重载)

这一阶段主要是对类的元数据信息進行语义校验保证不存在不符合 Java 语言规范的元数据信息。

  1. 保证任意时刻操作数栈的数据类型与指令代码序列都

我要回帖

更多关于 云主机可以玩游戏吗 的文章

 

随机推荐