6 ms·
Can you please elaborate?
by shanxS 8y ago
Can you please elaborate?
- pkulak 8y agoIt's just an object that lives on the stack, not the heap. In lots of languages when you create an object, it's pretty explicit where you're putting it. When Java was created, they wanted to get rid of that complexity, so primitives always go on the stack and objects always* go on the heap. * I think Java has escape analysis, which means that if it can determine that an object you created doesn't leave its context, it will go on the stack and won't add work for the garbage collector.
- chrisseaton 8y agoYou mention it in your footnote, but objects don't necessarily live on the heap - they can also live on the stack, or in registers, or in vector pipelines, or not at all. Also an interesting aside is that objects are never 'allocated on the stack'. As in you won't find the same layout of memory on the stack as you do on the heap. Instead objects are turned into data edges in the compiler's graph. It's much more abstract than literally doing an alloca.
- pgwhalen 8y agoFor further details on how objects are not exactly allocated on the stack: https://shipilev.net/jvm-anatomy-park/18-scalar-replacement/ https://shipilev.net/jvm-anatomy-park/18-scalar-replacement/
- electrum 8y agoIt's more about the memory layout than where the objects live. For example, consider a Point class with (x, y) members. If you have an array of these, each element in the array will be a reference/pointer to a separately allocated Point instance which can be anywhere in the heap. Accessing the array is expensive due to lack of locality (arbitrary memory access). Additionally, each instance has substantial object overhead (typically 8 or 16 bytes). There are various ways to deal with this. One way is to have separate arrays for x and y. Another way is to use a byte array and use Unsafe to read/write the integers. This is terrible from a developer standpoint and is only acceptable if it is a small part of your application. Value types solve this by giving you an efficient memory layout while still allowing you to code against them as normal. The slogan is "codes like a class, works like an int".
- hermitdev 8y agoValue types also give you more control over cache locality. Not having to travel that extra pointer redirection can really add up depending on what you're doing.
- namibj 8y agoThere is at least half a magnitude in speed benefit from a cache-line-size/alignment tuned B-tree compared to e.g. a red/black tree or similar, if used to 'only' store pointer-size value types, and even more if the value type takes up even less space. A case would be e.g. a float/int pair ordered by floats and used as a priority queue or such. That's an 8-byte-large value type. Though, it might, for that case, need to be doubled up if there is a uniqueness constraint on the integers associated with the floats. LLC cache pressure can be very real. I highly recommend perf stat -dd and perf top -g -e uops_executed.stall_cycles as well as the -e cycle_activity.stalls_ldm_pending. The former counts how long stalls happen, including those where instructions like DIV just take long to execute, the second counts stall events, i.e. one stall per load/store, not weighted for how long this stall event actually stalled the core. I'm pretty sure the former ignores cases where just one hyperthread stalls, but I'm not as sure for the latter. On Haswell and newer, unless using branch taken/not-taken profiling, I recommend setting perf config --system call-graph.record-mode=lbr before using perf top -g and perf record -g, as -fomit-frame-pointer binaries cause issues otherwise.
- blt 8y agoMoreover, they can be used in composite objects without pointer chasing. Suppose class A is a 32-byte value type. Then class B { A a1; A a2; } is one continuous 64-byte block of memory. Without value types, each A will be allocated separately on the heap, and B will be a pair of pointers. Lack of value types makes it hard to get cache efficient memory layouts in Java.
- trhway 8y agoOf course if/when a1 gets also referenced from outside while you may not need B any more that is then the things become more interesting.
- londons_explore 8y agoDepends how they implement value types. They might simply say 'you can't have a reference to a value type'. You either embed it in your class, or you copy it's value.
- slavapestov 8y ago> It's just an object that lives on the stack, not the heap. That's not quite correct. A reference type might contain a value type field, in which case the value type will still live on the heap. Conversely, a local variable's type might be a value type, and _still_ involve a heap allocation. For example, std::vector in C++ is a value type but the array storage is heap-allocated. So it's really about semantics, and not memory layout. Accessing an lvalue with a value type produces a copied rvalue; with reference types you're only copying the reference itself.
- hermitdev 8y ago> For example, std::vector in C++ is a value type but the array storage is heap-allocated. This isn't correct. Firstly, C++ doesn't have value or reference types. Where any object is stored depends purely on how it is allocated. Either in the free store (dynamic) or automatic storage (the stack). std::vector has a constant object size - typically the size of 3 pointers (but that depends on the implementation - the standard doesn't specify): the start of the memory (dynamically allocated for storage), end/capacity (typically either another pointer or a size_t of the current capacity), and back (typically either a pointer just past the last element used or a size_t + 1 of current size). It's storage is dynamically allocated, though, and for a std::vector<T> will have dynamically allocated sizeof(T) * capacity bytes (maybe more due to alignment issues). std::array is constexpr safe and does not (directly) dynamically allocate memory - size has to be known at compile time (hence it's a template argument). For instance, on my compiler (GCC 4.8.4), for this example program: #include <iostream> #include <vector> #include <array> int main() { std::vector<int> v_ten(0, 10); std::vector<int> v_twenty(0, 20); std::array<int, 10> a_ten{0}; std::array<int, 20> a_twenty{0}; std::cout << "sizeof(v_ten): " << sizeof(v_ten) << std::endl; std::cout << "sizeof(v_twenty): " << sizeof(v_twenty) << std::endl; std::cout << "sizeof(a_ten): " << sizeof(a_ten) << std::endl; std::cout << "sizeof(a_twenty): " << sizeof(a_twenty) << std::endl; return 0; } produces this output: sizeof(v_ten): 24 sizeof(v_twenty): 24 sizeof(a_ten): 40 sizeof(a_twenty): 80 edit: added comment about alignment.
- dbaupp 8y ago
- stcredzero 8y agoWhen Java was created, they wanted to get rid of that complexity, so primitives always go on the stack and objects always go on the heap.* This was inherited from Smalltalk. Everything was an "object reference," with tag bits and all, but SmallInteger was optimized by using the low 16 bits of the object reference pointer to store the SmallInteger. So you can think of Smalltalk as having SmallInteger as a "primitive" type with pass by value.