Java内存模型笔记

题记:
    看到C/C++写的内存池,不免了解下。同时学习下Java的Memory Model,学习和理解基于http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#jsr133

 

关于Java Memory Model

What is a memory model, anyway?

什么是内存模型?
In multiprocessor systems, processors generally have one or more layers of memory cache, which improves performance both by speeding access to data (because the data is closer to the processor) and reducing traffic on the shared memory bus (because many memory operations can be satisfied by local caches.)

At the processor level, a memory model defines necessary and sufficient conditions for knowing that writes to memory by other processors are visible to the current processor, and writes by the current processor are visible to other processors. Some processors exhibit a strong memory model, where all processors see exactly the same value for any given memory location at all times. Other processors exhibit a weaker memory model, where special instructions, called memory barriers, are required to flush or invalidate the local processor cache in order to see writes made by other processors or make writes by this processor visible to others. These memory barriers are usually performed when lock and unlock actions are taken; they are invisible to programmers in a high level language.

多核处理器通常都有1,2级或更多缓存,在处理器级别,内存模型通常规定了某处理器在写内存的时候需要对其他处理器都相互可见。一些处理器被设计成strong memory model,能让所有处理器在任何时候看到某内存里的值都是一样的。而有些处理器被设计成weawk memory model,拥有一个memory barrier的特殊结构,后面的没看明白。不管什么设计对于Programmer来说是透明的。

 

Recent trends in processor design have encouraged weaker memory models, because the relaxations they make for cache consistency allow for greater scalability across multiple processors and larger amounts of memory.

而“弱内存模型”在多处理器中,更能带来更好的可测试性和更多的内存。

 

The Java Memory Model describes what behaviors are legal in multithreaded code, and how threads may interact through memory. It describes the relationship between variables in a program and the low-level details of storing and retrieving them to and from memory or registers in a real computer system. It does this in a way that can be implemented correctly using a wide variety of hardware and a wide variety of compiler optimizations.

JAVA内存模型描述多线程操作内存的行为。描述了程序中的变量在内存中如何存储和获取的,或者在计算机中如何注册的。Java内存模型需要在各种硬件和编译器优化中正确实现这些东西。

 

Do other languages, like C++, have a memory model?

Most other programming languages, such as C and C++, were not designed with direct support for multithreading. The protections that these languages offer against the kinds of reorderings that take place in compilers and architectures are heavily dependent on the guarantees provided by the threading libraries used (such as pthreads), the compiler used, and the platform on which the code is run.

C/C++本身貌似不直接支持多线程,而是靠一些lib,比如pthreads。

The Java Memory Model was an ambitious undertaking; it was the first time that a programming language specification attempted to incorporate a memory model which could provide consistent semantics for concurrency across a variety of architectures.

 

What is meant by reordering?

There are a number of cases in which accesses to program variables (object instance fields, class static fields, and array elements) may appear to execute in a different order than was specified by the program. The compiler is free to take liberties with the ordering of instructions in the name of optimization. Processors may execute instructions out of order under certain circumstances. Data may be moved between registers, processor caches, and main memory in different order than specified by the program.For example, if a thread writes to field a and then to field b , and the value of b does not depend on the value of a , then the compiler is free to reorder these operations, and the cache is free to flush b to main memory before a .Most of the time, one thread doesn’t care what the other is doing. But when it does, that’s what synchronization is for.

大概意思是说代码的运行在程序里和寄存器、CPU缓存、主内存里是不一样的。这里举了个例子,比如一个写线程先写变量a,然后写变量b,a和b相互没有依赖的话,那么在缓存里,b可能会先于a写入主存中。大多数情况,单线程是不用关心这种顺序,但多线程的话,就需要用synchronize来保证顺序了。

 

What was wrong with the old memory model?

Nothing in the old memory model treated final fields differently from any other fiel.it was possible for a thread to see the default value of the field, and then at some later time see its constructed value.

在旧内存模型中,final变量和其他变量一样。比如String那种变量,这样会导致某一线程对于同一个final变量读取到不同的两个值。

 

 

What does synchronization do?

Synchronization ensures that memory writes by a thread before or during a synchronized block are made visible in a predictable manner to other threads which synchronize on the same monitor.

In the new memory model any memory operations which were visible to a thread before exiting a synchronized block are visible to any thread after it enters a synchronized block protected by the same monitor, since all the memory operations happen before the release, and the release happens before the acquire.

同步就是确保在写内存的时候只允许一个thread操作,而其他thread等待,当第一个thread写完从block出来后,下个线程才允许进入并查看修改后的内存,但需要第一个thread退出synchronization block后才可见修改后的值。而新模型中,第一个线程在block中发生写内存操作,其他threads就可以看到,而不需要等待当前thread退出block的时候。

 

How can final fields appear to change their values?

String s1 = "/usr/tmp";
String s2 = s1.substring(4); 

 

在老模型中,某线程是可以看到s2的offset刚开始为0,后来就变成4了。

 

How do final fields work under the new JMM?

The values for an object’s final fields are set in its constructor. Assuming the object is constructed “correctly”.In other words, do not place a reference to the object being constructed anywhere where another thread might be able to see it; do not assign it to a static field, do not register it as a listener with any other object, and so on. These tasks should be done after the constructor completes, not in the constructor.

能避免基本变量类型final型不出错的方法是:使用正确的构造函数,像下面这样

class FinalFieldExample {
  final int x;
  int y;
  static FinalFieldExample f;
  public FinalFieldExample() {
    x = 3;
    y = 4;
  }

  static void writer() {
    f = new FinalFieldExample();
  }

  static void reader() {
    if (f != null) {
      int i = f.x;
      int j = f.y;
    }
  }
}

bad construct

public FinalFieldExample() { // bad!
  x = 3;
  y = 4;
  // bad construction - allowing this to escape
  global.obj = this;
}

 

 

 

then threads that read the reference to this from global.obj are not guaranteed to see 3 for x . 

Now, having said all of this, if, after a thread constructs an immutable object (that is, an object that only contains final fields), you want to ensure that it is seen correctly by all of the other thread, you still typically need to use synchronization. There is no other way to ensure, for example, that the reference to the immutable object will be seen by the second thread. The guarantees the program gets from final fields should be carefully tempered with a deep and careful understanding of how concurrency is managed in your code.

上面举了2种构造方法的书写,说明了如何写对于final更安全的写法。甚至你还可以使用synchronization来保证final数据的安全。

 

What does volatile do?

     Volatile fields are special fields which are used for communicating state between threads. Each read of a volatile will see the last write to that volatile by any thread.They must also ensure that after they are written, they are flushed out of the cache to main memory, so they can immediately become visible to other threads. Similarly, before a volatile field is read, the cache must be invalidated so that the value in main memory, not the local processor cache, is the one seen.

    
任何读取volatile的变量的只会从主内存里读取,而非缓存的值,任何写volatile变量后会立刻flush到主内存,这样保证所有的线程读的都是同一个值。

Here is a simple example of how volatile fields can be used:

class VolatileExample {
  int x = 0;
  volatile boolean v = false;
  public void writer() {
    x = 42;
    v = true;
  }

  public void reader() {
    if (v == true) {
      //uses x - guaranteed to see 42.
    }
  }
}

 

 

Assume that one thread is calling writer , and another is calling reader . The write to v in writer releases the write to x to memory, and the read of v acquires that value from memory. Thus, if the reader sees the value true for v, it is also guaranteed to see the write to 42 that happened before it. This would not have been true under the old memory model.  If v were not volatile, then the compiler could reorder the writes in writer , and reader ‘s read of x might see 0.

这个例子说明了,如果v变量被写入主存了,那么x变量肯定为42,而这个在旧模型中是不成立的,是不保证顺序的。

 

Does the new memory model fix the “double-checked locking” problem?

In very early JVMs, synchronization was slow, and developers were eager to remove it — perhaps too eager. The double-checked locking idiom looks like this:

为了尽量减少synchronization带来的开销,出现了double-check的方法

// double-checked-locking - don't do this!

private static Something instance = null;

public Something getInstance() {
  if (instance == null) {
    synchronized (this) {
      if (instance == null)
        instance = new Something();
    }
  }
  return instance;
}

 This looks awfully clever — the synchronization is avoided on the common code path. There’s only one problem with it — it doesn’t work. Why not? The most obvious reason is that the writes which initialize instance and the write to the instance field can be reordered by the compiler or the cache, which would have the effect of returning what appears to be a partially constructed Something. The result would be that we read an uninitialized object. There is no way to fix it using the old Java memory model. More in-depth information can be found at Double-checked locking: Clever, but broken and The “Double Checked Locking is broken” declaration

JSR133说这样做是愚蠢的,原因是初始化instance变量和执行getInstance()方法的顺序是能被编译器或缓存打乱的,在旧模型中,是没有办法避免这个问题。原因没看明白。

 

Many people assumed that the use of the volatile keyword would eliminate the problems that arise when trying to use the double-checked-locking pattern. In JVMs prior to 1.5, volatile would not ensure that it worked (your mileage may vary). Under the new memory model, making the instance field volatile will “fix” the problems with double-checked locking, because then there will be a happens-before relationship between the initialization of the Something by the constructing thread and the return of its value by the thread that reads it.

Instead, use the Initialization On Demand Holder idiom, which is thread-safe and a lot easier to understand:

很多人认为volatile可以解决这个问题,在旧模型中不行,在新模型中”可以“,没看明白。

最好的线程安全方式如下:

 

private static class LazySomethingHolder {
  public static Something something = new Something();
}

public static Something getInstance() {
  return LazySomethingHolder.something;
}

 

 

 

 

 

 

BTW:
    OS默认就进行内存管理,只是他的scale范围是整个OS,需要管理整个OS的内存情况,有自己的内存空闲表,但如果应用程序频繁申请和释放内存的话,使用OS自身的复杂的内存管理开销相对较大,因为OS的可用内存可能零散的分布在整个内存区域里而不连续,所以很多应用会自己来管理内存来提高效率,办法是从OS那边开出一个适当大小的内存出来,自己应用所有的new和delete都在这块区域上做,自己管理显然提高了代码的复杂度,也会产生很多Bug。

    JVM也是开出一大片内存,用-Xmx来指定,自己管理内存申请和释放。因为JVM内存存在3大区域,其中young generation用 -Xmn指定,n=new,做了个小试验:

《Java内存模型笔记》

在写这个的时候,想了一个很傻的问题,如果我把-Xmx=1024m -Xmn=1m,运行个很大的app,app会不会outmemory,后来想了下才知道自己很傻,显然不会,GC会启动,object入survivor区域,eden又有内存了。

 

写内存池貌似需要考虑很多问题,这是个非常容易出现crash的地方。

内存池大小

池不够,需要增加内存池

需要对象大小是否要split

是否需要给每个对象加head

内存池锁问题

共享内存

池中寻找空闲内存的算法

平台相关性

 

 

 

 

 

    原文作者:java内存模型
    原文地址: https://blog.csdn.net/iteye_1465/article/details/82044438
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞